@warp-drive/core 5.7.0-alpha.3 → 5.7.0-alpha.4

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.
Files changed (33) hide show
  1. package/declarations/reactive/-private/default-mode.d.ts +71 -0
  2. package/declarations/reactive/-private/fields/get-field-key.d.ts +8 -0
  3. package/declarations/reactive/-private/fields/managed-array.d.ts +3 -5
  4. package/declarations/reactive/-private/fields/managed-object.d.ts +5 -3
  5. package/declarations/reactive/-private/kind/alias-field.d.ts +4 -0
  6. package/declarations/reactive/-private/kind/array-field.d.ts +9 -0
  7. package/declarations/reactive/-private/kind/attribute-field.d.ts +4 -0
  8. package/declarations/reactive/-private/kind/belongs-to-field.d.ts +4 -0
  9. package/declarations/reactive/-private/kind/collection-field.d.ts +4 -0
  10. package/declarations/reactive/-private/kind/derived-field.d.ts +4 -0
  11. package/declarations/reactive/-private/kind/generic-field.d.ts +4 -0
  12. package/declarations/reactive/-private/kind/has-many-field.d.ts +4 -0
  13. package/declarations/reactive/-private/kind/hash-field.d.ts +4 -0
  14. package/declarations/reactive/-private/kind/identity-field.d.ts +4 -0
  15. package/declarations/reactive/-private/kind/local-field.d.ts +4 -0
  16. package/declarations/reactive/-private/kind/object-field.d.ts +4 -0
  17. package/declarations/reactive/-private/kind/resource-field.d.ts +4 -0
  18. package/declarations/reactive/-private/kind/schema-array-field.d.ts +4 -0
  19. package/declarations/reactive/-private/kind/schema-object-field.d.ts +4 -0
  20. package/declarations/reactive/-private/record.d.ts +2 -4
  21. package/declarations/reactive/-private/schema.d.ts +5 -1
  22. package/declarations/store/-types/q/schema-service.d.ts +19 -31
  23. package/declarations/types/schema/fields.d.ts +309 -3
  24. package/dist/graph/-private.js +1 -1
  25. package/dist/{handler-D2jjnIA-.js → handler-Gm-q2q05.js} +1 -1
  26. package/dist/index.js +2 -2
  27. package/dist/reactive.js +1000 -530
  28. package/dist/{request-state-CejVJgdj.js → request-state-Bj0zUw2r.js} +5 -0
  29. package/dist/store/-private.js +2 -2
  30. package/dist/types/-private.js +1 -1
  31. package/dist/types/schema/fields.js +13 -0
  32. package/package.json +3 -3
  33. package/declarations/reactive/-private/fields/compute.d.ts +0 -43
package/dist/reactive.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isResourceSchema } from './types/schema/fields.js';
2
- import { F as withSignalStore, T as isExtensionProp, U as performExtensionSet, H as consumeInternalSignal, V as performArrayExtensionGet, x as entangleSignal, W as performObjectExtensionGet, d as SOURCE$1, f as fastPush, y as defineSignal, l as RelatedCollection, J as getOrCreateInternalSignal, G as notifyInternalSignal, B as Signals, h as setRecordIdentifier, r as recordIdentifierFor } from "./request-state-CejVJgdj.js";
2
+ import { F as withSignalStore, T as isExtensionProp, U as performExtensionSet, H as consumeInternalSignal, V as performArrayExtensionGet, x as entangleSignal, d as SOURCE$1, f as fastPush, l as RelatedCollection, J as getOrCreateInternalSignal, G as notifyInternalSignal, W as performObjectExtensionGet, y as defineSignal, B as Signals, h as setRecordIdentifier, r as recordIdentifierFor } from "./request-state-Bj0zUw2r.js";
3
3
  import { EnableHydration, STRUCTURED } from './types/request.js';
4
4
  import { macroCondition, getGlobalConfig } from '@embroider/macros';
5
5
  import { warn, deprecate } from '@ember/debug';
@@ -9,6 +9,21 @@ import { RecordStore, Type } from './types/symbols.js';
9
9
  import { getOrSetGlobal } from './types/-private.js';
10
10
  import { S as SOURCE, E as Editable, L as Legacy, I as Identifier, P as Parent, a as EmbeddedPath, D as Destroy, C as Checkout, b as EmbeddedField } from "./symbols-SIstXMLI.js";
11
11
  import './index.js';
12
+ function getAliasField(context) {
13
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
14
+ {
15
+ throw new Error(`Alias field access is not implemented`);
16
+ }
17
+ })() : {};
18
+ }
19
+ function setAliasField(context) {
20
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
21
+ {
22
+ throw new Error(`Alias field setting is not implemented`);
23
+ }
24
+ })() : {};
25
+ return false;
26
+ }
12
27
  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']);
13
28
  // const ARRAY_SETTER_METHODS = new Set<KeyType>(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
14
29
  const SYNC_PROPS = new Set(['[]', 'length']);
@@ -55,20 +70,22 @@ function safeForEach(instance, arr, store, callback, target) {
55
70
  }
56
71
  // eslint-disable-next-line @typescript-eslint/no-extraneous-class
57
72
  class ManagedArray {
58
- constructor(store, schema, cache, field, data, identifier, path, owner, isSchemaArray, editable, legacy) {
73
+ constructor(context, owner, data) {
59
74
  // eslint-disable-next-line @typescript-eslint/no-this-alias
60
75
  const self = this;
61
76
  this[SOURCE] = data?.slice();
62
- const IS_EDITABLE = this[Editable] = editable ?? false;
63
- this[Legacy] = legacy;
77
+ const IS_EDITABLE = this[Editable] = context.editable ?? false;
78
+ this[Legacy] = context.legacy;
79
+ const schema = context.store.schema;
80
+ const cache = context.store.cache;
64
81
  const signals = withSignalStore(this);
65
82
  let _SIGNAL = null;
66
83
  const boundFns = new Map();
67
- this.identifier = identifier;
68
- this.path = path;
84
+ this.identifier = context.resourceKey;
85
+ this.path = context.path;
69
86
  this.owner = owner;
70
87
  let transaction = false;
71
- const mode = field.options?.key ?? '@identity';
88
+ const mode = context.field.options?.key ?? '@identity';
72
89
  const RefStorage = mode === '@identity' ? WeakMap :
73
90
  // CAUTION CAUTION CAUTION
74
91
  // this is a pile of lies
@@ -77,8 +94,8 @@ class ManagedArray {
77
94
  // internal to a method like ours without us duplicating the code
78
95
  // into two separate methods.
79
96
  Map;
80
- const ManagedRecordRefs = isSchemaArray ? new RefStorage() : null;
81
- const extensions = legacy ? schema.CAUTION_MEGA_DANGER_ZONE_arrayExtensions(field) : null;
97
+ const ManagedRecordRefs = context.field.kind === 'schema-array' ? new RefStorage() : null;
98
+ const extensions = context.legacy ? schema.CAUTION_MEGA_DANGER_ZONE_arrayExtensions(context.field) : null;
82
99
  const proxy = new Proxy(this[SOURCE], {
83
100
  get(target, prop, receiver) {
84
101
  if (prop === ARRAY_SIGNAL) {
@@ -93,7 +110,7 @@ class ManagedArray {
93
110
  const index = convertToInt(prop);
94
111
  if (_SIGNAL.isStale && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
95
112
  _SIGNAL.isStale = false;
96
- const newData = cache.getAttr(identifier, path);
113
+ const newData = cache.getAttr(context.resourceKey, context.path);
97
114
  if (newData && newData !== self[SOURCE]) {
98
115
  self[SOURCE].length = 0;
99
116
  self[SOURCE].push(...newData);
@@ -108,7 +125,7 @@ class ManagedArray {
108
125
  if (mode === '@hash') {
109
126
  val = target[index];
110
127
  const hashField = schema.resource({
111
- type: field.type
128
+ type: context.field.type
112
129
  }).identity;
113
130
  const hashFn = schema.hashFn(hashField);
114
131
  val = hashFn(val, null, null);
@@ -126,7 +143,7 @@ class ManagedArray {
126
143
  }
127
144
  })(typeof mode === 'string') : {};
128
145
  const modeField = schema.resource({
129
- type: field.type
146
+ type: context.field.type
130
147
  }).fields.find(f => f.name === mode);
131
148
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
132
149
  if (!test) {
@@ -141,7 +158,7 @@ class ManagedArray {
141
158
  }
142
159
  val = mode === '@identity' ? target[index] : mode === '@index' ? '@index' : target[index][mode];
143
160
  }
144
- if (isSchemaArray) {
161
+ if (context.field.kind === 'schema-array') {
145
162
  if (!transaction) {
146
163
  consumeInternalSignal(_SIGNAL);
147
164
  }
@@ -149,7 +166,7 @@ class ManagedArray {
149
166
  const recordRef = ManagedRecordRefs.get(val);
150
167
  let record = recordRef?.deref();
151
168
  if (!record) {
152
- const recordPath = path.slice();
169
+ const recordPath = context.path.slice();
153
170
  // this is a dirty lie since path is string[] but really we
154
171
  // should change the types for paths to `Array<string | number>`
155
172
  // TODO we should allow the schema for the field to define a "key"
@@ -157,11 +174,16 @@ class ManagedArray {
157
174
  // same object reference from cache should result in same ReactiveResource
158
175
  // embedded object.
159
176
  recordPath.push(index);
160
- const recordIdentifier = self.owner[Identifier] || self.owner[Parent];
161
- record = new ReactiveResource(store, recordIdentifier, {
162
- [Editable]: self.owner[Editable],
163
- [Legacy]: self.owner[Legacy]
164
- }, true, field, recordPath);
177
+ record = new ReactiveResource({
178
+ store: context.store,
179
+ resourceKey: context.resourceKey,
180
+ modeName: context.modeName,
181
+ legacy: context.legacy,
182
+ editable: context.editable,
183
+ path: recordPath,
184
+ field: context.field
185
+ });
186
+
165
187
  // if mode is not @identity or @index, then access the key path now
166
188
  // to determine the key value.
167
189
  // chris says we can implement this as a special kind `@hash` which
@@ -177,9 +199,9 @@ class ManagedArray {
177
199
  if (!transaction) {
178
200
  consumeInternalSignal(_SIGNAL);
179
201
  }
180
- if (field.type) {
181
- const transform = schema.transformation(field);
182
- return transform.hydrate(val, field.options ?? null, self.owner);
202
+ if (context.field.type) {
203
+ const transform = schema.transformation(context.field);
204
+ return transform.hydrate(val, context.field.options ?? null, self.owner);
183
205
  }
184
206
  return val;
185
207
  }
@@ -190,7 +212,7 @@ class ManagedArray {
190
212
  fn = function () {
191
213
  consumeInternalSignal(_SIGNAL);
192
214
  transaction = true;
193
- const result = safeForEach(receiver, target, store, arguments[0], arguments[1]);
215
+ const result = safeForEach(receiver, target, context.store, arguments[0], arguments[1]);
194
216
  transaction = false;
195
217
  return result;
196
218
  };
@@ -233,9 +255,9 @@ class ManagedArray {
233
255
  },
234
256
  set(target, prop, value, receiver) {
235
257
  if (!IS_EDITABLE) {
236
- let errorPath = identifier.type;
237
- if (path) {
238
- errorPath = path[path.length - 1];
258
+ let errorPath = context.resourceKey.type;
259
+ if (context.path) {
260
+ errorPath = context.path[context.path.length - 1];
239
261
  }
240
262
  throw new Error(`Cannot set ${String(prop)} on ${errorPath} because the record is not editable`);
241
263
  }
@@ -252,20 +274,20 @@ class ManagedArray {
252
274
  }
253
275
  const reflect = Reflect.set(target, prop, value, receiver);
254
276
  if (reflect) {
255
- if (!field.type) {
256
- cache.setAttr(identifier, path, self[SOURCE]);
277
+ if (!context.field.type) {
278
+ cache.setAttr(context.resourceKey, context.path, self[SOURCE]);
257
279
  _SIGNAL.isStale = true;
258
280
  return true;
259
281
  }
260
282
  let rawValue = self[SOURCE];
261
- if (!isSchemaArray) {
262
- const transform = schema.transformation(field);
283
+ if (context.field.kind !== 'schema-array') {
284
+ const transform = schema.transformation(context.field);
263
285
  if (!transform) {
264
- throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
286
+ throw new Error(`No '${context.field.type}' transform defined for use by ${context.resourceKey.type}.${String(prop)}`);
265
287
  }
266
- rawValue = self[SOURCE].map(item => transform.serialize(item, field.options ?? null, self.owner));
288
+ rawValue = self[SOURCE].map(item => transform.serialize(item, context.field.options ?? null, self.owner));
267
289
  }
268
- cache.setAttr(identifier, path, rawValue);
290
+ cache.setAttr(context.resourceKey, context.path, rawValue);
269
291
  _SIGNAL.isStale = true;
270
292
  }
271
293
  return reflect;
@@ -302,27 +324,490 @@ const desc = {
302
324
  };
303
325
  // compat(desc);
304
326
  Object.defineProperty(ManagedArray.prototype, '[]', desc);
327
+ const ManagedArrayMap = getOrSetGlobal('ManagedArrayMap', new Map());
328
+ function peekManagedArray(record, field) {
329
+ const managedArrayMapForRecord = ManagedArrayMap.get(record);
330
+ if (managedArrayMapForRecord) {
331
+ return managedArrayMapForRecord.get(field.name);
332
+ }
333
+ }
334
+ function getArrayField(context) {
335
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
336
+ // the thing we hand out needs to know its owner and path in a private manner
337
+ // its "address" is the parent identifier (identifier) + field name (field.name)
338
+ // in the nested object case field name here is the full dot path from root resource to this value
339
+ // its "key" is the field on the parent record
340
+ // its "owner" is the parent record
341
+ const {
342
+ field,
343
+ record
344
+ } = context;
345
+ const managedArrayMapForRecord = ManagedArrayMap.get(record);
346
+ let managedArray;
347
+ if (managedArrayMapForRecord) {
348
+ managedArray = managedArrayMapForRecord.get(field.name);
349
+ }
350
+ if (managedArray) {
351
+ return managedArray;
352
+ } else {
353
+ const {
354
+ store,
355
+ resourceKey,
356
+ path
357
+ } = context;
358
+ const {
359
+ cache
360
+ } = store;
361
+ const rawValue = context.editable ? cache.getAttr(resourceKey, path) : cache.getRemoteAttr(resourceKey, path);
362
+ if (!rawValue) {
363
+ return null;
364
+ }
365
+ managedArray = new ManagedArray(context, record, rawValue);
366
+ if (!managedArrayMapForRecord) {
367
+ ManagedArrayMap.set(record, new Map([[field.name, managedArray]]));
368
+ } else {
369
+ managedArrayMapForRecord.set(field.name, managedArray);
370
+ }
371
+ }
372
+ return managedArray;
373
+ }
374
+ function setArrayField(context) {
375
+ const {
376
+ field,
377
+ record,
378
+ value
379
+ } = context;
380
+ const {
381
+ cache,
382
+ schema
383
+ } = context.store;
384
+ if (!field.type) {
385
+ cache.setAttr(context.resourceKey, context.path, value?.slice());
386
+ const peeked = peekManagedArray(record, field);
387
+ if (peeked) {
388
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
389
+ if (!test) {
390
+ throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
391
+ }
392
+ })(ARRAY_SIGNAL in peeked) : {};
393
+ const arrSignal = peeked[ARRAY_SIGNAL];
394
+ arrSignal.isStale = true;
395
+ }
396
+ if (!Array.isArray(value)) {
397
+ ManagedArrayMap.delete(record);
398
+ }
399
+ return true;
400
+ }
401
+ const transform = schema.transformation(field);
402
+ const rawValue = value.map(item => transform.serialize(item, field.options ?? null, record));
403
+ cache.setAttr(context.resourceKey, context.path, rawValue);
404
+ const peeked = peekManagedArray(record, field);
405
+ if (peeked) {
406
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
407
+ if (!test) {
408
+ throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
409
+ }
410
+ })(ARRAY_SIGNAL in peeked) : {};
411
+ const arrSignal = peeked[ARRAY_SIGNAL];
412
+ arrSignal.isStale = true;
413
+ }
414
+ return true;
415
+ }
416
+ function getAttributeField(context) {
417
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
418
+ const {
419
+ cache
420
+ } = context.store;
421
+ return context.editable ? cache.getAttr(context.resourceKey, context.path) : cache.getRemoteAttr(context.resourceKey, context.path);
422
+ }
423
+ function setAttributeField(context) {
424
+ context.store.cache.setAttr(context.resourceKey, context.path, context.value);
425
+ return true;
426
+ }
427
+ const InvalidKinds = ['alias', 'derived', '@local'];
428
+ function isInvalidKind(kind) {
429
+ return InvalidKinds.includes(kind);
430
+ }
431
+ function isNonIdentityCacheableField(field) {
432
+ return !isInvalidKind(field.kind) && field.kind !== '@id' && field.kind !== '@hash';
433
+ }
434
+ function getFieldCacheKeyStrict(field) {
435
+ return field.sourceKey || field.name;
436
+ }
437
+ function getFieldCacheKey(field) {
438
+ return 'sourceKey' in field && field.sourceKey ? field.sourceKey : field.name;
439
+ }
440
+ function getBelongsToField(context) {
441
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
442
+ const {
443
+ field,
444
+ resourceKey,
445
+ store
446
+ } = context;
447
+ const {
448
+ schema,
449
+ cache
450
+ } = store;
451
+ if (field.options.linksMode) {
452
+ const rawValue = context.editable ? cache.getRelationship(resourceKey, getFieldCacheKeyStrict(field)) : cache.getRemoteRelationship(resourceKey, getFieldCacheKeyStrict(field));
453
+ return rawValue.data ? store.peekRecord(rawValue.data) : null;
454
+ }
455
+
456
+ // FIXME move this to a "LegacyMode" make this part of "PolarisMode"
457
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
458
+ if (!test) {
459
+ throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
460
+ }
461
+ })(context.legacy) : {};
462
+ return schema._kind('@legacy', 'belongsTo').get(store, context.record, resourceKey, field);
463
+ }
464
+ function setBelongsToField(context) {
465
+ const {
466
+ store
467
+ } = context;
468
+ const {
469
+ schema
470
+ } = store;
471
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
472
+ if (!test) {
473
+ throw new Error(`Can only mutate belongsTo fields when the resource is in legacy mode`);
474
+ }
475
+ })(context.legacy) : {};
476
+ schema._kind('@legacy', 'belongsTo').set(store, context.record, context.resourceKey, context.field, context.value);
477
+ return true;
478
+ }
479
+ function getCollectionField(context) {
480
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
481
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
482
+ {
483
+ throw new Error(`Accessing collection fields is not yet implemented`);
484
+ }
485
+ })() : {};
486
+ }
487
+ function setCollectionField(context) {
488
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
489
+ {
490
+ throw new Error(`Setting collection fields is not yet implemented`);
491
+ }
492
+ })() : {};
493
+ return false;
494
+ }
495
+ function getDerivedField(context) {
496
+ const {
497
+ schema
498
+ } = context.store;
499
+ return schema.derivation(context.field)(context.record, context.field.options ?? null, context.field.name);
500
+ }
501
+ function setDerivedField(context) {
502
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
503
+ {
504
+ throw new Error(`ILLEGAL SET: Cannot set '${context.path.join('.')}' on '${context.resourceKey.type}' as ${context.field.kind} fields are not mutable`);
505
+ }
506
+ })() : {};
507
+ return false;
508
+ }
509
+ function getGenericField(context) {
510
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
511
+ const {
512
+ cache,
513
+ schema
514
+ } = context.store;
515
+ const rawValue = context.editable ? cache.getAttr(context.resourceKey, context.path) : cache.getRemoteAttr(context.resourceKey, context.path);
516
+ const {
517
+ field
518
+ } = context;
519
+ if (!field.type) {
520
+ return rawValue;
521
+ }
522
+ const transform = schema.transformation(field);
523
+ return transform.hydrate(rawValue, field.options ?? null, context.record);
524
+ }
525
+ function setGenericField(context) {
526
+ const {
527
+ cache,
528
+ schema
529
+ } = context.store;
530
+ const {
531
+ field
532
+ } = context;
533
+ if (!field.type) {
534
+ cache.setAttr(context.resourceKey, context.path, context.value);
535
+ return true;
536
+ }
537
+ const transform = schema.transformation(field);
538
+ const rawValue = transform.serialize(context.value, field.options ?? null, context.record);
539
+ cache.setAttr(context.resourceKey, context.path, rawValue);
540
+ return true;
541
+ }
542
+ class ManyArrayManager {
543
+ constructor(record, editable) {
544
+ this.record = record;
545
+ this.store = record[RecordStore];
546
+ this.identifier = record[Identifier];
547
+ this.editable = editable;
548
+ }
549
+ _syncArray(array) {
550
+ const method = this.editable ? 'getRelationship' : 'getRemoteRelationship';
551
+ // FIXME field needs to use sourceKey
552
+ const rawValue = this.store.cache[method](this.identifier, array.key);
553
+ if (rawValue.meta) {
554
+ array.meta = rawValue.meta;
555
+ }
556
+ if (rawValue.links) {
557
+ array.links = rawValue.links;
558
+ }
559
+ const currentState = array[SOURCE$1];
560
+
561
+ // unlike in the normal RecordArray case, we don't need to divorce the reference
562
+ // because we don't need to worry about associate/disassociate since the graph
563
+ // takes care of that for us
564
+ if (currentState !== rawValue.data) {
565
+ currentState.length = 0;
566
+ fastPush(currentState, rawValue.data);
567
+ }
568
+ }
569
+ reloadHasMany(key, options) {
570
+ // FIXME field needs to use sourceKey
571
+ const field = this.store.schema.fields(this.identifier).get(key);
572
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
573
+ if (!test) {
574
+ throw new Error(`Expected a hasMany field for ${key}`);
575
+ }
576
+ })(field?.kind === 'hasMany') : {};
577
+ const cacheOptions = options ? extractCacheOptions(options) : {
578
+ reload: true
579
+ };
580
+ cacheOptions.types = [field.type];
581
+ const rawValue = this.store.cache.getRelationship(this.identifier, key);
582
+ const req = {
583
+ url: getRelatedLink(rawValue),
584
+ op: 'findHasMany',
585
+ method: 'GET',
586
+ records: rawValue.data,
587
+ cacheOptions,
588
+ options: {
589
+ field,
590
+ identifier: this.identifier,
591
+ links: rawValue.links,
592
+ meta: rawValue.meta
593
+ },
594
+ [EnableHydration]: false
595
+ };
596
+ return this.store.request(req);
597
+ }
598
+ mutate(mutation) {
599
+ this.store.cache.mutate(mutation);
600
+ }
601
+ }
602
+ function getRelatedLink(resource) {
603
+ const related = resource.links?.related;
604
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
605
+ if (!test) {
606
+ throw new Error(`Expected a related link`);
607
+ }
608
+ })(related) : {};
609
+ return typeof related === 'object' ? related.href : related;
610
+ }
611
+ function extractCacheOptions(options) {
612
+ const cacheOptions = {};
613
+ if ('reload' in options) {
614
+ cacheOptions.reload = options.reload;
615
+ }
616
+ if ('backgroundReload' in options) {
617
+ cacheOptions.backgroundReload = options.backgroundReload;
618
+ }
619
+ return cacheOptions;
620
+ }
621
+ function getHasManyField(context) {
622
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
623
+ const {
624
+ store,
625
+ field
626
+ } = context;
627
+ if (field.options.linksMode) {
628
+ const {
629
+ record
630
+ } = context;
631
+ // the thing we hand out needs to know its owner and path in a private manner
632
+ // its "address" is the parent identifier (identifier) + field name (field.name)
633
+ // in the nested object case field name here is the full dot path from root resource to this value
634
+ // its "key" is the field on the parent record
635
+ // its "owner" is the parent record
636
+
637
+ const managedArrayMapForRecord = ManagedArrayMap.get(record);
638
+ let managedArray;
639
+ if (managedArrayMapForRecord) {
640
+ managedArray = managedArrayMapForRecord.get(getFieldCacheKeyStrict(field));
641
+ }
642
+ if (managedArray) {
643
+ return managedArray;
644
+ } else {
645
+ const {
646
+ editable,
647
+ resourceKey
648
+ } = context;
649
+ const {
650
+ cache
651
+ } = store;
652
+ const rawValue = cache.getRelationship(resourceKey, getFieldCacheKeyStrict(field));
653
+ if (!rawValue) {
654
+ return null;
655
+ }
656
+ managedArray = new RelatedCollection({
657
+ store,
658
+ type: field.type,
659
+ identifier: resourceKey,
660
+ cache,
661
+ field: context.legacy ? field : undefined,
662
+ // we divorce the reference here because ManyArray mutates the target directly
663
+ // before sending the mutation op to the cache. We may be able to avoid this in the future
664
+ identifiers: rawValue.data?.slice(),
665
+ key: field.name,
666
+ meta: rawValue.meta || null,
667
+ links: rawValue.links || null,
668
+ isPolymorphic: field.options.polymorphic ?? false,
669
+ isAsync: field.options.async ?? false,
670
+ // TODO: Grab the proper value
671
+ _inverseIsAsync: false,
672
+ // @ts-expect-error Typescript doesn't have a way for us to thread the generic backwards so it infers unknown instead of T
673
+ manager: new ManyArrayManager(record, editable),
674
+ isLoaded: true,
675
+ allowMutation: editable
676
+ });
677
+ if (!managedArrayMapForRecord) {
678
+ ManagedArrayMap.set(record, new Map([[field.name, managedArray]]));
679
+ } else {
680
+ managedArrayMapForRecord.set(field.name, managedArray);
681
+ }
682
+ }
683
+ return managedArray;
684
+ }
685
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
686
+ if (!test) {
687
+ throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
688
+ }
689
+ })(context.legacy) : {};
690
+ return store.schema._kind('@legacy', 'hasMany').get(store, context.record, context.resourceKey, field);
691
+ }
692
+ function setHasManyField(context) {
693
+ const {
694
+ store
695
+ } = context;
696
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
697
+ if (!test) {
698
+ throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
699
+ }
700
+ })(context.legacy) : {};
701
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
702
+ if (!test) {
703
+ throw new Error(`You must pass an array of records to set a hasMany relationship`);
704
+ }
705
+ })(Array.isArray(context.value)) : {};
706
+ store.schema._kind('@legacy', 'hasMany').set(store, context.record, context.resourceKey, context.field, context.value);
707
+ return true;
708
+ }
709
+ function getHashField(context) {
710
+ const {
711
+ field,
712
+ path,
713
+ resourceKey
714
+ } = context;
715
+ const {
716
+ schema,
717
+ cache
718
+ } = context.store;
719
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
720
+ if (!test) {
721
+ throw new Error(`Cannot use a ${field.kind} directly on a resource.`);
722
+ }
723
+ })(Array.isArray(path) && path.length > 1) : {};
724
+ const realPath = path.slice(0, -1);
725
+ const rawData = context.editable ? cache.getAttr(resourceKey, realPath) : cache.getRemoteAttr(resourceKey, realPath);
726
+ return schema.hashFn(field)(rawData, field.options ?? null, field.name ?? null);
727
+ }
728
+ function setHashField(context) {
729
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
730
+ {
731
+ throw new Error(`ILLEGAL SET: Cannot set '${Array.isArray(context.path) ? context.path.join('.') : context.path}' on '${context.resourceKey.type}' as ${context.field.kind} fields are not mutable`);
732
+ }
733
+ })() : {};
734
+ return false;
735
+ }
736
+ function getIdentityField(context) {
737
+ entangleSignal(context.signals, context.record, '@identity', null);
738
+ return context.resourceKey.id;
739
+ }
740
+ function setIdentityField(context) {
741
+ const {
742
+ value,
743
+ resourceKey,
744
+ store
745
+ } = context;
746
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
747
+ if (!test) {
748
+ throw new Error(`Expected to receive a string id`);
749
+ }
750
+ })(typeof value === 'string' && value.length) : {};
751
+ const normalizedId = String(value);
752
+ const didChange = normalizedId !== resourceKey.id;
753
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
754
+ if (!test) {
755
+ throw new Error(`Cannot set ${resourceKey.type} record's id to ${normalizedId}, because id is already ${resourceKey.id}`);
756
+ }
757
+ })(!didChange || resourceKey.id === null) : {};
758
+ if (normalizedId !== null && didChange) {
759
+ store._instanceCache.setRecordId(resourceKey, normalizedId);
760
+ store.notifications.notify(resourceKey, 'identity');
761
+ }
762
+ return true;
763
+ }
764
+ function getLocalField(context) {
765
+ const {
766
+ field
767
+ } = context;
768
+ const signal = getOrCreateInternalSignal(context.signals, context.record, field.name, field.options?.defaultValue ?? null);
769
+ consumeInternalSignal(signal);
770
+ return signal.value;
771
+ }
772
+ function setLocalField(context) {
773
+ const {
774
+ value
775
+ } = context;
776
+ const signal = getOrCreateInternalSignal(context.signals, context.record, context.field.name, value);
777
+ if (signal.value !== value) {
778
+ signal.value = value;
779
+ notifyInternalSignal(signal);
780
+ }
781
+ return true;
782
+ }
305
783
  const ObjectSymbols = new Set([OBJECT_SIGNAL, Parent, SOURCE, Editable, EmbeddedPath]);
306
784
 
307
785
  // const ignoredGlobalFields = new Set<string>(['setInterval', 'nodeType', 'nodeName', 'length', 'document', STRUCTURED]);
308
786
 
309
787
  // eslint-disable-next-line @typescript-eslint/no-extraneous-class
310
788
  class ManagedObject {
311
- constructor(schema, cache, field, data, identifier, path, owner, editable, legacy) {
789
+ constructor(context) {
790
+ const {
791
+ field,
792
+ path
793
+ } = context;
312
794
  // eslint-disable-next-line @typescript-eslint/no-this-alias
313
795
  const self = this;
314
- this[SOURCE] = {
315
- ...data
316
- };
796
+ this[SOURCE] = Object.assign({}, context.value);
317
797
  const signals = withSignalStore(this);
318
798
  const _SIGNAL = this[OBJECT_SIGNAL] = entangleSignal(signals, this, OBJECT_SIGNAL, undefined);
319
- this[Editable] = editable;
320
- this[Legacy] = legacy;
321
- this[Parent] = identifier;
799
+ this[Editable] = context.editable;
800
+ this[Legacy] = context.legacy;
801
+ this[Parent] = context.resourceKey;
322
802
  this[EmbeddedPath] = path;
803
+ const identifier = context.resourceKey;
804
+ const {
805
+ cache,
806
+ schema
807
+ } = context.store;
323
808
 
324
809
  // prettier-ignore
325
- const extensions = !legacy ? null : schema.CAUTION_MEGA_DANGER_ZONE_objectExtensions(field);
810
+ const extensions = !context.legacy ? null : schema.CAUTION_MEGA_DANGER_ZONE_objectExtensions(field);
326
811
  const proxy = new Proxy(this[SOURCE], {
327
812
  ownKeys() {
328
813
  return Object.keys(self[SOURCE]);
@@ -332,7 +817,7 @@ class ManagedObject {
332
817
  },
333
818
  getOwnPropertyDescriptor(target, prop) {
334
819
  return {
335
- writable: editable,
820
+ writable: context.editable,
336
821
  enumerable: true,
337
822
  configurable: true
338
823
  };
@@ -366,11 +851,9 @@ class ManagedObject {
366
851
  if (newData && newData !== self[SOURCE]) {
367
852
  if (field.type) {
368
853
  const transform = schema.transformation(field);
369
- newData = transform.hydrate(newData, field.options ?? null, owner);
854
+ newData = transform.hydrate(newData, field.options ?? null, context.record);
370
855
  }
371
- self[SOURCE] = {
372
- ...newData
373
- }; // Add type assertion for newData
856
+ self[SOURCE] = Object.assign({}, newData); // Add type assertion for newData
374
857
  }
375
858
  }
376
859
 
@@ -396,7 +879,7 @@ class ManagedObject {
396
879
  if (!test) {
397
880
  throw new Error(`Cannot set read-only property '${String(prop)}' on ManagedObject`);
398
881
  }
399
- })(editable) : {};
882
+ })(context.editable) : {};
400
883
 
401
884
  // since objects function as dictionaries, we can't defer to schema/data before extensions
402
885
  // unless the prop is in the existing data.
@@ -405,157 +888,35 @@ class ManagedObject {
405
888
  }
406
889
  const reflect = Reflect.set(target, prop, value, receiver);
407
890
  if (!reflect) {
408
- return false;
409
- }
410
- if (!field.type) {
411
- cache.setAttr(identifier, path, self[SOURCE]);
412
- } else {
413
- const transform = schema.transformation(field);
414
- const val = transform.serialize(self[SOURCE], field.options ?? null, owner);
415
- cache.setAttr(identifier, path, val);
416
- }
417
- _SIGNAL.isStale = true;
418
- return true;
419
- }
420
- });
421
- return proxy;
422
- }
423
- }
424
- class ManyArrayManager {
425
- constructor(record, editable) {
426
- this.record = record;
427
- this.store = record[RecordStore];
428
- this.identifier = record[Identifier];
429
- this.editable = editable;
430
- }
431
- _syncArray(array) {
432
- const method = this.editable ? 'getRelationship' : 'getRemoteRelationship';
433
- const rawValue = this.store.cache[method](this.identifier, array.key);
434
- if (rawValue.meta) {
435
- array.meta = rawValue.meta;
436
- }
437
- if (rawValue.links) {
438
- array.links = rawValue.links;
439
- }
440
- const currentState = array[SOURCE$1];
441
-
442
- // unlike in the normal RecordArray case, we don't need to divorce the reference
443
- // because we don't need to worry about associate/disassociate since the graph
444
- // takes care of that for us
445
- if (currentState !== rawValue.data) {
446
- currentState.length = 0;
447
- fastPush(currentState, rawValue.data);
448
- }
449
- }
450
- reloadHasMany(key, options) {
451
- const field = this.store.schema.fields(this.identifier).get(key);
452
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
453
- if (!test) {
454
- throw new Error(`Expected a hasMany field for ${key}`);
455
- }
456
- })(field?.kind === 'hasMany') : {};
457
- const cacheOptions = options ? extractCacheOptions(options) : {
458
- reload: true
459
- };
460
- cacheOptions.types = [field.type];
461
- const rawValue = this.store.cache.getRelationship(this.identifier, key);
462
- const req = {
463
- url: getRelatedLink(rawValue),
464
- op: 'findHasMany',
465
- method: 'GET',
466
- records: rawValue.data,
467
- cacheOptions,
468
- options: {
469
- field,
470
- identifier: this.identifier,
471
- links: rawValue.links,
472
- meta: rawValue.meta
473
- },
474
- [EnableHydration]: false
475
- };
476
- return this.store.request(req);
477
- }
478
- mutate(mutation) {
479
- this.store.cache.mutate(mutation);
480
- }
481
- }
482
- function getRelatedLink(resource) {
483
- const related = resource.links?.related;
484
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
485
- if (!test) {
486
- throw new Error(`Expected a related link`);
487
- }
488
- })(related) : {};
489
- return typeof related === 'object' ? related.href : related;
490
- }
491
- function extractCacheOptions(options) {
492
- const cacheOptions = {};
493
- if ('reload' in options) {
494
- cacheOptions.reload = options.reload;
495
- }
496
- if ('backgroundReload' in options) {
497
- cacheOptions.backgroundReload = options.backgroundReload;
891
+ return false;
892
+ }
893
+ if (!field.type) {
894
+ cache.setAttr(identifier, path, self[SOURCE]);
895
+ } else {
896
+ const transform = schema.transformation(field);
897
+ const val = transform.serialize(self[SOURCE], field.options ?? null, context.record);
898
+ cache.setAttr(identifier, path, val);
899
+ }
900
+ _SIGNAL.isStale = true;
901
+ return true;
902
+ }
903
+ });
904
+ return proxy;
498
905
  }
499
- return cacheOptions;
500
906
  }
501
- const ManagedArrayMap = getOrSetGlobal('ManagedArrayMap', new Map());
502
907
  const ManagedObjectMap = getOrSetGlobal('ManagedObjectMap', new Map());
503
- function computeLocal(record, field, prop) {
504
- const signals = withSignalStore(record);
505
- const signal = getOrCreateInternalSignal(signals, record, prop, field.options?.defaultValue ?? null);
506
- consumeInternalSignal(signal);
507
- return signal.value;
508
- }
509
- function peekManagedArray(record, field) {
510
- const managedArrayMapForRecord = ManagedArrayMap.get(record);
511
- if (managedArrayMapForRecord) {
512
- return managedArrayMapForRecord.get(field.name);
513
- }
514
- }
515
908
  function peekManagedObject(record, field) {
516
909
  const managedObjectMapForRecord = ManagedObjectMap.get(record);
517
910
  if (managedObjectMapForRecord) {
518
911
  return managedObjectMapForRecord.get(field.name);
519
912
  }
520
913
  }
521
- function computeField(schema, cache, record, identifier, field, prop, editable) {
522
- const rawValue = editable ? cache.getAttr(identifier, prop) : cache.getRemoteAttr(identifier, prop);
523
- if (!field.type) {
524
- return rawValue;
525
- }
526
- const transform = schema.transformation(field);
527
- return transform.hydrate(rawValue, field.options ?? null, record);
528
- }
529
- function computeArray(store, schema, cache, record, identifier, field, path, editable, legacy) {
530
- const isSchemaArray = field.kind === 'schema-array';
531
- // the thing we hand out needs to know its owner and path in a private manner
532
- // its "address" is the parent identifier (identifier) + field name (field.name)
533
- // in the nested object case field name here is the full dot path from root resource to this value
534
- // its "key" is the field on the parent record
535
- // its "owner" is the parent record
536
-
537
- const managedArrayMapForRecord = ManagedArrayMap.get(record);
538
- let managedArray;
539
- if (managedArrayMapForRecord) {
540
- managedArray = managedArrayMapForRecord.get(field.name);
541
- }
542
- if (managedArray) {
543
- return managedArray;
544
- } else {
545
- const rawValue = editable ? cache.getAttr(identifier, path) : cache.getRemoteAttr(identifier, path);
546
- if (!rawValue) {
547
- return null;
548
- }
549
- managedArray = new ManagedArray(store, schema, cache, field, rawValue, identifier, path, record, isSchemaArray, editable, legacy);
550
- if (!managedArrayMapForRecord) {
551
- ManagedArrayMap.set(record, new Map([[field.name, managedArray]]));
552
- } else {
553
- managedArrayMapForRecord.set(field.name, managedArray);
554
- }
555
- }
556
- return managedArray;
557
- }
558
- function computeObject(schema, cache, record, identifier, field, path, editable, legacy) {
914
+ function getObjectField(context) {
915
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
916
+ const {
917
+ record,
918
+ field
919
+ } = context;
559
920
  const managedObjectMapForRecord = ManagedObjectMap.get(record);
560
921
  let managedObject;
561
922
  if (managedObjectMapForRecord) {
@@ -564,7 +925,16 @@ function computeObject(schema, cache, record, identifier, field, path, editable,
564
925
  if (managedObject) {
565
926
  return managedObject;
566
927
  } else {
567
- let rawValue = editable ? cache.getAttr(identifier, path) : cache.getRemoteAttr(identifier, path);
928
+ const {
929
+ store,
930
+ resourceKey,
931
+ path
932
+ } = context;
933
+ const {
934
+ cache,
935
+ schema
936
+ } = store;
937
+ let rawValue = context.editable ? cache.getAttr(resourceKey, path) : cache.getRemoteAttr(resourceKey, path);
568
938
  if (!rawValue) {
569
939
  return null;
570
940
  }
@@ -572,7 +942,18 @@ function computeObject(schema, cache, record, identifier, field, path, editable,
572
942
  const transform = schema.transformation(field);
573
943
  rawValue = transform.hydrate(rawValue, field.options ?? null, record);
574
944
  }
575
- managedObject = new ManagedObject(schema, cache, field, rawValue, identifier, path, record, editable, legacy);
945
+ managedObject = new ManagedObject({
946
+ store,
947
+ resourceKey,
948
+ modeName: context.modeName,
949
+ legacy: context.legacy,
950
+ editable: context.editable,
951
+ path,
952
+ field,
953
+ record,
954
+ signals: context.signals,
955
+ value: rawValue
956
+ });
576
957
  if (!managedObjectMapForRecord) {
577
958
  ManagedObjectMap.set(record, new Map([[field.name, managedObject]]));
578
959
  } else {
@@ -581,48 +962,64 @@ function computeObject(schema, cache, record, identifier, field, path, editable,
581
962
  }
582
963
  return managedObject;
583
964
  }
584
- function computeSchemaObject(store, cache, record, identifier, field, path, legacy, editable) {
585
- const schemaObjectMapForRecord = ManagedObjectMap.get(record);
586
- let schemaObject;
587
- if (schemaObjectMapForRecord) {
588
- schemaObject = schemaObjectMapForRecord.get(field.name);
589
- }
590
- if (schemaObject) {
591
- return schemaObject;
592
- } else {
593
- const rawValue = editable ? cache.getAttr(identifier, path) : cache.getRemoteAttr(identifier, path);
594
- if (!rawValue) {
595
- return null;
965
+ function setObjectField(context) {
966
+ const {
967
+ field,
968
+ value,
969
+ record
970
+ } = context;
971
+ const {
972
+ cache,
973
+ schema
974
+ } = context.store;
975
+ if (!field.type) {
976
+ let newValue = value;
977
+ if (value !== null) {
978
+ newValue = {
979
+ ...value
980
+ };
981
+ } else {
982
+ ManagedObjectMap.delete(record);
596
983
  }
597
- const embeddedPath = path.slice();
598
- schemaObject = new ReactiveResource(store, identifier, {
599
- [Editable]: editable,
600
- [Legacy]: legacy
601
- }, true, field, embeddedPath);
602
- }
603
- if (!schemaObjectMapForRecord) {
604
- ManagedObjectMap.set(record, new Map([[field.name, schemaObject]]));
605
- } else {
606
- schemaObjectMapForRecord.set(field.name, schemaObject);
984
+ cache.setAttr(context.resourceKey, context.path, newValue);
985
+ const peeked = peekManagedObject(record, field);
986
+ if (peeked) {
987
+ const objSignal = peeked[OBJECT_SIGNAL];
988
+ objSignal.isStale = true;
989
+ }
990
+ return true;
607
991
  }
608
- return schemaObject;
609
- }
610
- function computeAttribute(cache, identifier, prop, editable) {
611
- return editable ? cache.getAttr(identifier, prop) : cache.getRemoteAttr(identifier, prop);
612
- }
613
- function computeDerivation(schema, record, identifier, field, prop) {
614
- return schema.derivation(field)(record, field.options ?? null, prop);
992
+ const transform = schema.transformation(field);
993
+ const rawValue = transform.serialize({
994
+ ...value
995
+ }, field.options ?? null, record);
996
+ cache.setAttr(context.resourceKey, context.path, rawValue);
997
+ const peeked = peekManagedObject(record, field);
998
+ if (peeked) {
999
+ const objSignal = peeked[OBJECT_SIGNAL];
1000
+ objSignal.isStale = true;
1001
+ }
1002
+ return true;
615
1003
  }
1004
+
616
1005
  // TODO probably this should just be a Document
617
1006
  // but its separate until we work out the lid situation
618
1007
  class ResourceRelationship {
619
- constructor(store, cache, parent, identifier, field, name, editable) {
620
- const rawValue = editable ? cache.getRelationship(identifier, name) : cache.getRemoteRelationship(identifier, name);
1008
+ constructor(context) {
1009
+ const {
1010
+ store,
1011
+ resourceKey
1012
+ } = context;
1013
+ const {
1014
+ cache
1015
+ } = store;
1016
+ const name = getFieldCacheKeyStrict(context.field);
1017
+ const rawValue = context.editable ? cache.getRelationship(resourceKey, name) : cache.getRemoteRelationship(resourceKey, name);
621
1018
 
622
1019
  // TODO setup true lids for relationship documents
623
1020
  // @ts-expect-error we need to give relationship documents a lid
624
1021
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
625
- this.lid = rawValue.lid ?? rawValue.links?.self ?? `relationship:${identifier.lid}.${name}`;
1022
+ this.lid = rawValue.lid ?? rawValue.links?.self ?? `relationship:${resourceKey.lid}.${name}`;
626
1023
  this.data = rawValue.data ? store.peekRecord(rawValue.data) : null;
627
1024
  this.name = name;
628
1025
  if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
@@ -633,7 +1030,7 @@ class ResourceRelationship {
633
1030
  this.meta = rawValue.meta ?? {};
634
1031
  }
635
1032
  this[RecordStore] = store;
636
- this[Parent] = parent;
1033
+ this[Parent] = context.record;
637
1034
  }
638
1035
  fetch(options) {
639
1036
  const url = options?.url ?? getHref(this.links.related) ?? getHref(this.links.self) ?? null;
@@ -659,60 +1056,220 @@ function getHref(link) {
659
1056
  }
660
1057
  return link.href;
661
1058
  }
662
- function computeResource(store, cache, parent, identifier, field, prop, editable) {
663
- if (field.kind !== 'resource') {
664
- throw new Error(`The schema for ${identifier.type}.${String(prop)} is not a resource relationship`);
1059
+ function getResourceField(context) {
1060
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
1061
+ return new ResourceRelationship(context);
1062
+ }
1063
+ function setResourceField(context) {
1064
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1065
+ {
1066
+ throw new Error(`setting resource relationships is not yet supported`);
1067
+ }
1068
+ })() : {};
1069
+ return false;
1070
+ }
1071
+ function setSchemaArrayField(context) {
1072
+ const arrayValue = context.value?.slice();
1073
+ if (!Array.isArray(arrayValue)) {
1074
+ ManagedArrayMap.delete(context.record);
1075
+ }
1076
+ context.store.cache.setAttr(context.resourceKey, context.path, arrayValue);
1077
+ const peeked = peekManagedArray(context.record, context.field);
1078
+ if (peeked) {
1079
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1080
+ if (!test) {
1081
+ throw new Error(`Expected the peekManagedArray for ${context.field.kind} to return a ManagedArray`);
1082
+ }
1083
+ })(ARRAY_SIGNAL in peeked) : {};
1084
+ const arrSignal = peeked[ARRAY_SIGNAL];
1085
+ arrSignal.isStale = true;
665
1086
  }
666
- return new ResourceRelationship(store, cache, parent, identifier, field, prop, editable);
1087
+ return true;
667
1088
  }
668
- function computeHasMany(store, schema, cache, record, identifier, field, path, editable, legacy) {
669
- // the thing we hand out needs to know its owner and path in a private manner
670
- // its "address" is the parent identifier (identifier) + field name (field.name)
671
- // in the nested object case field name here is the full dot path from root resource to this value
672
- // its "key" is the field on the parent record
673
- // its "owner" is the parent record
674
-
675
- const managedArrayMapForRecord = ManagedArrayMap.get(record);
676
- let managedArray;
677
- if (managedArrayMapForRecord) {
678
- managedArray = managedArrayMapForRecord.get(field.name);
1089
+ function getSchemaObjectField(context) {
1090
+ entangleSignal(context.signals, context.record, context.path.at(-1), null);
1091
+ const {
1092
+ record,
1093
+ field
1094
+ } = context;
1095
+ const schemaObjectMapForRecord = ManagedObjectMap.get(record);
1096
+ let schemaObject;
1097
+ if (schemaObjectMapForRecord) {
1098
+ schemaObject = schemaObjectMapForRecord.get(field.name);
679
1099
  }
680
- if (managedArray) {
681
- return managedArray;
1100
+ if (schemaObject) {
1101
+ return schemaObject;
682
1102
  } else {
683
- const rawValue = cache.getRelationship(identifier, field.name);
1103
+ const {
1104
+ store,
1105
+ resourceKey,
1106
+ path
1107
+ } = context;
1108
+ const {
1109
+ cache
1110
+ } = store;
1111
+ const rawValue = context.editable ? cache.getAttr(resourceKey, path) : cache.getRemoteAttr(resourceKey, path);
684
1112
  if (!rawValue) {
685
1113
  return null;
686
1114
  }
687
- managedArray = new RelatedCollection({
688
- store,
689
- type: field.type,
690
- identifier,
691
- cache,
692
- field: legacy ? field : undefined,
693
- // we divorce the reference here because ManyArray mutates the target directly
694
- // before sending the mutation op to the cache. We may be able to avoid this in the future
695
- identifiers: rawValue.data?.slice(),
696
- key: field.name,
697
- meta: rawValue.meta || null,
698
- links: rawValue.links || null,
699
- isPolymorphic: field.options.polymorphic ?? false,
700
- isAsync: field.options.async ?? false,
701
- // TODO: Grab the proper value
702
- _inverseIsAsync: false,
703
- // @ts-expect-error Typescript doesn't have a way for us to thread the generic backwards so it infers unknown instead of T
704
- manager: new ManyArrayManager(record, editable),
705
- isLoaded: true,
706
- allowMutation: editable
1115
+ schemaObject = new ReactiveResource({
1116
+ store: context.store,
1117
+ resourceKey: context.resourceKey,
1118
+ modeName: context.modeName,
1119
+ legacy: context.legacy,
1120
+ editable: context.editable,
1121
+ path: context.path,
1122
+ field: context.field
707
1123
  });
708
- if (!managedArrayMapForRecord) {
709
- ManagedArrayMap.set(record, new Map([[field.name, managedArray]]));
710
- } else {
711
- managedArrayMapForRecord.set(field.name, managedArray);
712
- }
713
1124
  }
714
- return managedArray;
1125
+ if (!schemaObjectMapForRecord) {
1126
+ ManagedObjectMap.set(record, new Map([[field.name, schemaObject]]));
1127
+ } else {
1128
+ schemaObjectMapForRecord.set(field.name, schemaObject);
1129
+ }
1130
+ return schemaObject;
1131
+ }
1132
+ function setSchemaObjectField(context) {
1133
+ const {
1134
+ store,
1135
+ value
1136
+ } = context;
1137
+ let newValue = value;
1138
+ if (value !== null) {
1139
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1140
+ if (!test) {
1141
+ throw new Error(`Expected value to be an object`);
1142
+ }
1143
+ })(typeof value === 'object') : {};
1144
+ newValue = {
1145
+ ...value
1146
+ };
1147
+ const schemaFields = store.schema.fields({
1148
+ type: context.field.type
1149
+ });
1150
+ for (const key of Object.keys(newValue)) {
1151
+ if (!schemaFields.has(key)) {
1152
+ throw new Error(`Field ${key} does not exist on schema object ${context.field.type}`);
1153
+ }
1154
+ }
1155
+ } else {
1156
+ ManagedObjectMap.delete(context.record);
1157
+ }
1158
+ store.cache.setAttr(context.resourceKey, context.path, newValue);
1159
+ // const peeked = peekManagedObject(self, field);
1160
+ // if (peeked) {
1161
+ // const objSignal = peeked[OBJECT_SIGNAL];
1162
+ // objSignal.isStale = true;
1163
+ // }
1164
+ return true;
715
1165
  }
1166
+ const DefaultMode = {
1167
+ '@hash': {
1168
+ get: getHashField,
1169
+ set: setHashField,
1170
+ mutable: false,
1171
+ enumerable: false,
1172
+ serializable: false
1173
+ },
1174
+ '@id': {
1175
+ get: getIdentityField,
1176
+ set: setIdentityField,
1177
+ mutable: true,
1178
+ enumerable: true,
1179
+ serializable: true
1180
+ },
1181
+ '@local': {
1182
+ get: getLocalField,
1183
+ set: setLocalField,
1184
+ mutable: true,
1185
+ enumerable: false,
1186
+ serializable: false
1187
+ },
1188
+ alias: {
1189
+ get: getAliasField,
1190
+ set: setAliasField,
1191
+ mutable: true,
1192
+ enumerable: true,
1193
+ serializable: false
1194
+ },
1195
+ array: {
1196
+ get: getArrayField,
1197
+ set: setArrayField,
1198
+ mutable: true,
1199
+ enumerable: true,
1200
+ serializable: true
1201
+ },
1202
+ attribute: {
1203
+ get: getAttributeField,
1204
+ set: setAttributeField,
1205
+ mutable: true,
1206
+ enumerable: true,
1207
+ serializable: true
1208
+ },
1209
+ belongsTo: {
1210
+ get: getBelongsToField,
1211
+ set: setBelongsToField,
1212
+ mutable: true,
1213
+ enumerable: true,
1214
+ serializable: true
1215
+ },
1216
+ collection: {
1217
+ get: getCollectionField,
1218
+ set: setCollectionField,
1219
+ mutable: true,
1220
+ enumerable: true,
1221
+ serializable: true
1222
+ },
1223
+ derived: {
1224
+ get: getDerivedField,
1225
+ set: setDerivedField,
1226
+ mutable: true,
1227
+ enumerable: true,
1228
+ serializable: false
1229
+ },
1230
+ field: {
1231
+ get: getGenericField,
1232
+ set: setGenericField,
1233
+ mutable: true,
1234
+ enumerable: true,
1235
+ serializable: true
1236
+ },
1237
+ hasMany: {
1238
+ get: getHasManyField,
1239
+ set: setHasManyField,
1240
+ mutable: true,
1241
+ enumerable: true,
1242
+ serializable: true
1243
+ },
1244
+ object: {
1245
+ get: getObjectField,
1246
+ set: setObjectField,
1247
+ mutable: true,
1248
+ enumerable: true,
1249
+ serializable: true
1250
+ },
1251
+ resource: {
1252
+ get: getResourceField,
1253
+ set: setResourceField,
1254
+ mutable: true,
1255
+ enumerable: true,
1256
+ serializable: true
1257
+ },
1258
+ 'schema-array': {
1259
+ get: getArrayField,
1260
+ set: setSchemaArrayField,
1261
+ mutable: true,
1262
+ enumerable: true,
1263
+ serializable: true
1264
+ },
1265
+ 'schema-object': {
1266
+ get: getSchemaObjectField,
1267
+ set: setSchemaObjectField,
1268
+ mutable: true,
1269
+ enumerable: true,
1270
+ serializable: true
1271
+ }
1272
+ };
716
1273
  const IgnoredGlobalFields = new Set(['length', 'nodeType', 'then', 'setInterval', 'document', STRUCTURED]);
717
1274
  const symbolList = [Destroy, RecordStore, Identifier, Editable, Parent, Checkout, Legacy, EmbeddedPath, EmbeddedField];
718
1275
  const RecordSymbols = new Set(symbolList);
@@ -737,24 +1294,29 @@ const Editables = new WeakMap();
737
1294
  */
738
1295
  // eslint-disable-next-line @typescript-eslint/no-extraneous-class
739
1296
  class ReactiveResource {
740
- constructor(store, identifier, Mode, isEmbedded = false, embeddedField = null, embeddedPath = null) {
741
- // eslint-disable-next-line @typescript-eslint/no-this-alias
742
- const self = this;
1297
+ constructor(context) {
1298
+ const {
1299
+ store
1300
+ } = context;
1301
+ const identifier = context.resourceKey;
1302
+ const embeddedField = context.field;
1303
+ const embeddedPath = context.path;
1304
+ const isEmbedded = context.field !== null;
743
1305
  this[RecordStore] = store;
744
1306
  if (isEmbedded) {
745
1307
  this[Parent] = identifier;
746
1308
  } else {
747
1309
  this[Identifier] = identifier;
748
1310
  }
749
- const IS_EDITABLE = this[Editable] = Mode[Editable] ?? false;
750
- this[Legacy] = Mode[Legacy] ?? false;
1311
+ const IS_EDITABLE = this[Editable] = context.editable ?? false;
1312
+ this[Legacy] = context.legacy ?? false;
751
1313
  const schema = store.schema;
752
- const cache = store.cache;
753
- const identityField = schema.resource(isEmbedded ? embeddedField : identifier).identity;
1314
+ const ResourceSchema = schema.resource(isEmbedded ? embeddedField : identifier);
1315
+ const identityField = ResourceSchema.identity;
754
1316
  const BoundFns = new Map();
755
1317
 
756
1318
  // prettier-ignore
757
- const extensions = !Mode[Legacy] ? null : isEmbedded ? schema.CAUTION_MEGA_DANGER_ZONE_objectExtensions(embeddedField) : schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(identifier);
1319
+ const extensions = !context.legacy ? null : isEmbedded ? schema.CAUTION_MEGA_DANGER_ZONE_objectExtensions(embeddedField) : schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(identifier);
758
1320
  this[EmbeddedField] = embeddedField;
759
1321
  this[EmbeddedPath] = embeddedPath;
760
1322
  const fields = isEmbedded ? schema.fields(embeddedField) : schema.fields(identifier);
@@ -934,74 +1496,64 @@ class ReactiveResource {
934
1496
  throw new Error(`Alias fields cannot alias '@id' '@local' '@hash' or 'derived' fields`);
935
1497
  }
936
1498
  })(maybeField.kind !== 'alias' || !['@id', '@local', '@hash', 'derived'].includes(maybeField.options.kind)) : {};
1499
+ /**
1500
+ * Prop Array is the path from a resource to the field including
1501
+ * intermediate "links" on arrays,objects,schema-arrays and schema-objects.
1502
+ *
1503
+ * E.g. in the following
1504
+ *
1505
+ * ```
1506
+ * const user = {
1507
+ * addresses: [{
1508
+ * street: 'Sunset Blvd',
1509
+ * zip: 90210
1510
+ * }]
1511
+ * }
1512
+ * ```
1513
+ *
1514
+ * The propArray for "street" is ['addresses', 0, 'street']
1515
+ *
1516
+ * Prop Array follows the `cache` path to the value, not the ui path.
1517
+ * Thus, if `addresses` has a sourceKey of `user_addresses` and
1518
+ * `zip` has a sourceKey of `zip_code` then the propArray for "zip" is
1519
+ * ['user_addresses', 0, 'zip_code']
1520
+ */
937
1521
  const propArray = isEmbedded ? embeddedPath.slice() : [];
938
1522
  // we use the field.name instead of prop here because we want to use the cache-path not
939
1523
  // the record path.
940
- propArray.push(field.name);
941
- // propArray.push(prop as string);
942
-
1524
+ // SAFETY: we lie as string here because if we were to get null
1525
+ // we would be in a field kind that won't use the propArray below.
1526
+ const fieldCacheKey = getFieldCacheKey(field);
1527
+ propArray.push(fieldCacheKey);
943
1528
  switch (field.kind) {
944
1529
  case '@id':
945
- entangleSignal(signals, receiver, '@identity', null);
946
- return identifier.id;
947
1530
  case '@hash':
948
- // TODO pass actual cache value not {}
949
- return schema.hashFn(field)({}, field.options ?? null, field.name ?? null);
950
1531
  case '@local':
951
- {
952
- return computeLocal(receiver, field, prop);
953
- }
1532
+ case 'derived':
954
1533
  case 'field':
955
- entangleSignal(signals, receiver, field.name, null);
956
- return computeField(schema, cache, target, identifier, field, propArray, IS_EDITABLE);
957
1534
  case 'attribute':
958
- entangleSignal(signals, receiver, field.name, null);
959
- return computeAttribute(cache, identifier, prop, IS_EDITABLE);
960
- case 'resource':
961
- entangleSignal(signals, receiver, field.name, null);
962
- return computeResource(store, cache, target, identifier, field, prop, IS_EDITABLE);
963
- case 'derived':
964
- return computeDerivation(schema, receiver, identifier, field, prop);
965
1535
  case 'schema-array':
966
1536
  case 'array':
967
- entangleSignal(signals, receiver, field.name, null);
968
- return computeArray(store, schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
969
- case 'object':
970
- entangleSignal(signals, receiver, field.name, null);
971
- return computeObject(schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
972
1537
  case 'schema-object':
973
- entangleSignal(signals, receiver, field.name, null);
974
- // run transform, then use that value as the object to manage
975
- return computeSchemaObject(store, cache, target, identifier, field, propArray, Mode[Legacy], Mode[Editable]);
1538
+ case 'object':
1539
+ case 'resource':
976
1540
  case 'belongsTo':
977
- if (field.options.linksMode) {
978
- entangleSignal(signals, receiver, field.name, null);
979
- const rawValue = IS_EDITABLE ? cache.getRelationship(identifier, field.name) : cache.getRemoteRelationship(identifier, field.name);
980
-
981
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
982
- return rawValue.data ? store.peekRecord(rawValue.data) : null;
983
- }
984
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
985
- if (!test) {
986
- throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
987
- }
988
- })(Mode[Legacy]) : {};
989
- entangleSignal(signals, receiver, field.name, null);
990
- return schema._kind('@legacy', 'belongsTo').get(store, receiver, identifier, field);
991
1541
  case 'hasMany':
992
- if (field.options.linksMode) {
993
- entangleSignal(signals, receiver, field.name, null);
994
- return computeHasMany(store, schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
995
- }
996
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
997
- if (!test) {
998
- throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
999
- }
1000
- })(Mode[Legacy]) : {};
1001
- entangleSignal(signals, receiver, field.name, null);
1002
- return schema._kind('@legacy', 'hasMany').get(store, receiver, identifier, field);
1542
+ case 'collection':
1543
+ return DefaultMode[field.kind].get({
1544
+ store,
1545
+ resourceKey: identifier,
1546
+ modeName: context.modeName,
1547
+ legacy: context.legacy,
1548
+ editable: context.editable,
1549
+ path: propArray,
1550
+ field: field,
1551
+ record: receiver,
1552
+ signals,
1553
+ value: null
1554
+ });
1003
1555
  default:
1004
- throw new Error(`Field '${String(prop)}' on '${identifier.type}' has the unknown kind '${field.kind}'`);
1556
+ assertNeverField(identifier, field, propArray);
1005
1557
  }
1006
1558
  },
1007
1559
  set(target, prop, value, receiver) {
@@ -1028,203 +1580,64 @@ class ReactiveResource {
1028
1580
  throw new Error(`Alias fields cannot alias '@id' '@local' '@hash' or 'derived' fields`);
1029
1581
  }
1030
1582
  })(maybeField.kind !== 'alias' || !['@id', '@local', '@hash', 'derived'].includes(maybeField.options.kind)) : {};
1583
+ /**
1584
+ * Prop Array is the path from a resource to the field including
1585
+ * intermediate "links" on arrays,objects,schema-arrays and schema-objects.
1586
+ *
1587
+ * E.g. in the following
1588
+ *
1589
+ * ```
1590
+ * const user = {
1591
+ * addresses: [{
1592
+ * street: 'Sunset Blvd',
1593
+ * zip: 90210
1594
+ * }]
1595
+ * }
1596
+ * ```
1597
+ *
1598
+ * The propArray for "street" is ['addresses', 0, 'street']
1599
+ *
1600
+ * Prop Array follows the `cache` path to the value, not the ui path.
1601
+ * Thus, if `addresses` has a sourceKey of `user_addresses` and
1602
+ * `zip` has a sourceKey of `zip_code` then the propArray for "zip" is
1603
+ * ['user_addresses', 0, 'zip_code']
1604
+ */
1031
1605
  const propArray = isEmbedded ? embeddedPath.slice() : [];
1032
1606
  // we use the field.name instead of prop here because we want to use the cache-path not
1033
1607
  // the record path.
1034
- propArray.push(field.name);
1035
- // propArray.push(prop as string);
1036
-
1608
+ // SAFETY: we lie as string here because if we were to get null
1609
+ // we would be in a field kind that won't use the propArray below.
1610
+ const fieldCacheKey = getFieldCacheKey(field);
1611
+ propArray.push(fieldCacheKey);
1037
1612
  switch (field.kind) {
1038
1613
  case '@id':
1039
- {
1040
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1041
- if (!test) {
1042
- throw new Error(`Expected to receive a string id`);
1043
- }
1044
- })(typeof value === 'string' && value.length) : {};
1045
- const normalizedId = String(value);
1046
- const didChange = normalizedId !== identifier.id;
1047
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1048
- if (!test) {
1049
- throw new Error(`Cannot set ${identifier.type} record's id to ${normalizedId}, because id is already ${identifier.id}`);
1050
- }
1051
- })(!didChange || identifier.id === null) : {};
1052
- if (normalizedId !== null && didChange) {
1053
- store._instanceCache.setRecordId(identifier, normalizedId);
1054
- store.notifications.notify(identifier, 'identity');
1055
- }
1056
- return true;
1057
- }
1614
+ case '@hash':
1058
1615
  case '@local':
1059
- {
1060
- const signal = getOrCreateInternalSignal(signals, receiver, prop, field.options?.defaultValue ?? null);
1061
- if (signal.value !== value) {
1062
- signal.value = value;
1063
- notifyInternalSignal(signal);
1064
- }
1065
- return true;
1066
- }
1067
1616
  case 'field':
1068
- {
1069
- if (!field.type) {
1070
- cache.setAttr(identifier, propArray, value);
1071
- return true;
1072
- }
1073
- const transform = schema.transformation(field);
1074
- const rawValue = transform.serialize(value, field.options ?? null, target);
1075
- cache.setAttr(identifier, propArray, rawValue);
1076
- return true;
1077
- }
1078
1617
  case 'attribute':
1079
- {
1080
- cache.setAttr(identifier, propArray, value);
1081
- return true;
1082
- }
1618
+ case 'derived':
1083
1619
  case 'array':
1084
- {
1085
- if (!field.type) {
1086
- cache.setAttr(identifier, propArray, value?.slice());
1087
- const peeked = peekManagedArray(self, field);
1088
- if (peeked) {
1089
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1090
- if (!test) {
1091
- throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
1092
- }
1093
- })(ARRAY_SIGNAL in peeked) : {};
1094
- const arrSignal = peeked[ARRAY_SIGNAL];
1095
- arrSignal.isStale = true;
1096
- }
1097
- if (!Array.isArray(value)) {
1098
- ManagedArrayMap.delete(target);
1099
- }
1100
- return true;
1101
- }
1102
- const transform = schema.transformation(field);
1103
- const rawValue = value.map(item => transform.serialize(item, field.options ?? null, target));
1104
- cache.setAttr(identifier, propArray, rawValue);
1105
- const peeked = peekManagedArray(self, field);
1106
- if (peeked) {
1107
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1108
- if (!test) {
1109
- throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
1110
- }
1111
- })(ARRAY_SIGNAL in peeked) : {};
1112
- const arrSignal = peeked[ARRAY_SIGNAL];
1113
- arrSignal.isStale = true;
1114
- }
1115
- return true;
1116
- }
1117
1620
  case 'schema-array':
1118
- {
1119
- const arrayValue = value?.slice();
1120
- if (!Array.isArray(arrayValue)) {
1121
- ManagedArrayMap.delete(target);
1122
- }
1123
- cache.setAttr(identifier, propArray, arrayValue);
1124
- const peeked = peekManagedArray(self, field);
1125
- if (peeked) {
1126
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1127
- if (!test) {
1128
- throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
1129
- }
1130
- })(ARRAY_SIGNAL in peeked) : {};
1131
- const arrSignal = peeked[ARRAY_SIGNAL];
1132
- arrSignal.isStale = true;
1133
- }
1134
- if (!Array.isArray(value)) {
1135
- ManagedArrayMap.delete(target);
1136
- }
1137
- return true;
1138
- }
1139
- case 'object':
1140
- {
1141
- if (!field.type) {
1142
- let newValue = value;
1143
- if (value !== null) {
1144
- newValue = {
1145
- ...value
1146
- };
1147
- } else {
1148
- ManagedObjectMap.delete(target);
1149
- }
1150
- cache.setAttr(identifier, propArray, newValue);
1151
- const peeked = peekManagedObject(self, field);
1152
- if (peeked) {
1153
- const objSignal = peeked[OBJECT_SIGNAL];
1154
- objSignal.isStale = true;
1155
- }
1156
- return true;
1157
- }
1158
- const transform = schema.transformation(field);
1159
- const rawValue = transform.serialize({
1160
- ...value
1161
- }, field.options ?? null, target);
1162
- cache.setAttr(identifier, propArray, rawValue);
1163
- const peeked = peekManagedObject(self, field);
1164
- if (peeked) {
1165
- const objSignal = peeked[OBJECT_SIGNAL];
1166
- objSignal.isStale = true;
1167
- }
1168
- return true;
1169
- }
1170
1621
  case 'schema-object':
1171
- {
1172
- let newValue = value;
1173
- if (value !== null) {
1174
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1175
- if (!test) {
1176
- throw new Error(`Expected value to be an object`);
1177
- }
1178
- })(typeof value === 'object') : {};
1179
- newValue = {
1180
- ...value
1181
- };
1182
- const schemaFields = schema.fields({
1183
- type: field.type
1184
- });
1185
- for (const key of Object.keys(newValue)) {
1186
- if (!schemaFields.has(key)) {
1187
- throw new Error(`Field ${key} does not exist on schema object ${field.type}`);
1188
- }
1189
- }
1190
- } else {
1191
- ManagedObjectMap.delete(target);
1192
- }
1193
- cache.setAttr(identifier, propArray, newValue);
1194
- // const peeked = peekManagedObject(self, field);
1195
- // if (peeked) {
1196
- // const objSignal = peeked[OBJECT_SIGNAL];
1197
- // objSignal.isStale = true;
1198
- // }
1199
- return true;
1200
- }
1201
- case 'derived':
1202
- {
1203
- throw new Error(`Cannot set ${String(prop)} on ${identifier.type} because it is derived`);
1204
- }
1622
+ case 'object':
1623
+ case 'resource':
1205
1624
  case 'belongsTo':
1206
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1207
- if (!test) {
1208
- throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
1209
- }
1210
- })(Mode[Legacy]) : {};
1211
- schema._kind('@legacy', 'belongsTo').set(store, receiver, identifier, field, value);
1212
- return true;
1213
1625
  case 'hasMany':
1214
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1215
- if (!test) {
1216
- throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
1217
- }
1218
- })(Mode[Legacy]) : {};
1219
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1220
- if (!test) {
1221
- throw new Error(`You must pass an array of records to set a hasMany relationship`);
1222
- }
1223
- })(Array.isArray(value)) : {};
1224
- schema._kind('@legacy', 'hasMany').set(store, receiver, identifier, field, value);
1225
- return true;
1626
+ case 'collection':
1627
+ return DefaultMode[field.kind].set({
1628
+ store,
1629
+ resourceKey: identifier,
1630
+ modeName: context.modeName,
1631
+ legacy: context.legacy,
1632
+ editable: context.editable,
1633
+ path: propArray,
1634
+ field: field,
1635
+ record: receiver,
1636
+ signals,
1637
+ value
1638
+ });
1226
1639
  default:
1227
- throw new Error(`Unknown field kind ${field.kind}`);
1640
+ return assertNeverField(identifier, field, propArray);
1228
1641
  }
1229
1642
  }
1230
1643
  });
@@ -1255,25 +1668,25 @@ class ReactiveResource {
1255
1668
  // TODO we should likely handle this notification here
1256
1669
  // also we should add a LOGGING flag
1257
1670
  // eslint-disable-next-line no-console
1258
- console.warn(`Notification unhandled for ${key.join(',')} on ${identifier.type}`, self);
1671
+ console.warn(`Notification unhandled for ${key.join(',')} on ${identifier.type}`, proxy);
1259
1672
  return;
1260
1673
  }
1261
1674
 
1262
1675
  // TODO we should add a LOGGING flag
1263
- // console.log(`Deep notification skipped for ${key.join('.')} on ${identifier.type}`, self);
1676
+ // console.log(`Deep notification skipped for ${key.join('.')} on ${identifier.type}`, proxy);
1264
1677
  // deep notify the key path
1265
1678
  } else {
1266
1679
  if (isEmbedded) return; // base paths never apply to embedded records
1267
1680
 
1268
1681
  // TODO determine what LOGGING flag to wrap this in if any
1269
- // console.log(`Notification for ${key} on ${identifier.type}`, self);
1682
+ // console.log(`Notification for ${key} on ${identifier.type}`, proxy);
1270
1683
  const signal = signals.get(key);
1271
1684
  if (signal) {
1272
1685
  notifyInternalSignal(signal);
1273
1686
  }
1274
1687
  const field = fields.get(key);
1275
1688
  if (field?.kind === 'array' || field?.kind === 'schema-array') {
1276
- const peeked = peekManagedArray(self, field);
1689
+ const peeked = peekManagedArray(proxy, field);
1277
1690
  if (peeked) {
1278
1691
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1279
1692
  if (!test) {
@@ -1285,7 +1698,7 @@ class ReactiveResource {
1285
1698
  }
1286
1699
  }
1287
1700
  if (field?.kind === 'object') {
1288
- const peeked = peekManagedObject(self, field);
1701
+ const peeked = peekManagedObject(proxy, field);
1289
1702
  if (peeked) {
1290
1703
  const objSignal = peeked[OBJECT_SIGNAL];
1291
1704
  notifyInternalSignal(objSignal);
@@ -1302,12 +1715,12 @@ class ReactiveResource {
1302
1715
  const field = fields.get(key);
1303
1716
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1304
1717
  if (!test) {
1305
- throw new Error(`Expected relationshp ${key} to be the name of a field`);
1718
+ throw new Error(`Expected relationship ${key} to be the name of a field`);
1306
1719
  }
1307
1720
  })(field) : {};
1308
1721
  if (field.kind === 'belongsTo') {
1309
1722
  // TODO determine what LOGGING flag to wrap this in if any
1310
- // console.log(`Notification for ${key} on ${identifier.type}`, self);
1723
+ // console.log(`Notification for ${key} on ${identifier.type}`, proxy);
1311
1724
  const signal = signals.get(key);
1312
1725
  if (signal) {
1313
1726
  notifyInternalSignal(signal);
@@ -1315,7 +1728,7 @@ class ReactiveResource {
1315
1728
  // FIXME
1316
1729
  } else if (field.kind === 'resource') ;else if (field.kind === 'hasMany') {
1317
1730
  if (field.options.linksMode) {
1318
- const peeked = peekManagedArray(self, field);
1731
+ const peeked = peekManagedArray(proxy, field);
1319
1732
  if (peeked) {
1320
1733
  notifyInternalSignal(peeked[ARRAY_SIGNAL]);
1321
1734
  }
@@ -1325,7 +1738,7 @@ class ReactiveResource {
1325
1738
  if (!test) {
1326
1739
  throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
1327
1740
  }
1328
- })(Mode[Legacy]) : {};
1741
+ })(context.legacy) : {};
1329
1742
  if (schema._kind('@legacy', 'hasMany').notify(store, proxy, identifier, field)) {
1330
1743
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1331
1744
  if (!test) {
@@ -1381,10 +1794,16 @@ function _CHECKOUT(record) {
1381
1794
  if (isEmbedded) {
1382
1795
  throw new Error(`Cannot checkout an embedded record (yet)`);
1383
1796
  }
1384
- const editableRecord = new ReactiveResource(record[RecordStore], record[Identifier], {
1385
- [Editable]: true,
1386
- [Legacy]: record[Legacy]
1387
- }, isEmbedded, embeddedType, embeddedPath);
1797
+ const legacy = record[Legacy];
1798
+ const editableRecord = new ReactiveResource({
1799
+ store: record[RecordStore],
1800
+ resourceKey: record[Identifier],
1801
+ modeName: legacy ? 'legacy' : 'polaris',
1802
+ legacy: legacy,
1803
+ editable: true,
1804
+ path: embeddedPath,
1805
+ field: embeddedType
1806
+ });
1388
1807
  setRecordIdentifier(editableRecord, recordIdentifierFor(record));
1389
1808
  return Promise.resolve(editableRecord);
1390
1809
  }
@@ -1397,6 +1816,14 @@ function _DESTROY(record) {
1397
1816
  }
1398
1817
  record[RecordStore].notifications.unsubscribe(record.___notifications);
1399
1818
  }
1819
+ function assertNeverField(identifier, field, path) {
1820
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1821
+ {
1822
+ throw new Error(`Cannot use unknown field kind ${field.kind} on <${identifier.type}>.${Array.isArray(path) ? path.join('.') : path}`);
1823
+ }
1824
+ })() : {};
1825
+ return false;
1826
+ }
1400
1827
  function instantiateRecord(store, identifier, createArgs) {
1401
1828
  const schema = store.schema;
1402
1829
  const resourceSchema = schema.resource(identifier);
@@ -1405,11 +1832,16 @@ function instantiateRecord(store, identifier, createArgs) {
1405
1832
  throw new Error(`Expected a resource schema`);
1406
1833
  }
1407
1834
  })(isResourceSchema(resourceSchema)) : {};
1408
- const isLegacy = resourceSchema?.legacy ?? false;
1409
- const isEditable = isLegacy || store.cache.isNew(identifier);
1410
- const record = new ReactiveResource(store, identifier, {
1411
- [Editable]: isEditable,
1412
- [Legacy]: isLegacy
1835
+ const legacy = resourceSchema?.legacy ?? false;
1836
+ const editable = legacy || store.cache.isNew(identifier);
1837
+ const record = new ReactiveResource({
1838
+ store,
1839
+ resourceKey: identifier,
1840
+ modeName: legacy ? 'legacy' : 'polaris',
1841
+ legacy: legacy,
1842
+ editable: editable,
1843
+ path: null,
1844
+ field: null
1413
1845
  });
1414
1846
  if (createArgs) {
1415
1847
  Object.assign(record, createArgs);
@@ -1836,16 +2268,21 @@ class SchemaService {
1836
2268
  relationships[field.name] = field;
1837
2269
  }
1838
2270
  }
2271
+ const cacheFields = null;
1839
2272
  const traits = new Set(isResourceSchema(schema) ? schema.traits : []);
1840
2273
  const finalized = traits.size === 0;
1841
2274
  const internalSchema = {
1842
2275
  original: schema,
1843
2276
  finalized,
1844
2277
  fields,
2278
+ cacheFields,
1845
2279
  relationships,
1846
2280
  attributes,
1847
2281
  traits
1848
2282
  };
2283
+ if (traits.size === 0) {
2284
+ internalSchema.cacheFields = getCacheFields(internalSchema);
2285
+ }
1849
2286
  this._schemas.set(schema.type, internalSchema);
1850
2287
  }
1851
2288
 
@@ -1976,6 +2413,20 @@ class SchemaService {
1976
2413
  }
1977
2414
  return schema.fields;
1978
2415
  }
2416
+ cacheFields({
2417
+ type
2418
+ }) {
2419
+ const schema = this._schemas.get(type);
2420
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
2421
+ if (!test) {
2422
+ throw new Error(`No schema defined for ${type}`);
2423
+ }
2424
+ })(schema) : {};
2425
+ if (!schema.finalized) {
2426
+ finalizeResource(this, schema);
2427
+ }
2428
+ return schema.cacheFields;
2429
+ }
1979
2430
  hasResource(resource) {
1980
2431
  return this._schemas.has(resource.type);
1981
2432
  }
@@ -2064,8 +2515,27 @@ function finalizeResource(schema, resource) {
2064
2515
  }
2065
2516
  mergeMap(fields, resource.fields);
2066
2517
  resource.fields = fields;
2518
+ resource.cacheFields = getCacheFields(resource);
2067
2519
  resource.finalized = true;
2068
2520
  }
2521
+ function getCacheFields(resource) {
2522
+ const {
2523
+ fields
2524
+ } = resource;
2525
+ const cacheFields = new Map();
2526
+ for (const [key, value] of fields) {
2527
+ if (isNonIdentityCacheableField(value)) {
2528
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
2529
+ if (!test) {
2530
+ throw new Error(`The sourceKey '${value.sourceKey}' for the field '${key}' on ${resource.original.type} is invalid because it matches the name of an existing field`);
2531
+ }
2532
+ })(!value.sourceKey || value.sourceKey === key || !fields.has(value.sourceKey)) : {};
2533
+ const cacheKey = getFieldCacheKeyStrict(value);
2534
+ cacheFields.set(cacheKey, value);
2535
+ }
2536
+ }
2537
+ return cacheFields;
2538
+ }
2069
2539
  function walkTrait(schema, trait, fields, seen, type, debugPath) {
2070
2540
  if (seen.has(trait)) {
2071
2541
  // if the trait is in the current path, we throw a cycle error in dev.