@simtlix/simfinity-js-client 1.0.6 → 3.19.0
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 +121 -3
- package/package.json +17 -6
- package/src/SimfinityClient.js +286 -392
- 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/IMPLEMENTATION_PROMPT.md +0 -1137
- package/examples/demo.js +0 -350
- 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,35 @@ 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
|
+
|
|
113
|
+
or(groups) {
|
|
114
|
+
if (!Array.isArray(groups)) {
|
|
115
|
+
throw new Error('or() expects an array of QLFilterGroup objects');
|
|
116
|
+
}
|
|
117
|
+
if (this._variables.OR) {
|
|
118
|
+
this._variables.OR.push(...groups);
|
|
119
|
+
} else {
|
|
120
|
+
this._variables.OR = [...groups];
|
|
121
|
+
}
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
and(groups) {
|
|
126
|
+
if (!Array.isArray(groups)) {
|
|
127
|
+
throw new Error('and() expects an array of QLFilterGroup objects');
|
|
128
|
+
}
|
|
129
|
+
if (this._variables.AND) {
|
|
130
|
+
this._variables.AND.push(...groups);
|
|
131
|
+
} else {
|
|
132
|
+
this._variables.AND = [...groups];
|
|
133
|
+
}
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
230
137
|
// -- Relation Field Selection --------------------------------------------
|
|
231
138
|
|
|
232
139
|
joinCollection(path, fields, filter) {
|
|
@@ -284,99 +191,28 @@ class QueryBuilder {
|
|
|
284
191
|
|
|
285
192
|
// -- Execution -----------------------------------------------------------
|
|
286
193
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (!this._hasExplicitFields) {
|
|
294
|
-
const autoFields = this._client._getScalarFields(this._typeName);
|
|
295
|
-
this._selectionTree.addScalars(autoFields);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const selectionSet = this._autoSelection || this._selectionTree.serialize();
|
|
299
|
-
if (!selectionSet) {
|
|
300
|
-
throw new Error(`No fields to select for type '${this._typeName}'`);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const varDefs = [];
|
|
304
|
-
const variables = {};
|
|
305
|
-
const argMappings = [];
|
|
306
|
-
|
|
307
|
-
for (const [key, val] of Object.entries(this._variables)) {
|
|
308
|
-
const arg = queryInfo.args.find(a => a.name === key);
|
|
309
|
-
if (arg) {
|
|
310
|
-
varDefs.push(`$${key}: ${typeRefToString(arg.type)}`);
|
|
311
|
-
variables[key] = val;
|
|
312
|
-
argMappings.push(`${key}: $${key}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(', ')})` : '';
|
|
317
|
-
const argsBlock = argMappings.length > 0 ? `(${argMappings.join(', ')})` : '';
|
|
318
|
-
const query = `query${varDefsStr} { ${queryInfo.name}${argsBlock} { ${selectionSet} } }`;
|
|
319
|
-
|
|
320
|
-
const response = await this._client._sendRequest(query, variables);
|
|
321
|
-
if (response.errors && !response.data?.[queryInfo.name]) {
|
|
322
|
-
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
323
|
-
error.graphQLErrors = response.errors;
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const result = response.data[queryInfo.name];
|
|
328
|
-
|
|
329
|
-
if (response.extensions) {
|
|
330
|
-
result.__extensions = response.extensions;
|
|
331
|
-
}
|
|
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
|
+
}
|
|
332
198
|
|
|
333
|
-
|
|
199
|
+
async exec() {
|
|
200
|
+
const { data, extensions } = await this.execWithMeta();
|
|
201
|
+
if (data !== null && typeof data === 'object' && extensions) data.__extensions = extensions;
|
|
202
|
+
return data;
|
|
334
203
|
}
|
|
335
204
|
|
|
336
205
|
async execWithMeta() {
|
|
337
|
-
const queryInfo = this._client._findPluralQuery(this._typeName);
|
|
338
|
-
if (!queryInfo) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const selectionSet = this._autoSelection || this._selectionTree.serialize();
|
|
348
|
-
if (!selectionSet) {
|
|
349
|
-
throw new Error(`No fields to select for type '${this._typeName}'`);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const varDefs = [];
|
|
353
|
-
const variables = {};
|
|
354
|
-
const argMappings = [];
|
|
355
|
-
|
|
356
|
-
for (const [key, val] of Object.entries(this._variables)) {
|
|
357
|
-
const arg = queryInfo.args.find(a => a.name === key);
|
|
358
|
-
if (arg) {
|
|
359
|
-
varDefs.push(`$${key}: ${typeRefToString(arg.type)}`);
|
|
360
|
-
variables[key] = val;
|
|
361
|
-
argMappings.push(`${key}: $${key}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(', ')})` : '';
|
|
366
|
-
const argsBlock = argMappings.length > 0 ? `(${argMappings.join(', ')})` : '';
|
|
367
|
-
const query = `query${varDefsStr} { ${queryInfo.name}${argsBlock} { ${selectionSet} } }`;
|
|
368
|
-
|
|
369
|
-
const response = await this._client._sendRequest(query, variables);
|
|
370
|
-
if (response.errors && !response.data?.[queryInfo.name]) {
|
|
371
|
-
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
372
|
-
error.graphQLErrors = response.errors;
|
|
373
|
-
throw error;
|
|
374
|
-
}
|
|
375
|
-
return {
|
|
376
|
-
data: response.data[queryInfo.name],
|
|
377
|
-
extensions: response.extensions || null,
|
|
378
|
-
};
|
|
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 };
|
|
379
214
|
}
|
|
215
|
+
|
|
380
216
|
}
|
|
381
217
|
|
|
382
218
|
// ---------------------------------------------------------------------------
|
|
@@ -404,7 +240,9 @@ class AggregateBuilder {
|
|
|
404
240
|
}
|
|
405
241
|
|
|
406
242
|
where(field, operatorOrTerms, value, value2) {
|
|
407
|
-
if (Array.isArray(operatorOrTerms)) {
|
|
243
|
+
if (operatorOrTerms && typeof operatorOrTerms === 'object' && !Array.isArray(operatorOrTerms)) {
|
|
244
|
+
this._variables[field] = { ...operatorOrTerms };
|
|
245
|
+
} else if (Array.isArray(operatorOrTerms)) {
|
|
408
246
|
if (this._variables[field]?.terms) {
|
|
409
247
|
this._variables[field].terms.push(...operatorOrTerms);
|
|
410
248
|
} else {
|
|
@@ -418,6 +256,35 @@ class AggregateBuilder {
|
|
|
418
256
|
return this;
|
|
419
257
|
}
|
|
420
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
|
+
|
|
264
|
+
or(groups) {
|
|
265
|
+
if (!Array.isArray(groups)) {
|
|
266
|
+
throw new Error('or() expects an array of QLFilterGroup objects');
|
|
267
|
+
}
|
|
268
|
+
if (this._variables.OR) {
|
|
269
|
+
this._variables.OR.push(...groups);
|
|
270
|
+
} else {
|
|
271
|
+
this._variables.OR = [...groups];
|
|
272
|
+
}
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
and(groups) {
|
|
277
|
+
if (!Array.isArray(groups)) {
|
|
278
|
+
throw new Error('and() expects an array of QLFilterGroup objects');
|
|
279
|
+
}
|
|
280
|
+
if (this._variables.AND) {
|
|
281
|
+
this._variables.AND.push(...groups);
|
|
282
|
+
} else {
|
|
283
|
+
this._variables.AND = [...groups];
|
|
284
|
+
}
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
|
|
421
288
|
page(page, size, count) {
|
|
422
289
|
const pagination = { page, size };
|
|
423
290
|
if (count !== undefined) pagination.count = count;
|
|
@@ -449,29 +316,9 @@ class AggregateBuilder {
|
|
|
449
316
|
facts: this._facts,
|
|
450
317
|
};
|
|
451
318
|
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
const argMappings = [];
|
|
319
|
+
const response = await this._client._requestOperation('query', queryInfo, this._variables, 'groupId facts');
|
|
320
|
+
this._client._checkErrors(response, queryInfo.name);
|
|
455
321
|
|
|
456
|
-
for (const [key, val] of Object.entries(this._variables)) {
|
|
457
|
-
const arg = queryInfo.args.find(a => a.name === key);
|
|
458
|
-
if (arg) {
|
|
459
|
-
varDefs.push(`$${key}: ${typeRefToString(arg.type)}`);
|
|
460
|
-
variables[key] = val;
|
|
461
|
-
argMappings.push(`${key}: $${key}`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(', ')})` : '';
|
|
466
|
-
const argsBlock = argMappings.length > 0 ? `(${argMappings.join(', ')})` : '';
|
|
467
|
-
const query = `query${varDefsStr} { ${queryInfo.name}${argsBlock} { groupId facts } }`;
|
|
468
|
-
|
|
469
|
-
const response = await this._client._sendRequest(query, variables);
|
|
470
|
-
if (response.errors) {
|
|
471
|
-
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
472
|
-
error.graphQLErrors = response.errors;
|
|
473
|
-
throw error;
|
|
474
|
-
}
|
|
475
322
|
return response.data[queryInfo.name];
|
|
476
323
|
}
|
|
477
324
|
}
|
|
@@ -489,6 +336,14 @@ export default class SimfinityClient {
|
|
|
489
336
|
* perform auth in your app and set tokens inside this callback or a closure it sees.
|
|
490
337
|
*/
|
|
491
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
|
+
});
|
|
492
347
|
this._endpoint = endpoint;
|
|
493
348
|
this._types = new Map();
|
|
494
349
|
this._queries = new Map();
|
|
@@ -505,14 +360,49 @@ export default class SimfinityClient {
|
|
|
505
360
|
// -- Initialization ------------------------------------------------------
|
|
506
361
|
|
|
507
362
|
async init() {
|
|
508
|
-
|
|
509
|
-
if (
|
|
510
|
-
|
|
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');
|
|
511
380
|
}
|
|
512
|
-
this.
|
|
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];
|
|
513
386
|
this._initialized = true;
|
|
514
387
|
}
|
|
515
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
|
+
|
|
516
406
|
_parseIntrospection(schema) {
|
|
517
407
|
const queryTypeName = schema.queryType?.name;
|
|
518
408
|
const mutationTypeName = schema.mutationType?.name;
|
|
@@ -525,20 +415,22 @@ export default class SimfinityClient {
|
|
|
525
415
|
_buildTypesRegistry(types) {
|
|
526
416
|
for (const type of types) {
|
|
527
417
|
if (type.name.startsWith('__')) continue;
|
|
528
|
-
const validKinds = ['OBJECT', 'ENUM', 'SCALAR', 'INPUT_OBJECT'];
|
|
418
|
+
const validKinds = ['OBJECT', 'ENUM', 'SCALAR', 'INPUT_OBJECT', 'INTERFACE', 'UNION'];
|
|
529
419
|
if (!validKinds.includes(type.kind)) continue;
|
|
530
420
|
|
|
531
421
|
const typeInfo = {
|
|
532
422
|
name: type.name,
|
|
533
423
|
kind: type.kind,
|
|
534
|
-
fields: (type.fields || []).map(f => ({
|
|
424
|
+
fields: (type.fields || type.inputFields || []).map(f => ({
|
|
535
425
|
name: f.name,
|
|
536
426
|
type: unwrapType(f.type),
|
|
537
427
|
rawType: f.type,
|
|
538
|
-
args: (f.args || []).map(a => ({
|
|
428
|
+
args: (f.args || []).map(a => ({ ...a, hasDefault: a.hasDefault ?? (a.defaultValue != null) })),
|
|
539
429
|
extensions: f.extensions || null,
|
|
430
|
+
hasDefault: f.hasDefault ?? (f.defaultValue != null), defaultValue: f.defaultValue,
|
|
540
431
|
})),
|
|
541
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,
|
|
542
434
|
};
|
|
543
435
|
this._types.set(type.name, typeInfo);
|
|
544
436
|
}
|
|
@@ -552,7 +444,8 @@ export default class SimfinityClient {
|
|
|
552
444
|
const returnType = unwrapType(field.type);
|
|
553
445
|
const queryInfo = {
|
|
554
446
|
name: field.name,
|
|
555
|
-
|
|
447
|
+
description: field.description,
|
|
448
|
+
args: (field.args || []).map(a => ({ ...a, hasDefault: a.hasDefault ?? (a.defaultValue != null) })),
|
|
556
449
|
returnType,
|
|
557
450
|
rawReturnType: field.type,
|
|
558
451
|
};
|
|
@@ -566,13 +459,19 @@ export default class SimfinityClient {
|
|
|
566
459
|
}
|
|
567
460
|
}
|
|
568
461
|
|
|
569
|
-
for (const
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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);
|
|
574
474
|
}
|
|
575
|
-
|
|
576
475
|
this._queryNameToType = new Map();
|
|
577
476
|
for (const [typeName, queryName] of this._typeNameToPlural) {
|
|
578
477
|
this._queryNameToType.set(queryName, typeName);
|
|
@@ -592,7 +491,8 @@ export default class SimfinityClient {
|
|
|
592
491
|
const returnType = unwrapType(field.type);
|
|
593
492
|
const mutationInfo = {
|
|
594
493
|
name: field.name,
|
|
595
|
-
|
|
494
|
+
description: field.description,
|
|
495
|
+
args: (field.args || []).map(a => ({ ...a, hasDefault: a.hasDefault ?? (a.defaultValue != null) })),
|
|
596
496
|
returnType,
|
|
597
497
|
rawReturnType: field.type,
|
|
598
498
|
category: 'custom',
|
|
@@ -606,7 +506,18 @@ export default class SimfinityClient {
|
|
|
606
506
|
|
|
607
507
|
_classifyMutation(mutationInfo, knownTypeNames) {
|
|
608
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
|
+
}
|
|
609
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
|
+
}
|
|
610
521
|
if (name === `add${typeName}`) {
|
|
611
522
|
Object.assign(mutationInfo, { category: 'add', typeName });
|
|
612
523
|
return;
|
|
@@ -640,7 +551,7 @@ export default class SimfinityClient {
|
|
|
640
551
|
const typeInfo = this._types.get(typeName);
|
|
641
552
|
if (!typeInfo) return 'id';
|
|
642
553
|
const scalars = typeInfo.fields
|
|
643
|
-
.filter(f => f.type.kind === 'SCALAR' || f.type.kind === 'ENUM')
|
|
554
|
+
.filter(f => (f.type.kind === 'SCALAR' || f.type.kind === 'ENUM') && canAutoSelect(f))
|
|
644
555
|
.map(f => f.name);
|
|
645
556
|
return scalars.length > 0 ? scalars.join(' ') : 'id';
|
|
646
557
|
}
|
|
@@ -675,14 +586,71 @@ export default class SimfinityClient {
|
|
|
675
586
|
}
|
|
676
587
|
const headers = { 'Content-Type': 'application/json' };
|
|
677
588
|
if (this._prepareHeaders) {
|
|
678
|
-
this._prepareHeaders(headers);
|
|
589
|
+
await this._prepareHeaders(headers);
|
|
679
590
|
}
|
|
680
|
-
const response = await _fetch(this._endpoint, {
|
|
591
|
+
const response = await this._fetch(this._endpoint, {
|
|
681
592
|
method: 'POST',
|
|
682
593
|
headers,
|
|
683
594
|
body: JSON.stringify(body),
|
|
684
595
|
});
|
|
685
|
-
|
|
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;
|
|
686
654
|
}
|
|
687
655
|
|
|
688
656
|
// -- Query Methods -------------------------------------------------------
|
|
@@ -718,7 +686,7 @@ export default class SimfinityClient {
|
|
|
718
686
|
searchField = 'name';
|
|
719
687
|
} else {
|
|
720
688
|
const firstScalar = typeInfo.fields.find(f =>
|
|
721
|
-
isScalarOrEnum(f.type.kind) && f.name !== 'id'
|
|
689
|
+
isScalarOrEnum(f.type.kind) && f.name !== 'id',
|
|
722
690
|
);
|
|
723
691
|
searchField = firstScalar?.name || 'id';
|
|
724
692
|
}
|
|
@@ -784,7 +752,7 @@ export default class SimfinityClient {
|
|
|
784
752
|
const transformedInput = options.transform
|
|
785
753
|
? this.transformInput(typeName, input, { mode: 'create', ...options })
|
|
786
754
|
: input;
|
|
787
|
-
return this._executeMutation(
|
|
755
|
+
return this._executeMutation(this._mutationName(typeName, 'add'), typeName, { input: transformedInput }, fields);
|
|
788
756
|
}
|
|
789
757
|
|
|
790
758
|
async update(typeName, id, input, fields, options = {}) {
|
|
@@ -793,15 +761,39 @@ export default class SimfinityClient {
|
|
|
793
761
|
? this.transformInput(typeName, input, { mode: 'update', ...options })
|
|
794
762
|
: input;
|
|
795
763
|
return this._executeMutation(
|
|
796
|
-
|
|
764
|
+
this._mutationName(typeName, 'update'), typeName,
|
|
797
765
|
{ input: { id, ...transformedInput } },
|
|
798
766
|
fields,
|
|
799
767
|
);
|
|
800
768
|
}
|
|
801
769
|
|
|
802
|
-
async delete(typeName, id, fields) {
|
|
770
|
+
async delete(typeName, id, fields, options = {}) {
|
|
803
771
|
this._ensureInitialized();
|
|
804
|
-
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);
|
|
805
797
|
}
|
|
806
798
|
|
|
807
799
|
// -- State Machine -------------------------------------------------------
|
|
@@ -884,35 +876,10 @@ export default class SimfinityClient {
|
|
|
884
876
|
// -- Shared Mutation Executor --------------------------------------------
|
|
885
877
|
|
|
886
878
|
async _executeMutation(mutationName, typeName, providedArgs, fields) {
|
|
887
|
-
const
|
|
888
|
-
if (!
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
const selectionSet = this._resolveFields(typeName, fields);
|
|
893
|
-
const varDefs = [];
|
|
894
|
-
const variables = {};
|
|
895
|
-
const argMappings = [];
|
|
896
|
-
|
|
897
|
-
for (const arg of mutationInfo.args) {
|
|
898
|
-
if (providedArgs[arg.name] !== undefined) {
|
|
899
|
-
varDefs.push(`$${arg.name}: ${typeRefToString(arg.type)}`);
|
|
900
|
-
variables[arg.name] = providedArgs[arg.name];
|
|
901
|
-
argMappings.push(`${arg.name}: $${arg.name}`);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(', ')})` : '';
|
|
906
|
-
const argsBlock = argMappings.length > 0 ? `(${argMappings.join(', ')})` : '';
|
|
907
|
-
const query = `mutation${varDefsStr} { ${mutationName}${argsBlock} { ${selectionSet} } }`;
|
|
908
|
-
|
|
909
|
-
const response = await this._sendRequest(query, variables);
|
|
910
|
-
if (response.errors) {
|
|
911
|
-
const error = new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
|
|
912
|
-
error.graphQLErrors = response.errors;
|
|
913
|
-
throw error;
|
|
914
|
-
}
|
|
915
|
-
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];
|
|
916
883
|
}
|
|
917
884
|
|
|
918
885
|
// -- Schema Getters ------------------------------------------------------
|
|
@@ -946,7 +913,7 @@ export default class SimfinityClient {
|
|
|
946
913
|
for (const field of typeInfo.fields) {
|
|
947
914
|
const { name, type: unwrapped, rawType, extensions } = field;
|
|
948
915
|
|
|
949
|
-
if (isListType(rawType)) continue;
|
|
916
|
+
if (isListType(rawType) || !canAutoSelect(field)) continue;
|
|
950
917
|
|
|
951
918
|
if (isScalarOrEnum(unwrapped.kind)) {
|
|
952
919
|
fieldSelections.push(name);
|
|
@@ -955,7 +922,7 @@ export default class SimfinityClient {
|
|
|
955
922
|
fieldTypeByColumn[name] = unwrapped.name || unwrapped.kind || 'SCALAR';
|
|
956
923
|
} else if (unwrapped.kind === 'OBJECT' && unwrapped.name) {
|
|
957
924
|
const objType = this._types.get(unwrapped.name);
|
|
958
|
-
const objFields = objType?.fields || [];
|
|
925
|
+
const objFields = (objType?.fields || []).filter((candidate) => isScalarOrEnum(candidate.type.kind) && !candidate.type.isList && canAutoSelect(candidate));
|
|
959
926
|
const objFieldNames = new Set(objFields.map(f => f.name));
|
|
960
927
|
const isEmbedded = extensions?.relation?.embedded === true;
|
|
961
928
|
|
|
@@ -967,7 +934,7 @@ export default class SimfinityClient {
|
|
|
967
934
|
chosenDisplay = 'name';
|
|
968
935
|
} else {
|
|
969
936
|
const firstScalar = objFields.find(f =>
|
|
970
|
-
isScalarOrEnum(f.type.kind) && !f.type.isList
|
|
937
|
+
isScalarOrEnum(f.type.kind) && !f.type.isList,
|
|
971
938
|
);
|
|
972
939
|
chosenDisplay = firstScalar?.name || null;
|
|
973
940
|
}
|
|
@@ -981,6 +948,7 @@ export default class SimfinityClient {
|
|
|
981
948
|
else if (objFields[0]) subFields.add(objFields[0].name);
|
|
982
949
|
}
|
|
983
950
|
|
|
951
|
+
if (subFields.size === 0) continue;
|
|
984
952
|
const subSelection = [...subFields].join(' ');
|
|
985
953
|
fieldSelections.push(`${name} { ${subSelection} }`);
|
|
986
954
|
columns.push(name);
|
|
@@ -996,13 +964,13 @@ export default class SimfinityClient {
|
|
|
996
964
|
}
|
|
997
965
|
}
|
|
998
966
|
|
|
999
|
-
const hasId = typeInfo.fields.some(f => f.name === 'id');
|
|
967
|
+
const hasId = typeInfo.fields.some(f => f.name === 'id' && canAutoSelect(f));
|
|
1000
968
|
if (hasId && !fieldSelections.includes('id')) {
|
|
1001
969
|
fieldSelections.unshift('id');
|
|
1002
970
|
}
|
|
1003
971
|
|
|
1004
972
|
return {
|
|
1005
|
-
selection: fieldSelections.join('\n'),
|
|
973
|
+
selection: fieldSelections.join('\n') || '__typename',
|
|
1006
974
|
columns,
|
|
1007
975
|
sortFieldByColumn,
|
|
1008
976
|
fieldTypeByColumn,
|
|
@@ -1036,7 +1004,7 @@ export default class SimfinityClient {
|
|
|
1036
1004
|
if (objFieldNames.includes('name')) return 'name';
|
|
1037
1005
|
|
|
1038
1006
|
const firstScalar = objectType.fields.find(f =>
|
|
1039
|
-
isScalarOrEnum(f.type.kind) && !f.type.isList
|
|
1007
|
+
isScalarOrEnum(f.type.kind) && !f.type.isList,
|
|
1040
1008
|
);
|
|
1041
1009
|
return firstScalar?.name || null;
|
|
1042
1010
|
}
|
|
@@ -1149,8 +1117,12 @@ export default class SimfinityClient {
|
|
|
1149
1117
|
// -- Mutation Input Transformation -----------------------------------------
|
|
1150
1118
|
|
|
1151
1119
|
transformInput(typeName, rawInput, options = {}) {
|
|
1120
|
+
this._ensureInitialized(); return transformRootInput(this, typeName, rawInput, options);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
_legacyTransformInput(typeName, rawInput, options = {}) {
|
|
1152
1124
|
this._ensureInitialized();
|
|
1153
|
-
const { skipFields = [], transientFields = []
|
|
1125
|
+
const { skipFields = [], transientFields = [] } = options;
|
|
1154
1126
|
const typeInfo = this._types.get(typeName);
|
|
1155
1127
|
if (!typeInfo) return { ...rawInput };
|
|
1156
1128
|
|
|
@@ -1184,7 +1156,7 @@ export default class SimfinityClient {
|
|
|
1184
1156
|
return this._deepRemoveTypename(transformed);
|
|
1185
1157
|
}
|
|
1186
1158
|
|
|
1187
|
-
_transformEmbeddedInput(objectTypeName, value
|
|
1159
|
+
_transformEmbeddedInput(objectTypeName, value) {
|
|
1188
1160
|
if (!value || typeof value !== 'object') return value;
|
|
1189
1161
|
const typeInfo = this._types.get(objectTypeName);
|
|
1190
1162
|
if (!typeInfo?.fields) return value;
|
|
@@ -1243,93 +1215,7 @@ export default class SimfinityClient {
|
|
|
1243
1215
|
// -- Collection Delta Mutations --------------------------------------------
|
|
1244
1216
|
|
|
1245
1217
|
transformCollectionDelta(collectionTypeName, delta, options = {}) {
|
|
1246
|
-
this._ensureInitialized();
|
|
1247
|
-
const { connectionField = null } = options;
|
|
1248
|
-
const result = {};
|
|
1249
|
-
|
|
1250
|
-
if (delta.added && delta.added.length > 0) {
|
|
1251
|
-
result.added = delta.added.map(item => {
|
|
1252
|
-
let clean = { ...item };
|
|
1253
|
-
|
|
1254
|
-
delete clean.__status;
|
|
1255
|
-
delete clean.__originalData;
|
|
1256
|
-
|
|
1257
|
-
if (connectionField && clean[connectionField] !== undefined) {
|
|
1258
|
-
delete clean[connectionField];
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
if (clean.id && typeof clean.id === 'string' && clean.id.startsWith('temp_')) {
|
|
1262
|
-
delete clean.id;
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
clean = this._cleanCollectionItem(collectionTypeName, clean);
|
|
1266
|
-
clean = this._deepRemoveTypename(clean);
|
|
1267
|
-
return clean;
|
|
1268
|
-
});
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
if (delta.updated && delta.updated.length > 0) {
|
|
1272
|
-
result.updated = delta.updated.map(item => {
|
|
1273
|
-
let clean = { ...item };
|
|
1274
|
-
|
|
1275
|
-
delete clean.__status;
|
|
1276
|
-
delete clean.__originalData;
|
|
1277
|
-
|
|
1278
|
-
if (connectionField && clean[connectionField] !== undefined) {
|
|
1279
|
-
delete clean[connectionField];
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
clean = this._cleanCollectionItem(collectionTypeName, clean);
|
|
1283
|
-
clean = this._deepRemoveTypename(clean);
|
|
1284
|
-
return clean;
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
if (delta.deleted && delta.deleted.length > 0) {
|
|
1289
|
-
result.deleted = delta.deleted.map(item => {
|
|
1290
|
-
if (typeof item === 'string') return item;
|
|
1291
|
-
if (typeof item === 'object' && item.id) return item.id;
|
|
1292
|
-
return item;
|
|
1293
|
-
});
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
return result;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
_cleanCollectionItem(collectionTypeName, item) {
|
|
1300
|
-
const typeInfo = this._types.get(collectionTypeName);
|
|
1301
|
-
if (!typeInfo?.fields) return item;
|
|
1302
|
-
|
|
1303
|
-
const cleaned = { ...item };
|
|
1304
|
-
|
|
1305
|
-
for (const field of typeInfo.fields) {
|
|
1306
|
-
const { name, type: unwrapped, rawType, extensions } = field;
|
|
1307
|
-
if (cleaned[name] === undefined) continue;
|
|
1308
|
-
|
|
1309
|
-
if (extensions?.stateMachine === true) {
|
|
1310
|
-
delete cleaned[name];
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
if (unwrapped.kind === 'OBJECT' && !extensions?.relation?.embedded) {
|
|
1315
|
-
const val = cleaned[name];
|
|
1316
|
-
if (val && typeof val === 'object' && 'id' in val) {
|
|
1317
|
-
cleaned[name] = { id: val.id };
|
|
1318
|
-
}
|
|
1319
|
-
continue;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
if (isNumericScalar(unwrapped.name) && typeof cleaned[name] === 'string') {
|
|
1323
|
-
cleaned[name] = Number(cleaned[name]);
|
|
1324
|
-
}
|
|
1325
|
-
if (isDateTimeScalar(unwrapped.name) && typeof cleaned[name] === 'string') {
|
|
1326
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(cleaned[name])) {
|
|
1327
|
-
cleaned[name] = `${cleaned[name]}T00:00:00.000Z`;
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
return cleaned;
|
|
1218
|
+
this._ensureInitialized(); return transformDelta(this, collectionTypeName, delta, options);
|
|
1333
1219
|
}
|
|
1334
1220
|
|
|
1335
1221
|
// -- Guard ---------------------------------------------------------------
|
|
@@ -1339,4 +1225,12 @@ export default class SimfinityClient {
|
|
|
1339
1225
|
throw new Error('Client not initialized. Call init() first.');
|
|
1340
1226
|
}
|
|
1341
1227
|
}
|
|
1228
|
+
|
|
1229
|
+
// -- Static Helpers -------------------------------------------------------
|
|
1230
|
+
|
|
1231
|
+
static condition(field, operator, value, path) {
|
|
1232
|
+
const cond = { field, operator, value };
|
|
1233
|
+
if (path !== undefined) cond.path = path;
|
|
1234
|
+
return { conditions: [cond] };
|
|
1235
|
+
}
|
|
1342
1236
|
}
|