@warp-drive-mirror/schema-record 0.0.0-beta.9 → 0.0.1

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 CHANGED
@@ -1,18 +1,21 @@
1
1
  import { macroCondition, getGlobalConfig, dependencySatisfies, importSync } from '@embroider/macros';
2
+ import { setRecordIdentifier, recordIdentifierFor } from '@ember-data-mirror/store/-private';
2
3
  import { createSignal, subscribe, defineSignal, peekSignal, getSignal, Signals, entangleSignal, addToTransaction } from '@ember-data-mirror/tracking/-private';
3
4
  import { STRUCTURED } from '@warp-drive-mirror/core-types/request';
4
5
  import { RecordStore } from '@warp-drive-mirror/core-types/symbols';
5
6
  import { getOrSetGlobal } from '@warp-drive-mirror/core-types/-private';
6
- import { S as SOURCE, A as ARRAY_SIGNAL, I as Identifier, E as Editable, L as Legacy, O as OBJECT_SIGNAL, P as Parent, D as Destroy, C as Checkout, a as EmbeddedPath, b as EmbeddedType } from "./symbols-DqoS4ybV.js";
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";
7
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']);
8
9
  // const ARRAY_SETTER_METHODS = new Set<KeyType>(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
9
10
  const SYNC_PROPS = new Set(['[]', 'length']);
10
11
  function isArrayGetter(prop) {
11
12
  return ARRAY_GETTER_METHODS.has(prop);
12
13
  }
13
- // function isArraySetter<T>(prop: KeyType): prop is keyof Array<T> {
14
- // return ARRAY_SETTER_METHODS.has(prop);
15
- // }
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
+
16
19
  // function isSelfProp<T extends object>(self: T, prop: KeyType): prop is keyof T {
17
20
  // return prop in self;
18
21
  // }
@@ -29,7 +32,7 @@ function safeForEach(instance, arr, store, callback, target) {
29
32
  }
30
33
  // clone to prevent mutation
31
34
  arr = arr.slice();
32
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
35
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
33
36
  if (!test) {
34
37
  throw new Error('`forEach` expects a function as first argument.');
35
38
  }
@@ -48,14 +51,16 @@ function safeForEach(instance, arr, store, callback, target) {
48
51
  }
49
52
  class ManagedArray {
50
53
  [SOURCE];
51
- constructor(store, schema, cache, field, data, address, path, owner, isSchemaArray) {
54
+ constructor(store, schema, cache, field, data, identifier, path, owner, isSchemaArray, editable, legacy) {
52
55
  // eslint-disable-next-line @typescript-eslint/no-this-alias
53
56
  const self = this;
54
57
  this[SOURCE] = data?.slice();
55
58
  this[ARRAY_SIGNAL] = createSignal(this, 'length');
59
+ const IS_EDITABLE = this[Editable] = editable ?? false;
60
+ this[Legacy] = legacy;
56
61
  const _SIGNAL = this[ARRAY_SIGNAL];
57
62
  const boundFns = new Map();
58
- this.address = address;
63
+ this.identifier = identifier;
59
64
  this.path = path;
60
65
  this.owner = owner;
61
66
  let transaction = false;
@@ -74,8 +79,8 @@ class ManagedArray {
74
79
  if (prop === ARRAY_SIGNAL) {
75
80
  return _SIGNAL;
76
81
  }
77
- if (prop === 'address') {
78
- return self.address;
82
+ if (prop === 'identifier') {
83
+ return self.identifier;
79
84
  }
80
85
  if (prop === 'owner') {
81
86
  return self.owner;
@@ -84,7 +89,7 @@ class ManagedArray {
84
89
  if (_SIGNAL.shouldReset && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
85
90
  _SIGNAL.t = false;
86
91
  _SIGNAL.shouldReset = false;
87
- const newData = cache.getAttr(address, path);
92
+ const newData = cache.getAttr(identifier, path);
88
93
  if (newData && newData !== self[SOURCE]) {
89
94
  self[SOURCE].length = 0;
90
95
  self[SOURCE].push(...newData);
@@ -107,7 +112,7 @@ class ManagedArray {
107
112
  // the given field does not exist OR
108
113
  // the field is anything other than a GenericField or LegacyAttributeField.
109
114
  if (mode !== '@identity' && mode !== '@index') {
110
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
115
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
111
116
  if (!test) {
112
117
  throw new Error('mode must be a string');
113
118
  }
@@ -115,12 +120,12 @@ class ManagedArray {
115
120
  const modeField = schema.resource({
116
121
  type: field.type
117
122
  }).fields.find(f => f.name === mode);
118
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
123
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
119
124
  if (!test) {
120
125
  throw new Error('field must exist in schema');
121
126
  }
122
127
  })(modeField) : {};
123
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
128
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
124
129
  if (!test) {
125
130
  throw new Error('field must be a GenericField or LegacyAttributeField');
126
131
  }
@@ -144,7 +149,8 @@ class ManagedArray {
144
149
  // same object reference from cache should result in same SchemaRecord
145
150
  // embedded object.
146
151
  recordPath.push(index);
147
- record = new SchemaRecord(store, self.owner[Identifier], {
152
+ const recordIdentifier = self.owner[Identifier] || self.owner[Parent];
153
+ record = new SchemaRecord(store, recordIdentifier, {
148
154
  [Editable]: self.owner[Editable],
149
155
  [Legacy]: self.owner[Legacy]
150
156
  }, true, field.type, recordPath);
@@ -195,12 +201,36 @@ class ManagedArray {
195
201
  }
196
202
  return fn;
197
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
+ }
198
221
  return Reflect.get(target, prop, receiver);
199
222
  },
200
223
  set(target, prop, value, receiver) {
201
- if (prop === 'address') {
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') {
202
232
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
203
- self.address = value;
233
+ self.identifier = value;
204
234
  return true;
205
235
  }
206
236
  if (prop === 'owner') {
@@ -211,7 +241,7 @@ class ManagedArray {
211
241
  const reflect = Reflect.set(target, prop, value, receiver);
212
242
  if (reflect) {
213
243
  if (!field.type) {
214
- cache.setAttr(address, path, self[SOURCE]);
244
+ cache.setAttr(identifier, path, self[SOURCE]);
215
245
  _SIGNAL.shouldReset = true;
216
246
  return true;
217
247
  }
@@ -219,11 +249,11 @@ class ManagedArray {
219
249
  if (!isSchemaArray) {
220
250
  const transform = schema.transformation(field);
221
251
  if (!transform) {
222
- throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`);
252
+ throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
223
253
  }
224
254
  rawValue = self[SOURCE].map(item => transform.serialize(item, field.options ?? null, self.owner));
225
255
  }
226
- cache.setAttr(address, path, rawValue);
256
+ cache.setAttr(identifier, path, rawValue);
227
257
  _SIGNAL.shouldReset = true;
228
258
  }
229
259
  return reflect;
@@ -232,41 +262,53 @@ class ManagedArray {
232
262
  return proxy;
233
263
  }
234
264
  }
235
- const ignoredGlobalFields = new Set(['constructor', 'setInterval', 'nodeType', 'length']);
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
+
236
269
  class ManagedObject {
237
- [SOURCE];
238
- constructor(store, schema, cache, field, data, address, key, owner, isSchemaObject) {
270
+ constructor(schema, cache, field, data, identifier, path, owner, editable, legacy) {
239
271
  // eslint-disable-next-line @typescript-eslint/no-this-alias
240
272
  const self = this;
241
273
  this[SOURCE] = {
242
274
  ...data
243
275
  };
244
276
  this[OBJECT_SIGNAL] = createSignal(this, 'length');
277
+ this[Editable] = editable;
278
+ this[Legacy] = legacy;
279
+ this[Parent] = identifier;
280
+ this[EmbeddedPath] = path;
245
281
  const _SIGNAL = this[OBJECT_SIGNAL];
246
- // const boundFns = new Map<KeyType, ProxiedMethod>();
247
- this.address = address;
248
- this.key = key;
249
- this.owner = owner;
250
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
+ },
251
296
  get(target, prop, receiver) {
252
- if (prop === OBJECT_SIGNAL) {
253
- return _SIGNAL;
297
+ if (ObjectSymbols.has(prop)) {
298
+ return self[prop];
254
299
  }
255
- if (prop === 'address') {
256
- return self.address;
257
- }
258
- if (prop === 'key') {
259
- return self.key;
260
- }
261
- if (prop === 'owner') {
262
- return self.owner;
300
+ if (prop === Symbol.toPrimitive) {
301
+ return null;
263
302
  }
264
303
  if (prop === Symbol.toStringTag) {
265
- return `ManagedObject<${address.type}:${address.id} (${address.lid})>`;
304
+ return `ManagedObject<${identifier.type}:${identifier.id} (${identifier.lid})>`;
305
+ }
306
+ if (prop === 'constructor') {
307
+ return Object;
266
308
  }
267
309
  if (prop === 'toString') {
268
310
  return function () {
269
- return `ManagedObject<${address.type}:${address.id} (${address.lid})>`;
311
+ return `ManagedObject<${identifier.type}:${identifier.id} (${identifier.lid})>`;
270
312
  };
271
313
  }
272
314
  if (prop === 'toHTML') {
@@ -277,71 +319,42 @@ class ManagedObject {
277
319
  if (_SIGNAL.shouldReset) {
278
320
  _SIGNAL.t = false;
279
321
  _SIGNAL.shouldReset = false;
280
- let newData = cache.getAttr(self.address, self.key);
322
+ let newData = cache.getAttr(identifier, path);
281
323
  if (newData && newData !== self[SOURCE]) {
282
- if (!isSchemaObject && field.type) {
324
+ if (field.type) {
283
325
  const transform = schema.transformation(field);
284
- newData = transform.hydrate(newData, field.options ?? null, self.owner);
326
+ newData = transform.hydrate(newData, field.options ?? null, owner);
285
327
  }
286
328
  self[SOURCE] = {
287
329
  ...newData
288
330
  }; // Add type assertion for newData
289
331
  }
290
332
  }
291
- if (isSchemaObject) {
292
- const fields = schema.fields({
293
- type: field.type
294
- });
295
- // TODO: is there a better way to do this?
296
- if (typeof prop === 'string' && !ignoredGlobalFields.has(prop) && !fields.has(prop)) {
297
- throw new Error(`Field ${prop} does not exist on schema object ${field.type}`);
298
- }
299
- }
300
333
  if (prop in self[SOURCE]) {
301
- {
302
- subscribe(_SIGNAL);
303
- }
334
+ subscribe(_SIGNAL);
304
335
  return self[SOURCE][prop];
305
336
  }
306
337
  return Reflect.get(target, prop, receiver);
307
338
  },
308
339
  set(target, prop, value, receiver) {
309
- if (prop === 'address') {
310
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
311
- self.address = value;
312
- return true;
313
- }
314
- if (prop === 'key') {
315
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
316
- self.key = value;
317
- return true;
318
- }
319
- if (prop === 'owner') {
320
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
321
- self.owner = value;
322
- return true;
323
- }
324
- if (isSchemaObject) {
325
- const fields = schema.fields({
326
- type: field.type
327
- });
328
- if (typeof prop === 'string' && !ignoredGlobalFields.has(prop) && !fields.has(prop)) {
329
- throw new Error(`Field ${prop} does not exist on schema object ${field.type}`);
340
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
341
+ if (!test) {
342
+ throw new Error(`Cannot set read-only property '${String(prop)}' on ManagedObject`);
330
343
  }
331
- }
344
+ })(editable) : {};
332
345
  const reflect = Reflect.set(target, prop, value, receiver);
333
- if (reflect) {
334
- if (isSchemaObject || !field.type) {
335
- cache.setAttr(self.address, self.key, self[SOURCE]);
336
- _SIGNAL.shouldReset = true;
337
- return true;
338
- }
346
+ if (!reflect) {
347
+ return false;
348
+ }
349
+ if (!field.type) {
350
+ cache.setAttr(identifier, path, self[SOURCE]);
351
+ } else {
339
352
  const transform = schema.transformation(field);
340
- const val = transform.serialize(self[SOURCE], field.options ?? null, self.owner);
341
- cache.setAttr(self.address, self.key, val);
342
- _SIGNAL.shouldReset = true;
353
+ const val = transform.serialize(self[SOURCE], field.options ?? null, owner);
354
+ cache.setAttr(identifier, path, val);
343
355
  }
344
- return reflect;
356
+ _SIGNAL.shouldReset = true;
357
+ return true;
345
358
  }
346
359
  });
347
360
  return proxy;
@@ -377,7 +390,7 @@ function computeField(schema, cache, record, identifier, field, prop) {
377
390
  const transform = schema.transformation(field);
378
391
  return transform.hydrate(rawValue, field.options ?? null, record);
379
392
  }
380
- function computeArray(store, schema, cache, record, identifier, field, path, isSchemaArray = false) {
393
+ function computeArray(store, schema, cache, record, identifier, field, path, isSchemaArray, editable, legacy) {
381
394
  // the thing we hand out needs to know its owner and path in a private manner
382
395
  // its "address" is the parent identifier (identifier) + field name (field.name)
383
396
  // in the nested object case field name here is the full dot path from root resource to this value
@@ -396,7 +409,7 @@ function computeArray(store, schema, cache, record, identifier, field, path, isS
396
409
  if (!rawValue) {
397
410
  return null;
398
411
  }
399
- managedArray = new ManagedArray(store, schema, cache, field, rawValue, identifier, path, record, isSchemaArray);
412
+ managedArray = new ManagedArray(store, schema, cache, field, rawValue, identifier, path, record, isSchemaArray, editable, legacy);
400
413
  if (!managedArrayMapForRecord) {
401
414
  ManagedArrayMap.set(record, new Map([[field, managedArray]]));
402
415
  } else {
@@ -405,7 +418,7 @@ function computeArray(store, schema, cache, record, identifier, field, path, isS
405
418
  }
406
419
  return managedArray;
407
420
  }
408
- function computeObject(store, schema, cache, record, identifier, field, prop, isSchemaObject = false) {
421
+ function computeObject(schema, cache, record, identifier, field, path, editable, legacy) {
409
422
  const managedObjectMapForRecord = ManagedObjectMap.get(record);
410
423
  let managedObject;
411
424
  if (managedObjectMapForRecord) {
@@ -414,17 +427,15 @@ function computeObject(store, schema, cache, record, identifier, field, prop, is
414
427
  if (managedObject) {
415
428
  return managedObject;
416
429
  } else {
417
- let rawValue = cache.getAttr(identifier, prop);
430
+ let rawValue = cache.getAttr(identifier, path);
418
431
  if (!rawValue) {
419
432
  return null;
420
433
  }
421
- if (field.kind === 'object') {
422
- if (field.type) {
423
- const transform = schema.transformation(field);
424
- rawValue = transform.hydrate(rawValue, field.options ?? null, record);
425
- }
434
+ if (field.type) {
435
+ const transform = schema.transformation(field);
436
+ rawValue = transform.hydrate(rawValue, field.options ?? null, record);
426
437
  }
427
- managedObject = new ManagedObject(store, schema, cache, field, rawValue, identifier, prop, record, isSchemaObject);
438
+ managedObject = new ManagedObject(schema, cache, field, rawValue, identifier, path, record, editable, legacy);
428
439
  if (!managedObjectMapForRecord) {
429
440
  ManagedObjectMap.set(record, new Map([[field, managedObject]]));
430
441
  } else {
@@ -433,6 +444,32 @@ function computeObject(store, schema, cache, record, identifier, field, prop, is
433
444
  }
434
445
  return managedObject;
435
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
+ }
436
473
  function computeAttribute(cache, identifier, prop) {
437
474
  return cache.getAttr(identifier, prop);
438
475
  }
@@ -452,7 +489,7 @@ class ResourceRelationship {
452
489
  this.lid = rawValue.lid ?? rawValue.links?.self ?? `relationship:${identifier.lid}.${name}`;
453
490
  this.data = rawValue.data ? store.peekRecord(rawValue.data) : null;
454
491
  this.name = name;
455
- if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
492
+ if (macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG)) {
456
493
  this.links = Object.freeze(Object.assign({}, rawValue.links));
457
494
  this.meta = Object.freeze(Object.assign({}, rawValue.meta));
458
495
  } else {
@@ -494,12 +531,13 @@ function computeResource(store, cache, parent, identifier, field, prop) {
494
531
  }
495
532
  const HAS_MODEL_PACKAGE = dependencySatisfies('@ember-data-mirror/model', '*');
496
533
  const getLegacySupport = macroCondition(dependencySatisfies('@ember-data-mirror/model', '*')) ? importSync('@ember-data-mirror/model/-private').lookupLegacySupport : null;
497
- const IgnoredGlobalFields = new Set(['length', 'nodeType', 'then', 'setInterval', STRUCTURED]);
534
+ const IgnoredGlobalFields = new Set(['length', 'nodeType', 'then', 'setInterval', 'document', STRUCTURED]);
498
535
  const symbolList = [Destroy, RecordStore, Identifier, Editable, Parent, Checkout, Legacy, Signals, EmbeddedPath, EmbeddedType];
499
536
  const RecordSymbols = new Set(symbolList);
500
537
  function isPathMatch(a, b) {
501
538
  return a.length === b.length && a.every((v, i) => v === b[i]);
502
539
  }
540
+ const Editables = new WeakMap();
503
541
  class SchemaRecord {
504
542
  constructor(store, identifier, Mode, isEmbedded = false, embeddedType = null, embeddedPath = null) {
505
543
  // eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -550,6 +588,7 @@ class SchemaRecord {
550
588
  case 'field':
551
589
  case 'attribute':
552
590
  case 'resource':
591
+ case 'alias':
553
592
  case 'belongsTo':
554
593
  case 'hasMany':
555
594
  case 'collection':
@@ -576,6 +615,11 @@ class SchemaRecord {
576
615
  return `SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})>`;
577
616
  };
578
617
  }
618
+ if (prop === 'toHTML') {
619
+ return function () {
620
+ return `<div>SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})></div>`;
621
+ };
622
+ }
579
623
  if (prop === Symbol.toPrimitive) {
580
624
  return null;
581
625
  }
@@ -589,10 +633,8 @@ class SchemaRecord {
589
633
  // for its own usage.
590
634
  // _, @, $, *
591
635
 
592
- const propArray = isEmbedded ? embeddedPath.slice() : [];
593
- propArray.push(prop);
594
- const field = prop === identityField?.name ? identityField : fields.get(prop);
595
- if (!field) {
636
+ const maybeField = prop === identityField?.name ? identityField : fields.get(prop);
637
+ if (!maybeField) {
596
638
  if (IgnoredGlobalFields.has(prop)) {
597
639
  return undefined;
598
640
  }
@@ -603,8 +645,24 @@ class SchemaRecord {
603
645
  if (typeof prop === 'symbol') {
604
646
  return undefined;
605
647
  }
606
- throw new Error(`No field named ${String(prop)} on ${identifier.type}`);
648
+ let type = identifier.type;
649
+ if (isEmbedded) {
650
+ type = embeddedType;
651
+ }
652
+ throw new Error(`No field named ${String(prop)} on ${type}`);
607
653
  }
654
+ const field = maybeField.kind === 'alias' ? maybeField.options : maybeField;
655
+ macroCondition(getGlobalConfig().WarpDriveMirror.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
+
608
666
  switch (field.kind) {
609
667
  case '@id':
610
668
  entangleSignal(signals, receiver, '@identity');
@@ -619,7 +677,7 @@ class SchemaRecord {
619
677
  return lastValue;
620
678
  }
621
679
  case 'field':
622
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
680
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
623
681
  if (!test) {
624
682
  throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
625
683
  }
@@ -630,7 +688,7 @@ class SchemaRecord {
630
688
  entangleSignal(signals, receiver, field.name);
631
689
  return computeAttribute(cache, identifier, prop);
632
690
  case 'resource':
633
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
691
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
634
692
  if (!test) {
635
693
  throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
636
694
  }
@@ -641,43 +699,53 @@ class SchemaRecord {
641
699
  return computeDerivation(schema, receiver, identifier, field, prop);
642
700
  case 'schema-array':
643
701
  entangleSignal(signals, receiver, field.name);
644
- return computeArray(store, schema, cache, target, identifier, field, propArray, true);
702
+ return computeArray(store, schema, cache, target, identifier, field, propArray, true, Mode[Editable], Mode[Legacy]);
645
703
  case 'array':
646
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
704
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
647
705
  if (!test) {
648
706
  throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
649
707
  }
650
708
  })(!target[Legacy]) : {};
651
709
  entangleSignal(signals, receiver, field.name);
652
- return computeArray(store, schema, cache, target, identifier, field, propArray);
653
- case 'schema-object':
654
- // validate any access off of schema, no transform to run
655
- // use raw cache value as the object to manage
656
- entangleSignal(signals, receiver, field.name);
657
- return computeObject(store, schema, cache, target, identifier, field, prop, true);
710
+ return computeArray(store, schema, cache, target, identifier, field, propArray, false, Mode[Editable], Mode[Legacy]);
658
711
  case 'object':
659
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
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
+ 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().WarpDriveMirror.env.DEBUG) ? (test => {
660
721
  if (!test) {
661
722
  throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
662
723
  }
663
724
  })(!target[Legacy]) : {};
664
725
  entangleSignal(signals, receiver, field.name);
665
726
  // run transform, then use that value as the object to manage
666
- return computeObject(store, schema, cache, target, identifier, field, prop);
727
+ return computeSchemaObject(store, cache, target, identifier, field, propArray, Mode[Legacy], Mode[Editable]);
667
728
  case 'belongsTo':
729
+ if (field.options.linksMode) {
730
+ entangleSignal(signals, receiver, field.name);
731
+ const rawValue = cache.getRelationship(identifier, field.name);
732
+
733
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
734
+ return rawValue.data ? store.peekRecord(rawValue.data) : null;
735
+ }
668
736
  if (!HAS_MODEL_PACKAGE) {
669
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
737
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
670
738
  {
671
739
  throw new Error(`Cannot use belongsTo fields in your schema unless @ember-data-mirror/model is installed to provide legacy model support. ${field.name} should likely be migrated to be a resource field.`);
672
740
  }
673
741
  })() : {};
674
742
  }
675
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
743
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
676
744
  if (!test) {
677
745
  throw new Error(`Expected to have a getLegacySupport function`);
678
746
  }
679
747
  })(getLegacySupport) : {};
680
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
748
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
681
749
  if (!test) {
682
750
  throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
683
751
  }
@@ -686,18 +754,18 @@ class SchemaRecord {
686
754
  return getLegacySupport(receiver).getBelongsTo(field.name);
687
755
  case 'hasMany':
688
756
  if (!HAS_MODEL_PACKAGE) {
689
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
757
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
690
758
  {
691
759
  throw new Error(`Cannot use hasMany fields in your schema unless @ember-data-mirror/model is installed to provide legacy model support. ${field.name} should likely be migrated to be a collection field.`);
692
760
  }
693
761
  })() : {};
694
762
  }
695
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
763
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
696
764
  if (!test) {
697
765
  throw new Error(`Expected to have a getLegacySupport function`);
698
766
  }
699
767
  })(getLegacySupport) : {};
700
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
768
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
701
769
  if (!test) {
702
770
  throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
703
771
  }
@@ -710,25 +778,37 @@ class SchemaRecord {
710
778
  },
711
779
  set(target, prop, value, receiver) {
712
780
  if (!IS_EDITABLE) {
713
- throw new Error(`Cannot set ${String(prop)} on ${identifier.type} because the record is not editable`);
781
+ const type = isEmbedded ? embeddedType : identifier.type;
782
+ throw new Error(`Cannot set ${String(prop)} on ${type} because the record is not editable`);
714
783
  }
715
- const propArray = isEmbedded ? embeddedPath.slice() : [];
716
- propArray.push(prop);
717
- const field = prop === identityField?.name ? identityField : fields.get(prop);
718
- if (!field) {
719
- throw new Error(`There is no field named ${String(prop)} on ${identifier.type}`);
784
+ const maybeField = prop === identityField?.name ? identityField : fields.get(prop);
785
+ if (!maybeField) {
786
+ const type = isEmbedded ? embeddedType : identifier.type;
787
+ throw new Error(`There is no field named ${String(prop)} on ${type}`);
720
788
  }
789
+ const field = maybeField.kind === 'alias' ? maybeField.options : maybeField;
790
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
791
+ if (!test) {
792
+ throw new Error(`Alias fields cannot alias '@id' '@local' '@hash' or 'derived' fields`);
793
+ }
794
+ })(maybeField.kind !== 'alias' || !['@id', '@local', '@hash', 'derived'].includes(maybeField.options.kind)) : {};
795
+ const propArray = isEmbedded ? embeddedPath.slice() : [];
796
+ // we use the field.name instead of prop here because we want to use the cache-path not
797
+ // the record path.
798
+ propArray.push(field.name);
799
+ // propArray.push(prop as string);
800
+
721
801
  switch (field.kind) {
722
802
  case '@id':
723
803
  {
724
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
804
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
725
805
  if (!test) {
726
806
  throw new Error(`Expected to receive a string id`);
727
807
  }
728
808
  })(typeof value === 'string' && value.length) : {};
729
809
  const normalizedId = String(value);
730
810
  const didChange = normalizedId !== identifier.id;
731
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
811
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
732
812
  if (!test) {
733
813
  throw new Error(`Cannot set ${identifier.type} record's id to ${normalizedId}, because id is already ${identifier.id}`);
734
814
  }
@@ -840,6 +920,11 @@ class SchemaRecord {
840
920
  {
841
921
  let newValue = value;
842
922
  if (value !== null) {
923
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
924
+ if (!test) {
925
+ throw new Error(`Expected value to be an object`);
926
+ }
927
+ })(typeof value === 'object') : {};
843
928
  newValue = {
844
929
  ...value
845
930
  };
@@ -855,11 +940,11 @@ class SchemaRecord {
855
940
  ManagedObjectMap.delete(target);
856
941
  }
857
942
  cache.setAttr(identifier, propArray, newValue);
858
- const peeked = peekManagedObject(self, field);
859
- if (peeked) {
860
- const objSignal = peeked[OBJECT_SIGNAL];
861
- objSignal.shouldReset = true;
862
- }
943
+ // const peeked = peekManagedObject(self, field);
944
+ // if (peeked) {
945
+ // const objSignal = peeked[OBJECT_SIGNAL];
946
+ // objSignal.shouldReset = true;
947
+ // }
863
948
  return true;
864
949
  }
865
950
  case 'derived':
@@ -868,18 +953,18 @@ class SchemaRecord {
868
953
  }
869
954
  case 'belongsTo':
870
955
  if (!HAS_MODEL_PACKAGE) {
871
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
956
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
872
957
  {
873
958
  throw new Error(`Cannot use belongsTo fields in your schema unless @ember-data-mirror/model is installed to provide legacy model support. ${field.name} should likely be migrated to be a resource field.`);
874
959
  }
875
960
  })() : {};
876
961
  }
877
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
962
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
878
963
  if (!test) {
879
964
  throw new Error(`Expected to have a getLegacySupport function`);
880
965
  }
881
966
  })(getLegacySupport) : {};
882
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
967
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
883
968
  if (!test) {
884
969
  throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
885
970
  }
@@ -890,23 +975,23 @@ class SchemaRecord {
890
975
  return true;
891
976
  case 'hasMany':
892
977
  if (!HAS_MODEL_PACKAGE) {
893
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
978
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
894
979
  {
895
980
  throw new Error(`Cannot use hasMany fields in your schema unless @ember-data-mirror/model is installed to provide legacy model support. ${field.name} should likely be migrated to be a collection field.`);
896
981
  }
897
982
  })() : {};
898
983
  }
899
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
984
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
900
985
  if (!test) {
901
986
  throw new Error(`Expected to have a getLegacySupport function`);
902
987
  }
903
988
  })(getLegacySupport) : {};
904
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
989
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
905
990
  if (!test) {
906
991
  throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
907
992
  }
908
993
  })(Mode[Legacy]) : {};
909
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
994
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
910
995
  if (!test) {
911
996
  throw new Error(`You must pass an array of records to set a hasMany relationship`);
912
997
  }
@@ -974,7 +1059,7 @@ class SchemaRecord {
974
1059
  addToTransaction(arrSignal);
975
1060
  }
976
1061
  }
977
- if (field?.kind === 'object' || field?.kind === 'schema-object') {
1062
+ if (field?.kind === 'object') {
978
1063
  const peeked = peekManagedObject(self, field);
979
1064
  if (peeked) {
980
1065
  const objSignal = peeked[OBJECT_SIGNAL];
@@ -991,7 +1076,7 @@ class SchemaRecord {
991
1076
  if (isEmbedded) return; // base paths never apply to embedded records
992
1077
 
993
1078
  const field = fields.get(key);
994
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1079
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
995
1080
  if (!test) {
996
1081
  throw new Error(`Expected relationshp ${key} to be the name of a field`);
997
1082
  }
@@ -1005,12 +1090,12 @@ class SchemaRecord {
1005
1090
  }
1006
1091
  // FIXME
1007
1092
  } else if (field.kind === 'resource') ;else if (field.kind === 'hasMany') {
1008
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1093
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1009
1094
  if (!test) {
1010
1095
  throw new Error(`Expected to have a getLegacySupport function`);
1011
1096
  }
1012
1097
  })(getLegacySupport) : {};
1013
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1098
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1014
1099
  if (!test) {
1015
1100
  throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
1016
1101
  }
@@ -1025,12 +1110,12 @@ class SchemaRecord {
1025
1110
  }
1026
1111
  if (manyArray) {
1027
1112
  manyArray.notify();
1028
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1113
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1029
1114
  if (!test) {
1030
1115
  throw new Error(`Expected options to exist on relationship meta`);
1031
1116
  }
1032
1117
  })(field.options) : {};
1033
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1118
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1034
1119
  if (!test) {
1035
1120
  throw new Error(`Expected async to exist on relationship meta options`);
1036
1121
  }
@@ -1060,7 +1145,22 @@ class SchemaRecord {
1060
1145
  this[RecordStore].notifications.unsubscribe(this.___notifications);
1061
1146
  }
1062
1147
  [Checkout]() {
1063
- return Promise.resolve(this);
1148
+ const editable = Editables.get(this);
1149
+ if (editable) {
1150
+ return Promise.resolve(editable);
1151
+ }
1152
+ const embeddedType = this[EmbeddedType];
1153
+ const embeddedPath = this[EmbeddedPath];
1154
+ const isEmbedded = embeddedType !== null && embeddedPath !== null;
1155
+ if (isEmbedded) {
1156
+ throw new Error(`Cannot checkout an embedded record (yet)`);
1157
+ }
1158
+ const editableRecord = new SchemaRecord(this[RecordStore], this[Identifier], {
1159
+ [Editable]: true,
1160
+ [Legacy]: this[Legacy]
1161
+ }, isEmbedded, embeddedType, embeddedPath);
1162
+ setRecordIdentifier(editableRecord, recordIdentifierFor(this));
1163
+ return Promise.resolve(editableRecord);
1064
1164
  }
1065
1165
  }
1066
- export { Editable, Legacy, SchemaRecord };
1166
+ export { Checkout, Editable, Legacy, SchemaRecord };