@warp-drive/core 5.7.0-alpha.2 → 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.
- package/declarations/reactive/-private/default-mode.d.ts +71 -0
- package/declarations/reactive/-private/fields/get-field-key.d.ts +8 -0
- package/declarations/reactive/-private/fields/managed-array.d.ts +3 -5
- package/declarations/reactive/-private/fields/managed-object.d.ts +5 -3
- package/declarations/reactive/-private/kind/alias-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/array-field.d.ts +9 -0
- package/declarations/reactive/-private/kind/attribute-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/belongs-to-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/collection-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/derived-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/generic-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/has-many-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/hash-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/identity-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/local-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/object-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/resource-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/schema-array-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/schema-object-field.d.ts +4 -0
- package/declarations/reactive/-private/record.d.ts +2 -4
- package/declarations/reactive/-private/schema.d.ts +5 -1
- package/declarations/store/-types/q/schema-service.d.ts +19 -31
- package/declarations/types/schema/fields.d.ts +309 -3
- package/dist/graph/-private.js +1 -1
- package/dist/{handler-D2jjnIA-.js → handler-Gm-q2q05.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/reactive.js +1000 -530
- package/dist/{request-state-CejVJgdj.js → request-state-Bj0zUw2r.js} +5 -0
- package/dist/store/-private.js +2 -2
- package/dist/types/-private.js +1 -1
- package/dist/types/schema/fields.js +13 -0
- package/package.json +3 -3
- 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,
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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 =
|
|
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(
|
|
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 (
|
|
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 ${
|
|
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(
|
|
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(
|
|
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] =
|
|
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,
|
|
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,
|
|
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
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
[
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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(
|
|
620
|
-
const
|
|
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:${
|
|
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] =
|
|
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
|
|
663
|
-
|
|
664
|
-
|
|
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
|
|
1087
|
+
return true;
|
|
667
1088
|
}
|
|
668
|
-
function
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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 (
|
|
681
|
-
return
|
|
1100
|
+
if (schemaObject) {
|
|
1101
|
+
return schemaObject;
|
|
682
1102
|
} else {
|
|
683
|
-
const
|
|
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
|
-
|
|
688
|
-
store,
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
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(
|
|
741
|
-
|
|
742
|
-
|
|
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] =
|
|
750
|
-
this[Legacy] =
|
|
1311
|
+
const IS_EDITABLE = this[Editable] = context.editable ?? false;
|
|
1312
|
+
this[Legacy] = context.legacy ?? false;
|
|
751
1313
|
const schema = store.schema;
|
|
752
|
-
const
|
|
753
|
-
const identityField =
|
|
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 = !
|
|
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
|
-
|
|
941
|
-
// propArray.
|
|
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
|
-
|
|
974
|
-
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1035
|
-
// propArray.
|
|
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
|
-
|
|
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
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
-
|
|
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}`,
|
|
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}`,
|
|
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}`,
|
|
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(
|
|
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(
|
|
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
|
|
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}`,
|
|
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(
|
|
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
|
-
})(
|
|
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
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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
|
|
1409
|
-
const
|
|
1410
|
-
const record = new ReactiveResource(
|
|
1411
|
-
|
|
1412
|
-
|
|
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.
|