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: (private) implement basic field support for schema-record #8935

Merged
merged 6 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ module.exports = {
'ember-data-types/q/promise-proxies.ts',
'ember-data-types/q/minimum-serializer-interface.ts',
'ember-data-types/q/minimum-adapter-interface.ts',
'ember-data-types/q/identifier.ts',
'ember-data-types/q/fetch-manager.ts',
'ember-data-types/q/ember-data-json-api.ts',
'@types/@ember/polyfills/index.d.ts',
Expand Down
2 changes: 1 addition & 1 deletion ember-data-types/q/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@module @ember-data/store
*/

import { ImmutableRequestInfo } from '@ember-data/request/-private/types';
import { ImmutableRequestInfo } from '@ember-data/request';
import {
CACHE_OWNER,
DEBUG_CLIENT_ORIGINATED,
Expand Down
13 changes: 2 additions & 11 deletions ember-data-types/q/record-data-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@
export interface RelationshipSchema {
kind: 'belongsTo' | 'hasMany';
type: string; // related type
/**
* @internal
* @deprecated
*/
key: string; // TODO @runspired remove our uses
// TODO @runspired should RFC be updated to make this optional?
// TODO @runspired sohuld RFC be update to enforce async and inverse are set? else internals need to know
// that meta came from @ember-data/model vs not from @ember-data/model as defaults should switch.
Expand All @@ -29,18 +24,14 @@ export interface RelationshipSchema {
export type RelationshipsSchema = Record<string, RelationshipSchema>;

export interface AttributeSchema {
/**
* @internal
*/
name: string;

kind?: 'attribute';
kind: 'attribute';

// TODO @runspired update RFC to make options optional
options?: {
[key: string]: unknown;
};
type?: string; // TODO @runspired update RFC to make type optional
type: string | null;
}

export type AttributesSchema = Record<string, AttributeSchema>;
2 changes: 1 addition & 1 deletion packages/graph/src/-private/-edge-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ export function upgradeDefinition(
// TODO we want to assert this but this breaks all of our shoddily written tests
/*
if (DEBUG) {
let inverseDoubleCheck = inverseMeta.type.inverseFor(inverseRelationshipName, store);
let inverseDoubleCheck = inverseFor(inverseRelationshipName, store);

assert(`The ${inverseBaseModelName}:${inverseRelationshipName} relationship declares 'inverse: null', but it was resolved as the inverse for ${baseModelName}:${relationshipName}.`, inverseDoubleCheck);
}
Expand Down
54 changes: 49 additions & 5 deletions packages/json-api/src/-private/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { ImplicitEdge } from '@ember-data/graph/-private/edges/implicit';
import type { ResourceEdge } from '@ember-data/graph/-private/edges/resource';
import type { Graph, GraphEdge } from '@ember-data/graph/-private/graph';
import type { StructuredDataDocument, StructuredDocument, StructuredErrorDocument } from '@ember-data/request';
import Store from '@ember-data/store';
import { StoreRequestInfo } from '@ember-data/store/-private/cache-handler';
import type { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache';
import type { ResourceBlob } from '@ember-data/types/cache/aliases';
Expand Down Expand Up @@ -426,6 +427,22 @@ export default class JSONAPICache implements Cache {
}
});
}

// @ts-expect-error we reach into private api here
const store: Store = this.__storeWrapper._store;
const attrs = this.__storeWrapper.getSchemaDefinitionService().attributesDefinitionFor(identifier);
Object.keys(attrs).forEach((key) => {
if (key in attributes && attributes[key] !== undefined) {
return;
}
const attr = attrs[key]!;
const defaultValue = getDefaultValue(attr, identifier, store);

if (defaultValue !== undefined) {
attributes[key] = defaultValue;
}
});

return {
type,
id,
Expand Down Expand Up @@ -689,6 +706,7 @@ export default class JSONAPICache implements Cache {
switch (kind) {
case 'attribute':
this.setAttr(identifier, name, propertyValue);
createOptions[name] = propertyValue;
break;
case 'belongsTo':
this.mutate({
Expand Down Expand Up @@ -1026,7 +1044,9 @@ export default class JSONAPICache implements Cache {
return cached.remoteAttrs[attr];
} else {
const attrSchema = this.__storeWrapper.getSchemaDefinitionService().attributesDefinitionFor(identifier)[attr];
return getDefaultValue(attrSchema?.options);

// @ts-expect-error we reach into private API here
return getDefaultValue(attrSchema, identifier, this.__storeWrapper._store);
}
}

Expand Down Expand Up @@ -1380,22 +1400,46 @@ function getRemoteState(rel) {
return rel.remoteState;
}

function getDefaultValue(options: { defaultValue?: unknown } | undefined) {
if (!options) {
function getDefaultValue(
schema: AttributeSchema | undefined,
identifier: StableRecordIdentifier,
store: Store
): unknown {
const options = schema?.options;

if (!schema || (!options && !schema.type)) {
return;
}
if (typeof options.defaultValue === 'function') {

// legacy support for defaultValues that are functions
if (typeof options?.defaultValue === 'function') {
// If anyone opens an issue for args not working right, we'll restore + deprecate it via a Proxy
// that lazily instantiates the record. We don't want to provide any args here
// because in a non @ember-data/model world they don't make sense.
return options.defaultValue();
} else {
// legacy support for defaultValues that are primitives
} else if (options && 'defaultValue' in options) {
let defaultValue = options.defaultValue;
assert(
`Non primitive defaultValues are not supported because they are shared between all instances. If you would like to use a complex object as a default value please provide a function that returns the complex object.`,
typeof defaultValue !== 'object' || defaultValue === null
);
return defaultValue;

// new style transforms
} else if (schema.type) {
const transform = (
store.schema as unknown as {
transforms?: Map<
string,
{ defaultValue(options: Record<string, unknown> | null, identifier: StableRecordIdentifier): unknown }
>;
}
).transforms?.get(schema.type);

if (transform?.defaultValue) {
return transform.defaultValue(options || null, identifier);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function _findHasMany(adapter, store, identifier, link, relationship, opt
promise = promise.then(
(adapterPayload) => {
assert(
`You made a 'findHasMany' request for a ${identifier.type}'s '${relationship.key}' relationship, using link '${link}' , but the adapter's response did not have any data`,
`You made a 'findHasMany' request for a ${identifier.type}'s '${relationship.name}' relationship, using link '${link}' , but the adapter's response did not have any data`,
payloadIsNotBlank(adapterPayload)
);
const modelClass = store.modelFor(relationship.type);
Expand Down Expand Up @@ -115,7 +115,7 @@ function syncRelationshipDataFromLink(store, payload, parentIdentifier, relation
id: parentIdentifier.id,
type: parentIdentifier.type,
relationships: {
[relationship.key]: relatedDataHash,
[relationship.name]: relatedDataHash,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ function findHasMany<T>(context: StoreRequestContext): Promise<T> {
}

// identifiers case

const fetches = new Array<globalThis.Promise<StableRecordIdentifier>>(identifiers!.length);
assert(`Expected an array of identifiers to fetch`, Array.isArray(identifiers));
const fetches = new Array<globalThis.Promise<StableRecordIdentifier>>(identifiers.length);
const manager = store._fetchManager;

for (let i = 0; i < identifiers!.length; i++) {
let identifier = identifiers![i];
for (let i = 0; i < identifiers.length; i++) {
let identifier = identifiers[i];
// TODO we probably can be lenient here and return from cache for the isNew case
assertIdentifierHasId(identifier);
fetches[i] = options.reload
Expand All @@ -183,10 +183,10 @@ function saveRecord<T>(context: StoreRequestContext): Promise<T> {
try {
let payloadCopy: unknown = payload ? JSON.parse(JSON.stringify(payload)) : payload;
// eslint-disable-next-line no-console
console.log(`EmberData | Payload - ${operation!}`, payloadCopy);
console.log(`EmberData | Payload - ${operation}`, payloadCopy);
} catch (e) {
// eslint-disable-next-line no-console
console.log(`EmberData | Payload - ${operation!}`, payload);
console.log(`EmberData | Payload - ${operation}`, payload);
}
}
let result: SingleResourceDataDocument;
Expand Down
2 changes: 2 additions & 0 deletions packages/model/src/-private/attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,10 @@ function attr(type, options) {

let meta = {
type: type,
kind: 'attribute',
isAttribute: true,
options: options,
key: null,
};

return computed({
Expand Down
2 changes: 1 addition & 1 deletion packages/model/src/-private/belongs-to.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ function belongsTo(modelName, options) {
isRelationship: true,
options: opts,
kind: 'belongsTo',
name: 'Belongs To',
name: '<Unknown BelongsTo>',
key: null,
};

Expand Down
2 changes: 1 addition & 1 deletion packages/model/src/-private/has-many.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ function hasMany(type, options) {
options,
isRelationship: true,
kind: 'hasMany',
name: 'Has Many',
name: '<Unknown BelongsTo>',
key: null,
};

Expand Down
2 changes: 1 addition & 1 deletion packages/model/src/-private/model.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Model extends EmberObject {
static relationshipsByName: Map<string, RelationshipSchema>;
static eachAttribute<T>(callback: (this: T, key: string, attribute: AttributeSchema) => void, binding?: T): void;
static eachRelationship<T>(callback: (this: T, key: string, relationship: RelationshipSchema) => void, binding?: T): void;
static eachTransformedAttribute<T>(callback: (this: T, key: string, type?: string) => void, binding?: T): void;
static eachTransformedAttribute<T>(callback: (this: T, key: string, type: string | null) => void, binding?: T): void;

static toString(): string;
static isModel: true;
Expand Down
Loading
Loading