@warp-drive/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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SchemaRecord } from "./record";
2
- import { E as Editable, L as Legacy, D as Destroy } from "./symbols-CAUfvZjq";
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/core-types/-private';
3
3
  import { STRUCTURED } from '@warp-drive/core-types/request';
4
4
  import { RecordStore } from '@warp-drive/core-types/symbols';
5
- import { S as SOURCE, A as ARRAY_SIGNAL, O as OBJECT_SIGNAL, D as Destroy, I as Identifier, E as Editable, P as Parent, C as Checkout, L as Legacy } from "./symbols-CAUfvZjq";
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, key, owner) {
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.key = key;
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(self.address, self.key);
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
- const val = target[index];
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.type);
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(self.address, self.key, self[SOURCE]);
214
+ cache.setAttr(address, path, self[SOURCE]);
150
215
  _SIGNAL.shouldReset = true;
151
216
  return true;
152
217
  }
153
- const transform = schema.transformation(field.type);
154
- macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
155
- if (!test) {
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
- })(transform) : {};
159
- const rawValue = self[SOURCE].map(item => transform.serialize(item, field.options ?? null, self.owner));
160
- cache.setAttr(self.address, self.key, rawValue);
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.type);
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.type);
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.type);
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, prop) {
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, prop);
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, prop, record);
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.type);
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
- if (!field.type) {
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
- this[Identifier] = identifier;
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
- const fields = schema.fields(identifier);
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
- const signal = signals.get(key);
449
- if (signal) {
450
- addToTransaction(signal);
451
- }
452
- const field = fields.get(key);
453
- if (field?.kind === 'array') {
454
- const peeked = peekManagedArray(self, field);
455
- if (peeked) {
456
- const arrSignal = peeked[ARRAY_SIGNAL];
457
- arrSignal.shouldReset = true;
458
- addToTransaction(arrSignal);
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.type)({}, field.options ?? null, field.name ?? null);
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, prop);
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
- throw new Error(`Not Implemented`);
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, prop);
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, prop, value);
695
+ cache.setAttr(identifier, propArray, value);
568
696
  return true;
569
697
  }
570
- const transform = schema.transformation(field.type);
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, prop, rawValue);
700
+ cache.setAttr(identifier, propArray, rawValue);
578
701
  return true;
579
702
  }
580
703
  case 'attribute':
581
704
  {
582
- cache.setAttr(identifier, prop, value);
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, prop, value?.slice());
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.type);
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, prop, rawValue);
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, prop, newValue);
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.type);
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, prop, rawValue);
772
+ cache.setAttr(identifier, propArray, rawValue);
643
773
  const peeked = peekManagedObject(self, field);
644
774
  if (peeked) {
645
775
  const objSignal = peeked[OBJECT_SIGNAL];