jinaga-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +22 -0
  2. package/dist/authentication/authentication-device.d.ts +20 -0
  3. package/dist/authentication/authentication-device.js +61 -0
  4. package/dist/authentication/authentication-device.js.map +1 -0
  5. package/dist/authentication/authentication-impl.d.ts +21 -0
  6. package/dist/authentication/authentication-impl.js +61 -0
  7. package/dist/authentication/authentication-impl.js.map +1 -0
  8. package/dist/authentication/authentication-session.d.ts +23 -0
  9. package/dist/authentication/authentication-session.js +85 -0
  10. package/dist/authentication/authentication-session.js.map +1 -0
  11. package/dist/authentication/authentication.d.ts +5 -0
  12. package/dist/authentication/authentication.js +3 -0
  13. package/dist/authentication/authentication.js.map +1 -0
  14. package/dist/authentication.d.ts +17 -0
  15. package/dist/authentication.js +52 -0
  16. package/dist/authentication.js.map +1 -0
  17. package/dist/authorization/authorization-keystore.d.ts +13 -0
  18. package/dist/authorization/authorization-keystore.js +61 -0
  19. package/dist/authorization/authorization-keystore.js.map +1 -0
  20. package/dist/http/node-http.d.ts +7 -0
  21. package/dist/http/node-http.js +126 -0
  22. package/dist/http/node-http.js.map +1 -0
  23. package/dist/http/router.d.ts +20 -0
  24. package/dist/http/router.js +223 -0
  25. package/dist/http/router.js.map +1 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +6 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/jinaga-server.d.ts +18 -0
  30. package/dist/jinaga-server.js +101 -0
  31. package/dist/jinaga-server.js.map +1 -0
  32. package/dist/keystore.d.ts +9 -0
  33. package/dist/keystore.js +3 -0
  34. package/dist/keystore.js.map +1 -0
  35. package/dist/memory/memory-keystore.d.ts +16 -0
  36. package/dist/memory/memory-keystore.js +86 -0
  37. package/dist/memory/memory-keystore.js.map +1 -0
  38. package/dist/postgres/connection.d.ts +12 -0
  39. package/dist/postgres/connection.js +83 -0
  40. package/dist/postgres/connection.js.map +1 -0
  41. package/dist/postgres/edge-record.d.ts +9 -0
  42. package/dist/postgres/edge-record.js +30 -0
  43. package/dist/postgres/edge-record.js.map +1 -0
  44. package/dist/postgres/maps.d.ts +21 -0
  45. package/dist/postgres/maps.js +86 -0
  46. package/dist/postgres/maps.js.map +1 -0
  47. package/dist/postgres/postgres-keystore.d.ts +20 -0
  48. package/dist/postgres/postgres-keystore.js +168 -0
  49. package/dist/postgres/postgres-keystore.js.map +1 -0
  50. package/dist/postgres/postgres-store.d.ts +20 -0
  51. package/dist/postgres/postgres-store.js +717 -0
  52. package/dist/postgres/postgres-store.js.map +1 -0
  53. package/dist/postgres/query-description-builder.d.ts +13 -0
  54. package/dist/postgres/query-description-builder.js +104 -0
  55. package/dist/postgres/query-description-builder.js.map +1 -0
  56. package/dist/postgres/query-description.d.ts +71 -0
  57. package/dist/postgres/query-description.js +256 -0
  58. package/dist/postgres/query-description.js.map +1 -0
  59. package/dist/postgres/specification-result-sql.d.ts +41 -0
  60. package/dist/postgres/specification-result-sql.js +260 -0
  61. package/dist/postgres/specification-result-sql.js.map +1 -0
  62. package/dist/postgres/specification-sql.d.ts +3 -0
  63. package/dist/postgres/specification-sql.js +119 -0
  64. package/dist/postgres/specification-sql.js.map +1 -0
  65. package/dist/postgres/sql.d.ts +10 -0
  66. package/dist/postgres/sql.js +396 -0
  67. package/dist/postgres/sql.js.map +1 -0
  68. package/dist/util/fn.d.ts +6 -0
  69. package/dist/util/fn.js +81 -0
  70. package/dist/util/fn.js.map +1 -0
  71. package/dist/util/promise.d.ts +1 -0
  72. package/dist/util/promise.js +10 -0
  73. package/dist/util/promise.js.map +1 -0
  74. package/docs/registrations.sql +22 -0
  75. package/docs/specification.txt +41 -0
  76. package/docs/sql-generator.gv +25 -0
  77. package/integration-test/docker-compose.yaml +13 -0
  78. package/integration-test/jinaga-test/Dockerfile +12 -0
  79. package/integration-test/jinaga-test/jinaga.test.js +425 -0
  80. package/integration-test/jinaga-test/package-lock.json +6767 -0
  81. package/integration-test/jinaga-test/package.json +25 -0
  82. package/integration-test/postgres/0-init-app-db.sh +16 -0
  83. package/integration-test/postgres/1-setup.sql +219 -0
  84. package/integration-test/postgres/2-grant.sh +17 -0
  85. package/integration-test/postgres/Dockerfile +5 -0
  86. package/integration-test/run.sh +17 -0
  87. package/integration-test/start_db.sh +2 -0
  88. package/integration-test/stop_db.sh +3 -0
  89. package/integration-test/test.sh +8 -0
  90. package/interactive/index.js +56 -0
  91. package/jest.config.js +12 -0
  92. package/package.json +51 -0
  93. package/scripts/combined.sql +46 -0
  94. package/scripts/cte.sql +55 -0
  95. package/scripts/separate.sql +27 -0
  96. package/setup.sh +34 -0
  97. package/setup.sql +329 -0
  98. package/src/authentication/authentication-device.ts +68 -0
  99. package/src/authentication/authentication-impl.ts +59 -0
  100. package/src/authentication/authentication-session.ts +95 -0
  101. package/src/authentication/authentication.ts +6 -0
  102. package/src/authentication.ts +45 -0
  103. package/src/authorization/authorization-keystore.ts +67 -0
  104. package/src/http/node-http.ts +106 -0
  105. package/src/http/router.ts +237 -0
  106. package/src/index.ts +1 -0
  107. package/src/jinaga-server.ts +125 -0
  108. package/src/keystore.ts +10 -0
  109. package/src/memory/memory-keystore.ts +93 -0
  110. package/src/postgres/connection.ts +73 -0
  111. package/src/postgres/edge-record.ts +36 -0
  112. package/src/postgres/maps.ts +89 -0
  113. package/src/postgres/postgres-keystore.ts +167 -0
  114. package/src/postgres/postgres-store.ts +874 -0
  115. package/src/postgres/query-description-builder.ts +116 -0
  116. package/src/postgres/query-description.ts +385 -0
  117. package/src/postgres/specification-result-sql.ts +362 -0
  118. package/src/postgres/specification-sql.ts +138 -0
  119. package/src/postgres/sql.ts +482 -0
  120. package/src/util/fn.ts +63 -0
  121. package/src/util/promise.ts +5 -0
  122. package/test/authorization/authorizationSpec.ts +209 -0
  123. package/test/postgres/postgresSpec.ts +385 -0
  124. package/test/postgres/postgresSpecificationSpec.ts +742 -0
  125. package/test/reporter.js +25 -0
  126. package/tsconfig.json +25 -0
  127. package/upgrade.sh +21 -0
@@ -0,0 +1,482 @@
1
+ import { Direction, ExistentialCondition, FactReference, Join, PropertyCondition, Quantifier, Step } from "jinaga";
2
+
3
+ import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
4
+
5
+ export type SqlQuery = {
6
+ empty: boolean,
7
+ sql: string,
8
+ parameters: any[],
9
+ pathLength: number,
10
+ factTypeNames: string[]
11
+ };
12
+
13
+ export function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap) : SqlQuery {
14
+ const builder = new QueryBuilder(factTypes, roleMap);
15
+
16
+ return builder.buildQuery(start, steps);
17
+ }
18
+
19
+ interface QueryJoinEdge {
20
+ table: 'edge';
21
+ direction: 'predecessor' | 'successor';
22
+ edgeAlias: number;
23
+ roleParameter: number;
24
+ }
25
+
26
+ interface QueryJoinFact {
27
+ table: 'fact';
28
+ factAlias: number;
29
+ }
30
+
31
+ type QueryJoin = QueryJoinEdge | QueryJoinFact;
32
+
33
+ interface QueryParts {
34
+ joins: QueryJoin[];
35
+ existentialClauses: ExistentialClause[];
36
+ };
37
+
38
+ interface ExistentialClause {
39
+ quantifier: Quantifier;
40
+ priorEdgeJoin: QueryJoinEdge;
41
+ query: QueryParts;
42
+ }
43
+
44
+ interface QueryBuilderStateStart {
45
+ state: 'start';
46
+ typeId: number;
47
+ typeName: string;
48
+ }
49
+ interface QueryBuilderStatePredecessorType {
50
+ state: 'predecessor-type';
51
+ typeId: number;
52
+ typeName: string;
53
+ }
54
+ interface QueryBuilderStateSuccessorType {
55
+ state: 'successor-type';
56
+ typeId: number;
57
+ typeName: string;
58
+ }
59
+ interface QueryBuilderStatePredecessorJoin {
60
+ state: 'predecessor-join';
61
+ position: string;
62
+ }
63
+ interface QueryBuilderStateSuccessorJoin {
64
+ state: 'successor-join';
65
+ role: string;
66
+ }
67
+ interface QueryBuilderStateEmpty {
68
+ state: 'empty';
69
+ }
70
+
71
+ type QueryBuilderState =
72
+ QueryBuilderStateStart |
73
+ QueryBuilderStatePredecessorType |
74
+ QueryBuilderStateSuccessorType |
75
+ QueryBuilderStatePredecessorJoin |
76
+ QueryBuilderStateSuccessorJoin |
77
+ QueryBuilderStateEmpty;
78
+
79
+ class QueryBuilder {
80
+ private nextEdge: number = 1;
81
+ private nextFact: number = 2;
82
+ private shouldEmitFacts: boolean = true;
83
+ private queryParts: QueryParts = {
84
+ joins: [],
85
+ existentialClauses: []
86
+ };
87
+ private roleIds: number[] = [];
88
+ private factTypeNames: string[] = [];
89
+
90
+ constructor(private factTypes: FactTypeMap, private roleMap: RoleMap) {
91
+ }
92
+
93
+ buildQuery(start: FactReference, steps: Step[]): SqlQuery {
94
+ if (steps.length === 0) {
95
+ return null;
96
+ }
97
+
98
+ const startTypeId = getFactTypeId(this.factTypes, start.type);
99
+ const startState: QueryBuilderState = {
100
+ state: 'start',
101
+ typeId: startTypeId,
102
+ typeName: start.type
103
+ };
104
+ const finalState = steps.reduce((state, step) => {
105
+ return this.matchStep(state, step);
106
+ }, startState);
107
+ this.end(finalState);
108
+
109
+ if (finalState.state === 'empty') {
110
+ return {
111
+ empty: true,
112
+ sql: '',
113
+ parameters: [],
114
+ pathLength: 0,
115
+ factTypeNames: []
116
+ };
117
+ }
118
+
119
+ const factAliases = this.queryParts.joins
120
+ .filter(j => j.table === 'fact')
121
+ .map(j => (j as QueryJoinFact).factAlias);
122
+ const hashes = factAliases.map(a => `f${a}.hash as hash${a}`).join(', ');
123
+ const joins = this.buildJoins(this.queryParts.joins, 'f1.fact_id');
124
+ const whereClause = this.buildWhereClause(this.queryParts.existentialClauses);
125
+ const sql = `SELECT ${hashes} FROM public.fact f1${joins} WHERE f1.fact_type_id = $1 AND f1.hash = $2${whereClause}`;
126
+
127
+ return {
128
+ empty: false,
129
+ sql,
130
+ parameters: [startTypeId, start.hash, ...this.roleIds],
131
+ pathLength: factAliases.length,
132
+ factTypeNames: this.factTypeNames
133
+ };
134
+ }
135
+
136
+ private buildJoins(joins: QueryJoin[], priorFactId: string) {
137
+ const clauses = joins.reduce((joins, join) => {
138
+ if (join.table === 'edge') {
139
+ if (join.direction === 'successor') {
140
+ const clause = ` JOIN public.edge e${join.edgeAlias} ` +
141
+ `ON e${join.edgeAlias}.predecessor_fact_id = ${joins.priorFactId} ` +
142
+ `AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
143
+ return {
144
+ priorFactId: `e${join.edgeAlias}.successor_fact_id`,
145
+ clauses: [...joins.clauses, clause]
146
+ };
147
+ }
148
+ else {
149
+ const clause = ` JOIN public.edge e${join.edgeAlias} ` +
150
+ `ON e${join.edgeAlias}.successor_fact_id = ${joins.priorFactId} ` +
151
+ `AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
152
+ return {
153
+ priorFactId: `e${join.edgeAlias}.predecessor_fact_id`,
154
+ clauses: [...joins.clauses, clause]
155
+ };
156
+ }
157
+ } else {
158
+ const clause = ` JOIN public.fact f${join.factAlias} ON f${join.factAlias}.fact_id = ${joins.priorFactId}`;
159
+ return {
160
+ priorFactId: joins.priorFactId,
161
+ clauses: [...joins.clauses, clause]
162
+ };
163
+ }
164
+ }, {
165
+ priorFactId,
166
+ clauses: []
167
+ }).clauses;
168
+ return clauses.join('');
169
+ }
170
+
171
+ buildWhereClause(existentialClauses: ExistentialClause[]): string {
172
+ if (existentialClauses.length === 0) {
173
+ return '';
174
+ }
175
+
176
+ const clauses = existentialClauses.map(clause => {
177
+ const quantifierSql = clause.quantifier === Quantifier.Exists ? 'EXISTS' : 'NOT EXISTS';
178
+ const [first, ...rest] : QueryJoinEdge[] = clause.query.joins as QueryJoinEdge[];
179
+ const firstHeadFactId = first.direction === 'predecessor'
180
+ ? `e${first.edgeAlias}.predecessor_fact_id`
181
+ : `e${first.edgeAlias}.successor_fact_id`;
182
+ const firstTailFactId = first.direction === 'predecessor'
183
+ ? `e${first.edgeAlias}.successor_fact_id`
184
+ : `e${first.edgeAlias}.predecessor_fact_id`;
185
+ const joins = this.buildJoins(rest, firstHeadFactId);
186
+ const priorFactId = clause.priorEdgeJoin.direction === 'predecessor'
187
+ ? `e${clause.priorEdgeJoin.edgeAlias}.predecessor_fact_id`
188
+ : `e${clause.priorEdgeJoin.edgeAlias}.successor_fact_id`;
189
+ const whereClause = this.buildWhereClause(clause.query.existentialClauses);
190
+ return ` AND ${quantifierSql} (SELECT 1 FROM public.edge e${first.edgeAlias}${joins} ` +
191
+ `WHERE ${firstTailFactId} = ${priorFactId} ` +
192
+ `AND e${first.edgeAlias}.role_id = $${first.roleParameter}${whereClause})`;
193
+ });
194
+ return clauses.join('');
195
+ }
196
+
197
+ buildNestedSql(typeId: number, typeName: string, steps: Step[]): QueryParts | null {
198
+ // Push a new query to the stack.
199
+ const parentQuery = this.queryParts;
200
+ const parentShouldEmitFact = this.shouldEmitFacts;
201
+ this.shouldEmitFacts = false;
202
+ this.queryParts = {
203
+ joins: [],
204
+ existentialClauses: []
205
+ };
206
+
207
+ const startState: QueryBuilderState = {
208
+ state: 'predecessor-type',
209
+ typeId: typeId,
210
+ typeName: typeName
211
+ };
212
+ const finalState = steps.reduce((state, step) => {
213
+ return this.matchStep(state, step);
214
+ }, startState);
215
+ this.end(finalState);
216
+
217
+ // Pop the stack and return the nested query.
218
+ const nestedQuery = this.queryParts;
219
+ this.shouldEmitFacts = parentShouldEmitFact;
220
+ this.queryParts = parentQuery;
221
+
222
+ // If the nested query cannot match any results, return null.
223
+ if (finalState.state === 'empty') {
224
+ return null;
225
+ }
226
+ return nestedQuery;
227
+ }
228
+
229
+ private matchStep(state: QueryBuilderState, step: Step): QueryBuilderState {
230
+ switch (state.state) {
231
+ case 'start':
232
+ return this.matchStepStart(state, step);
233
+ case 'predecessor-type':
234
+ return this.matchStepPredecessorType(state, step);
235
+ case 'predecessor-join':
236
+ return this.matchStepPredecessorJoin(state, step);
237
+ case 'successor-join':
238
+ return this.matchStepSuccessorJoin(state, step);
239
+ case 'successor-type':
240
+ return this.matchStepSuccessorType(state, step);
241
+ case 'empty':
242
+ return state;
243
+ default:
244
+ throw new Error(`Unknown state: ${(state as any).state}`);
245
+ }
246
+ }
247
+
248
+ private matchStepStart(state: QueryBuilderStateStart, step: Step): QueryBuilderState {
249
+ if (step instanceof PropertyCondition) {
250
+ if (step.name !== 'type') {
251
+ throw new Error(`Property condition on non-type property ${step.name}`);
252
+ }
253
+ const typeId = getFactTypeId(this.factTypes, step.value);
254
+ if (typeId !== state.typeId) {
255
+ throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
256
+ }
257
+ return state;
258
+ }
259
+ else if (step instanceof Join) {
260
+ if (step.direction === Direction.Predecessor) {
261
+ const roleId = getRoleId(this.roleMap, state.typeId, step.role);
262
+ if (!roleId) {
263
+ return {
264
+ state: 'empty'
265
+ };
266
+ }
267
+ this.emitEdge('predecessor', roleId);
268
+ return {
269
+ state: 'predecessor-join',
270
+ position: `${state.typeName}.${step.role}`
271
+ }
272
+ }
273
+ if (step.direction === Direction.Successor) {
274
+ return {
275
+ state: 'successor-join',
276
+ role: step.role
277
+ }
278
+ }
279
+ }
280
+ else if (step instanceof ExistentialCondition) {
281
+ return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
282
+ }
283
+ else {
284
+ throw new Error(`Unknown step ${step.constructor.name}`);
285
+ }
286
+ }
287
+
288
+ private matchStepPredecessorType(state: QueryBuilderStatePredecessorType, step: Step): QueryBuilderState {
289
+ if (step instanceof PropertyCondition) {
290
+ if (step.name !== 'type') {
291
+ throw new Error(`Property condition on non-type property ${step.name}`);
292
+ }
293
+ const typeId = getFactTypeId(this.factTypes, step.value);
294
+ if (typeId !== state.typeId) {
295
+ throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
296
+ }
297
+ return state;
298
+ }
299
+ else if (step instanceof Join) {
300
+ if (step.direction === Direction.Predecessor) {
301
+ const roleId = getRoleId(this.roleMap, state.typeId, step.role);
302
+ if (!roleId) {
303
+ return {
304
+ state: 'empty'
305
+ };
306
+ }
307
+ this.emitFact(state.typeName);
308
+ this.emitEdge('predecessor', roleId);
309
+ return {
310
+ state: 'predecessor-join',
311
+ position: `${state.typeName}.${step.role}`
312
+ }
313
+ }
314
+ if (step.direction === Direction.Successor) {
315
+ this.emitFact(state.typeName);
316
+ return {
317
+ state: 'successor-join',
318
+ role: step.role
319
+ }
320
+ }
321
+ }
322
+ else if (step instanceof ExistentialCondition) {
323
+ return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
324
+ }
325
+ else {
326
+ throw new Error(`Unknown step ${step.constructor.name}`);
327
+ }
328
+ }
329
+
330
+ private matchStepPredecessorJoin(state: QueryBuilderStatePredecessorJoin, step: Step): QueryBuilderState {
331
+ if (step instanceof PropertyCondition) {
332
+ if (step.name !== 'type') {
333
+ throw new Error(`Property condition on non-type property ${step.name}`);
334
+ }
335
+ const typeId = getFactTypeId(this.factTypes, step.value);
336
+ if (!typeId) {
337
+ return {
338
+ state: 'empty'
339
+ };
340
+ }
341
+ return {
342
+ state: 'predecessor-type',
343
+ typeId,
344
+ typeName: step.value
345
+ };
346
+ }
347
+ else {
348
+ throw new Error(`Missing type of "${state.position}"`);
349
+ }
350
+ }
351
+
352
+ private matchStepSuccessorJoin(state: QueryBuilderStateSuccessorJoin, step: Step): QueryBuilderState {
353
+ if (step instanceof PropertyCondition) {
354
+ if (step.name !== 'type') {
355
+ throw new Error(`Property condition on non-type property ${step.name}`);
356
+ }
357
+ const typeId = getFactTypeId(this.factTypes, step.value);
358
+ if (!typeId) {
359
+ return {
360
+ state: 'empty'
361
+ };
362
+ }
363
+ const roleId = getRoleId(this.roleMap, typeId, state.role);
364
+ if (!roleId) {
365
+ return {
366
+ state: 'empty'
367
+ };
368
+ }
369
+ this.emitEdge('successor', roleId);
370
+ return {
371
+ state: 'successor-type',
372
+ typeId: typeId,
373
+ typeName: step.value
374
+ };
375
+ }
376
+ else {
377
+ throw new Error(`Missing type for role ${state.role}`);
378
+ }
379
+ }
380
+
381
+ private matchStepSuccessorType(state: QueryBuilderStateSuccessorType, step: Step): QueryBuilderState {
382
+ if (step instanceof PropertyCondition) {
383
+ if (step.name !== 'type') {
384
+ throw new Error(`Property condition on non-type property ${step.name}`);
385
+ }
386
+ const typeId = getFactTypeId(this.factTypes, step.value);
387
+ if (typeId !== state.typeId) {
388
+ throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
389
+ }
390
+ return state;
391
+ }
392
+ else if (step instanceof Join) {
393
+ if (step.direction === Direction.Predecessor) {
394
+ const roleId = getRoleId(this.roleMap, state.typeId, step.role);
395
+ if (!roleId) {
396
+ return {
397
+ state: 'empty'
398
+ };
399
+ }
400
+ this.emitFact(state.typeName);
401
+ this.emitEdge('predecessor', roleId);
402
+ return {
403
+ state: 'predecessor-join',
404
+ position: `${state.typeName}.${step.role}`
405
+ };
406
+ }
407
+ else {
408
+ this.emitFact(state.typeName);
409
+ return {
410
+ state: 'successor-join',
411
+ role: step.role
412
+ };
413
+ }
414
+ }
415
+ else if (step instanceof ExistentialCondition) {
416
+ return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
417
+ }
418
+ else {
419
+ throw new Error(`Unknown step ${step.constructor.name}`);
420
+ }
421
+ }
422
+
423
+ private end(finalState: QueryBuilderState) {
424
+ if (finalState.state === 'successor-join') {
425
+ throw new Error(`Missing type for role ${finalState.role}`);
426
+ }
427
+ else if (finalState.state === 'predecessor-join') {
428
+ throw new Error(`Missing type of "${finalState.position}"`);
429
+ }
430
+ else if (finalState.state === 'predecessor-type' || finalState.state === 'successor-type') {
431
+ this.emitFact(finalState.typeName);
432
+ }
433
+ }
434
+
435
+ private emitEdge(direction: 'predecessor' | 'successor', roleId: number) {
436
+ const edgeAlias = this.nextEdge++;
437
+ this.roleIds.push(roleId);
438
+ this.queryParts.joins.push({
439
+ table: 'edge',
440
+ direction: direction,
441
+ edgeAlias: edgeAlias,
442
+ roleParameter: edgeAlias + 2
443
+ });
444
+ }
445
+
446
+ private emitFact(factTypeName: string) {
447
+ if (this.shouldEmitFacts) {
448
+ const factAlias = this.nextFact++;
449
+ this.queryParts.joins.push({
450
+ table: 'fact',
451
+ factAlias: factAlias
452
+ });
453
+ this.factTypeNames.push(factTypeName);
454
+ }
455
+ }
456
+
457
+ private pushExistentialCondition(typeId: number, typeName: string, state: QueryBuilderState, step: ExistentialCondition): QueryBuilderState {
458
+ const nested = this.buildNestedSql(typeId, typeName, step.steps);
459
+ if (!nested) {
460
+ if (step.quantifier === Quantifier.Exists) {
461
+ // An empty positive existential condition cannot match any facts.
462
+ return {
463
+ state: 'empty'
464
+ };
465
+ }
466
+ else {
467
+ // An empty negative existential condition cannot exclude any facts.
468
+ return state;
469
+ }
470
+ }
471
+ const lastJoin = this.queryParts.joins[this.queryParts.joins.length - 1];
472
+ if (lastJoin.table !== 'edge') {
473
+ throw new Error(`Existential condition on non-edge table ${lastJoin.table}`);
474
+ }
475
+ this.queryParts.existentialClauses.push({
476
+ quantifier: step.quantifier,
477
+ priorEdgeJoin: lastJoin,
478
+ query: nested
479
+ });
480
+ return state;
481
+ }
482
+ }
package/src/util/fn.ts ADDED
@@ -0,0 +1,63 @@
1
+ function safeFlatten<U>(results: U[][]) {
2
+ const flat = results.reduce((a, b) =>
3
+ (!a || a.length === 0) ? b :
4
+ (!b || b.length === 0) ? a :
5
+ a.concat(b));
6
+ return flat ? flat : [];
7
+ }
8
+
9
+ export async function flattenAsync<T, U>(collection: T[], selector: (element: T) => Promise<U[]>) {
10
+ if (collection.length === 0) {
11
+ return [];
12
+ }
13
+ else {
14
+ const results = await Promise.all(collection.map(selector));
15
+ return safeFlatten(results);
16
+ }
17
+ }
18
+
19
+ export function flatten<T, U>(collection: T[], selector: (element: T) => U[]) {
20
+ if (collection.length === 0) {
21
+ return [];
22
+ }
23
+ else {
24
+ return safeFlatten(collection.map(selector));
25
+ }
26
+ }
27
+
28
+ export async function mapAsync<T, U>(collection: T[], action: (element: T) => Promise<U>) {
29
+ if (collection.length === 0) {
30
+ return [];
31
+ }
32
+ else {
33
+ return await Promise.all(collection.map(action));
34
+ }
35
+ }
36
+
37
+ export async function filterAsync<T>(collection: T[], predicate: (element: T) => Promise<boolean>) {
38
+ if (collection.length === 0) {
39
+ return [];
40
+ }
41
+ else {
42
+ const filters = await Promise.all(collection.map(async element => ({
43
+ include: await predicate(element),
44
+ element
45
+ })));
46
+
47
+ return filters.filter(f => f.include).map(f => f.element);
48
+ }
49
+ }
50
+
51
+ export function findIndex<T>(array: T[], predicate: ((element: T) => boolean)): number {
52
+ for (let index = 0; index < array.length; index++) {
53
+ if (predicate(array[index])) {
54
+ return index;
55
+ }
56
+ }
57
+
58
+ return -1;
59
+ }
60
+
61
+ export function distinct<T>(value: T, index: number, self: T[]) {
62
+ return self.indexOf(value) === index;
63
+ }
@@ -0,0 +1,5 @@
1
+ export function delay(ms: number) {
2
+ return new Promise<void>((resolve, reject) => {
3
+ setTimeout(() => resolve(), ms);
4
+ });
5
+ }