@warp-drive-mirror/schema-record 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +19 -8
- package/README.md +231 -129
- package/dist/-private.js +1 -0
- package/dist/-private.js.map +1 -0
- package/dist/{record.js → index.js} +700 -99
- package/dist/index.js.map +1 -0
- package/dist/symbols-DqoS4ybV.js.map +1 -1
- package/logos/NCC-1701-a-gold.svg +4 -0
- package/logos/NCC-1701-a-gold_100.svg +1 -0
- package/logos/NCC-1701-a-gold_base-64.txt +1 -0
- package/logos/README.md +4 -0
- package/logos/docs-badge.svg +2 -0
- package/logos/ember-data-logo-dark.svg +12 -0
- package/logos/ember-data-logo-light.svg +12 -0
- package/logos/github-header.svg +444 -0
- package/logos/social1.png +0 -0
- package/logos/social2.png +0 -0
- package/logos/warp-drive-logo-dark.svg +4 -0
- package/logos/warp-drive-logo-gold.svg +4 -0
- package/package.json +32 -58
- package/unstable-preview-types/-private/{compute.d.ts → fields/compute.d.ts} +17 -15
- package/unstable-preview-types/-private/fields/compute.d.ts.map +1 -0
- package/unstable-preview-types/-private/{managed-array.d.ts → fields/managed-array.d.ts} +4 -4
- package/unstable-preview-types/-private/fields/managed-array.d.ts.map +1 -0
- package/unstable-preview-types/-private/{managed-object.d.ts → fields/managed-object.d.ts} +4 -4
- package/unstable-preview-types/-private/fields/managed-object.d.ts.map +1 -0
- package/unstable-preview-types/-private/fields/many-array-manager.d.ts +23 -0
- package/unstable-preview-types/-private/fields/many-array-manager.d.ts.map +1 -0
- package/unstable-preview-types/{hooks.d.ts → -private/hooks.d.ts} +3 -3
- package/unstable-preview-types/-private/hooks.d.ts.map +1 -0
- package/unstable-preview-types/{record.d.ts → -private/record.d.ts} +3 -3
- package/unstable-preview-types/-private/record.d.ts.map +1 -0
- package/unstable-preview-types/{schema.d.ts → -private/schema.d.ts} +61 -11
- package/unstable-preview-types/-private/schema.d.ts.map +1 -0
- package/unstable-preview-types/{symbols.d.ts → -private/symbols.d.ts} +1 -1
- package/unstable-preview-types/-private/symbols.d.ts.map +1 -0
- package/unstable-preview-types/-private.d.ts +4 -0
- package/unstable-preview-types/-private.d.ts.map +1 -0
- package/unstable-preview-types/index.d.ts +353 -7
- package/unstable-preview-types/index.d.ts.map +1 -0
- package/dist/hooks.js +0 -19
- package/dist/hooks.js.map +0 -1
- package/dist/record.js.map +0 -1
- package/dist/schema.js +0 -278
- package/dist/schema.js.map +0 -1
- package/unstable-preview-types/-private/compute.d.ts.map +0 -1
- package/unstable-preview-types/-private/managed-array.d.ts.map +0 -1
- package/unstable-preview-types/-private/managed-object.d.ts.map +0 -1
- package/unstable-preview-types/hooks.d.ts.map +0 -1
- package/unstable-preview-types/record.d.ts.map +0 -1
- package/unstable-preview-types/schema.d.ts.map +0 -1
- package/unstable-preview-types/symbols.d.ts.map +0 -1
- /package/{NCC-1701-a-blue.svg → logos/NCC-1701-a-blue.svg} +0 -0
- /package/{NCC-1701-a.svg → logos/NCC-1701-a.svg} +0 -0
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { isResourceSchema } from '@warp-drive-mirror/core-types/schema/fields';
|
|
1
2
|
import { macroCondition, getGlobalConfig, dependencySatisfies, importSync } from '@embroider/macros';
|
|
2
|
-
import { setRecordIdentifier, recordIdentifierFor } from '@ember-data-mirror/store/-private';
|
|
3
|
-
import { createSignal, subscribe, defineSignal, peekSignal, getSignal, Signals,
|
|
4
|
-
import { STRUCTURED } from '@warp-drive-mirror/core-types/request';
|
|
5
|
-
import { RecordStore } from '@warp-drive-mirror/core-types/symbols';
|
|
3
|
+
import { SOURCE as SOURCE$1, fastPush, RelatedCollection, setRecordIdentifier, recordIdentifierFor } from '@ember-data-mirror/store/-private';
|
|
4
|
+
import { createSignal, subscribe, defineSignal, peekSignal, getSignal, Signals, addToTransaction, entangleSignal } from '@ember-data-mirror/tracking/-private';
|
|
5
|
+
import { EnableHydration, STRUCTURED } from '@warp-drive-mirror/core-types/request';
|
|
6
|
+
import { RecordStore, Type } from '@warp-drive-mirror/core-types/symbols';
|
|
6
7
|
import { getOrSetGlobal } from '@warp-drive-mirror/core-types/-private';
|
|
7
8
|
import { S as SOURCE, A as ARRAY_SIGNAL, E as Editable, L as Legacy, I as Identifier, P as Parent, O as OBJECT_SIGNAL, a as EmbeddedPath, D as Destroy, C as Checkout, b as EmbeddedType } from "./symbols-DqoS4ybV.js";
|
|
9
|
+
import { deprecate } from '@ember/debug';
|
|
10
|
+
import { recordIdentifierFor as recordIdentifierFor$1 } from '@ember-data-mirror/store';
|
|
11
|
+
import { createCache, getValue } from '@ember-data-mirror/tracking';
|
|
8
12
|
const ARRAY_GETTER_METHODS = new Set([Symbol.iterator, 'concat', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'map', 'reduce', 'reduceRight', 'slice', 'some', 'values']);
|
|
9
13
|
// const ARRAY_SETTER_METHODS = new Set<KeyType>(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
|
|
10
14
|
const SYNC_PROPS = new Set(['[]', 'length']);
|
|
@@ -257,6 +261,12 @@ class ManagedArray {
|
|
|
257
261
|
_SIGNAL.shouldReset = true;
|
|
258
262
|
}
|
|
259
263
|
return reflect;
|
|
264
|
+
},
|
|
265
|
+
has(target, prop) {
|
|
266
|
+
if (prop === 'identifier' || prop === 'owner' || prop === ARRAY_SIGNAL) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
return Reflect.has(target, prop);
|
|
260
270
|
}
|
|
261
271
|
});
|
|
262
272
|
return proxy;
|
|
@@ -298,7 +308,7 @@ class ManagedObject {
|
|
|
298
308
|
return self[prop];
|
|
299
309
|
}
|
|
300
310
|
if (prop === Symbol.toPrimitive) {
|
|
301
|
-
return null;
|
|
311
|
+
return () => null;
|
|
302
312
|
}
|
|
303
313
|
if (prop === Symbol.toStringTag) {
|
|
304
314
|
return `ManagedObject<${identifier.type}:${identifier.id} (${identifier.lid})>`;
|
|
@@ -313,7 +323,12 @@ class ManagedObject {
|
|
|
313
323
|
}
|
|
314
324
|
if (prop === 'toHTML') {
|
|
315
325
|
return function () {
|
|
316
|
-
return '<
|
|
326
|
+
return '<span>ManagedObject</span>';
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (prop === 'toJSON') {
|
|
330
|
+
return function () {
|
|
331
|
+
return structuredClone(self[SOURCE]);
|
|
317
332
|
};
|
|
318
333
|
}
|
|
319
334
|
if (_SIGNAL.shouldReset) {
|
|
@@ -360,6 +375,81 @@ class ManagedObject {
|
|
|
360
375
|
return proxy;
|
|
361
376
|
}
|
|
362
377
|
}
|
|
378
|
+
class ManyArrayManager {
|
|
379
|
+
constructor(record) {
|
|
380
|
+
this.record = record;
|
|
381
|
+
this.store = record[RecordStore];
|
|
382
|
+
this.identifier = record[Identifier];
|
|
383
|
+
}
|
|
384
|
+
_syncArray(array) {
|
|
385
|
+
const rawValue = this.store.cache.getRelationship(this.identifier, array.key);
|
|
386
|
+
if (rawValue.meta) {
|
|
387
|
+
array.meta = rawValue.meta;
|
|
388
|
+
}
|
|
389
|
+
if (rawValue.links) {
|
|
390
|
+
array.links = rawValue.links;
|
|
391
|
+
}
|
|
392
|
+
const currentState = array[SOURCE$1];
|
|
393
|
+
|
|
394
|
+
// unlike in the normal RecordArray case, we don't need to divorce the reference
|
|
395
|
+
// because we don't need to worry about associate/disassociate since the graph
|
|
396
|
+
// takes care of that for us
|
|
397
|
+
if (currentState !== rawValue.data) {
|
|
398
|
+
currentState.length = 0;
|
|
399
|
+
fastPush(currentState, rawValue.data);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
reloadHasMany(key, options) {
|
|
403
|
+
const field = this.store.schema.fields(this.identifier).get(key);
|
|
404
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
405
|
+
if (!test) {
|
|
406
|
+
throw new Error(`Expected a hasMany field for ${key}`);
|
|
407
|
+
}
|
|
408
|
+
})(field?.kind === 'hasMany') : {};
|
|
409
|
+
const cacheOptions = options ? extractCacheOptions(options) : {
|
|
410
|
+
reload: true
|
|
411
|
+
};
|
|
412
|
+
cacheOptions.types = [field.type];
|
|
413
|
+
const rawValue = this.store.cache.getRelationship(this.identifier, key);
|
|
414
|
+
const req = {
|
|
415
|
+
url: getRelatedLink(rawValue),
|
|
416
|
+
op: 'findHasMany',
|
|
417
|
+
method: 'GET',
|
|
418
|
+
records: rawValue.data,
|
|
419
|
+
cacheOptions,
|
|
420
|
+
options: {
|
|
421
|
+
field,
|
|
422
|
+
identifier: this.identifier,
|
|
423
|
+
links: rawValue.links,
|
|
424
|
+
meta: rawValue.meta
|
|
425
|
+
},
|
|
426
|
+
[EnableHydration]: false
|
|
427
|
+
};
|
|
428
|
+
return this.store.request(req);
|
|
429
|
+
}
|
|
430
|
+
mutate(mutation) {
|
|
431
|
+
this.cache.mutate(mutation);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function getRelatedLink(resource) {
|
|
435
|
+
const related = resource.links?.related;
|
|
436
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
437
|
+
if (!test) {
|
|
438
|
+
throw new Error(`Expected a related link`);
|
|
439
|
+
}
|
|
440
|
+
})(related) : {};
|
|
441
|
+
return typeof related === 'object' ? related.href : related;
|
|
442
|
+
}
|
|
443
|
+
function extractCacheOptions(options) {
|
|
444
|
+
const cacheOptions = {};
|
|
445
|
+
if ('reload' in options) {
|
|
446
|
+
cacheOptions.reload = options.reload;
|
|
447
|
+
}
|
|
448
|
+
if ('backgroundReload' in options) {
|
|
449
|
+
cacheOptions.backgroundReload = options.backgroundReload;
|
|
450
|
+
}
|
|
451
|
+
return cacheOptions;
|
|
452
|
+
}
|
|
363
453
|
const ManagedArrayMap = getOrSetGlobal('ManagedArrayMap', new Map());
|
|
364
454
|
const ManagedObjectMap = getOrSetGlobal('ManagedObjectMap', new Map());
|
|
365
455
|
function computeLocal(record, field, prop) {
|
|
@@ -373,24 +463,25 @@ function computeLocal(record, field, prop) {
|
|
|
373
463
|
function peekManagedArray(record, field) {
|
|
374
464
|
const managedArrayMapForRecord = ManagedArrayMap.get(record);
|
|
375
465
|
if (managedArrayMapForRecord) {
|
|
376
|
-
return managedArrayMapForRecord.get(field);
|
|
466
|
+
return managedArrayMapForRecord.get(field.name);
|
|
377
467
|
}
|
|
378
468
|
}
|
|
379
469
|
function peekManagedObject(record, field) {
|
|
380
470
|
const managedObjectMapForRecord = ManagedObjectMap.get(record);
|
|
381
471
|
if (managedObjectMapForRecord) {
|
|
382
|
-
return managedObjectMapForRecord.get(field);
|
|
472
|
+
return managedObjectMapForRecord.get(field.name);
|
|
383
473
|
}
|
|
384
474
|
}
|
|
385
|
-
function computeField(schema, cache, record, identifier, field, prop) {
|
|
386
|
-
const rawValue = cache.getAttr(identifier, prop);
|
|
475
|
+
function computeField(schema, cache, record, identifier, field, prop, editable) {
|
|
476
|
+
const rawValue = editable ? cache.getAttr(identifier, prop) : cache.getRemoteAttr(identifier, prop);
|
|
387
477
|
if (!field.type) {
|
|
388
478
|
return rawValue;
|
|
389
479
|
}
|
|
390
480
|
const transform = schema.transformation(field);
|
|
391
481
|
return transform.hydrate(rawValue, field.options ?? null, record);
|
|
392
482
|
}
|
|
393
|
-
function computeArray(store, schema, cache, record, identifier, field, path,
|
|
483
|
+
function computeArray(store, schema, cache, record, identifier, field, path, editable, legacy) {
|
|
484
|
+
const isSchemaArray = field.kind === 'schema-array';
|
|
394
485
|
// the thing we hand out needs to know its owner and path in a private manner
|
|
395
486
|
// its "address" is the parent identifier (identifier) + field name (field.name)
|
|
396
487
|
// in the nested object case field name here is the full dot path from root resource to this value
|
|
@@ -400,20 +491,20 @@ function computeArray(store, schema, cache, record, identifier, field, path, isS
|
|
|
400
491
|
const managedArrayMapForRecord = ManagedArrayMap.get(record);
|
|
401
492
|
let managedArray;
|
|
402
493
|
if (managedArrayMapForRecord) {
|
|
403
|
-
managedArray = managedArrayMapForRecord.get(field);
|
|
494
|
+
managedArray = managedArrayMapForRecord.get(field.name);
|
|
404
495
|
}
|
|
405
496
|
if (managedArray) {
|
|
406
497
|
return managedArray;
|
|
407
498
|
} else {
|
|
408
|
-
const rawValue = cache.getAttr(identifier, path);
|
|
499
|
+
const rawValue = editable ? cache.getAttr(identifier, path) : cache.getRemoteAttr(identifier, path);
|
|
409
500
|
if (!rawValue) {
|
|
410
501
|
return null;
|
|
411
502
|
}
|
|
412
503
|
managedArray = new ManagedArray(store, schema, cache, field, rawValue, identifier, path, record, isSchemaArray, editable, legacy);
|
|
413
504
|
if (!managedArrayMapForRecord) {
|
|
414
|
-
ManagedArrayMap.set(record, new Map([[field, managedArray]]));
|
|
505
|
+
ManagedArrayMap.set(record, new Map([[field.name, managedArray]]));
|
|
415
506
|
} else {
|
|
416
|
-
managedArrayMapForRecord.set(field, managedArray);
|
|
507
|
+
managedArrayMapForRecord.set(field.name, managedArray);
|
|
417
508
|
}
|
|
418
509
|
}
|
|
419
510
|
return managedArray;
|
|
@@ -422,12 +513,12 @@ function computeObject(schema, cache, record, identifier, field, path, editable,
|
|
|
422
513
|
const managedObjectMapForRecord = ManagedObjectMap.get(record);
|
|
423
514
|
let managedObject;
|
|
424
515
|
if (managedObjectMapForRecord) {
|
|
425
|
-
managedObject = managedObjectMapForRecord.get(field);
|
|
516
|
+
managedObject = managedObjectMapForRecord.get(field.name);
|
|
426
517
|
}
|
|
427
518
|
if (managedObject) {
|
|
428
519
|
return managedObject;
|
|
429
520
|
} else {
|
|
430
|
-
let rawValue = cache.getAttr(identifier, path);
|
|
521
|
+
let rawValue = editable ? cache.getAttr(identifier, path) : cache.getRemoteAttr(identifier, path);
|
|
431
522
|
if (!rawValue) {
|
|
432
523
|
return null;
|
|
433
524
|
}
|
|
@@ -437,9 +528,9 @@ function computeObject(schema, cache, record, identifier, field, path, editable,
|
|
|
437
528
|
}
|
|
438
529
|
managedObject = new ManagedObject(schema, cache, field, rawValue, identifier, path, record, editable, legacy);
|
|
439
530
|
if (!managedObjectMapForRecord) {
|
|
440
|
-
ManagedObjectMap.set(record, new Map([[field, managedObject]]));
|
|
531
|
+
ManagedObjectMap.set(record, new Map([[field.name, managedObject]]));
|
|
441
532
|
} else {
|
|
442
|
-
managedObjectMapForRecord.set(field, managedObject);
|
|
533
|
+
managedObjectMapForRecord.set(field.name, managedObject);
|
|
443
534
|
}
|
|
444
535
|
}
|
|
445
536
|
return managedObject;
|
|
@@ -448,12 +539,12 @@ function computeSchemaObject(store, cache, record, identifier, field, path, lega
|
|
|
448
539
|
const schemaObjectMapForRecord = ManagedObjectMap.get(record);
|
|
449
540
|
let schemaObject;
|
|
450
541
|
if (schemaObjectMapForRecord) {
|
|
451
|
-
schemaObject = schemaObjectMapForRecord.get(field);
|
|
542
|
+
schemaObject = schemaObjectMapForRecord.get(field.name);
|
|
452
543
|
}
|
|
453
544
|
if (schemaObject) {
|
|
454
545
|
return schemaObject;
|
|
455
546
|
} else {
|
|
456
|
-
const rawValue = cache.getAttr(identifier, path);
|
|
547
|
+
const rawValue = editable ? cache.getAttr(identifier, path) : cache.getRemoteAttr(identifier, path);
|
|
457
548
|
if (!rawValue) {
|
|
458
549
|
return null;
|
|
459
550
|
}
|
|
@@ -464,14 +555,14 @@ function computeSchemaObject(store, cache, record, identifier, field, path, lega
|
|
|
464
555
|
}, true, field.type, embeddedPath);
|
|
465
556
|
}
|
|
466
557
|
if (!schemaObjectMapForRecord) {
|
|
467
|
-
ManagedObjectMap.set(record, new Map([[field, schemaObject]]));
|
|
558
|
+
ManagedObjectMap.set(record, new Map([[field.name, schemaObject]]));
|
|
468
559
|
} else {
|
|
469
|
-
schemaObjectMapForRecord.set(field, schemaObject);
|
|
560
|
+
schemaObjectMapForRecord.set(field.name, schemaObject);
|
|
470
561
|
}
|
|
471
562
|
return schemaObject;
|
|
472
563
|
}
|
|
473
|
-
function computeAttribute(cache, identifier, prop) {
|
|
474
|
-
return cache.getAttr(identifier, prop);
|
|
564
|
+
function computeAttribute(cache, identifier, prop, editable) {
|
|
565
|
+
return editable ? cache.getAttr(identifier, prop) : cache.getRemoteAttr(identifier, prop);
|
|
475
566
|
}
|
|
476
567
|
function computeDerivation(schema, record, identifier, field, prop) {
|
|
477
568
|
return schema.derivation(field)(record, field.options ?? null, prop);
|
|
@@ -480,8 +571,8 @@ function computeDerivation(schema, record, identifier, field, prop) {
|
|
|
480
571
|
// TODO probably this should just be a Document
|
|
481
572
|
// but its separate until we work out the lid situation
|
|
482
573
|
class ResourceRelationship {
|
|
483
|
-
constructor(store, cache, parent, identifier, field, name) {
|
|
484
|
-
const rawValue = cache.getRelationship(identifier, name);
|
|
574
|
+
constructor(store, cache, parent, identifier, field, name, editable) {
|
|
575
|
+
const rawValue = editable ? cache.getRelationship(identifier, name) : cache.getRemoteRelationship(identifier, name);
|
|
485
576
|
|
|
486
577
|
// TODO setup true lids for relationship documents
|
|
487
578
|
// @ts-expect-error we need to give relationship documents a lid
|
|
@@ -523,11 +614,56 @@ function getHref(link) {
|
|
|
523
614
|
}
|
|
524
615
|
return link.href;
|
|
525
616
|
}
|
|
526
|
-
function computeResource(store, cache, parent, identifier, field, prop) {
|
|
617
|
+
function computeResource(store, cache, parent, identifier, field, prop, editable) {
|
|
527
618
|
if (field.kind !== 'resource') {
|
|
528
619
|
throw new Error(`The schema for ${identifier.type}.${String(prop)} is not a resource relationship`);
|
|
529
620
|
}
|
|
530
|
-
return new ResourceRelationship(store, cache, parent, identifier, field, prop);
|
|
621
|
+
return new ResourceRelationship(store, cache, parent, identifier, field, prop, editable);
|
|
622
|
+
}
|
|
623
|
+
function computeHasMany(store, schema, cache, record, identifier, field, path, editable, legacy) {
|
|
624
|
+
// the thing we hand out needs to know its owner and path in a private manner
|
|
625
|
+
// its "address" is the parent identifier (identifier) + field name (field.name)
|
|
626
|
+
// in the nested object case field name here is the full dot path from root resource to this value
|
|
627
|
+
// its "key" is the field on the parent record
|
|
628
|
+
// its "owner" is the parent record
|
|
629
|
+
|
|
630
|
+
const managedArrayMapForRecord = ManagedArrayMap.get(record);
|
|
631
|
+
let managedArray;
|
|
632
|
+
if (managedArrayMapForRecord) {
|
|
633
|
+
managedArray = managedArrayMapForRecord.get(field.name);
|
|
634
|
+
}
|
|
635
|
+
if (managedArray) {
|
|
636
|
+
return managedArray;
|
|
637
|
+
} else {
|
|
638
|
+
const rawValue = cache.getRelationship(identifier, field.name);
|
|
639
|
+
if (!rawValue) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
managedArray = new RelatedCollection({
|
|
643
|
+
store,
|
|
644
|
+
type: field.type,
|
|
645
|
+
identifier,
|
|
646
|
+
cache,
|
|
647
|
+
identifiers: rawValue.data,
|
|
648
|
+
key: field.name,
|
|
649
|
+
meta: rawValue.meta || null,
|
|
650
|
+
links: rawValue.links || null,
|
|
651
|
+
isPolymorphic: field.options.polymorphic ?? false,
|
|
652
|
+
isAsync: field.options.async ?? false,
|
|
653
|
+
// TODO: Grab the proper value
|
|
654
|
+
_inverseIsAsync: false,
|
|
655
|
+
// @ts-expect-error Typescript doesn't have a way for us to thread the generic backwards so it infers unknown instead of T
|
|
656
|
+
manager: new ManyArrayManager(record),
|
|
657
|
+
isLoaded: true,
|
|
658
|
+
allowMutation: editable
|
|
659
|
+
});
|
|
660
|
+
if (!managedArrayMapForRecord) {
|
|
661
|
+
ManagedArrayMap.set(record, new Map([[field.name, managedArray]]));
|
|
662
|
+
} else {
|
|
663
|
+
managedArrayMapForRecord.set(field.name, managedArray);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return managedArray;
|
|
531
667
|
}
|
|
532
668
|
const HAS_MODEL_PACKAGE = dependencySatisfies('@ember-data-mirror/model', '*');
|
|
533
669
|
const getLegacySupport = macroCondition(dependencySatisfies('@ember-data-mirror/model', '*')) ? importSync('@ember-data-mirror/model/-private').lookupLegacySupport : null;
|
|
@@ -537,6 +673,9 @@ const RecordSymbols = new Set(symbolList);
|
|
|
537
673
|
function isPathMatch(a, b) {
|
|
538
674
|
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
539
675
|
}
|
|
676
|
+
function isNonEnumerableProp(prop) {
|
|
677
|
+
return prop === 'constructor' || prop === 'prototype' || prop === '__proto__' || prop === 'toString' || prop === 'toJSON' || prop === 'toHTML' || typeof prop === 'symbol';
|
|
678
|
+
}
|
|
540
679
|
const Editables = new WeakMap();
|
|
541
680
|
class SchemaRecord {
|
|
542
681
|
constructor(store, identifier, Mode, isEmbedded = false, embeddedType = null, embeddedPath = null) {
|
|
@@ -552,31 +691,46 @@ class SchemaRecord {
|
|
|
552
691
|
this[Legacy] = Mode[Legacy] ?? false;
|
|
553
692
|
const schema = store.schema;
|
|
554
693
|
const cache = store.cache;
|
|
555
|
-
const identityField = schema.resource(
|
|
694
|
+
const identityField = schema.resource(isEmbedded ? {
|
|
695
|
+
type: embeddedType
|
|
696
|
+
} : identifier).identity;
|
|
697
|
+
const BoundFns = new Map();
|
|
556
698
|
this[EmbeddedType] = embeddedType;
|
|
557
699
|
this[EmbeddedPath] = embeddedPath;
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
type: embeddedType
|
|
562
|
-
});
|
|
563
|
-
} else {
|
|
564
|
-
fields = schema.fields(identifier);
|
|
565
|
-
}
|
|
700
|
+
const fields = isEmbedded ? schema.fields({
|
|
701
|
+
type: embeddedType
|
|
702
|
+
}) : schema.fields(identifier);
|
|
566
703
|
const signals = new Map();
|
|
567
704
|
this[Signals] = signals;
|
|
568
705
|
const proxy = new Proxy(this, {
|
|
569
706
|
ownKeys() {
|
|
570
|
-
|
|
707
|
+
const identityKey = identityField?.name;
|
|
708
|
+
const keys = Array.from(fields.keys());
|
|
709
|
+
if (identityKey) {
|
|
710
|
+
keys.unshift(identityKey);
|
|
711
|
+
}
|
|
712
|
+
return keys;
|
|
571
713
|
},
|
|
572
714
|
has(target, prop) {
|
|
715
|
+
if (prop === Destroy || prop === Checkout) {
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
573
718
|
return fields.has(prop);
|
|
574
719
|
},
|
|
575
720
|
getOwnPropertyDescriptor(target, prop) {
|
|
576
|
-
|
|
577
|
-
|
|
721
|
+
const schemaForField = prop === identityField?.name ? identityField : fields.get(prop);
|
|
722
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
723
|
+
if (!test) {
|
|
724
|
+
throw new Error(`No field named ${String(prop)} on ${identifier.type}`);
|
|
725
|
+
}
|
|
726
|
+
})(schemaForField) : {};
|
|
727
|
+
if (isNonEnumerableProp(prop)) {
|
|
728
|
+
return {
|
|
729
|
+
writable: false,
|
|
730
|
+
enumerable: false,
|
|
731
|
+
configurable: true
|
|
732
|
+
};
|
|
578
733
|
}
|
|
579
|
-
const schemaForField = fields.get(prop);
|
|
580
734
|
switch (schemaForField.kind) {
|
|
581
735
|
case 'derived':
|
|
582
736
|
return {
|
|
@@ -584,6 +738,12 @@ class SchemaRecord {
|
|
|
584
738
|
enumerable: true,
|
|
585
739
|
configurable: true
|
|
586
740
|
};
|
|
741
|
+
case '@id':
|
|
742
|
+
return {
|
|
743
|
+
writable: identifier.id === null,
|
|
744
|
+
enumerable: true,
|
|
745
|
+
configurable: true
|
|
746
|
+
};
|
|
587
747
|
case '@local':
|
|
588
748
|
case 'field':
|
|
589
749
|
case 'attribute':
|
|
@@ -601,28 +761,18 @@ class SchemaRecord {
|
|
|
601
761
|
enumerable: true,
|
|
602
762
|
configurable: true
|
|
603
763
|
};
|
|
764
|
+
default:
|
|
765
|
+
return {
|
|
766
|
+
writable: false,
|
|
767
|
+
enumerable: false,
|
|
768
|
+
configurable: false
|
|
769
|
+
};
|
|
604
770
|
}
|
|
605
771
|
},
|
|
606
772
|
get(target, prop, receiver) {
|
|
607
773
|
if (RecordSymbols.has(prop)) {
|
|
608
774
|
return target[prop];
|
|
609
775
|
}
|
|
610
|
-
if (prop === Symbol.toStringTag) {
|
|
611
|
-
return `SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})>`;
|
|
612
|
-
}
|
|
613
|
-
if (prop === 'toString') {
|
|
614
|
-
return function () {
|
|
615
|
-
return `SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})>`;
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
if (prop === 'toHTML') {
|
|
619
|
-
return function () {
|
|
620
|
-
return `<div>SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})></div>`;
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
if (prop === Symbol.toPrimitive) {
|
|
624
|
-
return null;
|
|
625
|
-
}
|
|
626
776
|
|
|
627
777
|
// TODO make this a symbol
|
|
628
778
|
if (prop === '___notifications') {
|
|
@@ -638,18 +788,75 @@ class SchemaRecord {
|
|
|
638
788
|
if (IgnoredGlobalFields.has(prop)) {
|
|
639
789
|
return undefined;
|
|
640
790
|
}
|
|
791
|
+
|
|
792
|
+
/////////////////////////////////////////////////////////////
|
|
793
|
+
//// Note these bound function behaviors are essentially ////
|
|
794
|
+
//// built-in but overrideable derivations. ////
|
|
795
|
+
//// ////
|
|
796
|
+
//// The bar for this has to be "basic expectations of ////
|
|
797
|
+
/// an object" – very, very high ////
|
|
798
|
+
/////////////////////////////////////////////////////////////
|
|
799
|
+
|
|
800
|
+
if (prop === Symbol.toStringTag || prop === 'toString') {
|
|
801
|
+
let fn = BoundFns.get('toString');
|
|
802
|
+
if (!fn) {
|
|
803
|
+
fn = function () {
|
|
804
|
+
entangleSignal(signals, receiver, '@identity');
|
|
805
|
+
return `Record<${identifier.type}:${identifier.id} (${identifier.lid})>`;
|
|
806
|
+
};
|
|
807
|
+
BoundFns.set(prop, fn);
|
|
808
|
+
}
|
|
809
|
+
return fn;
|
|
810
|
+
}
|
|
811
|
+
if (prop === 'toHTML') {
|
|
812
|
+
let fn = BoundFns.get('toHTML');
|
|
813
|
+
if (!fn) {
|
|
814
|
+
fn = function () {
|
|
815
|
+
entangleSignal(signals, receiver, '@identity');
|
|
816
|
+
return `<span>Record<${identifier.type}:${identifier.id} (${identifier.lid})></span>`;
|
|
817
|
+
};
|
|
818
|
+
BoundFns.set(prop, fn);
|
|
819
|
+
}
|
|
820
|
+
return fn;
|
|
821
|
+
}
|
|
822
|
+
if (prop === 'toJSON') {
|
|
823
|
+
let fn = BoundFns.get('toJSON');
|
|
824
|
+
if (!fn) {
|
|
825
|
+
fn = function () {
|
|
826
|
+
const json = {};
|
|
827
|
+
for (const key in receiver) {
|
|
828
|
+
json[key] = receiver[key];
|
|
829
|
+
}
|
|
830
|
+
return json;
|
|
831
|
+
};
|
|
832
|
+
BoundFns.set(prop, fn);
|
|
833
|
+
}
|
|
834
|
+
return fn;
|
|
835
|
+
}
|
|
836
|
+
if (prop === Symbol.toPrimitive) return () => null;
|
|
837
|
+
if (prop === Symbol.iterator) {
|
|
838
|
+
let fn = BoundFns.get(Symbol.iterator);
|
|
839
|
+
if (!fn) {
|
|
840
|
+
fn = function* () {
|
|
841
|
+
for (const key in receiver) {
|
|
842
|
+
yield [key, receiver[key]];
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
BoundFns.set(Symbol.iterator, fn);
|
|
846
|
+
}
|
|
847
|
+
return fn;
|
|
848
|
+
}
|
|
641
849
|
if (prop === 'constructor') {
|
|
642
850
|
return SchemaRecord;
|
|
643
851
|
}
|
|
644
852
|
// too many things check for random symbols
|
|
645
|
-
if (typeof prop === 'symbol')
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
throw new Error(`No field named ${String(prop)} on ${type}`);
|
|
853
|
+
if (typeof prop === 'symbol') return undefined;
|
|
854
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
855
|
+
{
|
|
856
|
+
throw new Error(`No field named ${String(prop)} on ${isEmbedded ? embeddedType : identifier.type}`);
|
|
857
|
+
}
|
|
858
|
+
})() : {};
|
|
859
|
+
return undefined;
|
|
653
860
|
}
|
|
654
861
|
const field = maybeField.kind === 'alias' ? maybeField.options : maybeField;
|
|
655
862
|
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
@@ -677,58 +884,31 @@ class SchemaRecord {
|
|
|
677
884
|
return lastValue;
|
|
678
885
|
}
|
|
679
886
|
case 'field':
|
|
680
|
-
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
681
|
-
if (!test) {
|
|
682
|
-
throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
|
|
683
|
-
}
|
|
684
|
-
})(!target[Legacy]) : {};
|
|
685
887
|
entangleSignal(signals, receiver, field.name);
|
|
686
|
-
return computeField(schema, cache, target, identifier, field, propArray);
|
|
888
|
+
return computeField(schema, cache, target, identifier, field, propArray, IS_EDITABLE);
|
|
687
889
|
case 'attribute':
|
|
688
890
|
entangleSignal(signals, receiver, field.name);
|
|
689
|
-
return computeAttribute(cache, identifier, prop);
|
|
891
|
+
return computeAttribute(cache, identifier, prop, IS_EDITABLE);
|
|
690
892
|
case 'resource':
|
|
691
|
-
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
692
|
-
if (!test) {
|
|
693
|
-
throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
|
|
694
|
-
}
|
|
695
|
-
})(!target[Legacy]) : {};
|
|
696
893
|
entangleSignal(signals, receiver, field.name);
|
|
697
|
-
return computeResource(store, cache, target, identifier, field, prop);
|
|
894
|
+
return computeResource(store, cache, target, identifier, field, prop, IS_EDITABLE);
|
|
698
895
|
case 'derived':
|
|
699
896
|
return computeDerivation(schema, receiver, identifier, field, prop);
|
|
700
897
|
case 'schema-array':
|
|
701
|
-
entangleSignal(signals, receiver, field.name);
|
|
702
|
-
return computeArray(store, schema, cache, target, identifier, field, propArray, true, Mode[Editable], Mode[Legacy]);
|
|
703
898
|
case 'array':
|
|
704
|
-
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
705
|
-
if (!test) {
|
|
706
|
-
throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
|
|
707
|
-
}
|
|
708
|
-
})(!target[Legacy]) : {};
|
|
709
899
|
entangleSignal(signals, receiver, field.name);
|
|
710
|
-
return computeArray(store, schema, cache, target, identifier, field, propArray,
|
|
900
|
+
return computeArray(store, schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
|
|
711
901
|
case 'object':
|
|
712
|
-
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
713
|
-
if (!test) {
|
|
714
|
-
throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
|
|
715
|
-
}
|
|
716
|
-
})(!target[Legacy]) : {};
|
|
717
902
|
entangleSignal(signals, receiver, field.name);
|
|
718
903
|
return computeObject(schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
|
|
719
904
|
case 'schema-object':
|
|
720
|
-
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
721
|
-
if (!test) {
|
|
722
|
-
throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
|
|
723
|
-
}
|
|
724
|
-
})(!target[Legacy]) : {};
|
|
725
905
|
entangleSignal(signals, receiver, field.name);
|
|
726
906
|
// run transform, then use that value as the object to manage
|
|
727
907
|
return computeSchemaObject(store, cache, target, identifier, field, propArray, Mode[Legacy], Mode[Editable]);
|
|
728
908
|
case 'belongsTo':
|
|
729
909
|
if (field.options.linksMode) {
|
|
730
910
|
entangleSignal(signals, receiver, field.name);
|
|
731
|
-
const rawValue = cache.getRelationship(identifier, field.name);
|
|
911
|
+
const rawValue = IS_EDITABLE ? cache.getRelationship(identifier, field.name) : cache.getRemoteRelationship(identifier, field.name);
|
|
732
912
|
|
|
733
913
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
734
914
|
return rawValue.data ? store.peekRecord(rawValue.data) : null;
|
|
@@ -753,6 +933,10 @@ class SchemaRecord {
|
|
|
753
933
|
entangleSignal(signals, receiver, field.name);
|
|
754
934
|
return getLegacySupport(receiver).getBelongsTo(field.name);
|
|
755
935
|
case 'hasMany':
|
|
936
|
+
if (field.options.linksMode) {
|
|
937
|
+
entangleSignal(signals, receiver, field.name);
|
|
938
|
+
return computeHasMany(store, schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
|
|
939
|
+
}
|
|
756
940
|
if (!HAS_MODEL_PACKAGE) {
|
|
757
941
|
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
758
942
|
{
|
|
@@ -850,6 +1034,11 @@ class SchemaRecord {
|
|
|
850
1034
|
cache.setAttr(identifier, propArray, value?.slice());
|
|
851
1035
|
const peeked = peekManagedArray(self, field);
|
|
852
1036
|
if (peeked) {
|
|
1037
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1038
|
+
if (!test) {
|
|
1039
|
+
throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
|
|
1040
|
+
}
|
|
1041
|
+
})(ARRAY_SIGNAL in peeked) : {};
|
|
853
1042
|
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
854
1043
|
arrSignal.shouldReset = true;
|
|
855
1044
|
}
|
|
@@ -863,6 +1052,11 @@ class SchemaRecord {
|
|
|
863
1052
|
cache.setAttr(identifier, propArray, rawValue);
|
|
864
1053
|
const peeked = peekManagedArray(self, field);
|
|
865
1054
|
if (peeked) {
|
|
1055
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1056
|
+
if (!test) {
|
|
1057
|
+
throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
|
|
1058
|
+
}
|
|
1059
|
+
})(ARRAY_SIGNAL in peeked) : {};
|
|
866
1060
|
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
867
1061
|
arrSignal.shouldReset = true;
|
|
868
1062
|
}
|
|
@@ -877,6 +1071,11 @@ class SchemaRecord {
|
|
|
877
1071
|
cache.setAttr(identifier, propArray, arrayValue);
|
|
878
1072
|
const peeked = peekManagedArray(self, field);
|
|
879
1073
|
if (peeked) {
|
|
1074
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1075
|
+
if (!test) {
|
|
1076
|
+
throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
|
|
1077
|
+
}
|
|
1078
|
+
})(ARRAY_SIGNAL in peeked) : {};
|
|
880
1079
|
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
881
1080
|
arrSignal.shouldReset = true;
|
|
882
1081
|
}
|
|
@@ -1054,6 +1253,11 @@ class SchemaRecord {
|
|
|
1054
1253
|
if (field?.kind === 'array' || field?.kind === 'schema-array') {
|
|
1055
1254
|
const peeked = peekManagedArray(self, field);
|
|
1056
1255
|
if (peeked) {
|
|
1256
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1257
|
+
if (!test) {
|
|
1258
|
+
throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
|
|
1259
|
+
}
|
|
1260
|
+
})(ARRAY_SIGNAL in peeked) : {};
|
|
1057
1261
|
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
1058
1262
|
arrSignal.shouldReset = true;
|
|
1059
1263
|
addToTransaction(arrSignal);
|
|
@@ -1090,6 +1294,16 @@ class SchemaRecord {
|
|
|
1090
1294
|
}
|
|
1091
1295
|
// FIXME
|
|
1092
1296
|
} else if (field.kind === 'resource') ;else if (field.kind === 'hasMany') {
|
|
1297
|
+
if (field.options.linksMode) {
|
|
1298
|
+
const peeked = peekManagedArray(self, field);
|
|
1299
|
+
if (peeked) {
|
|
1300
|
+
// const arrSignal = peeked[ARRAY_SIGNAL];
|
|
1301
|
+
// arrSignal.shouldReset = true;
|
|
1302
|
+
// addToTransaction(arrSignal);
|
|
1303
|
+
peeked.notify();
|
|
1304
|
+
}
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1093
1307
|
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1094
1308
|
if (!test) {
|
|
1095
1309
|
throw new Error(`Expected to have a getLegacySupport function`);
|
|
@@ -1133,6 +1347,19 @@ class SchemaRecord {
|
|
|
1133
1347
|
break;
|
|
1134
1348
|
}
|
|
1135
1349
|
});
|
|
1350
|
+
if (macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG)) {
|
|
1351
|
+
Object.defineProperty(this, '__SHOW_ME_THE_DATA_(debug mode only)__', {
|
|
1352
|
+
enumerable: false,
|
|
1353
|
+
configurable: true,
|
|
1354
|
+
get() {
|
|
1355
|
+
const data = {};
|
|
1356
|
+
for (const key of fields.keys()) {
|
|
1357
|
+
data[key] = proxy[key];
|
|
1358
|
+
}
|
|
1359
|
+
return data;
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1136
1363
|
return proxy;
|
|
1137
1364
|
}
|
|
1138
1365
|
[Destroy]() {
|
|
@@ -1145,6 +1372,10 @@ class SchemaRecord {
|
|
|
1145
1372
|
this[RecordStore].notifications.unsubscribe(this.___notifications);
|
|
1146
1373
|
}
|
|
1147
1374
|
[Checkout]() {
|
|
1375
|
+
// IF we are already the editable record, throw an error
|
|
1376
|
+
if (this[Editable]) {
|
|
1377
|
+
throw new Error(`Cannot checkout an already editable record`);
|
|
1378
|
+
}
|
|
1148
1379
|
const editable = Editables.get(this);
|
|
1149
1380
|
if (editable) {
|
|
1150
1381
|
return Promise.resolve(editable);
|
|
@@ -1163,4 +1394,374 @@ class SchemaRecord {
|
|
|
1163
1394
|
return Promise.resolve(editableRecord);
|
|
1164
1395
|
}
|
|
1165
1396
|
}
|
|
1166
|
-
|
|
1397
|
+
function instantiateRecord(store, identifier, createArgs) {
|
|
1398
|
+
const schema = store.schema;
|
|
1399
|
+
const resourceSchema = schema.resource(identifier);
|
|
1400
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1401
|
+
if (!test) {
|
|
1402
|
+
throw new Error(`Expected a resource schema`);
|
|
1403
|
+
}
|
|
1404
|
+
})(isResourceSchema(resourceSchema)) : {};
|
|
1405
|
+
const isLegacy = resourceSchema?.legacy ?? false;
|
|
1406
|
+
const isEditable = isLegacy || store.cache.isNew(identifier);
|
|
1407
|
+
const record = new SchemaRecord(store, identifier, {
|
|
1408
|
+
[Editable]: isEditable,
|
|
1409
|
+
[Legacy]: isLegacy
|
|
1410
|
+
});
|
|
1411
|
+
if (createArgs) {
|
|
1412
|
+
Object.assign(record, createArgs);
|
|
1413
|
+
}
|
|
1414
|
+
return record;
|
|
1415
|
+
}
|
|
1416
|
+
function assertSchemaRecord(record) {
|
|
1417
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1418
|
+
if (!test) {
|
|
1419
|
+
throw new Error('Expected a SchemaRecord');
|
|
1420
|
+
}
|
|
1421
|
+
})(record && typeof record === 'object' && Destroy in record) : {};
|
|
1422
|
+
}
|
|
1423
|
+
function teardownRecord(record) {
|
|
1424
|
+
assertSchemaRecord(record);
|
|
1425
|
+
record[Destroy]();
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* @module @warp-drive-mirror/schema-record
|
|
1430
|
+
*/
|
|
1431
|
+
const Support = getOrSetGlobal('Support', new WeakMap());
|
|
1432
|
+
const ConstructorField = {
|
|
1433
|
+
type: '@constructor',
|
|
1434
|
+
name: 'constructor',
|
|
1435
|
+
kind: 'derived'
|
|
1436
|
+
};
|
|
1437
|
+
const TypeField = {
|
|
1438
|
+
type: '@identity',
|
|
1439
|
+
name: '$type',
|
|
1440
|
+
kind: 'derived',
|
|
1441
|
+
options: {
|
|
1442
|
+
key: 'type'
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
const DefaultIdentityField = {
|
|
1446
|
+
name: 'id',
|
|
1447
|
+
kind: '@id'
|
|
1448
|
+
};
|
|
1449
|
+
function _constructor(record) {
|
|
1450
|
+
let state = Support.get(record);
|
|
1451
|
+
if (!state) {
|
|
1452
|
+
state = {};
|
|
1453
|
+
Support.set(record, state);
|
|
1454
|
+
}
|
|
1455
|
+
return state._constructor = state._constructor || {
|
|
1456
|
+
name: `SchemaRecord<${recordIdentifierFor$1(record).type}>`,
|
|
1457
|
+
get modelName() {
|
|
1458
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1459
|
+
{
|
|
1460
|
+
throw new Error(`record.constructor.modelName is not available outside of legacy mode`);
|
|
1461
|
+
}
|
|
1462
|
+
})() : {};
|
|
1463
|
+
return undefined;
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
_constructor[Type] = '@constructor';
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* Utility for constructing a ResourceSchema with the recommended fields
|
|
1471
|
+
* for the Polaris experience.
|
|
1472
|
+
*
|
|
1473
|
+
* @method withDefaults
|
|
1474
|
+
* @for @warp-drive-mirror/schema-record
|
|
1475
|
+
* @static
|
|
1476
|
+
* @public
|
|
1477
|
+
* @param schema
|
|
1478
|
+
* @return {ResourceSchema}
|
|
1479
|
+
*/
|
|
1480
|
+
function withDefaults(schema) {
|
|
1481
|
+
schema.identity = schema.identity || DefaultIdentityField;
|
|
1482
|
+
|
|
1483
|
+
// because fields gets iterated in definition order,
|
|
1484
|
+
// we add TypeField to the beginning so that it will
|
|
1485
|
+
// appear right next to the identity field
|
|
1486
|
+
schema.fields.unshift(TypeField);
|
|
1487
|
+
schema.fields.push(ConstructorField);
|
|
1488
|
+
return schema;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* A derivation that computes its value from the
|
|
1493
|
+
* record's identity.
|
|
1494
|
+
*
|
|
1495
|
+
* It can be used via a derived field definition like:
|
|
1496
|
+
*
|
|
1497
|
+
* ```ts
|
|
1498
|
+
* {
|
|
1499
|
+
* kind: 'derived',
|
|
1500
|
+
* name: 'id',
|
|
1501
|
+
* type: '@identity',
|
|
1502
|
+
* options: { key: 'id' }
|
|
1503
|
+
* }
|
|
1504
|
+
* ```
|
|
1505
|
+
*
|
|
1506
|
+
* Valid keys are `'id'`, `'lid'`, `'type'`, and `'^'`.
|
|
1507
|
+
*
|
|
1508
|
+
* `^` returns the entire identifier object.
|
|
1509
|
+
*
|
|
1510
|
+
* @method fromIdentity
|
|
1511
|
+
* @for @warp-drive-mirror/schema-record
|
|
1512
|
+
* @static
|
|
1513
|
+
* @public
|
|
1514
|
+
*/
|
|
1515
|
+
|
|
1516
|
+
function fromIdentity(record, options, key) {
|
|
1517
|
+
const identifier = record[Identifier];
|
|
1518
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1519
|
+
if (!test) {
|
|
1520
|
+
throw new Error(`Cannot compute @identity for a record without an identifier`);
|
|
1521
|
+
}
|
|
1522
|
+
})(identifier) : {};
|
|
1523
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1524
|
+
if (!test) {
|
|
1525
|
+
throw new Error(`Expected to receive a key to compute @identity, but got ${String(options)}`);
|
|
1526
|
+
}
|
|
1527
|
+
})(options?.key && ['lid', 'id', 'type', '^'].includes(options.key)) : {};
|
|
1528
|
+
return options.key === '^' ? identifier : identifier[options.key];
|
|
1529
|
+
}
|
|
1530
|
+
fromIdentity[Type] = '@identity';
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Registers the default derivations for the SchemaRecord
|
|
1534
|
+
*
|
|
1535
|
+
* @method registerDerivations
|
|
1536
|
+
* @for @warp-drive-mirror/schema-record
|
|
1537
|
+
* @static
|
|
1538
|
+
* @public
|
|
1539
|
+
* @param {SchemaService} schema
|
|
1540
|
+
*/
|
|
1541
|
+
function registerDerivations(schema) {
|
|
1542
|
+
schema.registerDerivation(fromIdentity);
|
|
1543
|
+
schema.registerDerivation(_constructor);
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Wraps a derivation in a new function with Derivation signature but that looks
|
|
1547
|
+
* up the value in the cache before recomputing.
|
|
1548
|
+
*
|
|
1549
|
+
* @internal
|
|
1550
|
+
*/
|
|
1551
|
+
function makeCachedDerivation(derivation) {
|
|
1552
|
+
const memoizedDerivation = (record, options, prop) => {
|
|
1553
|
+
const signals = record[Signals];
|
|
1554
|
+
let signal = signals.get(prop);
|
|
1555
|
+
if (!signal) {
|
|
1556
|
+
signal = createCache(() => {
|
|
1557
|
+
return derivation(record, options, prop);
|
|
1558
|
+
}); // a total lie, for convenience of reusing the storage
|
|
1559
|
+
signals.set(prop, signal);
|
|
1560
|
+
}
|
|
1561
|
+
return getValue(signal);
|
|
1562
|
+
};
|
|
1563
|
+
memoizedDerivation[Type] = derivation[Type];
|
|
1564
|
+
return memoizedDerivation;
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* A SchemaService designed to work with dynamically registered schemas.
|
|
1568
|
+
*
|
|
1569
|
+
* @class SchemaService
|
|
1570
|
+
* @public
|
|
1571
|
+
*/
|
|
1572
|
+
class SchemaService {
|
|
1573
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1574
|
+
|
|
1575
|
+
constructor() {
|
|
1576
|
+
this._schemas = new Map();
|
|
1577
|
+
this._transforms = new Map();
|
|
1578
|
+
this._hashFns = new Map();
|
|
1579
|
+
this._derivations = new Map();
|
|
1580
|
+
}
|
|
1581
|
+
resourceTypes() {
|
|
1582
|
+
return Array.from(this._schemas.keys());
|
|
1583
|
+
}
|
|
1584
|
+
hasTrait(type) {
|
|
1585
|
+
return this._traits.has(type);
|
|
1586
|
+
}
|
|
1587
|
+
resourceHasTrait(resource, trait) {
|
|
1588
|
+
return this._schemas.get(resource.type).traits.has(trait);
|
|
1589
|
+
}
|
|
1590
|
+
transformation(field) {
|
|
1591
|
+
const kind = 'kind' in field ? field.kind : '<unknown kind>';
|
|
1592
|
+
const name = 'name' in field ? field.name : '<unknown name>';
|
|
1593
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1594
|
+
if (!test) {
|
|
1595
|
+
throw new Error(`'${kind}' fields cannot be transformed. Only fields of kind 'field' 'object' or 'array' can specify a transformation. Attempted to find '${field.type ?? '<unknown type>'}' on field '${name}'.`);
|
|
1596
|
+
}
|
|
1597
|
+
})(!('kind' in field) || ['field', 'object', 'array'].includes(kind)) : {};
|
|
1598
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1599
|
+
if (!test) {
|
|
1600
|
+
throw new Error(`Expected the '${kind}' field '${name}' to specify a transformation via 'field.type', but none was present`);
|
|
1601
|
+
}
|
|
1602
|
+
})(field.type) : {};
|
|
1603
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1604
|
+
if (!test) {
|
|
1605
|
+
throw new Error(`No transformation registered with name '${field.type}' for '${kind}' field '${name}'`);
|
|
1606
|
+
}
|
|
1607
|
+
})(this._transforms.has(field.type)) : {};
|
|
1608
|
+
return this._transforms.get(field.type);
|
|
1609
|
+
}
|
|
1610
|
+
derivation(field) {
|
|
1611
|
+
const kind = 'kind' in field ? field.kind : '<unknown kind>';
|
|
1612
|
+
const name = 'name' in field ? field.name : '<unknown name>';
|
|
1613
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1614
|
+
if (!test) {
|
|
1615
|
+
throw new Error(`The '${kind}' field '${name}' is not derived and so cannot be used to lookup a derivation`);
|
|
1616
|
+
}
|
|
1617
|
+
})(!('kind' in field) || kind === 'derived') : {};
|
|
1618
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1619
|
+
if (!test) {
|
|
1620
|
+
throw new Error(`Expected the '${kind}' field '${name}' to specify a derivation via 'field.type', but no value was present`);
|
|
1621
|
+
}
|
|
1622
|
+
})(field.type) : {};
|
|
1623
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1624
|
+
if (!test) {
|
|
1625
|
+
throw new Error(`No '${field.type}' derivation registered for use by the '${kind}' field '${name}'`);
|
|
1626
|
+
}
|
|
1627
|
+
})(this._derivations.has(field.type)) : {};
|
|
1628
|
+
return this._derivations.get(field.type);
|
|
1629
|
+
}
|
|
1630
|
+
hashFn(field) {
|
|
1631
|
+
const kind = 'kind' in field ? field.kind : '<unknown kind>';
|
|
1632
|
+
const name = 'name' in field ? field.name : '<unknown name>';
|
|
1633
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1634
|
+
if (!test) {
|
|
1635
|
+
throw new Error(`The '${kind}' field '${name}' is not a HashField and so cannot be used to lookup a hash function`);
|
|
1636
|
+
}
|
|
1637
|
+
})(!('kind' in field) || kind === '@hash') : {};
|
|
1638
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1639
|
+
if (!test) {
|
|
1640
|
+
throw new Error(`Expected the '${kind}' field '${name}' to specify a hash function via 'field.type', but no value was present`);
|
|
1641
|
+
}
|
|
1642
|
+
})(field.type) : {};
|
|
1643
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1644
|
+
if (!test) {
|
|
1645
|
+
throw new Error(`No '${field.type}' hash function is registered for use by the '${kind}' field '${name}'`);
|
|
1646
|
+
}
|
|
1647
|
+
})(this._hashFns.has(field.type)) : {};
|
|
1648
|
+
return this._hashFns.get(field.type);
|
|
1649
|
+
}
|
|
1650
|
+
resource(resource) {
|
|
1651
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1652
|
+
if (!test) {
|
|
1653
|
+
throw new Error(`No resource registered with name '${resource.type}'`);
|
|
1654
|
+
}
|
|
1655
|
+
})(this._schemas.has(resource.type)) : {};
|
|
1656
|
+
return this._schemas.get(resource.type).original;
|
|
1657
|
+
}
|
|
1658
|
+
registerResources(schemas) {
|
|
1659
|
+
schemas.forEach(schema => {
|
|
1660
|
+
this.registerResource(schema);
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
registerResource(schema) {
|
|
1664
|
+
const fields = new Map();
|
|
1665
|
+
const relationships = {};
|
|
1666
|
+
const attributes = {};
|
|
1667
|
+
schema.fields.forEach(field => {
|
|
1668
|
+
macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
|
|
1669
|
+
if (!test) {
|
|
1670
|
+
throw new Error(`${field.kind} is not valid inside a ResourceSchema's fields.`);
|
|
1671
|
+
}
|
|
1672
|
+
})(
|
|
1673
|
+
// @ts-expect-error we are checking for mistakes at runtime
|
|
1674
|
+
field.kind !== '@id' && field.kind !== '@hash') : {};
|
|
1675
|
+
fields.set(field.name, field);
|
|
1676
|
+
if (field.kind === 'attribute') {
|
|
1677
|
+
attributes[field.name] = field;
|
|
1678
|
+
} else if (field.kind === 'belongsTo' || field.kind === 'hasMany') {
|
|
1679
|
+
relationships[field.name] = field;
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
const traits = new Set(isResourceSchema(schema) ? schema.traits : []);
|
|
1683
|
+
traits.forEach(trait => {
|
|
1684
|
+
this._traits.add(trait);
|
|
1685
|
+
});
|
|
1686
|
+
const internalSchema = {
|
|
1687
|
+
original: schema,
|
|
1688
|
+
fields,
|
|
1689
|
+
relationships,
|
|
1690
|
+
attributes,
|
|
1691
|
+
traits
|
|
1692
|
+
};
|
|
1693
|
+
this._schemas.set(schema.type, internalSchema);
|
|
1694
|
+
}
|
|
1695
|
+
registerTransformation(transformation) {
|
|
1696
|
+
this._transforms.set(transformation[Type], transformation);
|
|
1697
|
+
}
|
|
1698
|
+
registerDerivation(derivation) {
|
|
1699
|
+
this._derivations.set(derivation[Type], makeCachedDerivation(derivation));
|
|
1700
|
+
}
|
|
1701
|
+
registerHashFn(hashFn) {
|
|
1702
|
+
this._hashFns.set(hashFn[Type], hashFn);
|
|
1703
|
+
}
|
|
1704
|
+
fields({
|
|
1705
|
+
type
|
|
1706
|
+
}) {
|
|
1707
|
+
const schema = this._schemas.get(type);
|
|
1708
|
+
if (!schema) {
|
|
1709
|
+
throw new Error(`No schema defined for ${type}`);
|
|
1710
|
+
}
|
|
1711
|
+
return schema.fields;
|
|
1712
|
+
}
|
|
1713
|
+
hasResource(resource) {
|
|
1714
|
+
return this._schemas.has(resource.type);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
if (macroCondition(getGlobalConfig().WarpDriveMirror.deprecations.ENABLE_LEGACY_SCHEMA_SERVICE)) {
|
|
1718
|
+
SchemaService.prototype.attributesDefinitionFor = function ({
|
|
1719
|
+
type
|
|
1720
|
+
}) {
|
|
1721
|
+
deprecate(`Use \`schema.fields({ type })\` instead of \`schema.attributesDefinitionFor({ type })\``, false, {
|
|
1722
|
+
id: 'ember-data-mirror:schema-service-updates',
|
|
1723
|
+
until: '6.0',
|
|
1724
|
+
for: 'ember-data-mirror',
|
|
1725
|
+
since: {
|
|
1726
|
+
available: '4.13',
|
|
1727
|
+
enabled: '5.4'
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
1730
|
+
const schema = this._schemas.get(type);
|
|
1731
|
+
if (!schema) {
|
|
1732
|
+
throw new Error(`No schema defined for ${type}`);
|
|
1733
|
+
}
|
|
1734
|
+
return schema.attributes;
|
|
1735
|
+
};
|
|
1736
|
+
SchemaService.prototype.relationshipsDefinitionFor = function ({
|
|
1737
|
+
type
|
|
1738
|
+
}) {
|
|
1739
|
+
deprecate(`Use \`schema.fields({ type })\` instead of \`schema.relationshipsDefinitionFor({ type })\``, false, {
|
|
1740
|
+
id: 'ember-data-mirror:schema-service-updates',
|
|
1741
|
+
until: '6.0',
|
|
1742
|
+
for: 'ember-data-mirror',
|
|
1743
|
+
since: {
|
|
1744
|
+
available: '4.13',
|
|
1745
|
+
enabled: '5.4'
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
const schema = this._schemas.get(type);
|
|
1749
|
+
if (!schema) {
|
|
1750
|
+
throw new Error(`No schema defined for ${type}`);
|
|
1751
|
+
}
|
|
1752
|
+
return schema.relationships;
|
|
1753
|
+
};
|
|
1754
|
+
SchemaService.prototype.doesTypeExist = function (type) {
|
|
1755
|
+
deprecate(`Use \`schema.hasResource({ type })\` instead of \`schema.doesTypeExist(type)\``, false, {
|
|
1756
|
+
id: 'ember-data-mirror:schema-service-updates',
|
|
1757
|
+
until: '6.0',
|
|
1758
|
+
for: 'ember-data-mirror',
|
|
1759
|
+
since: {
|
|
1760
|
+
available: '4.13',
|
|
1761
|
+
enabled: '5.4'
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
return this._schemas.has(type);
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
export { Checkout, SchemaService, fromIdentity, instantiateRecord, registerDerivations, teardownRecord, withDefaults };
|