jinaga-server 2.6.2 → 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 (31) 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 +13 -17
  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/integration-test/jinaga-test/jinaga.test.js +247 -231
  17. package/integration-test/jinaga-test/package-lock.json +9 -52
  18. package/integration-test/jinaga-test/package.json +2 -5
  19. package/package.json +2 -3
  20. package/src/authentication/authentication-device.ts +29 -7
  21. package/src/authentication/authentication-session.ts +29 -7
  22. package/src/authorization/authorization-keystore.ts +42 -22
  23. package/src/http/router.ts +13 -23
  24. package/src/jinaga-server.ts +5 -6
  25. package/src/postgres/postgres-store.ts +63 -149
  26. package/test/authorization/authorizationSpec.ts +75 -89
  27. package/dist/postgres/sql.d.ts +0 -10
  28. package/dist/postgres/sql.js +0 -408
  29. package/dist/postgres/sql.js.map +0 -1
  30. package/src/postgres/sql.ts +0 -498
  31. package/test/postgres/postgresSpec.ts +0 -391
@@ -1,21 +1,59 @@
1
1
  import {
2
- AuthenticationNoOp,
3
2
  AuthorizationRules,
3
+ buildModel,
4
4
  dehydrateFact,
5
- ensure,
5
+ FactEnvelope,
6
6
  FactManager,
7
7
  FactRecord,
8
8
  hydrate,
9
- Jinaga as j,
10
9
  MemoryStore,
11
10
  NetworkNoOp,
12
11
  ObservableSource,
13
- PassThroughFork
12
+ PassThroughFork,
13
+ User
14
14
  } from "jinaga";
15
15
 
16
16
  import { AuthorizationKeystore } from "../../src/authorization/authorization-keystore";
17
17
  import { MemoryKeystore } from "../../src/memory/memory-keystore";
18
18
 
19
+ class Hashtag {
20
+ public static Type = "Hashtag" as const;
21
+ public type = Hashtag.Type;
22
+
23
+ constructor(
24
+ public word: string
25
+ ) { }
26
+ }
27
+
28
+ class Tweet {
29
+ public static Type = "Tweet" as const;
30
+ public type = Tweet.Type;
31
+
32
+ constructor(
33
+ public message: string,
34
+ public sender: User
35
+ ) { }
36
+ }
37
+
38
+ class Like {
39
+ public static Type = "Like" as const;
40
+ public type = Like.Type;
41
+
42
+ constructor(
43
+ public user: User,
44
+ public tweet: Tweet
45
+ ) { }
46
+ }
47
+
48
+ class Delete {
49
+ public static Type = "Delete" as const;
50
+ public type = Delete.Type;
51
+
52
+ constructor(
53
+ public tweet: Tweet
54
+ ) { }
55
+ }
56
+
19
57
  describe('Authorization', () => {
20
58
  it('should authorize empty save', async () => {
21
59
  const authorization = givenAuthorization();
@@ -25,19 +63,13 @@ describe('Authorization', () => {
25
63
 
26
64
  it('should save a new fact', async () => {
27
65
  const authorization = givenAuthorization();
28
- const result = await whenSave(authorization, dehydrateFact({
29
- type: 'Hashtag',
30
- word: 'vorpal'
31
- }));
66
+ const result = await whenSave(authorization, dehydrateFact(new Hashtag('vorpal')));
32
67
  expect(result.length).toEqual(1);
33
68
  });
34
69
 
35
70
  it('should save a fact once', async () => {
36
71
  const authorization = givenAuthorization();
37
- const facts = dehydrateFact({
38
- type: 'Hashtag',
39
- word: 'vorpal'
40
- });
72
+ const facts = dehydrateFact(new Hashtag('vorpal'));
41
73
  await whenSave(authorization, facts);
42
74
  const result = await whenSave(authorization, facts);
43
75
  expect(result.length).toEqual(0);
@@ -46,64 +78,36 @@ describe('Authorization', () => {
46
78
  it('should reject a fact from an unauthorized user', async () => {
47
79
  const authorization = givenAuthorization();
48
80
  const mickeyMouse = givenOtherUser();
49
- const promise = whenSave(authorization, dehydrateFact({
50
- type: 'Tweet',
51
- message: 'Twas brillig',
52
- sender: mickeyMouse
53
- }));
81
+ const promise = whenSave(authorization, dehydrateFact(new Tweet("Twas brillig", mickeyMouse)));
54
82
  await expect(promise).rejects.not.toBeNull();
55
83
  });
56
84
 
57
85
  it('should accept a fact from an authorized user', async () => {
58
86
  const authorization = givenAuthorization();
59
87
  const lewiscarrol = await givenLoggedInUser(authorization);
60
- const result = await whenSave(authorization, dehydrateFact({
61
- type: 'Tweet',
62
- message: 'Twas brillig',
63
- sender: lewiscarrol
64
- }));
88
+ const result = await whenSave(authorization, dehydrateFact(new Tweet("Twas brillig", lewiscarrol)));
65
89
  expect(result.length).toBeGreaterThan(0);
66
90
  });
67
91
 
68
92
  it('should accept a predecessor from an authorized user', async () => {
69
93
  const authorization = givenAuthorization();
70
94
  const lewiscarrol = await givenLoggedInUser(authorization);
71
- const result = await whenSave(authorization, dehydrateFact({
72
- type: 'Like',
73
- user: lewiscarrol,
74
- tweet: {
75
- type: 'Tweet',
76
- message: 'Twas Brillig',
77
- sender: lewiscarrol
78
- }
79
- }));
80
- expect(result.filter(r => r.type === 'Tweet').length).toEqual(1);
95
+ const result = await whenSave(authorization, dehydrateFact(new Like(lewiscarrol, new Tweet('Twas Brillig', lewiscarrol))));
96
+ expect(result.filter(r => r.fact.type === 'Tweet').length).toEqual(1);
81
97
  });
82
98
 
83
99
  it('should reject a predecessor from an unauthorized user', async () => {
84
100
  const authorization = givenAuthorization();
85
101
  const lewiscarrol = await givenLoggedInUser(authorization);
86
102
  const mickeyMouse = givenOtherUser();
87
- const promise = whenSave(authorization, dehydrateFact({
88
- type: 'Like',
89
- user: lewiscarrol,
90
- tweet: {
91
- type: 'Tweet',
92
- message: 'Hiya, Pal!',
93
- sender: mickeyMouse
94
- }
95
- }));
103
+ const promise = whenSave(authorization, dehydrateFact(new Like(lewiscarrol, new Tweet("Hiya, Pal!", mickeyMouse))));
96
104
  await expect(promise).rejects.not.toBeNull();
97
105
  });
98
106
 
99
107
  it('should accept a pre-existing predecessor from an unauthorized user', async () => {
100
108
  const storage = givenStorage();
101
109
  const mickeyMouse = givenOtherUser();
102
- const tweet = {
103
- type: 'Tweet',
104
- message: 'Hiya, Pal!',
105
- sender: mickeyMouse
106
- };
110
+ const tweet = new Tweet("Hiya, Pal!", mickeyMouse);
107
111
  await storage.save(dehydrateFact(tweet).map(f => ({ fact: f, signatures: [] })));
108
112
 
109
113
  const authorization = givenAuthorizationWithStorage(storage);
@@ -114,17 +118,13 @@ describe('Authorization', () => {
114
118
  tweet: tweet
115
119
  }));
116
120
  expect(result.length).toBeGreaterThan(0);
117
- expect(result.filter(r => r.type === 'Tweet').length).toEqual(0);
121
+ expect(result.filter(r => r.fact.type === 'Tweet').length).toEqual(0);
118
122
  });
119
123
 
120
124
  it('should accept a fact authorized by predecessor', async () => {
121
125
  const authorization = givenAuthorization();
122
126
  const lewiscarrol = await givenLoggedInUser(authorization);
123
- const tweet = {
124
- type: 'Tweet',
125
- message: 'Twas brillig',
126
- sender: lewiscarrol
127
- };
127
+ const tweet = new Tweet("Twas brillig", lewiscarrol);
128
128
  await whenSave(authorization, dehydrateFact(tweet));
129
129
 
130
130
  const result = await whenSave(authorization, dehydrateFact({
@@ -137,11 +137,7 @@ describe('Authorization', () => {
137
137
  it('should accept a fact based on in-flight predecessor', async () => {
138
138
  const authorization = givenAuthorization();
139
139
  const lewiscarrol = await givenLoggedInUser(authorization);
140
- const tweet = {
141
- type: 'Tweet',
142
- message: 'Twas brillig',
143
- sender: lewiscarrol
144
- };
140
+ const tweet = new Tweet("Twas brillig", lewiscarrol);
145
141
  // Note that this is missing.
146
142
  // await whenSave(authorization, dehydrateFact(tweet));
147
143
 
@@ -165,32 +161,39 @@ function givenStorage() {
165
161
  function givenAuthorizationWithStorage(storage: MemoryStore) {
166
162
  const keystore = new MemoryKeystore();
167
163
  keystore.getOrCreateUserFact(givenMockUserIdentity());
168
- const authentication = new AuthenticationNoOp();
169
164
  const fork = new PassThroughFork(storage);
170
165
  const observableSource = new ObservableSource(storage);
171
166
  const network = new NetworkNoOp();
172
- const factManager = new FactManager(authentication, fork, observableSource, storage, network);
173
- const authorizationRules = new AuthorizationRules(undefined)
174
- .any('Hashtag')
175
- .no('Jinaga.User')
176
- .type('Tweet', j.for(tweetSender))
177
- .type('Like', j.for(likeUser))
178
- .type('Delete', j.for(deleteSender))
167
+ const factManager = new FactManager(fork, observableSource, storage, network);
168
+ const model = buildModel(b => b
169
+ .type(Hashtag)
170
+ .type(User)
171
+ .type(Tweet, m => m
172
+ .predecessor("sender", User))
173
+ .type(Like, m => m
174
+ .predecessor("user", User)
175
+ .predecessor("tweet", Tweet))
176
+ .type(Delete, m => m
177
+ .predecessor("tweet", Tweet))
178
+ );
179
+ const authorizationRules = new AuthorizationRules(model)
180
+ .any(Hashtag)
181
+ .no(User)
182
+ .type(Tweet, t => t.sender)
183
+ .type(Like, l => l.user)
184
+ .type(Delete, d => d.tweet.sender)
179
185
  ;
180
186
  return new AuthorizationKeystore(factManager, storage, keystore, authorizationRules, null);
181
187
  }
182
188
 
183
189
  function givenOtherUser() {
184
- return {
185
- type: 'Jinaga.User',
186
- publicKey: 'other'
187
- };
190
+ return new User('other');
188
191
  }
189
192
 
190
193
  async function givenLoggedInUser(authorization: AuthorizationKeystore) {
191
194
  const userIdentity = givenMockUserIdentity();
192
195
  const userFact = await authorization.getOrCreateUserFact(userIdentity);
193
- const user = hydrate<{}>(userFact);
196
+ const user = hydrate<User>(userFact);
194
197
  return user;
195
198
  }
196
199
 
@@ -203,24 +206,7 @@ function givenMockUserIdentity() {
203
206
 
204
207
  async function whenSave(authorization: AuthorizationKeystore, facts: FactRecord[]) {
205
208
  const userIdentity = givenMockUserIdentity();
206
- const result = await authorization.save(userIdentity, facts);
209
+ const envelopes: FactEnvelope[] = facts.map(f => ({ fact: f, signatures: [] }));
210
+ const result = await authorization.save(userIdentity, envelopes);
207
211
  return result;
208
- }
209
-
210
- function tweetSender(t: { sender: {} }) {
211
- ensure(t).has("sender", "Jinaga.User");
212
-
213
- return j.match(t.sender);
214
- }
215
-
216
- function likeUser(l: { user: {} }) {
217
- ensure(l).has("user", "Jinaga.User");
218
-
219
- return j.match(l.user);
220
- }
221
-
222
- function deleteSender(d: { tweet: { sender: {} } }) {
223
- ensure(d).has("tweet", "Tweet").has("sender", "Jinaga.User");
224
-
225
- return j.match(d.tweet.sender);
226
212
  }
@@ -1,10 +0,0 @@
1
- import { FactReference, Step } from "jinaga";
2
- import { FactTypeMap, RoleMap } from "./maps";
3
- export declare type SqlQuery = {
4
- empty: boolean;
5
- sql: string;
6
- parameters: any[];
7
- pathLength: number;
8
- factTypeNames: string[];
9
- };
10
- export declare function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap, schema: string): SqlQuery | null;
@@ -1,408 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sqlFromSteps = void 0;
4
- const jinaga_1 = require("jinaga");
5
- const maps_1 = require("./maps");
6
- function sqlFromSteps(start, steps, factTypes, roleMap, schema) {
7
- const builder = new QueryBuilder(factTypes, roleMap);
8
- return builder.buildQuery(start, steps, schema);
9
- }
10
- exports.sqlFromSteps = sqlFromSteps;
11
- ;
12
- class QueryBuilder {
13
- constructor(factTypes, roleMap) {
14
- this.factTypes = factTypes;
15
- this.roleMap = roleMap;
16
- this.nextEdge = 1;
17
- this.nextFact = 2;
18
- this.shouldEmitFacts = true;
19
- this.queryParts = {
20
- joins: [],
21
- existentialClauses: []
22
- };
23
- this.roleIds = [];
24
- this.factTypeNames = [];
25
- }
26
- buildQuery(start, steps, schema) {
27
- if (steps.length === 0) {
28
- return null;
29
- }
30
- const startTypeId = (0, maps_1.ensureGetFactTypeId)(this.factTypes, start.type);
31
- const startState = {
32
- state: 'start',
33
- typeId: startTypeId,
34
- typeName: start.type
35
- };
36
- const finalState = steps.reduce((state, step) => {
37
- return this.matchStep(state, step);
38
- }, startState);
39
- this.end(finalState);
40
- if (finalState.state === 'empty') {
41
- return {
42
- empty: true,
43
- sql: '',
44
- parameters: [],
45
- pathLength: 0,
46
- factTypeNames: []
47
- };
48
- }
49
- const factAliases = this.queryParts.joins
50
- .filter(j => j.table === 'fact')
51
- .map(j => j.factAlias);
52
- const hashes = factAliases.map(a => `f${a}.hash as hash${a}`).join(', ');
53
- const joins = this.buildJoins(schema, this.queryParts.joins, 'f1.fact_id');
54
- const whereClause = this.buildWhereClause(schema, this.queryParts.existentialClauses);
55
- const sql = `SELECT ${hashes} FROM ${schema}.fact f1${joins} WHERE f1.fact_type_id = $1 AND f1.hash = $2${whereClause}`;
56
- return {
57
- empty: false,
58
- sql,
59
- parameters: [startTypeId, start.hash, ...this.roleIds],
60
- pathLength: factAliases.length,
61
- factTypeNames: this.factTypeNames
62
- };
63
- }
64
- buildJoins(schema, joins, priorFactId) {
65
- ;
66
- const initialJoins = {
67
- priorFactId,
68
- clauses: []
69
- };
70
- const clauses = joins.reduce((joins, join) => {
71
- if (join.table === 'edge') {
72
- if (join.direction === 'successor') {
73
- const clause = ` JOIN ${schema}.edge e${join.edgeAlias} ` +
74
- `ON e${join.edgeAlias}.predecessor_fact_id = ${joins.priorFactId} ` +
75
- `AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
76
- return {
77
- priorFactId: `e${join.edgeAlias}.successor_fact_id`,
78
- clauses: [...joins.clauses, clause]
79
- };
80
- }
81
- else {
82
- const clause = ` JOIN ${schema}.edge e${join.edgeAlias} ` +
83
- `ON e${join.edgeAlias}.successor_fact_id = ${joins.priorFactId} ` +
84
- `AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
85
- return {
86
- priorFactId: `e${join.edgeAlias}.predecessor_fact_id`,
87
- clauses: [...joins.clauses, clause]
88
- };
89
- }
90
- }
91
- else {
92
- const clause = ` JOIN ${schema}.fact f${join.factAlias} ON f${join.factAlias}.fact_id = ${joins.priorFactId}`;
93
- return {
94
- priorFactId: joins.priorFactId,
95
- clauses: [...joins.clauses, clause]
96
- };
97
- }
98
- }, initialJoins).clauses;
99
- return clauses.join('');
100
- }
101
- buildWhereClause(schema, existentialClauses) {
102
- if (existentialClauses.length === 0) {
103
- return '';
104
- }
105
- const clauses = existentialClauses.map(clause => {
106
- const quantifierSql = clause.quantifier === jinaga_1.Quantifier.Exists ? 'EXISTS' : 'NOT EXISTS';
107
- const [first, ...rest] = clause.query.joins;
108
- const firstHeadFactId = first.direction === 'predecessor'
109
- ? `e${first.edgeAlias}.predecessor_fact_id`
110
- : `e${first.edgeAlias}.successor_fact_id`;
111
- const firstTailFactId = first.direction === 'predecessor'
112
- ? `e${first.edgeAlias}.successor_fact_id`
113
- : `e${first.edgeAlias}.predecessor_fact_id`;
114
- const joins = this.buildJoins(schema, rest, firstHeadFactId);
115
- const priorFactId = clause.priorEdgeJoin.direction === 'predecessor'
116
- ? `e${clause.priorEdgeJoin.edgeAlias}.predecessor_fact_id`
117
- : `e${clause.priorEdgeJoin.edgeAlias}.successor_fact_id`;
118
- const whereClause = this.buildWhereClause(schema, clause.query.existentialClauses);
119
- return ` AND ${quantifierSql} (SELECT 1 FROM ${schema}.edge e${first.edgeAlias}${joins} ` +
120
- `WHERE ${firstTailFactId} = ${priorFactId} ` +
121
- `AND e${first.edgeAlias}.role_id = $${first.roleParameter}${whereClause})`;
122
- });
123
- return clauses.join('');
124
- }
125
- buildNestedSql(typeId, typeName, steps) {
126
- // Push a new query to the stack.
127
- const parentQuery = this.queryParts;
128
- const parentShouldEmitFact = this.shouldEmitFacts;
129
- this.shouldEmitFacts = false;
130
- this.queryParts = {
131
- joins: [],
132
- existentialClauses: []
133
- };
134
- const startState = {
135
- state: 'predecessor-type',
136
- typeId: typeId,
137
- typeName: typeName
138
- };
139
- const finalState = steps.reduce((state, step) => {
140
- return this.matchStep(state, step);
141
- }, startState);
142
- this.end(finalState);
143
- // Pop the stack and return the nested query.
144
- const nestedQuery = this.queryParts;
145
- this.shouldEmitFacts = parentShouldEmitFact;
146
- this.queryParts = parentQuery;
147
- // If the nested query cannot match any results, return null.
148
- if (finalState.state === 'empty') {
149
- return null;
150
- }
151
- return nestedQuery;
152
- }
153
- matchStep(state, step) {
154
- switch (state.state) {
155
- case 'start':
156
- return this.matchStepStart(state, step);
157
- case 'predecessor-type':
158
- return this.matchStepPredecessorType(state, step);
159
- case 'predecessor-join':
160
- return this.matchStepPredecessorJoin(state, step);
161
- case 'successor-join':
162
- return this.matchStepSuccessorJoin(state, step);
163
- case 'successor-type':
164
- return this.matchStepSuccessorType(state, step);
165
- case 'empty':
166
- return state;
167
- default:
168
- throw new Error(`Unknown state: ${state.state}`);
169
- }
170
- }
171
- matchStepStart(state, step) {
172
- if (step instanceof jinaga_1.PropertyCondition) {
173
- if (step.name !== 'type') {
174
- throw new Error(`Property condition on non-type property ${step.name}`);
175
- }
176
- const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
177
- if (typeId !== state.typeId) {
178
- throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
179
- }
180
- return state;
181
- }
182
- else if (step instanceof jinaga_1.Join) {
183
- if (step.direction === jinaga_1.Direction.Predecessor) {
184
- const roleId = (0, maps_1.getRoleId)(this.roleMap, state.typeId, step.role);
185
- if (!roleId) {
186
- return {
187
- state: 'empty'
188
- };
189
- }
190
- this.emitEdge('predecessor', roleId);
191
- return {
192
- state: 'predecessor-join',
193
- position: `${state.typeName}.${step.role}`
194
- };
195
- }
196
- else if (step.direction === jinaga_1.Direction.Successor) {
197
- return {
198
- state: 'successor-join',
199
- role: step.role
200
- };
201
- }
202
- else {
203
- throw new Error(`Unknown direction ${step.direction}`);
204
- }
205
- }
206
- else if (step instanceof jinaga_1.ExistentialCondition) {
207
- return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
208
- }
209
- else {
210
- throw new Error(`Unknown step ${step.constructor.name}`);
211
- }
212
- }
213
- matchStepPredecessorType(state, step) {
214
- if (step instanceof jinaga_1.PropertyCondition) {
215
- if (step.name !== 'type') {
216
- throw new Error(`Property condition on non-type property ${step.name}`);
217
- }
218
- const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
219
- if (typeId !== state.typeId) {
220
- throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
221
- }
222
- return state;
223
- }
224
- else if (step instanceof jinaga_1.Join) {
225
- if (step.direction === jinaga_1.Direction.Predecessor) {
226
- const roleId = (0, maps_1.getRoleId)(this.roleMap, state.typeId, step.role);
227
- if (!roleId) {
228
- return {
229
- state: 'empty'
230
- };
231
- }
232
- this.emitFact(state.typeName);
233
- this.emitEdge('predecessor', roleId);
234
- return {
235
- state: 'predecessor-join',
236
- position: `${state.typeName}.${step.role}`
237
- };
238
- }
239
- else if (step.direction === jinaga_1.Direction.Successor) {
240
- this.emitFact(state.typeName);
241
- return {
242
- state: 'successor-join',
243
- role: step.role
244
- };
245
- }
246
- else {
247
- throw new Error(`Unknown direction ${step.direction}`);
248
- }
249
- }
250
- else if (step instanceof jinaga_1.ExistentialCondition) {
251
- return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
252
- }
253
- else {
254
- throw new Error(`Unknown step ${step.constructor.name}`);
255
- }
256
- }
257
- matchStepPredecessorJoin(state, step) {
258
- if (step instanceof jinaga_1.PropertyCondition) {
259
- if (step.name !== 'type') {
260
- throw new Error(`Property condition on non-type property ${step.name}`);
261
- }
262
- const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
263
- if (!typeId) {
264
- return {
265
- state: 'empty'
266
- };
267
- }
268
- return {
269
- state: 'predecessor-type',
270
- typeId,
271
- typeName: step.value
272
- };
273
- }
274
- else {
275
- throw new Error(`Missing type of "${state.position}"`);
276
- }
277
- }
278
- matchStepSuccessorJoin(state, step) {
279
- if (step instanceof jinaga_1.PropertyCondition) {
280
- if (step.name !== 'type') {
281
- throw new Error(`Property condition on non-type property ${step.name}`);
282
- }
283
- const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
284
- if (!typeId) {
285
- return {
286
- state: 'empty'
287
- };
288
- }
289
- const roleId = (0, maps_1.getRoleId)(this.roleMap, typeId, state.role);
290
- if (!roleId) {
291
- return {
292
- state: 'empty'
293
- };
294
- }
295
- this.emitEdge('successor', roleId);
296
- return {
297
- state: 'successor-type',
298
- typeId: typeId,
299
- typeName: step.value
300
- };
301
- }
302
- else {
303
- throw new Error(`Missing type for role ${state.role}`);
304
- }
305
- }
306
- matchStepSuccessorType(state, step) {
307
- if (step instanceof jinaga_1.PropertyCondition) {
308
- if (step.name !== 'type') {
309
- throw new Error(`Property condition on non-type property ${step.name}`);
310
- }
311
- const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
312
- if (typeId !== state.typeId) {
313
- throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
314
- }
315
- return state;
316
- }
317
- else if (step instanceof jinaga_1.Join) {
318
- if (step.direction === jinaga_1.Direction.Predecessor) {
319
- const roleId = (0, maps_1.getRoleId)(this.roleMap, state.typeId, step.role);
320
- if (!roleId) {
321
- return {
322
- state: 'empty'
323
- };
324
- }
325
- this.emitFact(state.typeName);
326
- this.emitEdge('predecessor', roleId);
327
- return {
328
- state: 'predecessor-join',
329
- position: `${state.typeName}.${step.role}`
330
- };
331
- }
332
- else {
333
- this.emitFact(state.typeName);
334
- return {
335
- state: 'successor-join',
336
- role: step.role
337
- };
338
- }
339
- }
340
- else if (step instanceof jinaga_1.ExistentialCondition) {
341
- return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
342
- }
343
- else {
344
- throw new Error(`Unknown step ${step.constructor.name}`);
345
- }
346
- }
347
- end(finalState) {
348
- if (finalState.state === 'successor-join') {
349
- throw new Error(`Missing type for role ${finalState.role}`);
350
- }
351
- else if (finalState.state === 'predecessor-join') {
352
- throw new Error(`Missing type of "${finalState.position}"`);
353
- }
354
- else if (finalState.state === 'predecessor-type' || finalState.state === 'successor-type') {
355
- this.emitFact(finalState.typeName);
356
- }
357
- }
358
- emitEdge(direction, roleId) {
359
- const edgeAlias = this.nextEdge++;
360
- this.roleIds.push(roleId);
361
- this.queryParts.joins.push({
362
- table: 'edge',
363
- direction: direction,
364
- edgeAlias: edgeAlias,
365
- roleParameter: edgeAlias + 2
366
- });
367
- }
368
- emitFact(factTypeName) {
369
- if (this.shouldEmitFacts) {
370
- const factAlias = this.nextFact++;
371
- this.queryParts.joins.push({
372
- table: 'fact',
373
- factAlias: factAlias
374
- });
375
- this.factTypeNames.push(factTypeName);
376
- }
377
- }
378
- pushExistentialCondition(typeId, typeName, state, step) {
379
- if (this.queryParts.joins.length === 0) {
380
- // We cannot apply a condition until we have some joins.
381
- return state;
382
- }
383
- const lastJoin = this.queryParts.joins[this.queryParts.joins.length - 1];
384
- if (lastJoin.table !== 'edge') {
385
- throw new Error(`Existential condition on non-edge table ${lastJoin.table}`);
386
- }
387
- const nested = this.buildNestedSql(typeId, typeName, step.steps);
388
- if (!nested) {
389
- if (step.quantifier === jinaga_1.Quantifier.Exists) {
390
- // An empty positive existential condition cannot match any facts.
391
- return {
392
- state: 'empty'
393
- };
394
- }
395
- else {
396
- // An empty negative existential condition cannot exclude any facts.
397
- return state;
398
- }
399
- }
400
- this.queryParts.existentialClauses.push({
401
- quantifier: step.quantifier,
402
- priorEdgeJoin: lastJoin,
403
- query: nested
404
- });
405
- return state;
406
- }
407
- }
408
- //# sourceMappingURL=sql.js.map