jinaga-server 1.0.0 → 1.1.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 (63) hide show
  1. package/README.md +103 -0
  2. package/dist/authentication/authentication-device.d.ts +3 -1
  3. package/dist/authentication/authentication-device.js +3 -0
  4. package/dist/authentication/authentication-device.js.map +1 -1
  5. package/dist/authentication/authentication-impl.d.ts +3 -2
  6. package/dist/authentication/authentication-impl.js +3 -0
  7. package/dist/authentication/authentication-impl.js.map +1 -1
  8. package/dist/authentication/authentication-session.d.ts +4 -2
  9. package/dist/authentication/authentication-session.js +7 -2
  10. package/dist/authentication/authentication-session.js.map +1 -1
  11. package/dist/authentication.d.ts +5 -4
  12. package/dist/authentication.js +3 -0
  13. package/dist/authentication.js.map +1 -1
  14. package/dist/authorization/authorization-keystore.d.ts +5 -3
  15. package/dist/authorization/authorization-keystore.js +12 -9
  16. package/dist/authorization/authorization-keystore.js.map +1 -1
  17. package/dist/http/feed-cache.d.ts +5 -0
  18. package/dist/http/feed-cache.js +3 -0
  19. package/dist/http/feed-cache.js.map +1 -0
  20. package/dist/http/node-http.js +4 -1
  21. package/dist/http/node-http.js.map +1 -1
  22. package/dist/http/router.d.ts +5 -1
  23. package/dist/http/router.js +157 -24
  24. package/dist/http/router.js.map +1 -1
  25. package/dist/jinaga-server.js +6 -4
  26. package/dist/jinaga-server.js.map +1 -1
  27. package/dist/memory/memory-feed-cache.d.ts +7 -0
  28. package/dist/memory/memory-feed-cache.js +17 -0
  29. package/dist/memory/memory-feed-cache.js.map +1 -0
  30. package/dist/postgres/postgres-store.d.ts +5 -2
  31. package/dist/postgres/postgres-store.js +53 -28
  32. package/dist/postgres/postgres-store.js.map +1 -1
  33. package/dist/postgres/specification-result-sql.d.ts +11 -4
  34. package/dist/postgres/specification-result-sql.js +302 -5
  35. package/dist/postgres/specification-result-sql.js.map +1 -1
  36. package/dist/postgres/specification-sql.d.ts +14 -3
  37. package/dist/postgres/specification-sql.js +233 -100
  38. package/dist/postgres/specification-sql.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/authentication/authentication-device.ts +6 -0
  41. package/src/authentication/authentication-impl.ts +6 -3
  42. package/src/authentication/authentication-session.ts +11 -3
  43. package/src/authentication.ts +18 -5
  44. package/src/authorization/authorization-keystore.ts +14 -8
  45. package/src/http/feed-cache.ts +6 -0
  46. package/src/http/node-http.ts +5 -2
  47. package/src/http/router.ts +179 -31
  48. package/src/jinaga-server.ts +11 -9
  49. package/src/memory/memory-feed-cache.ts +15 -0
  50. package/src/postgres/postgres-store.ts +59 -31
  51. package/src/postgres/specification-result-sql.ts +450 -10
  52. package/src/postgres/specification-sql.ts +335 -115
  53. package/test/authorization/authorizationSpec.ts +11 -2
  54. package/test/postgres/postgresSpec.ts +31 -25
  55. package/test/postgres/postgresSpecificationSpec.ts +40 -68
  56. package/dist/postgres/query-description-builder.d.ts +0 -13
  57. package/dist/postgres/query-description-builder.js +0 -104
  58. package/dist/postgres/query-description-builder.js.map +0 -1
  59. package/dist/postgres/query-description.d.ts +0 -71
  60. package/dist/postgres/query-description.js +0 -256
  61. package/dist/postgres/query-description.js.map +0 -1
  62. package/src/postgres/query-description-builder.ts +0 -116
  63. package/src/postgres/query-description.ts +0 -385
@@ -1,138 +1,358 @@
1
- import { FactBookmark, FactReference, Match, Projection, Specification } from "jinaga";
1
+ import {
2
+ buildFeeds,
3
+ EdgeDescription as FeedEdgeDescription,
4
+ FactReference,
5
+ Feed,
6
+ InputDescription as FeedInputDescription,
7
+ NotExistsConditionDescription as FeedNotExistsConditionDescription,
8
+ OutputDescription as FeedOutputDescription,
9
+ Specification,
10
+ } from "jinaga";
2
11
 
3
- import { FactTypeMap, getFactTypeId, RoleMap } from "./maps";
4
- import { FactDescription, InputDescription, QueryDescription, SpecificationSqlQuery } from "./query-description";
5
- import { QueryDescriptionBuilder } from "./query-description-builder";
12
+ import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
6
13
 
7
- type FactByIdentifier = {
8
- [identifier: string]: FactDescription;
14
+ interface SpecificationLabel {
15
+ type: string;
16
+ index: number;
17
+ }
18
+
19
+ interface SpecificationSqlQuery {
20
+ sql: string;
21
+ parameters: (string | number | number[])[];
22
+ labels: SpecificationLabel[];
23
+ bookmark: string;
9
24
  };
10
25
 
11
- class DescriptionBuilder extends QueryDescriptionBuilder {
26
+ interface InputDescription {
27
+ factIndex: number;
28
+ factTypeId: number;
29
+ factHash: string;
30
+ factTypeParameter: number;
31
+ factHashParameter: number;
32
+ }
33
+ interface OutputDescription {
34
+ type: string;
35
+ factIndex: number;
36
+ }
37
+ interface EdgeDescription {
38
+ edgeIndex: number;
39
+ predecessorFactIndex: number;
40
+ successorFactIndex: number;
41
+ roleParameter: number;
42
+ }
43
+ interface NotExistsConditionDescription {
44
+ edges: EdgeDescription[];
45
+ notExistsConditions: NotExistsConditionDescription[];
46
+ }
47
+ class QueryDescription {
12
48
  constructor(
13
- factTypes: FactTypeMap,
14
- roleMap: RoleMap
15
- ) { super(factTypes, roleMap); }
16
-
17
- public buildDescriptions(start: FactReference[], specification: Specification): QueryDescription[] {
18
- // Verify that the number of start facts equals the number of inputs
19
- if (start.length !== specification.given.length) {
20
- throw new Error(`The number of start facts (${start.length}) does not equal the number of inputs (${specification.given.length})`);
21
- }
22
- // Verify that the input type matches the start fact type
23
- for (let i = 0; i < start.length; i++) {
24
- if (start[i].type !== specification.given[i].type) {
25
- throw new Error(`The type of start fact ${i} (${start[i].type}) does not match the type of input ${i} (${specification.given[i].type})`);
49
+ private readonly inputs: InputDescription[],
50
+ private readonly parameters: (string | number)[],
51
+ private readonly outputs: OutputDescription[],
52
+ private readonly edges: EdgeDescription[],
53
+ private readonly notExistsConditions: NotExistsConditionDescription[] = []
54
+ ) {}
55
+
56
+ generateSqlQuery(bookmark: string, limit: number): SpecificationSqlQuery {
57
+ const hashes = this.outputs
58
+ .map(output => `f${output.factIndex}.hash as hash${output.factIndex}`)
59
+ .join(", ");
60
+ const factIds = this.outputs
61
+ .map(output => `f${output.factIndex}.fact_id`)
62
+ .join(", ");
63
+ const firstEdge = this.edges[0];
64
+ const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
65
+ const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
66
+ const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact.factIndex;
67
+ const writtenFactIndexes = new Set<number>().add(firstFactIndex);
68
+ const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
69
+ const inputWhereClauses = this.inputs
70
+ .filter(input => input.factTypeParameter !== 0)
71
+ .map(input => `f${input.factIndex}.fact_type_id = $${input.factTypeParameter} AND f${input.factIndex}.hash = $${input.factHashParameter}`)
72
+ .join(" AND ");
73
+ const notExistsWhereClauses = this.notExistsConditions
74
+ .map(notExistsWhereClause => ` AND NOT EXISTS (${generateNotExistsWhereClause(notExistsWhereClause, writtenFactIndexes)})`)
75
+ .join("");
76
+ const bookmarkParameter = this.parameters.length + 1;
77
+ const limitParameter = bookmarkParameter + 1;
78
+ const sql = `SELECT ${hashes}, sort(array[${factIds}], 'desc') as bookmark FROM public.fact f${firstFactIndex}${joins.join("")} WHERE ${inputWhereClauses}${notExistsWhereClauses} AND sort(array[${factIds}], 'desc') > $${bookmarkParameter} ORDER BY bookmark ASC LIMIT $${limitParameter}`;
79
+ const bookmarkValue: number[] = parseBookmark(bookmark);
80
+ return {
81
+ sql,
82
+ parameters: [...this.parameters, bookmarkValue, limit],
83
+ labels: this.outputs.map(output => ({
84
+ type: output.type,
85
+ index: output.factIndex
86
+ })),
87
+ bookmark: "[]"
88
+ };
89
+ }
90
+ }
91
+
92
+ function generateJoins(edges: EdgeDescription[], writtenFactIndexes: Set<number>) {
93
+ const joins: string[] = [];
94
+ edges.forEach(edge => {
95
+ if (writtenFactIndexes.has(edge.predecessorFactIndex)) {
96
+ if (writtenFactIndexes.has(edge.successorFactIndex)) {
97
+ joins.push(
98
+ ` JOIN public.edge e${edge.edgeIndex}` +
99
+ ` ON e${edge.edgeIndex}.predecessor_fact_id = f${edge.predecessorFactIndex}.fact_id` +
100
+ ` AND e${edge.edgeIndex}.successor_fact_id = f${edge.successorFactIndex}.fact_id` +
101
+ ` AND e${edge.edgeIndex}.role_id = $${edge.roleParameter}`
102
+ );
26
103
  }
104
+ else {
105
+ joins.push(
106
+ ` JOIN public.edge e${edge.edgeIndex}` +
107
+ ` ON e${edge.edgeIndex}.predecessor_fact_id = f${edge.predecessorFactIndex}.fact_id` +
108
+ ` AND e${edge.edgeIndex}.role_id = $${edge.roleParameter}`
109
+ );
110
+ joins.push(
111
+ ` JOIN public.fact f${edge.successorFactIndex}` +
112
+ ` ON f${edge.successorFactIndex}.fact_id = e${edge.edgeIndex}.successor_fact_id`
113
+ );
114
+ writtenFactIndexes.add(edge.successorFactIndex);
115
+ }
116
+ }
117
+ else if (writtenFactIndexes.has(edge.successorFactIndex)) {
118
+ joins.push(
119
+ ` JOIN public.edge e${edge.edgeIndex}` +
120
+ ` ON e${edge.edgeIndex}.successor_fact_id = f${edge.successorFactIndex}.fact_id` +
121
+ ` AND e${edge.edgeIndex}.role_id = $${edge.roleParameter}`
122
+ );
123
+ joins.push(
124
+ ` JOIN public.fact f${edge.predecessorFactIndex}` +
125
+ ` ON f${edge.predecessorFactIndex}.fact_id = e${edge.edgeIndex}.predecessor_fact_id`
126
+ );
127
+ writtenFactIndexes.add(edge.predecessorFactIndex);
27
128
  }
129
+ else {
130
+ throw new Error("Neither predecessor nor successor fact has been written");
131
+ }
132
+ });
133
+ return joins;
134
+ }
28
135
 
29
- // Allocate a fact table for each given.
30
- // While the fact type and hash parameters are zero, the join will not be written.
31
- const inputs: InputDescription[] = specification.given
32
- .map((label, i) => ({
33
- label: label.name,
34
- factIndex: i+1,
35
- factTypeId: getFactTypeId(this.factTypes, label.type),
36
- factHash: start[i].hash,
37
- factTypeParameter: 0,
38
- factHashParameter: 0
39
- }));
40
- const facts: FactDescription[] = specification.given
41
- .map((label, i) => ({
42
- factIndex: i+1,
43
- type: label.type
44
- }));
45
- const givenFacts = specification.given.reduce((knownFacts, label, i) => ({
46
- ...knownFacts,
47
- [label.name]: facts[i]
48
- }), {} as FactByIdentifier);
49
-
50
-
51
- // The QueryDescription is an immutable data type.
52
- // Initialize it with the inputs and facts.
53
- // The DescriptionBuilder will branch at various points, and
54
- // build on the current query description along each branch.
55
- const initialQueryDescription = new QueryDescription(inputs, [], [], facts, [], []);
56
- const { queryDescriptions, knownFacts } = this.addEdges(initialQueryDescription, givenFacts, [], "", specification.matches);
57
-
58
- // The final query description represents the complete tuple.
59
- // Build projections onto that one.
60
- const finalQueryDescription = queryDescriptions[queryDescriptions.length - 1];
61
- if (Array.isArray(specification.childProjections)) {
62
- const queryDescriptionsWithProjections = this.addProjections(finalQueryDescription, knownFacts, specification.childProjections);
63
- return [ ...queryDescriptions, ...queryDescriptionsWithProjections ];
136
+ function generateNotExistsWhereClause(notExistsWhereClause: NotExistsConditionDescription, outerFactIndexes: Set<number>): string {
137
+ const firstEdge = notExistsWhereClause.edges[0];
138
+ const writtenFactIndexes = new Set<number>(outerFactIndexes);
139
+ const firstJoin: string[] = [];
140
+ const whereClause: string[] = [];
141
+ if (writtenFactIndexes.has(firstEdge.predecessorFactIndex)) {
142
+ if (writtenFactIndexes.has(firstEdge.successorFactIndex)) {
143
+ throw new Error("Not yet implemented");
64
144
  }
65
145
  else {
66
- return queryDescriptions;
146
+ whereClause.push(
147
+ `e${firstEdge.edgeIndex}.predecessor_fact_id = f${firstEdge.predecessorFactIndex}.fact_id` +
148
+ ` AND e${firstEdge.edgeIndex}.role_id = $${firstEdge.roleParameter}`
149
+ );
150
+ firstJoin.push(
151
+ ` JOIN public.fact f${firstEdge.successorFactIndex}` +
152
+ ` ON f${firstEdge.successorFactIndex}.fact_id = e${firstEdge.edgeIndex}.successor_fact_id`
153
+ );
154
+ writtenFactIndexes.add(firstEdge.successorFactIndex);
67
155
  }
68
156
  }
157
+ else if (writtenFactIndexes.has(firstEdge.successorFactIndex)) {
158
+ whereClause.push(
159
+ `e${firstEdge.edgeIndex}.successor_fact_id = f${firstEdge.successorFactIndex}.fact_id` +
160
+ ` AND e${firstEdge.edgeIndex}.role_id = $${firstEdge.roleParameter}`
161
+ );
162
+ firstJoin.push(
163
+ ` JOIN public.fact f${firstEdge.predecessorFactIndex}` +
164
+ ` ON f${firstEdge.predecessorFactIndex}.fact_id = e${firstEdge.edgeIndex}.predecessor_fact_id`
165
+ );
166
+ writtenFactIndexes.add(firstEdge.predecessorFactIndex);
167
+ }
168
+ else {
169
+ throw new Error("Neither predecessor nor successor fact has been written");
170
+ }
171
+ const tailJoins: string[] = generateJoins(notExistsWhereClause.edges.slice(1), writtenFactIndexes);
172
+ const joins = firstJoin.concat(tailJoins);
173
+ return `SELECT 1 FROM public.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}`;
174
+ }
175
+
176
+ function parseBookmark(bookmark: string): number[] {
177
+ if (bookmark === undefined || bookmark === null || bookmark === "") {
178
+ return [];
179
+ }
180
+ else {
181
+ return bookmark.split(".").map(str => parseInt(str));
182
+ }
183
+ }
184
+
185
+ class DescriptionBuilder {
186
+ constructor(
187
+ private factTypes: FactTypeMap,
188
+ private roleMap: RoleMap
189
+ ) { }
190
+
191
+ isSatisfiable(feed: Feed, edges: FeedEdgeDescription[]): boolean {
192
+ for (const edge of edges) {
193
+ const successor = feed.facts.find(f => f.factIndex === edge.successorFactIndex);
194
+ if (!successor) {
195
+ return false;
196
+ }
197
+
198
+ const predecessor = feed.facts.find(f => f.factIndex === edge.predecessorFactIndex);
199
+ if (!predecessor) {
200
+ return false;
201
+ }
202
+
203
+ const successorFactTypeId = getFactTypeId(this.factTypes, successor.factType);
204
+ if (!successorFactTypeId) {
205
+ return false;
206
+ }
207
+
208
+ if (!getRoleId(this.roleMap, successorFactTypeId, edge.roleName)) {
209
+ return false;
210
+ }
69
211
 
70
- private addEdges(queryDescription: QueryDescription, knownFacts: FactByIdentifier, path: number[], prefix: string, matches: Match[]): { queryDescriptions: QueryDescription[], knownFacts: FactByIdentifier } {
71
- const queryDescriptions: QueryDescription[] = [];
72
- for (const match of matches) {
73
- for (const condition of match.conditions) {
74
- if (condition.type === "path") {
75
- ({queryDescription, knownFacts} = this.addPathCondition(queryDescription, knownFacts, path, match.unknown, prefix, condition));
76
- }
77
- else if (condition.type === "existential") {
78
- if (condition.exists) {
79
- // Include the edges of the existential condition into the current
80
- // query description.
81
- const { queryDescriptions: newQueryDescriptions } = this.addEdges(queryDescription, knownFacts, path, prefix, condition.matches);
82
- const last = newQueryDescriptions.length - 1;
83
- queryDescriptions.push(...newQueryDescriptions.slice(0, last));
84
- queryDescription = newQueryDescriptions[last];
85
- }
86
- else {
87
- // Branch from the current query description and follow the
88
- // edges of the existential condition.
89
- // This will produce tuples that prove the condition false.
90
- const { queryDescriptions: newQueryDescriptions } = this.addEdges(queryDescription, knownFacts, path, prefix, condition.matches);
91
-
92
- // Then apply the where clause and continue with the tuple where it is true.
93
- // The path describes which not-exists condition we are currently building on.
94
- // Because the path is not empty, labeled facts will be included in the output.
95
- const { query: queryDescriptionWithNotExist, path: conditionalPath } = queryDescription.withNotExistsCondition(path);
96
- const { queryDescriptions: newQueryDescriptionsWithNotExists } = this.addEdges(queryDescriptionWithNotExist, knownFacts, conditionalPath, prefix, condition.matches);
97
- const last = newQueryDescriptionsWithNotExists.length - 1;
98
- const queryDescriptionConditional = newQueryDescriptionsWithNotExists[last];
99
-
100
- // If the negative existential condition is not satisfiable, then
101
- // that means that the condition will always be true.
102
- // We can therefore skip the branch for the negative existential condition.
103
- if (queryDescriptionConditional.isSatisfiable()) {
104
- queryDescriptions.push(...newQueryDescriptions);
105
- queryDescriptions.push(...newQueryDescriptionsWithNotExists.slice(0, last));
106
- queryDescription = queryDescriptionConditional;
107
- }
108
- }
109
- }
212
+ const predecessorFactTypeId = getFactTypeId(this.factTypes, predecessor.factType);
213
+ if (!predecessorFactTypeId) {
214
+ return false;
110
215
  }
111
216
  }
112
- queryDescriptions.push(queryDescription);
113
- return { queryDescriptions, knownFacts };
217
+
218
+ return true;
114
219
  }
115
220
 
116
- addProjections(queryDescription: QueryDescription, knownFacts: FactByIdentifier, projections: Projection[]): QueryDescription[] {
117
- const queryDescriptions: QueryDescription[] = [];
118
- projections.forEach(projection => {
119
- if (projection.type === "specification") {
120
- // Produce more facts in the tuple, and prefix the labels with the projection name.
121
- const prefix = projection.name + ".";
122
- const { queryDescriptions: queryDescriptionsWithEdges } = this.addEdges(queryDescription, knownFacts, [], prefix, projection.matches);
123
- queryDescriptions.push(...queryDescriptionsWithEdges);
124
- }
125
- });
126
- return queryDescriptions;
221
+ buildDescription(feed: Feed): QueryDescription {
222
+ const parameters: (string | number)[] = [];
223
+ function addParameter(value: string | number) {
224
+ parameters.push(value);
225
+ return parameters.length;
226
+ }
227
+
228
+ // Allocate parameters for the inputs.
229
+ const inputs: InputDescription[] = feed.inputs.map(input =>
230
+ this.buildInputDescription(feed, input, addParameter)
231
+ );
232
+
233
+ // Allocate parameters for the edge roles.
234
+ const edges: EdgeDescription[] = feed.edges.map(edge =>
235
+ this.buildEdgeDescription(feed, edge, addParameter)
236
+ );
237
+
238
+ // Allocate parameters for the conditional roles.
239
+ const satisfiableNotExistsConditions = feed.notExistsConditions.filter(condition =>
240
+ this.isSatisfiable(feed, condition.edges)
241
+ );
242
+ const notExistsConditions: NotExistsConditionDescription[] = satisfiableNotExistsConditions.map(condition =>
243
+ this.buildNotExistsConditionDescription(feed, condition, addParameter)
244
+ );
245
+
246
+ const outputs: OutputDescription[] = feed.outputs.map(output =>
247
+ this.buildOutputDescription(feed, output)
248
+ );
249
+
250
+ return new QueryDescription(
251
+ inputs,
252
+ parameters,
253
+ outputs,
254
+ edges,
255
+ notExistsConditions
256
+ );
257
+ }
258
+
259
+ private buildInputDescription(feed: Feed, input: FeedInputDescription, addParameter: (value: string | number) => number): InputDescription {
260
+ const fact = feed.facts.find(f => f.factIndex === input.factIndex);
261
+ if (!fact) {
262
+ throw new Error(`Fact not found: ${input.factIndex}`);
263
+ }
264
+
265
+ const factTypeId = getFactTypeId(this.factTypes, fact.factType);
266
+ if (!factTypeId) {
267
+ throw new Error(`Fact type not found: ${fact.factType}`);
268
+ }
269
+
270
+ const factTypeParameter = addParameter(factTypeId);
271
+ const factHashParameter = addParameter(input.factHash);
272
+ return {
273
+ factIndex: input.factIndex,
274
+ factTypeId,
275
+ factTypeParameter,
276
+ factHash: input.factHash,
277
+ factHashParameter
278
+ };
279
+ }
280
+
281
+ private buildEdgeDescription(feed: Feed, edge: FeedEdgeDescription, addParameter: (value: string | number) => number) {
282
+ const fact = feed.facts.find(f => f.factIndex === edge.successorFactIndex);
283
+ if (!fact) {
284
+ throw new Error(`Fact not found: ${edge.successorFactIndex}`);
285
+ }
286
+
287
+ const factTypeId = getFactTypeId(this.factTypes, fact.factType);
288
+ if (!factTypeId) {
289
+ throw new Error(`Fact type not found: ${fact.factType}`);
290
+ }
291
+
292
+ const roleId = getRoleId(this.roleMap, factTypeId, edge.roleName);
293
+ if (!roleId) {
294
+ throw new Error(`Role not found: ${fact.factType}.${edge.roleName}`);
295
+ }
296
+
297
+ const roleParameter = addParameter(roleId);
298
+ return <EdgeDescription>{
299
+ edgeIndex: edge.edgeIndex,
300
+ predecessorFactIndex: edge.predecessorFactIndex,
301
+ successorFactIndex: edge.successorFactIndex,
302
+ roleParameter
303
+ };
304
+ }
305
+
306
+ private buildOutputDescription(feed: Feed, output: FeedOutputDescription) {
307
+ const fact = feed.facts.find(f => f.factIndex === output.factIndex);
308
+ if (!fact) {
309
+ throw new Error(`Fact not found: ${output.factIndex}`);
310
+ }
311
+
312
+ return <OutputDescription>{
313
+ factIndex: output.factIndex,
314
+ type: fact.factType
315
+ };
316
+ }
317
+
318
+ private buildNotExistsConditionDescription(feed: Feed, condition: FeedNotExistsConditionDescription, addParameter: (value: string | number) => number): NotExistsConditionDescription {
319
+ const edges = condition.edges.map(edge =>
320
+ this.buildEdgeDescription(feed, edge, addParameter)
321
+ );
322
+ const notExistsConditions = condition.notExistsConditions.map(condition =>
323
+ this.buildNotExistsConditionDescription(feed, condition, addParameter)
324
+ );
325
+
326
+ return {
327
+ edges,
328
+ notExistsConditions
329
+ }
127
330
  }
128
331
  }
129
332
 
130
- export function sqlFromSpecification(start: FactReference[], bookmarks: FactBookmark[], limit: number, specification: Specification, factTypes: Map<string, number>, roleMap: Map<number, Map<string, number>>): SpecificationSqlQuery[] {
333
+ export function sqlFromSpecification(start: FactReference[], bookmarks: string[], limit: number, specification: Specification, factTypes: Map<string, number>, roleMap: Map<number, Map<string, number>>): SpecificationSqlQuery[] {
334
+ const feeds = buildFeeds(start, specification);
131
335
  const descriptionBuilder = new DescriptionBuilder(factTypes, roleMap);
132
- const descriptions = descriptionBuilder.buildDescriptions(start, specification);
336
+ const feedAndBookmark = feeds.map((feed, index) => ({
337
+ feed,
338
+ bookmark: bookmarks[index]
339
+ }));
340
+ const satisfiableFeedsAndBookmarks = feedAndBookmark.filter(fb =>
341
+ descriptionBuilder.isSatisfiable(fb.feed, fb.feed.edges));
342
+ const sqlQueries = satisfiableFeedsAndBookmarks.map(fb => {
343
+ const description = descriptionBuilder.buildDescription(fb.feed);
344
+ const sql = description.generateSqlQuery(fb.bookmark, limit);
345
+ return sql;
346
+ });
347
+ return sqlQueries;
348
+ }
133
349
 
134
- // Only generate SQL for satisfiable queries.
135
- return descriptions
136
- .filter(description => description.isSatisfiable())
137
- .map(description => description.generateSqlQuery(bookmarks, limit));
350
+ export function sqlFromFeed(feed: Feed, bookmark: string, limit: number, factTypes: Map<string, number>, roleMap: Map<number, Map<string, number>>): SpecificationSqlQuery | null {
351
+ const descriptionBuilder = new DescriptionBuilder(factTypes, roleMap);
352
+ if (!descriptionBuilder.isSatisfiable(feed, feed.edges)) {
353
+ return null;
354
+ }
355
+ const description = descriptionBuilder.buildDescription(feed);
356
+ const sql = description.generateSqlQuery(bookmark, limit);
357
+ return sql;
138
358
  }
@@ -1,4 +1,13 @@
1
- import { AuthorizationRules, dehydrateFact, ensure, FactRecord, FeedImpl, hydrate, Jinaga as j, MemoryStore } from "jinaga";
1
+ import {
2
+ AuthorizationRules,
3
+ dehydrateFact,
4
+ ensure,
5
+ FactRecord,
6
+ hydrate,
7
+ Jinaga as j,
8
+ MemoryStore,
9
+ ObservableSourceImpl,
10
+ } from "jinaga";
2
11
 
3
12
  import { AuthorizationKeystore } from "../../src/authorization/authorization-keystore";
4
13
  import { MemoryKeystore } from "../../src/memory/memory-keystore";
@@ -152,7 +161,7 @@ function givenStorage() {
152
161
  function givenAuthorizationWithStorage(storage: MemoryStore) {
153
162
  const keystore = new MemoryKeystore();
154
163
  keystore.getOrCreateUserFact(givenMockUserIdentity());
155
- const feed = new FeedImpl(storage);
164
+ const feed = new ObservableSourceImpl(storage);
156
165
  const authorizationRules = new AuthorizationRules()
157
166
  .any('Hashtag')
158
167
  .no('Jinaga.User')