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

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;
254
- }
255
- if (prop === 'address') {
256
- return self.address;
297
+ if (ObjectSymbols.has(prop)) {
298
+ return self[prop];
257
299
  }
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,46 @@ 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':
668
729
  if (!HAS_MODEL_PACKAGE) {
669
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
730
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
670
731
  {
671
732
  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
733
  }
673
734
  })() : {};
674
735
  }
675
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
736
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
676
737
  if (!test) {
677
738
  throw new Error(`Expected to have a getLegacySupport function`);
678
739
  }
679
740
  })(getLegacySupport) : {};
680
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
741
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
681
742
  if (!test) {
682
743
  throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
683
744
  }
@@ -686,18 +747,18 @@ class SchemaRecord {
686
747
  return getLegacySupport(receiver).getBelongsTo(field.name);
687
748
  case 'hasMany':
688
749
  if (!HAS_MODEL_PACKAGE) {
689
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
750
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
690
751
  {
691
752
  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
753
  }
693
754
  })() : {};
694
755
  }
695
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
756
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
696
757
  if (!test) {
697
758
  throw new Error(`Expected to have a getLegacySupport function`);
698
759
  }
699
760
  })(getLegacySupport) : {};
700
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
761
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
701
762
  if (!test) {
702
763
  throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
703
764
  }
@@ -710,25 +771,37 @@ class SchemaRecord {
710
771
  },
711
772
  set(target, prop, value, receiver) {
712
773
  if (!IS_EDITABLE) {
713
- throw new Error(`Cannot set ${String(prop)} on ${identifier.type} because the record is not editable`);
774
+ const type = isEmbedded ? embeddedType : identifier.type;
775
+ throw new Error(`Cannot set ${String(prop)} on ${type} because the record is not editable`);
714
776
  }
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}`);
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}`);
720
781
  }
782
+ const field = maybeField.kind === 'alias' ? maybeField.options : maybeField;
783
+ macroCondition(getGlobalConfig().WarpDriveMirror.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
+
721
794
  switch (field.kind) {
722
795
  case '@id':
723
796
  {
724
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
797
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
725
798
  if (!test) {
726
799
  throw new Error(`Expected to receive a string id`);
727
800
  }
728
801
  })(typeof value === 'string' && value.length) : {};
729
802
  const normalizedId = String(value);
730
803
  const didChange = normalizedId !== identifier.id;
731
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
804
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
732
805
  if (!test) {
733
806
  throw new Error(`Cannot set ${identifier.type} record's id to ${normalizedId}, because id is already ${identifier.id}`);
734
807
  }
@@ -840,6 +913,11 @@ class SchemaRecord {
840
913
  {
841
914
  let newValue = value;
842
915
  if (value !== null) {
916
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
917
+ if (!test) {
918
+ throw new Error(`Expected value to be an object`);
919
+ }
920
+ })(typeof value === 'object') : {};
843
921
  newValue = {
844
922
  ...value
845
923
  };
@@ -855,11 +933,11 @@ class SchemaRecord {
855
933
  ManagedObjectMap.delete(target);
856
934
  }
857
935
  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
- }
936
+ // const peeked = peekManagedObject(self, field);
937
+ // if (peeked) {
938
+ // const objSignal = peeked[OBJECT_SIGNAL];
939
+ // objSignal.shouldReset = true;
940
+ // }
863
941
  return true;
864
942
  }
865
943
  case 'derived':
@@ -868,18 +946,18 @@ class SchemaRecord {
868
946
  }
869
947
  case 'belongsTo':
870
948
  if (!HAS_MODEL_PACKAGE) {
871
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
949
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
872
950
  {
873
951
  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
952
  }
875
953
  })() : {};
876
954
  }
877
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
955
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
878
956
  if (!test) {
879
957
  throw new Error(`Expected to have a getLegacySupport function`);
880
958
  }
881
959
  })(getLegacySupport) : {};
882
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
960
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
883
961
  if (!test) {
884
962
  throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
885
963
  }
@@ -890,23 +968,23 @@ class SchemaRecord {
890
968
  return true;
891
969
  case 'hasMany':
892
970
  if (!HAS_MODEL_PACKAGE) {
893
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
971
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
894
972
  {
895
973
  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
974
  }
897
975
  })() : {};
898
976
  }
899
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
977
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
900
978
  if (!test) {
901
979
  throw new Error(`Expected to have a getLegacySupport function`);
902
980
  }
903
981
  })(getLegacySupport) : {};
904
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
982
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
905
983
  if (!test) {
906
984
  throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
907
985
  }
908
986
  })(Mode[Legacy]) : {};
909
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
987
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
910
988
  if (!test) {
911
989
  throw new Error(`You must pass an array of records to set a hasMany relationship`);
912
990
  }
@@ -974,7 +1052,7 @@ class SchemaRecord {
974
1052
  addToTransaction(arrSignal);
975
1053
  }
976
1054
  }
977
- if (field?.kind === 'object' || field?.kind === 'schema-object') {
1055
+ if (field?.kind === 'object') {
978
1056
  const peeked = peekManagedObject(self, field);
979
1057
  if (peeked) {
980
1058
  const objSignal = peeked[OBJECT_SIGNAL];
@@ -991,7 +1069,7 @@ class SchemaRecord {
991
1069
  if (isEmbedded) return; // base paths never apply to embedded records
992
1070
 
993
1071
  const field = fields.get(key);
994
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1072
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
995
1073
  if (!test) {
996
1074
  throw new Error(`Expected relationshp ${key} to be the name of a field`);
997
1075
  }
@@ -1005,12 +1083,12 @@ class SchemaRecord {
1005
1083
  }
1006
1084
  // FIXME
1007
1085
  } else if (field.kind === 'resource') ;else if (field.kind === 'hasMany') {
1008
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1086
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1009
1087
  if (!test) {
1010
1088
  throw new Error(`Expected to have a getLegacySupport function`);
1011
1089
  }
1012
1090
  })(getLegacySupport) : {};
1013
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1091
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1014
1092
  if (!test) {
1015
1093
  throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
1016
1094
  }
@@ -1025,12 +1103,12 @@ class SchemaRecord {
1025
1103
  }
1026
1104
  if (manyArray) {
1027
1105
  manyArray.notify();
1028
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1106
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1029
1107
  if (!test) {
1030
1108
  throw new Error(`Expected options to exist on relationship meta`);
1031
1109
  }
1032
1110
  })(field.options) : {};
1033
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1111
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1034
1112
  if (!test) {
1035
1113
  throw new Error(`Expected async to exist on relationship meta options`);
1036
1114
  }
@@ -1060,7 +1138,22 @@ class SchemaRecord {
1060
1138
  this[RecordStore].notifications.unsubscribe(this.___notifications);
1061
1139
  }
1062
1140
  [Checkout]() {
1063
- return Promise.resolve(this);
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);
1064
1157
  }
1065
1158
  }
1066
- export { Editable, Legacy, SchemaRecord };
1159
+ export { Checkout, Editable, Legacy, SchemaRecord };