@simtlix/simfinity-js-client 1.1.0 → 3.19.1
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/README.md +36 -5
- package/package.json +18 -6
- package/src/SimfinityClient.js +231 -393
- package/src/inputs.js +106 -0
- package/src/metadata.js +104 -0
- package/src/selection.js +88 -0
- package/types/index.d.ts +91 -0
- package/.claude/worktrees/flamboyant-cartwright/.claude/settings.local.json +0 -18
- package/.claude/worktrees/flamboyant-cartwright/IMPLEMENTATION_PROMPT.md +0 -1137
- package/.claude/worktrees/flamboyant-cartwright/LICENSE +0 -201
- package/.claude/worktrees/flamboyant-cartwright/README.md +0 -491
- package/.claude/worktrees/flamboyant-cartwright/examples/demo.js +0 -402
- package/.claude/worktrees/flamboyant-cartwright/package-lock.json +0 -107
- package/.claude/worktrees/flamboyant-cartwright/package.json +0 -29
- package/.claude/worktrees/flamboyant-cartwright/src/SimfinityClient.js +0 -1398
- package/IMPLEMENTATION_PROMPT.md +0 -1137
- package/examples/demo.js +0 -402
- package/simtlix-simfinity-js-client-1.0.0.tgz +0 -0
- package/simtlix-simfinity-js-client-1.0.1.tgz +0 -0
- package/simtlix-simfinity-js-client-1.0.3.tgz +0 -0
- package/simtlix-simfinity-js-client-1.0.4.tgz +0 -0
- package/simtlix-simfinity-js-client-1.0.5.tgz +0 -0
package/src/SimfinityClient.js
CHANGED
|
@@ -1,69 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
'Use Node.js 18+ (which includes native fetch) or provide a fetch polyfill.'
|
|
5
|
-
);
|
|
6
|
-
}
|
|
7
|
-
const _fetch = globalThis.fetch.bind(globalThis);
|
|
8
|
-
|
|
9
|
-
const INTROSPECTION_QUERY = `
|
|
10
|
-
query IntrospectionQuery {
|
|
11
|
-
__schema {
|
|
12
|
-
queryType { name }
|
|
13
|
-
mutationType { name }
|
|
14
|
-
types {
|
|
15
|
-
name
|
|
16
|
-
kind
|
|
17
|
-
fields {
|
|
18
|
-
name
|
|
19
|
-
type {
|
|
20
|
-
kind name
|
|
21
|
-
ofType {
|
|
22
|
-
kind name
|
|
23
|
-
ofType {
|
|
24
|
-
kind name
|
|
25
|
-
ofType {
|
|
26
|
-
kind name
|
|
27
|
-
ofType {
|
|
28
|
-
kind name
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
args {
|
|
35
|
-
name
|
|
36
|
-
type {
|
|
37
|
-
kind name
|
|
38
|
-
ofType {
|
|
39
|
-
kind name
|
|
40
|
-
ofType {
|
|
41
|
-
kind name
|
|
42
|
-
ofType {
|
|
43
|
-
kind name
|
|
44
|
-
ofType {
|
|
45
|
-
kind name
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
extensions {
|
|
53
|
-
relation {
|
|
54
|
-
displayField
|
|
55
|
-
embedded
|
|
56
|
-
connectionField
|
|
57
|
-
}
|
|
58
|
-
stateMachine
|
|
59
|
-
readOnly
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
enumValues { name }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
`;
|
|
1
|
+
import { normalizeDescriptor, introspectionQuery } from './metadata.js';
|
|
2
|
+
import { SelectionTree } from './selection.js';
|
|
3
|
+
import { transformRootInput, transformDelta } from './inputs.js';
|
|
67
4
|
|
|
68
5
|
function unwrapType(typeRef) {
|
|
69
6
|
let isList = false;
|
|
@@ -106,6 +43,8 @@ function isDateTimeScalar(name) {
|
|
|
106
43
|
|| n === 'isodate' || n === 'graphqldate' || n === 'graphqldatetime';
|
|
107
44
|
}
|
|
108
45
|
|
|
46
|
+
const canAutoSelect = (field) => !field.args?.some((arg) => arg.type.kind === 'NON_NULL' && !arg.hasDefault);
|
|
47
|
+
|
|
109
48
|
function isScalarOrEnum(kind) {
|
|
110
49
|
return kind === 'SCALAR' || kind === 'ENUM';
|
|
111
50
|
}
|
|
@@ -131,77 +70,14 @@ function typeRefToString(typeRef) {
|
|
|
131
70
|
// into a GraphQL selection set string.
|
|
132
71
|
// ---------------------------------------------------------------------------
|
|
133
72
|
|
|
134
|
-
class SelectionTree {
|
|
135
|
-
constructor() {
|
|
136
|
-
this.scalars = new Set();
|
|
137
|
-
this.children = new Map();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
addScalars(fieldNames) {
|
|
141
|
-
for (const name of fieldNames.trim().split(/\s+/)) {
|
|
142
|
-
if (name) this.scalars.add(name);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
addSelection(selectionString) {
|
|
147
|
-
const tokens = selectionString.trim().split(/\s+/);
|
|
148
|
-
let i = 0;
|
|
149
|
-
while (i < tokens.length) {
|
|
150
|
-
const token = tokens[i];
|
|
151
|
-
if (token === '{' || token === '}') { i++; continue; }
|
|
152
|
-
if (i + 1 < tokens.length && tokens[i + 1] === '{') {
|
|
153
|
-
let depth = 1;
|
|
154
|
-
let j = i + 2;
|
|
155
|
-
while (j < tokens.length && depth > 0) {
|
|
156
|
-
if (tokens[j] === '{') depth++;
|
|
157
|
-
else if (tokens[j] === '}') depth--;
|
|
158
|
-
j++;
|
|
159
|
-
}
|
|
160
|
-
const inner = tokens.slice(i + 2, j - 1).join(' ');
|
|
161
|
-
if (!this.children.has(token)) {
|
|
162
|
-
this.children.set(token, new SelectionTree());
|
|
163
|
-
}
|
|
164
|
-
this.children.get(token).addSelection(inner);
|
|
165
|
-
i = j;
|
|
166
|
-
} else {
|
|
167
|
-
this.scalars.add(token);
|
|
168
|
-
i++;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
addNested(dottedPath, fieldNames) {
|
|
174
|
-
const segments = dottedPath.split('.');
|
|
175
|
-
let node = this;
|
|
176
|
-
for (const segment of segments) {
|
|
177
|
-
if (!node.children.has(segment)) {
|
|
178
|
-
node.children.set(segment, new SelectionTree());
|
|
179
|
-
}
|
|
180
|
-
node = node.children.get(segment);
|
|
181
|
-
}
|
|
182
|
-
node.addScalars(fieldNames);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
serialize() {
|
|
186
|
-
const parts = [...this.scalars];
|
|
187
|
-
for (const [name, child] of this.children) {
|
|
188
|
-
const childStr = child.serialize();
|
|
189
|
-
if (childStr) parts.push(`${name} { ${childStr} }`);
|
|
190
|
-
}
|
|
191
|
-
return parts.join(' ');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
isEmpty() {
|
|
195
|
-
return this.scalars.size === 0 && this.children.size === 0;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
73
|
// ---------------------------------------------------------------------------
|
|
200
74
|
// QueryBuilder -- chainable builder returned by SimfinityClient.find()
|
|
201
75
|
// ---------------------------------------------------------------------------
|
|
202
76
|
|
|
203
77
|
class QueryBuilder {
|
|
204
|
-
constructor(client, typeName) {
|
|
78
|
+
constructor(client, typeName, queryInfo = null) {
|
|
79
|
+
this._queryInfo = queryInfo;
|
|
80
|
+
this._collectionArguments = new Map();
|
|
205
81
|
this._client = client;
|
|
206
82
|
this._typeName = typeName;
|
|
207
83
|
this._variables = {};
|
|
@@ -213,7 +89,9 @@ class QueryBuilder {
|
|
|
213
89
|
// -- Filtering -----------------------------------------------------------
|
|
214
90
|
|
|
215
91
|
where(field, operatorOrTerms, value, value2) {
|
|
216
|
-
if (Array.isArray(operatorOrTerms)) {
|
|
92
|
+
if (operatorOrTerms && typeof operatorOrTerms === 'object' && !Array.isArray(operatorOrTerms)) {
|
|
93
|
+
this._variables[field] = { ...operatorOrTerms };
|
|
94
|
+
} else if (Array.isArray(operatorOrTerms)) {
|
|
217
95
|
if (this._variables[field]?.terms) {
|
|
218
96
|
this._variables[field].terms.push(...operatorOrTerms);
|
|
219
97
|
} else {
|
|
@@ -227,6 +105,11 @@ class QueryBuilder {
|
|
|
227
105
|
return this;
|
|
228
106
|
}
|
|
229
107
|
|
|
108
|
+
filter(group) {
|
|
109
|
+
if (!group || typeof group !== 'object' || Array.isArray(group)) throw new Error('filter() expects a QLFilterGroup');
|
|
110
|
+
return this.and([group]);
|
|
111
|
+
}
|
|
112
|
+
|
|
230
113
|
or(groups) {
|
|
231
114
|
if (!Array.isArray(groups)) {
|
|
232
115
|
throw new Error('or() expects an array of QLFilterGroup objects');
|
|
@@ -308,99 +191,28 @@ class QueryBuilder {
|
|
|
308
191
|
|
|
309
192
|
// -- Execution -----------------------------------------------------------
|
|
310
193
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (!this._hasExplicitFields) {
|
|
318
|
-
const autoFields = this._client._getScalarFields(this._typeName);
|
|
319
|
-
this._selectionTree.addScalars(autoFields);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const selectionSet = this._autoSelection || this._selectionTree.serialize();
|
|
323
|
-
if (!selectionSet) {
|
|
324
|
-
throw new Error(`No fields to select for type '${this._typeName}'`);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const varDefs = [];
|
|
328
|
-
const variables = {};
|
|
329
|
-
const argMappings = [];
|
|
330
|
-
|
|
331
|
-
for (const [key, val] of Object.entries(this._variables)) {
|
|
332
|
-
const arg = queryInfo.args.find(a => a.name === key);
|
|
333
|
-
if (arg) {
|
|
334
|
-
varDefs.push(`$${key}: ${typeRefToString(arg.type)}`);
|
|
335
|
-
variables[key] = val;
|
|
336
|
-
argMappings.push(`${key}: $${key}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(', ')})` : '';
|
|
341
|
-
const argsBlock = argMappings.length > 0 ? `(${argMappings.join(', ')})` : '';
|
|
342
|
-
const query = `query${varDefsStr} { ${queryInfo.name}${argsBlock} { ${selectionSet} } }`;
|
|
343
|
-
|
|
344
|
-
const response = await this._client._sendRequest(query, variables);
|
|
345
|
-
if (response.errors && !response.data?.[queryInfo.name]) {
|
|
346
|
-
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
347
|
-
error.graphQLErrors = response.errors;
|
|
348
|
-
throw error;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const result = response.data[queryInfo.name];
|
|
352
|
-
|
|
353
|
-
if (response.extensions) {
|
|
354
|
-
result.__extensions = response.extensions;
|
|
355
|
-
}
|
|
194
|
+
collectionArgs(path, args) {
|
|
195
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) throw new Error('collectionArgs expects an argument object');
|
|
196
|
+
this._collectionArguments.set(path, { ...args }); return this;
|
|
197
|
+
}
|
|
356
198
|
|
|
357
|
-
|
|
199
|
+
async exec() {
|
|
200
|
+
const { data, extensions } = await this.execWithMeta();
|
|
201
|
+
if (data !== null && typeof data === 'object' && extensions) data.__extensions = extensions;
|
|
202
|
+
return data;
|
|
358
203
|
}
|
|
359
204
|
|
|
360
205
|
async execWithMeta() {
|
|
361
|
-
const queryInfo = this._client._findPluralQuery(this._typeName);
|
|
362
|
-
if (!queryInfo) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const selectionSet = this._autoSelection || this._selectionTree.serialize();
|
|
372
|
-
if (!selectionSet) {
|
|
373
|
-
throw new Error(`No fields to select for type '${this._typeName}'`);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const varDefs = [];
|
|
377
|
-
const variables = {};
|
|
378
|
-
const argMappings = [];
|
|
379
|
-
|
|
380
|
-
for (const [key, val] of Object.entries(this._variables)) {
|
|
381
|
-
const arg = queryInfo.args.find(a => a.name === key);
|
|
382
|
-
if (arg) {
|
|
383
|
-
varDefs.push(`$${key}: ${typeRefToString(arg.type)}`);
|
|
384
|
-
variables[key] = val;
|
|
385
|
-
argMappings.push(`${key}: $${key}`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(', ')})` : '';
|
|
390
|
-
const argsBlock = argMappings.length > 0 ? `(${argMappings.join(', ')})` : '';
|
|
391
|
-
const query = `query${varDefsStr} { ${queryInfo.name}${argsBlock} { ${selectionSet} } }`;
|
|
392
|
-
|
|
393
|
-
const response = await this._client._sendRequest(query, variables);
|
|
394
|
-
if (response.errors && !response.data?.[queryInfo.name]) {
|
|
395
|
-
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
396
|
-
error.graphQLErrors = response.errors;
|
|
397
|
-
throw error;
|
|
398
|
-
}
|
|
399
|
-
return {
|
|
400
|
-
data: response.data[queryInfo.name],
|
|
401
|
-
extensions: response.extensions || null,
|
|
402
|
-
};
|
|
206
|
+
const queryInfo = this._queryInfo || this._client._findPluralQuery(this._typeName);
|
|
207
|
+
if (!queryInfo) throw new Error(`No plural query found for type '${this._typeName}'`);
|
|
208
|
+
if (!this._hasExplicitFields) this._selectionTree.addScalars(this._client._getScalarFields(this._typeName));
|
|
209
|
+
const tree = this._autoSelection ? new SelectionTree() : this._selectionTree;
|
|
210
|
+
if (this._autoSelection) tree.addSelection(this._autoSelection);
|
|
211
|
+
const response = await this._client._requestOperation('query', queryInfo, this._variables, tree, this._collectionArguments);
|
|
212
|
+
this._client._checkErrors(response, queryInfo.name, true);
|
|
213
|
+
return { data: response.data?.[queryInfo.name], extensions: response.extensions || null };
|
|
403
214
|
}
|
|
215
|
+
|
|
404
216
|
}
|
|
405
217
|
|
|
406
218
|
// ---------------------------------------------------------------------------
|
|
@@ -428,7 +240,9 @@ class AggregateBuilder {
|
|
|
428
240
|
}
|
|
429
241
|
|
|
430
242
|
where(field, operatorOrTerms, value, value2) {
|
|
431
|
-
if (Array.isArray(operatorOrTerms)) {
|
|
243
|
+
if (operatorOrTerms && typeof operatorOrTerms === 'object' && !Array.isArray(operatorOrTerms)) {
|
|
244
|
+
this._variables[field] = { ...operatorOrTerms };
|
|
245
|
+
} else if (Array.isArray(operatorOrTerms)) {
|
|
432
246
|
if (this._variables[field]?.terms) {
|
|
433
247
|
this._variables[field].terms.push(...operatorOrTerms);
|
|
434
248
|
} else {
|
|
@@ -442,6 +256,11 @@ class AggregateBuilder {
|
|
|
442
256
|
return this;
|
|
443
257
|
}
|
|
444
258
|
|
|
259
|
+
filter(group) {
|
|
260
|
+
if (!group || typeof group !== 'object' || Array.isArray(group)) throw new Error('filter() expects a QLFilterGroup');
|
|
261
|
+
return this.and([group]);
|
|
262
|
+
}
|
|
263
|
+
|
|
445
264
|
or(groups) {
|
|
446
265
|
if (!Array.isArray(groups)) {
|
|
447
266
|
throw new Error('or() expects an array of QLFilterGroup objects');
|
|
@@ -497,29 +316,9 @@ class AggregateBuilder {
|
|
|
497
316
|
facts: this._facts,
|
|
498
317
|
};
|
|
499
318
|
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
const argMappings = [];
|
|
319
|
+
const response = await this._client._requestOperation('query', queryInfo, this._variables, 'groupId facts');
|
|
320
|
+
this._client._checkErrors(response, queryInfo.name);
|
|
503
321
|
|
|
504
|
-
for (const [key, val] of Object.entries(this._variables)) {
|
|
505
|
-
const arg = queryInfo.args.find(a => a.name === key);
|
|
506
|
-
if (arg) {
|
|
507
|
-
varDefs.push(`$${key}: ${typeRefToString(arg.type)}`);
|
|
508
|
-
variables[key] = val;
|
|
509
|
-
argMappings.push(`${key}: $${key}`);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(', ')})` : '';
|
|
514
|
-
const argsBlock = argMappings.length > 0 ? `(${argMappings.join(', ')})` : '';
|
|
515
|
-
const query = `query${varDefsStr} { ${queryInfo.name}${argsBlock} { groupId facts } }`;
|
|
516
|
-
|
|
517
|
-
const response = await this._client._sendRequest(query, variables);
|
|
518
|
-
if (response.errors) {
|
|
519
|
-
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
520
|
-
error.graphQLErrors = response.errors;
|
|
521
|
-
throw error;
|
|
522
|
-
}
|
|
523
322
|
return response.data[queryInfo.name];
|
|
524
323
|
}
|
|
525
324
|
}
|
|
@@ -537,6 +336,14 @@ export default class SimfinityClient {
|
|
|
537
336
|
* perform auth in your app and set tokens inside this callback or a closure it sees.
|
|
538
337
|
*/
|
|
539
338
|
constructor(endpoint, options = {}) {
|
|
339
|
+
if (options.descriptor !== undefined && options.descriptorEndpoint !== undefined) throw new Error('descriptor and descriptorEndpoint are mutually exclusive');
|
|
340
|
+
if (options.introspection !== undefined && !['extended', 'standard'].includes(options.introspection)) throw new Error('Invalid introspection mode');
|
|
341
|
+
this._options = options;
|
|
342
|
+
this._descriptor = null;
|
|
343
|
+
this._fetch = options.fetch || ((...args) => {
|
|
344
|
+
if (typeof globalThis.fetch !== 'function') throw new Error('SimfinityClient requires fetch or options.fetch');
|
|
345
|
+
return globalThis.fetch(...args);
|
|
346
|
+
});
|
|
540
347
|
this._endpoint = endpoint;
|
|
541
348
|
this._types = new Map();
|
|
542
349
|
this._queries = new Map();
|
|
@@ -553,14 +360,49 @@ export default class SimfinityClient {
|
|
|
553
360
|
// -- Initialization ------------------------------------------------------
|
|
554
361
|
|
|
555
362
|
async init() {
|
|
556
|
-
|
|
557
|
-
if (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
363
|
+
let descriptor = this._options.descriptor;
|
|
364
|
+
if (this._options.descriptorEndpoint !== undefined) {
|
|
365
|
+
const headers = {};
|
|
366
|
+
if (this._prepareHeaders) await this._prepareHeaders(headers);
|
|
367
|
+
const response = await this._fetch(this._options.descriptorEndpoint, { method: 'GET', headers });
|
|
368
|
+
if (!response.ok) throw new Error(`Descriptor HTTP request failed (${response.status})`);
|
|
369
|
+
descriptor = await response.json();
|
|
370
|
+
}
|
|
371
|
+
let schema;
|
|
372
|
+
if (descriptor !== undefined) {
|
|
373
|
+
const normalized = normalizeDescriptor(descriptor);
|
|
374
|
+
schema = normalized.schema; descriptor = normalized.descriptor;
|
|
375
|
+
} else {
|
|
376
|
+
const response = await this._sendRequest(introspectionQuery(this._options.introspection !== 'standard'));
|
|
377
|
+
if (response.errors) { const error = new Error(`Introspection failed: ${JSON.stringify(response.errors)}`); error.graphQLErrors = response.errors; throw error; }
|
|
378
|
+
schema = response.data?.__schema;
|
|
379
|
+
if (!schema || !Array.isArray(schema.types)) throw new Error('Invalid introspection response');
|
|
380
|
+
}
|
|
381
|
+
const draft = new SimfinityClient(this._endpoint);
|
|
382
|
+
draft._parseIntrospection(schema);
|
|
383
|
+
draft._descriptor = descriptor || null;
|
|
384
|
+
if (descriptor) draft._applyDescriptor(descriptor);
|
|
385
|
+
for (const key of ['_types', '_queries', '_mutations', '_typeNameToPlural', '_typeNameToSingular', '_typeNameToAggregate', '_queryNameToType', '_descriptor']) this[key] = draft[key];
|
|
561
386
|
this._initialized = true;
|
|
562
387
|
}
|
|
563
388
|
|
|
389
|
+
getDescriptor() { return this._descriptor; }
|
|
390
|
+
|
|
391
|
+
_applyDescriptor(descriptor) {
|
|
392
|
+
this._typeNameToPlural.clear(); this._typeNameToSingular.clear(); this._typeNameToAggregate.clear();
|
|
393
|
+
for (const [typeName, type] of Object.entries(descriptor.types)) {
|
|
394
|
+
for (const [kind, map] of [['list', this._typeNameToPlural], ['get', this._typeNameToSingular], ['aggregate', this._typeNameToAggregate]]) {
|
|
395
|
+
if (type.endpoints?.[kind]) map.set(typeName, type.endpoints[kind]);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
for (const [root, registry] of [['query', this._queries], ['mutation', this._mutations]]) {
|
|
399
|
+
for (const [name, operation] of Object.entries(descriptor.operations[root])) {
|
|
400
|
+
Object.assign(registry.get(name), operation, { returnType: registry.get(name).returnType, args: registry.get(name).args,
|
|
401
|
+
category: operation.operation === 'transition' ? 'stateTransition' : operation.operation, typeName: operation.type });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
564
406
|
_parseIntrospection(schema) {
|
|
565
407
|
const queryTypeName = schema.queryType?.name;
|
|
566
408
|
const mutationTypeName = schema.mutationType?.name;
|
|
@@ -573,20 +415,22 @@ export default class SimfinityClient {
|
|
|
573
415
|
_buildTypesRegistry(types) {
|
|
574
416
|
for (const type of types) {
|
|
575
417
|
if (type.name.startsWith('__')) continue;
|
|
576
|
-
const validKinds = ['OBJECT', 'ENUM', 'SCALAR', 'INPUT_OBJECT'];
|
|
418
|
+
const validKinds = ['OBJECT', 'ENUM', 'SCALAR', 'INPUT_OBJECT', 'INTERFACE', 'UNION'];
|
|
577
419
|
if (!validKinds.includes(type.kind)) continue;
|
|
578
420
|
|
|
579
421
|
const typeInfo = {
|
|
580
422
|
name: type.name,
|
|
581
423
|
kind: type.kind,
|
|
582
|
-
fields: (type.fields || []).map(f => ({
|
|
424
|
+
fields: (type.fields || type.inputFields || []).map(f => ({
|
|
583
425
|
name: f.name,
|
|
584
426
|
type: unwrapType(f.type),
|
|
585
427
|
rawType: f.type,
|
|
586
|
-
args: (f.args || []).map(a => ({
|
|
428
|
+
args: (f.args || []).map(a => ({ ...a, hasDefault: a.hasDefault ?? (a.defaultValue != null) })),
|
|
587
429
|
extensions: f.extensions || null,
|
|
430
|
+
hasDefault: f.hasDefault ?? (f.defaultValue != null), defaultValue: f.defaultValue,
|
|
588
431
|
})),
|
|
589
432
|
enumValues: (type.enumValues || []).map(v => v.name),
|
|
433
|
+
baseScalar: type.baseScalar, possibleTypes: type.possibleTypes?.map((value) => typeof value === 'string' ? value : value.name), interfaces: type.interfaces?.map((value) => typeof value === 'string' ? value : value.name), isOneOf: type.isOneOf,
|
|
590
434
|
};
|
|
591
435
|
this._types.set(type.name, typeInfo);
|
|
592
436
|
}
|
|
@@ -600,7 +444,8 @@ export default class SimfinityClient {
|
|
|
600
444
|
const returnType = unwrapType(field.type);
|
|
601
445
|
const queryInfo = {
|
|
602
446
|
name: field.name,
|
|
603
|
-
|
|
447
|
+
description: field.description,
|
|
448
|
+
args: (field.args || []).map(a => ({ ...a, hasDefault: a.hasDefault ?? (a.defaultValue != null) })),
|
|
604
449
|
returnType,
|
|
605
450
|
rawReturnType: field.type,
|
|
606
451
|
};
|
|
@@ -614,13 +459,19 @@ export default class SimfinityClient {
|
|
|
614
459
|
}
|
|
615
460
|
}
|
|
616
461
|
|
|
617
|
-
for (const
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
462
|
+
for (const typeName of this._typeNameToPlural.keys()) {
|
|
463
|
+
const candidates = [...this._queries.values()].filter((query) => query.returnType.name === typeName && query.returnType.isList && !query.args.some((arg) => arg.name === 'aggregation'));
|
|
464
|
+
const canonical = candidates.filter((query) => this._queries.has(`${query.name}_aggregate`));
|
|
465
|
+
const selected = canonical.length === 1 ? canonical[0] : canonical.length === 0 && candidates.length === 1 ? candidates[0] : null;
|
|
466
|
+
if (selected) {
|
|
467
|
+
this._typeNameToPlural.set(typeName, selected.name);
|
|
468
|
+
if (canonical.length === 1) this._typeNameToAggregate.set(typeName, `${selected.name}_aggregate`);
|
|
469
|
+
} else this._typeNameToPlural.delete(typeName);
|
|
470
|
+
}
|
|
471
|
+
for (const typeName of this._typeNameToSingular.keys()) {
|
|
472
|
+
const candidates = [...this._queries.values()].filter((query) => query.returnType.name === typeName && !query.returnType.isList && query.args.some((arg) => arg.name === 'id'));
|
|
473
|
+
if (candidates.length !== 1) this._typeNameToSingular.delete(typeName);
|
|
622
474
|
}
|
|
623
|
-
|
|
624
475
|
this._queryNameToType = new Map();
|
|
625
476
|
for (const [typeName, queryName] of this._typeNameToPlural) {
|
|
626
477
|
this._queryNameToType.set(queryName, typeName);
|
|
@@ -640,7 +491,8 @@ export default class SimfinityClient {
|
|
|
640
491
|
const returnType = unwrapType(field.type);
|
|
641
492
|
const mutationInfo = {
|
|
642
493
|
name: field.name,
|
|
643
|
-
|
|
494
|
+
description: field.description,
|
|
495
|
+
args: (field.args || []).map(a => ({ ...a, hasDefault: a.hasDefault ?? (a.defaultValue != null) })),
|
|
644
496
|
returnType,
|
|
645
497
|
rawReturnType: field.type,
|
|
646
498
|
category: 'custom',
|
|
@@ -654,7 +506,18 @@ export default class SimfinityClient {
|
|
|
654
506
|
|
|
655
507
|
_classifyMutation(mutationInfo, knownTypeNames) {
|
|
656
508
|
const name = mutationInfo.name;
|
|
509
|
+
for (const kind of ['add', 'update', 'delete']) {
|
|
510
|
+
if (mutationInfo.description === kind && name.startsWith(kind) && knownTypeNames.includes(mutationInfo.returnType.name)) {
|
|
511
|
+
Object.assign(mutationInfo, { category: kind, typeName: mutationInfo.returnType.name }); return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
657
514
|
for (const typeName of knownTypeNames) {
|
|
515
|
+
const singular = this._typeNameToSingular.get(typeName);
|
|
516
|
+
const suffix = `_${singular}`;
|
|
517
|
+
if (mutationInfo.returnType.name === typeName && singular && name.endsWith(suffix)
|
|
518
|
+
&& this._types.get(typeName)?.fields.some((field) => field.extensions?.stateMachine)) {
|
|
519
|
+
Object.assign(mutationInfo, { category: 'stateTransition', typeName, action: name.slice(0, -suffix.length) }); return;
|
|
520
|
+
}
|
|
658
521
|
if (name === `add${typeName}`) {
|
|
659
522
|
Object.assign(mutationInfo, { category: 'add', typeName });
|
|
660
523
|
return;
|
|
@@ -688,7 +551,7 @@ export default class SimfinityClient {
|
|
|
688
551
|
const typeInfo = this._types.get(typeName);
|
|
689
552
|
if (!typeInfo) return 'id';
|
|
690
553
|
const scalars = typeInfo.fields
|
|
691
|
-
.filter(f => f.type.kind === 'SCALAR' || f.type.kind === 'ENUM')
|
|
554
|
+
.filter(f => (f.type.kind === 'SCALAR' || f.type.kind === 'ENUM') && canAutoSelect(f))
|
|
692
555
|
.map(f => f.name);
|
|
693
556
|
return scalars.length > 0 ? scalars.join(' ') : 'id';
|
|
694
557
|
}
|
|
@@ -723,14 +586,71 @@ export default class SimfinityClient {
|
|
|
723
586
|
}
|
|
724
587
|
const headers = { 'Content-Type': 'application/json' };
|
|
725
588
|
if (this._prepareHeaders) {
|
|
726
|
-
this._prepareHeaders(headers);
|
|
589
|
+
await this._prepareHeaders(headers);
|
|
727
590
|
}
|
|
728
|
-
const response = await _fetch(this._endpoint, {
|
|
591
|
+
const response = await this._fetch(this._endpoint, {
|
|
729
592
|
method: 'POST',
|
|
730
593
|
headers,
|
|
731
594
|
body: JSON.stringify(body),
|
|
732
595
|
});
|
|
733
|
-
|
|
596
|
+
let envelope;
|
|
597
|
+
try { envelope = await response.json(); } catch { throw new Error(`Invalid JSON response (HTTP ${response.status})`); }
|
|
598
|
+
if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)
|
|
599
|
+
|| (envelope.errors !== undefined && (!Array.isArray(envelope.errors) || envelope.errors.length === 0 || envelope.errors.some((error) => !error || typeof error.message !== 'string')))
|
|
600
|
+
|| (!Object.hasOwn(envelope, 'data') && !envelope.errors)
|
|
601
|
+
|| (envelope.data !== undefined && envelope.data !== null && (typeof envelope.data !== 'object' || Array.isArray(envelope.data)))) throw new Error('Invalid GraphQL response');
|
|
602
|
+
if (!response.ok && !envelope.errors) throw new Error(`HTTP request failed (${response.status})`);
|
|
603
|
+
return envelope;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
_checkErrors(response, name, partial = false) {
|
|
607
|
+
if (response.errors && (!partial || !response.data?.[name])) {
|
|
608
|
+
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
609
|
+
error.graphQLErrors = response.errors; throw error;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async _requestOperation(kind, info, provided = {}, selection, collectionArguments = new Map()) {
|
|
614
|
+
const definitions = []; const variables = {}; const mappings = [];
|
|
615
|
+
for (const [name, value] of Object.entries(provided)) {
|
|
616
|
+
if (value === undefined) continue;
|
|
617
|
+
const arg = info.args.find((item) => item.name === name);
|
|
618
|
+
if (!arg) throw new Error(`Unknown ${kind} argument ${info.name}.${name}`);
|
|
619
|
+
definitions.push(`$${name}: ${typeRefToString(arg.type)}`); variables[name] = value; mappings.push(`${name}: $${name}`);
|
|
620
|
+
}
|
|
621
|
+
const context = { types: this._types, arguments: collectionArguments, used: new Set(), variable: (ref, value) => {
|
|
622
|
+
let name = `c${definitions.length}`; while (Object.hasOwn(variables, name)) name += '_';
|
|
623
|
+
variables[name] = value; definitions.push(`$${name}: ${typeRefToString(ref)}`); return name;
|
|
624
|
+
} };
|
|
625
|
+
const composite = ['OBJECT', 'INTERFACE', 'UNION'].includes(info.returnType.kind);
|
|
626
|
+
let fields = '';
|
|
627
|
+
if (composite) {
|
|
628
|
+
const tree = selection instanceof SelectionTree ? selection : new SelectionTree();
|
|
629
|
+
if (!(selection instanceof SelectionTree)) tree.addSelection(selection || this._getScalarFields(info.returnType.name));
|
|
630
|
+
fields = tree.serialize(context, info.returnType.name);
|
|
631
|
+
if (!fields) throw new Error(`No fields to select for ${info.name}`);
|
|
632
|
+
}
|
|
633
|
+
for (const path of collectionArguments.keys()) if (!context.used.has(path)) throw new Error(`Collection path is not selected: ${path}`);
|
|
634
|
+
const document = `${kind}${definitions.length ? `(${definitions.join(', ')})` : ''} { ${info.name}${mappings.length ? `(${mappings.join(', ')})` : ''}${fields ? ` { ${fields} }` : ''} }`;
|
|
635
|
+
return this._sendRequest(document, variables);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async query(name, args = {}, fields) {
|
|
639
|
+
this._ensureInitialized(); const info = this._queries.get(name);
|
|
640
|
+
if (!info) throw new Error(`Query '${name}' not found`);
|
|
641
|
+
const response = await this._requestOperation('query', info, args, fields);
|
|
642
|
+
this._checkErrors(response, name); return response.data?.[name];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
getViews(typeName) {
|
|
646
|
+
this._ensureInitialized();
|
|
647
|
+
return [...this._queries.values()].filter((info) => info.view && (!typeName || info.typeName === typeName));
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
findView(name, params = {}) {
|
|
651
|
+
this._ensureInitialized(); const info = this._queries.get(name);
|
|
652
|
+
if (!info?.returnType.isList || (this._descriptor && !info.view)) throw new Error(`View '${name}' not found`);
|
|
653
|
+
const builder = new QueryBuilder(this, info.returnType.name, info); builder._variables = { ...params }; return builder;
|
|
734
654
|
}
|
|
735
655
|
|
|
736
656
|
// -- Query Methods -------------------------------------------------------
|
|
@@ -766,7 +686,7 @@ export default class SimfinityClient {
|
|
|
766
686
|
searchField = 'name';
|
|
767
687
|
} else {
|
|
768
688
|
const firstScalar = typeInfo.fields.find(f =>
|
|
769
|
-
isScalarOrEnum(f.type.kind) && f.name !== 'id'
|
|
689
|
+
isScalarOrEnum(f.type.kind) && f.name !== 'id',
|
|
770
690
|
);
|
|
771
691
|
searchField = firstScalar?.name || 'id';
|
|
772
692
|
}
|
|
@@ -832,7 +752,7 @@ export default class SimfinityClient {
|
|
|
832
752
|
const transformedInput = options.transform
|
|
833
753
|
? this.transformInput(typeName, input, { mode: 'create', ...options })
|
|
834
754
|
: input;
|
|
835
|
-
return this._executeMutation(
|
|
755
|
+
return this._executeMutation(this._mutationName(typeName, 'add'), typeName, { input: transformedInput }, fields);
|
|
836
756
|
}
|
|
837
757
|
|
|
838
758
|
async update(typeName, id, input, fields, options = {}) {
|
|
@@ -841,15 +761,39 @@ export default class SimfinityClient {
|
|
|
841
761
|
? this.transformInput(typeName, input, { mode: 'update', ...options })
|
|
842
762
|
: input;
|
|
843
763
|
return this._executeMutation(
|
|
844
|
-
|
|
764
|
+
this._mutationName(typeName, 'update'), typeName,
|
|
845
765
|
{ input: { id, ...transformedInput } },
|
|
846
766
|
fields,
|
|
847
767
|
);
|
|
848
768
|
}
|
|
849
769
|
|
|
850
|
-
async delete(typeName, id, fields) {
|
|
770
|
+
async delete(typeName, id, fields, options = {}) {
|
|
851
771
|
this._ensureInitialized();
|
|
852
|
-
return this._executeMutation(
|
|
772
|
+
return this._executeMutation(this._mutationName(typeName, 'delete'), typeName, { id, ...(options.version !== undefined ? { version: options.version } : {}) }, fields);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
_mutationName(typeName, kind) {
|
|
776
|
+
if (this._descriptor) {
|
|
777
|
+
const name = this._descriptor.types[typeName]?.endpoints?.[kind];
|
|
778
|
+
if (!name) throw new Error(`No ${kind} mutation for '${typeName}'`);
|
|
779
|
+
return name;
|
|
780
|
+
}
|
|
781
|
+
return [...this._mutations.values()].find((info) => info.category === kind && info.typeName === typeName)?.name || `${kind}${typeName}`;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
_inputTypeName(typeName, kind) {
|
|
785
|
+
const descriptor = this._descriptor?.types[typeName]?.inputs?.[kind];
|
|
786
|
+
if (descriptor) return descriptor;
|
|
787
|
+
const info = this._mutations.get(this._mutationName(typeName, kind));
|
|
788
|
+
const input = info?.args.find((arg) => arg.name === 'input');
|
|
789
|
+
return input ? unwrapType(input.type).name : null;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
async transaction(operations, fields = 'id', options = {}) {
|
|
793
|
+
this._ensureInitialized();
|
|
794
|
+
const info = options.name ? this._mutations.get(options.name) : [...this._mutations.values()].find((item) => item.category === 'transaction');
|
|
795
|
+
if (!info || (this._descriptor && info.category !== 'transaction') || (!this._descriptor && !options.name)) throw new Error('Generated transaction not found; introspection requires an explicit transaction name');
|
|
796
|
+
return this._executeMutation(info.name, info.returnType.name, { operations }, fields);
|
|
853
797
|
}
|
|
854
798
|
|
|
855
799
|
// -- State Machine -------------------------------------------------------
|
|
@@ -932,35 +876,10 @@ export default class SimfinityClient {
|
|
|
932
876
|
// -- Shared Mutation Executor --------------------------------------------
|
|
933
877
|
|
|
934
878
|
async _executeMutation(mutationName, typeName, providedArgs, fields) {
|
|
935
|
-
const
|
|
936
|
-
if (!
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
const selectionSet = this._resolveFields(typeName, fields);
|
|
941
|
-
const varDefs = [];
|
|
942
|
-
const variables = {};
|
|
943
|
-
const argMappings = [];
|
|
944
|
-
|
|
945
|
-
for (const arg of mutationInfo.args) {
|
|
946
|
-
if (providedArgs[arg.name] !== undefined) {
|
|
947
|
-
varDefs.push(`$${arg.name}: ${typeRefToString(arg.type)}`);
|
|
948
|
-
variables[arg.name] = providedArgs[arg.name];
|
|
949
|
-
argMappings.push(`${arg.name}: $${arg.name}`);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(', ')})` : '';
|
|
954
|
-
const argsBlock = argMappings.length > 0 ? `(${argMappings.join(', ')})` : '';
|
|
955
|
-
const query = `mutation${varDefsStr} { ${mutationName}${argsBlock} { ${selectionSet} } }`;
|
|
956
|
-
|
|
957
|
-
const response = await this._sendRequest(query, variables);
|
|
958
|
-
if (response.errors) {
|
|
959
|
-
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
960
|
-
error.graphQLErrors = response.errors;
|
|
961
|
-
throw error;
|
|
962
|
-
}
|
|
963
|
-
return response.data[mutationName];
|
|
879
|
+
const info = this._mutations.get(mutationName);
|
|
880
|
+
if (!info) throw new Error(`Mutation '${mutationName}' not found`);
|
|
881
|
+
const response = await this._requestOperation('mutation', info, providedArgs, fields);
|
|
882
|
+
this._checkErrors(response, mutationName); return response.data?.[mutationName];
|
|
964
883
|
}
|
|
965
884
|
|
|
966
885
|
// -- Schema Getters ------------------------------------------------------
|
|
@@ -994,7 +913,7 @@ export default class SimfinityClient {
|
|
|
994
913
|
for (const field of typeInfo.fields) {
|
|
995
914
|
const { name, type: unwrapped, rawType, extensions } = field;
|
|
996
915
|
|
|
997
|
-
if (isListType(rawType)) continue;
|
|
916
|
+
if (isListType(rawType) || !canAutoSelect(field)) continue;
|
|
998
917
|
|
|
999
918
|
if (isScalarOrEnum(unwrapped.kind)) {
|
|
1000
919
|
fieldSelections.push(name);
|
|
@@ -1003,7 +922,7 @@ export default class SimfinityClient {
|
|
|
1003
922
|
fieldTypeByColumn[name] = unwrapped.name || unwrapped.kind || 'SCALAR';
|
|
1004
923
|
} else if (unwrapped.kind === 'OBJECT' && unwrapped.name) {
|
|
1005
924
|
const objType = this._types.get(unwrapped.name);
|
|
1006
|
-
const objFields = objType?.fields || [];
|
|
925
|
+
const objFields = (objType?.fields || []).filter((candidate) => isScalarOrEnum(candidate.type.kind) && !candidate.type.isList && canAutoSelect(candidate));
|
|
1007
926
|
const objFieldNames = new Set(objFields.map(f => f.name));
|
|
1008
927
|
const isEmbedded = extensions?.relation?.embedded === true;
|
|
1009
928
|
|
|
@@ -1015,7 +934,7 @@ export default class SimfinityClient {
|
|
|
1015
934
|
chosenDisplay = 'name';
|
|
1016
935
|
} else {
|
|
1017
936
|
const firstScalar = objFields.find(f =>
|
|
1018
|
-
isScalarOrEnum(f.type.kind) && !f.type.isList
|
|
937
|
+
isScalarOrEnum(f.type.kind) && !f.type.isList,
|
|
1019
938
|
);
|
|
1020
939
|
chosenDisplay = firstScalar?.name || null;
|
|
1021
940
|
}
|
|
@@ -1029,6 +948,7 @@ export default class SimfinityClient {
|
|
|
1029
948
|
else if (objFields[0]) subFields.add(objFields[0].name);
|
|
1030
949
|
}
|
|
1031
950
|
|
|
951
|
+
if (subFields.size === 0) continue;
|
|
1032
952
|
const subSelection = [...subFields].join(' ');
|
|
1033
953
|
fieldSelections.push(`${name} { ${subSelection} }`);
|
|
1034
954
|
columns.push(name);
|
|
@@ -1044,13 +964,13 @@ export default class SimfinityClient {
|
|
|
1044
964
|
}
|
|
1045
965
|
}
|
|
1046
966
|
|
|
1047
|
-
const hasId = typeInfo.fields.some(f => f.name === 'id');
|
|
967
|
+
const hasId = typeInfo.fields.some(f => f.name === 'id' && canAutoSelect(f));
|
|
1048
968
|
if (hasId && !fieldSelections.includes('id')) {
|
|
1049
969
|
fieldSelections.unshift('id');
|
|
1050
970
|
}
|
|
1051
971
|
|
|
1052
972
|
return {
|
|
1053
|
-
selection: fieldSelections.join('\n'),
|
|
973
|
+
selection: fieldSelections.join('\n') || '__typename',
|
|
1054
974
|
columns,
|
|
1055
975
|
sortFieldByColumn,
|
|
1056
976
|
fieldTypeByColumn,
|
|
@@ -1084,7 +1004,7 @@ export default class SimfinityClient {
|
|
|
1084
1004
|
if (objFieldNames.includes('name')) return 'name';
|
|
1085
1005
|
|
|
1086
1006
|
const firstScalar = objectType.fields.find(f =>
|
|
1087
|
-
isScalarOrEnum(f.type.kind) && !f.type.isList
|
|
1007
|
+
isScalarOrEnum(f.type.kind) && !f.type.isList,
|
|
1088
1008
|
);
|
|
1089
1009
|
return firstScalar?.name || null;
|
|
1090
1010
|
}
|
|
@@ -1197,8 +1117,12 @@ export default class SimfinityClient {
|
|
|
1197
1117
|
// -- Mutation Input Transformation -----------------------------------------
|
|
1198
1118
|
|
|
1199
1119
|
transformInput(typeName, rawInput, options = {}) {
|
|
1120
|
+
this._ensureInitialized(); return transformRootInput(this, typeName, rawInput, options);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
_legacyTransformInput(typeName, rawInput, options = {}) {
|
|
1200
1124
|
this._ensureInitialized();
|
|
1201
|
-
const { skipFields = [], transientFields = []
|
|
1125
|
+
const { skipFields = [], transientFields = [] } = options;
|
|
1202
1126
|
const typeInfo = this._types.get(typeName);
|
|
1203
1127
|
if (!typeInfo) return { ...rawInput };
|
|
1204
1128
|
|
|
@@ -1232,7 +1156,7 @@ export default class SimfinityClient {
|
|
|
1232
1156
|
return this._deepRemoveTypename(transformed);
|
|
1233
1157
|
}
|
|
1234
1158
|
|
|
1235
|
-
_transformEmbeddedInput(objectTypeName, value
|
|
1159
|
+
_transformEmbeddedInput(objectTypeName, value) {
|
|
1236
1160
|
if (!value || typeof value !== 'object') return value;
|
|
1237
1161
|
const typeInfo = this._types.get(objectTypeName);
|
|
1238
1162
|
if (!typeInfo?.fields) return value;
|
|
@@ -1291,93 +1215,7 @@ export default class SimfinityClient {
|
|
|
1291
1215
|
// -- Collection Delta Mutations --------------------------------------------
|
|
1292
1216
|
|
|
1293
1217
|
transformCollectionDelta(collectionTypeName, delta, options = {}) {
|
|
1294
|
-
this._ensureInitialized();
|
|
1295
|
-
const { connectionField = null } = options;
|
|
1296
|
-
const result = {};
|
|
1297
|
-
|
|
1298
|
-
if (delta.added && delta.added.length > 0) {
|
|
1299
|
-
result.added = delta.added.map(item => {
|
|
1300
|
-
let clean = { ...item };
|
|
1301
|
-
|
|
1302
|
-
delete clean.__status;
|
|
1303
|
-
delete clean.__originalData;
|
|
1304
|
-
|
|
1305
|
-
if (connectionField && clean[connectionField] !== undefined) {
|
|
1306
|
-
delete clean[connectionField];
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
if (clean.id && typeof clean.id === 'string' && clean.id.startsWith('temp_')) {
|
|
1310
|
-
delete clean.id;
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
clean = this._cleanCollectionItem(collectionTypeName, clean);
|
|
1314
|
-
clean = this._deepRemoveTypename(clean);
|
|
1315
|
-
return clean;
|
|
1316
|
-
});
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
if (delta.updated && delta.updated.length > 0) {
|
|
1320
|
-
result.updated = delta.updated.map(item => {
|
|
1321
|
-
let clean = { ...item };
|
|
1322
|
-
|
|
1323
|
-
delete clean.__status;
|
|
1324
|
-
delete clean.__originalData;
|
|
1325
|
-
|
|
1326
|
-
if (connectionField && clean[connectionField] !== undefined) {
|
|
1327
|
-
delete clean[connectionField];
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
clean = this._cleanCollectionItem(collectionTypeName, clean);
|
|
1331
|
-
clean = this._deepRemoveTypename(clean);
|
|
1332
|
-
return clean;
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
if (delta.deleted && delta.deleted.length > 0) {
|
|
1337
|
-
result.deleted = delta.deleted.map(item => {
|
|
1338
|
-
if (typeof item === 'string') return item;
|
|
1339
|
-
if (typeof item === 'object' && item.id) return item.id;
|
|
1340
|
-
return item;
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
return result;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
_cleanCollectionItem(collectionTypeName, item) {
|
|
1348
|
-
const typeInfo = this._types.get(collectionTypeName);
|
|
1349
|
-
if (!typeInfo?.fields) return item;
|
|
1350
|
-
|
|
1351
|
-
const cleaned = { ...item };
|
|
1352
|
-
|
|
1353
|
-
for (const field of typeInfo.fields) {
|
|
1354
|
-
const { name, type: unwrapped, rawType, extensions } = field;
|
|
1355
|
-
if (cleaned[name] === undefined) continue;
|
|
1356
|
-
|
|
1357
|
-
if (extensions?.stateMachine === true) {
|
|
1358
|
-
delete cleaned[name];
|
|
1359
|
-
continue;
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
if (unwrapped.kind === 'OBJECT' && !extensions?.relation?.embedded) {
|
|
1363
|
-
const val = cleaned[name];
|
|
1364
|
-
if (val && typeof val === 'object' && 'id' in val) {
|
|
1365
|
-
cleaned[name] = { id: val.id };
|
|
1366
|
-
}
|
|
1367
|
-
continue;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
if (isNumericScalar(unwrapped.name) && typeof cleaned[name] === 'string') {
|
|
1371
|
-
cleaned[name] = Number(cleaned[name]);
|
|
1372
|
-
}
|
|
1373
|
-
if (isDateTimeScalar(unwrapped.name) && typeof cleaned[name] === 'string') {
|
|
1374
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(cleaned[name])) {
|
|
1375
|
-
cleaned[name] = `${cleaned[name]}T00:00:00.000Z`;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
return cleaned;
|
|
1218
|
+
this._ensureInitialized(); return transformDelta(this, collectionTypeName, delta, options);
|
|
1381
1219
|
}
|
|
1382
1220
|
|
|
1383
1221
|
// -- Guard ---------------------------------------------------------------
|