@warp-drive/core 5.7.0-alpha.1 → 5.7.0-alpha.11
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 +73 -0
- package/declarations/reactive/-private/fields/get-field-key.d.ts +8 -0
- package/declarations/reactive/-private/fields/managed-array.d.ts +5 -8
- package/declarations/reactive/-private/fields/managed-object.d.ts +7 -9
- package/declarations/reactive/-private/kind/alias-field.d.ts +4 -0
- package/declarations/reactive/-private/kind/array-field.d.ts +4 -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 +6 -21
- package/declarations/reactive/-private/schema.d.ts +6 -2
- package/declarations/reactive/-private/symbols.d.ts +1 -6
- package/declarations/reactive/-private.d.ts +1 -1
- package/declarations/reactive.d.ts +1 -0
- package/declarations/request/-private/fetch.d.ts +2 -0
- package/declarations/store/-types/q/schema-service.d.ts +27 -32
- package/declarations/store/-types/q/store.d.ts +6 -7
- package/declarations/store/deprecated/-private.d.ts +7 -7
- package/declarations/store/deprecated/store.d.ts +5 -5
- package/declarations/types/-private.d.ts +1 -1
- package/declarations/types/cache.d.ts +0 -2
- package/declarations/types/params.d.ts +2 -3
- package/declarations/types/request.d.ts +6 -6
- package/declarations/types/schema/fields.d.ts +377 -13
- package/dist/graph/-private.js +1 -1
- package/dist/{handler-D2jjnIA-.js → handler-SdXlte1w.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/reactive/-private.js +1 -1
- package/dist/reactive.js +1171 -603
- package/dist/{request-state-CejVJgdj.js → request-state-CeN66aML.js} +12 -10
- package/dist/store/-private.js +2 -2
- package/dist/{symbols-SIstXMLI.js → symbols-BoONANuz.js} +2 -7
- package/dist/types/-private.js +1 -1
- package/dist/types/schema/fields.js +21 -2
- package/package.json +3 -3
- package/declarations/reactive/-private/fields/compute.d.ts +0 -43
package/dist/reactive.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
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, E as peekInternalSignal, 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-CeN66aML.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';
|
|
6
6
|
import './utils/string.js';
|
|
7
7
|
import { A as ARRAY_SIGNAL, O as OBJECT_SIGNAL, c as createMemo } from "./configure-B48bFHOl.js";
|
|
8
8
|
import { RecordStore, Type } from './types/symbols.js';
|
|
9
|
+
import { S as SOURCE, C as Context, D as Destroy, a as Checkout } from "./symbols-BoONANuz.js";
|
|
9
10
|
import { getOrSetGlobal } from './types/-private.js';
|
|
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,21 +70,27 @@ 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 =
|
|
63
|
-
this[
|
|
77
|
+
const IS_EDITABLE = context.editable ?? false;
|
|
78
|
+
this[Context] = context;
|
|
79
|
+
const schema = context.store.schema;
|
|
80
|
+
const cache = context.store.cache;
|
|
81
|
+
const {
|
|
82
|
+
field
|
|
83
|
+
} = context;
|
|
64
84
|
const signals = withSignalStore(this);
|
|
65
85
|
let _SIGNAL = null;
|
|
66
86
|
const boundFns = new Map();
|
|
67
|
-
this.identifier =
|
|
68
|
-
this.path = path;
|
|
87
|
+
this.identifier = context.resourceKey;
|
|
88
|
+
this.path = context.path;
|
|
69
89
|
this.owner = owner;
|
|
70
90
|
let transaction = false;
|
|
71
|
-
const
|
|
72
|
-
|
|
91
|
+
const KeyMode = field.options?.key ?? '@identity';
|
|
92
|
+
// listener.
|
|
93
|
+
const RefStorage = KeyMode === '@identity' ? WeakMap :
|
|
73
94
|
// CAUTION CAUTION CAUTION
|
|
74
95
|
// this is a pile of lies
|
|
75
96
|
// the Map is Map<string, WeakRef<ReactiveResource>>
|
|
@@ -77,8 +98,8 @@ class ManagedArray {
|
|
|
77
98
|
// internal to a method like ours without us duplicating the code
|
|
78
99
|
// into two separate methods.
|
|
79
100
|
Map;
|
|
80
|
-
const ManagedRecordRefs =
|
|
81
|
-
const extensions = legacy ? schema.CAUTION_MEGA_DANGER_ZONE_arrayExtensions(field) : null;
|
|
101
|
+
const ManagedRecordRefs = field.kind === 'schema-array' ? new RefStorage() : null;
|
|
102
|
+
const extensions = context.legacy ? schema.CAUTION_MEGA_DANGER_ZONE_arrayExtensions(field) : null;
|
|
82
103
|
const proxy = new Proxy(this[SOURCE], {
|
|
83
104
|
get(target, prop, receiver) {
|
|
84
105
|
if (prop === ARRAY_SIGNAL) {
|
|
@@ -93,7 +114,7 @@ class ManagedArray {
|
|
|
93
114
|
const index = convertToInt(prop);
|
|
94
115
|
if (_SIGNAL.isStale && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
|
|
95
116
|
_SIGNAL.isStale = false;
|
|
96
|
-
const newData = cache.getAttr(
|
|
117
|
+
const newData = cache.getAttr(context.resourceKey, context.path);
|
|
97
118
|
if (newData && newData !== self[SOURCE]) {
|
|
98
119
|
self[SOURCE].length = 0;
|
|
99
120
|
self[SOURCE].push(...newData);
|
|
@@ -104,14 +125,72 @@ class ManagedArray {
|
|
|
104
125
|
}
|
|
105
126
|
if (prop === '[]') return consumeInternalSignal(_SIGNAL), receiver;
|
|
106
127
|
if (index !== null) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
128
|
+
if (!transaction) {
|
|
129
|
+
consumeInternalSignal(_SIGNAL);
|
|
130
|
+
}
|
|
131
|
+
const rawValue = target[index];
|
|
132
|
+
if (field.kind === 'array') {
|
|
133
|
+
if (field.type) {
|
|
134
|
+
const transform = schema.transformation(field);
|
|
135
|
+
return transform.hydrate(rawValue, field.options ?? null, self.owner);
|
|
136
|
+
}
|
|
137
|
+
return rawValue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* When the array is polymorphic, we need to determine the real type
|
|
142
|
+
* in order to apply the correct identity as schema-object identity
|
|
143
|
+
* is only required to be unique by type
|
|
144
|
+
*/
|
|
145
|
+
let objectType;
|
|
146
|
+
if (field.options?.polymorphic) {
|
|
147
|
+
const typePath = field.options.type ?? 'type';
|
|
148
|
+
// if we are polymorphic, then context.field.options.type will
|
|
149
|
+
// either specify a path on the rawValue to use as the type, defaulting to "type" or
|
|
150
|
+
// the special string "@hash" which tells us to treat field.type as a hashFn name with which
|
|
151
|
+
// to calc the type.
|
|
152
|
+
if (typePath === '@hash') {
|
|
153
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
154
|
+
if (!test) {
|
|
155
|
+
throw new Error(`Expected the field to define a hashFn as its type`);
|
|
156
|
+
}
|
|
157
|
+
})(field.type) : {};
|
|
158
|
+
const hashFn = schema.hashFn({
|
|
159
|
+
type: field.type
|
|
160
|
+
});
|
|
161
|
+
// TODO consider if there are better options and name args we could provide.
|
|
162
|
+
objectType = hashFn(rawValue, null, null);
|
|
163
|
+
} else {
|
|
164
|
+
objectType = rawValue[typePath];
|
|
165
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
166
|
+
if (!test) {
|
|
167
|
+
throw new Error(`Expected the type path for the field to be a value on the raw object`);
|
|
168
|
+
}
|
|
169
|
+
})(typePath && objectType && typeof objectType === 'string') : {};
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
173
|
+
if (!test) {
|
|
174
|
+
throw new Error(`A non-polymorphic SchemaArrayField must provide a SchemaObject type in its definition`);
|
|
175
|
+
}
|
|
176
|
+
})(field.type) : {};
|
|
177
|
+
objectType = field.type;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* When KeyMode=@hash the ReactiveResource is keyed into
|
|
182
|
+
* ManagedRecordRefs by the return value of @hash on the rawValue.
|
|
183
|
+
*
|
|
184
|
+
* This means that we could find a way to only recompute the identity
|
|
185
|
+
* when ARRAY_SIGNAL is dirty if hash performance becomes a bottleneck.
|
|
186
|
+
*/
|
|
187
|
+
let schemaObjectKeyValue;
|
|
188
|
+
if (KeyMode === '@hash') {
|
|
110
189
|
const hashField = schema.resource({
|
|
111
|
-
type:
|
|
190
|
+
type: objectType
|
|
112
191
|
}).identity;
|
|
113
192
|
const hashFn = schema.hashFn(hashField);
|
|
114
|
-
|
|
193
|
+
schemaObjectKeyValue = hashFn(rawValue, hashField.options ?? null, hashField.name);
|
|
115
194
|
} else {
|
|
116
195
|
// if mode is not @identity or @index, then access the key path.
|
|
117
196
|
// we should assert that `mode` is a string
|
|
@@ -119,69 +198,78 @@ class ManagedArray {
|
|
|
119
198
|
// and, we likely should lookup the associated field and throw an error IF
|
|
120
199
|
// the given field does not exist OR
|
|
121
200
|
// the field is anything other than a GenericField or LegacyAttributeField.
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
if (isSchemaArray) {
|
|
145
|
-
if (!transaction) {
|
|
146
|
-
consumeInternalSignal(_SIGNAL);
|
|
147
|
-
}
|
|
148
|
-
if (val) {
|
|
149
|
-
const recordRef = ManagedRecordRefs.get(val);
|
|
150
|
-
let record = recordRef?.deref();
|
|
151
|
-
if (!record) {
|
|
152
|
-
const recordPath = path.slice();
|
|
153
|
-
// this is a dirty lie since path is string[] but really we
|
|
154
|
-
// should change the types for paths to `Array<string | number>`
|
|
155
|
-
// TODO we should allow the schema for the field to define a "key"
|
|
156
|
-
// for stability. Default should be `@identity` which means that
|
|
157
|
-
// same object reference from cache should result in same ReactiveResource
|
|
158
|
-
// embedded object.
|
|
159
|
-
recordPath.push(index);
|
|
160
|
-
const recordIdentifier = self.owner[Identifier] || self.owner[Parent];
|
|
161
|
-
record = new ReactiveResource(store, recordIdentifier, {
|
|
162
|
-
[Editable]: self.owner[Editable],
|
|
163
|
-
[Legacy]: self.owner[Legacy]
|
|
164
|
-
}, true, field, recordPath);
|
|
165
|
-
// if mode is not @identity or @index, then access the key path now
|
|
166
|
-
// to determine the key value.
|
|
167
|
-
// chris says we can implement this as a special kind `@hash` which
|
|
168
|
-
// would be a function that only has access to the cache value and not
|
|
169
|
-
// the record itself, so derivation is possible but intentionally limited
|
|
170
|
-
// and non-reactive?
|
|
171
|
-
ManagedRecordRefs.set(val, new WeakRef(record));
|
|
201
|
+
if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
|
|
202
|
+
const isPathKeyMode = KeyMode !== '@identity' && KeyMode !== '@index';
|
|
203
|
+
if (isPathKeyMode) {
|
|
204
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
205
|
+
if (!test) {
|
|
206
|
+
throw new Error('mode must be a string');
|
|
207
|
+
}
|
|
208
|
+
})(typeof KeyMode === 'string' && KeyMode !== '') : {};
|
|
209
|
+
const modeField = schema.fields({
|
|
210
|
+
type: objectType
|
|
211
|
+
}).get(KeyMode);
|
|
212
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
213
|
+
if (!test) {
|
|
214
|
+
throw new Error('field must exist in schema');
|
|
215
|
+
}
|
|
216
|
+
})(modeField) : {};
|
|
217
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
218
|
+
if (!test) {
|
|
219
|
+
throw new Error('field must be a GenericField or LegacyAttributeField');
|
|
220
|
+
}
|
|
221
|
+
})(modeField.kind === 'field' || modeField.kind === 'attribute') : {};
|
|
172
222
|
}
|
|
173
|
-
return record;
|
|
174
223
|
}
|
|
175
|
-
|
|
224
|
+
schemaObjectKeyValue = KeyMode === '@identity' ? rawValue : KeyMode === '@index' ? index : rawValue[KeyMode];
|
|
176
225
|
}
|
|
177
|
-
if (!
|
|
178
|
-
|
|
226
|
+
if (!schemaObjectKeyValue) {
|
|
227
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
228
|
+
{
|
|
229
|
+
throw new Error(`Unexpected out of bounds access on SchemaArray`);
|
|
230
|
+
}
|
|
231
|
+
})() : {};
|
|
232
|
+
return undefined;
|
|
179
233
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
234
|
+
const recordRef = ManagedRecordRefs.get(schemaObjectKeyValue);
|
|
235
|
+
const record = recordRef?.value.deref();
|
|
236
|
+
|
|
237
|
+
// confirm the type and key still match
|
|
238
|
+
if (record && recordRef.type === objectType && recordRef.identity === schemaObjectKeyValue) {
|
|
239
|
+
if (recordRef.index !== index) {
|
|
240
|
+
recordRef.index = index;
|
|
241
|
+
recordRef.context.path[recordRef.context.path.length - 1] = index;
|
|
242
|
+
}
|
|
243
|
+
return record;
|
|
244
|
+
} else if (record) {
|
|
245
|
+
// TODO schedule idle once we can
|
|
246
|
+
void Promise.resolve().then(() => {
|
|
247
|
+
record[Destroy]();
|
|
248
|
+
});
|
|
183
249
|
}
|
|
184
|
-
|
|
250
|
+
const recordPath = context.path.slice();
|
|
251
|
+
// this is a dirty lie since path is string[] but really we
|
|
252
|
+
// should change the types for paths to `Array<string | number>`
|
|
253
|
+
recordPath.push(index);
|
|
254
|
+
const objectContext = {
|
|
255
|
+
store: context.store,
|
|
256
|
+
resourceKey: context.resourceKey,
|
|
257
|
+
modeName: context.modeName,
|
|
258
|
+
legacy: context.legacy,
|
|
259
|
+
editable: context.editable,
|
|
260
|
+
path: recordPath,
|
|
261
|
+
field: field,
|
|
262
|
+
value: objectType
|
|
263
|
+
};
|
|
264
|
+
const schemaObject = new ReactiveResource(objectContext);
|
|
265
|
+
ManagedRecordRefs.set(schemaObjectKeyValue, {
|
|
266
|
+
type: objectType,
|
|
267
|
+
identity: schemaObjectKeyValue,
|
|
268
|
+
index,
|
|
269
|
+
context: objectContext,
|
|
270
|
+
value: new WeakRef(schemaObject)
|
|
271
|
+
});
|
|
272
|
+
return schemaObject;
|
|
185
273
|
}
|
|
186
274
|
if (isArrayGetter(prop)) {
|
|
187
275
|
let fn = boundFns.get(prop);
|
|
@@ -190,7 +278,7 @@ class ManagedArray {
|
|
|
190
278
|
fn = function () {
|
|
191
279
|
consumeInternalSignal(_SIGNAL);
|
|
192
280
|
transaction = true;
|
|
193
|
-
const result = safeForEach(receiver, target, store, arguments[0], arguments[1]);
|
|
281
|
+
const result = safeForEach(receiver, target, context.store, arguments[0], arguments[1]);
|
|
194
282
|
transaction = false;
|
|
195
283
|
return result;
|
|
196
284
|
};
|
|
@@ -233,9 +321,9 @@ class ManagedArray {
|
|
|
233
321
|
},
|
|
234
322
|
set(target, prop, value, receiver) {
|
|
235
323
|
if (!IS_EDITABLE) {
|
|
236
|
-
let errorPath =
|
|
237
|
-
if (path) {
|
|
238
|
-
errorPath = path[path.length - 1];
|
|
324
|
+
let errorPath = context.resourceKey.type;
|
|
325
|
+
if (context.path) {
|
|
326
|
+
errorPath = context.path[context.path.length - 1];
|
|
239
327
|
}
|
|
240
328
|
throw new Error(`Cannot set ${String(prop)} on ${errorPath} because the record is not editable`);
|
|
241
329
|
}
|
|
@@ -253,19 +341,19 @@ class ManagedArray {
|
|
|
253
341
|
const reflect = Reflect.set(target, prop, value, receiver);
|
|
254
342
|
if (reflect) {
|
|
255
343
|
if (!field.type) {
|
|
256
|
-
cache.setAttr(
|
|
344
|
+
cache.setAttr(context.resourceKey, context.path, self[SOURCE]);
|
|
257
345
|
_SIGNAL.isStale = true;
|
|
258
346
|
return true;
|
|
259
347
|
}
|
|
260
348
|
let rawValue = self[SOURCE];
|
|
261
|
-
if (
|
|
349
|
+
if (field.kind !== 'schema-array') {
|
|
262
350
|
const transform = schema.transformation(field);
|
|
263
351
|
if (!transform) {
|
|
264
|
-
throw new Error(`No '${field.type}' transform defined for use by ${
|
|
352
|
+
throw new Error(`No '${field.type}' transform defined for use by ${context.resourceKey.type}.${String(prop)}`);
|
|
265
353
|
}
|
|
266
354
|
rawValue = self[SOURCE].map(item => transform.serialize(item, field.options ?? null, self.owner));
|
|
267
355
|
}
|
|
268
|
-
cache.setAttr(
|
|
356
|
+
cache.setAttr(context.resourceKey, context.path, rawValue);
|
|
269
357
|
_SIGNAL.isStale = true;
|
|
270
358
|
}
|
|
271
359
|
return reflect;
|
|
@@ -302,27 +390,455 @@ const desc = {
|
|
|
302
390
|
};
|
|
303
391
|
// compat(desc);
|
|
304
392
|
Object.defineProperty(ManagedArray.prototype, '[]', desc);
|
|
305
|
-
|
|
393
|
+
function getArrayField(context) {
|
|
394
|
+
const signal = entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
395
|
+
// the thing we hand out needs to know its owner and path in a private manner
|
|
396
|
+
// its "address" is the parent identifier (identifier) + field name (field.name)
|
|
397
|
+
// in the nested object case field name here is the full dot path from root resource to this value
|
|
398
|
+
// its "key" is the field on the parent record
|
|
399
|
+
// its "owner" is the parent record
|
|
400
|
+
const {
|
|
401
|
+
record
|
|
402
|
+
} = context;
|
|
403
|
+
let managedArray = signal.value;
|
|
404
|
+
if (managedArray) {
|
|
405
|
+
return managedArray;
|
|
406
|
+
} else {
|
|
407
|
+
const {
|
|
408
|
+
store,
|
|
409
|
+
resourceKey,
|
|
410
|
+
path,
|
|
411
|
+
field
|
|
412
|
+
} = context;
|
|
413
|
+
const {
|
|
414
|
+
cache
|
|
415
|
+
} = store;
|
|
416
|
+
let rawValue = context.editable ? cache.getAttr(resourceKey, path) : cache.getRemoteAttr(resourceKey, path);
|
|
417
|
+
if (!rawValue && field.kind === 'schema-array' && field.options?.defaultValue) {
|
|
418
|
+
rawValue = [];
|
|
419
|
+
}
|
|
420
|
+
if (!rawValue) {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
managedArray = new ManagedArray(context, record, rawValue);
|
|
424
|
+
signal.value = managedArray;
|
|
425
|
+
}
|
|
426
|
+
return managedArray;
|
|
427
|
+
}
|
|
428
|
+
function setArrayField(context) {
|
|
429
|
+
const {
|
|
430
|
+
field,
|
|
431
|
+
record,
|
|
432
|
+
value
|
|
433
|
+
} = context;
|
|
434
|
+
const {
|
|
435
|
+
cache,
|
|
436
|
+
schema
|
|
437
|
+
} = context.store;
|
|
438
|
+
const fieldSignal = peekInternalSignal(context.signals, context.path.at(-1));
|
|
439
|
+
const peeked = fieldSignal?.value;
|
|
440
|
+
const transform = field.type ? schema.transformation(field) : null;
|
|
441
|
+
const rawValue = field.type ? value.map(item => transform.serialize(item, field.options ?? null, record)) : value?.slice();
|
|
442
|
+
cache.setAttr(context.resourceKey, context.path, rawValue);
|
|
443
|
+
if (peeked) {
|
|
444
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
445
|
+
if (!test) {
|
|
446
|
+
throw new Error(`Expected the peekManagedArray for ${field.kind} to return a ManagedArray`);
|
|
447
|
+
}
|
|
448
|
+
})(ARRAY_SIGNAL in peeked) : {};
|
|
449
|
+
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
450
|
+
arrSignal.isStale = true;
|
|
451
|
+
// TODO run array destroy?
|
|
452
|
+
}
|
|
453
|
+
if (!Array.isArray(rawValue) && fieldSignal) {
|
|
454
|
+
fieldSignal.value = null;
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
function getAttributeField(context) {
|
|
459
|
+
entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
460
|
+
const {
|
|
461
|
+
cache
|
|
462
|
+
} = context.store;
|
|
463
|
+
return context.editable ? cache.getAttr(context.resourceKey, context.path) : cache.getRemoteAttr(context.resourceKey, context.path);
|
|
464
|
+
}
|
|
465
|
+
function setAttributeField(context) {
|
|
466
|
+
context.store.cache.setAttr(context.resourceKey, context.path, context.value);
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
const InvalidKinds = ['alias', 'derived', '@local'];
|
|
470
|
+
function isInvalidKind(kind) {
|
|
471
|
+
return InvalidKinds.includes(kind);
|
|
472
|
+
}
|
|
473
|
+
function isNonIdentityCacheableField(field) {
|
|
474
|
+
return !isInvalidKind(field.kind) && field.kind !== '@id' && field.kind !== '@hash';
|
|
475
|
+
}
|
|
476
|
+
function getFieldCacheKeyStrict(field) {
|
|
477
|
+
return field.sourceKey || field.name;
|
|
478
|
+
}
|
|
479
|
+
function getFieldCacheKey(field) {
|
|
480
|
+
return 'sourceKey' in field && field.sourceKey ? field.sourceKey : field.name;
|
|
481
|
+
}
|
|
482
|
+
function getBelongsToField(context) {
|
|
483
|
+
entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
484
|
+
const {
|
|
485
|
+
field,
|
|
486
|
+
resourceKey,
|
|
487
|
+
store
|
|
488
|
+
} = context;
|
|
489
|
+
const {
|
|
490
|
+
schema,
|
|
491
|
+
cache
|
|
492
|
+
} = store;
|
|
493
|
+
if (field.options.linksMode) {
|
|
494
|
+
const rawValue = context.editable ? cache.getRelationship(resourceKey, getFieldCacheKeyStrict(field)) : cache.getRemoteRelationship(resourceKey, getFieldCacheKeyStrict(field));
|
|
495
|
+
return rawValue.data ? store.peekRecord(rawValue.data) : null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// FIXME move this to a "LegacyMode" make this part of "PolarisMode"
|
|
499
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
500
|
+
if (!test) {
|
|
501
|
+
throw new Error(`Can only use belongsTo fields when the resource is in legacy mode`);
|
|
502
|
+
}
|
|
503
|
+
})(context.legacy) : {};
|
|
504
|
+
return schema._kind('@legacy', 'belongsTo').get(store, context.record, resourceKey, field);
|
|
505
|
+
}
|
|
506
|
+
function setBelongsToField(context) {
|
|
507
|
+
const {
|
|
508
|
+
store
|
|
509
|
+
} = context;
|
|
510
|
+
const {
|
|
511
|
+
schema
|
|
512
|
+
} = store;
|
|
513
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
514
|
+
if (!test) {
|
|
515
|
+
throw new Error(`Can only mutate belongsTo fields when the resource is in legacy mode`);
|
|
516
|
+
}
|
|
517
|
+
})(context.legacy) : {};
|
|
518
|
+
schema._kind('@legacy', 'belongsTo').set(store, context.record, context.resourceKey, context.field, context.value);
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
function getCollectionField(context) {
|
|
522
|
+
entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
523
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
524
|
+
{
|
|
525
|
+
throw new Error(`Accessing collection fields is not yet implemented`);
|
|
526
|
+
}
|
|
527
|
+
})() : {};
|
|
528
|
+
}
|
|
529
|
+
function setCollectionField(context) {
|
|
530
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
531
|
+
{
|
|
532
|
+
throw new Error(`Setting collection fields is not yet implemented`);
|
|
533
|
+
}
|
|
534
|
+
})() : {};
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
function getDerivedField(context) {
|
|
538
|
+
const {
|
|
539
|
+
schema
|
|
540
|
+
} = context.store;
|
|
541
|
+
return schema.derivation(context.field)(context.record, context.field.options ?? null, context.field.name);
|
|
542
|
+
}
|
|
543
|
+
function setDerivedField(context) {
|
|
544
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
545
|
+
{
|
|
546
|
+
throw new Error(`ILLEGAL SET: Cannot set '${context.path.join('.')}' on '${context.resourceKey.type}' as ${context.field.kind} fields are not mutable`);
|
|
547
|
+
}
|
|
548
|
+
})() : {};
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
function getGenericField(context) {
|
|
552
|
+
entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
553
|
+
const {
|
|
554
|
+
cache,
|
|
555
|
+
schema
|
|
556
|
+
} = context.store;
|
|
557
|
+
const rawValue = context.editable ? cache.getAttr(context.resourceKey, context.path) : cache.getRemoteAttr(context.resourceKey, context.path);
|
|
558
|
+
const {
|
|
559
|
+
field
|
|
560
|
+
} = context;
|
|
561
|
+
if (!field.type) {
|
|
562
|
+
return rawValue;
|
|
563
|
+
}
|
|
564
|
+
const transform = schema.transformation(field);
|
|
565
|
+
return transform.hydrate(rawValue, field.options ?? null, context.record);
|
|
566
|
+
}
|
|
567
|
+
function setGenericField(context) {
|
|
568
|
+
const {
|
|
569
|
+
cache,
|
|
570
|
+
schema
|
|
571
|
+
} = context.store;
|
|
572
|
+
const {
|
|
573
|
+
field
|
|
574
|
+
} = context;
|
|
575
|
+
if (!field.type) {
|
|
576
|
+
cache.setAttr(context.resourceKey, context.path, context.value);
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
const transform = schema.transformation(field);
|
|
580
|
+
const rawValue = transform.serialize(context.value, field.options ?? null, context.record);
|
|
581
|
+
cache.setAttr(context.resourceKey, context.path, rawValue);
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
class ManyArrayManager {
|
|
585
|
+
constructor(record, editable) {
|
|
586
|
+
const context = record[Context];
|
|
587
|
+
this.record = record;
|
|
588
|
+
this.store = context.store;
|
|
589
|
+
this.identifier = context.resourceKey;
|
|
590
|
+
this.editable = editable;
|
|
591
|
+
}
|
|
592
|
+
_syncArray(array) {
|
|
593
|
+
const method = this.editable ? 'getRelationship' : 'getRemoteRelationship';
|
|
594
|
+
// FIXME field needs to use sourceKey
|
|
595
|
+
const rawValue = this.store.cache[method](this.identifier, array.key);
|
|
596
|
+
if (rawValue.meta) {
|
|
597
|
+
array.meta = rawValue.meta;
|
|
598
|
+
}
|
|
599
|
+
if (rawValue.links) {
|
|
600
|
+
array.links = rawValue.links;
|
|
601
|
+
}
|
|
602
|
+
const currentState = array[SOURCE$1];
|
|
603
|
+
|
|
604
|
+
// unlike in the normal RecordArray case, we don't need to divorce the reference
|
|
605
|
+
// because we don't need to worry about associate/disassociate since the graph
|
|
606
|
+
// takes care of that for us
|
|
607
|
+
if (currentState !== rawValue.data) {
|
|
608
|
+
currentState.length = 0;
|
|
609
|
+
fastPush(currentState, rawValue.data);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
reloadHasMany(key, options) {
|
|
613
|
+
// FIXME field needs to use sourceKey
|
|
614
|
+
const field = this.store.schema.fields(this.identifier).get(key);
|
|
615
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
616
|
+
if (!test) {
|
|
617
|
+
throw new Error(`Expected a hasMany field for ${key}`);
|
|
618
|
+
}
|
|
619
|
+
})(field?.kind === 'hasMany') : {};
|
|
620
|
+
const cacheOptions = options ? extractCacheOptions(options) : {
|
|
621
|
+
reload: true
|
|
622
|
+
};
|
|
623
|
+
cacheOptions.types = [field.type];
|
|
624
|
+
const rawValue = this.store.cache.getRelationship(this.identifier, key);
|
|
625
|
+
const req = {
|
|
626
|
+
url: getRelatedLink(rawValue),
|
|
627
|
+
op: 'findHasMany',
|
|
628
|
+
method: 'GET',
|
|
629
|
+
records: rawValue.data,
|
|
630
|
+
cacheOptions,
|
|
631
|
+
options: {
|
|
632
|
+
field,
|
|
633
|
+
identifier: this.identifier,
|
|
634
|
+
links: rawValue.links,
|
|
635
|
+
meta: rawValue.meta
|
|
636
|
+
},
|
|
637
|
+
[EnableHydration]: false
|
|
638
|
+
};
|
|
639
|
+
return this.store.request(req);
|
|
640
|
+
}
|
|
641
|
+
mutate(mutation) {
|
|
642
|
+
this.store.cache.mutate(mutation);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function getRelatedLink(resource) {
|
|
646
|
+
const related = resource.links?.related;
|
|
647
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
648
|
+
if (!test) {
|
|
649
|
+
throw new Error(`Expected a related link`);
|
|
650
|
+
}
|
|
651
|
+
})(related) : {};
|
|
652
|
+
return typeof related === 'object' ? related.href : related;
|
|
653
|
+
}
|
|
654
|
+
function extractCacheOptions(options) {
|
|
655
|
+
const cacheOptions = {};
|
|
656
|
+
if ('reload' in options) {
|
|
657
|
+
cacheOptions.reload = options.reload;
|
|
658
|
+
}
|
|
659
|
+
if ('backgroundReload' in options) {
|
|
660
|
+
cacheOptions.backgroundReload = options.backgroundReload;
|
|
661
|
+
}
|
|
662
|
+
return cacheOptions;
|
|
663
|
+
}
|
|
664
|
+
function getHasManyField(context) {
|
|
665
|
+
const signal = entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
666
|
+
const {
|
|
667
|
+
store,
|
|
668
|
+
field
|
|
669
|
+
} = context;
|
|
670
|
+
if (field.options.linksMode) {
|
|
671
|
+
const {
|
|
672
|
+
record
|
|
673
|
+
} = context;
|
|
674
|
+
// the thing we hand out needs to know its owner and path in a private manner
|
|
675
|
+
// its "address" is the parent identifier (identifier) + field name (field.name)
|
|
676
|
+
// in the nested object case field name here is the full dot path from root resource to this value
|
|
677
|
+
// its "key" is the field on the parent record
|
|
678
|
+
// its "owner" is the parent record
|
|
679
|
+
|
|
680
|
+
const cached = signal.value;
|
|
681
|
+
if (cached) {
|
|
682
|
+
return cached;
|
|
683
|
+
}
|
|
684
|
+
const {
|
|
685
|
+
editable,
|
|
686
|
+
resourceKey
|
|
687
|
+
} = context;
|
|
688
|
+
const {
|
|
689
|
+
cache
|
|
690
|
+
} = store;
|
|
691
|
+
const rawValue = cache.getRelationship(resourceKey, getFieldCacheKeyStrict(field));
|
|
692
|
+
if (!rawValue) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
const managedArray = new RelatedCollection({
|
|
696
|
+
store,
|
|
697
|
+
type: field.type,
|
|
698
|
+
identifier: resourceKey,
|
|
699
|
+
cache,
|
|
700
|
+
field: context.legacy ? field : undefined,
|
|
701
|
+
// we divorce the reference here because ManyArray mutates the target directly
|
|
702
|
+
// before sending the mutation op to the cache. We may be able to avoid this in the future
|
|
703
|
+
identifiers: rawValue.data?.slice(),
|
|
704
|
+
key: field.name,
|
|
705
|
+
meta: rawValue.meta || null,
|
|
706
|
+
links: rawValue.links || null,
|
|
707
|
+
isPolymorphic: field.options.polymorphic ?? false,
|
|
708
|
+
isAsync: field.options.async ?? false,
|
|
709
|
+
// TODO: Grab the proper value
|
|
710
|
+
_inverseIsAsync: false,
|
|
711
|
+
// @ts-expect-error Typescript doesn't have a way for us to thread the generic backwards so it infers unknown instead of T
|
|
712
|
+
manager: new ManyArrayManager(record, editable),
|
|
713
|
+
isLoaded: true,
|
|
714
|
+
allowMutation: editable
|
|
715
|
+
});
|
|
716
|
+
signal.value = managedArray;
|
|
717
|
+
return managedArray;
|
|
718
|
+
}
|
|
719
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
720
|
+
if (!test) {
|
|
721
|
+
throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
|
|
722
|
+
}
|
|
723
|
+
})(context.legacy) : {};
|
|
724
|
+
return store.schema._kind('@legacy', 'hasMany').get(store, context.record, context.resourceKey, field);
|
|
725
|
+
}
|
|
726
|
+
function setHasManyField(context) {
|
|
727
|
+
const {
|
|
728
|
+
store
|
|
729
|
+
} = context;
|
|
730
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
731
|
+
if (!test) {
|
|
732
|
+
throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
|
|
733
|
+
}
|
|
734
|
+
})(context.legacy) : {};
|
|
735
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
736
|
+
if (!test) {
|
|
737
|
+
throw new Error(`You must pass an array of records to set a hasMany relationship`);
|
|
738
|
+
}
|
|
739
|
+
})(Array.isArray(context.value)) : {};
|
|
740
|
+
store.schema._kind('@legacy', 'hasMany').set(store, context.record, context.resourceKey, context.field, context.value);
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
function getHashField(context) {
|
|
744
|
+
const {
|
|
745
|
+
field,
|
|
746
|
+
path,
|
|
747
|
+
resourceKey
|
|
748
|
+
} = context;
|
|
749
|
+
const {
|
|
750
|
+
schema,
|
|
751
|
+
cache
|
|
752
|
+
} = context.store;
|
|
753
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
754
|
+
if (!test) {
|
|
755
|
+
throw new Error(`Cannot use a ${field.kind} directly on a resource.`);
|
|
756
|
+
}
|
|
757
|
+
})(Array.isArray(path) && path.length > 1) : {};
|
|
758
|
+
const realPath = path.slice(0, -1);
|
|
759
|
+
const rawData = context.editable ? cache.getAttr(resourceKey, realPath) : cache.getRemoteAttr(resourceKey, realPath);
|
|
760
|
+
return schema.hashFn(field)(rawData, field.options ?? null, field.name ?? null);
|
|
761
|
+
}
|
|
762
|
+
function setHashField(context) {
|
|
763
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
764
|
+
{
|
|
765
|
+
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`);
|
|
766
|
+
}
|
|
767
|
+
})() : {};
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
function getIdentityField(context) {
|
|
771
|
+
entangleSignal(context.signals, context.record, '@identity', null);
|
|
772
|
+
return context.resourceKey.id;
|
|
773
|
+
}
|
|
774
|
+
function setIdentityField(context) {
|
|
775
|
+
const {
|
|
776
|
+
value,
|
|
777
|
+
resourceKey,
|
|
778
|
+
store
|
|
779
|
+
} = context;
|
|
780
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
781
|
+
if (!test) {
|
|
782
|
+
throw new Error(`Expected to receive a string id`);
|
|
783
|
+
}
|
|
784
|
+
})(typeof value === 'string' && value.length) : {};
|
|
785
|
+
const normalizedId = String(value);
|
|
786
|
+
const didChange = normalizedId !== resourceKey.id;
|
|
787
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
788
|
+
if (!test) {
|
|
789
|
+
throw new Error(`Cannot set ${resourceKey.type} record's id to ${normalizedId}, because id is already ${resourceKey.id}`);
|
|
790
|
+
}
|
|
791
|
+
})(!didChange || resourceKey.id === null) : {};
|
|
792
|
+
if (normalizedId !== null && didChange) {
|
|
793
|
+
store._instanceCache.setRecordId(resourceKey, normalizedId);
|
|
794
|
+
store.notifications.notify(resourceKey, 'identity');
|
|
795
|
+
}
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
function getLocalField(context) {
|
|
799
|
+
const {
|
|
800
|
+
field
|
|
801
|
+
} = context;
|
|
802
|
+
const signal = getOrCreateInternalSignal(context.signals, context.record, field.name, field.options?.defaultValue ?? null);
|
|
803
|
+
consumeInternalSignal(signal);
|
|
804
|
+
return signal.value;
|
|
805
|
+
}
|
|
806
|
+
function setLocalField(context) {
|
|
807
|
+
const {
|
|
808
|
+
value
|
|
809
|
+
} = context;
|
|
810
|
+
const signal = getOrCreateInternalSignal(context.signals, context.record, context.field.name, value);
|
|
811
|
+
if (signal.value !== value) {
|
|
812
|
+
signal.value = value;
|
|
813
|
+
notifyInternalSignal(signal);
|
|
814
|
+
}
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
const ObjectSymbols = new Set([OBJECT_SIGNAL, Context, SOURCE]);
|
|
306
818
|
|
|
307
819
|
// const ignoredGlobalFields = new Set<string>(['setInterval', 'nodeType', 'nodeName', 'length', 'document', STRUCTURED]);
|
|
308
820
|
|
|
309
821
|
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
|
310
822
|
class ManagedObject {
|
|
311
|
-
constructor(
|
|
823
|
+
constructor(context) {
|
|
824
|
+
const {
|
|
825
|
+
field,
|
|
826
|
+
path
|
|
827
|
+
} = context;
|
|
312
828
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
313
829
|
const self = this;
|
|
314
|
-
this[SOURCE] = {
|
|
315
|
-
...data
|
|
316
|
-
};
|
|
830
|
+
this[SOURCE] = Object.assign({}, context.value);
|
|
317
831
|
const signals = withSignalStore(this);
|
|
318
832
|
const _SIGNAL = this[OBJECT_SIGNAL] = entangleSignal(signals, this, OBJECT_SIGNAL, undefined);
|
|
319
|
-
this[
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
833
|
+
this[Context] = context;
|
|
834
|
+
const identifier = context.resourceKey;
|
|
835
|
+
const {
|
|
836
|
+
cache,
|
|
837
|
+
schema
|
|
838
|
+
} = context.store;
|
|
323
839
|
|
|
324
840
|
// prettier-ignore
|
|
325
|
-
const extensions = !legacy ? null : schema.CAUTION_MEGA_DANGER_ZONE_objectExtensions(field);
|
|
841
|
+
const extensions = !context.legacy ? null : schema.CAUTION_MEGA_DANGER_ZONE_objectExtensions(field, null);
|
|
326
842
|
const proxy = new Proxy(this[SOURCE], {
|
|
327
843
|
ownKeys() {
|
|
328
844
|
return Object.keys(self[SOURCE]);
|
|
@@ -332,7 +848,7 @@ class ManagedObject {
|
|
|
332
848
|
},
|
|
333
849
|
getOwnPropertyDescriptor(target, prop) {
|
|
334
850
|
return {
|
|
335
|
-
writable: editable,
|
|
851
|
+
writable: context.editable,
|
|
336
852
|
enumerable: true,
|
|
337
853
|
configurable: true
|
|
338
854
|
};
|
|
@@ -366,11 +882,9 @@ class ManagedObject {
|
|
|
366
882
|
if (newData && newData !== self[SOURCE]) {
|
|
367
883
|
if (field.type) {
|
|
368
884
|
const transform = schema.transformation(field);
|
|
369
|
-
newData = transform.hydrate(newData, field.options ?? null,
|
|
885
|
+
newData = transform.hydrate(newData, field.options ?? null, context.record);
|
|
370
886
|
}
|
|
371
|
-
self[SOURCE] = {
|
|
372
|
-
...newData
|
|
373
|
-
}; // Add type assertion for newData
|
|
887
|
+
self[SOURCE] = Object.assign({}, newData); // Add type assertion for newData
|
|
374
888
|
}
|
|
375
889
|
}
|
|
376
890
|
|
|
@@ -396,7 +910,7 @@ class ManagedObject {
|
|
|
396
910
|
if (!test) {
|
|
397
911
|
throw new Error(`Cannot set read-only property '${String(prop)}' on ManagedObject`);
|
|
398
912
|
}
|
|
399
|
-
})(editable) : {};
|
|
913
|
+
})(context.editable) : {};
|
|
400
914
|
|
|
401
915
|
// since objects function as dictionaries, we can't defer to schema/data before extensions
|
|
402
916
|
// unless the prop is in the existing data.
|
|
@@ -411,151 +925,29 @@ class ManagedObject {
|
|
|
411
925
|
cache.setAttr(identifier, path, self[SOURCE]);
|
|
412
926
|
} else {
|
|
413
927
|
const transform = schema.transformation(field);
|
|
414
|
-
const val = transform.serialize(self[SOURCE], field.options ?? null,
|
|
928
|
+
const val = transform.serialize(self[SOURCE], field.options ?? null, context.record);
|
|
415
929
|
cache.setAttr(identifier, path, val);
|
|
416
930
|
}
|
|
417
931
|
_SIGNAL.isStale = true;
|
|
418
932
|
return true;
|
|
419
933
|
}
|
|
420
934
|
});
|
|
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;
|
|
935
|
+
return proxy;
|
|
498
936
|
}
|
|
499
|
-
return cacheOptions;
|
|
500
937
|
}
|
|
501
|
-
const ManagedArrayMap = getOrSetGlobal('ManagedArrayMap', new Map());
|
|
502
938
|
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
939
|
function peekManagedObject(record, field) {
|
|
516
940
|
const managedObjectMapForRecord = ManagedObjectMap.get(record);
|
|
517
941
|
if (managedObjectMapForRecord) {
|
|
518
942
|
return managedObjectMapForRecord.get(field.name);
|
|
519
943
|
}
|
|
520
944
|
}
|
|
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) {
|
|
945
|
+
function getObjectField(context) {
|
|
946
|
+
entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
947
|
+
const {
|
|
948
|
+
record,
|
|
949
|
+
field
|
|
950
|
+
} = context;
|
|
559
951
|
const managedObjectMapForRecord = ManagedObjectMap.get(record);
|
|
560
952
|
let managedObject;
|
|
561
953
|
if (managedObjectMapForRecord) {
|
|
@@ -564,7 +956,16 @@ function computeObject(schema, cache, record, identifier, field, path, editable,
|
|
|
564
956
|
if (managedObject) {
|
|
565
957
|
return managedObject;
|
|
566
958
|
} else {
|
|
567
|
-
|
|
959
|
+
const {
|
|
960
|
+
store,
|
|
961
|
+
resourceKey,
|
|
962
|
+
path
|
|
963
|
+
} = context;
|
|
964
|
+
const {
|
|
965
|
+
cache,
|
|
966
|
+
schema
|
|
967
|
+
} = store;
|
|
968
|
+
let rawValue = context.editable ? cache.getAttr(resourceKey, path) : cache.getRemoteAttr(resourceKey, path);
|
|
568
969
|
if (!rawValue) {
|
|
569
970
|
return null;
|
|
570
971
|
}
|
|
@@ -572,7 +973,18 @@ function computeObject(schema, cache, record, identifier, field, path, editable,
|
|
|
572
973
|
const transform = schema.transformation(field);
|
|
573
974
|
rawValue = transform.hydrate(rawValue, field.options ?? null, record);
|
|
574
975
|
}
|
|
575
|
-
managedObject = new ManagedObject(
|
|
976
|
+
managedObject = new ManagedObject({
|
|
977
|
+
store,
|
|
978
|
+
resourceKey,
|
|
979
|
+
modeName: context.modeName,
|
|
980
|
+
legacy: context.legacy,
|
|
981
|
+
editable: context.editable,
|
|
982
|
+
path,
|
|
983
|
+
field,
|
|
984
|
+
record,
|
|
985
|
+
signals: context.signals,
|
|
986
|
+
value: rawValue
|
|
987
|
+
});
|
|
576
988
|
if (!managedObjectMapForRecord) {
|
|
577
989
|
ManagedObjectMap.set(record, new Map([[field.name, managedObject]]));
|
|
578
990
|
} else {
|
|
@@ -581,48 +993,64 @@ function computeObject(schema, cache, record, identifier, field, path, editable,
|
|
|
581
993
|
}
|
|
582
994
|
return managedObject;
|
|
583
995
|
}
|
|
584
|
-
function
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
996
|
+
function setObjectField(context) {
|
|
997
|
+
const {
|
|
998
|
+
field,
|
|
999
|
+
value,
|
|
1000
|
+
record
|
|
1001
|
+
} = context;
|
|
1002
|
+
const {
|
|
1003
|
+
cache,
|
|
1004
|
+
schema
|
|
1005
|
+
} = context.store;
|
|
1006
|
+
if (!field.type) {
|
|
1007
|
+
let newValue = value;
|
|
1008
|
+
if (value !== null) {
|
|
1009
|
+
newValue = {
|
|
1010
|
+
...value
|
|
1011
|
+
};
|
|
1012
|
+
} else {
|
|
1013
|
+
ManagedObjectMap.delete(record);
|
|
596
1014
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
[
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
ManagedObjectMap.set(record, new Map([[field.name, schemaObject]]));
|
|
605
|
-
} else {
|
|
606
|
-
schemaObjectMapForRecord.set(field.name, schemaObject);
|
|
1015
|
+
cache.setAttr(context.resourceKey, context.path, newValue);
|
|
1016
|
+
const peeked = peekManagedObject(record, field);
|
|
1017
|
+
if (peeked) {
|
|
1018
|
+
const objSignal = peeked[OBJECT_SIGNAL];
|
|
1019
|
+
objSignal.isStale = true;
|
|
1020
|
+
}
|
|
1021
|
+
return true;
|
|
607
1022
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1023
|
+
const transform = schema.transformation(field);
|
|
1024
|
+
const rawValue = transform.serialize({
|
|
1025
|
+
...value
|
|
1026
|
+
}, field.options ?? null, record);
|
|
1027
|
+
cache.setAttr(context.resourceKey, context.path, rawValue);
|
|
1028
|
+
const peeked = peekManagedObject(record, field);
|
|
1029
|
+
if (peeked) {
|
|
1030
|
+
const objSignal = peeked[OBJECT_SIGNAL];
|
|
1031
|
+
objSignal.isStale = true;
|
|
1032
|
+
}
|
|
1033
|
+
return true;
|
|
615
1034
|
}
|
|
1035
|
+
|
|
616
1036
|
// TODO probably this should just be a Document
|
|
617
1037
|
// but its separate until we work out the lid situation
|
|
618
1038
|
class ResourceRelationship {
|
|
619
|
-
constructor(
|
|
620
|
-
const
|
|
1039
|
+
constructor(context) {
|
|
1040
|
+
const {
|
|
1041
|
+
store,
|
|
1042
|
+
resourceKey
|
|
1043
|
+
} = context;
|
|
1044
|
+
const {
|
|
1045
|
+
cache
|
|
1046
|
+
} = store;
|
|
1047
|
+
const name = getFieldCacheKeyStrict(context.field);
|
|
1048
|
+
const rawValue = context.editable ? cache.getRelationship(resourceKey, name) : cache.getRemoteRelationship(resourceKey, name);
|
|
621
1049
|
|
|
622
1050
|
// TODO setup true lids for relationship documents
|
|
623
1051
|
// @ts-expect-error we need to give relationship documents a lid
|
|
624
1052
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
625
|
-
this.lid = rawValue.lid ?? rawValue.links?.self ?? `relationship:${
|
|
1053
|
+
this.lid = rawValue.lid ?? rawValue.links?.self ?? `relationship:${resourceKey.lid}.${name}`;
|
|
626
1054
|
this.data = rawValue.data ? store.peekRecord(rawValue.data) : null;
|
|
627
1055
|
this.name = name;
|
|
628
1056
|
if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
|
|
@@ -632,19 +1060,18 @@ class ResourceRelationship {
|
|
|
632
1060
|
this.links = rawValue.links ?? {};
|
|
633
1061
|
this.meta = rawValue.meta ?? {};
|
|
634
1062
|
}
|
|
635
|
-
this[
|
|
636
|
-
this[Parent] = parent;
|
|
1063
|
+
this[Context] = context;
|
|
637
1064
|
}
|
|
638
1065
|
fetch(options) {
|
|
639
1066
|
const url = options?.url ?? getHref(this.links.related) ?? getHref(this.links.self) ?? null;
|
|
640
1067
|
if (!url) {
|
|
641
|
-
throw new Error(`Cannot ${options?.method ?? 'fetch'} ${this[
|
|
1068
|
+
throw new Error(`Cannot ${options?.method ?? 'fetch'} ${this[Context].resourceKey.type}.${String(this.name)} because it has no related link`);
|
|
642
1069
|
}
|
|
643
1070
|
const request = Object.assign({
|
|
644
1071
|
url,
|
|
645
1072
|
method: 'GET'
|
|
646
1073
|
}, options);
|
|
647
|
-
return this[
|
|
1074
|
+
return this[Context].store.request(request);
|
|
648
1075
|
}
|
|
649
1076
|
}
|
|
650
1077
|
defineSignal(ResourceRelationship.prototype, 'data', null);
|
|
@@ -659,62 +1086,276 @@ function getHref(link) {
|
|
|
659
1086
|
}
|
|
660
1087
|
return link.href;
|
|
661
1088
|
}
|
|
662
|
-
function
|
|
663
|
-
|
|
664
|
-
|
|
1089
|
+
function getResourceField(context) {
|
|
1090
|
+
entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
1091
|
+
return new ResourceRelationship(context);
|
|
1092
|
+
}
|
|
1093
|
+
function setResourceField(context) {
|
|
1094
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1095
|
+
{
|
|
1096
|
+
throw new Error(`setting resource relationships is not yet supported`);
|
|
1097
|
+
}
|
|
1098
|
+
})() : {};
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
function setSchemaArrayField(context) {
|
|
1102
|
+
const arrayValue = context.value?.slice();
|
|
1103
|
+
const fieldSignal = peekInternalSignal(context.signals, context.path.at(-1));
|
|
1104
|
+
const peeked = fieldSignal?.value;
|
|
1105
|
+
context.store.cache.setAttr(context.resourceKey, context.path, arrayValue);
|
|
1106
|
+
if (peeked) {
|
|
1107
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1108
|
+
if (!test) {
|
|
1109
|
+
throw new Error(`Expected the peekManagedArray for ${context.field.kind} to return a ManagedArray`);
|
|
1110
|
+
}
|
|
1111
|
+
})(ARRAY_SIGNAL in peeked) : {};
|
|
1112
|
+
const arrSignal = peeked[ARRAY_SIGNAL];
|
|
1113
|
+
arrSignal.isStale = true;
|
|
1114
|
+
if (!Array.isArray(arrayValue)) {
|
|
1115
|
+
fieldSignal.value = null;
|
|
1116
|
+
}
|
|
665
1117
|
}
|
|
666
|
-
return
|
|
1118
|
+
return true;
|
|
667
1119
|
}
|
|
668
|
-
function
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
1120
|
+
function getSchemaObjectField(context) {
|
|
1121
|
+
const signal = entangleSignal(context.signals, context.record, context.path.at(-1), null);
|
|
1122
|
+
const {
|
|
1123
|
+
store,
|
|
1124
|
+
resourceKey,
|
|
1125
|
+
path,
|
|
1126
|
+
field
|
|
1127
|
+
} = context;
|
|
1128
|
+
const {
|
|
1129
|
+
cache
|
|
1130
|
+
} = store;
|
|
1131
|
+
let rawValue = context.editable ? cache.getAttr(resourceKey, path) : cache.getRemoteAttr(resourceKey, path);
|
|
1132
|
+
if (!rawValue && !field.options?.polymorphic && field.options?.defaultValue) {
|
|
1133
|
+
rawValue = {};
|
|
1134
|
+
}
|
|
1135
|
+
if (!rawValue) {
|
|
1136
|
+
if (signal.value) {
|
|
1137
|
+
const value = signal.value;
|
|
1138
|
+
// TODO if we had idle scheduling this should be done there.
|
|
1139
|
+
void Promise.resolve().then(() => {
|
|
1140
|
+
value.value[Destroy]();
|
|
1141
|
+
});
|
|
1142
|
+
signal.value = null;
|
|
1143
|
+
}
|
|
1144
|
+
return null;
|
|
679
1145
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1146
|
+
const {
|
|
1147
|
+
schema
|
|
1148
|
+
} = store;
|
|
1149
|
+
let objectType;
|
|
1150
|
+
if (field.options?.polymorphic) {
|
|
1151
|
+
const typePath = field.options.type ?? 'type';
|
|
1152
|
+
// if we are polymorphic, then context.field.options.type will
|
|
1153
|
+
// either specify a path on the rawValue to use as the type, defaulting to "type" or
|
|
1154
|
+
// the special string "@hash" which tells us to treat field.type as a hashFn name with which
|
|
1155
|
+
// to calc the type.
|
|
1156
|
+
if (typePath === '@hash') {
|
|
1157
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1158
|
+
if (!test) {
|
|
1159
|
+
throw new Error(`Expected the field to define a hashFn as its type`);
|
|
1160
|
+
}
|
|
1161
|
+
})(field.type) : {};
|
|
1162
|
+
const hashFn = schema.hashFn({
|
|
1163
|
+
type: field.type
|
|
1164
|
+
});
|
|
1165
|
+
// TODO consider if there are better options and name args we could provide.
|
|
1166
|
+
objectType = hashFn(rawValue, null, null);
|
|
1167
|
+
} else {
|
|
1168
|
+
objectType = rawValue[typePath];
|
|
1169
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1170
|
+
if (!test) {
|
|
1171
|
+
throw new Error(`Expected the type path for the field to be a value on the raw object`);
|
|
1172
|
+
}
|
|
1173
|
+
})(typePath && objectType && typeof objectType === 'string') : {};
|
|
686
1174
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
|
707
|
-
});
|
|
708
|
-
if (!managedArrayMapForRecord) {
|
|
709
|
-
ManagedArrayMap.set(record, new Map([[field.name, managedArray]]));
|
|
1175
|
+
} else {
|
|
1176
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1177
|
+
if (!test) {
|
|
1178
|
+
throw new Error(`A non-polymorphic SchemaObjectField must provide a SchemaObject type in its definition`);
|
|
1179
|
+
}
|
|
1180
|
+
})(field.type) : {};
|
|
1181
|
+
objectType = field.type;
|
|
1182
|
+
}
|
|
1183
|
+
const hashField = schema.resource({
|
|
1184
|
+
type: objectType
|
|
1185
|
+
}).identity;
|
|
1186
|
+
const identity = hashField ? schema.hashFn(hashField)(rawValue, hashField.options ?? null, hashField.name) : field.name;
|
|
1187
|
+
const cachedSchemaObject = signal.value;
|
|
1188
|
+
if (cachedSchemaObject) {
|
|
1189
|
+
if (cachedSchemaObject.type === objectType && cachedSchemaObject.identity === identity) {
|
|
1190
|
+
return cachedSchemaObject.value;
|
|
710
1191
|
} else {
|
|
711
|
-
|
|
1192
|
+
// TODO if we had idle scheduling this should be done there.
|
|
1193
|
+
void Promise.resolve().then(() => {
|
|
1194
|
+
cachedSchemaObject.value[Destroy]();
|
|
1195
|
+
});
|
|
712
1196
|
}
|
|
713
1197
|
}
|
|
714
|
-
|
|
1198
|
+
const schemaObject = new ReactiveResource({
|
|
1199
|
+
store: context.store,
|
|
1200
|
+
resourceKey: context.resourceKey,
|
|
1201
|
+
modeName: context.modeName,
|
|
1202
|
+
legacy: context.legacy,
|
|
1203
|
+
editable: context.editable,
|
|
1204
|
+
path: context.path,
|
|
1205
|
+
field: context.field,
|
|
1206
|
+
value: objectType
|
|
1207
|
+
});
|
|
1208
|
+
signal.value = {
|
|
1209
|
+
type: objectType,
|
|
1210
|
+
identity: identity,
|
|
1211
|
+
value: schemaObject
|
|
1212
|
+
};
|
|
1213
|
+
return schemaObject;
|
|
715
1214
|
}
|
|
1215
|
+
function setSchemaObjectField(context) {
|
|
1216
|
+
const {
|
|
1217
|
+
store,
|
|
1218
|
+
value
|
|
1219
|
+
} = context;
|
|
1220
|
+
let newValue = value;
|
|
1221
|
+
if (value !== null) {
|
|
1222
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1223
|
+
if (!test) {
|
|
1224
|
+
throw new Error(`Expected value to be an object`);
|
|
1225
|
+
}
|
|
1226
|
+
})(typeof value === 'object') : {};
|
|
1227
|
+
newValue = {
|
|
1228
|
+
...value
|
|
1229
|
+
};
|
|
1230
|
+
// FIXME the case of field.type to string here is likely incorrect
|
|
1231
|
+
const schemaFields = store.schema.fields({
|
|
1232
|
+
type: context.field.type
|
|
1233
|
+
});
|
|
1234
|
+
for (const key of Object.keys(newValue)) {
|
|
1235
|
+
if (!schemaFields.has(key)) {
|
|
1236
|
+
throw new Error(`Field ${key} does not exist on schema object ${context.field.type}`);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
} else {
|
|
1240
|
+
ManagedObjectMap.delete(context.record);
|
|
1241
|
+
}
|
|
1242
|
+
store.cache.setAttr(context.resourceKey, context.path, newValue);
|
|
1243
|
+
// const peeked = peekManagedObject(self, field);
|
|
1244
|
+
// if (peeked) {
|
|
1245
|
+
// const objSignal = peeked[OBJECT_SIGNAL];
|
|
1246
|
+
// objSignal.isStale = true;
|
|
1247
|
+
// }
|
|
1248
|
+
return true;
|
|
1249
|
+
}
|
|
1250
|
+
const DefaultMode = {
|
|
1251
|
+
'@hash': {
|
|
1252
|
+
get: getHashField,
|
|
1253
|
+
set: setHashField,
|
|
1254
|
+
mutable: false,
|
|
1255
|
+
enumerable: false,
|
|
1256
|
+
serializable: false
|
|
1257
|
+
},
|
|
1258
|
+
'@id': {
|
|
1259
|
+
get: getIdentityField,
|
|
1260
|
+
set: setIdentityField,
|
|
1261
|
+
mutable: true,
|
|
1262
|
+
enumerable: true,
|
|
1263
|
+
serializable: true
|
|
1264
|
+
},
|
|
1265
|
+
'@local': {
|
|
1266
|
+
get: getLocalField,
|
|
1267
|
+
set: setLocalField,
|
|
1268
|
+
mutable: true,
|
|
1269
|
+
enumerable: false,
|
|
1270
|
+
serializable: false
|
|
1271
|
+
},
|
|
1272
|
+
alias: {
|
|
1273
|
+
get: getAliasField,
|
|
1274
|
+
set: setAliasField,
|
|
1275
|
+
mutable: true,
|
|
1276
|
+
enumerable: true,
|
|
1277
|
+
serializable: false
|
|
1278
|
+
},
|
|
1279
|
+
array: {
|
|
1280
|
+
get: getArrayField,
|
|
1281
|
+
set: setArrayField,
|
|
1282
|
+
mutable: true,
|
|
1283
|
+
enumerable: true,
|
|
1284
|
+
serializable: true
|
|
1285
|
+
},
|
|
1286
|
+
attribute: {
|
|
1287
|
+
get: getAttributeField,
|
|
1288
|
+
set: setAttributeField,
|
|
1289
|
+
mutable: true,
|
|
1290
|
+
enumerable: true,
|
|
1291
|
+
serializable: true
|
|
1292
|
+
},
|
|
1293
|
+
belongsTo: {
|
|
1294
|
+
get: getBelongsToField,
|
|
1295
|
+
set: setBelongsToField,
|
|
1296
|
+
mutable: true,
|
|
1297
|
+
enumerable: true,
|
|
1298
|
+
serializable: true
|
|
1299
|
+
},
|
|
1300
|
+
collection: {
|
|
1301
|
+
get: getCollectionField,
|
|
1302
|
+
set: setCollectionField,
|
|
1303
|
+
mutable: true,
|
|
1304
|
+
enumerable: true,
|
|
1305
|
+
serializable: true
|
|
1306
|
+
},
|
|
1307
|
+
derived: {
|
|
1308
|
+
get: getDerivedField,
|
|
1309
|
+
set: setDerivedField,
|
|
1310
|
+
mutable: true,
|
|
1311
|
+
enumerable: true,
|
|
1312
|
+
serializable: false
|
|
1313
|
+
},
|
|
1314
|
+
field: {
|
|
1315
|
+
get: getGenericField,
|
|
1316
|
+
set: setGenericField,
|
|
1317
|
+
mutable: true,
|
|
1318
|
+
enumerable: true,
|
|
1319
|
+
serializable: true
|
|
1320
|
+
},
|
|
1321
|
+
hasMany: {
|
|
1322
|
+
get: getHasManyField,
|
|
1323
|
+
set: setHasManyField,
|
|
1324
|
+
mutable: true,
|
|
1325
|
+
enumerable: true,
|
|
1326
|
+
serializable: true
|
|
1327
|
+
},
|
|
1328
|
+
object: {
|
|
1329
|
+
get: getObjectField,
|
|
1330
|
+
set: setObjectField,
|
|
1331
|
+
mutable: true,
|
|
1332
|
+
enumerable: true,
|
|
1333
|
+
serializable: true
|
|
1334
|
+
},
|
|
1335
|
+
resource: {
|
|
1336
|
+
get: getResourceField,
|
|
1337
|
+
set: setResourceField,
|
|
1338
|
+
mutable: true,
|
|
1339
|
+
enumerable: true,
|
|
1340
|
+
serializable: true
|
|
1341
|
+
},
|
|
1342
|
+
'schema-array': {
|
|
1343
|
+
get: getArrayField,
|
|
1344
|
+
set: setSchemaArrayField,
|
|
1345
|
+
mutable: true,
|
|
1346
|
+
enumerable: true,
|
|
1347
|
+
serializable: true
|
|
1348
|
+
},
|
|
1349
|
+
'schema-object': {
|
|
1350
|
+
get: getSchemaObjectField,
|
|
1351
|
+
set: setSchemaObjectField,
|
|
1352
|
+
mutable: true,
|
|
1353
|
+
enumerable: true,
|
|
1354
|
+
serializable: true
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
716
1357
|
const IgnoredGlobalFields = new Set(['length', 'nodeType', 'then', 'setInterval', 'document', STRUCTURED]);
|
|
717
|
-
const symbolList = [Destroy, RecordStore,
|
|
1358
|
+
const symbolList = [Context, Destroy, RecordStore, Checkout];
|
|
718
1359
|
const RecordSymbols = new Set(symbolList);
|
|
719
1360
|
function isPathMatch(a, b) {
|
|
720
1361
|
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
@@ -737,27 +1378,34 @@ const Editables = new WeakMap();
|
|
|
737
1378
|
*/
|
|
738
1379
|
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
|
739
1380
|
class ReactiveResource {
|
|
740
|
-
constructor(
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const IS_EDITABLE = this[Editable] = Mode[Editable] ?? false;
|
|
750
|
-
this[Legacy] = Mode[Legacy] ?? false;
|
|
1381
|
+
constructor(context) {
|
|
1382
|
+
const {
|
|
1383
|
+
store
|
|
1384
|
+
} = context;
|
|
1385
|
+
const identifier = context.resourceKey;
|
|
1386
|
+
const embeddedField = context.field;
|
|
1387
|
+
const embeddedPath = context.path;
|
|
1388
|
+
const isEmbedded = context.field !== null;
|
|
1389
|
+
const IS_EDITABLE = context.editable ?? false;
|
|
751
1390
|
const schema = store.schema;
|
|
752
|
-
const
|
|
753
|
-
const
|
|
1391
|
+
const objectType = isEmbedded ? context.value : identifier.type;
|
|
1392
|
+
const ResourceSchema = schema.resource(isEmbedded ? {
|
|
1393
|
+
type: objectType
|
|
1394
|
+
} : identifier);
|
|
1395
|
+
const identityField = ResourceSchema.identity;
|
|
754
1396
|
const BoundFns = new Map();
|
|
755
1397
|
|
|
756
1398
|
// prettier-ignore
|
|
757
|
-
const extensions = !
|
|
758
|
-
this[
|
|
759
|
-
this[
|
|
760
|
-
const fields = isEmbedded ? schema.fields(
|
|
1399
|
+
const extensions = !context.legacy ? null : isEmbedded ? schema.CAUTION_MEGA_DANGER_ZONE_objectExtensions(embeddedField, objectType) : schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(identifier);
|
|
1400
|
+
this[Context] = context;
|
|
1401
|
+
this[RecordStore] = context.store;
|
|
1402
|
+
const fields = isEmbedded ? schema.fields({
|
|
1403
|
+
type: objectType
|
|
1404
|
+
}) : schema.fields(identifier);
|
|
1405
|
+
const method = typeof schema.cacheFields === 'function' ? 'cacheFields' : 'fields';
|
|
1406
|
+
const cacheFields = isEmbedded ? schema[method]({
|
|
1407
|
+
type: objectType
|
|
1408
|
+
}) : schema[method](identifier);
|
|
761
1409
|
const signals = withSignalStore(this);
|
|
762
1410
|
const proxy = new Proxy(this, {
|
|
763
1411
|
ownKeys() {
|
|
@@ -934,74 +1582,64 @@ class ReactiveResource {
|
|
|
934
1582
|
throw new Error(`Alias fields cannot alias '@id' '@local' '@hash' or 'derived' fields`);
|
|
935
1583
|
}
|
|
936
1584
|
})(maybeField.kind !== 'alias' || !['@id', '@local', '@hash', 'derived'].includes(maybeField.options.kind)) : {};
|
|
1585
|
+
/**
|
|
1586
|
+
* Prop Array is the path from a resource to the field including
|
|
1587
|
+
* intermediate "links" on arrays,objects,schema-arrays and schema-objects.
|
|
1588
|
+
*
|
|
1589
|
+
* E.g. in the following
|
|
1590
|
+
*
|
|
1591
|
+
* ```
|
|
1592
|
+
* const user = {
|
|
1593
|
+
* addresses: [{
|
|
1594
|
+
* street: 'Sunset Blvd',
|
|
1595
|
+
* zip: 90210
|
|
1596
|
+
* }]
|
|
1597
|
+
* }
|
|
1598
|
+
* ```
|
|
1599
|
+
*
|
|
1600
|
+
* The propArray for "street" is ['addresses', 0, 'street']
|
|
1601
|
+
*
|
|
1602
|
+
* Prop Array follows the `cache` path to the value, not the ui path.
|
|
1603
|
+
* Thus, if `addresses` has a sourceKey of `user_addresses` and
|
|
1604
|
+
* `zip` has a sourceKey of `zip_code` then the propArray for "zip" is
|
|
1605
|
+
* ['user_addresses', 0, 'zip_code']
|
|
1606
|
+
*/
|
|
937
1607
|
const propArray = isEmbedded ? embeddedPath.slice() : [];
|
|
938
1608
|
// we use the field.name instead of prop here because we want to use the cache-path not
|
|
939
1609
|
// the record path.
|
|
940
|
-
|
|
941
|
-
// propArray.
|
|
942
|
-
|
|
1610
|
+
// SAFETY: we lie as string here because if we were to get null
|
|
1611
|
+
// we would be in a field kind that won't use the propArray below.
|
|
1612
|
+
const fieldCacheKey = getFieldCacheKey(field);
|
|
1613
|
+
propArray.push(fieldCacheKey);
|
|
943
1614
|
switch (field.kind) {
|
|
944
1615
|
case '@id':
|
|
945
|
-
entangleSignal(signals, receiver, '@identity', null);
|
|
946
|
-
return identifier.id;
|
|
947
1616
|
case '@hash':
|
|
948
|
-
// TODO pass actual cache value not {}
|
|
949
|
-
return schema.hashFn(field)({}, field.options ?? null, field.name ?? null);
|
|
950
1617
|
case '@local':
|
|
951
|
-
|
|
952
|
-
return computeLocal(receiver, field, prop);
|
|
953
|
-
}
|
|
1618
|
+
case 'derived':
|
|
954
1619
|
case 'field':
|
|
955
|
-
entangleSignal(signals, receiver, field.name, null);
|
|
956
|
-
return computeField(schema, cache, target, identifier, field, propArray, IS_EDITABLE);
|
|
957
1620
|
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
1621
|
case 'schema-array':
|
|
966
1622
|
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
1623
|
case 'schema-object':
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
return computeSchemaObject(store, cache, target, identifier, field, propArray, Mode[Legacy], Mode[Editable]);
|
|
1624
|
+
case 'object':
|
|
1625
|
+
case 'resource':
|
|
976
1626
|
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
1627
|
case 'hasMany':
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1628
|
+
case 'collection':
|
|
1629
|
+
return DefaultMode[field.kind].get({
|
|
1630
|
+
store,
|
|
1631
|
+
resourceKey: identifier,
|
|
1632
|
+
modeName: context.modeName,
|
|
1633
|
+
legacy: context.legacy,
|
|
1634
|
+
editable: context.editable,
|
|
1635
|
+
path: propArray,
|
|
1636
|
+
field: field,
|
|
1637
|
+
record: receiver,
|
|
1638
|
+
signals,
|
|
1639
|
+
value: null
|
|
1640
|
+
});
|
|
1003
1641
|
default:
|
|
1004
|
-
|
|
1642
|
+
assertNeverField(identifier, field, propArray);
|
|
1005
1643
|
}
|
|
1006
1644
|
},
|
|
1007
1645
|
set(target, prop, value, receiver) {
|
|
@@ -1028,203 +1666,64 @@ class ReactiveResource {
|
|
|
1028
1666
|
throw new Error(`Alias fields cannot alias '@id' '@local' '@hash' or 'derived' fields`);
|
|
1029
1667
|
}
|
|
1030
1668
|
})(maybeField.kind !== 'alias' || !['@id', '@local', '@hash', 'derived'].includes(maybeField.options.kind)) : {};
|
|
1669
|
+
/**
|
|
1670
|
+
* Prop Array is the path from a resource to the field including
|
|
1671
|
+
* intermediate "links" on arrays,objects,schema-arrays and schema-objects.
|
|
1672
|
+
*
|
|
1673
|
+
* E.g. in the following
|
|
1674
|
+
*
|
|
1675
|
+
* ```
|
|
1676
|
+
* const user = {
|
|
1677
|
+
* addresses: [{
|
|
1678
|
+
* street: 'Sunset Blvd',
|
|
1679
|
+
* zip: 90210
|
|
1680
|
+
* }]
|
|
1681
|
+
* }
|
|
1682
|
+
* ```
|
|
1683
|
+
*
|
|
1684
|
+
* The propArray for "street" is ['addresses', 0, 'street']
|
|
1685
|
+
*
|
|
1686
|
+
* Prop Array follows the `cache` path to the value, not the ui path.
|
|
1687
|
+
* Thus, if `addresses` has a sourceKey of `user_addresses` and
|
|
1688
|
+
* `zip` has a sourceKey of `zip_code` then the propArray for "zip" is
|
|
1689
|
+
* ['user_addresses', 0, 'zip_code']
|
|
1690
|
+
*/
|
|
1031
1691
|
const propArray = isEmbedded ? embeddedPath.slice() : [];
|
|
1032
1692
|
// we use the field.name instead of prop here because we want to use the cache-path not
|
|
1033
1693
|
// the record path.
|
|
1034
|
-
|
|
1035
|
-
// propArray.
|
|
1036
|
-
|
|
1694
|
+
// SAFETY: we lie as string here because if we were to get null
|
|
1695
|
+
// we would be in a field kind that won't use the propArray below.
|
|
1696
|
+
const fieldCacheKey = getFieldCacheKey(field);
|
|
1697
|
+
propArray.push(fieldCacheKey);
|
|
1037
1698
|
switch (field.kind) {
|
|
1038
1699
|
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
|
-
}
|
|
1700
|
+
case '@hash':
|
|
1058
1701
|
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
1702
|
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
1703
|
case 'attribute':
|
|
1079
|
-
|
|
1080
|
-
cache.setAttr(identifier, propArray, value);
|
|
1081
|
-
return true;
|
|
1082
|
-
}
|
|
1704
|
+
case 'derived':
|
|
1083
1705
|
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
1706
|
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
1707
|
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
|
-
}
|
|
1708
|
+
case 'object':
|
|
1709
|
+
case 'resource':
|
|
1205
1710
|
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
1711
|
case 'hasMany':
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1712
|
+
case 'collection':
|
|
1713
|
+
return DefaultMode[field.kind].set({
|
|
1714
|
+
store,
|
|
1715
|
+
resourceKey: identifier,
|
|
1716
|
+
modeName: context.modeName,
|
|
1717
|
+
legacy: context.legacy,
|
|
1718
|
+
editable: context.editable,
|
|
1719
|
+
path: propArray,
|
|
1720
|
+
field: field,
|
|
1721
|
+
record: receiver,
|
|
1722
|
+
signals,
|
|
1723
|
+
value
|
|
1724
|
+
});
|
|
1226
1725
|
default:
|
|
1227
|
-
|
|
1726
|
+
return assertNeverField(identifier, field, propArray);
|
|
1228
1727
|
}
|
|
1229
1728
|
}
|
|
1230
1729
|
});
|
|
@@ -1255,25 +1754,25 @@ class ReactiveResource {
|
|
|
1255
1754
|
// TODO we should likely handle this notification here
|
|
1256
1755
|
// also we should add a LOGGING flag
|
|
1257
1756
|
// eslint-disable-next-line no-console
|
|
1258
|
-
console.warn(`Notification unhandled for ${key.join(',')} on ${identifier.type}`,
|
|
1757
|
+
console.warn(`Notification unhandled for ${key.join(',')} on ${identifier.type}`, proxy);
|
|
1259
1758
|
return;
|
|
1260
1759
|
}
|
|
1261
1760
|
|
|
1262
1761
|
// TODO we should add a LOGGING flag
|
|
1263
|
-
// console.log(`Deep notification skipped for ${key.join('.')} on ${identifier.type}`,
|
|
1762
|
+
// console.log(`Deep notification skipped for ${key.join('.')} on ${identifier.type}`, proxy);
|
|
1264
1763
|
// deep notify the key path
|
|
1265
1764
|
} else {
|
|
1266
1765
|
if (isEmbedded) return; // base paths never apply to embedded records
|
|
1267
1766
|
|
|
1268
1767
|
// TODO determine what LOGGING flag to wrap this in if any
|
|
1269
|
-
// console.log(`Notification for ${key} on ${identifier.type}`,
|
|
1768
|
+
// console.log(`Notification for ${key} on ${identifier.type}`, proxy);
|
|
1270
1769
|
const signal = signals.get(key);
|
|
1271
1770
|
if (signal) {
|
|
1272
1771
|
notifyInternalSignal(signal);
|
|
1273
1772
|
}
|
|
1274
|
-
const field =
|
|
1773
|
+
const field = cacheFields.get(key);
|
|
1275
1774
|
if (field?.kind === 'array' || field?.kind === 'schema-array') {
|
|
1276
|
-
const peeked =
|
|
1775
|
+
const peeked = signal?.value;
|
|
1277
1776
|
if (peeked) {
|
|
1278
1777
|
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1279
1778
|
if (!test) {
|
|
@@ -1285,7 +1784,7 @@ class ReactiveResource {
|
|
|
1285
1784
|
}
|
|
1286
1785
|
}
|
|
1287
1786
|
if (field?.kind === 'object') {
|
|
1288
|
-
const peeked = peekManagedObject(
|
|
1787
|
+
const peeked = peekManagedObject(proxy, field);
|
|
1289
1788
|
if (peeked) {
|
|
1290
1789
|
const objSignal = peeked[OBJECT_SIGNAL];
|
|
1291
1790
|
notifyInternalSignal(objSignal);
|
|
@@ -1299,15 +1798,15 @@ class ReactiveResource {
|
|
|
1299
1798
|
if (Array.isArray(key)) ;else {
|
|
1300
1799
|
if (isEmbedded) return; // base paths never apply to embedded records
|
|
1301
1800
|
|
|
1302
|
-
const field =
|
|
1801
|
+
const field = cacheFields.get(key);
|
|
1303
1802
|
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1304
1803
|
if (!test) {
|
|
1305
|
-
throw new Error(`Expected
|
|
1804
|
+
throw new Error(`Expected relationship ${key} to be the name of a field`);
|
|
1306
1805
|
}
|
|
1307
1806
|
})(field) : {};
|
|
1308
1807
|
if (field.kind === 'belongsTo') {
|
|
1309
1808
|
// TODO determine what LOGGING flag to wrap this in if any
|
|
1310
|
-
// console.log(`Notification for ${key} on ${identifier.type}`,
|
|
1809
|
+
// console.log(`Notification for ${key} on ${identifier.type}`, proxy);
|
|
1311
1810
|
const signal = signals.get(key);
|
|
1312
1811
|
if (signal) {
|
|
1313
1812
|
notifyInternalSignal(signal);
|
|
@@ -1315,9 +1814,12 @@ class ReactiveResource {
|
|
|
1315
1814
|
// FIXME
|
|
1316
1815
|
} else if (field.kind === 'resource') ;else if (field.kind === 'hasMany') {
|
|
1317
1816
|
if (field.options.linksMode) {
|
|
1318
|
-
const
|
|
1319
|
-
if (
|
|
1320
|
-
|
|
1817
|
+
const signal = signals.get(key);
|
|
1818
|
+
if (signal) {
|
|
1819
|
+
const peeked = signal.value;
|
|
1820
|
+
if (peeked) {
|
|
1821
|
+
notifyInternalSignal(peeked[ARRAY_SIGNAL]);
|
|
1822
|
+
}
|
|
1321
1823
|
}
|
|
1322
1824
|
return;
|
|
1323
1825
|
}
|
|
@@ -1325,7 +1827,7 @@ class ReactiveResource {
|
|
|
1325
1827
|
if (!test) {
|
|
1326
1828
|
throw new Error(`Can only use hasMany fields when the resource is in legacy mode`);
|
|
1327
1829
|
}
|
|
1328
|
-
})(
|
|
1830
|
+
})(context.legacy) : {};
|
|
1329
1831
|
if (schema._kind('@legacy', 'hasMany').notify(store, proxy, identifier, field)) {
|
|
1330
1832
|
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1331
1833
|
if (!test) {
|
|
@@ -1367,35 +1869,52 @@ class ReactiveResource {
|
|
|
1367
1869
|
}
|
|
1368
1870
|
}
|
|
1369
1871
|
function _CHECKOUT(record) {
|
|
1872
|
+
const context = record[Context];
|
|
1873
|
+
|
|
1370
1874
|
// IF we are already the editable record, throw an error
|
|
1371
|
-
if (
|
|
1875
|
+
if (context.editable) {
|
|
1372
1876
|
throw new Error(`Cannot checkout an already editable record`);
|
|
1373
1877
|
}
|
|
1374
1878
|
const editable = Editables.get(record);
|
|
1375
1879
|
if (editable) {
|
|
1376
1880
|
return Promise.resolve(editable);
|
|
1377
1881
|
}
|
|
1378
|
-
const
|
|
1379
|
-
const embeddedPath = record[EmbeddedPath];
|
|
1380
|
-
const isEmbedded = embeddedType !== null && embeddedPath !== null;
|
|
1882
|
+
const isEmbedded = context.field !== null && context.path !== null;
|
|
1381
1883
|
if (isEmbedded) {
|
|
1382
1884
|
throw new Error(`Cannot checkout an embedded record (yet)`);
|
|
1383
1885
|
}
|
|
1384
|
-
const editableRecord = new ReactiveResource(
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1886
|
+
const editableRecord = new ReactiveResource({
|
|
1887
|
+
store: context.store,
|
|
1888
|
+
resourceKey: context.resourceKey,
|
|
1889
|
+
modeName: context.legacy ? 'legacy' : 'polaris',
|
|
1890
|
+
legacy: context.legacy,
|
|
1891
|
+
editable: true,
|
|
1892
|
+
path: null,
|
|
1893
|
+
field: null,
|
|
1894
|
+
value: null
|
|
1895
|
+
});
|
|
1388
1896
|
setRecordIdentifier(editableRecord, recordIdentifierFor(record));
|
|
1389
1897
|
return Promise.resolve(editableRecord);
|
|
1390
1898
|
}
|
|
1391
1899
|
function _DESTROY(record) {
|
|
1392
|
-
if (record[
|
|
1900
|
+
if (record[Context].legacy) {
|
|
1393
1901
|
// @ts-expect-error
|
|
1394
1902
|
record.isDestroying = true;
|
|
1395
1903
|
// @ts-expect-error
|
|
1396
1904
|
record.isDestroyed = true;
|
|
1397
1905
|
}
|
|
1398
|
-
record[
|
|
1906
|
+
record[Context].store.notifications.unsubscribe(record.___notifications);
|
|
1907
|
+
|
|
1908
|
+
// FIXME we need a way to also unsubscribe all SchemaObjects when the primary
|
|
1909
|
+
// resource is destroyed.
|
|
1910
|
+
}
|
|
1911
|
+
function assertNeverField(identifier, field, path) {
|
|
1912
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1913
|
+
{
|
|
1914
|
+
throw new Error(`Cannot use unknown field kind ${field.kind} on <${identifier.type}>.${Array.isArray(path) ? path.join('.') : path}`);
|
|
1915
|
+
}
|
|
1916
|
+
})() : {};
|
|
1917
|
+
return false;
|
|
1399
1918
|
}
|
|
1400
1919
|
function instantiateRecord(store, identifier, createArgs) {
|
|
1401
1920
|
const schema = store.schema;
|
|
@@ -1405,11 +1924,17 @@ function instantiateRecord(store, identifier, createArgs) {
|
|
|
1405
1924
|
throw new Error(`Expected a resource schema`);
|
|
1406
1925
|
}
|
|
1407
1926
|
})(isResourceSchema(resourceSchema)) : {};
|
|
1408
|
-
const
|
|
1409
|
-
const
|
|
1410
|
-
const record = new ReactiveResource(
|
|
1411
|
-
|
|
1412
|
-
|
|
1927
|
+
const legacy = resourceSchema?.legacy ?? false;
|
|
1928
|
+
const editable = legacy || store.cache.isNew(identifier);
|
|
1929
|
+
const record = new ReactiveResource({
|
|
1930
|
+
store,
|
|
1931
|
+
resourceKey: identifier,
|
|
1932
|
+
modeName: legacy ? 'legacy' : 'polaris',
|
|
1933
|
+
legacy: legacy,
|
|
1934
|
+
editable: editable,
|
|
1935
|
+
path: null,
|
|
1936
|
+
field: null,
|
|
1937
|
+
value: null
|
|
1413
1938
|
});
|
|
1414
1939
|
if (createArgs) {
|
|
1415
1940
|
Object.assign(record, createArgs);
|
|
@@ -1546,7 +2071,7 @@ function getExt(extCache, type, extName) {
|
|
|
1546
2071
|
function hasObjectSchema(field) {
|
|
1547
2072
|
return 'kind' in field && (field.kind === 'schema-array' || field.kind === 'schema-object');
|
|
1548
2073
|
}
|
|
1549
|
-
function processExtensions(schema, field, scenario) {
|
|
2074
|
+
function processExtensions(schema, field, scenario, resolvedType) {
|
|
1550
2075
|
// if we're looking up extensions for a resource, there is no
|
|
1551
2076
|
// merging required so if we have no objectExtensions
|
|
1552
2077
|
// we are done.
|
|
@@ -1573,12 +2098,16 @@ function processExtensions(schema, field, scenario) {
|
|
|
1573
2098
|
if (!hasObjectSchema(field)) {
|
|
1574
2099
|
return null;
|
|
1575
2100
|
}
|
|
1576
|
-
return schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(
|
|
2101
|
+
return schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(resolvedType ? {
|
|
2102
|
+
type: resolvedType
|
|
2103
|
+
} : field);
|
|
1577
2104
|
}
|
|
1578
2105
|
|
|
1579
2106
|
// if we have made it here, we have extensions, lets check if there's
|
|
1580
2107
|
// a cached version we can use
|
|
1581
|
-
const baseExtensions = scenario === 'resource' && hasObjectSchema(field) ? schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(field) : scenario === 'object' && hasObjectSchema(field) ? schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(
|
|
2108
|
+
const baseExtensions = scenario === 'resource' && hasObjectSchema(field) ? schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(field) : scenario === 'object' && hasObjectSchema(field) ? schema.CAUTION_MEGA_DANGER_ZONE_resourceExtensions(resolvedType ? {
|
|
2109
|
+
type: resolvedType
|
|
2110
|
+
} : field) : null;
|
|
1582
2111
|
if (!baseExtensions && extensions.length === 1) {
|
|
1583
2112
|
const value = getExt(extCache, type, extensions[0]);
|
|
1584
2113
|
fieldCache[type].set(field, value);
|
|
@@ -1646,7 +2175,8 @@ function withDefaults(schema) {
|
|
|
1646
2175
|
* @public
|
|
1647
2176
|
*/
|
|
1648
2177
|
const fromIdentity = (record, options, key) => {
|
|
1649
|
-
const
|
|
2178
|
+
const context = record[Context];
|
|
2179
|
+
const identifier = context.resourceKey;
|
|
1650
2180
|
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
1651
2181
|
if (!test) {
|
|
1652
2182
|
throw new Error(`Cannot compute @identity for a record without an identifier`);
|
|
@@ -1836,16 +2366,21 @@ class SchemaService {
|
|
|
1836
2366
|
relationships[field.name] = field;
|
|
1837
2367
|
}
|
|
1838
2368
|
}
|
|
2369
|
+
const cacheFields = null;
|
|
1839
2370
|
const traits = new Set(isResourceSchema(schema) ? schema.traits : []);
|
|
1840
2371
|
const finalized = traits.size === 0;
|
|
1841
2372
|
const internalSchema = {
|
|
1842
2373
|
original: schema,
|
|
1843
2374
|
finalized,
|
|
1844
2375
|
fields,
|
|
2376
|
+
cacheFields,
|
|
1845
2377
|
relationships,
|
|
1846
2378
|
attributes,
|
|
1847
2379
|
traits
|
|
1848
2380
|
};
|
|
2381
|
+
if (traits.size === 0) {
|
|
2382
|
+
internalSchema.cacheFields = getCacheFields(internalSchema);
|
|
2383
|
+
}
|
|
1849
2384
|
this._schemas.set(schema.type, internalSchema);
|
|
1850
2385
|
}
|
|
1851
2386
|
|
|
@@ -1902,13 +2437,13 @@ class SchemaService {
|
|
|
1902
2437
|
}
|
|
1903
2438
|
CAUTION_MEGA_DANGER_ZONE_resourceExtensions(resource) {
|
|
1904
2439
|
const schema = this.resource(resource);
|
|
1905
|
-
return processExtensions(this, schema, 'resource');
|
|
2440
|
+
return processExtensions(this, schema, 'resource', null);
|
|
1906
2441
|
}
|
|
1907
|
-
CAUTION_MEGA_DANGER_ZONE_objectExtensions(field) {
|
|
1908
|
-
return processExtensions(this, field, 'object');
|
|
2442
|
+
CAUTION_MEGA_DANGER_ZONE_objectExtensions(field, resolvedType) {
|
|
2443
|
+
return processExtensions(this, field, 'object', resolvedType);
|
|
1909
2444
|
}
|
|
1910
2445
|
CAUTION_MEGA_DANGER_ZONE_arrayExtensions(field) {
|
|
1911
|
-
return processExtensions(this, field, 'array');
|
|
2446
|
+
return processExtensions(this, field, 'array', null);
|
|
1912
2447
|
}
|
|
1913
2448
|
CAUTION_MEGA_DANGER_ZONE_hasExtension(ext) {
|
|
1914
2449
|
return this._extensions[ext.kind].has(ext.name);
|
|
@@ -1976,6 +2511,20 @@ class SchemaService {
|
|
|
1976
2511
|
}
|
|
1977
2512
|
return schema.fields;
|
|
1978
2513
|
}
|
|
2514
|
+
cacheFields({
|
|
2515
|
+
type
|
|
2516
|
+
}) {
|
|
2517
|
+
const schema = this._schemas.get(type);
|
|
2518
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
2519
|
+
if (!test) {
|
|
2520
|
+
throw new Error(`No schema defined for ${type}`);
|
|
2521
|
+
}
|
|
2522
|
+
})(schema) : {};
|
|
2523
|
+
if (!schema.finalized) {
|
|
2524
|
+
finalizeResource(this, schema);
|
|
2525
|
+
}
|
|
2526
|
+
return schema.cacheFields;
|
|
2527
|
+
}
|
|
1979
2528
|
hasResource(resource) {
|
|
1980
2529
|
return this._schemas.has(resource.type);
|
|
1981
2530
|
}
|
|
@@ -2064,8 +2613,27 @@ function finalizeResource(schema, resource) {
|
|
|
2064
2613
|
}
|
|
2065
2614
|
mergeMap(fields, resource.fields);
|
|
2066
2615
|
resource.fields = fields;
|
|
2616
|
+
resource.cacheFields = getCacheFields(resource);
|
|
2067
2617
|
resource.finalized = true;
|
|
2068
2618
|
}
|
|
2619
|
+
function getCacheFields(resource) {
|
|
2620
|
+
const {
|
|
2621
|
+
fields
|
|
2622
|
+
} = resource;
|
|
2623
|
+
const cacheFields = new Map();
|
|
2624
|
+
for (const [key, value] of fields) {
|
|
2625
|
+
if (isNonIdentityCacheableField(value)) {
|
|
2626
|
+
macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
|
|
2627
|
+
if (!test) {
|
|
2628
|
+
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`);
|
|
2629
|
+
}
|
|
2630
|
+
})(!value.sourceKey || value.sourceKey === key || !fields.has(value.sourceKey)) : {};
|
|
2631
|
+
const cacheKey = getFieldCacheKeyStrict(value);
|
|
2632
|
+
cacheFields.set(cacheKey, value);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
return cacheFields;
|
|
2636
|
+
}
|
|
2069
2637
|
function walkTrait(schema, trait, fields, seen, type, debugPath) {
|
|
2070
2638
|
if (seen.has(trait)) {
|
|
2071
2639
|
// if the trait is in the current path, we throw a cycle error in dev.
|