@comake/skl-js-engine 1.4.2 → 1.5.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/dist/SklEngine.d.ts +9 -0
- package/dist/SklEngine.d.ts.map +1 -1
- package/dist/SklEngine.js +122 -12
- package/dist/SklEngine.js.map +1 -1
- package/dist/SklEngineOptions.d.ts +32 -0
- package/dist/SklEngineOptions.d.ts.map +1 -1
- package/dist/SklEngineOptions.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/FindOptionsTypes.d.ts +4 -1
- package/dist/storage/FindOptionsTypes.d.ts.map +1 -1
- package/dist/storage/FindOptionsTypes.js.map +1 -1
- package/dist/storage/query-adapter/sparql/SparqlQueryBuilder.d.ts.map +1 -1
- package/dist/storage/query-adapter/sparql/SparqlQueryBuilder.js +21 -0
- package/dist/storage/query-adapter/sparql/SparqlQueryBuilder.js.map +1 -1
- package/dist/tools/explain-findall-sparql.d.ts +2 -0
- package/dist/tools/explain-findall-sparql.d.ts.map +1 -0
- package/dist/tools/explain-findall-sparql.js +303 -0
- package/dist/tools/explain-findall-sparql.js.map +1 -0
- package/dist/util/ReadCacheHelper.d.ts +14 -0
- package/dist/util/ReadCacheHelper.d.ts.map +1 -0
- package/dist/util/ReadCacheHelper.js +61 -0
- package/dist/util/ReadCacheHelper.js.map +1 -0
- package/package.json +2 -1
- package/src/SklEngine.ts +150 -13
- package/src/SklEngineOptions.ts +40 -0
- package/src/index.ts +1 -0
- package/src/storage/FindOptionsTypes.ts +4 -1
- package/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts +22 -0
- package/src/tools/explain-findall-sparql.ts +387 -0
- package/src/util/ReadCacheHelper.ts +64 -0
package/src/SklEngine.ts
CHANGED
|
@@ -34,7 +34,7 @@ import type { ExecutionOptions } from './JsExecutor';
|
|
|
34
34
|
import type { ICodeExecutor } from './JsExecutor/types';
|
|
35
35
|
import { Logger } from './logger';
|
|
36
36
|
import { Mapper } from './mapping/Mapper';
|
|
37
|
-
import type { SklEngineOptions } from './SklEngineOptions';
|
|
37
|
+
import type { ReadCacheOperation, ReadCachePolicy, ReadCacheStore, SklEngineOptions } from './SklEngineOptions';
|
|
38
38
|
import type { FindOperator } from './storage/FindOperator';
|
|
39
39
|
import type { FindAllOptions, FindOneOptions, FindOptionsWhere } from './storage/FindOptionsTypes';
|
|
40
40
|
import type { GroupByOptions, GroupByResponse } from './storage/GroupOptionTypes';
|
|
@@ -48,6 +48,7 @@ import { ZeroOrMorePath } from './storage/operator/ZeroOrMorePath';
|
|
|
48
48
|
import type { QueryAdapter, RawQueryResult } from './storage/query-adapter/QueryAdapter';
|
|
49
49
|
import { SparqlQueryAdapter } from './storage/query-adapter/sparql/SparqlQueryAdapter';
|
|
50
50
|
import { PerformanceLogger } from './util/PerformanceLogger';
|
|
51
|
+
import { buildReadCacheKey, ReadCacheSingleflight } from './util/ReadCacheHelper';
|
|
51
52
|
// Import './util/safeJsonStringify';
|
|
52
53
|
import type {
|
|
53
54
|
Callbacks,
|
|
@@ -82,6 +83,7 @@ export type CapabilityInterface = Record<string, CapabilityHandler>;
|
|
|
82
83
|
export type MappingResponseOption<T extends boolean> = T extends true ? JSONObject : NodeObject;
|
|
83
84
|
|
|
84
85
|
export type WriteOptions = { bypassHooks?: boolean };
|
|
86
|
+
const DEFAULT_READ_CACHE_TTL_MS = 60_000;
|
|
85
87
|
|
|
86
88
|
export class SKLEngine implements SKLEngineInterface {
|
|
87
89
|
private readonly queryAdapter: QueryAdapter;
|
|
@@ -94,6 +96,10 @@ export class SKLEngine implements SKLEngineInterface {
|
|
|
94
96
|
private readonly skdsEndpointUrl: string;
|
|
95
97
|
private readonly scriptPath: string;
|
|
96
98
|
private readonly logger: Logger;
|
|
99
|
+
private readonly readCache?: ReadCacheStore;
|
|
100
|
+
private readonly readCachePolicy?: ReadCachePolicy;
|
|
101
|
+
private readonly readCacheNamespace?: string;
|
|
102
|
+
private readonly readCacheSingleflight = new ReadCacheSingleflight();
|
|
97
103
|
public constructor(options: SklEngineOptions) {
|
|
98
104
|
this.queryAdapter = new SparqlQueryAdapter(options);
|
|
99
105
|
this.disableValidation = options.disableValidation;
|
|
@@ -101,6 +107,9 @@ export class SKLEngine implements SKLEngineInterface {
|
|
|
101
107
|
this.inputFiles = options.inputFiles;
|
|
102
108
|
this.skdsEndpointUrl = (options as any).endpointUrl;
|
|
103
109
|
this.scriptPath = (options as any).scriptPath;
|
|
110
|
+
this.readCache = options.readCache;
|
|
111
|
+
this.readCachePolicy = options.readCachePolicy;
|
|
112
|
+
this.readCacheNamespace = options.readCacheNamespace;
|
|
104
113
|
if (options.functions) {
|
|
105
114
|
this.functions = Object.fromEntries(
|
|
106
115
|
Object.entries(options.functions).map(([key, func]) => [
|
|
@@ -145,21 +154,120 @@ export class SKLEngine implements SKLEngineInterface {
|
|
|
145
154
|
return await this.queryAdapter.executeRawConstructQuery(query, frame);
|
|
146
155
|
}
|
|
147
156
|
|
|
157
|
+
private cloneForReadCache<T>(value: T): T {
|
|
158
|
+
try {
|
|
159
|
+
return structuredClone(value);
|
|
160
|
+
} catch {
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private stripBypassCacheFromFindOneOptions(options?: FindOneOptions): FindOneOptions | undefined {
|
|
166
|
+
if (!options) {
|
|
167
|
+
return options;
|
|
168
|
+
}
|
|
169
|
+
const { bypassCache, ...rest } = options;
|
|
170
|
+
return rest;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private stripBypassCacheFromFindAllOptions(options?: FindAllOptions): FindAllOptions | undefined {
|
|
174
|
+
if (!options) {
|
|
175
|
+
return options;
|
|
176
|
+
}
|
|
177
|
+
const { bypassCache, ...rest } = options;
|
|
178
|
+
return rest;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private stripBypassCacheFromWhere(where?: FindOptionsWhere): FindOptionsWhere | undefined {
|
|
182
|
+
if (!where) {
|
|
183
|
+
return where;
|
|
184
|
+
}
|
|
185
|
+
const { bypassCache, ...rest } = where;
|
|
186
|
+
return rest;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async runReadWithCache<T>(params: {
|
|
190
|
+
operation: ReadCacheOperation;
|
|
191
|
+
args: readonly unknown[];
|
|
192
|
+
bypassCache?: boolean;
|
|
193
|
+
execute: () => Promise<T>;
|
|
194
|
+
}): Promise<T> {
|
|
195
|
+
if (params.bypassCache) {
|
|
196
|
+
return await params.execute();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!this.readCache || !this.readCachePolicy) {
|
|
200
|
+
return await params.execute();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const policyDecision = this.readCachePolicy({
|
|
204
|
+
operation: params.operation,
|
|
205
|
+
args: params.args,
|
|
206
|
+
endpointUrl: this.skdsEndpointUrl,
|
|
207
|
+
namespace: this.readCacheNamespace
|
|
208
|
+
});
|
|
209
|
+
if (!policyDecision.cache) {
|
|
210
|
+
return await params.execute();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const ttlMs = policyDecision.ttlMs ?? DEFAULT_READ_CACHE_TTL_MS;
|
|
214
|
+
if (ttlMs <= 0) {
|
|
215
|
+
return await params.execute();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const cacheKey = buildReadCacheKey({
|
|
219
|
+
operation: params.operation,
|
|
220
|
+
args: params.args,
|
|
221
|
+
endpointUrl: this.skdsEndpointUrl,
|
|
222
|
+
namespace: this.readCacheNamespace,
|
|
223
|
+
keyHint: policyDecision.keyHint
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return await this.readCacheSingleflight.do(cacheKey, async() => {
|
|
227
|
+
try {
|
|
228
|
+
const cached = await this.readCache?.get(cacheKey);
|
|
229
|
+
if (cached !== undefined) {
|
|
230
|
+
return this.cloneForReadCache(cached as T);
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
this.logger.debug('Read cache lookup failed; continuing without cache', error as any);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const result = await params.execute();
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
await this.readCache?.set(cacheKey, this.cloneForReadCache(result), ttlMs);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
this.logger.debug('Read cache write failed; continuing without cache', error as any);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return result;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
148
248
|
public async find(options?: FindOneOptions): Promise<Entity> {
|
|
249
|
+
const bypassCache = options?.bypassCache;
|
|
250
|
+
const optionsWithoutBypass = this.stripBypassCacheFromFindOneOptions(options);
|
|
251
|
+
|
|
149
252
|
return PerformanceLogger.withSpanRoot('SklEngine.find', async() => {
|
|
150
253
|
const context = {
|
|
151
254
|
entities: [],
|
|
152
255
|
operation: 'find',
|
|
153
|
-
operationParameters: { options },
|
|
256
|
+
operationParameters: { options: optionsWithoutBypass },
|
|
154
257
|
sklEngine: this
|
|
155
258
|
};
|
|
156
259
|
|
|
157
260
|
await globalHooks.execute(HookTypes.READ, HookStages.BEFORE, context);
|
|
158
261
|
|
|
159
262
|
try {
|
|
160
|
-
const entity = await this.
|
|
263
|
+
const entity = await this.runReadWithCache({
|
|
264
|
+
operation: 'find',
|
|
265
|
+
args: [optionsWithoutBypass],
|
|
266
|
+
bypassCache,
|
|
267
|
+
execute: async() => await this.queryAdapter.find(optionsWithoutBypass)
|
|
268
|
+
});
|
|
161
269
|
if (!entity) {
|
|
162
|
-
throw new Error(`No schema found with fields matching ${JSON.stringify(
|
|
270
|
+
throw new Error(`No schema found with fields matching ${JSON.stringify(optionsWithoutBypass)}`);
|
|
163
271
|
}
|
|
164
272
|
|
|
165
273
|
const updatedContext = { ...context, entities: [entity]};
|
|
@@ -170,31 +278,39 @@ export class SKLEngine implements SKLEngineInterface {
|
|
|
170
278
|
await globalHooks.execute(HookTypes.READ, HookStages.ERROR, context, error);
|
|
171
279
|
throw error;
|
|
172
280
|
}
|
|
173
|
-
}, { options });
|
|
281
|
+
}, { options: optionsWithoutBypass });
|
|
174
282
|
}
|
|
175
283
|
|
|
176
284
|
public async findBy(where: FindOptionsWhere, notFoundErrorMessage?: string): Promise<Entity> {
|
|
285
|
+
const bypassCache = where?.bypassCache;
|
|
286
|
+
const whereWithoutBypass = this.stripBypassCacheFromWhere(where)!;
|
|
287
|
+
|
|
177
288
|
return PerformanceLogger.withSpanRoot('SklEngine.findBy', async() => {
|
|
178
289
|
const context = {
|
|
179
290
|
entities: [],
|
|
180
291
|
operation: 'findBy',
|
|
181
|
-
operationParameters: { where },
|
|
292
|
+
operationParameters: { where: whereWithoutBypass },
|
|
182
293
|
sklEngine: this
|
|
183
294
|
};
|
|
184
295
|
await globalHooks.execute(HookTypes.READ, HookStages.BEFORE, context);
|
|
185
296
|
try {
|
|
186
|
-
const entity = await this.
|
|
297
|
+
const entity = await this.runReadWithCache({
|
|
298
|
+
operation: 'findBy',
|
|
299
|
+
args: [whereWithoutBypass, notFoundErrorMessage],
|
|
300
|
+
bypassCache,
|
|
301
|
+
execute: async() => await this.queryAdapter.findBy(whereWithoutBypass)
|
|
302
|
+
});
|
|
187
303
|
if (entity) {
|
|
188
304
|
const updatedContext = { ...context, entities: [entity]};
|
|
189
305
|
await globalHooks.execute(HookTypes.READ, HookStages.AFTER, updatedContext, entity);
|
|
190
306
|
return entity;
|
|
191
307
|
}
|
|
192
|
-
throw new Error(notFoundErrorMessage ?? `No schema found with fields matching ${JSON.stringify(
|
|
308
|
+
throw new Error(notFoundErrorMessage ?? `No schema found with fields matching ${JSON.stringify(whereWithoutBypass)}`);
|
|
193
309
|
} catch (error: unknown) {
|
|
194
310
|
await globalHooks.execute(HookTypes.READ, HookStages.ERROR, context, error);
|
|
195
311
|
throw error;
|
|
196
312
|
}
|
|
197
|
-
}, { where });
|
|
313
|
+
}, { where: whereWithoutBypass });
|
|
198
314
|
}
|
|
199
315
|
|
|
200
316
|
public async findByIfExists(options: FindOptionsWhere): Promise<Entity | undefined> {
|
|
@@ -207,16 +323,24 @@ export class SKLEngine implements SKLEngineInterface {
|
|
|
207
323
|
}
|
|
208
324
|
|
|
209
325
|
public async findAll(options?: FindAllOptions): Promise<Entity[]> {
|
|
326
|
+
const bypassCache = options?.bypassCache;
|
|
327
|
+
const optionsWithoutBypass = this.stripBypassCacheFromFindAllOptions(options);
|
|
328
|
+
|
|
210
329
|
return PerformanceLogger.withSpanRoot('SklEngine.findAll', async() => {
|
|
211
330
|
const context = {
|
|
212
331
|
entities: [],
|
|
213
332
|
operation: 'findAll',
|
|
214
|
-
operationParameters: { options },
|
|
333
|
+
operationParameters: { options: optionsWithoutBypass },
|
|
215
334
|
sklEngine: this
|
|
216
335
|
};
|
|
217
336
|
await globalHooks.execute(HookTypes.READ, HookStages.BEFORE, context);
|
|
218
337
|
try {
|
|
219
|
-
const entities = await this.
|
|
338
|
+
const entities = await this.runReadWithCache({
|
|
339
|
+
operation: 'findAll',
|
|
340
|
+
args: [optionsWithoutBypass],
|
|
341
|
+
bypassCache,
|
|
342
|
+
execute: async() => await this.queryAdapter.findAll(optionsWithoutBypass)
|
|
343
|
+
});
|
|
220
344
|
const updatedContext = { ...context, entities };
|
|
221
345
|
await globalHooks.execute(HookTypes.READ, HookStages.AFTER, updatedContext, entities);
|
|
222
346
|
return entities;
|
|
@@ -224,7 +348,7 @@ export class SKLEngine implements SKLEngineInterface {
|
|
|
224
348
|
await globalHooks.execute(HookTypes.READ, HookStages.ERROR, context, error);
|
|
225
349
|
throw error;
|
|
226
350
|
}
|
|
227
|
-
}, { options });
|
|
351
|
+
}, { options: optionsWithoutBypass });
|
|
228
352
|
}
|
|
229
353
|
|
|
230
354
|
public async groupBy(options: GroupByOptions): Promise<GroupByResponse> {
|
|
@@ -274,7 +398,20 @@ export class SKLEngine implements SKLEngineInterface {
|
|
|
274
398
|
}
|
|
275
399
|
|
|
276
400
|
public async count(options?: FindAllOptions): Promise<number> {
|
|
277
|
-
|
|
401
|
+
const bypassCache = options?.bypassCache;
|
|
402
|
+
const optionsWithoutBypass = this.stripBypassCacheFromFindAllOptions(options);
|
|
403
|
+
|
|
404
|
+
return PerformanceLogger.withSpanRoot(
|
|
405
|
+
'SklEngine.count',
|
|
406
|
+
async() =>
|
|
407
|
+
await this.runReadWithCache({
|
|
408
|
+
operation: 'count',
|
|
409
|
+
args: [optionsWithoutBypass],
|
|
410
|
+
bypassCache,
|
|
411
|
+
execute: async() => await this.queryAdapter.count(optionsWithoutBypass)
|
|
412
|
+
}),
|
|
413
|
+
{ options: optionsWithoutBypass }
|
|
414
|
+
);
|
|
278
415
|
}
|
|
279
416
|
|
|
280
417
|
public async save(entity: Entity, options?: WriteOptions): Promise<Entity>;
|
package/src/SklEngineOptions.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import type { SparqlQueryAdapterOptions } from './storage/query-adapter/sparql/SparqlQueryAdapterOptions';
|
|
2
2
|
import type { Callbacks } from './util/Types';
|
|
3
3
|
|
|
4
|
+
export type ReadCacheOperation = 'find' | 'findBy' | 'findAll' | 'count';
|
|
5
|
+
|
|
6
|
+
export interface ReadCacheStore {
|
|
7
|
+
get: (key: string) => Promise<unknown | undefined>;
|
|
8
|
+
set: (key: string, value: unknown, ttlMs: number) => Promise<void>;
|
|
9
|
+
delete?: (key: string) => Promise<void>;
|
|
10
|
+
clear?: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ReadCachePolicyInput {
|
|
14
|
+
operation: ReadCacheOperation;
|
|
15
|
+
args: readonly unknown[];
|
|
16
|
+
endpointUrl?: string;
|
|
17
|
+
namespace?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ReadCachePolicyDecision {
|
|
21
|
+
cache: boolean;
|
|
22
|
+
ttlMs?: number;
|
|
23
|
+
keyHint?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ReadCachePolicy = (input: ReadCachePolicyInput) => ReadCachePolicyDecision;
|
|
27
|
+
|
|
4
28
|
export type SklEngineOptions = SparqlQueryAdapterOptions & {
|
|
5
29
|
/**
|
|
6
30
|
* Callbacks to execute upon events.
|
|
@@ -25,4 +49,20 @@ export type SklEngineOptions = SparqlQueryAdapterOptions & {
|
|
|
25
49
|
* The path to the executor script.
|
|
26
50
|
*/
|
|
27
51
|
readonly scriptPath?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Optional read cache store used for select read operations.
|
|
55
|
+
* Cache decisions are made by readCachePolicy.
|
|
56
|
+
*/
|
|
57
|
+
readonly readCache?: ReadCacheStore;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Optional policy that decides whether a given read operation should be cached.
|
|
61
|
+
*/
|
|
62
|
+
readonly readCachePolicy?: ReadCachePolicy;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Optional namespace to scope cache keys (for example tenant/account isolation).
|
|
66
|
+
*/
|
|
67
|
+
readonly readCacheNamespace?: string;
|
|
28
68
|
};
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './hooks/types';
|
|
|
7
7
|
export * from './JsExecutor';
|
|
8
8
|
export * from './mapping/Mapper';
|
|
9
9
|
export * from './SklEngine';
|
|
10
|
+
export * from './SklEngineOptions';
|
|
10
11
|
// Storage
|
|
11
12
|
export * from './storage/FindOperator';
|
|
12
13
|
export * from './storage/FindOptionsTypes';
|
|
@@ -15,6 +15,7 @@ export interface FindOneOptions {
|
|
|
15
15
|
skipFraming?: boolean;
|
|
16
16
|
group?: Variable;
|
|
17
17
|
entitySelectVariable?: Variable;
|
|
18
|
+
bypassCache?: boolean;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export type FindOptionsRelationsValue =
|
|
@@ -78,7 +79,8 @@ export interface FindOptionsWhere {
|
|
|
78
79
|
binds?: BindPattern[];
|
|
79
80
|
and?: FindOptionsWhere[];
|
|
80
81
|
or?: FindOptionsWhere[];
|
|
81
|
-
|
|
82
|
+
bypassCache?: boolean;
|
|
83
|
+
[k: string]: FindOptionsWhereField | BindPattern[] | FindOptionsWhere[] | boolean | undefined;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
// Add these new types
|
|
@@ -106,4 +108,5 @@ export interface FindCountOptions {
|
|
|
106
108
|
relations?: FindOptionsRelations;
|
|
107
109
|
order?: FindOptionsOrder;
|
|
108
110
|
offset?: number;
|
|
111
|
+
bypassCache?: boolean;
|
|
109
112
|
}
|
|
@@ -341,6 +341,28 @@ export class SparqlQueryBuilder {
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
private createWhereQueryData(subject: Variable, where?: FindOptionsWhere, isTopLevel = false): WhereQueryData {
|
|
344
|
+
|
|
345
|
+
// Optimization: if the top-level where contains an ID constraint plus additional filters,
|
|
346
|
+
// also push the ID constraint into graphWhere. This allows the outer CONSTRUCT query
|
|
347
|
+
// to be restricted by VALUES (?entity) which can be substantially faster on some SPARQL backends.
|
|
348
|
+
//
|
|
349
|
+
// Non-breaking: keep the existing behavior where `id` participates in the main WHERE too.
|
|
350
|
+
if (isTopLevel && where && 'id' in where && Object.keys(where).length > 1) {
|
|
351
|
+
const { id, ...restWhere } = where as FindOptionsWhere & { id: IdFindOptionsWhereField };
|
|
352
|
+
if (id !== undefined) {
|
|
353
|
+
const idQueryData = this.createWhereQueryDataForIdValue(subject, id);
|
|
354
|
+
const restQueryData = this.createWhereQueryData(subject, restWhere, false);
|
|
355
|
+
return {
|
|
356
|
+
...restQueryData,
|
|
357
|
+
values: [ ...idQueryData.values, ...restQueryData.values ],
|
|
358
|
+
filters: [ ...idQueryData.filters, ...restQueryData.filters ],
|
|
359
|
+
triples: [ ...idQueryData.triples, ...restQueryData.triples ],
|
|
360
|
+
graphValues: [ ...idQueryData.values, ...restQueryData.graphValues ],
|
|
361
|
+
graphFilters: [ ...idQueryData.filters, ...restQueryData.graphFilters ],
|
|
362
|
+
graphTriples: [ ...idQueryData.triples, ...restQueryData.graphTriples ]
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
344
366
|
if (isTopLevel && Object.keys(where ?? {}).length === 1 && 'id' in where!) {
|
|
345
367
|
const { values, filters, triples } = this.createWhereQueryDataForIdValue(subject, where.id!);
|
|
346
368
|
return {
|