Skip to content

Commit

Permalink
feat: activate all feature flags related to custom model classes (#7510)
Browse files Browse the repository at this point in the history
* feat: activate all ffs related to CUSTOM_MODEL_CLASS

* fix groupRecordsForFindMany

* add key param to property/attributes/relationships buckets

* fix unsubscribe

* fix type signature

* disable minification to see what's going on in prod

* fix IE11 support

* Revert "disable minification to see what's going on in prod"

This reverts commit edb19ea.

* add note to property
  • Loading branch information
runspired authored May 11, 2021
1 parent f0d6949 commit f76a0e0
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 53 deletions.
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,
};
6 changes: 5 additions & 1 deletion packages/store/addon/-private/system/core-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2529,7 +2529,11 @@ abstract class CoreStore extends Service {
}
});
},
({ error, parsedErrors }) => {
(e) => {
if (typeof e === 'string') {
throw e;
}
const { error, parsedErrors } = e;
this.recordWasInvalid(internalModel, parsedErrors, error);
throw error;
}
Expand Down
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);
}
}
14 changes: 12 additions & 2 deletions packages/store/addon/-private/system/model/shim-model-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ export function getShimClass(store: CoreStore, modelName: string): ShimModelClas
return shim;
}

function mapFromHash<T>(hash: Dict<T>): Map<string, T> {
let map = new Map();
for (let i in hash) {
if (Object.prototype.hasOwnProperty.call(hash, i)) {
map.set(i, hash[i]);
}
}
return map;
}

// Mimics the static apis of DSModel
export default class ShimModelClass implements ModelSchema {
// TODO Maybe expose the class here?
Expand All @@ -39,12 +49,12 @@ export default class ShimModelClass implements ModelSchema {

get attributes(): Map<string, AttributeSchema> {
let attrs = this.__store._attributesDefinitionFor(this.modelName);
return new Map(Object.entries(attrs) as [string, AttributeSchema][]);
return mapFromHash(attrs);
}

get relationshipsByName(): Map<string, RelationshipSchema> {
let relationships = this.__store._relationshipsDefinitionFor(this.modelName);
return new Map(Object.entries(relationships) as [string, RelationshipSchema][]);
return mapFromHash(relationships);
}

eachAttribute<T>(callback: (this: T, key: string, attribute: AttributeSchema) => void, binding?: T) {
Expand Down
33 changes: 21 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,28 @@ 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'
| 'state'
| 'property'; // 'property' is an internal EmberData only transition period concept.

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 +42,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;
}
}

0 comments on commit f76a0e0

Please sign in to comment.