@warp-drive-mirror/schema-record 0.0.0-alpha.56 → 0.0.0-alpha.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks.js +1 -1
- package/dist/record.js +238 -108
- package/dist/record.js.map +1 -1
- package/dist/schema.js +53 -17
- package/dist/schema.js.map +1 -1
- package/dist/{symbols-CAUfvZjq.js → symbols-DqoS4ybV.js} +3 -1
- package/dist/{symbols-CAUfvZjq.js.map → symbols-DqoS4ybV.js.map} +1 -1
- package/package.json +11 -11
- package/unstable-preview-types/managed-array.d.ts +4 -4
- package/unstable-preview-types/managed-array.d.ts.map +1 -1
- package/unstable-preview-types/managed-object.d.ts.map +1 -1
- package/unstable-preview-types/record.d.ts +6 -2
- package/unstable-preview-types/record.d.ts.map +1 -1
- package/unstable-preview-types/schema.d.ts +10 -4
- package/unstable-preview-types/schema.d.ts.map +1 -1
- package/unstable-preview-types/symbols.d.ts +2 -0
- package/unstable-preview-types/symbols.d.ts.map +1 -1
package/dist/hooks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SchemaRecord } from "./record";
|
|
2
|
-
import { E as Editable, L as Legacy, D as Destroy } from "./symbols-
|
|
2
|
+
import { E as Editable, L as Legacy, D as Destroy } from "./symbols-DqoS4ybV";
|
|
3
3
|
function instantiateRecord(store, identifier, createArgs) {
|
|
4
4
|
const schema = store.schema;
|
|
5
5
|
const isLegacy = schema.resource(identifier)?.legacy ?? false;
|
package/dist/record.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createSignal, subscribe, defineSignal, Signals, addToTransaction, entan
|
|
|
2
2
|
import { getOrSetGlobal } from '@warp-drive-mirror/core-types/-private';
|
|
3
3
|
import { STRUCTURED } from '@warp-drive-mirror/core-types/request';
|
|
4
4
|
import { RecordStore } from '@warp-drive-mirror/core-types/symbols';
|
|
5
|
-
import { S as SOURCE, A as ARRAY_SIGNAL,
|
|
5
|
+
import { S as SOURCE, A as ARRAY_SIGNAL, I as Identifier, E as Editable, L as Legacy, O as OBJECT_SIGNAL, D as Destroy, P as Parent, C as Checkout, a as EmbeddedPath, b as EmbeddedType } from "./symbols-DqoS4ybV";
|
|
6
6
|
import { macroCondition, getGlobalConfig } from '@embroider/macros';
|
|
7
7
|
const ARRAY_GETTER_METHODS = new Set([Symbol.iterator, 'concat', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'map', 'reduce', 'reduceRight', 'slice', 'some', 'values']);
|
|
8
8
|
// const ARRAY_SETTER_METHODS = new Set<KeyType>(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
|
|
@@ -48,7 +48,7 @@ function safeForEach(instance, arr, store, callback, target) {
|
|
|
48
48
|
}
|
|
49
49
|
class ManagedArray {
|
|
50
50
|
[SOURCE];
|
|
51
|
-
constructor(store, schema, cache, field, data, address,
|
|
51
|
+
constructor(store, schema, cache, field, data, address, path, owner, isSchemaArray) {
|
|
52
52
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
53
53
|
const self = this;
|
|
54
54
|
this[SOURCE] = data?.slice();
|
|
@@ -56,9 +56,19 @@ class ManagedArray {
|
|
|
56
56
|
const _SIGNAL = this[ARRAY_SIGNAL];
|
|
57
57
|
const boundFns = new Map();
|
|
58
58
|
this.address = address;
|
|
59
|
-
this.
|
|
59
|
+
this.path = path;
|
|
60
60
|
this.owner = owner;
|
|
61
61
|
let transaction = false;
|
|
62
|
+
const mode = field.options?.key ?? '@identity';
|
|
63
|
+
const RefStorage = mode === '@identity' ? WeakMap :
|
|
64
|
+
// CAUTION CAUTION CAUTION
|
|
65
|
+
// this is a pile of lies
|
|
66
|
+
// the Map is Map<string, WeakRef<SchemaRecord>>
|
|
67
|
+
// but TS does not understand how to juggle modes like this
|
|
68
|
+
// internal to a method like ours without us duplicating the code
|
|
69
|
+
// into two separate methods.
|
|
70
|
+
Map;
|
|
71
|
+
const ManagedRecordRefs = isSchemaArray ? new RefStorage() : null;
|
|
62
72
|
const proxy = new Proxy(this[SOURCE], {
|
|
63
73
|
get(target, prop, receiver) {
|
|
64
74
|
if (prop === ARRAY_SIGNAL) {
|
|
@@ -67,9 +77,6 @@ class ManagedArray {
|
|
|
67
77
|
if (prop === 'address') {
|
|
68
78
|
return self.address;
|
|
69
79
|
}
|
|
70
|
-
if (prop === 'key') {
|
|
71
|
-
return self.key;
|
|
72
|
-
}
|
|
73
80
|
if (prop === 'owner') {
|
|
74
81
|
return self.owner;
|
|
75
82
|
}
|
|
@@ -77,24 +84,87 @@ class ManagedArray {
|
|
|
77
84
|
if (_SIGNAL.shouldReset && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
|
|
78
85
|
_SIGNAL.t = false;
|
|
79
86
|
_SIGNAL.shouldReset = false;
|
|
80
|
-
const newData = cache.getAttr(
|
|
87
|
+
const newData = cache.getAttr(address, path);
|
|
81
88
|
if (newData && newData !== self[SOURCE]) {
|
|
82
89
|
self[SOURCE].length = 0;
|
|
83
90
|
self[SOURCE].push(...newData);
|
|
84
91
|
}
|
|
85
92
|
}
|
|
86
93
|
if (index !== null) {
|
|
87
|
-
|
|
94
|
+
let val;
|
|
95
|
+
if (mode === '@hash') {
|
|
96
|
+
val = target[index];
|
|
97
|
+
const hashField = schema.resource({
|
|
98
|
+
type: field.type
|
|
99
|
+
}).identity;
|
|
100
|
+
const hashFn = schema.hashFn(hashField);
|
|
101
|
+
val = hashFn(val, null, null);
|
|
102
|
+
} else {
|
|
103
|
+
// if mode is not @identity or @index, then access the key path.
|
|
104
|
+
// we should assert that `mode` is a string
|
|
105
|
+
// it should read directly from the cache value for that field (e.g. no derivation, no transformation)
|
|
106
|
+
// and, we likely should lookup the associated field and throw an error IF
|
|
107
|
+
// the given field does not exist OR
|
|
108
|
+
// the field is anything other than a GenericField or LegacyAttributeField.
|
|
109
|
+
if (mode !== '@identity' && mode !== '@index') {
|
|
110
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
111
|
+
if (!test) {
|
|
112
|
+
throw new Error('mode must be a string');
|
|
113
|
+
}
|
|
114
|
+
})(typeof mode === 'string') : {};
|
|
115
|
+
const modeField = schema.resource({
|
|
116
|
+
type: field.type
|
|
117
|
+
}).fields.find(f => f.name === mode);
|
|
118
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
119
|
+
if (!test) {
|
|
120
|
+
throw new Error('field must exist in schema');
|
|
121
|
+
}
|
|
122
|
+
})(modeField) : {};
|
|
123
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
124
|
+
if (!test) {
|
|
125
|
+
throw new Error('field must be a GenericField or LegacyAttributeField');
|
|
126
|
+
}
|
|
127
|
+
})(modeField.kind === 'field' || modeField.kind === 'attribute') : {};
|
|
128
|
+
}
|
|
129
|
+
val = mode === '@identity' ? target[index] : mode === '@index' ? '@index' : target[index][mode];
|
|
130
|
+
}
|
|
131
|
+
if (isSchemaArray) {
|
|
132
|
+
if (!transaction) {
|
|
133
|
+
subscribe(_SIGNAL);
|
|
134
|
+
}
|
|
135
|
+
if (val) {
|
|
136
|
+
const recordRef = ManagedRecordRefs.get(val);
|
|
137
|
+
let record = recordRef?.deref();
|
|
138
|
+
if (!record) {
|
|
139
|
+
const recordPath = path.slice();
|
|
140
|
+
// this is a dirty lie since path is string[] but really we
|
|
141
|
+
// should change the types for paths to `Array<string | number>`
|
|
142
|
+
// TODO we should allow the schema for the field to define a "key"
|
|
143
|
+
// for stability. Default should be `@identity` which means that
|
|
144
|
+
// same object reference from cache should result in same SchemaRecord
|
|
145
|
+
// embedded object.
|
|
146
|
+
recordPath.push(index);
|
|
147
|
+
record = new SchemaRecord(store, self.owner[Identifier], {
|
|
148
|
+
[Editable]: self.owner[Editable],
|
|
149
|
+
[Legacy]: self.owner[Legacy]
|
|
150
|
+
}, true, field.type, recordPath);
|
|
151
|
+
// if mode is not @identity or @index, then access the key path now
|
|
152
|
+
// to determine the key value.
|
|
153
|
+
// chris says we can implement this as a special kind `@hash` which
|
|
154
|
+
// would be a function that only has access to the cache value and not
|
|
155
|
+
// the record itself, so derivation is possible but intentionally limited
|
|
156
|
+
// and non-reactive?
|
|
157
|
+
ManagedRecordRefs.set(val, new WeakRef(record));
|
|
158
|
+
}
|
|
159
|
+
return record;
|
|
160
|
+
}
|
|
161
|
+
return val;
|
|
162
|
+
}
|
|
88
163
|
if (!transaction) {
|
|
89
164
|
subscribe(_SIGNAL);
|
|
90
165
|
}
|
|
91
166
|
if (field.type) {
|
|
92
|
-
const transform = schema.transformation(field
|
|
93
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
94
|
-
if (!test) {
|
|
95
|
-
throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`);
|
|
96
|
-
}
|
|
97
|
-
})(transform) : {};
|
|
167
|
+
const transform = schema.transformation(field);
|
|
98
168
|
return transform.hydrate(val, field.options ?? null, self.owner);
|
|
99
169
|
}
|
|
100
170
|
return val;
|
|
@@ -133,11 +203,6 @@ class ManagedArray {
|
|
|
133
203
|
self.address = value;
|
|
134
204
|
return true;
|
|
135
205
|
}
|
|
136
|
-
if (prop === 'key') {
|
|
137
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
138
|
-
self.key = value;
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
206
|
if (prop === 'owner') {
|
|
142
207
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
143
208
|
self.owner = value;
|
|
@@ -146,18 +211,19 @@ class ManagedArray {
|
|
|
146
211
|
const reflect = Reflect.set(target, prop, value, receiver);
|
|
147
212
|
if (reflect) {
|
|
148
213
|
if (!field.type) {
|
|
149
|
-
cache.setAttr(
|
|
214
|
+
cache.setAttr(address, path, self[SOURCE]);
|
|
150
215
|
_SIGNAL.shouldReset = true;
|
|
151
216
|
return true;
|
|
152
217
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
218
|
+
let rawValue = self[SOURCE];
|
|
219
|
+
if (!isSchemaArray) {
|
|
220
|
+
const transform = schema.transformation(field);
|
|
221
|
+
if (!transform) {
|
|
156
222
|
throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`);
|
|
157
223
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
cache.setAttr(
|
|
224
|
+
rawValue = self[SOURCE].map(item => transform.serialize(item, field.options ?? null, self.owner));
|
|
225
|
+
}
|
|
226
|
+
cache.setAttr(address, path, rawValue);
|
|
161
227
|
_SIGNAL.shouldReset = true;
|
|
162
228
|
}
|
|
163
229
|
return reflect;
|
|
@@ -200,12 +266,7 @@ class ManagedObject {
|
|
|
200
266
|
let newData = cache.getAttr(self.address, self.key);
|
|
201
267
|
if (newData && newData !== self[SOURCE]) {
|
|
202
268
|
if (field.type) {
|
|
203
|
-
const transform = schema.transformation(field
|
|
204
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
205
|
-
if (!test) {
|
|
206
|
-
throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`);
|
|
207
|
-
}
|
|
208
|
-
})(transform) : {};
|
|
269
|
+
const transform = schema.transformation(field);
|
|
209
270
|
newData = transform.hydrate(newData, field.options ?? null, self.owner);
|
|
210
271
|
}
|
|
211
272
|
self[SOURCE] = {
|
|
@@ -244,12 +305,7 @@ class ManagedObject {
|
|
|
244
305
|
_SIGNAL.shouldReset = true;
|
|
245
306
|
return true;
|
|
246
307
|
}
|
|
247
|
-
const transform = schema.transformation(field
|
|
248
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
249
|
-
if (!test) {
|
|
250
|
-
throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`);
|
|
251
|
-
}
|
|
252
|
-
})(transform) : {};
|
|
308
|
+
const transform = schema.transformation(field);
|
|
253
309
|
const val = transform.serialize(self[SOURCE], field.options ?? null, self.owner);
|
|
254
310
|
cache.setAttr(self.address, self.key, val);
|
|
255
311
|
_SIGNAL.shouldReset = true;
|
|
@@ -260,8 +316,8 @@ class ManagedObject {
|
|
|
260
316
|
return proxy;
|
|
261
317
|
}
|
|
262
318
|
}
|
|
263
|
-
const IgnoredGlobalFields = new Set(['then', STRUCTURED]);
|
|
264
|
-
const symbolList = [Destroy, RecordStore, Identifier, Editable, Parent, Checkout, Legacy, Signals];
|
|
319
|
+
const IgnoredGlobalFields = new Set(['length', 'nodeType', 'then', 'setInterval', STRUCTURED]);
|
|
320
|
+
const symbolList = [Destroy, RecordStore, Identifier, Editable, Parent, Checkout, Legacy, Signals, EmbeddedPath, EmbeddedType];
|
|
265
321
|
const RecordSymbols = new Set(symbolList);
|
|
266
322
|
const ManagedArrayMap = getOrSetGlobal('ManagedArrayMap', new Map());
|
|
267
323
|
const ManagedObjectMap = getOrSetGlobal('ManagedObjectMap', new Map());
|
|
@@ -290,15 +346,10 @@ function computeField(schema, cache, record, identifier, field, prop) {
|
|
|
290
346
|
if (!field.type) {
|
|
291
347
|
return rawValue;
|
|
292
348
|
}
|
|
293
|
-
const transform = schema.transformation(field
|
|
294
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
295
|
-
if (!test) {
|
|
296
|
-
throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
|
|
297
|
-
}
|
|
298
|
-
})(transform) : {};
|
|
349
|
+
const transform = schema.transformation(field);
|
|
299
350
|
return transform.hydrate(rawValue, field.options ?? null, record);
|
|
300
351
|
}
|
|
301
|
-
function computeArray(store, schema, cache, record, identifier, field,
|
|
352
|
+
function computeArray(store, schema, cache, record, identifier, field, path, isSchemaArray = false) {
|
|
302
353
|
// the thing we hand out needs to know its owner and path in a private manner
|
|
303
354
|
// its "address" is the parent identifier (identifier) + field name (field.name)
|
|
304
355
|
// in the nested object case field name here is the full dot path from root resource to this value
|
|
@@ -313,11 +364,11 @@ function computeArray(store, schema, cache, record, identifier, field, prop) {
|
|
|
313
364
|
if (managedArray) {
|
|
314
365
|
return managedArray;
|
|
315
366
|
} else {
|
|
316
|
-
const rawValue = cache.getAttr(identifier,
|
|
367
|
+
const rawValue = cache.getAttr(identifier, path);
|
|
317
368
|
if (!rawValue) {
|
|
318
369
|
return null;
|
|
319
370
|
}
|
|
320
|
-
managedArray = new ManagedArray(store, schema, cache, field, rawValue, identifier,
|
|
371
|
+
managedArray = new ManagedArray(store, schema, cache, field, rawValue, identifier, path, record, isSchemaArray);
|
|
321
372
|
if (!managedArrayMapForRecord) {
|
|
322
373
|
ManagedArrayMap.set(record, new Map([[field, managedArray]]));
|
|
323
374
|
} else {
|
|
@@ -341,12 +392,7 @@ function computeObject(store, schema, cache, record, identifier, field, prop) {
|
|
|
341
392
|
}
|
|
342
393
|
if (field.kind === 'object') {
|
|
343
394
|
if (field.type) {
|
|
344
|
-
const transform = schema.transformation(field
|
|
345
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
346
|
-
if (!test) {
|
|
347
|
-
throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
|
|
348
|
-
}
|
|
349
|
-
})(transform) : {};
|
|
395
|
+
const transform = schema.transformation(field);
|
|
350
396
|
rawValue = transform.hydrate(rawValue, field.options ?? null, record);
|
|
351
397
|
}
|
|
352
398
|
}
|
|
@@ -363,16 +409,7 @@ function computeAttribute(cache, identifier, prop) {
|
|
|
363
409
|
return cache.getAttr(identifier, prop);
|
|
364
410
|
}
|
|
365
411
|
function computeDerivation(schema, record, identifier, field, prop) {
|
|
366
|
-
|
|
367
|
-
throw new Error(`The schema for ${identifier.type}.${String(prop)} is missing the type of the derivation`);
|
|
368
|
-
}
|
|
369
|
-
const derivation = schema.derivation(field.type);
|
|
370
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
371
|
-
if (!test) {
|
|
372
|
-
throw new Error(`No '${field.type}' derivation defined for use by ${identifier.type}.${String(prop)}`);
|
|
373
|
-
}
|
|
374
|
-
})(derivation) : {};
|
|
375
|
-
return derivation(record, field.options ?? null, prop);
|
|
412
|
+
return schema.derivation(field)(record, field.options ?? null, prop);
|
|
376
413
|
}
|
|
377
414
|
|
|
378
415
|
// TODO probably this should just be a Document
|
|
@@ -412,6 +449,9 @@ class ResourceRelationship {
|
|
|
412
449
|
defineSignal(ResourceRelationship.prototype, 'data');
|
|
413
450
|
defineSignal(ResourceRelationship.prototype, 'links');
|
|
414
451
|
defineSignal(ResourceRelationship.prototype, 'meta');
|
|
452
|
+
function isPathMatch(a, b) {
|
|
453
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
454
|
+
}
|
|
415
455
|
function getHref(link) {
|
|
416
456
|
if (!link) {
|
|
417
457
|
return null;
|
|
@@ -428,34 +468,70 @@ function computeResource(store, cache, parent, identifier, field, prop) {
|
|
|
428
468
|
return new ResourceRelationship(store, cache, parent, identifier, field, prop);
|
|
429
469
|
}
|
|
430
470
|
class SchemaRecord {
|
|
431
|
-
constructor(store, identifier, Mode) {
|
|
471
|
+
constructor(store, identifier, Mode, isEmbedded = false, embeddedType = null, embeddedPath = null) {
|
|
432
472
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
433
473
|
const self = this;
|
|
434
474
|
this[RecordStore] = store;
|
|
435
|
-
|
|
475
|
+
if (isEmbedded) {
|
|
476
|
+
this[Parent] = identifier;
|
|
477
|
+
} else {
|
|
478
|
+
this[Identifier] = identifier;
|
|
479
|
+
}
|
|
436
480
|
const IS_EDITABLE = this[Editable] = Mode[Editable] ?? false;
|
|
437
481
|
this[Legacy] = Mode[Legacy] ?? false;
|
|
438
482
|
const schema = store.schema;
|
|
439
483
|
const cache = store.cache;
|
|
440
484
|
const identityField = schema.resource(identifier).identity;
|
|
441
|
-
|
|
485
|
+
this[EmbeddedType] = embeddedType;
|
|
486
|
+
this[EmbeddedPath] = embeddedPath;
|
|
487
|
+
let fields;
|
|
488
|
+
if (isEmbedded) {
|
|
489
|
+
fields = schema.fields({
|
|
490
|
+
type: embeddedType
|
|
491
|
+
});
|
|
492
|
+
} else {
|
|
493
|
+
fields = schema.fields(identifier);
|
|
494
|
+
}
|
|
442
495
|
const signals = new Map();
|
|
443
496
|
this[Signals] = signals;
|
|
497
|
+
// what signal do we need for embedded record?
|
|
444
498
|
this.___notifications = store.notifications.subscribe(identifier, (_, type, key) => {
|
|
445
499
|
switch (type) {
|
|
446
500
|
case 'attributes':
|
|
447
501
|
if (key) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
502
|
+
if (Array.isArray(key)) {
|
|
503
|
+
if (!isEmbedded) return; // deep paths will be handled by embedded records
|
|
504
|
+
// TODO we should have the notification manager
|
|
505
|
+
// ensure it is safe for each callback to mutate this array
|
|
506
|
+
if (isPathMatch(embeddedPath, key)) {
|
|
507
|
+
// handle the notification
|
|
508
|
+
// TODO we should likely handle this notification here
|
|
509
|
+
// also we should add a LOGGING flag
|
|
510
|
+
// eslint-disable-next-line no-console
|
|
511
|
+
console.warn(`Notification unhandled for ${key.join(',')} on ${identifier.type}`, self);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// TODO we should add a LOGGING flag
|
|
516
|
+
// console.log(`Deep notification skipped for ${key.join('.')} on ${identifier.type}`, self);
|
|
517
|
+
// deep notify the key path
|
|
518
|
+
} else {
|
|
519
|
+
if (isEmbedded) return; // base paths never apply to embedded records
|
|
520
|
+
|
|
521
|
+
// TODO determine what LOGGING flag to wrap this in if any
|
|
522
|
+
// console.log(`Notification for ${key} on ${identifier.type}`, self);
|
|
523
|
+
const signal = signals.get(key);
|
|
524
|
+
if (signal) {
|
|
525
|
+
addToTransaction(signal);
|
|
526
|
+
}
|
|
527
|
+
const field = fields.get(key);
|
|
528
|
+
if (field?.kind === 'array' || field?.kind === 'schema-array') {
|
|
529
|
+
const peeked = peekManagedArray(self, field);
|
|
530
|
+
if (peeked) {
|
|
531
|
+
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
532
|
+
arrSignal.shouldReset = true;
|
|
533
|
+
addToTransaction(arrSignal);
|
|
534
|
+
}
|
|
459
535
|
}
|
|
460
536
|
}
|
|
461
537
|
}
|
|
@@ -463,10 +539,54 @@ class SchemaRecord {
|
|
|
463
539
|
}
|
|
464
540
|
});
|
|
465
541
|
return new Proxy(this, {
|
|
542
|
+
ownKeys() {
|
|
543
|
+
return Array.from(fields.keys());
|
|
544
|
+
},
|
|
545
|
+
has(target, prop) {
|
|
546
|
+
return fields.has(prop);
|
|
547
|
+
},
|
|
548
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
549
|
+
if (!fields.has(prop)) {
|
|
550
|
+
throw new Error(`No field named ${String(prop)} on ${identifier.type}`);
|
|
551
|
+
}
|
|
552
|
+
const schemaForField = fields.get(prop);
|
|
553
|
+
switch (schemaForField.kind) {
|
|
554
|
+
case 'derived':
|
|
555
|
+
return {
|
|
556
|
+
writable: false,
|
|
557
|
+
enumerable: true,
|
|
558
|
+
configurable: true
|
|
559
|
+
};
|
|
560
|
+
case '@local':
|
|
561
|
+
case 'field':
|
|
562
|
+
case 'attribute':
|
|
563
|
+
case 'resource':
|
|
564
|
+
case 'schema-array':
|
|
565
|
+
case 'array':
|
|
566
|
+
case 'schema-object':
|
|
567
|
+
case 'object':
|
|
568
|
+
return {
|
|
569
|
+
writable: IS_EDITABLE,
|
|
570
|
+
enumerable: true,
|
|
571
|
+
configurable: true
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
},
|
|
466
575
|
get(target, prop, receiver) {
|
|
467
576
|
if (RecordSymbols.has(prop)) {
|
|
468
577
|
return target[prop];
|
|
469
578
|
}
|
|
579
|
+
if (prop === Symbol.toStringTag) {
|
|
580
|
+
return `SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})>`;
|
|
581
|
+
}
|
|
582
|
+
if (prop === 'toString') {
|
|
583
|
+
return function () {
|
|
584
|
+
return `SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})>`;
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
if (prop === Symbol.toPrimitive) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
470
590
|
if (prop === '___notifications') {
|
|
471
591
|
return target.___notifications;
|
|
472
592
|
}
|
|
@@ -475,11 +595,16 @@ class SchemaRecord {
|
|
|
475
595
|
// for its own usage.
|
|
476
596
|
// _, @, $, *
|
|
477
597
|
|
|
598
|
+
const propArray = isEmbedded ? embeddedPath.slice() : [];
|
|
599
|
+
propArray.push(prop);
|
|
478
600
|
const field = prop === identityField?.name ? identityField : fields.get(prop);
|
|
479
601
|
if (!field) {
|
|
480
602
|
if (IgnoredGlobalFields.has(prop)) {
|
|
481
603
|
return undefined;
|
|
482
604
|
}
|
|
605
|
+
if (prop === 'constructor') {
|
|
606
|
+
return SchemaRecord;
|
|
607
|
+
}
|
|
483
608
|
throw new Error(`No field named ${String(prop)} on ${identifier.type}`);
|
|
484
609
|
}
|
|
485
610
|
switch (field.kind) {
|
|
@@ -488,7 +613,7 @@ class SchemaRecord {
|
|
|
488
613
|
return identifier.id;
|
|
489
614
|
case '@hash':
|
|
490
615
|
// TODO pass actual cache value not {}
|
|
491
|
-
return schema.hashFn(field
|
|
616
|
+
return schema.hashFn(field)({}, field.options ?? null, field.name ?? null);
|
|
492
617
|
case '@local':
|
|
493
618
|
{
|
|
494
619
|
const lastValue = computeLocal(receiver, field, prop);
|
|
@@ -502,7 +627,7 @@ class SchemaRecord {
|
|
|
502
627
|
}
|
|
503
628
|
})(!target[Legacy]) : {};
|
|
504
629
|
entangleSignal(signals, receiver, field.name);
|
|
505
|
-
return computeField(schema, cache, target, identifier, field,
|
|
630
|
+
return computeField(schema, cache, target, identifier, field, propArray);
|
|
506
631
|
case 'attribute':
|
|
507
632
|
entangleSignal(signals, receiver, field.name);
|
|
508
633
|
return computeAttribute(cache, identifier, prop);
|
|
@@ -517,7 +642,8 @@ class SchemaRecord {
|
|
|
517
642
|
case 'derived':
|
|
518
643
|
return computeDerivation(schema, receiver, identifier, field, prop);
|
|
519
644
|
case 'schema-array':
|
|
520
|
-
|
|
645
|
+
entangleSignal(signals, receiver, field.name);
|
|
646
|
+
return computeArray(store, schema, cache, target, identifier, field, propArray, true);
|
|
521
647
|
case 'array':
|
|
522
648
|
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
523
649
|
if (!test) {
|
|
@@ -525,7 +651,7 @@ class SchemaRecord {
|
|
|
525
651
|
}
|
|
526
652
|
})(!target[Legacy]) : {};
|
|
527
653
|
entangleSignal(signals, receiver, field.name);
|
|
528
|
-
return computeArray(store, schema, cache, target, identifier, field,
|
|
654
|
+
return computeArray(store, schema, cache, target, identifier, field, propArray);
|
|
529
655
|
case 'schema-object':
|
|
530
656
|
// validate any access off of schema, no transform to run
|
|
531
657
|
// use raw cache value as the object to manage
|
|
@@ -547,6 +673,8 @@ class SchemaRecord {
|
|
|
547
673
|
if (!IS_EDITABLE) {
|
|
548
674
|
throw new Error(`Cannot set ${String(prop)} on ${identifier.type} because the record is not editable`);
|
|
549
675
|
}
|
|
676
|
+
const propArray = isEmbedded ? embeddedPath.slice() : [];
|
|
677
|
+
propArray.push(prop);
|
|
550
678
|
const field = fields.get(prop);
|
|
551
679
|
if (!field) {
|
|
552
680
|
throw new Error(`There is no field named ${String(prop)} on ${identifier.type}`);
|
|
@@ -564,28 +692,23 @@ class SchemaRecord {
|
|
|
564
692
|
case 'field':
|
|
565
693
|
{
|
|
566
694
|
if (!field.type) {
|
|
567
|
-
cache.setAttr(identifier,
|
|
695
|
+
cache.setAttr(identifier, propArray, value);
|
|
568
696
|
return true;
|
|
569
697
|
}
|
|
570
|
-
const transform = schema.transformation(field
|
|
571
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
572
|
-
if (!test) {
|
|
573
|
-
throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
|
|
574
|
-
}
|
|
575
|
-
})(transform) : {};
|
|
698
|
+
const transform = schema.transformation(field);
|
|
576
699
|
const rawValue = transform.serialize(value, field.options ?? null, target);
|
|
577
|
-
cache.setAttr(identifier,
|
|
700
|
+
cache.setAttr(identifier, propArray, rawValue);
|
|
578
701
|
return true;
|
|
579
702
|
}
|
|
580
703
|
case 'attribute':
|
|
581
704
|
{
|
|
582
|
-
cache.setAttr(identifier,
|
|
705
|
+
cache.setAttr(identifier, propArray, value);
|
|
583
706
|
return true;
|
|
584
707
|
}
|
|
585
708
|
case 'array':
|
|
586
709
|
{
|
|
587
710
|
if (!field.type) {
|
|
588
|
-
cache.setAttr(identifier,
|
|
711
|
+
cache.setAttr(identifier, propArray, value?.slice());
|
|
589
712
|
const peeked = peekManagedArray(self, field);
|
|
590
713
|
if (peeked) {
|
|
591
714
|
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
@@ -596,19 +719,31 @@ class SchemaRecord {
|
|
|
596
719
|
}
|
|
597
720
|
return true;
|
|
598
721
|
}
|
|
599
|
-
const transform = schema.transformation(field
|
|
600
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
601
|
-
if (!test) {
|
|
602
|
-
throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
|
|
603
|
-
}
|
|
604
|
-
})(transform) : {};
|
|
722
|
+
const transform = schema.transformation(field);
|
|
605
723
|
const rawValue = value.map(item => transform.serialize(item, field.options ?? null, target));
|
|
606
|
-
cache.setAttr(identifier,
|
|
724
|
+
cache.setAttr(identifier, propArray, rawValue);
|
|
725
|
+
const peeked = peekManagedArray(self, field);
|
|
726
|
+
if (peeked) {
|
|
727
|
+
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
728
|
+
arrSignal.shouldReset = true;
|
|
729
|
+
}
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
case 'schema-array':
|
|
733
|
+
{
|
|
734
|
+
const arrayValue = value?.slice();
|
|
735
|
+
if (!Array.isArray(arrayValue)) {
|
|
736
|
+
ManagedArrayMap.delete(target);
|
|
737
|
+
}
|
|
738
|
+
cache.setAttr(identifier, propArray, arrayValue);
|
|
607
739
|
const peeked = peekManagedArray(self, field);
|
|
608
740
|
if (peeked) {
|
|
609
741
|
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
610
742
|
arrSignal.shouldReset = true;
|
|
611
743
|
}
|
|
744
|
+
if (!Array.isArray(value)) {
|
|
745
|
+
ManagedArrayMap.delete(target);
|
|
746
|
+
}
|
|
612
747
|
return true;
|
|
613
748
|
}
|
|
614
749
|
case 'object':
|
|
@@ -622,7 +757,7 @@ class SchemaRecord {
|
|
|
622
757
|
} else {
|
|
623
758
|
ManagedObjectMap.delete(target);
|
|
624
759
|
}
|
|
625
|
-
cache.setAttr(identifier,
|
|
760
|
+
cache.setAttr(identifier, propArray, newValue);
|
|
626
761
|
const peeked = peekManagedObject(self, field);
|
|
627
762
|
if (peeked) {
|
|
628
763
|
const objSignal = peeked[OBJECT_SIGNAL];
|
|
@@ -630,16 +765,11 @@ class SchemaRecord {
|
|
|
630
765
|
}
|
|
631
766
|
return true;
|
|
632
767
|
}
|
|
633
|
-
const transform = schema.transformation(field
|
|
634
|
-
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
635
|
-
if (!test) {
|
|
636
|
-
throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
|
|
637
|
-
}
|
|
638
|
-
})(transform) : {};
|
|
768
|
+
const transform = schema.transformation(field);
|
|
639
769
|
const rawValue = transform.serialize({
|
|
640
770
|
...value
|
|
641
771
|
}, field.options ?? null, target);
|
|
642
|
-
cache.setAttr(identifier,
|
|
772
|
+
cache.setAttr(identifier, propArray, rawValue);
|
|
643
773
|
const peeked = peekManagedObject(self, field);
|
|
644
774
|
if (peeked) {
|
|
645
775
|
const objSignal = peeked[OBJECT_SIGNAL];
|