Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: activate all feature flags related to custom model classes #7510

Merged
merged 9 commits into from
May 11, 2021
1 change: 1 addition & 0 deletions packages/-ember-data/ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = function (defaults) {
throwUnlessParallelizable: true,
},
'ember-cli-terser': {
enabled: false,
exclude: ['assets/dummy.js', 'assets/tests.js', 'assets/test-support.js', 'dist/docs/*', 'docs/*'],
},
});
Expand Down
10 changes: 5 additions & 5 deletions packages/canary-features/addon/default-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,12 @@
*/
export default {
SAMPLE_FEATURE_FLAG: null,
RECORD_DATA_ERRORS: null,
RECORD_DATA_STATE: null,
RECORD_DATA_ERRORS: true,
RECORD_DATA_STATE: true,
IDENTIFIERS: true,
REQUEST_SERVICE: null,
CUSTOM_MODEL_CLASS: null,
REQUEST_SERVICE: true,
CUSTOM_MODEL_CLASS: true,
FULL_LINKS_ON_RELATIONSHIPS: true,
RECORD_ARRAY_MANAGER_IDENTIFIERS: true,
REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT: null,
REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT: true,
};
8 changes: 7 additions & 1 deletion packages/store/addon/-private/system/ds-model-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { getShimClass } from './model/shim-model-class';
import normalizeModelName from './normalize-model-name';
import { DSModelSchemaDefinitionService, getModelFactory } from './schema-definition-service';

type NotificationType = import('./record-notification-manager').NotificationType;

type RelationshipsSchema = import('../ts-interfaces/record-data-schemas').RelationshipsSchema;
type SchemaDefinitionService = import('../ts-interfaces/schema-definition-service').SchemaDefinitionService;
type RecordDataRecordWrapper = import('../ts-interfaces/record-data-record-wrapper').RecordDataRecordWrapper;
Expand Down Expand Up @@ -51,7 +53,11 @@ class Store extends CoreStore {
delete createOptions.container;
let record = this._modelFactoryFor(modelName).create(createOptions);
//todo optimize
notificationManager.subscribe(identifier, (identifier, value) => notifyChanges(identifier, value, record, this));
notificationManager.subscribe(
identifier,
(identifier: StableRecordIdentifier, value: NotificationType, key?: string) =>
notifyChanges(identifier, value, key, record, this)
);
return record;
}

Expand Down
7 changes: 6 additions & 1 deletion packages/store/addon/-private/system/fetch-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,12 @@ export default class FetchManager {
snapshots[i] = new Snapshot(options, identifiers[i], this._store);
}

let groups: Snapshot[][] = adapter.groupRecordsForFindMany(this, snapshots);
let groups: Snapshot[][];
if (adapter.groupRecordsForFindMany) {
groups = adapter.groupRecordsForFindMany(this, snapshots);
} else {
groups = [snapshots];
}

for (let i = 0, l = groups.length; i < l; i++) {
this._processCoalescedGroup(seeking, groups[i], adapter, optionsMap, modelName);
Expand Down
14 changes: 9 additions & 5 deletions packages/store/addon/-private/system/model/internal-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ export default class InternalModel {
manyArrayRecordAdded(key: string) {
if (this.hasRecord) {
if (CUSTOM_MODEL_CLASS) {
this.store._notificationManager.notify(this.identifier, 'relationships');
this.store._notificationManager.notify(this.identifier, 'relationships', key);
} else {
this._record.notifyHasManyAdded(key);
}
Expand All @@ -952,7 +952,7 @@ export default class InternalModel {
notifyHasManyChange(key: string) {
if (this.hasRecord) {
if (CUSTOM_MODEL_CLASS) {
this.store._notificationManager.notify(this.identifier, 'relationships');
this.store._notificationManager.notify(this.identifier, 'relationships', key);
} else {
let manyArray = this._manyArrayCache[key] || this._retainedManyArrayCache[key];
if (manyArray) {
Expand All @@ -970,7 +970,7 @@ export default class InternalModel {
notifyBelongsToChange(key: string) {
if (this.hasRecord) {
if (CUSTOM_MODEL_CLASS) {
this.store._notificationManager.notify(this.identifier, 'relationships');
this.store._notificationManager.notify(this.identifier, 'relationships', key);
} else {
this._record.notifyBelongsToChange(key, this._record);
}
Expand All @@ -991,10 +991,14 @@ export default class InternalModel {
return didRemoveUnloadedModel;
}

notifyPropertyChange(key) {
notifyPropertyChange(key: string) {
if (this.hasRecord) {
if (CUSTOM_MODEL_CLASS) {
this.store._notificationManager.notify(this.identifier, 'property');
// TODO this should likely *mostly* be the `attributes` bucket
// but it seems for local mutations we rely on computed updating
// iteself when set. As we design our own thing we may need to change
// that.
this.store._notificationManager.notify(this.identifier, 'property', key);
} else {
this._record.notifyPropertyChange(key);
}
Expand Down
74 changes: 48 additions & 26 deletions packages/store/addon/-private/system/model/notify-changes.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,37 @@
import { cacheFor } from '@ember/object/internals';

type CoreStore = import('../core-store').default;

type NotificationType = import('../record-notification-manager').NotificationType;

type Store = import('../ds-model-store').default;
type DSModel = import('../../ts-interfaces/ds-model').DSModel;
type Model = InstanceType<typeof import('@ember-data/model').default>;
type StableRecordIdentifier = import('../../ts-interfaces/identifier').StableRecordIdentifier;

export default function notifyChanges(
identifier: StableRecordIdentifier,
value: 'attributes' | 'relationships' | 'errors' | 'meta' | 'unload' | 'identity' | 'property' | 'state',
record: DSModel,
value: NotificationType,
key: string | undefined,
record: Model,
store: Store
) {
if (value === 'attributes') {
record.eachAttribute((key) => {
let currentValue = cacheFor(record, key);
let internalModel = store._internalModelForResource(identifier);
if (currentValue !== internalModel._recordData.getAttr(key)) {
record.notifyPropertyChange(key);
}
});
if (key) {
notifyAttribute(store, identifier, key, record);
} else {
record.eachAttribute((key) => {
notifyAttribute(store, identifier, key, record);
});
}
} else if (value === 'relationships') {
record.eachRelationship((key, meta) => {
let internalModel = store._internalModelForResource(identifier);
if (meta.kind === 'belongsTo') {
record.notifyPropertyChange(key);
} else if (meta.kind === 'hasMany') {
let didRemoveUnloadedModel = false;
if (meta.options.async) {
record.notifyPropertyChange(key);
didRemoveUnloadedModel = internalModel.hasManyRemovalCheck(key);
}
let manyArray = internalModel._manyArrayCache[key] || internalModel._retainedManyArrayCache[key];
if (manyArray && !didRemoveUnloadedModel) {
manyArray.retrieveLatest();
}
}
});
if (key) {
let meta = record.constructor.relationshipsByName.get(key);
notifyRelationship(store, identifier, key, record, meta);
} else {
record.eachRelationship((key, meta) => {
notifyRelationship(store, identifier, key, record, meta);
});
}
} else if (value === 'errors') {
let internalModel = store._internalModelForResource(identifier);
//TODO guard
Expand All @@ -47,3 +44,28 @@ export default function notifyChanges(
record.notifyPropertyChange('id');
}
}

function notifyRelationship(store: CoreStore, identifier: StableRecordIdentifier, key: string, record: Model, meta) {
let internalModel = store._internalModelForResource(identifier);
if (meta.kind === 'belongsTo') {
record.notifyPropertyChange(key);
} else if (meta.kind === 'hasMany') {
let didRemoveUnloadedModel = false;
if (meta.options.async) {
record.notifyPropertyChange(key);
didRemoveUnloadedModel = internalModel.hasManyRemovalCheck(key);
}
let manyArray = internalModel._manyArrayCache[key] || internalModel._retainedManyArrayCache[key];
if (manyArray && !didRemoveUnloadedModel) {
manyArray.retrieveLatest();
}
}
}

function notifyAttribute(store: CoreStore, identifier: StableRecordIdentifier, key: string, record: Model) {
let currentValue = cacheFor(record, key);
let internalModel = store._internalModelForResource(identifier);
if (currentValue !== internalModel._recordData.getAttr(key)) {
record.notifyPropertyChange(key);
}
}
32 changes: 20 additions & 12 deletions packages/store/addon/-private/system/record-notification-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,27 @@ type UnsubscribeToken = Object;
const Cache = new WeakMap<StableRecordIdentifier, NotificationCallback>();
const Tokens = new WeakMap<UnsubscribeToken, StableRecordIdentifier>();

interface NotificationCallback {
(
identifier: StableRecordIdentifier,
notificationType: 'attributes' | 'relationships' | 'identity' | 'errors' | 'meta' | 'unload' | 'property' | 'state'
): void;
export type NotificationType =
| 'attributes'
| 'relationships'
| 'identity'
| 'errors'
| 'meta'
| 'unload'
| 'property'
runspired marked this conversation as resolved.
Show resolved Hide resolved
| 'state';
export interface NotificationCallback {
(identifier: RecordIdentifier, notificationType: 'attributes' | 'relationships' | 'property', key?: string): void;
(identifier: RecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): void;
(identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void;
}

export function unsubscribe(token: UnsubscribeToken) {
let identifier = Tokens.get(token);
if (!identifier) {
throw new Error('Passed unknown unsubscribe token to unsubscribe');
}
Tokens.delete(token);
Cache.delete(identifier);
}
/*
Expand All @@ -32,21 +41,20 @@ export default class NotificationManager {
subscribe(identifier: RecordIdentifier, callback: NotificationCallback): UnsubscribeToken {
let stableIdentifier = identifierCacheFor(this.store).getOrCreateRecordIdentifier(identifier);
Cache.set(stableIdentifier, callback);
let unsubToken = new Object();
let unsubToken = {};
Tokens.set(unsubToken, stableIdentifier);
return identifier;
return unsubToken;
}

notify(
identifier: RecordIdentifier,
value: 'attributes' | 'relationships' | 'errors' | 'meta' | 'identity' | 'unload' | 'property' | 'state'
): boolean {
notify(identifier: RecordIdentifier, value: 'attributes' | 'relationships' | 'property', key?: string): boolean;
notify(identifier: RecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): boolean;
notify(identifier: RecordIdentifier, value: NotificationType, key?: string): boolean {
let stableIdentifier = identifierCacheFor(this.store).getOrCreateRecordIdentifier(identifier);
let callback = Cache.get(stableIdentifier);
if (!callback) {
return false;
}
callback(stableIdentifier, value);
callback(stableIdentifier, value, key);
return true;
}
}