@warp-drive/schema-record 0.0.0-alpha.101

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/dist/record.js ADDED
@@ -0,0 +1,1159 @@
1
+ import { macroCondition, getGlobalConfig, dependencySatisfies, importSync } from '@embroider/macros';
2
+ import { setRecordIdentifier, recordIdentifierFor } from '@ember-data/store/-private';
3
+ import { createSignal, subscribe, defineSignal, peekSignal, getSignal, Signals, entangleSignal, addToTransaction } from '@ember-data/tracking/-private';
4
+ import { STRUCTURED } from '@warp-drive/core-types/request';
5
+ import { RecordStore } from '@warp-drive/core-types/symbols';
6
+ import { getOrSetGlobal } from '@warp-drive/core-types/-private';
7
+ 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";
8
+ 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
+ // const ARRAY_SETTER_METHODS = new Set<KeyType>(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
10
+ const SYNC_PROPS = new Set(['[]', 'length']);
11
+ function isArrayGetter(prop) {
12
+ return ARRAY_GETTER_METHODS.has(prop);
13
+ }
14
+ const ARRAY_SETTER_METHODS = new Set(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
15
+ function isArraySetter(prop) {
16
+ return ARRAY_SETTER_METHODS.has(prop);
17
+ }
18
+
19
+ // function isSelfProp<T extends object>(self: T, prop: KeyType): prop is keyof T {
20
+ // return prop in self;
21
+ // }
22
+
23
+ function convertToInt(prop) {
24
+ if (typeof prop === 'symbol') return null;
25
+ const num = Number(prop);
26
+ if (isNaN(num)) return null;
27
+ return num % 1 === 0 ? num : null;
28
+ }
29
+ function safeForEach(instance, arr, store, callback, target) {
30
+ if (target === undefined) {
31
+ target = null;
32
+ }
33
+ // clone to prevent mutation
34
+ arr = arr.slice();
35
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
36
+ if (!test) {
37
+ throw new Error('`forEach` expects a function as first argument.');
38
+ }
39
+ })(typeof callback === 'function') : {};
40
+
41
+ // because we retrieveLatest above we need not worry if array is mutated during iteration
42
+ // by unloadRecord/rollbackAttributes
43
+ // push/add/removeObject may still be problematic
44
+ // but this is a more traditionally expected forEach bug.
45
+ const length = arr.length; // we need to access length to ensure we are consumed
46
+
47
+ for (let index = 0; index < length; index++) {
48
+ callback.call(target, arr[index], index, instance);
49
+ }
50
+ return instance;
51
+ }
52
+ class ManagedArray {
53
+ [SOURCE];
54
+ constructor(store, schema, cache, field, data, identifier, path, owner, isSchemaArray, editable, legacy) {
55
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
56
+ const self = this;
57
+ this[SOURCE] = data?.slice();
58
+ this[ARRAY_SIGNAL] = createSignal(this, 'length');
59
+ const IS_EDITABLE = this[Editable] = editable ?? false;
60
+ this[Legacy] = legacy;
61
+ const _SIGNAL = this[ARRAY_SIGNAL];
62
+ const boundFns = new Map();
63
+ this.identifier = identifier;
64
+ this.path = path;
65
+ this.owner = owner;
66
+ let transaction = false;
67
+ const mode = field.options?.key ?? '@identity';
68
+ const RefStorage = mode === '@identity' ? WeakMap :
69
+ // CAUTION CAUTION CAUTION
70
+ // this is a pile of lies
71
+ // the Map is Map<string, WeakRef<SchemaRecord>>
72
+ // but TS does not understand how to juggle modes like this
73
+ // internal to a method like ours without us duplicating the code
74
+ // into two separate methods.
75
+ Map;
76
+ const ManagedRecordRefs = isSchemaArray ? new RefStorage() : null;
77
+ const proxy = new Proxy(this[SOURCE], {
78
+ get(target, prop, receiver) {
79
+ if (prop === ARRAY_SIGNAL) {
80
+ return _SIGNAL;
81
+ }
82
+ if (prop === 'identifier') {
83
+ return self.identifier;
84
+ }
85
+ if (prop === 'owner') {
86
+ return self.owner;
87
+ }
88
+ const index = convertToInt(prop);
89
+ if (_SIGNAL.shouldReset && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
90
+ _SIGNAL.t = false;
91
+ _SIGNAL.shouldReset = false;
92
+ const newData = cache.getAttr(identifier, path);
93
+ if (newData && newData !== self[SOURCE]) {
94
+ self[SOURCE].length = 0;
95
+ self[SOURCE].push(...newData);
96
+ }
97
+ }
98
+ if (index !== null) {
99
+ let val;
100
+ if (mode === '@hash') {
101
+ val = target[index];
102
+ const hashField = schema.resource({
103
+ type: field.type
104
+ }).identity;
105
+ const hashFn = schema.hashFn(hashField);
106
+ val = hashFn(val, null, null);
107
+ } else {
108
+ // if mode is not @identity or @index, then access the key path.
109
+ // we should assert that `mode` is a string
110
+ // it should read directly from the cache value for that field (e.g. no derivation, no transformation)
111
+ // and, we likely should lookup the associated field and throw an error IF
112
+ // the given field does not exist OR
113
+ // the field is anything other than a GenericField or LegacyAttributeField.
114
+ if (mode !== '@identity' && mode !== '@index') {
115
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
116
+ if (!test) {
117
+ throw new Error('mode must be a string');
118
+ }
119
+ })(typeof mode === 'string') : {};
120
+ const modeField = schema.resource({
121
+ type: field.type
122
+ }).fields.find(f => f.name === mode);
123
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
124
+ if (!test) {
125
+ throw new Error('field must exist in schema');
126
+ }
127
+ })(modeField) : {};
128
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
129
+ if (!test) {
130
+ throw new Error('field must be a GenericField or LegacyAttributeField');
131
+ }
132
+ })(modeField.kind === 'field' || modeField.kind === 'attribute') : {};
133
+ }
134
+ val = mode === '@identity' ? target[index] : mode === '@index' ? '@index' : target[index][mode];
135
+ }
136
+ if (isSchemaArray) {
137
+ if (!transaction) {
138
+ subscribe(_SIGNAL);
139
+ }
140
+ if (val) {
141
+ const recordRef = ManagedRecordRefs.get(val);
142
+ let record = recordRef?.deref();
143
+ if (!record) {
144
+ const recordPath = path.slice();
145
+ // this is a dirty lie since path is string[] but really we
146
+ // should change the types for paths to `Array<string | number>`
147
+ // TODO we should allow the schema for the field to define a "key"
148
+ // for stability. Default should be `@identity` which means that
149
+ // same object reference from cache should result in same SchemaRecord
150
+ // embedded object.
151
+ recordPath.push(index);
152
+ const recordIdentifier = self.owner[Identifier] || self.owner[Parent];
153
+ record = new SchemaRecord(store, recordIdentifier, {
154
+ [Editable]: self.owner[Editable],
155
+ [Legacy]: self.owner[Legacy]
156
+ }, true, field.type, recordPath);
157
+ // if mode is not @identity or @index, then access the key path now
158
+ // to determine the key value.
159
+ // chris says we can implement this as a special kind `@hash` which
160
+ // would be a function that only has access to the cache value and not
161
+ // the record itself, so derivation is possible but intentionally limited
162
+ // and non-reactive?
163
+ ManagedRecordRefs.set(val, new WeakRef(record));
164
+ }
165
+ return record;
166
+ }
167
+ return val;
168
+ }
169
+ if (!transaction) {
170
+ subscribe(_SIGNAL);
171
+ }
172
+ if (field.type) {
173
+ const transform = schema.transformation(field);
174
+ return transform.hydrate(val, field.options ?? null, self.owner);
175
+ }
176
+ return val;
177
+ }
178
+ if (isArrayGetter(prop)) {
179
+ let fn = boundFns.get(prop);
180
+ if (fn === undefined) {
181
+ if (prop === 'forEach') {
182
+ fn = function () {
183
+ subscribe(_SIGNAL);
184
+ transaction = true;
185
+ const result = safeForEach(receiver, target, store, arguments[0], arguments[1]);
186
+ transaction = false;
187
+ return result;
188
+ };
189
+ } else {
190
+ fn = function () {
191
+ subscribe(_SIGNAL);
192
+ // array functions must run through Reflect to work properly
193
+ // binding via other means will not work.
194
+ transaction = true;
195
+ const result = Reflect.apply(target[prop], receiver, arguments);
196
+ transaction = false;
197
+ return result;
198
+ };
199
+ }
200
+ boundFns.set(prop, fn);
201
+ }
202
+ return fn;
203
+ }
204
+ if (isArraySetter(prop)) {
205
+ let fn = boundFns.get(prop);
206
+ if (fn === undefined) {
207
+ fn = function () {
208
+ if (!IS_EDITABLE) {
209
+ throw new Error(`Mutating this array via ${String(prop)} is not allowed because the record is not editable`);
210
+ }
211
+ subscribe(_SIGNAL);
212
+ transaction = true;
213
+ const result = Reflect.apply(target[prop], receiver, arguments);
214
+ transaction = false;
215
+ return result;
216
+ };
217
+ boundFns.set(prop, fn);
218
+ }
219
+ return fn;
220
+ }
221
+ return Reflect.get(target, prop, receiver);
222
+ },
223
+ set(target, prop, value, receiver) {
224
+ if (!IS_EDITABLE) {
225
+ let errorPath = identifier.type;
226
+ if (path) {
227
+ errorPath = path[path.length - 1];
228
+ }
229
+ throw new Error(`Cannot set ${String(prop)} on ${errorPath} because the record is not editable`);
230
+ }
231
+ if (prop === 'identifier') {
232
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
233
+ self.identifier = value;
234
+ return true;
235
+ }
236
+ if (prop === 'owner') {
237
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
238
+ self.owner = value;
239
+ return true;
240
+ }
241
+ const reflect = Reflect.set(target, prop, value, receiver);
242
+ if (reflect) {
243
+ if (!field.type) {
244
+ cache.setAttr(identifier, path, self[SOURCE]);
245
+ _SIGNAL.shouldReset = true;
246
+ return true;
247
+ }
248
+ let rawValue = self[SOURCE];
249
+ if (!isSchemaArray) {
250
+ const transform = schema.transformation(field);
251
+ if (!transform) {
252
+ throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
253
+ }
254
+ rawValue = self[SOURCE].map(item => transform.serialize(item, field.options ?? null, self.owner));
255
+ }
256
+ cache.setAttr(identifier, path, rawValue);
257
+ _SIGNAL.shouldReset = true;
258
+ }
259
+ return reflect;
260
+ }
261
+ });
262
+ return proxy;
263
+ }
264
+ }
265
+ const ObjectSymbols = new Set([OBJECT_SIGNAL, Parent, SOURCE, Editable, EmbeddedPath]);
266
+
267
+ // const ignoredGlobalFields = new Set<string>(['setInterval', 'nodeType', 'nodeName', 'length', 'document', STRUCTURED]);
268
+
269
+ class ManagedObject {
270
+ constructor(schema, cache, field, data, identifier, path, owner, editable, legacy) {
271
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
272
+ const self = this;
273
+ this[SOURCE] = {
274
+ ...data
275
+ };
276
+ this[OBJECT_SIGNAL] = createSignal(this, 'length');
277
+ this[Editable] = editable;
278
+ this[Legacy] = legacy;
279
+ this[Parent] = identifier;
280
+ this[EmbeddedPath] = path;
281
+ const _SIGNAL = this[OBJECT_SIGNAL];
282
+ const proxy = new Proxy(this[SOURCE], {
283
+ ownKeys() {
284
+ return Object.keys(self[SOURCE]);
285
+ },
286
+ has(target, prop) {
287
+ return prop in self[SOURCE];
288
+ },
289
+ getOwnPropertyDescriptor(target, prop) {
290
+ return {
291
+ writable: editable,
292
+ enumerable: true,
293
+ configurable: true
294
+ };
295
+ },
296
+ get(target, prop, receiver) {
297
+ if (ObjectSymbols.has(prop)) {
298
+ return self[prop];
299
+ }
300
+ if (prop === Symbol.toPrimitive) {
301
+ return null;
302
+ }
303
+ if (prop === Symbol.toStringTag) {
304
+ return `ManagedObject<${identifier.type}:${identifier.id} (${identifier.lid})>`;
305
+ }
306
+ if (prop === 'constructor') {
307
+ return Object;
308
+ }
309
+ if (prop === 'toString') {
310
+ return function () {
311
+ return `ManagedObject<${identifier.type}:${identifier.id} (${identifier.lid})>`;
312
+ };
313
+ }
314
+ if (prop === 'toHTML') {
315
+ return function () {
316
+ return '<div>ManagedObject</div>';
317
+ };
318
+ }
319
+ if (_SIGNAL.shouldReset) {
320
+ _SIGNAL.t = false;
321
+ _SIGNAL.shouldReset = false;
322
+ let newData = cache.getAttr(identifier, path);
323
+ if (newData && newData !== self[SOURCE]) {
324
+ if (field.type) {
325
+ const transform = schema.transformation(field);
326
+ newData = transform.hydrate(newData, field.options ?? null, owner);
327
+ }
328
+ self[SOURCE] = {
329
+ ...newData
330
+ }; // Add type assertion for newData
331
+ }
332
+ }
333
+ if (prop in self[SOURCE]) {
334
+ subscribe(_SIGNAL);
335
+ return self[SOURCE][prop];
336
+ }
337
+ return Reflect.get(target, prop, receiver);
338
+ },
339
+ set(target, prop, value, receiver) {
340
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
341
+ if (!test) {
342
+ throw new Error(`Cannot set read-only property '${String(prop)}' on ManagedObject`);
343
+ }
344
+ })(editable) : {};
345
+ const reflect = Reflect.set(target, prop, value, receiver);
346
+ if (!reflect) {
347
+ return false;
348
+ }
349
+ if (!field.type) {
350
+ cache.setAttr(identifier, path, self[SOURCE]);
351
+ } else {
352
+ const transform = schema.transformation(field);
353
+ const val = transform.serialize(self[SOURCE], field.options ?? null, owner);
354
+ cache.setAttr(identifier, path, val);
355
+ }
356
+ _SIGNAL.shouldReset = true;
357
+ return true;
358
+ }
359
+ });
360
+ return proxy;
361
+ }
362
+ }
363
+ const ManagedArrayMap = getOrSetGlobal('ManagedArrayMap', new Map());
364
+ const ManagedObjectMap = getOrSetGlobal('ManagedObjectMap', new Map());
365
+ function computeLocal(record, field, prop) {
366
+ let signal = peekSignal(record, prop);
367
+ if (!signal) {
368
+ signal = getSignal(record, prop, false);
369
+ signal.lastValue = field.options?.defaultValue ?? null;
370
+ }
371
+ return signal.lastValue;
372
+ }
373
+ function peekManagedArray(record, field) {
374
+ const managedArrayMapForRecord = ManagedArrayMap.get(record);
375
+ if (managedArrayMapForRecord) {
376
+ return managedArrayMapForRecord.get(field);
377
+ }
378
+ }
379
+ function peekManagedObject(record, field) {
380
+ const managedObjectMapForRecord = ManagedObjectMap.get(record);
381
+ if (managedObjectMapForRecord) {
382
+ return managedObjectMapForRecord.get(field);
383
+ }
384
+ }
385
+ function computeField(schema, cache, record, identifier, field, prop) {
386
+ const rawValue = cache.getAttr(identifier, prop);
387
+ if (!field.type) {
388
+ return rawValue;
389
+ }
390
+ const transform = schema.transformation(field);
391
+ return transform.hydrate(rawValue, field.options ?? null, record);
392
+ }
393
+ function computeArray(store, schema, cache, record, identifier, field, path, isSchemaArray, editable, legacy) {
394
+ // the thing we hand out needs to know its owner and path in a private manner
395
+ // its "address" is the parent identifier (identifier) + field name (field.name)
396
+ // in the nested object case field name here is the full dot path from root resource to this value
397
+ // its "key" is the field on the parent record
398
+ // its "owner" is the parent record
399
+
400
+ const managedArrayMapForRecord = ManagedArrayMap.get(record);
401
+ let managedArray;
402
+ if (managedArrayMapForRecord) {
403
+ managedArray = managedArrayMapForRecord.get(field);
404
+ }
405
+ if (managedArray) {
406
+ return managedArray;
407
+ } else {
408
+ const rawValue = cache.getAttr(identifier, path);
409
+ if (!rawValue) {
410
+ return null;
411
+ }
412
+ managedArray = new ManagedArray(store, schema, cache, field, rawValue, identifier, path, record, isSchemaArray, editable, legacy);
413
+ if (!managedArrayMapForRecord) {
414
+ ManagedArrayMap.set(record, new Map([[field, managedArray]]));
415
+ } else {
416
+ managedArrayMapForRecord.set(field, managedArray);
417
+ }
418
+ }
419
+ return managedArray;
420
+ }
421
+ function computeObject(schema, cache, record, identifier, field, path, editable, legacy) {
422
+ const managedObjectMapForRecord = ManagedObjectMap.get(record);
423
+ let managedObject;
424
+ if (managedObjectMapForRecord) {
425
+ managedObject = managedObjectMapForRecord.get(field);
426
+ }
427
+ if (managedObject) {
428
+ return managedObject;
429
+ } else {
430
+ let rawValue = cache.getAttr(identifier, path);
431
+ if (!rawValue) {
432
+ return null;
433
+ }
434
+ if (field.type) {
435
+ const transform = schema.transformation(field);
436
+ rawValue = transform.hydrate(rawValue, field.options ?? null, record);
437
+ }
438
+ managedObject = new ManagedObject(schema, cache, field, rawValue, identifier, path, record, editable, legacy);
439
+ if (!managedObjectMapForRecord) {
440
+ ManagedObjectMap.set(record, new Map([[field, managedObject]]));
441
+ } else {
442
+ managedObjectMapForRecord.set(field, managedObject);
443
+ }
444
+ }
445
+ return managedObject;
446
+ }
447
+ function computeSchemaObject(store, cache, record, identifier, field, path, legacy, editable) {
448
+ const schemaObjectMapForRecord = ManagedObjectMap.get(record);
449
+ let schemaObject;
450
+ if (schemaObjectMapForRecord) {
451
+ schemaObject = schemaObjectMapForRecord.get(field);
452
+ }
453
+ if (schemaObject) {
454
+ return schemaObject;
455
+ } else {
456
+ const rawValue = cache.getAttr(identifier, path);
457
+ if (!rawValue) {
458
+ return null;
459
+ }
460
+ const embeddedPath = path.slice();
461
+ schemaObject = new SchemaRecord(store, identifier, {
462
+ [Editable]: editable,
463
+ [Legacy]: legacy
464
+ }, true, field.type, embeddedPath);
465
+ }
466
+ if (!schemaObjectMapForRecord) {
467
+ ManagedObjectMap.set(record, new Map([[field, schemaObject]]));
468
+ } else {
469
+ schemaObjectMapForRecord.set(field, schemaObject);
470
+ }
471
+ return schemaObject;
472
+ }
473
+ function computeAttribute(cache, identifier, prop) {
474
+ return cache.getAttr(identifier, prop);
475
+ }
476
+ function computeDerivation(schema, record, identifier, field, prop) {
477
+ return schema.derivation(field)(record, field.options ?? null, prop);
478
+ }
479
+
480
+ // TODO probably this should just be a Document
481
+ // but its separate until we work out the lid situation
482
+ class ResourceRelationship {
483
+ constructor(store, cache, parent, identifier, field, name) {
484
+ const rawValue = cache.getRelationship(identifier, name);
485
+
486
+ // TODO setup true lids for relationship documents
487
+ // @ts-expect-error we need to give relationship documents a lid
488
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
489
+ this.lid = rawValue.lid ?? rawValue.links?.self ?? `relationship:${identifier.lid}.${name}`;
490
+ this.data = rawValue.data ? store.peekRecord(rawValue.data) : null;
491
+ this.name = name;
492
+ if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
493
+ this.links = Object.freeze(Object.assign({}, rawValue.links));
494
+ this.meta = Object.freeze(Object.assign({}, rawValue.meta));
495
+ } else {
496
+ this.links = rawValue.links ?? {};
497
+ this.meta = rawValue.meta ?? {};
498
+ }
499
+ this[RecordStore] = store;
500
+ this[Parent] = parent;
501
+ }
502
+ fetch(options) {
503
+ const url = options?.url ?? getHref(this.links.related) ?? getHref(this.links.self) ?? null;
504
+ if (!url) {
505
+ throw new Error(`Cannot ${options?.method ?? 'fetch'} ${this[Parent][Identifier].type}.${String(this.name)} because it has no related link`);
506
+ }
507
+ const request = Object.assign({
508
+ url,
509
+ method: 'GET'
510
+ }, options);
511
+ return this[RecordStore].request(request);
512
+ }
513
+ }
514
+ defineSignal(ResourceRelationship.prototype, 'data');
515
+ defineSignal(ResourceRelationship.prototype, 'links');
516
+ defineSignal(ResourceRelationship.prototype, 'meta');
517
+ function getHref(link) {
518
+ if (!link) {
519
+ return null;
520
+ }
521
+ if (typeof link === 'string') {
522
+ return link;
523
+ }
524
+ return link.href;
525
+ }
526
+ function computeResource(store, cache, parent, identifier, field, prop) {
527
+ if (field.kind !== 'resource') {
528
+ throw new Error(`The schema for ${identifier.type}.${String(prop)} is not a resource relationship`);
529
+ }
530
+ return new ResourceRelationship(store, cache, parent, identifier, field, prop);
531
+ }
532
+ const HAS_MODEL_PACKAGE = dependencySatisfies('@ember-data/model', '*');
533
+ const getLegacySupport = macroCondition(dependencySatisfies('@ember-data/model', '*')) ? importSync('@ember-data/model/-private').lookupLegacySupport : null;
534
+ const IgnoredGlobalFields = new Set(['length', 'nodeType', 'then', 'setInterval', 'document', STRUCTURED]);
535
+ const symbolList = [Destroy, RecordStore, Identifier, Editable, Parent, Checkout, Legacy, Signals, EmbeddedPath, EmbeddedType];
536
+ const RecordSymbols = new Set(symbolList);
537
+ function isPathMatch(a, b) {
538
+ return a.length === b.length && a.every((v, i) => v === b[i]);
539
+ }
540
+ const Editables = new WeakMap();
541
+ class SchemaRecord {
542
+ constructor(store, identifier, Mode, isEmbedded = false, embeddedType = null, embeddedPath = null) {
543
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
544
+ const self = this;
545
+ this[RecordStore] = store;
546
+ if (isEmbedded) {
547
+ this[Parent] = identifier;
548
+ } else {
549
+ this[Identifier] = identifier;
550
+ }
551
+ const IS_EDITABLE = this[Editable] = Mode[Editable] ?? false;
552
+ this[Legacy] = Mode[Legacy] ?? false;
553
+ const schema = store.schema;
554
+ const cache = store.cache;
555
+ const identityField = schema.resource(identifier).identity;
556
+ this[EmbeddedType] = embeddedType;
557
+ this[EmbeddedPath] = embeddedPath;
558
+ let fields;
559
+ if (isEmbedded) {
560
+ fields = schema.fields({
561
+ type: embeddedType
562
+ });
563
+ } else {
564
+ fields = schema.fields(identifier);
565
+ }
566
+ const signals = new Map();
567
+ this[Signals] = signals;
568
+ const proxy = new Proxy(this, {
569
+ ownKeys() {
570
+ return Array.from(fields.keys());
571
+ },
572
+ has(target, prop) {
573
+ return fields.has(prop);
574
+ },
575
+ getOwnPropertyDescriptor(target, prop) {
576
+ if (!fields.has(prop)) {
577
+ throw new Error(`No field named ${String(prop)} on ${identifier.type}`);
578
+ }
579
+ const schemaForField = fields.get(prop);
580
+ switch (schemaForField.kind) {
581
+ case 'derived':
582
+ return {
583
+ writable: false,
584
+ enumerable: true,
585
+ configurable: true
586
+ };
587
+ case '@local':
588
+ case 'field':
589
+ case 'attribute':
590
+ case 'resource':
591
+ case 'alias':
592
+ case 'belongsTo':
593
+ case 'hasMany':
594
+ case 'collection':
595
+ case 'schema-array':
596
+ case 'array':
597
+ case 'schema-object':
598
+ case 'object':
599
+ return {
600
+ writable: IS_EDITABLE,
601
+ enumerable: true,
602
+ configurable: true
603
+ };
604
+ }
605
+ },
606
+ get(target, prop, receiver) {
607
+ if (RecordSymbols.has(prop)) {
608
+ return target[prop];
609
+ }
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
+
627
+ // TODO make this a symbol
628
+ if (prop === '___notifications') {
629
+ return target.___notifications;
630
+ }
631
+
632
+ // SchemaRecord reserves use of keys that begin with these characters
633
+ // for its own usage.
634
+ // _, @, $, *
635
+
636
+ const maybeField = prop === identityField?.name ? identityField : fields.get(prop);
637
+ if (!maybeField) {
638
+ if (IgnoredGlobalFields.has(prop)) {
639
+ return undefined;
640
+ }
641
+ if (prop === 'constructor') {
642
+ return SchemaRecord;
643
+ }
644
+ // too many things check for random symbols
645
+ if (typeof prop === 'symbol') {
646
+ return undefined;
647
+ }
648
+ let type = identifier.type;
649
+ if (isEmbedded) {
650
+ type = embeddedType;
651
+ }
652
+ throw new Error(`No field named ${String(prop)} on ${type}`);
653
+ }
654
+ const field = maybeField.kind === 'alias' ? maybeField.options : maybeField;
655
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
656
+ if (!test) {
657
+ throw new Error(`Alias fields cannot alias '@id' '@local' '@hash' or 'derived' fields`);
658
+ }
659
+ })(maybeField.kind !== 'alias' || !['@id', '@local', '@hash', 'derived'].includes(maybeField.options.kind)) : {};
660
+ const propArray = isEmbedded ? embeddedPath.slice() : [];
661
+ // we use the field.name instead of prop here because we want to use the cache-path not
662
+ // the record path.
663
+ propArray.push(field.name);
664
+ // propArray.push(prop as string);
665
+
666
+ switch (field.kind) {
667
+ case '@id':
668
+ entangleSignal(signals, receiver, '@identity');
669
+ return identifier.id;
670
+ case '@hash':
671
+ // TODO pass actual cache value not {}
672
+ return schema.hashFn(field)({}, field.options ?? null, field.name ?? null);
673
+ case '@local':
674
+ {
675
+ const lastValue = computeLocal(receiver, field, prop);
676
+ entangleSignal(signals, receiver, prop);
677
+ return lastValue;
678
+ }
679
+ case 'field':
680
+ macroCondition(getGlobalConfig().WarpDrive.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
+ entangleSignal(signals, receiver, field.name);
686
+ return computeField(schema, cache, target, identifier, field, propArray);
687
+ case 'attribute':
688
+ entangleSignal(signals, receiver, field.name);
689
+ return computeAttribute(cache, identifier, prop);
690
+ case 'resource':
691
+ macroCondition(getGlobalConfig().WarpDrive.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
+ entangleSignal(signals, receiver, field.name);
697
+ return computeResource(store, cache, target, identifier, field, prop);
698
+ case 'derived':
699
+ return computeDerivation(schema, receiver, identifier, field, prop);
700
+ 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
+ case 'array':
704
+ macroCondition(getGlobalConfig().WarpDrive.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
+ entangleSignal(signals, receiver, field.name);
710
+ return computeArray(store, schema, cache, target, identifier, field, propArray, false, Mode[Editable], Mode[Legacy]);
711
+ case 'object':
712
+ macroCondition(getGlobalConfig().WarpDrive.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
+ entangleSignal(signals, receiver, field.name);
718
+ return computeObject(schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
719
+ case 'schema-object':
720
+ macroCondition(getGlobalConfig().WarpDrive.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
+ entangleSignal(signals, receiver, field.name);
726
+ // run transform, then use that value as the object to manage
727
+ return computeSchemaObject(store, cache, target, identifier, field, propArray, Mode[Legacy], Mode[Editable]);
728
+ case 'belongsTo':
729
+ if (!HAS_MODEL_PACKAGE) {
730
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
731
+ {
732
+ throw new Error(`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.`);
733
+ }
734
+ })() : {};
735
+ }
736
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
737
+ if (!test) {
738
+ throw new Error(`Expected to have a getLegacySupport function`);
739
+ }
740
+ })(getLegacySupport) : {};
741
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
742
+ if (!test) {
743
+ throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
744
+ }
745
+ })(Mode[Legacy]) : {};
746
+ entangleSignal(signals, receiver, field.name);
747
+ return getLegacySupport(receiver).getBelongsTo(field.name);
748
+ case 'hasMany':
749
+ if (!HAS_MODEL_PACKAGE) {
750
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
751
+ {
752
+ throw new Error(`Cannot use hasMany fields in your schema unless @ember-data/model is installed to provide legacy model support. ${field.name} should likely be migrated to be a collection field.`);
753
+ }
754
+ })() : {};
755
+ }
756
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
757
+ if (!test) {
758
+ throw new Error(`Expected to have a getLegacySupport function`);
759
+ }
760
+ })(getLegacySupport) : {};
761
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
762
+ if (!test) {
763
+ throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
764
+ }
765
+ })(Mode[Legacy]) : {};
766
+ entangleSignal(signals, receiver, field.name);
767
+ return getLegacySupport(receiver).getHasMany(field.name);
768
+ default:
769
+ throw new Error(`Field '${String(prop)}' on '${identifier.type}' has the unknown kind '${field.kind}'`);
770
+ }
771
+ },
772
+ set(target, prop, value, receiver) {
773
+ if (!IS_EDITABLE) {
774
+ const type = isEmbedded ? embeddedType : identifier.type;
775
+ throw new Error(`Cannot set ${String(prop)} on ${type} because the record is not editable`);
776
+ }
777
+ const maybeField = prop === identityField?.name ? identityField : fields.get(prop);
778
+ if (!maybeField) {
779
+ const type = isEmbedded ? embeddedType : identifier.type;
780
+ throw new Error(`There is no field named ${String(prop)} on ${type}`);
781
+ }
782
+ const field = maybeField.kind === 'alias' ? maybeField.options : maybeField;
783
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
784
+ if (!test) {
785
+ throw new Error(`Alias fields cannot alias '@id' '@local' '@hash' or 'derived' fields`);
786
+ }
787
+ })(maybeField.kind !== 'alias' || !['@id', '@local', '@hash', 'derived'].includes(maybeField.options.kind)) : {};
788
+ const propArray = isEmbedded ? embeddedPath.slice() : [];
789
+ // we use the field.name instead of prop here because we want to use the cache-path not
790
+ // the record path.
791
+ propArray.push(field.name);
792
+ // propArray.push(prop as string);
793
+
794
+ switch (field.kind) {
795
+ case '@id':
796
+ {
797
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
798
+ if (!test) {
799
+ throw new Error(`Expected to receive a string id`);
800
+ }
801
+ })(typeof value === 'string' && value.length) : {};
802
+ const normalizedId = String(value);
803
+ const didChange = normalizedId !== identifier.id;
804
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
805
+ if (!test) {
806
+ throw new Error(`Cannot set ${identifier.type} record's id to ${normalizedId}, because id is already ${identifier.id}`);
807
+ }
808
+ })(!didChange || identifier.id === null) : {};
809
+ if (normalizedId !== null && didChange) {
810
+ store._instanceCache.setRecordId(identifier, normalizedId);
811
+ store.notifications.notify(identifier, 'identity');
812
+ }
813
+ return true;
814
+ }
815
+ case '@local':
816
+ {
817
+ const signal = getSignal(receiver, prop, true);
818
+ if (signal.lastValue !== value) {
819
+ signal.lastValue = value;
820
+ addToTransaction(signal);
821
+ }
822
+ return true;
823
+ }
824
+ case 'field':
825
+ {
826
+ if (!field.type) {
827
+ cache.setAttr(identifier, propArray, value);
828
+ return true;
829
+ }
830
+ const transform = schema.transformation(field);
831
+ const rawValue = transform.serialize(value, field.options ?? null, target);
832
+ cache.setAttr(identifier, propArray, rawValue);
833
+ return true;
834
+ }
835
+ case 'attribute':
836
+ {
837
+ cache.setAttr(identifier, propArray, value);
838
+ return true;
839
+ }
840
+ case 'array':
841
+ {
842
+ if (!field.type) {
843
+ cache.setAttr(identifier, propArray, value?.slice());
844
+ const peeked = peekManagedArray(self, field);
845
+ if (peeked) {
846
+ const arrSignal = peeked[ARRAY_SIGNAL];
847
+ arrSignal.shouldReset = true;
848
+ }
849
+ if (!Array.isArray(value)) {
850
+ ManagedArrayMap.delete(target);
851
+ }
852
+ return true;
853
+ }
854
+ const transform = schema.transformation(field);
855
+ const rawValue = value.map(item => transform.serialize(item, field.options ?? null, target));
856
+ cache.setAttr(identifier, propArray, rawValue);
857
+ const peeked = peekManagedArray(self, field);
858
+ if (peeked) {
859
+ const arrSignal = peeked[ARRAY_SIGNAL];
860
+ arrSignal.shouldReset = true;
861
+ }
862
+ return true;
863
+ }
864
+ case 'schema-array':
865
+ {
866
+ const arrayValue = value?.slice();
867
+ if (!Array.isArray(arrayValue)) {
868
+ ManagedArrayMap.delete(target);
869
+ }
870
+ cache.setAttr(identifier, propArray, arrayValue);
871
+ const peeked = peekManagedArray(self, field);
872
+ if (peeked) {
873
+ const arrSignal = peeked[ARRAY_SIGNAL];
874
+ arrSignal.shouldReset = true;
875
+ }
876
+ if (!Array.isArray(value)) {
877
+ ManagedArrayMap.delete(target);
878
+ }
879
+ return true;
880
+ }
881
+ case 'object':
882
+ {
883
+ if (!field.type) {
884
+ let newValue = value;
885
+ if (value !== null) {
886
+ newValue = {
887
+ ...value
888
+ };
889
+ } else {
890
+ ManagedObjectMap.delete(target);
891
+ }
892
+ cache.setAttr(identifier, propArray, newValue);
893
+ const peeked = peekManagedObject(self, field);
894
+ if (peeked) {
895
+ const objSignal = peeked[OBJECT_SIGNAL];
896
+ objSignal.shouldReset = true;
897
+ }
898
+ return true;
899
+ }
900
+ const transform = schema.transformation(field);
901
+ const rawValue = transform.serialize({
902
+ ...value
903
+ }, field.options ?? null, target);
904
+ cache.setAttr(identifier, propArray, rawValue);
905
+ const peeked = peekManagedObject(self, field);
906
+ if (peeked) {
907
+ const objSignal = peeked[OBJECT_SIGNAL];
908
+ objSignal.shouldReset = true;
909
+ }
910
+ return true;
911
+ }
912
+ case 'schema-object':
913
+ {
914
+ let newValue = value;
915
+ if (value !== null) {
916
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
917
+ if (!test) {
918
+ throw new Error(`Expected value to be an object`);
919
+ }
920
+ })(typeof value === 'object') : {};
921
+ newValue = {
922
+ ...value
923
+ };
924
+ const schemaFields = schema.fields({
925
+ type: field.type
926
+ });
927
+ for (const key of Object.keys(newValue)) {
928
+ if (!schemaFields.has(key)) {
929
+ throw new Error(`Field ${key} does not exist on schema object ${field.type}`);
930
+ }
931
+ }
932
+ } else {
933
+ ManagedObjectMap.delete(target);
934
+ }
935
+ cache.setAttr(identifier, propArray, newValue);
936
+ // const peeked = peekManagedObject(self, field);
937
+ // if (peeked) {
938
+ // const objSignal = peeked[OBJECT_SIGNAL];
939
+ // objSignal.shouldReset = true;
940
+ // }
941
+ return true;
942
+ }
943
+ case 'derived':
944
+ {
945
+ throw new Error(`Cannot set ${String(prop)} on ${identifier.type} because it is derived`);
946
+ }
947
+ case 'belongsTo':
948
+ if (!HAS_MODEL_PACKAGE) {
949
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
950
+ {
951
+ throw new Error(`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.`);
952
+ }
953
+ })() : {};
954
+ }
955
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
956
+ if (!test) {
957
+ throw new Error(`Expected to have a getLegacySupport function`);
958
+ }
959
+ })(getLegacySupport) : {};
960
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
961
+ if (!test) {
962
+ throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
963
+ }
964
+ })(Mode[Legacy]) : {};
965
+ store._join(() => {
966
+ getLegacySupport(receiver).setDirtyBelongsTo(field.name, value);
967
+ });
968
+ return true;
969
+ case 'hasMany':
970
+ if (!HAS_MODEL_PACKAGE) {
971
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
972
+ {
973
+ throw new Error(`Cannot use hasMany fields in your schema unless @ember-data/model is installed to provide legacy model support. ${field.name} should likely be migrated to be a collection field.`);
974
+ }
975
+ })() : {};
976
+ }
977
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
978
+ if (!test) {
979
+ throw new Error(`Expected to have a getLegacySupport function`);
980
+ }
981
+ })(getLegacySupport) : {};
982
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
983
+ if (!test) {
984
+ throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
985
+ }
986
+ })(Mode[Legacy]) : {};
987
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
988
+ if (!test) {
989
+ throw new Error(`You must pass an array of records to set a hasMany relationship`);
990
+ }
991
+ })(Array.isArray(value)) : {};
992
+ store._join(() => {
993
+ const support = getLegacySupport(receiver);
994
+ const manyArray = support.getManyArray(field.name);
995
+ manyArray.splice(0, manyArray.length, ...value);
996
+ });
997
+ return true;
998
+ default:
999
+ throw new Error(`Unknown field kind ${field.kind}`);
1000
+ }
1001
+ }
1002
+ });
1003
+
1004
+ // what signal do we need for embedded record?
1005
+ this.___notifications = store.notifications.subscribe(identifier, (_, type, key) => {
1006
+ switch (type) {
1007
+ case 'identity':
1008
+ {
1009
+ if (isEmbedded || !identityField) return; // base paths never apply to embedded records
1010
+
1011
+ if (identityField.name && identityField.kind === '@id') {
1012
+ const signal = signals.get('@identity');
1013
+ if (signal) {
1014
+ addToTransaction(signal);
1015
+ }
1016
+ }
1017
+ break;
1018
+ }
1019
+ case 'attributes':
1020
+ if (key) {
1021
+ if (Array.isArray(key)) {
1022
+ if (!isEmbedded) return; // deep paths will be handled by embedded records
1023
+ // TODO we should have the notification manager
1024
+ // ensure it is safe for each callback to mutate this array
1025
+ if (isPathMatch(embeddedPath, key)) {
1026
+ // handle the notification
1027
+ // TODO we should likely handle this notification here
1028
+ // also we should add a LOGGING flag
1029
+ // eslint-disable-next-line no-console
1030
+ console.warn(`Notification unhandled for ${key.join(',')} on ${identifier.type}`, self);
1031
+ return;
1032
+ }
1033
+
1034
+ // TODO we should add a LOGGING flag
1035
+ // console.log(`Deep notification skipped for ${key.join('.')} on ${identifier.type}`, self);
1036
+ // deep notify the key path
1037
+ } else {
1038
+ if (isEmbedded) return; // base paths never apply to embedded records
1039
+
1040
+ // TODO determine what LOGGING flag to wrap this in if any
1041
+ // console.log(`Notification for ${key} on ${identifier.type}`, self);
1042
+ const signal = signals.get(key);
1043
+ if (signal) {
1044
+ addToTransaction(signal);
1045
+ }
1046
+ const field = fields.get(key);
1047
+ if (field?.kind === 'array' || field?.kind === 'schema-array') {
1048
+ const peeked = peekManagedArray(self, field);
1049
+ if (peeked) {
1050
+ const arrSignal = peeked[ARRAY_SIGNAL];
1051
+ arrSignal.shouldReset = true;
1052
+ addToTransaction(arrSignal);
1053
+ }
1054
+ }
1055
+ if (field?.kind === 'object') {
1056
+ const peeked = peekManagedObject(self, field);
1057
+ if (peeked) {
1058
+ const objSignal = peeked[OBJECT_SIGNAL];
1059
+ objSignal.shouldReset = true;
1060
+ addToTransaction(objSignal);
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+ break;
1066
+ case 'relationships':
1067
+ if (key) {
1068
+ if (Array.isArray(key)) ;else {
1069
+ if (isEmbedded) return; // base paths never apply to embedded records
1070
+
1071
+ const field = fields.get(key);
1072
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1073
+ if (!test) {
1074
+ throw new Error(`Expected relationshp ${key} to be the name of a field`);
1075
+ }
1076
+ })(field) : {};
1077
+ if (field.kind === 'belongsTo') {
1078
+ // TODO determine what LOGGING flag to wrap this in if any
1079
+ // console.log(`Notification for ${key} on ${identifier.type}`, self);
1080
+ const signal = signals.get(key);
1081
+ if (signal) {
1082
+ addToTransaction(signal);
1083
+ }
1084
+ // FIXME
1085
+ } else if (field.kind === 'resource') ;else if (field.kind === 'hasMany') {
1086
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1087
+ if (!test) {
1088
+ throw new Error(`Expected to have a getLegacySupport function`);
1089
+ }
1090
+ })(getLegacySupport) : {};
1091
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1092
+ if (!test) {
1093
+ throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
1094
+ }
1095
+ })(Mode[Legacy]) : {};
1096
+ const support = getLegacySupport(proxy);
1097
+ const manyArray = support && support._manyArrayCache[key];
1098
+ const hasPromise = support && support._relationshipPromisesCache[key];
1099
+ if (manyArray && hasPromise) {
1100
+ // do nothing, we will notify the ManyArray directly
1101
+ // once the fetch has completed.
1102
+ return;
1103
+ }
1104
+ if (manyArray) {
1105
+ manyArray.notify();
1106
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1107
+ if (!test) {
1108
+ throw new Error(`Expected options to exist on relationship meta`);
1109
+ }
1110
+ })(field.options) : {};
1111
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1112
+ if (!test) {
1113
+ throw new Error(`Expected async to exist on relationship meta options`);
1114
+ }
1115
+ })('async' in field.options) : {};
1116
+ if (field.options.async) {
1117
+ const signal = signals.get(key);
1118
+ if (signal) {
1119
+ addToTransaction(signal);
1120
+ }
1121
+ }
1122
+ }
1123
+ } else if (field.kind === 'collection') ;
1124
+ }
1125
+ }
1126
+ break;
1127
+ }
1128
+ });
1129
+ return proxy;
1130
+ }
1131
+ [Destroy]() {
1132
+ if (this[Legacy]) {
1133
+ // @ts-expect-error
1134
+ this.isDestroying = true;
1135
+ // @ts-expect-error
1136
+ this.isDestroyed = true;
1137
+ }
1138
+ this[RecordStore].notifications.unsubscribe(this.___notifications);
1139
+ }
1140
+ [Checkout]() {
1141
+ const editable = Editables.get(this);
1142
+ if (editable) {
1143
+ return Promise.resolve(editable);
1144
+ }
1145
+ const embeddedType = this[EmbeddedType];
1146
+ const embeddedPath = this[EmbeddedPath];
1147
+ const isEmbedded = embeddedType !== null && embeddedPath !== null;
1148
+ if (isEmbedded) {
1149
+ throw new Error(`Cannot checkout an embedded record (yet)`);
1150
+ }
1151
+ const editableRecord = new SchemaRecord(this[RecordStore], this[Identifier], {
1152
+ [Editable]: true,
1153
+ [Legacy]: this[Legacy]
1154
+ }, isEmbedded, embeddedType, embeddedPath);
1155
+ setRecordIdentifier(editableRecord, recordIdentifierFor(this));
1156
+ return Promise.resolve(editableRecord);
1157
+ }
1158
+ }
1159
+ export { Checkout, Editable, Legacy, SchemaRecord };