From 68544a2022692365c40c8255bfe0369d14f82c9a Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Tue, 29 Oct 2024 16:04:34 -0700 Subject: [PATCH] chore: stub out linksMode work --- packages/core-types/src/schema/fields.ts | 68 +++++++++++++++ packages/model/src/-private/belongs-to.ts | 1 + .../src/-private/references/belongs-to.ts | 2 + packages/schema-record/src/record.ts | 12 +++ .../tests/legacy/reads/relationships-test.ts | 10 +++ .../tests/reads/belongs-to-test.ts | 83 +++++++++++++++++++ 6 files changed, 176 insertions(+) create mode 100644 tests/warp-drive__schema-record/tests/reads/belongs-to-test.ts diff --git a/packages/core-types/src/schema/fields.ts b/packages/core-types/src/schema/fields.ts index 22b485841f2..34056b03715 100644 --- a/packages/core-types/src/schema/fields.ts +++ b/packages/core-types/src/schema/fields.ts @@ -729,6 +729,40 @@ export type LegacyBelongsToField = { */ polymorphic?: boolean; + /** + * Whether this field should ever make use of the legacy support infra + * from @ember-data/model and the LegacyNetworkMiddleware for adapters and serializers. + * + * When true, none of the legacy support will be utilized. Sync relationships + * will be expected to already have all their data. When reloading a sync relationship + * you would be expected to have a `related link` available from a prior relationship + * payload e.g. + * + * ```ts + * { + * data: { + * type: 'user', + * id: '2', + * attributes: { name: 'Chris' }, + * relationships: { + * bestFriend: { + * links: { related: "/users/1/bestFriend" }, + * data: { type: 'user', id: '1' }, + * } + * } + * }, + * included: [ + * { type: 'user', id: '1', attributes: { name: 'Krystan' } } + * ] + * } + * ``` + * + * Async relationships will be loaded via their link if needed. + * + * @typedoc + */ + linksMode?: true; + /** * When omitted, the cache data for this field will * clear local state of all changes except for the @@ -819,6 +853,40 @@ export type LegacyHasManyField = { */ polymorphic?: boolean; + /** + * Whether this field should ever make use of the legacy support infra + * from @ember-data/model and the LegacyNetworkMiddleware for adapters and serializers. + * + * When true, none of the legacy support will be utilized. Sync relationships + * will be expected to already have all their data. When reloading a sync relationship + * you would be expected to have a `related link` available from a prior relationship + * payload e.g. + * + * ```ts + * { + * data: { + * type: 'user', + * id: '2', + * attributes: { name: 'Chris' }, + * relationships: { + * bestFriends: { + * links: { related: "/users/1/bestFriends" }, + * data: [ { type: 'user', id: '1' } ], + * } + * } + * }, + * included: [ + * { type: 'user', id: '1', attributes: { name: 'Krystan' } } + * ] + * } + * ``` + * + * Async relationships will be loaded via their link if needed. + * + * @typedoc + */ + linksMode?: true; + /** * When omitted, the cache data for this field will * clear local state of all changes except for the diff --git a/packages/model/src/-private/belongs-to.ts b/packages/model/src/-private/belongs-to.ts index 8f3f387cd21..2f3d5890956 100644 --- a/packages/model/src/-private/belongs-to.ts +++ b/packages/model/src/-private/belongs-to.ts @@ -20,6 +20,7 @@ export type RelationshipOptions = { inverse: null | (IsUnknown extends true ? string : keyof NoNull & string); polymorphic?: boolean; as?: string; + linksMode?: true; resetOnRemoteUpdate?: boolean; }; diff --git a/packages/model/src/-private/references/belongs-to.ts b/packages/model/src/-private/references/belongs-to.ts index f5363c091f9..8bc2031826d 100644 --- a/packages/model/src/-private/references/belongs-to.ts +++ b/packages/model/src/-private/references/belongs-to.ts @@ -634,6 +634,8 @@ export default class BelongsToReference< @param {Object} options the options to pass in. @return {Promise} a promise that resolves with the record in this belongs-to relationship. */ + // load and reload on the references will need to understand linksmode and make a `findBelongsTo` + // or `findHasMany` operation request with the link instead of getBelongsto/reloadBelongsTo etc async load(options?: Record): Promise { const support: LegacySupport = (LEGACY_SUPPORT as Map).get( this.___identifier diff --git a/packages/schema-record/src/record.ts b/packages/schema-record/src/record.ts index c6ee6388bcf..a72b25f94c1 100644 --- a/packages/schema-record/src/record.ts +++ b/packages/schema-record/src/record.ts @@ -245,6 +245,7 @@ export class SchemaRecord { entangleSignal(signals, receiver, field.name); return computeAttribute(cache, identifier, prop as string); case 'resource': + // we will do something very similar to this for belongsTo in links mode assert( `SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`, !target[Legacy] @@ -310,14 +311,25 @@ export class SchemaRecord { Mode[Editable] ); case 'belongsTo': + if (field.options.linksMode) { + // do non-legacy approach else do the below + // unlike computeResource, we will just return the record value + // in the async case, we should probably just return a promise resolving to the record value + // we can error for the async case initially in favor of shipping sync case quickly + } if (!HAS_MODEL_PACKAGE) { assert( `Cannot use belongsTo fields in your schema unless @ember-data/model is installed to provide legacy model support. ${field.name} should likely be migrated to be a resource field.` ); } + // change here would be to detect the new "links-only" mode + // likely we should do this via an option on the schema + // if in that mode, you are no longer required to use legacy + // if in that mode, even if legacy, we no longer go through getLegacySupport assert(`Expected to have a getLegacySupport function`, getLegacySupport); assert(`Can only use belongsTo fields when the resource is in legacy mode`, Mode[Legacy]); entangleSignal(signals, receiver, field.name); + return getLegacySupport(receiver as unknown as MinimalLegacyRecord).getBelongsTo(field.name); case 'hasMany': if (!HAS_MODEL_PACKAGE) { diff --git a/tests/warp-drive__schema-record/tests/legacy/reads/relationships-test.ts b/tests/warp-drive__schema-record/tests/legacy/reads/relationships-test.ts index f834798c21d..f1fb38e9b11 100644 --- a/tests/warp-drive__schema-record/tests/legacy/reads/relationships-test.ts +++ b/tests/warp-drive__schema-record/tests/legacy/reads/relationships-test.ts @@ -1,3 +1,13 @@ +// before doing any of the things for legacy, write and fix tests for non-legacy links-only mode +// - those tests will be in a new file under tests/reads/belongs-to-test.ts in this test app +// +// the main thing to do for legacy: +// we should add tests to this file for a record in legacy-mode but with links-only mode on relationships +// +// a second thing to do for legacy: +// we should also add tests to the main test suite to confirm that instances of @ember-data/model in links-only +// mode fetch their async relationship data via the link via requestmanager without requiring the legacy support +// infrastructure. import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; diff --git a/tests/warp-drive__schema-record/tests/reads/belongs-to-test.ts b/tests/warp-drive__schema-record/tests/reads/belongs-to-test.ts new file mode 100644 index 00000000000..16af4149d16 --- /dev/null +++ b/tests/warp-drive__schema-record/tests/reads/belongs-to-test.ts @@ -0,0 +1,83 @@ +import type { TestContext } from '@ember/test-helpers'; + +import { module, test } from 'qunit'; + +import { setupTest } from 'ember-qunit'; + +import type Store from '@ember-data/store'; +import type { Type } from '@warp-drive/core-types/symbols'; +import { registerDerivations, withDefaults } from '@warp-drive/schema-record/schema'; + +type User = { + id: string | null; + $type: 'user'; + name: string; + bestFriend: User | null; + [Type]: 'user'; +}; + +module('Reads | belongsTo in linksMode', function (hooks) { + setupTest(hooks); + + test('we can use sync belongsTo in linksMode', function (this: TestContext, assert) { + const store = this.owner.lookup('service:store') as Store; + const { schema } = store; + + registerDerivations(schema); + + schema.registerResource( + withDefaults({ + type: 'user', + fields: [ + { + name: 'name', + kind: 'field', + }, + { + name: 'bestFriend', + type: 'user', + kind: 'belongsTo', + options: { inverse: 'bestFriend', async: false, linksMode: true }, + }, + ], + }) + ); + + const record = store.push({ + data: { + type: 'user', + id: '1', + attributes: { + name: 'Chris', + }, + relationships: { + bestFriend: { + data: { type: 'user', id: '2' }, + }, + }, + }, + included: [ + { + type: 'user', + id: '2', + attributes: { + name: 'Rey', + }, + relationships: { + bestFriend: { + data: { type: 'user', id: '1' }, + }, + }, + }, + ], + }); + + assert.strictEqual(record.id, '1', 'id is accessible'); + assert.strictEqual(record.$type, 'user', '$type is accessible'); + assert.strictEqual(record.name, 'Chris', 'name is accessible'); + assert.strictEqual(record.bestFriend?.id, '2', 'bestFriend.id is accessible'); + assert.strictEqual(record.bestFriend?.$type, 'user', 'bestFriend.user is accessible'); + assert.strictEqual(record.bestFriend?.name, 'Rey', 'bestFriend.name is accessible'); + assert.strictEqual(record.bestFriend?.bestFriend?.id, record.id, 'bestFriend is reciprocal'); + }); +});