@_linked/core 0.0.1 → 1.0.0-next.20260216062729

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/LICENSE +1 -1
  3. package/README.md +147 -92
  4. package/lib/cjs/queries/SelectQuery.d.ts +5 -0
  5. package/lib/cjs/queries/SelectQuery.js +14 -0
  6. package/lib/cjs/queries/SelectQuery.js.map +1 -1
  7. package/lib/cjs/shapes/SHACL.d.ts +1 -0
  8. package/lib/cjs/shapes/SHACL.js +82 -2
  9. package/lib/cjs/shapes/SHACL.js.map +1 -1
  10. package/lib/cjs/shapes/Shape.d.ts +13 -1
  11. package/lib/cjs/shapes/Shape.js +6 -0
  12. package/lib/cjs/shapes/Shape.js.map +1 -1
  13. package/lib/cjs/test-helpers/query-fixtures.d.ts +583 -0
  14. package/lib/cjs/test-helpers/query-fixtures.js +39 -1
  15. package/lib/cjs/test-helpers/query-fixtures.js.map +1 -1
  16. package/lib/esm/queries/SelectQuery.d.ts +5 -0
  17. package/lib/esm/queries/SelectQuery.js +14 -0
  18. package/lib/esm/queries/SelectQuery.js.map +1 -1
  19. package/lib/esm/shapes/SHACL.d.ts +1 -0
  20. package/lib/esm/shapes/SHACL.js +82 -2
  21. package/lib/esm/shapes/SHACL.js.map +1 -1
  22. package/lib/esm/shapes/Shape.d.ts +13 -1
  23. package/lib/esm/shapes/Shape.js +6 -0
  24. package/lib/esm/shapes/Shape.js.map +1 -1
  25. package/lib/esm/test-helpers/query-fixtures.d.ts +583 -0
  26. package/lib/esm/test-helpers/query-fixtures.js +38 -0
  27. package/lib/esm/test-helpers/query-fixtures.js.map +1 -1
  28. package/package.json +17 -1
  29. package/.context/notes.md +0 -0
  30. package/.context/todos.md +0 -0
  31. package/AGENTS.md +0 -59
  32. package/docs/001-core-extraction.md +0 -305
  33. package/jest.config.js +0 -25
  34. package/scripts/dual-package.js +0 -25
  35. package/src/collections/CoreMap.ts +0 -127
  36. package/src/collections/CoreSet.ts +0 -171
  37. package/src/collections/ShapeSet.ts +0 -18
  38. package/src/index.ts +0 -88
  39. package/src/interfaces/ICoreIterable.ts +0 -35
  40. package/src/interfaces/IFileStore.ts +0 -28
  41. package/src/interfaces/IQuadStore.ts +0 -16
  42. package/src/interfaces/IQueryParser.ts +0 -51
  43. package/src/ontologies/lincd.ts +0 -25
  44. package/src/ontologies/npm.ts +0 -15
  45. package/src/ontologies/owl.ts +0 -26
  46. package/src/ontologies/rdf.ts +0 -32
  47. package/src/ontologies/rdfs.ts +0 -38
  48. package/src/ontologies/shacl.ts +0 -136
  49. package/src/ontologies/xsd.ts +0 -47
  50. package/src/package.ts +0 -11
  51. package/src/queries/CreateQuery.ts +0 -41
  52. package/src/queries/DeleteQuery.ts +0 -54
  53. package/src/queries/MutationQuery.ts +0 -287
  54. package/src/queries/QueryContext.ts +0 -41
  55. package/src/queries/QueryFactory.ts +0 -275
  56. package/src/queries/QueryParser.ts +0 -79
  57. package/src/queries/SelectQuery.ts +0 -2101
  58. package/src/queries/UpdateQuery.ts +0 -47
  59. package/src/shapes/List.ts +0 -52
  60. package/src/shapes/SHACL.ts +0 -653
  61. package/src/shapes/Shape.ts +0 -282
  62. package/src/test-helpers/query-fixtures.ts +0 -313
  63. package/src/tests/core-utils.test.ts +0 -286
  64. package/src/tests/metadata.test.ts +0 -65
  65. package/src/tests/query.test.ts +0 -599
  66. package/src/tests/query.types.test.ts +0 -606
  67. package/src/tests/store-routing.test.ts +0 -133
  68. package/src/utils/LinkedErrorLogging.ts +0 -25
  69. package/src/utils/LinkedFileStorage.ts +0 -75
  70. package/src/utils/LinkedStorage.ts +0 -120
  71. package/src/utils/NameSpace.ts +0 -5
  72. package/src/utils/NodeReference.ts +0 -16
  73. package/src/utils/Package.ts +0 -681
  74. package/src/utils/Prefix.ts +0 -108
  75. package/src/utils/ShapeClass.ts +0 -335
  76. package/src/utils/Types.ts +0 -19
  77. package/src/utils/URI.ts +0 -40
  78. package/src/utils/cached.ts +0 -53
  79. package/tsconfig-cjs.json +0 -8
  80. package/tsconfig-esm.json +0 -9
  81. package/tsconfig.json +0 -29
@@ -1,2101 +0,0 @@
1
- import {Shape,ShapeType} from '../shapes/Shape.js';
2
- import {PropertyShape} from '../shapes/SHACL.js';
3
- import {ShapeSet} from '../collections/ShapeSet.js';
4
- import {shacl} from '../ontologies/shacl.js';
5
- import {CoreSet} from '../collections/CoreSet.js';
6
- import {CoreMap} from '../collections/CoreMap.js';
7
- import {getPropertyShapeByLabel,getShapeClass} from '../utils/ShapeClass.js';
8
- import {NodeReferenceValue,Prettify,QueryFactory,ShapeReferenceValue} from './QueryFactory.js';
9
- import {xsd} from '../ontologies/xsd.js';
10
-
11
- /**
12
- * ###################################
13
- * #### TYPES FOR QUERY BUILDING ####
14
- * ###################################
15
- */
16
- export type JSPrimitive = JSNonNullPrimitive | null | undefined;
17
- export type JSNonNullPrimitive = string | number | boolean | Date;
18
-
19
- const isSameRef = (
20
- a?: NodeReferenceValue,
21
- b?: NodeReferenceValue,
22
- ): boolean => !!a && !!b && a.id === b.id;
23
-
24
- export type SingleResult<ResultType> =
25
- ResultType extends Array<infer R>
26
- ? R
27
- : ResultType extends Set<infer R>
28
- ? R
29
- : ResultType;
30
-
31
- /**
32
- * All the possible types that a regular get/set method of a Shape can return
33
- */
34
- export type AccessorReturnValue =
35
- | Shape
36
- | ShapeSet
37
- | JSPrimitive
38
- | NodeReferenceValue;
39
-
40
- export type WhereClause<S extends Shape | AccessorReturnValue> =
41
- | Evaluation
42
- | ((s: ToQueryBuilderObject<S>) => Evaluation);
43
-
44
- export type QueryBuildFn<T extends Shape, ResponseType> = (
45
- p: ToQueryBuilderObject<T>,
46
- q: SelectQueryFactory<T>,
47
- ) => ResponseType;
48
-
49
- export type QueryWrapperObject<ShapeType extends Shape = any> = {
50
- [key: string]: SelectQueryFactory<ShapeType>;
51
- };
52
- export type CustomQueryObject = {[key: string]: QueryPath};
53
-
54
- export type SelectPath = QueryPath[] | CustomQueryObject;
55
- export type SortByPath = {
56
- paths: QueryPath[];
57
- direction: 'ASC' | 'DESC';
58
- };
59
-
60
- /**
61
- * A LinkedQuery is used to build a query, when complete it can be turned into a LinkedQueryObject
62
- * that is used to send across the network as it can be serialized to JSON
63
- * @todo add | UpdateQuery and others
64
- */
65
- export interface LinkedQuery {
66
- type: string;
67
- }
68
-
69
- export type SubQueryPaths = SelectPath;
70
-
71
- /**
72
- * A QueryPath is an array of QuerySteps, representing the path of properties that were requested to reach a certain value
73
- */
74
- export type QueryPath = (QueryStep | SubQueryPaths)[] | WherePath;
75
-
76
- /**
77
- * A plain JS object that represents a LinkedQuery created by a Shape.select(...) call
78
- * It can be sent across the network.
79
- * @see LinkedQuery
80
- */
81
- export interface SelectQuery<S extends Shape = Shape, ResultType = any>
82
- extends LinkedQuery {
83
- select: SelectPath;
84
- where?: WherePath;
85
- sortBy?: SortByPath;
86
- subject?: S | QResult<S>;
87
- limit?: number;
88
- offset?: number;
89
- shape?: ShapeType<S>;
90
- singleResult?: boolean;
91
- }
92
-
93
- /**
94
- * Much like a querypath, except it can only contain QuerySteps
95
- */
96
- export type QueryPropertyPath = QueryStep[];
97
-
98
- /**
99
- * A QueryStep is a single step in a query path
100
- * It contains the property that was requested, and optionally a where clause
101
- */
102
- export type QueryStep =
103
- | PropertyQueryStep
104
- | SizeStep
105
- | CustomQueryObject
106
- | ShapeReferenceValue;
107
- export type SizeStep = {
108
- count: QueryPropertyPath;
109
- label?: string;
110
- };
111
- export type PropertyQueryStep = {
112
- property: PropertyShape;
113
- where?: WherePath;
114
- };
115
-
116
- export type WhereAndOr = {
117
- firstPath: WherePath;
118
- andOr: AndOrQueryToken[];
119
- };
120
-
121
- /**
122
- * A WhereQuery is a (sub)query that is used to filter down the results of its parent query
123
- * Hence it extends LinkedQuery and can do anything a normal query can
124
- */
125
- export type AndOrQueryToken = {
126
- and?: WherePath;
127
- or?: WherePath;
128
- };
129
-
130
- export enum WhereMethods {
131
- EQUALS = '=',
132
- SOME = 'some',
133
- EVERY = 'every',
134
- }
135
-
136
- /**
137
- * Maps all the return types of get/set methods of a Shape and maps their return types to QueryBuilderObjects
138
- */
139
- export type QueryShapeProps<
140
- T extends Shape,
141
- Source,
142
- Property extends string | number | symbol = any,
143
- > = {
144
- [P in keyof T]: ToQueryBuilderObject<T[P], QShape<T, Source, Property>, P>;
145
- };
146
-
147
- /**
148
- * This type states that the ShapeSet has access to the same methods as the shape of all the items in the set
149
- * (this is enabled with the QueryShapeSet.proxifyShapeSet method)
150
- * Each value of the shape is converted to a QueryBuilderObject
151
- */
152
- export type QueryShapeSetProps<SourceShapeSet, Shape> = {
153
- [P in keyof Shape]: ToQueryBuilderObject<Shape[P], SourceShapeSet, P>;
154
- };
155
-
156
- /**
157
- * ShapeSets are converted to QueryShapeSets, but also inherit all the properties of the shape that each item in the set has (with converted result types)
158
- */
159
- export type QShapeSet<
160
- ShapeSetType extends Shape,
161
- Source = null,
162
- Property extends string | number | symbol = null,
163
- > = QueryShapeSet<ShapeSetType, Source, Property> &
164
- QueryShapeSetProps<
165
- QueryShapeSet<ShapeSetType, Source, Property>,
166
- ShapeSetType
167
- >;
168
-
169
- /**
170
- * Shapes are converted to QueryShapes, but also inherit all the properties of the shape (with converted result types)
171
- */
172
- export type QShape<
173
- T extends Shape,
174
- Source = any,
175
- Property extends string | number | symbol = any,
176
- > = QueryShape<T, Source, Property> & QueryShapeProps<T, Source, Property>;
177
-
178
- export type ToQueryBuilderObject<
179
- T,
180
- Source = null,
181
- Property extends string | number | symbol = '',
182
- > =
183
- T extends ShapeSet<infer ShapeSetType>
184
- ? QShapeSet<ShapeSetType, Source, Property>
185
- : T extends Shape
186
- ? QShape<T, Source, Property>
187
- : T extends string | number | Date | boolean
188
- ? ToQueryPrimitive<T, Source, Property>
189
- : // : QueryBuilderObject<T,Source,Property>;
190
- T extends Array<infer AT>
191
- ? AT extends Date | string | number
192
- ? QueryPrimitiveSet<ToQueryPrimitive<AT, Source, Property>>
193
- : AT extends boolean
194
- ? QueryBoolean
195
- : AT[]
196
- : //added support for get/set methods that return NodeReferenceValue, treating them as plain Shapes
197
- T extends NodeReferenceValue
198
- ? QShape<Shape, Source, Property>
199
- : QueryBuilderObject<T, Source, Property>;
200
-
201
- export type ToQueryPrimitive<
202
- T extends string | number | Date | boolean,
203
- Source,
204
- Property extends string | number | symbol = '',
205
- > = T extends string
206
- ? QueryString<Source, Property>
207
- : T extends number
208
- ? QueryNumber<Source, Property>
209
- : T extends Date
210
- ? QueryDate<Source, Property>
211
- : T extends boolean
212
- ? QueryBoolean<Source, Property>
213
- : never;
214
-
215
- export type WherePath = WhereEvaluationPath | WhereAndOr;
216
-
217
- export type WhereEvaluationPath = {
218
- path: QueryPropertyPath;
219
- method: WhereMethods;
220
- args: QueryArg[];
221
- };
222
-
223
- // WherePath can also be an and/or wrapper; use this guard to safely access args.
224
- export const isWhereEvaluationPath = (
225
- value: WherePath,
226
- ): value is WhereEvaluationPath => {
227
- return !!value && 'args' in value;
228
- };
229
-
230
- /**
231
- * An argument can be a direct reference to a node, a js primitive (boolean,number), a path to resolve (like from a query context variables)
232
- * Or a wherePath in the case of some() or every() (e.g. x.where(x.friends.some(f => f.age > 18) -> the argument is a wherePath)
233
- */
234
- export type QueryArg =
235
- | NodeReferenceValue
236
- | JSNonNullPrimitive
237
- | ArgPath
238
- | WherePath;
239
- export type ArgPath = {
240
- path: QueryPropertyPath;
241
- subject: ShapeReferenceValue;
242
- };
243
-
244
- export type ComponentQueryPath = (QueryStep | SubQueryPaths)[] | WherePath;
245
-
246
- export type QueryComponentLike<ShapeType extends Shape, CompQueryResult> = {
247
- query:
248
- | SelectQueryFactory<ShapeType, CompQueryResult>
249
- | Record<string, SelectQueryFactory<ShapeType, CompQueryResult>>;
250
- };
251
- /**
252
- * ###################################
253
- * #### QUERY RESULT TYPES ####
254
- * ###################################
255
- */
256
-
257
- export type NodeResultMap = CoreMap<string, QResult<any, any>>;
258
-
259
- export type QResult<ShapeType extends Shape = Shape, Object = {}> = Object & {
260
- id: string;
261
- // shape?: ShapeType;
262
- };
263
-
264
- export type QueryProps<Q extends SelectQueryFactory<any>> =
265
- Q extends SelectQueryFactory<infer ShapeType, infer ResponseType>
266
- ? QueryResponseToResultType<ResponseType, ShapeType>
267
- : never;
268
-
269
- export type QueryControllerProps = {
270
- query?: QueryController;
271
- };
272
- export type QueryController = {
273
- nextPage: () => void;
274
- previousPage: () => void;
275
- setLimit: (limit: number) => void;
276
- setPage: (page: number) => void;
277
- };
278
-
279
- export type PatchedQueryPromise<ResultType, ShapeType extends Shape> = {
280
- where(
281
- validation: WhereClause<ShapeType>,
282
- ): PatchedQueryPromise<ResultType, ShapeType>;
283
- limit(lim: number): PatchedQueryPromise<ResultType, ShapeType>;
284
- sortBy(
285
- sortParam: any,
286
- direction?: 'ASC' | 'DESC',
287
- ): PatchedQueryPromise<ResultType, ShapeType>;
288
- one(): PatchedQueryPromise<SingleResult<ResultType>, ShapeType>;
289
- } & Promise<ResultType>;
290
-
291
- export type GetCustomObjectKeys<T> = T extends QueryWrapperObject
292
- ? {
293
- [P in keyof T]: T[P] extends SelectQueryFactory<any>
294
- ? ToQueryResultSet<T[P]>
295
- : never;
296
- }
297
- : [];
298
-
299
- export type QueryIndividualResultType<T extends SelectQueryFactory<any>> =
300
- T extends SelectQueryFactory<infer ShapeType, infer ResponseType>
301
- ? QueryResponseToResultType<ResponseType, ShapeType>
302
- : null;
303
-
304
- export type ToQueryResultSet<T> =
305
- T extends SelectQueryFactory<infer ShapeType, infer ResponseType>
306
- ? QueryResponseToResultType<ResponseType, ShapeType>[]
307
- : null;
308
-
309
- /**
310
- * MAIN ENTRY to convert the response of a query into a result object
311
- */
312
- export type QueryResponseToResultType<
313
- T,
314
- QShapeType extends Shape = null,
315
- HasName = false,
316
- // PreserveArray = false,
317
- > = T extends QueryBuilderObject
318
- ? GetQueryObjectResultType<T, {}, false, HasName>
319
- : T extends SelectQueryFactory<any, infer Response, infer Source>
320
- ? GetNestedQueryResultType<Response, Source>
321
- : T extends Array<infer Type>
322
- ? UnionToIntersection<QueryResponseToResultType<Type>>
323
- : // ? PreserveArray extends true ? QueryResponseToResultType<Type,null,null,true>[] : UnionToIntersection<QueryResponseToResultType<Type,null,null,true>>
324
- T extends Evaluation
325
- ? boolean
326
- : T extends Object
327
- ? QResult<QShapeType, Prettify<ObjectToPlainResult<T>>>
328
- : never;
329
-
330
- /**
331
- * Turns a QueryBuilderObject into a plain JS object
332
- * @param QV the query value type
333
- * @param SubProperties to add extra properties into the result object (used to merge arrays into objects for example)
334
- * @param SourceOverwrite if the source of the query value should be overwritten
335
- */
336
- //QV QueryBuilderObject<string[],QShape<Person,null,''>,'nickNames'>[]
337
- //SubProperties = {}
338
- export type GetQueryObjectResultType<
339
- QV,
340
- SubProperties = {},
341
- PrimitiveArray = false,
342
- HasName = false,
343
- > =
344
- //note: count needs to be above primitive
345
- QV extends SetSize<infer Source>
346
- ? SetSizeToQueryResult<Source, HasName>
347
- : QV extends QueryPrimitive<infer Primitive, infer Source, infer Property>
348
- ? CreateQResult<
349
- Source,
350
- PrimitiveArray extends true ? Primitive[] : Primitive,
351
- Property,
352
- {},
353
- HasName
354
- >
355
- : QV extends QueryShape<infer ShapeType, infer Source, infer Property>
356
- ? CreateQResult<Source, ShapeType, Property, SubProperties, HasName>
357
- : // CreateQResult<Source, ShapeType, Property>
358
- QV extends BoundComponent<infer Source, infer CompQueryResult>
359
- ? GetQueryObjectResultType<
360
- Source,
361
- SubProperties & QueryResponseToResultType<CompQueryResult>,
362
- PrimitiveArray,
363
- HasName
364
- >
365
- : QV extends QueryShapeSet<
366
- infer ShapeType,
367
- infer Source,
368
- infer Property
369
- >
370
- ? CreateShapeSetQResult<
371
- ShapeType,
372
- Source,
373
- Property,
374
- SubProperties,
375
- HasName
376
- >
377
- : QV extends QueryPrimitiveSet<
378
- infer QPrim extends QueryPrimitive<any>
379
- >
380
- ? GetQueryObjectResultType<QPrim, null, null, true>
381
- : QV extends Array<infer Type>
382
- ? UnionToIntersection<QueryResponseToResultType<Type>>
383
- : QV extends QueryBoolean<any, any>
384
- ? 'bool'
385
- : never;
386
-
387
- //for now, we don't pass result types of nested queries of bound components
388
- //instead we just pass on the result as it would have been if the query element was not extended with ".preLoadFor()"
389
- export type GetShapesResultTypeWithSource<Source> =
390
- QueryResponseToResultType<Source>;
391
- // export type GetShapesResultTypeWithSource<Source> =
392
- // Source extends QueryShape<infer ShapeType, infer Source, infer Property>
393
- // ? CreateQResult<Source, ShapeType, Property>
394
- // : Source extends QueryShapeSet<
395
- // infer ShapeType,
396
- // infer Source,
397
- // infer Property
398
- // >
399
- // ? CreateShapeSetQResult<ShapeType, Source, Property>
400
- // : never;
401
-
402
- type GetQueryObjectProperty<T> =
403
- T extends QueryBuilderObject<any, any, infer Property>
404
- ? Property
405
- : T extends SelectQueryFactory<
406
- infer SubShapeType,
407
- infer SubResponse,
408
- infer SubSource
409
- >
410
- ? GetQueryObjectProperty<SubSource>
411
- : never;
412
- type GetQueryObjectOriginal<T> =
413
- T extends QueryBuilderObject<infer Original>
414
- ? Original
415
- : T extends SelectQueryFactory<
416
- infer SubShapeType,
417
- infer SubResponse,
418
- infer SubSource
419
- >
420
- ? GetNestedQueryResultType<SubResponse, SubSource>
421
- : never;
422
- /**
423
- * Converts an intersection of QueryBuilderObjects into a plain JS object
424
- * i.e. QueryString<Person,"name"> | QueryString<Person,"hobby"> --> {name: string, hobby: string}
425
- * To do this we get the Property of each QueryBuilderObject, and use it as the key in the resulting object
426
- * and, we get the Original type of each QueryBuilderObject, and use it as the value in the resulting object
427
- */
428
- type QueryValueIntersectionToObject<Items> = {
429
- [Type in Items as GetQueryObjectProperty<Type>]: true; //GetQueryObjectOriginal<Type>;
430
- };
431
-
432
- export type SetSizeToQueryResult<Source, HasName = false> =
433
- Source extends QueryShapeSet<
434
- infer ShapeType,
435
- infer ParentSource,
436
- infer SourceProperty
437
- >
438
- ? HasName extends false
439
- ? //when we count something and we already know what the name of the variable of the resulting number is, then we return a number
440
- //But if we count a shapeset and its NOT in a custom object where a key (name) is already known, then we return a QResult
441
- //This QResult will be the same as it would be if there was no .count() statement. Except now it returns a number (hence we send number as value type)
442
- CreateQResult<ParentSource, number, SourceProperty>
443
- : number
444
- : number;
445
-
446
- /**
447
- * If the source is an object (it extends shape)
448
- * then the result is a plain JS Object, with Property as its key, with type Value
449
- */
450
- export type CreateQResult<
451
- Source,
452
- Value = undefined,
453
- Property extends string | number | symbol = '',
454
- SubProperties = {},
455
- HasName = false,
456
- > =
457
- Source extends QueryShape<
458
- infer SourceShapeType,
459
- infer ParentSource,
460
- infer SourceProperty
461
- >
462
- ? //if the parent source is null, that means this is the final source-node in the query
463
- ParentSource extends null
464
- ? HasName extends true
465
- ? Value
466
- : //TODO: this must be simplified and rewritten
467
- // it is likely the most complex part of the type system currently
468
- // It turns out that sub-.select() on a QueryShapeSet ends up here with Value being null, and sub properties need to be added to the QResult itself
469
- // Whilst sub-.select() on a single QueryShape ends up here with Value being defined, in which case the SubProperties need to be included in the inner QResult
470
- Value extends null
471
- ? //hence we create a single QResult, but do not use CreateQResult (which will keep creating nested QResults)
472
- QResult<
473
- SourceShapeType,
474
- {
475
- //we pass Value and Value but not Property, so that when the value is a Shape or ShapeSet, there is recursion
476
- //but for all other cases (like string, number, boolean) the value is just passed through
477
- [P in Property]: CreateQResult<Value, Value>;
478
- } & SubProperties
479
- >
480
- : //hence we create a single QResult, but do not use CreateQResult (which will keep creating nested QResults)
481
- QResult<
482
- SourceShapeType,
483
- {
484
- //we pass Value and Value but not Property, so that when the value is a Shape or ShapeSet, there is recursion
485
- //but for all other cases (like string, number, boolean) the value is just passed through
486
- [P in Property]: CreateQResult<Value, Value, '', SubProperties>;
487
- }
488
- >
489
- : CreateQResult<
490
- ParentSource,
491
- QResult<
492
- SourceShapeType,
493
- {
494
- //we pass Value and Value but not Property, so that when the value is a Shape or ShapeSet, there is recursion
495
- //but for all other cases (like string, number, boolean) the value is just passed through
496
- [P in Property]: CreateQResult<Value, Value>;
497
- } & SubProperties
498
- >,
499
- SourceProperty,
500
- {},
501
- HasName
502
- >
503
- : Source extends QueryShapeSet<
504
- infer ShapeType,
505
- infer ParentSource,
506
- infer SourceProperty
507
- >
508
- ? //for a ShapeSet, we make the current result (a QResult) the value of a parent QResult (created with ToQueryResult)
509
- CreateQResult<
510
- ParentSource,
511
- QResult<
512
- ShapeType,
513
- {
514
- //we pass Value and Value but not Property, so that when the value is a Shape or ShapeSet, there is recursion
515
- //but for all other cases (like string, number, boolean) the value is just passed through
516
- [P in Property]: CreateQResult<Value, Value, null, SubProperties>;
517
- }
518
- >[],
519
- SourceProperty,
520
- {},
521
- HasName
522
- >
523
- : //Source is not a QueryShape or QueryShape set (currently sometimes used by end QueryPrimitives) ..
524
- // this needs to convert to value (amongst other things) for .select({customKeys}) and ObjectToPlainResult
525
- Value extends Shape
526
- ? QResult<Value, SubProperties>
527
- : // : Value extends boolean ? 'boolean' : Value;
528
- NormaliseBoolean<Value>;
529
-
530
- type NormaliseBoolean<T> = [T] extends [boolean] ? boolean : T;
531
-
532
- export type CreateShapeSetQResult<
533
- ShapeType = undefined,
534
- Source = undefined,
535
- Property extends string | number | symbol = '',
536
- SubProperties = {},
537
- HasName = false,
538
- > =
539
- Source extends QueryShape<infer SourceShapeType, infer ParentSource>
540
- ? //if HasName is true and source is a QueryShape, but ITS source (ParentSource) is null
541
- //then we don't want to create a nested QResult, but instead we ignore this last property and we return an array of QResults of this source
542
- //This is used by custom object keys with values like: p.friends, which should return an array of QResult<Person> Objects, not a {friends:...} QResult
543
- //NOTE: this notation check if 2 statements are true: HasName is true, and ParentSource is null
544
- [HasName, ParentSource] extends [true, null]
545
- ? CreateQResult<Source, null, null>[]
546
- : QResult<
547
- SourceShapeType,
548
- {[P in Property]: CreateQResult<Source, null, null, SubProperties>[]}
549
- >
550
- : Source extends QueryShapeSet<
551
- infer ShapeType,
552
- infer ParentSource,
553
- infer SourceProperty
554
- >
555
- ? //for a shapeset source, we make the current result (a QResult) the value of a parent QResult (created with ToQueryResult)
556
- CreateQResult<
557
- ParentSource,
558
- QResult<
559
- ShapeType,
560
- {
561
- [P in Property]: CreateQResult<ShapeType>[];
562
- }
563
- >[],
564
- SourceProperty,
565
- {},
566
- HasName
567
- >
568
- : CreateQResult<ShapeType>;
569
-
570
- /**
571
- * Ignores the source and property, and returns the converted value
572
- */
573
- export type ObjectToPlainResult<T> = {
574
- //passing true as sourceOverwrite will mean that the original source is ignored and so the converted value will not be wrapped in a QResult
575
- // [P in keyof T]: QueryResponseToResultType<T[P], null, true>;
576
- [P in keyof T]: QueryResponseToResultType<T[P], null, true>;
577
- };
578
-
579
- export type GetSource<Source, Overwrite> = Overwrite extends null
580
- ? Source
581
- : Overwrite;
582
-
583
- type GetNestedQueryResultType<Response, Source> =
584
- Source extends QueryBuilderObject
585
- ? //if the linked query originates from within another query (like with select())
586
- //then we turn the source into a result. And then pass the selected properties as "SubProperties"
587
- //regardless of whether the response type is an array or object, it gets converted into a result value object
588
- GetQueryObjectResultType<Source, QueryResponseToResultType<Response>>
589
- : //by default: we just convert the response type into a result value object
590
- QueryResponseToResultType<Response>[];
591
-
592
- //https://stackoverflow.com/a/50375286/977206
593
- type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
594
- x: infer I,
595
- ) => void
596
- ? I
597
- : never;
598
-
599
- /**
600
- * Converts the response of a nested query into a QResult object
601
- */
602
- type ResponseToObject<R> =
603
- R extends Array<infer Type extends QueryBuilderObject>
604
- ? QueryValueIntersectionToObject<Type>
605
- : Prettify<ObjectToPlainResult<R>>;
606
-
607
- export type GetQueryResponseType<Q> =
608
- Q extends SelectQueryFactory<any, infer ResponseType> ? ResponseType : Q;
609
-
610
- export type GetQueryShapeType<Q> =
611
- Q extends SelectQueryFactory<infer ShapeType, infer ResponseType>
612
- ? ShapeType
613
- : never;
614
-
615
- export type QueryResponseToEndValues<T> = T extends SetSize
616
- ? number[]
617
- : T extends SelectQueryFactory<any, infer Response>
618
- ? QueryResponseToEndValues<Response>[]
619
- : T extends QueryShapeSet<infer ShapeType>
620
- ? ShapeSet<ShapeType>
621
- : T extends QueryShape<infer ShapeType>
622
- ? ShapeType
623
- : T extends QueryString
624
- ? string[]
625
- : T extends Array<infer ArrType>
626
- ? Array<QueryResponseToEndValues<ArrType>>
627
- : T extends Evaluation
628
- ? boolean[]
629
- : T;
630
-
631
- /**
632
- * ###################################
633
- * #### QUERY BUILDING CLASSES ####
634
- * ###################################
635
- */
636
-
637
- export class QueryBuilderObject<
638
- OriginalValue = any,
639
- Source = any,
640
- Property extends string | number | symbol = any,
641
- > {
642
- //is null by default to avoid warnings when trying to access wherePath when its undefined
643
- wherePath?: WherePath = null;
644
- protected originalValue?: OriginalValue;
645
- protected source: Source;
646
- protected prop: Property;
647
-
648
- constructor(
649
- public property?: PropertyShape,
650
- public subject?: QueryShape<any> | QueryShapeSet<any> | QueryPrimitiveSet,
651
- ) {}
652
-
653
- /**
654
- * Converts an original value into a query value
655
- * @param originalValue
656
- * @param requestedPropertyShape the property shape that is connected to the get accessor that returned the original value
657
- */
658
- static convertOriginal(
659
- originalValue: AccessorReturnValue,
660
- property: PropertyShape,
661
- subject: QueryShape<any> | QueryShapeSet<any> | QueryShape<any>,
662
- ): QueryBuilderObject {
663
- if (originalValue instanceof Shape) {
664
- return QueryShape.create(originalValue, property, subject);
665
- } else if (originalValue instanceof ShapeSet) {
666
- return QueryShapeSet.create(originalValue, property, subject);
667
- } else if (typeof originalValue === 'string') {
668
- return new QueryString(originalValue, property, subject);
669
- } else if (typeof originalValue === 'number') {
670
- return new QueryNumber(originalValue, property, subject);
671
- } else if (typeof originalValue === 'boolean') {
672
- return new QueryBoolean(originalValue, property, subject);
673
- } else if (originalValue instanceof Date) {
674
- return new QueryDate(originalValue, property, subject);
675
- } else if (Array.isArray(originalValue)) {
676
- return new QueryPrimitiveSet(originalValue, property, subject);
677
- } else if (
678
- originalValue &&
679
- typeof originalValue === 'object' &&
680
- 'id' in originalValue
681
- ) {
682
- //Support accessors that return NodeReferenceValue when a value shape is known.
683
- if (property.valueShape) {
684
- const shapeClass = getShapeClass(property.valueShape) as any;
685
- if (!shapeClass) {
686
- throw new Error(
687
- `Shape class not found for ${property.valueShape.id}`,
688
- );
689
- }
690
- const shape = new shapeClass();
691
- shape.id = (originalValue as NodeReferenceValue).id;
692
- return QueryShape.create(shape, property, subject);
693
- }
694
- throw new Error(
695
- subject.getOriginalValue().nodeShape.label +
696
- '.' +
697
- property.label +
698
- ': A property accessor should return a Shape or a primitive value. Returning a NodeReferenceValue is currently not supported.',
699
- );
700
- } else {
701
- throw new Error('Unknown query path result type: ' + originalValue);
702
- }
703
- }
704
-
705
- /**
706
- * Create a Query Builder Object based on the requested PropertyShape
707
- */
708
- static generatePathValue(
709
- // originalValue: AccessorReturnValue,
710
- property: PropertyShape,
711
- subject: QueryShape<any> | QueryShapeSet<any> | QueryShape<any>,
712
- ): QueryBuilderObject {
713
- let datatype = property.datatype;
714
- let valueShape = property.valueShape;
715
- let singleValue = property.maxCount <= 1;
716
- if (datatype) {
717
- if (singleValue) {
718
- if (isSameRef(datatype, xsd.integer)) {
719
- return new QueryNumber(0, property, subject);
720
- } else if (isSameRef(datatype, xsd.boolean)) {
721
- return new QueryBoolean(false, property, subject);
722
- } else if (
723
- isSameRef(datatype, xsd.dateTime) ||
724
- isSameRef(datatype, xsd.date)
725
- ) {
726
- return new QueryDate(new Date(), property, subject);
727
- } else if (isSameRef(datatype, xsd.string)) {
728
- return new QueryString('', property, subject);
729
- }
730
- } else {
731
- //TODO review this, do we need property & subject in both of these? currently yes, but why
732
- return new QueryPrimitiveSet([''], property, subject, [
733
- new QueryString('', property, subject),
734
- ]);
735
- }
736
- }
737
- if (valueShape) {
738
- const shapeClass = getShapeClass(valueShape) as any;
739
- if(!shapeClass) {
740
- //TODO: getShapeClassAsync -> which will lazy load the shape class
741
- // but Im not sure if that's even possible with dynamic import paths, that are only known at runtime
742
- //UPDATE: we should not need to load shapeclasses. We just need to be able to access shapes.
743
- // but the problem remains that the ImageObject shape needs to be available, but thats easier, as its data
744
- throw new Error(`Shape class not found for ${valueShape.id}`);
745
- }
746
- const shapeValue = new shapeClass();
747
- if (singleValue) {
748
- return QueryShape.create(shapeValue, property, subject);
749
- } else {
750
- return QueryShapeSet.create(
751
- new ShapeSet([shapeValue]),
752
- property,
753
- subject,
754
- );
755
- }
756
- }
757
-
758
- //no value shape and no data type.
759
- //Lets look at the node kind
760
- if (
761
- isSameRef(property.nodeKind, shacl.Literal) ||
762
- isSameRef(property.nodeKind, shacl.BlankNodeOrLiteral)
763
- ) {
764
- if (singleValue) {
765
- //default to string if no datatype is set
766
- return new QueryString('', property, subject);
767
- } else {
768
- //TODO review this, do we need property & subject in both of these? currently yes, but why
769
- return new QueryPrimitiveSet([''], property, subject, [
770
- new QueryString('', property, subject),
771
- ]);
772
- }
773
- }
774
-
775
- //if an object is expected and no value shape is set, then warn
776
- throw Error(
777
- `No shape set for objectProperty ${property.parentNodeShape.label}.${property.label}`,
778
- );
779
-
780
- // //and use a generic shape
781
- // const shapeValue = new (Shape as any)(new TestNode(path));
782
- // if (singleValue) {
783
- // return QueryShape.create(shapeValue, property, subject);
784
- // } else {
785
- // //check if shapeValue is iterable
786
- // if (!(Symbol.iterator in Object(shapeValue))) {
787
- // throw new Error(
788
- // `Property ${property.parentNodeShape.label}.${property.label} is not marked as single value (maxCount:1), but the value is not iterable`,
789
- // );
790
- // }
791
- // return QueryShapeSet.create(new ShapeSet(shapeValue), property, subject);
792
- // }
793
- }
794
-
795
- static getOriginalSource(
796
- endValue: ShapeSet<Shape> | Shape[] | QueryPrimitiveSet,
797
- ): // | QueryValueSetOfSets,
798
- ShapeSet;
799
-
800
- static getOriginalSource(endValue: Shape): Shape;
801
-
802
- static getOriginalSource(endValue: QueryString): Shape | string;
803
-
804
- static getOriginalSource(
805
- endValue: string[] | QueryBuilderObject,
806
- ): Shape | ShapeSet;
807
-
808
- static getOriginalSource(
809
- endValue:
810
- | ShapeSet
811
- | Shape[]
812
- | Shape
813
- | string[]
814
- | QueryBuilderObject
815
- | QueryPrimitiveSet,
816
- ): AccessorReturnValue {
817
- if (typeof endValue === 'undefined') return undefined;
818
- if (endValue instanceof QueryPrimitiveSet) {
819
- return new ShapeSet(
820
- endValue.contents.map(
821
- (endValue) => this.getOriginalSource(endValue) as any as Shape,
822
- ),
823
- ) as ShapeSet;
824
- }
825
- if (endValue instanceof QueryString) {
826
- return endValue.subject
827
- ? this.getOriginalSource(endValue.subject as QueryShapeSet)
828
- : endValue.originalValue;
829
- }
830
- if (endValue instanceof QueryShape) {
831
- if (endValue.subject && !endValue.isSource) {
832
- return this.getOriginalSource(
833
- endValue.subject as QueryShape<any> | QueryShapeSet<any>,
834
- );
835
- }
836
- return endValue.originalValue;
837
- } else if (endValue instanceof Shape) {
838
- return endValue;
839
- } else if (endValue instanceof QueryShapeSet) {
840
- return new ShapeSet(
841
- (endValue as QueryShapeSet).queryShapes.map(
842
- (queryShape: QueryShape) =>
843
- this.getOriginalSource(queryShape) as Shape,
844
- ),
845
- );
846
- } else {
847
- throw new Error('Unimplemented. Return as is?');
848
- }
849
- }
850
-
851
- getOriginalValue() {
852
- return this.originalValue;
853
- }
854
-
855
- getPropertyStep(): QueryStep {
856
- return {
857
- property: this.property,
858
- where: this.wherePath,
859
- };
860
- }
861
-
862
- preloadFor<ShapeType extends Shape, CompQueryRes>(
863
- component: QueryComponentLike<ShapeType, CompQueryRes>,
864
- ): BoundComponent<this, CompQueryRes> {
865
- return new BoundComponent<this, CompQueryRes>(component, this);
866
- }
867
-
868
- limit(lim: number) {
869
- console.log(lim);
870
- }
871
-
872
- /**
873
- * Returns the path of properties that were requested to reach this value
874
- */
875
- getPropertyPath(currentPath?: QueryPropertyPath): QueryPropertyPath {
876
- let path: QueryPropertyPath = currentPath || [];
877
- //add the step of this object to the beginning of the path (so that the next parent will always before the current item)
878
- if (this.property || this.wherePath) {
879
- path.unshift(this.getPropertyStep());
880
- }
881
- if (this.subject) {
882
- return this.subject.getPropertyPath(path);
883
- }
884
- //when query context is used as the first step, then the first step is just a pointer to the subject it represents
885
- if ((this.originalValue as Shape)?.__queryContextId) {
886
- path.unshift(convertQueryContext(this as any as QueryShape));
887
- }
888
- return path;
889
- }
890
- }
891
-
892
- export class BoundComponent<
893
- Source extends QueryBuilderObject,
894
- CompQueryResult = any,
895
- > extends QueryBuilderObject {
896
- constructor(
897
- public originalValue: QueryComponentLike<any, CompQueryResult>,
898
- public source: Source,
899
- ) {
900
- super(null, null);
901
- }
902
-
903
- getParentQueryFactory(): SelectQueryFactory<any> {
904
- let parentQuery: SelectQueryFactory<any> | Object =
905
- this.originalValue.query;
906
-
907
- if (parentQuery instanceof SelectQueryFactory) {
908
- return parentQuery;
909
- }
910
- if (typeof parentQuery === 'object') {
911
- if (Object.keys(parentQuery).length > 1) {
912
- throw new Error(
913
- 'Only one key is allowed to map a query to a property for linkedSetComponents',
914
- );
915
- }
916
- for (let key in parentQuery) {
917
- if (parentQuery[key] instanceof SelectQueryFactory) {
918
- return parentQuery[key];
919
- }
920
- throw new Error(
921
- 'Unknown value type for query object. Keep to this format: {propName: Shape.query(s => ...)}',
922
- );
923
- }
924
- }
925
- throw new Error(
926
- 'Unknown data query type. Expected a LinkedQuery (from Shape.query()) or an object with 1 key whose value is a LinkedQuery',
927
- );
928
- }
929
-
930
- getPropertyPath() {
931
- let sourcePath: ComponentQueryPath = this.source.getPropertyPath();
932
- let requestQuery = this.getParentQueryFactory();
933
- let compSelectQuery = requestQuery.getQueryObject().select;
934
-
935
- if (Array.isArray(sourcePath)) {
936
- sourcePath.push(
937
- compSelectQuery.length === 1
938
- ? compSelectQuery[0].length === 1
939
- ? compSelectQuery[0][0]
940
- : compSelectQuery[0]
941
- : compSelectQuery,
942
- );
943
- }
944
- return sourcePath as QueryPropertyPath;
945
- }
946
- }
947
-
948
- /**
949
- * Converts query context to a ShapeReferenceValue
950
- */
951
- const convertQueryContext = (shape: QueryShape): ShapeReferenceValue => {
952
- return {
953
- id: (shape.originalValue as Shape).__queryContextId,
954
- shape: {
955
- id: shape.originalValue.nodeShape.id,
956
- },
957
- } as ShapeReferenceValue;
958
- };
959
-
960
- const processWhereClause = (
961
- validation: WhereClause<any>,
962
- shape?,
963
- ): WherePath => {
964
- if (validation instanceof Function) {
965
- if (!shape) {
966
- throw new Error('Cannot process where clause without shape');
967
- }
968
- return new LinkedWhereQuery(shape, validation).getWherePath();
969
- } else {
970
- return (validation as Evaluation).getWherePath();
971
- }
972
- };
973
-
974
- export class QueryShapeSet<
975
- S extends Shape = Shape,
976
- Source = any,
977
- Property extends string | number | symbol = any,
978
- > extends QueryBuilderObject<ShapeSet<S>, Source, Property> {
979
- public queryShapes: CoreSet<QueryShape>;
980
- private proxy;
981
-
982
- constructor(
983
- _originalValue?: ShapeSet<S>,
984
- property?: PropertyShape,
985
- subject?: QueryShape<any> | QueryShapeSet<any>,
986
- ) {
987
- super(property, subject);
988
- this.originalValue = _originalValue;
989
-
990
- //Note that QueryShapeSet intentionally does not store the _originalValue shape set, because it manipulates this.queryShapes
991
- // and then recreates the original shape set when getOriginalValue() is called
992
- this.queryShapes = new CoreSet(
993
- _originalValue?.map((shape) =>
994
- QueryShape.create(shape, property, subject),
995
- ),
996
- );
997
- }
998
-
999
- static create<S extends Shape = Shape>(
1000
- originalValue: ShapeSet<S>,
1001
- property: PropertyShape,
1002
- subject: QueryShape<any> | QueryShapeSet<any>,
1003
- ) {
1004
- let instance = new QueryShapeSet<S>(originalValue, property, subject);
1005
-
1006
- let proxy = this.proxifyShapeSet<S>(instance);
1007
- return proxy;
1008
- }
1009
-
1010
- static proxifyShapeSet<T extends Shape = Shape>(
1011
- queryShapeSet: QueryShapeSet<T>,
1012
- ) {
1013
- let originalShapeSet = queryShapeSet.getOriginalValue();
1014
-
1015
- queryShapeSet.proxy = new Proxy(queryShapeSet, {
1016
- get(target, key, receiver) {
1017
- //if the key is a string
1018
- if (typeof key === 'string') {
1019
- //if this is a get method that is implemented by the QueryShapeSet, then use that
1020
- if (key in queryShapeSet) {
1021
- //if it's a function, then bind it to the queryShape and return it so it can be called
1022
- if (typeof queryShapeSet[key] === 'function') {
1023
- return target[key].bind(target);
1024
- }
1025
- //if it's a get method, then return that
1026
- //NOTE: we may not need this if we don't use any get methods in QueryValue classes?
1027
- return queryShapeSet[key];
1028
- }
1029
-
1030
- //if not, then a method/accessor was called that likely fits with the methods of the original SHAPE of the items in the shape set
1031
- //As in Shape.friends.name -> key would be name, which is requested from (each item in!) a ShapeSet of Shapes
1032
- //So here we find back the shape that all items have in common, and then find the property shape that matches the key
1033
- //NOTE: this will only work if the key corresponds with an accessor in the shape that uses a @linkedProperty decorator
1034
- let leastSpecificShape = queryShapeSet
1035
- .getOriginalValue()
1036
- .getLeastSpecificShape();
1037
- let valueShape = leastSpecificShape ? leastSpecificShape.shape : null;
1038
- if (!valueShape && queryShapeSet.property?.valueShape) {
1039
- const shapeClass = getShapeClass(queryShapeSet.property.valueShape);
1040
- valueShape = shapeClass?.shape;
1041
- }
1042
- let propertyShape: PropertyShape = valueShape
1043
- ?.getPropertyShapes(true)
1044
- .find((propertyShape) => propertyShape.label === key);
1045
-
1046
- //if the property shape is found
1047
- if (propertyShape) {
1048
- return queryShapeSet.callPropertyShapeAccessor(propertyShape);
1049
- } else if (
1050
- //else if a method of the original shape is called, like .forEach() or similar
1051
- originalShapeSet[key] &&
1052
- typeof originalShapeSet[key] === 'function'
1053
- ) {
1054
- //then return that method and bind the original value as 'this'
1055
- return originalShapeSet[key].bind(originalShapeSet);
1056
- } else if (key !== 'then' && key !== '$$typeof') {
1057
- //TODO: there is a strange bug with "then" being called, only for queries that access ShapeSets (multi value props), but I'm not sure where it comes from
1058
- //hiding the warning for now in that case as it doesn't seem to affect the results
1059
- console.warn(
1060
- 'Could not find property shape for key ' +
1061
- key +
1062
- ' on shape ' +
1063
- valueShape?.label +
1064
- '. Make sure the get method exists and is decorated with @linkedProperty / @objectProperty / @literalProperty',
1065
- );
1066
- }
1067
- }
1068
- //otherwise return the value of the property on the original shape
1069
- return originalShapeSet[key];
1070
- },
1071
- });
1072
- return queryShapeSet.proxy;
1073
- }
1074
-
1075
- as<ShapeClass extends typeof Shape>(
1076
- shape: ShapeClass,
1077
- ): QShapeSet<InstanceType<ShapeClass>, Source, Property> {
1078
- //if the shape is not the same as the original value, then we need to create a new query shape
1079
- if (!shape.shape.equals(this.originalValue.getLeastSpecificShape().shape)) {
1080
- let newOriginal = new ShapeSet(
1081
- this.originalValue.map((existing) => {
1082
- const instance = new (shape as any)();
1083
- if (existing?.id) {
1084
- instance.id = existing.id;
1085
- }
1086
- return instance;
1087
- }),
1088
- );
1089
- return QueryShapeSet.create(
1090
- newOriginal,
1091
- this.property,
1092
- this.subject as any,
1093
- );
1094
- }
1095
- // else return this
1096
- return this as any as QShapeSet<InstanceType<ShapeClass>, Source, Property>;
1097
- }
1098
-
1099
- add(item) {
1100
- this.queryShapes.add(item);
1101
- }
1102
-
1103
- concat(other: QueryShapeSet): QueryShapeSet {
1104
- if (other) {
1105
- if (other instanceof QueryShapeSet) {
1106
- (other as QueryShapeSet).queryShapes.forEach(
1107
- this.queryShapes.add.bind(this.queryShapes),
1108
- );
1109
- } else {
1110
- throw new Error('Unknown type: ' + other);
1111
- }
1112
- }
1113
- return this;
1114
- }
1115
-
1116
- filter(filterFn): QueryShapeSet {
1117
- let clone = new QueryShapeSet(
1118
- new ShapeSet(),
1119
- this.property,
1120
- this.subject as QueryShape<any> | QueryShapeSet<any>,
1121
- );
1122
- clone.queryShapes = this.queryShapes.filter(filterFn);
1123
- return clone;
1124
- }
1125
-
1126
- setSource(val: boolean) {
1127
- this.queryShapes.forEach((shape) => {
1128
- shape.isSource = val;
1129
- });
1130
- }
1131
-
1132
- getOriginalValue() {
1133
- return new ShapeSet(
1134
- this.queryShapes.map((shape) => {
1135
- return shape.originalValue;
1136
- }),
1137
- ) as ShapeSet<S>;
1138
- }
1139
-
1140
- callPropertyShapeAccessor(
1141
- propertyShape: PropertyShape,
1142
- ): QueryShapeSet | QueryPrimitiveSet {
1143
- //call the get method for that property shape on each item in the shape set
1144
- //and return the result as a new shape set
1145
- let result: QueryPrimitiveSet | QueryShapeSet; //QueryValueSetOfSets;
1146
-
1147
- //if we expect the accessor to return a Primitive (string,number,boolean,Date)
1148
- if (isSameRef(propertyShape.nodeKind, shacl.Literal)) {
1149
- //then return a Set of QueryPrimitives
1150
- result = new QueryPrimitiveSet(null, propertyShape, this);
1151
- } else {
1152
- // result = QueryValueSetOfSets.create(propertyShape, this); //QueryShapeSet.create(null, propertyShape, this);
1153
- result = QueryShapeSet.create(null, propertyShape, this);
1154
- }
1155
- let expectSingleValues =
1156
- typeof propertyShape.maxCount === 'number' && propertyShape.maxCount <= 1;
1157
-
1158
- this.queryShapes.forEach((shape) => {
1159
- //access the propertyShapes accessor,
1160
- // since the shape should already be converted to a QueryShape, the result is a QueryValue also
1161
- let shapeQueryValue = shape[propertyShape.label];
1162
-
1163
- //only add results if something was actually returned, if the property is not defined for this shape the result can be undefined
1164
- if (shapeQueryValue) {
1165
- if (expectSingleValues) {
1166
- (result as any).add(shapeQueryValue);
1167
- } else {
1168
- //if each of the shapes in a set return a new shapeset for the request accessor
1169
- //then we merge all the returned values into a single shapeset
1170
- (result as QueryShapeSet).concat(shapeQueryValue);
1171
- }
1172
- }
1173
- });
1174
- return result;
1175
- }
1176
-
1177
- //countable?, resultKey?: string
1178
- size(): SetSize<this> {
1179
- //when count() is called we want to count the number of items in the entire query path
1180
- return new SetSize(this); //countable, resultKey
1181
- }
1182
-
1183
- // get testItem() {}
1184
- where(validation: WhereClause<S>): this {
1185
- if (
1186
- (this.getPropertyPath() as QueryStep[]).some(
1187
- (step) => (step as PropertyQueryStep).where,
1188
- )
1189
- ) {
1190
- throw new Error(
1191
- 'You cannot call where() from within a where() clause. Consider using some() or every() instead',
1192
- );
1193
- }
1194
- let leastSpecificShape = this.getOriginalValue().getLeastSpecificShape();
1195
- this.wherePath = processWhereClause(validation, leastSpecificShape);
1196
- //return this.proxy because after Shape.friends.where() we can call other methods of Shape.friends
1197
- //and for that we need the proxy
1198
- return this.proxy;
1199
- }
1200
-
1201
- select<QF = unknown>(
1202
- subQueryFn: QueryBuildFn<S, QF>,
1203
- ): SelectQueryFactory<S, QF, QueryShapeSet<S, Source, Property>> {
1204
- let leastSpecificShape = this.getOriginalValue().getLeastSpecificShape();
1205
- let subQuery = new SelectQueryFactory(leastSpecificShape, subQueryFn);
1206
- subQuery.parentQueryPath = this.getPropertyPath();
1207
- return subQuery as any;
1208
- }
1209
-
1210
- some(validation: WhereClause<S>): SetEvaluation {
1211
- return this.someOrEvery(validation, WhereMethods.SOME);
1212
- }
1213
-
1214
- every(validation: WhereClause<S>): SetEvaluation {
1215
- return this.someOrEvery(validation, WhereMethods.EVERY);
1216
- }
1217
-
1218
- private someOrEvery(validation: WhereClause<S>, method: WhereMethods) {
1219
- let leastSpecificShape = this.getOriginalValue().getLeastSpecificShape();
1220
- //do we need to store this here? or are we accessing the evaluation and then going backwards?
1221
- //in that case just pass it to the evaluation and don't use this.wherePath
1222
- let wherePath = processWhereClause(validation, leastSpecificShape);
1223
- return new SetEvaluation(this, method, [wherePath]);
1224
- }
1225
- }
1226
-
1227
- export class QueryShape<
1228
- S extends Shape = Shape,
1229
- Source = any,
1230
- Property extends string | number | symbol = any,
1231
- > extends QueryBuilderObject<S, Source, Property> {
1232
- public isSource: boolean;
1233
- private proxy;
1234
-
1235
- constructor(
1236
- public originalValue: S,
1237
- property?: PropertyShape,
1238
- subject?: QueryShape<any> | QueryShapeSet<any>,
1239
- ) {
1240
- super(property, subject);
1241
- }
1242
-
1243
- get id() {
1244
- return (
1245
- (this.originalValue as Shape).__queryContextId ||
1246
- this.originalValue['id']
1247
- );
1248
- }
1249
-
1250
- // where(validation: WhereClause<S>): this {
1251
- // let nodeShape = this.originalValue.nodeShape;
1252
- // this.wherePath = processWhereClause(validation, nodeShape);
1253
- // //return this because after Shape.friends.where() we can call other methods of Shape.friends
1254
- // return this.proxy;
1255
- // }
1256
-
1257
- static create(
1258
- original: Shape,
1259
- property?: PropertyShape,
1260
- subject?: QueryShape<any> | QueryShapeSet<any>,
1261
- ) {
1262
- let instance = new QueryShape(original, property, subject);
1263
- let proxy = this.proxifyQueryShape(instance);
1264
- return proxy;
1265
- }
1266
-
1267
- private static proxifyQueryShape<T extends Shape>(queryShape: QueryShape<T>) {
1268
- let originalShape = queryShape.originalValue;
1269
- queryShape.proxy = new Proxy(queryShape, {
1270
- get(target, key, receiver) {
1271
- //if the key is a string
1272
- if (typeof key === 'string') {
1273
- //if this is a get method that is implemented by the QueryShape, then use that
1274
- if (key in queryShape) {
1275
- //if it's a function, then bind it to the queryShape and return it so it can be called
1276
- if (typeof queryShape[key] === 'function') {
1277
- return target[key].bind(target);
1278
- }
1279
- //if it's a get method, then return that
1280
- //NOTE: we may not need this if we don't use any get methods in QueryValue classes?
1281
- return queryShape[key];
1282
- }
1283
-
1284
- //if not, then a method/accessor of the original shape was called
1285
- //then check if we have indexed any property shapes with that name for this shapes NodeShape
1286
- //NOTE: this will only work with a @linkedProperty decorator
1287
- // let propertyShape = originalShape.nodeShape
1288
- // .getPropertyShapes()
1289
- // .find((propertyShape) => propertyShape.label === key);
1290
-
1291
- let propertyShape = getPropertyShapeByLabel(
1292
- originalShape.constructor as typeof Shape,
1293
- key,
1294
- );
1295
- if (propertyShape) {
1296
- //generate the query shape based on the property shape
1297
- // let nodeValue;
1298
- // if(propertyShape.maxCount <= 1) {
1299
- // nodeValue = new TestNode(propertyShape.path);
1300
- // } else {
1301
- // nodeValue = new NodeSet(new TestNode(propertyShape.path));
1302
- // }
1303
-
1304
- return QueryBuilderObject.generatePathValue(propertyShape, target);
1305
-
1306
- //get the value of the property from the original shape
1307
- // let value = originalShape[key];
1308
- // //convert the value into a query value
1309
- // return QueryBuilderObject.convertOriginal(
1310
- // value,
1311
- // propertyShape,
1312
- // queryShape,
1313
- // );
1314
- }
1315
- }
1316
- if (key !== 'then' && key !== '$$typeof') {
1317
- // //otherwise return the value of the property on the original shape
1318
- //generate stack trace for debugging
1319
- let stack = new Error().stack;
1320
- //https://stackoverflow.com/a/49725198/977206
1321
- const stackLines = stack.split('\n').slice(1); //remove the "Error" line
1322
- console.warn(
1323
- `${originalShape.constructor.name}.${key.toString()} is accessed in a query, but it does not have a @linkedProperty decorator. Queries can only access decorated get/set methods. ${stackLines.join('\n')}`,
1324
- );
1325
- // } else {
1326
- // console.error('Proxy is accessed like a promise');
1327
- }
1328
- return originalShape[key];
1329
- },
1330
- });
1331
- return queryShape.proxy;
1332
- }
1333
-
1334
- as<ShapeClass extends typeof Shape>(
1335
- shape: ShapeClass,
1336
- ): QShape<InstanceType<ShapeClass>, Source, Property> {
1337
- //if the shape is not the same as the original value, then we need to create a new query shape
1338
- if (!shape.shape.equals(this.originalValue.nodeShape)) {
1339
- let newOriginal = new (shape as any)();
1340
- if (this.originalValue.id) {
1341
- newOriginal.id = this.originalValue.id;
1342
- }
1343
- return QueryShape.create(newOriginal, this.property, this.subject as any);
1344
- }
1345
- // else return this
1346
- return this as any as QShape<InstanceType<ShapeClass>, Source, Property>;
1347
- // return this.proxy;
1348
- }
1349
-
1350
- equals(otherValue: NodeReferenceValue | QShape<any>) {
1351
- return new Evaluation(this, WhereMethods.EQUALS, [otherValue]);
1352
- }
1353
-
1354
- select<QF = unknown>(
1355
- subQueryFn: QueryBuildFn<S, QF>,
1356
- ): SelectQueryFactory<S, QF, QueryShape<S, Source, Property>> {
1357
- let leastSpecificShape = getShapeClass(
1358
- (this.getOriginalValue() as Shape).nodeShape.id,
1359
- );
1360
- let subQuery = new SelectQueryFactory(
1361
- leastSpecificShape as ShapeType,
1362
- subQueryFn,
1363
- );
1364
- subQuery.parentQueryPath = this.getPropertyPath();
1365
- return subQuery as any;
1366
- }
1367
-
1368
- // count(countable: QueryBuilderObject, resultKey?: string): SetSize<this> {
1369
- // return new SetSize(this, countable, resultKey);
1370
- // // return this._count;
1371
- // }
1372
- }
1373
-
1374
- export class Evaluation {
1375
- private _andOr: AndOrQueryToken[] = [];
1376
-
1377
- constructor(
1378
- public value: QueryBuilderObject | QueryPrimitiveSet,
1379
- public method: WhereMethods,
1380
- public args: QueryArg[],
1381
- ) {}
1382
-
1383
- getPropertyPath() {
1384
- return this.getWherePath();
1385
- }
1386
-
1387
- processArgs(): QueryArg[] {
1388
- //if the args are not an array, then we convert them to an array
1389
- if (!this.args || !Array.isArray(this.args)) {
1390
- return [];
1391
- }
1392
- //convert each arg to a QueryBuilderObject
1393
- return this.args.map((arg) => {
1394
- if (arg instanceof QueryBuilderObject) {
1395
- let path = arg.getPropertyPath();
1396
- let subject;
1397
- if (path[0] && (path[0] as ShapeReferenceValue).id) {
1398
- subject = path.shift();
1399
- }
1400
- if ((!path || path.length === 0) && subject) {
1401
- return subject as ShapeReferenceValue;
1402
- }
1403
- return {
1404
- path,
1405
- subject,
1406
- } as ArgPath;
1407
- } else {
1408
- return arg;
1409
- }
1410
- });
1411
- }
1412
-
1413
- getWherePath(): WherePath {
1414
- let evalPath: WhereEvaluationPath = {
1415
- path: this.value.getPropertyPath(),
1416
- method: this.method,
1417
- args: this.processArgs(),
1418
- };
1419
-
1420
- if (this._andOr.length > 0) {
1421
- return {
1422
- firstPath: evalPath,
1423
- andOr: this._andOr,
1424
- };
1425
- }
1426
- return evalPath;
1427
- }
1428
-
1429
- and(subQuery: WhereClause<any>) {
1430
- this._andOr.push({
1431
- and: processWhereClause(subQuery),
1432
- });
1433
- return this;
1434
- }
1435
-
1436
- or(subQuery: WhereClause<any>) {
1437
- this._andOr.push({
1438
- or: processWhereClause(subQuery),
1439
- });
1440
- return this;
1441
- }
1442
- }
1443
-
1444
- class SetEvaluation extends Evaluation {}
1445
-
1446
- // class QueryBoolean extends QueryBuilderObject<boolean> {
1447
- // constructor(
1448
- // property?: PropertyShape,
1449
- // subject?: QueryShape<any> | QueryShapeSet<any>,
1450
- // ) {
1451
- // super(property, subject);
1452
- // }
1453
- // }
1454
-
1455
- /**
1456
- * The class that is used for when JS primitives are converted to a QueryValue
1457
- * This is extended by QueryString, QueryNumber, QueryBoolean, etc
1458
- */
1459
- export abstract class QueryPrimitive<
1460
- T,
1461
- Source = any,
1462
- Property extends string | number | symbol = any,
1463
- > extends QueryBuilderObject<T, Source, Property> {
1464
- constructor(
1465
- public originalValue?: T,
1466
- public property?: PropertyShape,
1467
- public subject?: QueryShape<any> | QueryShapeSet<any> | QueryPrimitiveSet,
1468
- ) {
1469
- super(property, subject);
1470
- }
1471
-
1472
- equals(otherValue: JSPrimitive | QueryBuilderObject) {
1473
- //TODO: review types, this is working but currently QueryBuilderObject is not accepted as a type of args
1474
- return new Evaluation(this, WhereMethods.EQUALS, [otherValue as any]);
1475
- }
1476
-
1477
- where(validation: WhereClause<string>): this {
1478
- // let nodeShape = this.subject.getOriginalValue().nodeShape;
1479
- this.wherePath = processWhereClause(validation, new QueryString(''));
1480
- //return this because after Shape.friends.where() we can call other methods of Shape.friends
1481
- return this as any;
1482
- }
1483
- }
1484
-
1485
- //@TODO: QueryString, QueryNumber, QueryBoolean, QueryDate can all be replaced with QueryPrimitive, and we can infer the original type, no need for these extra classes
1486
- //UPDATE some of this has started. Query response to result conversion is using QueryPrimitive only
1487
- export class QueryString<
1488
- Source = any,
1489
- Property extends string | number | symbol = '',
1490
- > extends QueryPrimitive<string, Source, Property> {}
1491
-
1492
- export class QueryDate<
1493
- Source = any,
1494
- Property extends string | number | symbol = any,
1495
- > extends QueryPrimitive<Date, Source, Property> {}
1496
-
1497
- export class QueryNumber<
1498
- Source = any,
1499
- Property extends string | number | symbol = any,
1500
- > extends QueryPrimitive<number, Source, Property> {}
1501
-
1502
- export class QueryBoolean<
1503
- Source = any,
1504
- Property extends string | number | symbol = any,
1505
- > extends QueryPrimitive<boolean, Source, Property> {}
1506
-
1507
- export class QueryPrimitiveSet<
1508
- QPrimitive extends QueryPrimitive<any> = null,
1509
- > extends QueryBuilderObject<any, any, any> {
1510
- public contents: CoreSet<QPrimitive>;
1511
-
1512
- constructor(
1513
- public originalValue?: JSNonNullPrimitive[],
1514
- public property?: PropertyShape,
1515
- public subject?: QueryShapeSet<any> | QueryShape<any>,
1516
- items?,
1517
- ) {
1518
- super(property, subject);
1519
- this.contents = new CoreSet(items);
1520
- }
1521
-
1522
- add(item) {
1523
- this.contents.add(item);
1524
- }
1525
-
1526
- values() {
1527
- return [...this.contents.values()];
1528
- }
1529
-
1530
- //this is needed because we extend CoreSet which has a createNew method but does not expect the constructor to have arguments
1531
- createNew(...args): this {
1532
- return new (<any>this.constructor)(
1533
- this.property,
1534
- this.subject,
1535
- ...args,
1536
- ) as this;
1537
- }
1538
-
1539
- //TODO: see if we can merge these methods of QueryString and QueryPrimitiveSet and soon other things like QueryNumber
1540
- // so that they're only defined once
1541
- equals(other) {
1542
- return new Evaluation(this, WhereMethods.EQUALS, [other]);
1543
- }
1544
-
1545
- getPropertyStep(): QueryStep {
1546
- if (this.contents.size > 1) {
1547
- throw new Error(
1548
- 'This should never happen? Not implemented: get property path for a QueryPrimitiveSet with multiple values',
1549
- );
1550
- }
1551
- return this.contents.first().getPropertyStep();
1552
- }
1553
-
1554
- getPropertyPath(): QueryPropertyPath {
1555
- if (this.contents.size > 1) {
1556
- throw new Error(
1557
- 'This should never happen? Not implemented: get property path for a QueryPrimitiveSet with multiple values',
1558
- );
1559
- }
1560
- //here we let the first item in the set return its property path, because all items will be the same
1561
- //however, sometimes the path goes through the subject of this SET rather than the individual items (which have an individual shape as subject)
1562
- //so we pass the subject of this set so it can be used
1563
- let first = this.contents.first();
1564
- if (first) {
1565
- (first.subject as QueryShapeSet).wherePath =
1566
- (first.subject as QueryShapeSet).wherePath || this.subject.wherePath;
1567
- return first.getPropertyPath();
1568
- } else {
1569
- console.warn(
1570
- `QueryPrimitiveSet without items. From ${this.subject.getOriginalValue().nodeShape.label}.${this.property.label}. What to return as property path?`,
1571
- );
1572
- return this.subject.getPropertyPath();
1573
- }
1574
- }
1575
-
1576
- //countable, resultKey?: string
1577
- size(): SetSize<this> {
1578
- return new SetSize(this as QueryPrimitiveSet);
1579
- //countable, resultKey
1580
- }
1581
- }
1582
-
1583
- let documentLoaded = false;
1584
- let callbackStack = [];
1585
- const docReady = () => {
1586
- documentLoaded = true;
1587
- callbackStack.forEach((callback) => callback());
1588
- callbackStack = [];
1589
- };
1590
- if (typeof document === 'undefined' || document.readyState !== 'loading') {
1591
- docReady();
1592
- } else {
1593
- documentLoaded = false;
1594
- document.addEventListener('DOMContentLoaded', () => () => {
1595
- docReady();
1596
- });
1597
- setTimeout(() => {
1598
- if (!documentLoaded) {
1599
- console.warn('⚠️ Forcing init after timeout');
1600
- docReady();
1601
- }
1602
- }, 3500);
1603
- }
1604
- //only continue to parse the query if the document is ready, and all shapes from initial bundles are loaded
1605
- export var onQueriesReady = (callback) => {
1606
- if (!documentLoaded) {
1607
- callbackStack.push(callback);
1608
- } else {
1609
- callback();
1610
- }
1611
- };
1612
-
1613
- export class SelectQueryFactory<
1614
- S extends Shape,
1615
- ResponseType = any,
1616
- Source = any,
1617
- > extends QueryFactory {
1618
- /**
1619
- * The returned value when the query was initially run.
1620
- * Will likely be an array or object or query values that can be used to trace back which methods/accessors were used in the query.
1621
- * @private
1622
- */
1623
- public traceResponse: ResponseType;
1624
- public sortResponse: any;
1625
- public sortDirection: string;
1626
- public parentQueryPath: QueryPath;
1627
- public singleResult: boolean;
1628
- private limit: number;
1629
- private offset: number;
1630
- private wherePath: WherePath;
1631
- private initPromise: {
1632
- promise: Promise<any>;
1633
- resolve;
1634
- reject;
1635
- complete?: boolean;
1636
- };
1637
- debugStack: string;
1638
-
1639
- constructor(
1640
- public shape: ShapeType<S>,
1641
- private queryBuildFn?: QueryBuildFn<S, ResponseType>,
1642
- public subject?: S | ShapeSet<S> | QResult<S>,
1643
- ) {
1644
- super();
1645
-
1646
- let promise, resolve, reject;
1647
- promise = new Promise((res, rej) => {
1648
- resolve = res;
1649
- reject = rej;
1650
- });
1651
- this.initPromise = {promise, resolve, reject, complete: false};
1652
-
1653
- //only continue to parse the query if the document is ready, and all shapes from initial bundles are loaded
1654
- if (typeof document === 'undefined' || document.readyState !== 'loading') {
1655
- this.init();
1656
- } else {
1657
- document.addEventListener('DOMContentLoaded', () => this.init());
1658
- setTimeout(() => {
1659
- if (!this.initPromise.complete) {
1660
- console.warn('⚠️ Forcing init after timeout');
1661
- this.init();
1662
- }
1663
- }, 3500);
1664
- }
1665
- }
1666
-
1667
- setLimit(limit: number) {
1668
- this.limit = limit;
1669
- }
1670
-
1671
- getLimit() {
1672
- return this.limit;
1673
- }
1674
-
1675
- setOffset(offset: number) {
1676
- this.offset = offset;
1677
- }
1678
-
1679
- getOffset() {
1680
- return this.offset;
1681
- }
1682
-
1683
- setSubject(subject) {
1684
- this.subject = subject;
1685
- return this;
1686
- }
1687
-
1688
- where(validation: WhereClause<S>): this {
1689
- this.wherePath = processWhereClause(validation, this.shape);
1690
- return this;
1691
- }
1692
-
1693
- exec(): Promise<QueryResponseToResultType<ResponseType>[]> {
1694
- return Shape.queryParser.selectQuery(this);
1695
- }
1696
-
1697
- /**
1698
- * Turns the LinkedQuery into a SelectQuery, which is a plain JS object that can be serialized to JSON
1699
- */
1700
- getQueryObject(): SelectQuery<S> {
1701
- try {
1702
- let queryPaths = this.getQueryPaths();
1703
- let selectQuery = {
1704
- type: 'select',
1705
- select: queryPaths,
1706
- subject: this.getSubject(),
1707
- limit: this.limit,
1708
- offset: this.offset,
1709
- shape: this.shape,
1710
- sortBy: this.getSortByPath(),
1711
- //the query is selecting a single result if it explicitly requested it, or if the subject is a specific subject (with a URI or ID)
1712
- singleResult:
1713
- this.singleResult ||
1714
- !!(
1715
- this.subject &&
1716
- ('id' in (this.subject as S) || 'id' in (this.subject as QResult<S>))
1717
- ),
1718
- } as SelectQuery<S>;
1719
-
1720
- if (this.wherePath) {
1721
- selectQuery.where = this.wherePath;
1722
- }
1723
- return selectQuery;
1724
- } catch (err) {
1725
- console.error('Error in getQueryObject', err);
1726
- throw err;
1727
- }
1728
- }
1729
-
1730
- // applyTo(subject) {
1731
- // return new LinkedQuery(this.shape, this.queryBuildFn, subject);
1732
- // }
1733
-
1734
- getSubject() {
1735
- //if the subject is a QueryShape which comes from query context
1736
- //then it will carry a query context id and we convert it to a node reference
1737
- //NOTE: its important to access originalValue instead of .node directly because QueryShape.node may be undefined
1738
- if ((this.subject as QueryShape)?.originalValue?.__queryContextId) {
1739
- return convertQueryContext(this.subject as QueryShape);
1740
- }
1741
- // }
1742
- return this.subject;
1743
- }
1744
-
1745
- /**
1746
- * Returns an array of query paths
1747
- * A single query can request multiple things in multiple "query paths" (For example this is using 2 paths: Shape.select(p => [p.name, p.friends.name]))
1748
- * Each query path is returned as array of the property paths requested, with potential where clauses (together called a QueryStep)
1749
- */
1750
- getQueryPaths(
1751
- response = this.traceResponse,
1752
- ): CustomQueryObject | QueryPath[] {
1753
- let queryPaths: QueryPath[] = [];
1754
- let queryObject: CustomQueryObject;
1755
- //if the trace response is an array, then multiple paths were requested
1756
- if (
1757
- response instanceof QueryBuilderObject ||
1758
- response instanceof QueryPrimitiveSet
1759
- ) {
1760
- //if it's a single value, then only one path was requested, and we can add it directly
1761
- queryPaths.push(response.getPropertyPath());
1762
- } else if (Array.isArray(response) || response instanceof Set) {
1763
- response.forEach((endValue) => {
1764
- if (endValue instanceof QueryBuilderObject) {
1765
- queryPaths.push(endValue.getPropertyPath());
1766
- } else if (endValue instanceof SelectQueryFactory) {
1767
- queryPaths.push(
1768
- (endValue as SelectQueryFactory<any>).getQueryPaths() as any,
1769
- );
1770
- }
1771
- });
1772
- } else if (response instanceof Evaluation) {
1773
- queryPaths.push(response.getWherePath());
1774
- } else if (response instanceof SelectQueryFactory) {
1775
- queryPaths.push(
1776
- (response as SelectQueryFactory<any, any>).getQueryPaths() as any,
1777
- );
1778
- } else if (!response) {
1779
- //that's totally fine. For example Person.select().where(p => p.name.equals('John'))
1780
- //will return all persons with the name John, but no properties are selected for these persons
1781
- }
1782
- //if it's an object
1783
- else if (typeof response === 'object') {
1784
- queryObject = {};
1785
- //then loop over all the keys
1786
- Object.getOwnPropertyNames(response).forEach((key) => {
1787
- //and add the property paths for each key
1788
- const value = response[key];
1789
- //TODO: we could potentially make Evaluation extend QueryValue, and rename getPropertyPath to something more generic,
1790
- //that way we can simplify the code perhaps? Or would we loose type clarity? (QueryStep is the generic one for QueryValue, and Evaluation can just return WherePath right?)
1791
- if (
1792
- value instanceof QueryBuilderObject ||
1793
- value instanceof QueryPrimitiveSet
1794
- ) {
1795
- queryObject[key] = value.getPropertyPath();
1796
- } else if (value instanceof Evaluation) {
1797
- queryObject[key] = value.getWherePath();
1798
- } else {
1799
- throw Error('Unknown trace response type for key ' + key);
1800
- }
1801
- });
1802
- } else {
1803
- throw Error('Unknown trace response type');
1804
- }
1805
-
1806
- if (this.parentQueryPath) {
1807
- queryPaths = (this.parentQueryPath as any[]).concat([
1808
- queryObject || queryPaths,
1809
- ]);
1810
- //reset the variable so it doesn't get used again below
1811
- queryObject = null;
1812
- }
1813
- return queryObject || queryPaths;
1814
- }
1815
-
1816
- isValidSetResult(qResults: QResult<any>[]) {
1817
- return qResults.every((qResult) => {
1818
- return this.isValidResult(qResult);
1819
- });
1820
- }
1821
-
1822
- isValidResult(qResult: QResult<any>) {
1823
- let select = this.getQueryObject().select;
1824
- if (Array.isArray(select)) {
1825
- return this.isValidQueryPathsResult(qResult, select);
1826
- } else if (typeof select === 'object') {
1827
- return this.isValidCustomObjectResult(qResult, select);
1828
- }
1829
- }
1830
-
1831
- clone() {
1832
- return new SelectQueryFactory(this.shape, this.queryBuildFn, this.subject);
1833
- }
1834
-
1835
- /**
1836
- * Makes a clone of the query template, sets the subject and executes the query
1837
- * @param subject
1838
- */
1839
- execFor(subject) {
1840
- //TODO: Differentiate between the result of Shape.query and the internal query in Shape.select?
1841
- // so that Shape.query can never be executed. Its just a template
1842
- return this.clone().setSubject(subject).exec();
1843
- }
1844
-
1845
- patchResultPromise<ResultType>(
1846
- p: Promise<ResultType>,
1847
- ): PatchedQueryPromise<any, S> {
1848
- let pAdjusted = p as PatchedQueryPromise<ResultType, S>;
1849
- p['where'] = (
1850
- validation: WhereClause<S>,
1851
- ): PatchedQueryPromise<ResultType, S> => {
1852
- // preventExec();
1853
- this.where(validation);
1854
- return pAdjusted;
1855
- };
1856
- p['limit'] = (lim: number): PatchedQueryPromise<ResultType, S> => {
1857
- this.setLimit(lim);
1858
- return pAdjusted;
1859
- };
1860
- p['sortBy'] = (
1861
- sortFn: QueryBuildFn<S, any>,
1862
- direction: string = 'ASC',
1863
- ): PatchedQueryPromise<ResultType, S> => {
1864
- this.sortBy(sortFn, direction);
1865
- return pAdjusted;
1866
- };
1867
- p['one'] = (): PatchedQueryPromise<ResultType, S> => {
1868
- this.setLimit(1);
1869
- this.singleResult = true;
1870
- return pAdjusted;
1871
- };
1872
-
1873
- return p as any as PatchedQueryPromise<SingleResult<ResultType>, S>;
1874
- }
1875
-
1876
- sortBy<R>(sortFn: QueryBuildFn<S, R>, direction) {
1877
- let queryShape = this.getQueryShape();
1878
- if (sortFn) {
1879
- this.sortResponse = sortFn(queryShape as any, this);
1880
- this.sortDirection = direction;
1881
- }
1882
- return this;
1883
- }
1884
-
1885
- private init() {
1886
- let queryShape = this.getQueryShape();
1887
-
1888
- if (this.queryBuildFn) {
1889
- let queryResponse = this.queryBuildFn(queryShape as any, this);
1890
- this.traceResponse = queryResponse;
1891
- }
1892
- this.initPromise.resolve(this.traceResponse);
1893
- this.initPromise.complete = true;
1894
- }
1895
-
1896
- private initialized() {
1897
- return this.initPromise.promise;
1898
- }
1899
-
1900
- /**
1901
- * Returns the dummy shape instance who's properties can be accessed freely inside a queryBuildFn
1902
- * It is used to trace the properties that are accessed in the queryBuildFn
1903
- * @private
1904
- */
1905
- private getQueryShape() {
1906
- let queryShape: QueryBuilderObject;
1907
- //if the given class already extends QueryValue
1908
- if (this.shape instanceof QueryBuilderObject) {
1909
- //then we're likely dealing with QueryPrimitives (end values like strings)
1910
- //and we can use the given query value directly for the query evaluation
1911
- queryShape = this.shape;
1912
- } else {
1913
- //else a shape class is given, and we need to create a dummy node to apply and trace the query
1914
- let dummyShape = new (this.shape as any)();
1915
- queryShape = QueryShape.create(dummyShape);
1916
- }
1917
- return queryShape;
1918
- }
1919
-
1920
- private getSortByPath() {
1921
- if (!this.sortResponse) return null;
1922
- //TODO: we should put more restrictions on sortBy and getting query paths from the response
1923
- // currently it reuses much of the select logic, but for example using .where() should probably not be allowed in a sortBy function?
1924
- return {
1925
- paths: this.getQueryPaths(this.sortResponse),
1926
- direction: this.sortDirection,
1927
- };
1928
- }
1929
-
1930
- private isValidQueryPathsResult(qResult: QResult<any>, select: QueryPath[]) {
1931
- return select.every((path) => {
1932
- return this.isValidQueryPathResult(qResult, path);
1933
- });
1934
- }
1935
-
1936
- private isValidQueryPathResult(
1937
- qResult: QResult<any>,
1938
- path: QueryPath,
1939
- nameOverwrite?: string,
1940
- ) {
1941
- if (Array.isArray(path)) {
1942
- return this.isValidQueryStepResult(
1943
- qResult,
1944
- path[0],
1945
- path.splice(1),
1946
- nameOverwrite,
1947
- );
1948
- } else {
1949
- if ((path as WhereAndOr).firstPath) {
1950
- return this.isValidQueryPathResult(
1951
- qResult,
1952
- (path as WhereAndOr).firstPath,
1953
- );
1954
- } else if ((path as WhereEvaluationPath).path) {
1955
- return this.isValidQueryPathResult(
1956
- qResult,
1957
- (path as WhereEvaluationPath).path,
1958
- );
1959
- }
1960
- }
1961
- }
1962
-
1963
- private isValidQueryStepResult(
1964
- qResult: QResult<any>,
1965
- step: QueryStep | SubQueryPaths,
1966
- restPath: (QueryStep | SubQueryPaths)[] = [],
1967
- nameOverwrite?: string,
1968
- ): boolean {
1969
- if (!qResult) {
1970
- return false;
1971
- }
1972
- if ((step as PropertyQueryStep).property) {
1973
- //if a name overwrite is given we check if that key exists instead of the property label
1974
- //this happens with custom objects: for the first property step, the named key will be the accessKey used in the result instead of the first property label.
1975
- //e.g. {title:item.name} in a query will result in a "title" key in the result, not "name"
1976
- const accessKey =
1977
- nameOverwrite || (step as PropertyQueryStep).property.label;
1978
- //also check if this property needs to have a value (minCount > 0), if not it can be empty and undefined
1979
- // if (!qResult.hasOwnProperty(accessKey) && (step as PropertyQueryStep).property.minCount > 0) {
1980
- //the key must be in the object. If there is no value then it should be null (or undefined, but null works better with JSON.stringify, as it keeps the key. Whilst undefined keys get removed)
1981
- if (!qResult.hasOwnProperty(accessKey)) {
1982
- return false;
1983
- }
1984
- if (restPath.length > 0) {
1985
- return this.isValidQueryStepResult(
1986
- qResult[accessKey],
1987
- restPath[0],
1988
- restPath.splice(1),
1989
- );
1990
- }
1991
- return true;
1992
- } else if ((step as SizeStep).count) {
1993
- return this.isValidQueryStepResult(qResult, (step as SizeStep).count[0]);
1994
- } else if (Array.isArray(step)) {
1995
- return step.every((subStep) => {
1996
- return this.isValidQueryPathResult(qResult, subStep);
1997
- });
1998
- } else if (typeof step === 'object') {
1999
- if (Array.isArray(qResult)) {
2000
- return qResult.every((singleResult) => {
2001
- return this.isValidQueryStepResult(singleResult, step);
2002
- });
2003
- }
2004
- return this.isValidCustomObjectResult(qResult, step as CustomQueryObject);
2005
- }
2006
- }
2007
-
2008
- private isValidCustomObjectResult(
2009
- qResult: QResult<any>,
2010
- step: CustomQueryObject,
2011
- ) {
2012
- //for custom objects, all keys need to be defined, even if the value is undefined
2013
- for (let key in step as CustomQueryObject) {
2014
- if (!qResult.hasOwnProperty(key)) {
2015
- return false;
2016
- }
2017
- let path: QueryPath = step[key];
2018
- if (!this.isValidQueryPathResult(qResult, path, key)) {
2019
- return false;
2020
- }
2021
- // return this.isValidQueryPathResult(qResult[key], path);
2022
- }
2023
- return true;
2024
- }
2025
- }
2026
-
2027
- export class SetSize<Source = null> extends QueryNumber<Source> {
2028
- constructor(
2029
- public subject: QueryShapeSet | QueryShape | QueryPrimitiveSet,
2030
- public countable?: QueryBuilderObject,
2031
- public label?: string,
2032
- ) {
2033
- super();
2034
- }
2035
-
2036
- as(label: string) {
2037
- this.label = label;
2038
- return this;
2039
- }
2040
-
2041
- getPropertyPath(): QueryPropertyPath {
2042
- //if a countable argument was given
2043
- // if (this.countable) {
2044
- //then creating the count step is straightforward
2045
- // let countablePath = this.countable.getPropertyPath();
2046
- // if (countablePath.some((step) => Array.isArray(step))) {
2047
- // throw new Error(
2048
- // 'Cannot count a diverging path. Provide one path of properties to count',
2049
- // );
2050
- // }
2051
- // let self: CountStep = {
2052
- // count: this.countable?.getPropertyPath(),
2053
- // label: this.label,
2054
- // };
2055
- // //and we can add the count step to the path of the subject
2056
- // let parent = this.subject.getPropertyPath();
2057
- // parent.push(self);
2058
- // return parent;
2059
- // } else {
2060
-
2061
- //if nothing to count was given as an argument,
2062
- //then we just count the last property in the path
2063
- //also, we use the label of the last property as the label of the count step
2064
- let countable = this.subject.getPropertyStep();
2065
- let self: SizeStep = {
2066
- count: [countable],
2067
- label: this.label || this.subject.property.label, //the default is property name + 'Size', i.e., friendsSize
2068
- //numFriends
2069
- // label: this.label || 'num'+this.subject.property.label[0].toUpperCase()+this.subject.property.label.slice(1),//the default is property name + 'Size', i.e., friendsSize
2070
- };
2071
-
2072
- //in that case we request the path of the subject of the subject (the parent of the parent)
2073
- //and add the CountStep to that path
2074
- //since we already used the subject as the thing that's counted.
2075
- if (this.subject.subject) {
2076
- let path = this.subject.subject.getPropertyPath();
2077
- path.push(self);
2078
- return path;
2079
- }
2080
- //if there is no parent of a parent, then we just return the count step as the whole path
2081
- return [self];
2082
- // }
2083
- }
2084
- }
2085
-
2086
- /**
2087
- * A sub query that is used to filter results
2088
- * i.e p.friends.where(f => //LinkedWhereQuery here)
2089
- */
2090
- export class LinkedWhereQuery<
2091
- S extends Shape,
2092
- ResponseType = any,
2093
- > extends SelectQueryFactory<S, ResponseType> {
2094
- getResponse() {
2095
- return this.traceResponse as Evaluation;
2096
- }
2097
-
2098
- getWherePath() {
2099
- return (this.traceResponse as Evaluation).getWherePath();
2100
- }
2101
- }