@snowtop/ent 0.1.11 → 0.1.13
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/action/experimental_action.js +2 -2
- package/action/operations.js +4 -2
- package/core/base.d.ts +12 -10
- package/core/clause.d.ts +4 -3
- package/core/clause.js +98 -43
- package/core/config.d.ts +6 -0
- package/core/context.d.ts +6 -14
- package/core/context.js +9 -4
- package/core/db.js +1 -1
- package/core/ent.d.ts +1 -0
- package/core/ent.js +30 -11
- package/core/loaders/assoc_count_loader.js +1 -1
- package/core/loaders/assoc_edge_loader.d.ts +3 -0
- package/core/loaders/assoc_edge_loader.js +18 -0
- package/core/loaders/object_loader.js +2 -2
- package/core/loaders/query_loader.d.ts +3 -3
- package/core/loaders/query_loader.js +2 -1
- package/core/loaders/raw_count_loader.js +1 -1
- package/core/query/assoc_query.d.ts +22 -0
- package/core/query/assoc_query.js +101 -2
- package/core/query/custom_query.js +2 -9
- package/core/query/query.d.ts +1 -0
- package/core/query/query.js +72 -11
- package/core/query/shared_assoc_test.js +404 -7
- package/core/query/shared_test.js +9 -37
- package/core/query_impl.d.ts +2 -1
- package/core/query_impl.js +25 -7
- package/graphql/query/edge_connection.js +2 -2
- package/package.json +2 -2
- package/parse_schema/parse.d.ts +4 -3
- package/parse_schema/parse.js +4 -3
- package/schema/base_schema.d.ts +3 -0
- package/schema/base_schema.js +2 -0
- package/schema/schema.d.ts +1 -0
- package/schema/struct_field.d.ts +4 -2
- package/schema/struct_field.js +33 -4
- package/scripts/custom_graphql.js +1 -1
- package/testutils/builder.d.ts +1 -1
- package/testutils/builder.js +4 -4
- package/testutils/ent-graphql-tests/index.js +2 -2
- package/testutils/fake_data/fake_contact.js +1 -1
- package/testutils/fake_data/fake_event.js +1 -1
- package/testutils/fake_data/test_helpers.js +2 -2
- package/testutils/fake_data/user_query.js +1 -1
- package/testutils/query.d.ts +9 -0
- package/testutils/query.js +45 -0
- package/testutils/write.js +3 -3
|
@@ -2,6 +2,7 @@ import { Context, ID, EdgeQueryableDataOptions, Loader, LoaderFactory } from "..
|
|
|
2
2
|
import { AssocEdge, AssocEdgeConstructor } from "../ent";
|
|
3
3
|
export interface AssocLoader<T extends AssocEdge> extends Loader<ID, T[]> {
|
|
4
4
|
loadEdgeForID2(id: ID, id2: ID): Promise<T | undefined>;
|
|
5
|
+
loadTwoWay(id: ID): Promise<T[]>;
|
|
5
6
|
}
|
|
6
7
|
export declare class AssocEdgeLoader<T extends AssocEdge> implements Loader<ID, T[]> {
|
|
7
8
|
private edgeType;
|
|
@@ -13,6 +14,7 @@ export declare class AssocEdgeLoader<T extends AssocEdge> implements Loader<ID,
|
|
|
13
14
|
constructor(edgeType: string, edgeCtr: AssocEdgeConstructor<T>, options: EdgeQueryableDataOptions, context: Context);
|
|
14
15
|
private getLoader;
|
|
15
16
|
load(id: ID): Promise<T[]>;
|
|
17
|
+
loadTwoWay(id: ID): Promise<T[]>;
|
|
16
18
|
loadEdgeForID2(id: ID, id2: ID): Promise<T | undefined>;
|
|
17
19
|
clearAll(): void;
|
|
18
20
|
}
|
|
@@ -23,6 +25,7 @@ export declare class AssocDirectEdgeLoader<T extends AssocEdge> implements Loade
|
|
|
23
25
|
context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined;
|
|
24
26
|
constructor(edgeType: string, edgeCtr: AssocEdgeConstructor<T>, options?: Partial<Pick<import("../base").QueryableDataOptions, "clause" | "limit" | "orderby" | "disableTransformations">> | undefined, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined);
|
|
25
27
|
load(id: ID): Promise<T[]>;
|
|
28
|
+
loadTwoWay(id: ID): Promise<T[]>;
|
|
26
29
|
loadEdgeForID2(id: ID, id2: ID): Promise<T | undefined>;
|
|
27
30
|
clearAll(): void;
|
|
28
31
|
}
|
|
@@ -113,6 +113,15 @@ class AssocEdgeLoader {
|
|
|
113
113
|
const loader = await this.loaderFn();
|
|
114
114
|
return loader.load(id);
|
|
115
115
|
}
|
|
116
|
+
async loadTwoWay(id) {
|
|
117
|
+
return (0, ent_1.loadTwoWayEdges)({
|
|
118
|
+
ctr: this.edgeCtr,
|
|
119
|
+
id1: id,
|
|
120
|
+
edgeType: this.edgeType,
|
|
121
|
+
context: this.context,
|
|
122
|
+
queryOptions: this.options,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
116
125
|
// maybe eventually optimize this
|
|
117
126
|
async loadEdgeForID2(id, id2) {
|
|
118
127
|
return (0, ent_1.loadEdgeForID2)({
|
|
@@ -145,6 +154,15 @@ class AssocDirectEdgeLoader {
|
|
|
145
154
|
ctr: this.edgeCtr,
|
|
146
155
|
});
|
|
147
156
|
}
|
|
157
|
+
async loadTwoWay(id) {
|
|
158
|
+
return (0, ent_1.loadTwoWayEdges)({
|
|
159
|
+
ctr: this.edgeCtr,
|
|
160
|
+
id1: id,
|
|
161
|
+
edgeType: this.edgeType,
|
|
162
|
+
context: this.context,
|
|
163
|
+
queryOptions: this.options,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
148
166
|
async loadEdgeForID2(id, id2) {
|
|
149
167
|
return (0, ent_1.loadEdgeForID2)({
|
|
150
168
|
id1: id,
|
|
@@ -67,7 +67,7 @@ async function loadRowsForClauseLoader(options, clause) {
|
|
|
67
67
|
const rowOptions = {
|
|
68
68
|
...options,
|
|
69
69
|
// @ts-expect-error clause in LoadRowOptions doesn't take templatized version of Clause
|
|
70
|
-
clause: (0, clause_1.getCombinedClause)(options, clause),
|
|
70
|
+
clause: (0, clause_1.getCombinedClause)(options, clause, true),
|
|
71
71
|
};
|
|
72
72
|
return (await (0, ent_1.loadRows)(rowOptions));
|
|
73
73
|
}
|
|
@@ -75,7 +75,7 @@ async function loadCountForClauseLoader(options, clause) {
|
|
|
75
75
|
const rowOptions = {
|
|
76
76
|
...options,
|
|
77
77
|
// @ts-expect-error clause in LoadRowOptions doesn't take templatized version of Clause
|
|
78
|
-
clause: (0, clause_1.getCombinedClause)(options, clause),
|
|
78
|
+
clause: (0, clause_1.getCombinedClause)(options, clause, true),
|
|
79
79
|
};
|
|
80
80
|
const row = await (0, ent_1.loadRow)({
|
|
81
81
|
...rowOptions,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { Context,
|
|
1
|
+
import { Context, EdgeQueryableDataOptions, Loader, LoaderFactory, Data } from "../base";
|
|
2
2
|
import * as clause from "../clause";
|
|
3
3
|
import { ObjectLoaderFactory } from "./object_loader";
|
|
4
4
|
import { OrderBy } from "../query_impl";
|
|
5
5
|
declare class QueryDirectLoader<K extends any> implements Loader<K, Data[]> {
|
|
6
6
|
private options;
|
|
7
7
|
private queryOptions?;
|
|
8
|
-
context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined;
|
|
8
|
+
context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, import("../base").ID | null>> | undefined;
|
|
9
9
|
private memoizedInitPrime;
|
|
10
10
|
private primedLoaders;
|
|
11
|
-
constructor(options: QueryOptions, queryOptions?: Partial<Pick<import("../base").QueryableDataOptions, "clause" | "limit" | "orderby" | "disableTransformations">> | undefined, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined);
|
|
11
|
+
constructor(options: QueryOptions, queryOptions?: Partial<Pick<import("../base").QueryableDataOptions, "clause" | "limit" | "orderby" | "disableTransformations">> | undefined, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, import("../base").ID | null>> | undefined);
|
|
12
12
|
private initPrime;
|
|
13
13
|
load(id: K): Promise<Data[]>;
|
|
14
14
|
clearAll(): void;
|
|
@@ -57,9 +57,10 @@ async function simpleCase(options, id, queryOptions) {
|
|
|
57
57
|
throw new Error(`need options.groupCol or options.clause`);
|
|
58
58
|
}
|
|
59
59
|
if (queryOptions?.clause) {
|
|
60
|
+
// TODO does this one need a getCombinedClause check??
|
|
60
61
|
cls = clause.And(cls, queryOptions.clause);
|
|
61
62
|
}
|
|
62
|
-
return
|
|
63
|
+
return (0, ent_1.loadRows)({
|
|
63
64
|
...options,
|
|
64
65
|
clause: cls,
|
|
65
66
|
orderby: getOrderByLocal(options, queryOptions),
|
|
@@ -11,6 +11,7 @@ export declare abstract class AssocEdgeQueryBase<TSource extends Ent<TViewer>, T
|
|
|
11
11
|
private countLoaderFactory;
|
|
12
12
|
private dataLoaderFactory;
|
|
13
13
|
private options;
|
|
14
|
+
private loadTwoWay;
|
|
14
15
|
constructor(viewer: TViewer, src: EdgeQuerySource<TSource, TDest, TViewer>, countLoaderFactory: AssocEdgeCountLoaderFactory, dataLoaderFactory: AssocEdgeLoaderFactory<TEdge>, options: LoadEntOptions<TDest, TViewer> | loaderOptionsFunc<TViewer>);
|
|
15
16
|
private isEdgeQuery;
|
|
16
17
|
abstract sourceEnt(id: ID): Promise<Ent | null>;
|
|
@@ -27,6 +28,27 @@ export declare abstract class AssocEdgeQueryBase<TSource extends Ent<TViewer>, T
|
|
|
27
28
|
__beforeBETA(time: Date): this;
|
|
28
29
|
__afterBETA(time: Date): this;
|
|
29
30
|
__withinBeta(start: Date, end: Date): this;
|
|
31
|
+
/**
|
|
32
|
+
* intersect multiple queries together with this one to get candidate edges
|
|
33
|
+
* the edges returned will always be from the originating edge query
|
|
34
|
+
*
|
|
35
|
+
* @param others list of other queries to intersect with the source edge
|
|
36
|
+
*/
|
|
37
|
+
__intersect(...others: AssocEdgeQueryBase<any, TDest, TEdge, TViewer>[]): this;
|
|
38
|
+
/**
|
|
39
|
+
* union multiple queries together with this one to get candidate edges
|
|
40
|
+
* if the edge exists in the source query, that's the edge returned
|
|
41
|
+
* if the edge doesn't exist, the first edge in the list of queries that has the edge is returned
|
|
42
|
+
* @param others list of other queries to union with the source edge
|
|
43
|
+
*/
|
|
44
|
+
__union<TSource2 extends Ent<TViewer>>(...others: AssocEdgeQueryBase<TSource2, TDest, TEdge, TViewer>[]): this;
|
|
45
|
+
/**
|
|
46
|
+
* this fetches edges where there's a two way connection between both sets of edges
|
|
47
|
+
* e.g. in a social networking system, where the source and dest are both following each other
|
|
48
|
+
*
|
|
49
|
+
* will not work in the future when there's sharding...
|
|
50
|
+
*/
|
|
51
|
+
__twoWay(): this;
|
|
30
52
|
}
|
|
31
53
|
export interface EdgeQueryCtr<TSource extends Ent, TDest extends Ent, TEdge extends AssocEdge> {
|
|
32
54
|
new (viewer: Viewer, src: EdgeQuerySource<TSource>): EdgeQuery<TSource, TDest, TEdge>;
|
|
@@ -39,6 +39,7 @@ class AssocEdgeQueryBase extends query_1.BaseEdgeQuery {
|
|
|
39
39
|
this.countLoaderFactory = countLoaderFactory;
|
|
40
40
|
this.dataLoaderFactory = dataLoaderFactory;
|
|
41
41
|
this.options = options;
|
|
42
|
+
this.loadTwoWay = false;
|
|
42
43
|
}
|
|
43
44
|
isEdgeQuery(obj) {
|
|
44
45
|
if (obj.queryIDs !== undefined) {
|
|
@@ -147,7 +148,9 @@ class AssocEdgeQueryBase extends query_1.BaseEdgeQuery {
|
|
|
147
148
|
// doesn't make sense
|
|
148
149
|
// so only makes sense if one of these...
|
|
149
150
|
// Id2 needs to be an option
|
|
150
|
-
const edges =
|
|
151
|
+
const edges = this.loadTwoWay
|
|
152
|
+
? await loader.loadTwoWay(info.id)
|
|
153
|
+
: await loader.load(info.id);
|
|
151
154
|
this.edges.set(info.id, edges);
|
|
152
155
|
}));
|
|
153
156
|
}
|
|
@@ -186,10 +189,47 @@ class AssocEdgeQueryBase extends query_1.BaseEdgeQuery {
|
|
|
186
189
|
}
|
|
187
190
|
// start is inclusive, end is exclusive
|
|
188
191
|
__withinBeta(start, end) {
|
|
189
|
-
this.__assertNoFiltersBETA("
|
|
192
|
+
this.__assertNoFiltersBETA("within");
|
|
190
193
|
this.__addCustomFilterBETA(new WithinFilter(start, end));
|
|
191
194
|
return this;
|
|
192
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* intersect multiple queries together with this one to get candidate edges
|
|
198
|
+
* the edges returned will always be from the originating edge query
|
|
199
|
+
*
|
|
200
|
+
* @param others list of other queries to intersect with the source edge
|
|
201
|
+
*/
|
|
202
|
+
__intersect(...others) {
|
|
203
|
+
// TODO I don't really see a reason why we can't chain first or something first before this
|
|
204
|
+
// but for now let's not support it
|
|
205
|
+
// when we do this correctly, we'll allow chaining
|
|
206
|
+
this.__assertNoFiltersBETA("intersect");
|
|
207
|
+
this.__addCustomFilterBETA(new IntersectFilter(others));
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* union multiple queries together with this one to get candidate edges
|
|
212
|
+
* if the edge exists in the source query, that's the edge returned
|
|
213
|
+
* if the edge doesn't exist, the first edge in the list of queries that has the edge is returned
|
|
214
|
+
* @param others list of other queries to union with the source edge
|
|
215
|
+
*/
|
|
216
|
+
__union(...others) {
|
|
217
|
+
// same chain comment from intersect...
|
|
218
|
+
this.__assertNoFiltersBETA("union");
|
|
219
|
+
this.__addCustomFilterBETA(new UnionFilter(others));
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* this fetches edges where there's a two way connection between both sets of edges
|
|
224
|
+
* e.g. in a social networking system, where the source and dest are both following each other
|
|
225
|
+
*
|
|
226
|
+
* will not work in the future when there's sharding...
|
|
227
|
+
*/
|
|
228
|
+
__twoWay() {
|
|
229
|
+
this.__assertNoFiltersBETA("twoWay");
|
|
230
|
+
this.loadTwoWay = true;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
193
233
|
}
|
|
194
234
|
exports.AssocEdgeQueryBase = AssocEdgeQueryBase;
|
|
195
235
|
class BeforeFilter {
|
|
@@ -223,3 +263,62 @@ class WithinFilter {
|
|
|
223
263
|
return options;
|
|
224
264
|
}
|
|
225
265
|
}
|
|
266
|
+
class IntersectFilter {
|
|
267
|
+
constructor(queries) {
|
|
268
|
+
this.queries = queries;
|
|
269
|
+
this.edges = [];
|
|
270
|
+
}
|
|
271
|
+
async fetch() {
|
|
272
|
+
let i = 0;
|
|
273
|
+
// maybe future optimization. instead of this, use a SQL query if edge_types are the same
|
|
274
|
+
for await (const query of this.queries) {
|
|
275
|
+
const edges = await query.queryEdges();
|
|
276
|
+
const set = new Set();
|
|
277
|
+
edges.forEach((edge) => {
|
|
278
|
+
set.add(edge.id2);
|
|
279
|
+
});
|
|
280
|
+
this.edges[i] = set;
|
|
281
|
+
i++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
filter(_id, edges) {
|
|
285
|
+
return edges.filter((edge) => {
|
|
286
|
+
return this.edges.every((set) => {
|
|
287
|
+
return set.has(edge.id2);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
class UnionFilter {
|
|
293
|
+
constructor(queries) {
|
|
294
|
+
this.queries = queries;
|
|
295
|
+
this.edges = [];
|
|
296
|
+
}
|
|
297
|
+
async fetch() {
|
|
298
|
+
let i = 0;
|
|
299
|
+
// maybe future optimization. instead of this, use a SQL query if edge_types are the same
|
|
300
|
+
for await (const query of this.queries) {
|
|
301
|
+
const edges = await query.queryEdges();
|
|
302
|
+
this.edges[i] = edges;
|
|
303
|
+
i++;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
filter(_id, edges) {
|
|
307
|
+
const set = new Set();
|
|
308
|
+
const result = [];
|
|
309
|
+
for (const edge of edges) {
|
|
310
|
+
set.add(edge.id2);
|
|
311
|
+
result.push(edge);
|
|
312
|
+
}
|
|
313
|
+
for (const edges of this.edges) {
|
|
314
|
+
for (const edge of edges) {
|
|
315
|
+
if (set.has(edge.id2)) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
result.push(edge);
|
|
319
|
+
set.add(edge.id2);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -7,17 +7,10 @@ const loaders_1 = require("../loaders");
|
|
|
7
7
|
const query_1 = require("./query");
|
|
8
8
|
function getClause(opts) {
|
|
9
9
|
let cls = opts.clause;
|
|
10
|
-
if (opts.disableTransformations) {
|
|
10
|
+
if (opts.disableTransformations || !cls) {
|
|
11
11
|
return cls;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
if (typeof optClause === "function") {
|
|
15
|
-
optClause = optClause();
|
|
16
|
-
}
|
|
17
|
-
if (!optClause) {
|
|
18
|
-
return cls;
|
|
19
|
-
}
|
|
20
|
-
return (0, clause_1.AndOptional)(cls, optClause);
|
|
13
|
+
return (0, clause_1.getCombinedClause)(opts.loadEntOptions.loaderFactory?.options, cls, true);
|
|
21
14
|
}
|
|
22
15
|
function getRawCountLoader(viewer, opts) {
|
|
23
16
|
if (!viewer.context?.cache) {
|
package/core/query/query.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface EdgeQuery<TSource extends Ent, TDest extends Ent, TEdge extends
|
|
|
20
20
|
sourceEnt(id: ID): Promise<Ent | null>;
|
|
21
21
|
}
|
|
22
22
|
export interface EdgeQueryFilter<T extends Data> {
|
|
23
|
+
fetch?(): Promise<void>;
|
|
23
24
|
filter?(id: ID, edges: T[]): T[];
|
|
24
25
|
query?(options: EdgeQueryableDataOptions): EdgeQueryableDataOptions | Promise<EdgeQueryableDataOptions>;
|
|
25
26
|
paginationInfo?(id: ID): PaginationInfo | undefined;
|
package/core/query/query.js
CHANGED
|
@@ -65,6 +65,7 @@ class FirstFilter {
|
|
|
65
65
|
constructor(options) {
|
|
66
66
|
this.options = options;
|
|
67
67
|
this.pageMap = new Map();
|
|
68
|
+
this.usedQuery = false;
|
|
68
69
|
assertPositive(options.limit);
|
|
69
70
|
this.sortCol = options.sortCol;
|
|
70
71
|
if (options.after) {
|
|
@@ -72,18 +73,58 @@ class FirstFilter {
|
|
|
72
73
|
}
|
|
73
74
|
this.edgeQuery = options.query;
|
|
74
75
|
}
|
|
76
|
+
setPageMap(ret, id, hasNextPage) {
|
|
77
|
+
this.pageMap.set(id, {
|
|
78
|
+
hasNextPage,
|
|
79
|
+
// hasPreviousPage always false even if there's a previous page because
|
|
80
|
+
// we shouldn't be querying in both directions at the same
|
|
81
|
+
hasPreviousPage: false,
|
|
82
|
+
startCursor: this.edgeQuery.getCursor(ret[0]),
|
|
83
|
+
endCursor: this.edgeQuery.getCursor(ret[ret.length - 1]),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
75
86
|
filter(id, edges) {
|
|
76
87
|
if (edges.length > this.options.limit) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
// we need a way to know where the cursor is and if we used it and if not need to do this in TypeScript and not in SQL
|
|
89
|
+
// so can't filter from 0 but specific item
|
|
90
|
+
// if we used the query or we're querying the first N, slice from 0
|
|
91
|
+
if (this.usedQuery || !this.offset) {
|
|
92
|
+
const ret = edges.slice(0, this.options.limit);
|
|
93
|
+
this.setPageMap(ret, id, true);
|
|
94
|
+
return ret;
|
|
95
|
+
}
|
|
96
|
+
else if (this.offset) {
|
|
97
|
+
const ret = [];
|
|
98
|
+
let found = false;
|
|
99
|
+
let i = 0;
|
|
100
|
+
let hasNextPage = false;
|
|
101
|
+
for (const edge of edges) {
|
|
102
|
+
const id = edge[this.options.cursorCol];
|
|
103
|
+
if (id === this.offset) {
|
|
104
|
+
// found!
|
|
105
|
+
found = true;
|
|
106
|
+
hasNextPage = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (found) {
|
|
110
|
+
ret.push(edge);
|
|
111
|
+
if (ret.length === this.options.limit) {
|
|
112
|
+
if (i === ret.length - 1) {
|
|
113
|
+
hasNextPage = false;
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
i++;
|
|
119
|
+
}
|
|
120
|
+
if (hasNextPage && ret.length < this.options.limit) {
|
|
121
|
+
hasNextPage = false;
|
|
122
|
+
}
|
|
123
|
+
if (ret.length) {
|
|
124
|
+
this.setPageMap(ret, id, hasNextPage);
|
|
125
|
+
}
|
|
126
|
+
return ret;
|
|
127
|
+
}
|
|
87
128
|
}
|
|
88
129
|
// TODO: in the future, when we have caching for edges
|
|
89
130
|
// we'll want to hit that cache instead of passing the limit down to the
|
|
@@ -93,6 +134,7 @@ class FirstFilter {
|
|
|
93
134
|
return edges;
|
|
94
135
|
}
|
|
95
136
|
async query(options) {
|
|
137
|
+
this.usedQuery = true;
|
|
96
138
|
// we fetch an extra one to see if we're at the end
|
|
97
139
|
const limit = this.options.limit + 1;
|
|
98
140
|
options.limit = limit;
|
|
@@ -212,7 +254,7 @@ class BaseEdgeQuery {
|
|
|
212
254
|
this.limitAdded = false;
|
|
213
255
|
// this is basically just raw rows
|
|
214
256
|
this.queryEdges = async () => {
|
|
215
|
-
return
|
|
257
|
+
return this.querySingleEdge("queryEdges");
|
|
216
258
|
};
|
|
217
259
|
this.queryAllEdges = async () => {
|
|
218
260
|
return this.memoizedloadEdges();
|
|
@@ -402,15 +444,34 @@ class BaseEdgeQuery {
|
|
|
402
444
|
// may need to bring sql mode or something back
|
|
403
445
|
for (const filter of this.filters) {
|
|
404
446
|
if (filter.query) {
|
|
447
|
+
if (filter.fetch) {
|
|
448
|
+
throw new Error(`a filter that augments the query cannot currently fetch`);
|
|
449
|
+
}
|
|
405
450
|
let res = filter.query(options);
|
|
406
451
|
options = (0, types_1.isPromise)(res) ? await res : res;
|
|
407
452
|
}
|
|
453
|
+
else {
|
|
454
|
+
// if we've seen a filter that doesn't have a query, we can't do anything in SQL
|
|
455
|
+
// TODO figure out filter interactions https://github.com/lolopinto/ent/issues/685
|
|
456
|
+
// this is a scenario where if we have the first N filters that can modify the query,
|
|
457
|
+
// we do that in SQL and then we do the rest in code
|
|
458
|
+
// but once we have something doing it in code (e.g. intersect()), we can't do anything else in SQL
|
|
459
|
+
// and then have to differentiate between filters that augment (limit or add to) the items returned
|
|
460
|
+
// and those that reorder...
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
408
463
|
}
|
|
409
464
|
await this.loadRawData(idsInfo, options);
|
|
410
465
|
// no filters. nothing to do here.
|
|
411
466
|
if (!this.filters.length) {
|
|
412
467
|
return this.edges;
|
|
413
468
|
}
|
|
469
|
+
// fetch anything we need to filter the query
|
|
470
|
+
for await (const filter of this.filters) {
|
|
471
|
+
if (filter.fetch) {
|
|
472
|
+
await filter.fetch();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
414
475
|
// filter as needed
|
|
415
476
|
for (let [id, edges] of this.edges) {
|
|
416
477
|
this.filters.forEach((filter) => {
|