@warp-drive-mirror/schema-record 4.13.0-alpha.5 → 4.13.0-alpha.6

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/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { isResourceSchema } from '@warp-drive-mirror/core-types/schema/fields';
1
2
  import { macroCondition, getGlobalConfig, dependencySatisfies, importSync } from '@embroider/macros';
2
3
  import { SOURCE as SOURCE$1, fastPush, RelatedCollection, setRecordIdentifier, recordIdentifierFor } from '@ember-data-mirror/store/-private';
3
4
  import { createSignal, subscribe, defineSignal, peekSignal, getSignal, Signals, addToTransaction, entangleSignal } from '@ember-data-mirror/tracking/-private';
@@ -307,7 +308,7 @@ class ManagedObject {
307
308
  return self[prop];
308
309
  }
309
310
  if (prop === Symbol.toPrimitive) {
310
- return null;
311
+ return () => null;
311
312
  }
312
313
  if (prop === Symbol.toStringTag) {
313
314
  return `ManagedObject<${identifier.type}:${identifier.id} (${identifier.lid})>`;
@@ -322,7 +323,12 @@ class ManagedObject {
322
323
  }
323
324
  if (prop === 'toHTML') {
324
325
  return function () {
325
- return '<div>ManagedObject</div>';
326
+ return '<span>ManagedObject</span>';
327
+ };
328
+ }
329
+ if (prop === 'toJSON') {
330
+ return function () {
331
+ return structuredClone(self[SOURCE]);
326
332
  };
327
333
  }
328
334
  if (_SIGNAL.shouldReset) {
@@ -661,6 +667,9 @@ const RecordSymbols = new Set(symbolList);
661
667
  function isPathMatch(a, b) {
662
668
  return a.length === b.length && a.every((v, i) => v === b[i]);
663
669
  }
670
+ function isNonEnumerableProp(prop) {
671
+ return prop === 'constructor' || prop === 'prototype' || prop === '__proto__' || prop === 'toString' || prop === 'toJSON' || prop === 'toHTML' || typeof prop === 'symbol';
672
+ }
664
673
  const Editables = new WeakMap();
665
674
  class SchemaRecord {
666
675
  constructor(store, identifier, Mode, isEmbedded = false, embeddedType = null, embeddedPath = null) {
@@ -676,22 +685,25 @@ class SchemaRecord {
676
685
  this[Legacy] = Mode[Legacy] ?? false;
677
686
  const schema = store.schema;
678
687
  const cache = store.cache;
679
- const identityField = schema.resource(identifier).identity;
688
+ const identityField = schema.resource(isEmbedded ? {
689
+ type: embeddedType
690
+ } : identifier).identity;
691
+ const BoundFns = new Map();
680
692
  this[EmbeddedType] = embeddedType;
681
693
  this[EmbeddedPath] = embeddedPath;
682
- let fields;
683
- if (isEmbedded) {
684
- fields = schema.fields({
685
- type: embeddedType
686
- });
687
- } else {
688
- fields = schema.fields(identifier);
689
- }
694
+ const fields = isEmbedded ? schema.fields({
695
+ type: embeddedType
696
+ }) : schema.fields(identifier);
690
697
  const signals = new Map();
691
698
  this[Signals] = signals;
692
699
  const proxy = new Proxy(this, {
693
700
  ownKeys() {
694
- return Array.from(fields.keys());
701
+ const identityKey = identityField?.name;
702
+ const keys = Array.from(fields.keys());
703
+ if (identityKey) {
704
+ keys.unshift(identityKey);
705
+ }
706
+ return keys;
695
707
  },
696
708
  has(target, prop) {
697
709
  if (prop === Destroy || prop === Checkout) {
@@ -700,10 +712,19 @@ class SchemaRecord {
700
712
  return fields.has(prop);
701
713
  },
702
714
  getOwnPropertyDescriptor(target, prop) {
703
- if (!fields.has(prop)) {
704
- throw new Error(`No field named ${String(prop)} on ${identifier.type}`);
715
+ const schemaForField = prop === identityField?.name ? identityField : fields.get(prop);
716
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
717
+ if (!test) {
718
+ throw new Error(`No field named ${String(prop)} on ${identifier.type}`);
719
+ }
720
+ })(schemaForField) : {};
721
+ if (isNonEnumerableProp(prop)) {
722
+ return {
723
+ writable: false,
724
+ enumerable: false,
725
+ configurable: true
726
+ };
705
727
  }
706
- const schemaForField = fields.get(prop);
707
728
  switch (schemaForField.kind) {
708
729
  case 'derived':
709
730
  return {
@@ -711,6 +732,12 @@ class SchemaRecord {
711
732
  enumerable: true,
712
733
  configurable: true
713
734
  };
735
+ case '@id':
736
+ return {
737
+ writable: identifier.id === null,
738
+ enumerable: true,
739
+ configurable: true
740
+ };
714
741
  case '@local':
715
742
  case 'field':
716
743
  case 'attribute':
@@ -728,28 +755,18 @@ class SchemaRecord {
728
755
  enumerable: true,
729
756
  configurable: true
730
757
  };
758
+ default:
759
+ return {
760
+ writable: false,
761
+ enumerable: false,
762
+ configurable: false
763
+ };
731
764
  }
732
765
  },
733
766
  get(target, prop, receiver) {
734
767
  if (RecordSymbols.has(prop)) {
735
768
  return target[prop];
736
769
  }
737
- if (prop === Symbol.toStringTag) {
738
- return `SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})>`;
739
- }
740
- if (prop === 'toString') {
741
- return function () {
742
- return `SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})>`;
743
- };
744
- }
745
- if (prop === 'toHTML') {
746
- return function () {
747
- return `<div>SchemaRecord<${identifier.type}:${identifier.id} (${identifier.lid})></div>`;
748
- };
749
- }
750
- if (prop === Symbol.toPrimitive) {
751
- return null;
752
- }
753
770
 
754
771
  // TODO make this a symbol
755
772
  if (prop === '___notifications') {
@@ -765,18 +782,75 @@ class SchemaRecord {
765
782
  if (IgnoredGlobalFields.has(prop)) {
766
783
  return undefined;
767
784
  }
785
+
786
+ /////////////////////////////////////////////////////////////
787
+ //// Note these bound function behaviors are essentially ////
788
+ //// built-in but overrideable derivations. ////
789
+ //// ////
790
+ //// The bar for this has to be "basic expectations of ////
791
+ /// an object" – very, very high ////
792
+ /////////////////////////////////////////////////////////////
793
+
794
+ if (prop === Symbol.toStringTag || prop === 'toString') {
795
+ let fn = BoundFns.get('toString');
796
+ if (!fn) {
797
+ fn = function () {
798
+ entangleSignal(signals, receiver, '@identity');
799
+ return `Record<${identifier.type}:${identifier.id} (${identifier.lid})>`;
800
+ };
801
+ BoundFns.set(prop, fn);
802
+ }
803
+ return fn;
804
+ }
805
+ if (prop === 'toHTML') {
806
+ let fn = BoundFns.get('toHTML');
807
+ if (!fn) {
808
+ fn = function () {
809
+ entangleSignal(signals, receiver, '@identity');
810
+ return `<span>Record<${identifier.type}:${identifier.id} (${identifier.lid})></span>`;
811
+ };
812
+ BoundFns.set(prop, fn);
813
+ }
814
+ return fn;
815
+ }
816
+ if (prop === 'toJSON') {
817
+ let fn = BoundFns.get('toJSON');
818
+ if (!fn) {
819
+ fn = function () {
820
+ const json = {};
821
+ for (const key in receiver) {
822
+ json[key] = receiver[key];
823
+ }
824
+ return json;
825
+ };
826
+ BoundFns.set(prop, fn);
827
+ }
828
+ return fn;
829
+ }
830
+ if (prop === Symbol.toPrimitive) return () => null;
831
+ if (prop === Symbol.iterator) {
832
+ let fn = BoundFns.get(Symbol.iterator);
833
+ if (!fn) {
834
+ fn = function* () {
835
+ for (const key in receiver) {
836
+ yield [key, receiver[key]];
837
+ }
838
+ };
839
+ BoundFns.set(Symbol.iterator, fn);
840
+ }
841
+ return fn;
842
+ }
768
843
  if (prop === 'constructor') {
769
844
  return SchemaRecord;
770
845
  }
771
846
  // too many things check for random symbols
772
- if (typeof prop === 'symbol') {
773
- return undefined;
774
- }
775
- let type = identifier.type;
776
- if (isEmbedded) {
777
- type = embeddedType;
778
- }
779
- throw new Error(`No field named ${String(prop)} on ${type}`);
847
+ if (typeof prop === 'symbol') return undefined;
848
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
849
+ {
850
+ throw new Error(`No field named ${String(prop)} on ${isEmbedded ? embeddedType : identifier.type}`);
851
+ }
852
+ })() : {};
853
+ return undefined;
780
854
  }
781
855
  const field = maybeField.kind === 'alias' ? maybeField.options : maybeField;
782
856
  macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
@@ -804,49 +878,24 @@ class SchemaRecord {
804
878
  return lastValue;
805
879
  }
806
880
  case 'field':
807
- macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
808
- if (!test) {
809
- throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
810
- }
811
- })(!target[Legacy]) : {};
812
881
  entangleSignal(signals, receiver, field.name);
813
882
  return computeField(schema, cache, target, identifier, field, propArray, IS_EDITABLE);
814
883
  case 'attribute':
815
884
  entangleSignal(signals, receiver, field.name);
816
885
  return computeAttribute(cache, identifier, prop, IS_EDITABLE);
817
886
  case 'resource':
818
- macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
819
- if (!test) {
820
- throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
821
- }
822
- })(!target[Legacy]) : {};
823
887
  entangleSignal(signals, receiver, field.name);
824
888
  return computeResource(store, cache, target, identifier, field, prop, IS_EDITABLE);
825
889
  case 'derived':
826
890
  return computeDerivation(schema, receiver, identifier, field, prop);
827
891
  case 'schema-array':
828
892
  case 'array':
829
- macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
830
- if (!test) {
831
- throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
832
- }
833
- })(!target[Legacy]) : {};
834
893
  entangleSignal(signals, receiver, field.name);
835
894
  return computeArray(store, schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
836
895
  case 'object':
837
- macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
838
- if (!test) {
839
- throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
840
- }
841
- })(!target[Legacy]) : {};
842
896
  entangleSignal(signals, receiver, field.name);
843
897
  return computeObject(schema, cache, target, identifier, field, propArray, Mode[Editable], Mode[Legacy]);
844
898
  case 'schema-object':
845
- macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
846
- if (!test) {
847
- throw new Error(`SchemaRecord.${field.name} is not available in legacy mode because it has type '${field.kind}'`);
848
- }
849
- })(!target[Legacy]) : {};
850
899
  entangleSignal(signals, receiver, field.name);
851
900
  // run transform, then use that value as the object to manage
852
901
  return computeSchemaObject(store, cache, target, identifier, field, propArray, Mode[Legacy], Mode[Editable]);
@@ -1292,6 +1341,19 @@ class SchemaRecord {
1292
1341
  break;
1293
1342
  }
1294
1343
  });
1344
+ if (macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG)) {
1345
+ Object.defineProperty(this, '__SHOW_ME_THE_DATA_(debug mode only)__', {
1346
+ enumerable: false,
1347
+ configurable: true,
1348
+ get() {
1349
+ const data = {};
1350
+ for (const key of fields.keys()) {
1351
+ data[key] = proxy[key];
1352
+ }
1353
+ return data;
1354
+ }
1355
+ });
1356
+ }
1295
1357
  return proxy;
1296
1358
  }
1297
1359
  [Destroy]() {
@@ -1328,7 +1390,13 @@ class SchemaRecord {
1328
1390
  }
1329
1391
  function instantiateRecord(store, identifier, createArgs) {
1330
1392
  const schema = store.schema;
1331
- const isLegacy = schema.resource(identifier)?.legacy ?? false;
1393
+ const resourceSchema = schema.resource(identifier);
1394
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1395
+ if (!test) {
1396
+ throw new Error(`Expected a resource schema`);
1397
+ }
1398
+ })(isResourceSchema(resourceSchema)) : {};
1399
+ const isLegacy = resourceSchema?.legacy ?? false;
1332
1400
  const isEditable = isLegacy || store.cache.isNew(identifier);
1333
1401
  const record = new SchemaRecord(store, identifier, {
1334
1402
  [Editable]: isEditable,
@@ -1351,18 +1419,23 @@ function teardownRecord(record) {
1351
1419
  record[Destroy]();
1352
1420
  }
1353
1421
  const Support = getOrSetGlobal('Support', new WeakMap());
1354
- const SchemaRecordFields = [{
1422
+ const ConstructorField = {
1355
1423
  type: '@constructor',
1356
1424
  name: 'constructor',
1357
1425
  kind: 'derived'
1358
- }, {
1426
+ };
1427
+ const TypeField = {
1359
1428
  type: '@identity',
1360
1429
  name: '$type',
1361
1430
  kind: 'derived',
1362
1431
  options: {
1363
1432
  key: 'type'
1364
1433
  }
1365
- }];
1434
+ };
1435
+ const DefaultIdentityField = {
1436
+ name: 'id',
1437
+ kind: '@id'
1438
+ };
1366
1439
  function _constructor(record) {
1367
1440
  let state = Support.get(record);
1368
1441
  if (!state) {
@@ -1372,17 +1445,29 @@ function _constructor(record) {
1372
1445
  return state._constructor = state._constructor || {
1373
1446
  name: `SchemaRecord<${recordIdentifierFor$1(record).type}>`,
1374
1447
  get modelName() {
1375
- throw new Error('Cannot access record.constructor.modelName on non-Legacy Schema Records.');
1448
+ macroCondition(getGlobalConfig().WarpDriveMirror.env.DEBUG) ? (test => {
1449
+ {
1450
+ throw new Error(`record.constructor.modelName is not available outside of legacy mode`);
1451
+ }
1452
+ })() : {};
1453
+ return undefined;
1376
1454
  }
1377
1455
  };
1378
1456
  }
1379
1457
  _constructor[Type] = '@constructor';
1458
+
1459
+ /**
1460
+ * Utility for constructing a ResourceSchema with the recommended fields
1461
+ * for the Polaris experience.
1462
+ */
1380
1463
  function withDefaults(schema) {
1381
- schema.identity = schema.identity || {
1382
- name: 'id',
1383
- kind: '@id'
1384
- };
1385
- schema.fields.push(...SchemaRecordFields);
1464
+ schema.identity = schema.identity || DefaultIdentityField;
1465
+
1466
+ // because fields gets iterated in definition order,
1467
+ // we add TypeField to the beginning so that it will
1468
+ // appear right next to the identity field
1469
+ schema.fields.unshift(TypeField);
1470
+ schema.fields.push(ConstructorField);
1386
1471
  return schema;
1387
1472
  }
1388
1473
  function fromIdentity(record, options, key) {
@@ -1534,7 +1619,7 @@ class SchemaService {
1534
1619
  relationships[field.name] = field;
1535
1620
  }
1536
1621
  });
1537
- const traits = new Set(schema.traits);
1622
+ const traits = new Set(isResourceSchema(schema) ? schema.traits : []);
1538
1623
  traits.forEach(trait => {
1539
1624
  this._traits.add(trait);
1540
1625
  });