@simtlix/simfinity-js-client 1.0.2 → 1.1.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/.claude/worktrees/flamboyant-cartwright/.claude/settings.local.json +18 -0
- package/.claude/worktrees/flamboyant-cartwright/IMPLEMENTATION_PROMPT.md +1137 -0
- package/.claude/worktrees/flamboyant-cartwright/LICENSE +201 -0
- package/.claude/worktrees/flamboyant-cartwright/README.md +491 -0
- package/.claude/worktrees/flamboyant-cartwright/examples/demo.js +402 -0
- package/.claude/worktrees/flamboyant-cartwright/package-lock.json +107 -0
- package/.claude/worktrees/flamboyant-cartwright/package.json +29 -0
- package/.claude/worktrees/flamboyant-cartwright/src/SimfinityClient.js +1398 -0
- package/README.md +114 -1
- package/examples/demo.js +52 -0
- package/package.json +1 -1
- 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 +71 -2
package/README.md
CHANGED
|
@@ -16,16 +16,29 @@ import SimfinityClient from '@simtlix/simfinity-js-client';
|
|
|
16
16
|
const client = new SimfinityClient('http://localhost:3000/graphql');
|
|
17
17
|
await client.init();
|
|
18
18
|
|
|
19
|
+
// Optional: attach auth headers (perform login in your app; this client does not include login)
|
|
20
|
+
const tokenStore = { accessToken: null };
|
|
21
|
+
const authedClient = new SimfinityClient('http://localhost:3000/graphql', {
|
|
22
|
+
prepareHeaders(headers) {
|
|
23
|
+
if (tokenStore.accessToken) {
|
|
24
|
+
headers['Authorization'] = `Bearer ${tokenStore.accessToken}`;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
await authedClient.init();
|
|
29
|
+
|
|
19
30
|
const series = await client.find('serie').exec();
|
|
20
31
|
console.log(series);
|
|
21
32
|
```
|
|
22
33
|
|
|
23
34
|
## API Reference
|
|
24
35
|
|
|
25
|
-
### `new SimfinityClient(endpoint)`
|
|
36
|
+
### `new SimfinityClient(endpoint [, options])`
|
|
26
37
|
|
|
27
38
|
Creates a new client instance pointing at the given GraphQL endpoint.
|
|
28
39
|
|
|
40
|
+
- **`options.prepareHeaders(headers)`** (optional): invoked before every GraphQL HTTP request. Mutate the plain `headers` object to add credentials (for example `Authorization`). The client does not implement login — call your auth API separately, then set tokens in a closure or store that `prepareHeaders` reads.
|
|
41
|
+
|
|
29
42
|
### `client.init()`
|
|
30
43
|
|
|
31
44
|
Runs an introspection query against the endpoint and builds internal registries of types, queries, and mutations — including Simfinity-specific field extensions (relation metadata, state machine flags, read-only markers). **Must be called before any other method.**
|
|
@@ -70,6 +83,106 @@ Filter by a collection field using an array of term objects:
|
|
|
70
83
|
])
|
|
71
84
|
```
|
|
72
85
|
|
|
86
|
+
#### `.or(groups)` / `.and(groups)`
|
|
87
|
+
|
|
88
|
+
Add AND/OR logical filter groups for complex boolean conditions. Each method accepts an array of `QLFilterGroup` objects and can be called multiple times (groups accumulate). These combine with existing flat `.where()` filters via AND at the top level.
|
|
89
|
+
|
|
90
|
+
A `QLFilterGroup` is a plain object with optional `AND`, `OR`, and `conditions` fields:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
{
|
|
94
|
+
conditions: [{ field: 'category', operator: 'EQ', value: 'Drama' }],
|
|
95
|
+
AND: [/* nested QLFilterGroup objects */],
|
|
96
|
+
OR: [/* nested QLFilterGroup objects */],
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Simple OR** -- return series in either Drama or Comedy:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
const results = await client.find('serie')
|
|
104
|
+
.or([
|
|
105
|
+
SimfinityClient.condition('categories', 'EQ', 'Drama'),
|
|
106
|
+
SimfinityClient.condition('categories', 'EQ', 'Comedy'),
|
|
107
|
+
])
|
|
108
|
+
.fields('id name categories')
|
|
109
|
+
.exec();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Flat filters combined with OR** -- flat `.where()` is ANDed with the OR group:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
const results = await client.find('serie')
|
|
116
|
+
.where('categories', 'NE', 'Horror')
|
|
117
|
+
.or([
|
|
118
|
+
SimfinityClient.condition('name', 'LIKE', 'Breaking'),
|
|
119
|
+
SimfinityClient.condition('name', 'LIKE', 'Game'),
|
|
120
|
+
])
|
|
121
|
+
.fields('id name categories')
|
|
122
|
+
.exec();
|
|
123
|
+
// Equivalent to: categories != "Horror" AND (name LIKE "Breaking" OR name LIKE "Game")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Nested AND inside OR**:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
const results = await client.find('serie')
|
|
130
|
+
.or([
|
|
131
|
+
{ AND: [
|
|
132
|
+
SimfinityClient.condition('categories', 'EQ', 'Drama'),
|
|
133
|
+
SimfinityClient.condition('year', 'GTE', 2020),
|
|
134
|
+
]},
|
|
135
|
+
{ AND: [
|
|
136
|
+
SimfinityClient.condition('categories', 'EQ', 'Comedy'),
|
|
137
|
+
SimfinityClient.condition('year', 'GTE', 2015),
|
|
138
|
+
]},
|
|
139
|
+
])
|
|
140
|
+
.exec();
|
|
141
|
+
// Equivalent to: (categories = "Drama" AND year >= 2020) OR (categories = "Comedy" AND year >= 2015)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**AND with nested OR groups**:
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
const results = await client.find('serie')
|
|
148
|
+
.where('year', 'GTE', 2000)
|
|
149
|
+
.and([
|
|
150
|
+
{ OR: [
|
|
151
|
+
SimfinityClient.condition('categories', 'EQ', 'Drama'),
|
|
152
|
+
SimfinityClient.condition('categories', 'EQ', 'Comedy'),
|
|
153
|
+
]},
|
|
154
|
+
{ OR: [
|
|
155
|
+
SimfinityClient.condition('director', 'EQ', 'UK', 'country'),
|
|
156
|
+
SimfinityClient.condition('director', 'EQ', 'US', 'country'),
|
|
157
|
+
]},
|
|
158
|
+
])
|
|
159
|
+
.exec();
|
|
160
|
+
// Equivalent to: year >= 2000 AND (Drama OR Comedy) AND (UK OR US director)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Relationship path filtering** -- use the `path` parameter for object field conditions:
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
.or([
|
|
167
|
+
SimfinityClient.condition('director', 'LIKE', 'Adams', 'name'),
|
|
168
|
+
SimfinityClient.condition('director', 'LIKE', 'Nolan', 'name'),
|
|
169
|
+
])
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The Aggregate Builder also supports `.or()` and `.and()` with identical behavior. The server enforces a maximum nesting depth of 5 levels.
|
|
173
|
+
|
|
174
|
+
#### `SimfinityClient.condition(field, operator, value [, path])`
|
|
175
|
+
|
|
176
|
+
Static convenience factory for creating a filter group with a single condition. Returns a `QLFilterGroup` object:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
SimfinityClient.condition('category', 'EQ', 'Drama')
|
|
180
|
+
// Equivalent to: { conditions: [{ field: 'category', operator: 'EQ', value: 'Drama' }] }
|
|
181
|
+
|
|
182
|
+
SimfinityClient.condition('author', 'LIKE', 'Adams', 'name')
|
|
183
|
+
// Equivalent to: { conditions: [{ field: 'author', operator: 'LIKE', value: 'Adams', path: 'name' }] }
|
|
184
|
+
```
|
|
185
|
+
|
|
73
186
|
#### `.joinObject(path, fields)`
|
|
74
187
|
|
|
75
188
|
Include a related object's fields in the selection.
|
package/examples/demo.js
CHANGED
|
@@ -344,6 +344,58 @@ const log = (label, data) => {
|
|
|
344
344
|
totalCount: extensions?.count,
|
|
345
345
|
});
|
|
346
346
|
|
|
347
|
+
// -----------------------------------------------------------------------
|
|
348
|
+
// 28. find() with OR filter
|
|
349
|
+
// -----------------------------------------------------------------------
|
|
350
|
+
const orResults = await client.find(serieType)
|
|
351
|
+
.or([
|
|
352
|
+
SimfinityClient.condition('categories', 'EQ', 'Drama'),
|
|
353
|
+
SimfinityClient.condition('categories', 'EQ', 'Comedy'),
|
|
354
|
+
])
|
|
355
|
+
.fields('id name categories')
|
|
356
|
+
.exec();
|
|
357
|
+
log('28. find() -- OR filter (Drama OR Comedy)', orResults);
|
|
358
|
+
|
|
359
|
+
// -----------------------------------------------------------------------
|
|
360
|
+
// 29. find() with flat where + OR combined
|
|
361
|
+
// -----------------------------------------------------------------------
|
|
362
|
+
const combinedOrResults = await client.find(serieType)
|
|
363
|
+
.where('categories', 'NE', 'Horror')
|
|
364
|
+
.or([
|
|
365
|
+
SimfinityClient.condition('name', 'LIKE', 'Breaking'),
|
|
366
|
+
SimfinityClient.condition('name', 'LIKE', 'Game'),
|
|
367
|
+
])
|
|
368
|
+
.fields('id name categories')
|
|
369
|
+
.exec();
|
|
370
|
+
log('29. find() -- flat where + OR (not Horror AND (name LIKE Breaking OR Game))', combinedOrResults);
|
|
371
|
+
|
|
372
|
+
// -----------------------------------------------------------------------
|
|
373
|
+
// 30. find() with AND containing nested OR
|
|
374
|
+
// -----------------------------------------------------------------------
|
|
375
|
+
const nestedAndOrResults = await client.find(serieType)
|
|
376
|
+
.and([
|
|
377
|
+
{ OR: [
|
|
378
|
+
SimfinityClient.condition('categories', 'EQ', 'Drama'),
|
|
379
|
+
SimfinityClient.condition('categories', 'EQ', 'Comedy'),
|
|
380
|
+
]},
|
|
381
|
+
])
|
|
382
|
+
.fields('id name categories')
|
|
383
|
+
.exec();
|
|
384
|
+
log('30. find() -- AND with nested OR groups', nestedAndOrResults);
|
|
385
|
+
|
|
386
|
+
// -----------------------------------------------------------------------
|
|
387
|
+
// 31. aggregate() with OR filter
|
|
388
|
+
// -----------------------------------------------------------------------
|
|
389
|
+
const aggOrResults = await client.aggregate(seasonType)
|
|
390
|
+
.groupBy('serie')
|
|
391
|
+
.fact('COUNT', 'seasonCount', 'id')
|
|
392
|
+
.or([
|
|
393
|
+
SimfinityClient.condition('year', 'GTE', 2020),
|
|
394
|
+
SimfinityClient.condition('year', 'LTE', 2010),
|
|
395
|
+
])
|
|
396
|
+
.exec();
|
|
397
|
+
log('31. aggregate() -- OR filter (year >= 2020 OR year <= 2010)', aggOrResults);
|
|
398
|
+
|
|
347
399
|
console.log('\n' + '='.repeat(60));
|
|
348
400
|
console.log('Demo complete.');
|
|
349
401
|
console.log('='.repeat(60));
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/SimfinityClient.js
CHANGED
|
@@ -227,6 +227,30 @@ class QueryBuilder {
|
|
|
227
227
|
return this;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
or(groups) {
|
|
231
|
+
if (!Array.isArray(groups)) {
|
|
232
|
+
throw new Error('or() expects an array of QLFilterGroup objects');
|
|
233
|
+
}
|
|
234
|
+
if (this._variables.OR) {
|
|
235
|
+
this._variables.OR.push(...groups);
|
|
236
|
+
} else {
|
|
237
|
+
this._variables.OR = [...groups];
|
|
238
|
+
}
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
and(groups) {
|
|
243
|
+
if (!Array.isArray(groups)) {
|
|
244
|
+
throw new Error('and() expects an array of QLFilterGroup objects');
|
|
245
|
+
}
|
|
246
|
+
if (this._variables.AND) {
|
|
247
|
+
this._variables.AND.push(...groups);
|
|
248
|
+
} else {
|
|
249
|
+
this._variables.AND = [...groups];
|
|
250
|
+
}
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
|
|
230
254
|
// -- Relation Field Selection --------------------------------------------
|
|
231
255
|
|
|
232
256
|
joinCollection(path, fields, filter) {
|
|
@@ -418,6 +442,30 @@ class AggregateBuilder {
|
|
|
418
442
|
return this;
|
|
419
443
|
}
|
|
420
444
|
|
|
445
|
+
or(groups) {
|
|
446
|
+
if (!Array.isArray(groups)) {
|
|
447
|
+
throw new Error('or() expects an array of QLFilterGroup objects');
|
|
448
|
+
}
|
|
449
|
+
if (this._variables.OR) {
|
|
450
|
+
this._variables.OR.push(...groups);
|
|
451
|
+
} else {
|
|
452
|
+
this._variables.OR = [...groups];
|
|
453
|
+
}
|
|
454
|
+
return this;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
and(groups) {
|
|
458
|
+
if (!Array.isArray(groups)) {
|
|
459
|
+
throw new Error('and() expects an array of QLFilterGroup objects');
|
|
460
|
+
}
|
|
461
|
+
if (this._variables.AND) {
|
|
462
|
+
this._variables.AND.push(...groups);
|
|
463
|
+
} else {
|
|
464
|
+
this._variables.AND = [...groups];
|
|
465
|
+
}
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
|
|
421
469
|
page(page, size, count) {
|
|
422
470
|
const pagination = { page, size };
|
|
423
471
|
if (count !== undefined) pagination.count = count;
|
|
@@ -481,7 +529,14 @@ class AggregateBuilder {
|
|
|
481
529
|
// ---------------------------------------------------------------------------
|
|
482
530
|
|
|
483
531
|
export default class SimfinityClient {
|
|
484
|
-
|
|
532
|
+
/**
|
|
533
|
+
* @param {string} endpoint GraphQL HTTP endpoint URL
|
|
534
|
+
* @param {{ prepareHeaders?: (headers: Record<string, string>) => void }} [options]
|
|
535
|
+
* Optional `prepareHeaders` runs before each request; mutate `headers` to add auth
|
|
536
|
+
* (e.g. `headers['Authorization'] = 'Bearer …'`). Login is not part of this client —
|
|
537
|
+
* perform auth in your app and set tokens inside this callback or a closure it sees.
|
|
538
|
+
*/
|
|
539
|
+
constructor(endpoint, options = {}) {
|
|
485
540
|
this._endpoint = endpoint;
|
|
486
541
|
this._types = new Map();
|
|
487
542
|
this._queries = new Map();
|
|
@@ -491,6 +546,8 @@ export default class SimfinityClient {
|
|
|
491
546
|
this._typeNameToAggregate = new Map();
|
|
492
547
|
this._queryNameToType = new Map();
|
|
493
548
|
this._initialized = false;
|
|
549
|
+
this._prepareHeaders =
|
|
550
|
+
typeof options.prepareHeaders === 'function' ? options.prepareHeaders : null;
|
|
494
551
|
}
|
|
495
552
|
|
|
496
553
|
// -- Initialization ------------------------------------------------------
|
|
@@ -664,9 +721,13 @@ export default class SimfinityClient {
|
|
|
664
721
|
if (variables && Object.keys(variables).length > 0) {
|
|
665
722
|
body.variables = variables;
|
|
666
723
|
}
|
|
724
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
725
|
+
if (this._prepareHeaders) {
|
|
726
|
+
this._prepareHeaders(headers);
|
|
727
|
+
}
|
|
667
728
|
const response = await _fetch(this._endpoint, {
|
|
668
729
|
method: 'POST',
|
|
669
|
-
headers
|
|
730
|
+
headers,
|
|
670
731
|
body: JSON.stringify(body),
|
|
671
732
|
});
|
|
672
733
|
return response.json();
|
|
@@ -1326,4 +1387,12 @@ export default class SimfinityClient {
|
|
|
1326
1387
|
throw new Error('Client not initialized. Call init() first.');
|
|
1327
1388
|
}
|
|
1328
1389
|
}
|
|
1390
|
+
|
|
1391
|
+
// -- Static Helpers -------------------------------------------------------
|
|
1392
|
+
|
|
1393
|
+
static condition(field, operator, value, path) {
|
|
1394
|
+
const cond = { field, operator, value };
|
|
1395
|
+
if (path !== undefined) cond.path = path;
|
|
1396
|
+
return { conditions: [cond] };
|
|
1397
|
+
}
|
|
1329
1398
|
}
|