jinaga-server 2.6.1 → 3.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 (32) hide show
  1. package/dist/authentication/authentication-device.js +27 -6
  2. package/dist/authentication/authentication-device.js.map +1 -1
  3. package/dist/authentication/authentication-session.js +27 -6
  4. package/dist/authentication/authentication-session.js.map +1 -1
  5. package/dist/authorization/authorization-keystore.d.ts +4 -5
  6. package/dist/authorization/authorization-keystore.js +42 -18
  7. package/dist/authorization/authorization-keystore.js.map +1 -1
  8. package/dist/http/router.d.ts +1 -2
  9. package/dist/http/router.js +14 -18
  10. package/dist/http/router.js.map +1 -1
  11. package/dist/jinaga-server.js +5 -6
  12. package/dist/jinaga-server.js.map +1 -1
  13. package/dist/postgres/postgres-store.d.ts +2 -5
  14. package/dist/postgres/postgres-store.js +44 -121
  15. package/dist/postgres/postgres-store.js.map +1 -1
  16. package/http/sans-auth/Feed sites.http +1 -1
  17. package/integration-test/jinaga-test/jinaga.test.js +247 -231
  18. package/integration-test/jinaga-test/package-lock.json +16 -59
  19. package/integration-test/jinaga-test/package.json +2 -5
  20. package/package.json +3 -4
  21. package/src/authentication/authentication-device.ts +29 -7
  22. package/src/authentication/authentication-session.ts +29 -7
  23. package/src/authorization/authorization-keystore.ts +42 -22
  24. package/src/http/router.ts +14 -24
  25. package/src/jinaga-server.ts +5 -6
  26. package/src/postgres/postgres-store.ts +63 -149
  27. package/test/authorization/authorizationSpec.ts +75 -89
  28. package/dist/postgres/sql.d.ts +0 -10
  29. package/dist/postgres/sql.js +0 -408
  30. package/dist/postgres/sql.js.map +0 -1
  31. package/src/postgres/sql.ts +0 -498
  32. package/test/postgres/postgresSpec.ts +0 -391
@@ -1,498 +0,0 @@
1
- import { Direction, ExistentialCondition, FactReference, Join, PropertyCondition, Quantifier, Step } from "jinaga";
2
-
3
- import { ensureGetFactTypeId, 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, schema: string) : SqlQuery | null {
14
- const builder = new QueryBuilder(factTypes, roleMap);
15
-
16
- return builder.buildQuery(start, steps, schema);
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[], schema: string): SqlQuery | null {
94
- if (steps.length === 0) {
95
- return null;
96
- }
97
-
98
- const startTypeId = ensureGetFactTypeId(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: QueryBuilderState, 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(schema, this.queryParts.joins, 'f1.fact_id');
124
- const whereClause = this.buildWhereClause(schema, this.queryParts.existentialClauses);
125
- const sql = `SELECT ${hashes} FROM ${schema}.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(schema: string, joins: QueryJoin[], priorFactId: string) {
137
- interface Joins {
138
- priorFactId: string,
139
- clauses: string[]
140
- };
141
- const initialJoins: Joins = {
142
- priorFactId,
143
- clauses: []
144
- };
145
- const clauses = joins.reduce((joins, join) => {
146
- if (join.table === 'edge') {
147
- if (join.direction === 'successor') {
148
- const clause = ` JOIN ${schema}.edge e${join.edgeAlias} ` +
149
- `ON e${join.edgeAlias}.predecessor_fact_id = ${joins.priorFactId} ` +
150
- `AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
151
- return {
152
- priorFactId: `e${join.edgeAlias}.successor_fact_id`,
153
- clauses: [...joins.clauses, clause]
154
- };
155
- }
156
- else {
157
- const clause = ` JOIN ${schema}.edge e${join.edgeAlias} ` +
158
- `ON e${join.edgeAlias}.successor_fact_id = ${joins.priorFactId} ` +
159
- `AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
160
- return {
161
- priorFactId: `e${join.edgeAlias}.predecessor_fact_id`,
162
- clauses: [...joins.clauses, clause]
163
- };
164
- }
165
- } else {
166
- const clause = ` JOIN ${schema}.fact f${join.factAlias} ON f${join.factAlias}.fact_id = ${joins.priorFactId}`;
167
- return {
168
- priorFactId: joins.priorFactId,
169
- clauses: [...joins.clauses, clause]
170
- };
171
- }
172
- }, initialJoins).clauses;
173
- return clauses.join('');
174
- }
175
-
176
- buildWhereClause(schema: string, existentialClauses: ExistentialClause[]): string {
177
- if (existentialClauses.length === 0) {
178
- return '';
179
- }
180
-
181
- const clauses = existentialClauses.map(clause => {
182
- const quantifierSql = clause.quantifier === Quantifier.Exists ? 'EXISTS' : 'NOT EXISTS';
183
- const [first, ...rest] : QueryJoinEdge[] = clause.query.joins as QueryJoinEdge[];
184
- const firstHeadFactId = first.direction === 'predecessor'
185
- ? `e${first.edgeAlias}.predecessor_fact_id`
186
- : `e${first.edgeAlias}.successor_fact_id`;
187
- const firstTailFactId = first.direction === 'predecessor'
188
- ? `e${first.edgeAlias}.successor_fact_id`
189
- : `e${first.edgeAlias}.predecessor_fact_id`;
190
- const joins = this.buildJoins(schema, rest, firstHeadFactId);
191
- const priorFactId = clause.priorEdgeJoin.direction === 'predecessor'
192
- ? `e${clause.priorEdgeJoin.edgeAlias}.predecessor_fact_id`
193
- : `e${clause.priorEdgeJoin.edgeAlias}.successor_fact_id`;
194
- const whereClause = this.buildWhereClause(schema, clause.query.existentialClauses);
195
- return ` AND ${quantifierSql} (SELECT 1 FROM ${schema}.edge e${first.edgeAlias}${joins} ` +
196
- `WHERE ${firstTailFactId} = ${priorFactId} ` +
197
- `AND e${first.edgeAlias}.role_id = $${first.roleParameter}${whereClause})`;
198
- });
199
- return clauses.join('');
200
- }
201
-
202
- buildNestedSql(typeId: number, typeName: string, steps: Step[]): QueryParts | null {
203
- // Push a new query to the stack.
204
- const parentQuery = this.queryParts;
205
- const parentShouldEmitFact = this.shouldEmitFacts;
206
- this.shouldEmitFacts = false;
207
- this.queryParts = {
208
- joins: [],
209
- existentialClauses: []
210
- };
211
-
212
- const startState: QueryBuilderState = {
213
- state: 'predecessor-type',
214
- typeId: typeId,
215
- typeName: typeName
216
- };
217
- const finalState = steps.reduce((state: QueryBuilderState, step) => {
218
- return this.matchStep(state, step);
219
- }, startState);
220
- this.end(finalState);
221
-
222
- // Pop the stack and return the nested query.
223
- const nestedQuery = this.queryParts;
224
- this.shouldEmitFacts = parentShouldEmitFact;
225
- this.queryParts = parentQuery;
226
-
227
- // If the nested query cannot match any results, return null.
228
- if (finalState.state === 'empty') {
229
- return null;
230
- }
231
- return nestedQuery;
232
- }
233
-
234
- private matchStep(state: QueryBuilderState, step: Step): QueryBuilderState {
235
- switch (state.state) {
236
- case 'start':
237
- return this.matchStepStart(state, step);
238
- case 'predecessor-type':
239
- return this.matchStepPredecessorType(state, step);
240
- case 'predecessor-join':
241
- return this.matchStepPredecessorJoin(state, step);
242
- case 'successor-join':
243
- return this.matchStepSuccessorJoin(state, step);
244
- case 'successor-type':
245
- return this.matchStepSuccessorType(state, step);
246
- case 'empty':
247
- return state;
248
- default:
249
- throw new Error(`Unknown state: ${(state as any).state}`);
250
- }
251
- }
252
-
253
- private matchStepStart(state: QueryBuilderStateStart, step: Step): QueryBuilderState {
254
- if (step instanceof PropertyCondition) {
255
- if (step.name !== 'type') {
256
- throw new Error(`Property condition on non-type property ${step.name}`);
257
- }
258
- const typeId = getFactTypeId(this.factTypes, step.value);
259
- if (typeId !== state.typeId) {
260
- throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
261
- }
262
- return state;
263
- }
264
- else if (step instanceof Join) {
265
- if (step.direction === Direction.Predecessor) {
266
- const roleId = getRoleId(this.roleMap, state.typeId, step.role);
267
- if (!roleId) {
268
- return {
269
- state: 'empty'
270
- };
271
- }
272
- this.emitEdge('predecessor', roleId);
273
- return {
274
- state: 'predecessor-join',
275
- position: `${state.typeName}.${step.role}`
276
- }
277
- }
278
- else if (step.direction === Direction.Successor) {
279
- return {
280
- state: 'successor-join',
281
- role: step.role
282
- }
283
- }
284
- else {
285
- throw new Error(`Unknown direction ${step.direction}`);
286
- }
287
- }
288
- else if (step instanceof ExistentialCondition) {
289
- return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
290
- }
291
- else {
292
- throw new Error(`Unknown step ${step.constructor.name}`);
293
- }
294
- }
295
-
296
- private matchStepPredecessorType(state: QueryBuilderStatePredecessorType, step: Step): QueryBuilderState {
297
- if (step instanceof PropertyCondition) {
298
- if (step.name !== 'type') {
299
- throw new Error(`Property condition on non-type property ${step.name}`);
300
- }
301
- const typeId = getFactTypeId(this.factTypes, step.value);
302
- if (typeId !== state.typeId) {
303
- throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
304
- }
305
- return state;
306
- }
307
- else if (step instanceof Join) {
308
- if (step.direction === Direction.Predecessor) {
309
- const roleId = getRoleId(this.roleMap, state.typeId, step.role);
310
- if (!roleId) {
311
- return {
312
- state: 'empty'
313
- };
314
- }
315
- this.emitFact(state.typeName);
316
- this.emitEdge('predecessor', roleId);
317
- return {
318
- state: 'predecessor-join',
319
- position: `${state.typeName}.${step.role}`
320
- }
321
- }
322
- else if (step.direction === Direction.Successor) {
323
- this.emitFact(state.typeName);
324
- return {
325
- state: 'successor-join',
326
- role: step.role
327
- }
328
- }
329
- else {
330
- throw new Error(`Unknown direction ${step.direction}`);
331
- }
332
- }
333
- else if (step instanceof ExistentialCondition) {
334
- return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
335
- }
336
- else {
337
- throw new Error(`Unknown step ${step.constructor.name}`);
338
- }
339
- }
340
-
341
- private matchStepPredecessorJoin(state: QueryBuilderStatePredecessorJoin, step: Step): QueryBuilderState {
342
- if (step instanceof PropertyCondition) {
343
- if (step.name !== 'type') {
344
- throw new Error(`Property condition on non-type property ${step.name}`);
345
- }
346
- const typeId = getFactTypeId(this.factTypes, step.value);
347
- if (!typeId) {
348
- return {
349
- state: 'empty'
350
- };
351
- }
352
- return {
353
- state: 'predecessor-type',
354
- typeId,
355
- typeName: step.value
356
- };
357
- }
358
- else {
359
- throw new Error(`Missing type of "${state.position}"`);
360
- }
361
- }
362
-
363
- private matchStepSuccessorJoin(state: QueryBuilderStateSuccessorJoin, step: Step): QueryBuilderState {
364
- if (step instanceof PropertyCondition) {
365
- if (step.name !== 'type') {
366
- throw new Error(`Property condition on non-type property ${step.name}`);
367
- }
368
- const typeId = getFactTypeId(this.factTypes, step.value);
369
- if (!typeId) {
370
- return {
371
- state: 'empty'
372
- };
373
- }
374
- const roleId = getRoleId(this.roleMap, typeId, state.role);
375
- if (!roleId) {
376
- return {
377
- state: 'empty'
378
- };
379
- }
380
- this.emitEdge('successor', roleId);
381
- return {
382
- state: 'successor-type',
383
- typeId: typeId,
384
- typeName: step.value
385
- };
386
- }
387
- else {
388
- throw new Error(`Missing type for role ${state.role}`);
389
- }
390
- }
391
-
392
- private matchStepSuccessorType(state: QueryBuilderStateSuccessorType, step: Step): QueryBuilderState {
393
- if (step instanceof PropertyCondition) {
394
- if (step.name !== 'type') {
395
- throw new Error(`Property condition on non-type property ${step.name}`);
396
- }
397
- const typeId = getFactTypeId(this.factTypes, step.value);
398
- if (typeId !== state.typeId) {
399
- throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
400
- }
401
- return state;
402
- }
403
- else if (step instanceof Join) {
404
- if (step.direction === Direction.Predecessor) {
405
- const roleId = getRoleId(this.roleMap, state.typeId, step.role);
406
- if (!roleId) {
407
- return {
408
- state: 'empty'
409
- };
410
- }
411
- this.emitFact(state.typeName);
412
- this.emitEdge('predecessor', roleId);
413
- return {
414
- state: 'predecessor-join',
415
- position: `${state.typeName}.${step.role}`
416
- };
417
- }
418
- else {
419
- this.emitFact(state.typeName);
420
- return {
421
- state: 'successor-join',
422
- role: step.role
423
- };
424
- }
425
- }
426
- else if (step instanceof ExistentialCondition) {
427
- return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
428
- }
429
- else {
430
- throw new Error(`Unknown step ${step.constructor.name}`);
431
- }
432
- }
433
-
434
- private end(finalState: QueryBuilderState) {
435
- if (finalState.state === 'successor-join') {
436
- throw new Error(`Missing type for role ${finalState.role}`);
437
- }
438
- else if (finalState.state === 'predecessor-join') {
439
- throw new Error(`Missing type of "${finalState.position}"`);
440
- }
441
- else if (finalState.state === 'predecessor-type' || finalState.state === 'successor-type') {
442
- this.emitFact(finalState.typeName);
443
- }
444
- }
445
-
446
- private emitEdge(direction: 'predecessor' | 'successor', roleId: number) {
447
- const edgeAlias = this.nextEdge++;
448
- this.roleIds.push(roleId);
449
- this.queryParts.joins.push({
450
- table: 'edge',
451
- direction: direction,
452
- edgeAlias: edgeAlias,
453
- roleParameter: edgeAlias + 2
454
- });
455
- }
456
-
457
- private emitFact(factTypeName: string) {
458
- if (this.shouldEmitFacts) {
459
- const factAlias = this.nextFact++;
460
- this.queryParts.joins.push({
461
- table: 'fact',
462
- factAlias: factAlias
463
- });
464
- this.factTypeNames.push(factTypeName);
465
- }
466
- }
467
-
468
- private pushExistentialCondition(typeId: number, typeName: string, state: QueryBuilderState, step: ExistentialCondition): QueryBuilderState {
469
- if (this.queryParts.joins.length === 0) {
470
- // We cannot apply a condition until we have some joins.
471
- return state;
472
- }
473
- const lastJoin = this.queryParts.joins[this.queryParts.joins.length - 1];
474
- if (lastJoin.table !== 'edge') {
475
- throw new Error(`Existential condition on non-edge table ${lastJoin.table}`);
476
- }
477
-
478
- const nested = this.buildNestedSql(typeId, typeName, step.steps);
479
- if (!nested) {
480
- if (step.quantifier === Quantifier.Exists) {
481
- // An empty positive existential condition cannot match any facts.
482
- return {
483
- state: 'empty'
484
- };
485
- }
486
- else {
487
- // An empty negative existential condition cannot exclude any facts.
488
- return state;
489
- }
490
- }
491
- this.queryParts.existentialClauses.push({
492
- quantifier: step.quantifier,
493
- priorEdgeJoin: lastJoin,
494
- query: nested
495
- });
496
- return state;
497
- }
498
- }