@_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.
- package/CHANGELOG.md +71 -0
- package/LICENSE +1 -1
- package/README.md +147 -92
- package/lib/cjs/queries/SelectQuery.d.ts +5 -0
- package/lib/cjs/queries/SelectQuery.js +14 -0
- package/lib/cjs/queries/SelectQuery.js.map +1 -1
- package/lib/cjs/shapes/SHACL.d.ts +1 -0
- package/lib/cjs/shapes/SHACL.js +82 -2
- package/lib/cjs/shapes/SHACL.js.map +1 -1
- package/lib/cjs/shapes/Shape.d.ts +13 -1
- package/lib/cjs/shapes/Shape.js +6 -0
- package/lib/cjs/shapes/Shape.js.map +1 -1
- package/lib/cjs/test-helpers/query-fixtures.d.ts +583 -0
- package/lib/cjs/test-helpers/query-fixtures.js +39 -1
- package/lib/cjs/test-helpers/query-fixtures.js.map +1 -1
- package/lib/esm/queries/SelectQuery.d.ts +5 -0
- package/lib/esm/queries/SelectQuery.js +14 -0
- package/lib/esm/queries/SelectQuery.js.map +1 -1
- package/lib/esm/shapes/SHACL.d.ts +1 -0
- package/lib/esm/shapes/SHACL.js +82 -2
- package/lib/esm/shapes/SHACL.js.map +1 -1
- package/lib/esm/shapes/Shape.d.ts +13 -1
- package/lib/esm/shapes/Shape.js +6 -0
- package/lib/esm/shapes/Shape.js.map +1 -1
- package/lib/esm/test-helpers/query-fixtures.d.ts +583 -0
- package/lib/esm/test-helpers/query-fixtures.js +38 -0
- package/lib/esm/test-helpers/query-fixtures.js.map +1 -1
- package/package.json +17 -1
- package/.context/notes.md +0 -0
- package/.context/todos.md +0 -0
- package/AGENTS.md +0 -59
- package/docs/001-core-extraction.md +0 -305
- package/jest.config.js +0 -25
- package/scripts/dual-package.js +0 -25
- package/src/collections/CoreMap.ts +0 -127
- package/src/collections/CoreSet.ts +0 -171
- package/src/collections/ShapeSet.ts +0 -18
- package/src/index.ts +0 -88
- package/src/interfaces/ICoreIterable.ts +0 -35
- package/src/interfaces/IFileStore.ts +0 -28
- package/src/interfaces/IQuadStore.ts +0 -16
- package/src/interfaces/IQueryParser.ts +0 -51
- package/src/ontologies/lincd.ts +0 -25
- package/src/ontologies/npm.ts +0 -15
- package/src/ontologies/owl.ts +0 -26
- package/src/ontologies/rdf.ts +0 -32
- package/src/ontologies/rdfs.ts +0 -38
- package/src/ontologies/shacl.ts +0 -136
- package/src/ontologies/xsd.ts +0 -47
- package/src/package.ts +0 -11
- package/src/queries/CreateQuery.ts +0 -41
- package/src/queries/DeleteQuery.ts +0 -54
- package/src/queries/MutationQuery.ts +0 -287
- package/src/queries/QueryContext.ts +0 -41
- package/src/queries/QueryFactory.ts +0 -275
- package/src/queries/QueryParser.ts +0 -79
- package/src/queries/SelectQuery.ts +0 -2101
- package/src/queries/UpdateQuery.ts +0 -47
- package/src/shapes/List.ts +0 -52
- package/src/shapes/SHACL.ts +0 -653
- package/src/shapes/Shape.ts +0 -282
- package/src/test-helpers/query-fixtures.ts +0 -313
- package/src/tests/core-utils.test.ts +0 -286
- package/src/tests/metadata.test.ts +0 -65
- package/src/tests/query.test.ts +0 -599
- package/src/tests/query.types.test.ts +0 -606
- package/src/tests/store-routing.test.ts +0 -133
- package/src/utils/LinkedErrorLogging.ts +0 -25
- package/src/utils/LinkedFileStorage.ts +0 -75
- package/src/utils/LinkedStorage.ts +0 -120
- package/src/utils/NameSpace.ts +0 -5
- package/src/utils/NodeReference.ts +0 -16
- package/src/utils/Package.ts +0 -681
- package/src/utils/Prefix.ts +0 -108
- package/src/utils/ShapeClass.ts +0 -335
- package/src/utils/Types.ts +0 -19
- package/src/utils/URI.ts +0 -40
- package/src/utils/cached.ts +0 -53
- package/tsconfig-cjs.json +0 -8
- package/tsconfig-esm.json +0 -9
- 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
|
-
}
|