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
@@ -63,7 +63,7 @@ export class NodeHttpConnection implements HttpConnection {
63
63
  if (res.statusCode === 403) {
64
64
  reject(response);
65
65
  }
66
- else if (res.statusCode>= 400) {
66
+ else if (!res.statusCode || res.statusCode>= 400) {
67
67
  resolve({
68
68
  result: "retry",
69
69
  error: response
@@ -100,7 +100,10 @@ export class NodeHttpConnection implements HttpConnection {
100
100
  }
101
101
  }
102
102
 
103
- function isJsonResult(contentType: string) {
103
+ function isJsonResult(contentType: string | undefined) {
104
+ if (!contentType) {
105
+ return false;
106
+ }
104
107
  const json = contentType.split(";").indexOf("application/json");
105
108
  return json >= 0;
106
109
  }
@@ -1,8 +1,12 @@
1
1
  import { Handler, Router } from "express";
2
2
  import {
3
3
  Authorization,
4
+ buildFeeds,
5
+ computeObjectHash,
4
6
  Declaration,
5
7
  FactRecord,
8
+ FactReference,
9
+ Feed,
6
10
  Forbidden,
7
11
  fromDescriptiveString,
8
12
  LoadMessage,
@@ -11,20 +15,48 @@ import {
11
15
  QueryMessage,
12
16
  QueryResponse,
13
17
  SaveMessage,
14
- SaveResponse,
18
+ Specification,
15
19
  SpecificationParser,
16
20
  Trace,
17
21
  UserIdentity,
18
22
  } from "jinaga";
23
+ import { FeedResponse, FeedsResponse } from "jinaga/dist/http/messages";
19
24
 
20
- function get<U>(method: ((req: RequestUser) => Promise<U>)): Handler {
25
+ import { FeedCache } from "./feed-cache";
26
+
27
+ interface ParsedQs { [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[] }
28
+
29
+ function get<U>(method: ((req: RequestUser, params?: { [key: string]: string }, query?: ParsedQs) => Promise<U>)): Handler {
30
+ return (req, res, next) => {
31
+ const user = <RequestUser>req.user;
32
+ method(user, req.params, req.query)
33
+ .then(response => {
34
+ if (!response) {
35
+ res.sendStatus(404);
36
+ next();
37
+ }
38
+ else {
39
+ res.setHeader('Content-Type', 'application/json');
40
+ res.send(JSON.stringify(response));
41
+ next();
42
+ }
43
+ })
44
+ .catch(error => {
45
+ Trace.error(error);
46
+ res.sendStatus(500);
47
+ next();
48
+ });
49
+ };
50
+ }
51
+
52
+ function getAuthenticate<U>(method: ((req: RequestUser, params?: { [key: string]: string }) => Promise<U>)): Handler {
21
53
  return (req, res, next) => {
22
54
  const user = <RequestUser>req.user;
23
55
  if (!user) {
24
56
  res.sendStatus(401);
25
57
  }
26
58
  else {
27
- method(user)
59
+ method(user, req.params)
28
60
  .then(response => {
29
61
  res.setHeader('Content-Type', 'application/json');
30
62
  res.send(JSON.stringify(response));
@@ -39,18 +71,24 @@ function get<U>(method: ((req: RequestUser) => Promise<U>)): Handler {
39
71
  };
40
72
  }
41
73
 
42
- function post<T, U>(method: (user: RequestUser, message: T) => Promise<U>): Handler {
74
+ function post<T, U>(method: (user: RequestUser, message: T, params?: { [key: string]: string }) => Promise<U>): Handler {
43
75
  return (req, res, next) => {
44
76
  const user = <RequestUser>req.user;
45
77
  const message = <T>req.body;
46
78
  if (!message) {
47
79
  throw new Error('Ensure that you have called app.use(express.json()).');
48
80
  }
49
- method(user, message)
81
+ method(user, message, req.params)
50
82
  .then(response => {
51
- res.setHeader('Content-Type', 'application/json');
52
- res.send(JSON.stringify(response));
53
- next();
83
+ if (!response) {
84
+ res.sendStatus(404);
85
+ next();
86
+ }
87
+ else {
88
+ res.setHeader('Content-Type', 'application/json');
89
+ res.send(JSON.stringify(response));
90
+ next();
91
+ }
54
92
  })
55
93
  .catch(error => {
56
94
  if (error instanceof Forbidden) {
@@ -100,6 +138,65 @@ function postString<U>(method: (user: RequestUser, message: string) => Promise<U
100
138
  };
101
139
  }
102
140
 
141
+ function postCreate<T>(method: (user: RequestUser, message: T) => Promise<void>): Handler {
142
+ return (req, res, next) => {
143
+ const user = <RequestUser>req.user;
144
+ const message = <T>req.body;
145
+ if (!message) {
146
+ throw new Error('Ensure that you have called app.use(express.json()).');
147
+ }
148
+ method(user, message)
149
+ .then(_ => {
150
+ res.sendStatus(201);
151
+ next();
152
+ })
153
+ .catch(error => {
154
+ if (error instanceof Forbidden) {
155
+ Trace.warn(error.message);
156
+ res.setHeader('Content-Type', 'text/plain');
157
+ res.status(403).send(error.message);
158
+ }
159
+ else {
160
+ Trace.error(error);
161
+ res.setHeader('Content-Type', 'text/plain');
162
+ res.sendStatus(500).send(error.message);
163
+ }
164
+ next();
165
+ });
166
+ };
167
+ }
168
+
169
+ function postStringCreate(method: (user: RequestUser, message: string) => Promise<void>): Handler {
170
+ return (req, res, next) => {
171
+ const user = <RequestUser>req.user;
172
+ const input = <string>req.body;
173
+ if (!input || typeof(input) !== 'string') {
174
+ res.setHeader('Content-Type', 'text/plain');
175
+ res.status(500).send('Expected Content-Type text/plain. Ensure that you have called app.use(express.text()).');
176
+ }
177
+ else {
178
+ method(user, input)
179
+ .then(_ => {
180
+ res.sendStatus(201);
181
+ next();
182
+ })
183
+ .catch(error => {
184
+ if (error instanceof Forbidden) {
185
+ Trace.warn(error.message);
186
+ res.setHeader('Content-Type', 'text/plain');
187
+ res.status(403).send(error.message);
188
+ }
189
+ else {
190
+ Trace.error(error);
191
+ res.setHeader('Content-Type', 'text/plain');
192
+ res.status(400).send(error.message);
193
+ }
194
+ next();
195
+ });
196
+ }
197
+ };
198
+ }
199
+
103
200
  function serializeUserIdentity(user: RequestUser) : UserIdentity {
104
201
  if (!user) {
105
202
  return null;
@@ -119,16 +216,17 @@ export interface RequestUser {
119
216
  export class HttpRouter {
120
217
  handler: Handler;
121
218
 
122
- constructor(private authorization: Authorization) {
219
+ constructor(private authorization: Authorization, private feedCache: FeedCache) {
123
220
  const router = Router();
124
- router.get('/login', get(user => this.login(user)));
221
+ router.get('/login', getAuthenticate(user => this.login(user)));
125
222
  router.post('/query', post((user, queryMessage: QueryMessage) => this.query(user, queryMessage)));
126
223
  router.post('/load', post((user, loadMessage: LoadMessage) => this.load(user, loadMessage)));
127
- router.post('/save', post((user, saveMessage: SaveMessage) => this.save(user, saveMessage)));
224
+ router.post('/save', postCreate((user, saveMessage: SaveMessage) => this.save(user, saveMessage)));
128
225
 
129
226
  router.post('/read', postString((user, input: string) => this.read(user, input)));
130
- router.post('/write', post((user, input: string) => this.write(user, input)));
131
- router.post('/feed', post((user, input: string) => this.feed(user, input)));
227
+ router.post('/write', postStringCreate((user, input: string) => this.write(user, input)));
228
+ router.post('/feeds', post((user, input: string) => this.feeds(user, input)));
229
+ router.get('/feeds/:hash', get((user, params, query) => this.feed(user, params, query)));
132
230
 
133
231
  this.handler = router;
134
232
  }
@@ -161,34 +259,25 @@ export class HttpRouter {
161
259
  };
162
260
  }
163
261
 
164
- private async save(user: RequestUser, saveMessage: SaveMessage) : Promise<SaveResponse> {
262
+ private async save(user: RequestUser, saveMessage: SaveMessage) : Promise<void> {
165
263
  const userIdentity = serializeUserIdentity(user);
166
264
  await this.authorization.save(userIdentity, saveMessage.facts);
167
- return {};
168
265
  }
169
266
 
170
267
  private async read(user: RequestUser, input: string): Promise<any[]> {
171
268
  const knownFacts = await this.getKnownFacts(user);
172
269
  const parser = new SpecificationParser(input);
173
270
  parser.skipWhitespace();
174
- var declaration = parser.parseDeclaration(knownFacts);
175
- var specification = parser.parseSpecification();
176
-
177
- // Select starting facts that match the inputs
178
- const start = specification.given.map(input => {
179
- const declaredFact = declaration.find(d => d.name === input.name);
180
- if (!declaredFact) {
181
- throw new Error(`No fact named ${input.name} was declared`);
182
- }
183
- return declaredFact.declared.reference;
184
- });
271
+ const declaration = parser.parseDeclaration(knownFacts);
272
+ const specification = parser.parseSpecification();
273
+ const start = this.selectStart(specification, declaration);
185
274
 
186
275
  const userIdentity = serializeUserIdentity(user);
187
276
  const results = await this.authorization.read(userIdentity, start, specification);
188
277
  return results;
189
278
  }
190
279
 
191
- private async write(user: RequestUser, input: string): Promise<SaveResponse> {
280
+ private async write(user: RequestUser, input: string): Promise<void> {
192
281
  const knownFacts = await this.getKnownFacts(user);
193
282
  const parser = new SpecificationParser(input);
194
283
  parser.skipWhitespace();
@@ -204,11 +293,54 @@ export class HttpRouter {
204
293
 
205
294
  const userIdentity = serializeUserIdentity(user);
206
295
  await this.authorization.save(userIdentity, factRecords);
207
- return {};
208
296
  }
209
297
 
210
- private feed(user: RequestUser, input: string): Promise<string> {
211
- throw new Error("Method not implemented.");
298
+ private async feeds(user: RequestUser, input: string): Promise<FeedsResponse> {
299
+ const knownFacts = await this.getKnownFacts(user);
300
+ const parser = new SpecificationParser(input);
301
+ parser.skipWhitespace();
302
+ const declaration = parser.parseDeclaration(knownFacts);
303
+ const specification = parser.parseSpecification();
304
+ const start = this.selectStart(specification, declaration);
305
+
306
+ const feedDefinitions = buildFeeds(start, specification).map(feed => ({
307
+ hash: urlSafeHash(feed),
308
+ feed
309
+ }));
310
+ // Store all feeds in the cache.
311
+ for (const feedDefinition of feedDefinitions) {
312
+ await this.feedCache.storeFeed(feedDefinition.hash, feedDefinition.feed);
313
+ }
314
+
315
+ return {
316
+ feeds: feedDefinitions.map(f => f.hash)
317
+ }
318
+ }
319
+
320
+ private async feed(user: RequestUser, params: { [key: string]: string }, query: ParsedQs): Promise<FeedResponse | null> {
321
+ const feedHash = params["hash"];
322
+ if (!feedHash) {
323
+ return null;
324
+ }
325
+
326
+ const feed = await this.feedCache.getFeed(feedHash);
327
+ if (!feed) {
328
+ return null;
329
+ }
330
+
331
+ const bookmark = query["b"] as string ?? "";
332
+
333
+ const userIdentity = serializeUserIdentity(user);
334
+ const results = await this.authorization.feed(userIdentity, feed, bookmark);
335
+ // Return distinct fact references from all the tuples.
336
+ const references = results.tuples.flatMap(t => t.facts).filter((value, index, self) =>
337
+ self.findIndex(f => f.hash === value.hash && f.type === value.type) === index
338
+ );
339
+ const response: FeedResponse = {
340
+ references,
341
+ bookmark: results.bookmark
342
+ };
343
+ return response;
212
344
  }
213
345
 
214
346
  private async getKnownFacts(user: RequestUser): Promise<Declaration> {
@@ -234,4 +366,20 @@ export class HttpRouter {
234
366
  return [];
235
367
  }
236
368
  }
237
- }
369
+
370
+ private selectStart(specification: Specification, declaration: Declaration) : FactReference[] {
371
+ // Select starting facts that match the inputs
372
+ return specification.given.map(input => {
373
+ const declaredFact = declaration.find(d => d.name === input.name);
374
+ if (!declaredFact) {
375
+ throw new Error(`No fact named ${input.name} was declared`);
376
+ }
377
+ return declaredFact.declared.reference;
378
+ });
379
+ }
380
+ }
381
+
382
+ function urlSafeHash(feed: Feed): string {
383
+ const base64 = computeObjectHash(feed);
384
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
385
+ }
@@ -4,11 +4,11 @@ import {
4
4
  AuthorizationNoOp,
5
5
  AuthorizationRules,
6
6
  Cache,
7
- Feed,
8
- FeedImpl,
9
7
  Fork,
10
8
  Jinaga,
11
9
  MemoryStore,
10
+ ObservableSource,
11
+ ObservableSourceImpl,
12
12
  PassThroughFork,
13
13
  Storage,
14
14
  SyncStatusNotifier,
@@ -23,6 +23,7 @@ import { AuthorizationKeystore } from "./authorization/authorization-keystore";
23
23
  import { NodeHttpConnection } from "./http/node-http";
24
24
  import { HttpRouter, RequestUser } from "./http/router";
25
25
  import { Keystore } from "./keystore";
26
+ import { MemoryFeedCache } from "./memory/memory-feed-cache";
26
27
  import { PostgresKeystore } from "./postgres/postgres-keystore";
27
28
  import { PostgresStore } from "./postgres/postgres-store";
28
29
 
@@ -51,12 +52,13 @@ export class JinagaServer {
51
52
  static create(config: JinagaServerConfig): JinagaServerInstance {
52
53
  const syncStatusNotifier = new SyncStatusNotifier();
53
54
  const store = createStore(config);
54
- const feed = new FeedImpl(store);
55
- const fork = createFork(config, feed, syncStatusNotifier);
55
+ const source = new ObservableSourceImpl(store);
56
+ const fork = createFork(config, source, syncStatusNotifier);
56
57
  const keystore = new PostgresKeystore(config.pgKeystore);
57
58
  const authorizationRules = config.authorization ? config.authorization(new AuthorizationRules()) : null;
58
59
  const authorization = createAuthorization(authorizationRules, fork, keystore);
59
- const router = new HttpRouter(authorization);
60
+ const feedCache = new MemoryFeedCache();
61
+ const router = new HttpRouter(authorization, feedCache);
60
62
  const authentication = new AuthenticationDevice(fork, keystore, localDeviceIdentity);
61
63
  const memory = new MemoryStore();
62
64
  const j: Jinaga = new Jinaga(authentication, memory, syncStatusNotifier);
@@ -69,7 +71,7 @@ export class JinagaServer {
69
71
  handler: router.handler,
70
72
  j,
71
73
  withSession: (req, callback) => {
72
- return withSession(feed, keystore, authorizationRules, req, callback);
74
+ return withSession(source, keystore, authorizationRules, req, callback);
73
75
  },
74
76
  close
75
77
  }
@@ -87,7 +89,7 @@ function createStore(config: JinagaServerConfig): Storage {
87
89
  }
88
90
  }
89
91
 
90
- function createFork(config: JinagaServerConfig, feed: Feed, syncStatusNotifier: SyncStatusNotifier): Fork {
92
+ function createFork(config: JinagaServerConfig, feed: ObservableSource, syncStatusNotifier: SyncStatusNotifier): Fork {
91
93
  if (config.httpEndpoint) {
92
94
  const httpConnection = new NodeHttpConnection(config.httpEndpoint);
93
95
  const httpTimeoutSeconds = config.httpTimeoutSeconds || 5;
@@ -102,7 +104,7 @@ function createFork(config: JinagaServerConfig, feed: Feed, syncStatusNotifier:
102
104
  }
103
105
  }
104
106
 
105
- function createAuthorization(authorizationRules: AuthorizationRules | null, feed: Feed, keystore: Keystore | null): Authorization {
107
+ function createAuthorization(authorizationRules: AuthorizationRules | null, feed: ObservableSource, keystore: Keystore | null): Authorization {
106
108
  if (keystore) {
107
109
  const authorization = new AuthorizationKeystore(feed, keystore, authorizationRules);
108
110
  return authorization;
@@ -112,7 +114,7 @@ function createAuthorization(authorizationRules: AuthorizationRules | null, feed
112
114
  }
113
115
  }
114
116
 
115
- async function withSession(feed: Feed, keystore: Keystore, authorizationRules: AuthorizationRules | null, req: Request, callback: ((j: Jinaga) => Promise<void>)) {
117
+ async function withSession(feed: ObservableSource, keystore: Keystore, authorizationRules: AuthorizationRules | null, req: Request, callback: ((j: Jinaga) => Promise<void>)) {
116
118
  const user = <RequestUser>req.user;
117
119
  const userIdentity: UserIdentity = {
118
120
  provider: user.provider,
@@ -0,0 +1,15 @@
1
+ import { Feed } from "jinaga";
2
+ import { FeedCache } from "../http/feed-cache";
3
+
4
+ export class MemoryFeedCache implements FeedCache {
5
+ private feeds: { [feedHash: string]: Feed } = {};
6
+
7
+ storeFeed(feedHash: string, feed: Feed): Promise<void> {
8
+ this.feeds[feedHash] = feed;
9
+ return Promise.resolve();
10
+ }
11
+
12
+ getFeed(feedHash: string): Promise<Feed | undefined> {
13
+ return Promise.resolve(this.feeds[feedHash]);
14
+ }
15
+ }
@@ -2,16 +2,17 @@ import {
2
2
  canonicalPredecessors,
3
3
  Direction,
4
4
  ExistentialCondition,
5
- FactBookmark,
6
5
  FactEnvelope,
7
6
  FactPath,
8
7
  FactRecord,
9
8
  FactReference,
10
9
  factReferenceEquals,
11
- FactStream,
12
10
  FactTuple,
11
+ Feed,
13
12
  getAllFactTypes,
13
+ getAllFactTypesFromFeed,
14
14
  getAllRoles,
15
+ getAllRolesFromFeed,
15
16
  Join,
16
17
  PredecessorCollection,
17
18
  PropertyCondition,
@@ -20,6 +21,7 @@ import {
20
21
  Step,
21
22
  Storage,
22
23
  } from "jinaga";
24
+ import { FactFeed } from "jinaga/dist/storage";
23
25
  import { PoolClient } from "pg";
24
26
 
25
27
  import { distinct, flatten } from "../util/fn";
@@ -47,9 +49,8 @@ import {
47
49
  PublicKeyMap,
48
50
  RoleMap,
49
51
  } from "./maps";
50
- import { SpecificationLabel } from "./query-description";
51
52
  import { ResultSetTree, resultSqlFromSpecification, SqlQueryTree } from "./specification-result-sql";
52
- import { sqlFromSpecification } from "./specification-sql";
53
+ import { sqlFromFeed } from "./specification-sql";
53
54
  import { sqlFromSteps } from "./sql";
54
55
 
55
56
  interface FactTypeResult {
@@ -98,11 +99,9 @@ interface AncestorResult {
98
99
  }[];
99
100
  }
100
101
 
101
- function loadFactReference(r: Row): FactReference {
102
- return {
103
- type: r.type,
104
- hash: r.hash
105
- };
102
+ interface SpecificationLabel {
103
+ type: string;
104
+ index: number;
106
105
  }
107
106
 
108
107
  function loadFactTuple(labels: SpecificationLabel[], row: Row): FactTuple {
@@ -247,30 +246,25 @@ export class PostgresStore implements Storage {
247
246
  return composer.compose(resultSets);
248
247
  }
249
248
 
250
- async streamsFromSpecification(start: FactReference[], bookmarks: FactBookmark[], limit: number, specification: Specification): Promise<FactStream[]> {
251
- const factTypes = await this.loadFactTypesFromSpecification(specification);
252
- const roleMap = await this.loadRolesFromSpecification(specification, factTypes);
249
+ async feed(feed: Feed, bookmark: string): Promise<FactFeed> {
250
+ const factTypes: FactTypeMap = await this.loadFactTypesFromFeed(feed);
251
+ const roleMap: RoleMap = await this.loadRolesFromFeed(feed, factTypes);
252
+ const sql = sqlFromFeed(feed, bookmark, 100, factTypes, roleMap);
253
+ if (!sql) {
254
+ return {
255
+ tuples: [],
256
+ bookmark
257
+ };
258
+ }
253
259
 
254
- const sqlQueries = sqlFromSpecification(start, bookmarks, limit, specification, factTypes, roleMap);
255
- const streams = await this.connectionFactory.with(async (connection) => {
256
- const streams: FactStream[] = [];
257
- for (const sqlQuery of sqlQueries) {
258
- try {
259
- const { rows } = await connection.query(sqlQuery.sql, sqlQuery.parameters);
260
- const tuples = rows.map(row => loadFactTuple(sqlQuery.labels, row));
261
- streams.push({
262
- labels: sqlQuery.labels.map(l => l.name),
263
- tuples,
264
- bookmark: tuples.length > 0 ? tuples[tuples.length - 1].bookmark : sqlQuery.bookmark
265
- });
266
- }
267
- catch (error) {
268
- throw new Error(`Could not execute query "${sqlQuery.sql}", parameters ${sqlQuery.parameters}:\n${error}`);
269
- }
270
- }
271
- return streams;
260
+ const { rows } = await this.connectionFactory.with(async (connection) => {
261
+ return await connection.query(sql.sql, sql.parameters);
272
262
  });
273
- return streams;
263
+ const tuples = rows.map(row => loadFactTuple(sql.labels, row));
264
+ return {
265
+ tuples,
266
+ bookmark: tuples.length > 0 ? tuples[tuples.length - 1].bookmark : bookmark
267
+ };
274
268
  }
275
269
 
276
270
  private async loadFactTypesFromSteps(steps: Step[], startType: string): Promise<FactTypeMap> {
@@ -289,6 +283,21 @@ export class PostgresStore implements Storage {
289
283
  return factTypes;
290
284
  }
291
285
 
286
+ async loadFactTypesFromFeed(feed: Feed): Promise<FactTypeMap> {
287
+ const factTypes = this.factTypeMap;
288
+ const unknownFactTypes = getAllFactTypesFromFeed(feed)
289
+ .filter(factType => !factTypes.has(factType));
290
+ if (unknownFactTypes.length > 0) {
291
+ const loadedFactTypes = await this.connectionFactory.with(async (connection) => {
292
+ return await loadFactTypes(unknownFactTypes, connection);
293
+ });
294
+ const merged = mergeFactTypes(this.factTypeMap, loadedFactTypes);
295
+ this.factTypeMap = merged;
296
+ return merged;
297
+ }
298
+ return factTypes;
299
+ }
300
+
292
301
  async loadFactTypesFromSpecification(specification: Specification): Promise<FactTypeMap> {
293
302
  const factTypes = this.factTypeMap;
294
303
  const unknownFactTypes = getAllFactTypes(specification)
@@ -338,6 +347,25 @@ export class PostgresStore implements Storage {
338
347
  return roleMap;
339
348
  }
340
349
 
350
+ async loadRolesFromFeed(feed: Feed, factTypes: FactTypeMap): Promise<RoleMap> {
351
+ const roleMap = this.roleMap;
352
+ const unknownRoles = getAllRolesFromFeed(feed)
353
+ .map(r => ({
354
+ defining_fact_type_id: getFactTypeId(factTypes, r.definingFactType),
355
+ role: r.name
356
+ }))
357
+ .filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
358
+ if (unknownRoles.length > 0) {
359
+ const loadedRoles = await this.connectionFactory.with(async (connection) => {
360
+ return await loadRoles(unknownRoles, roleMap, connection);
361
+ });
362
+ const merged = mergeRoleMaps(this.roleMap, loadedRoles);
363
+ this.roleMap = merged;
364
+ return merged;
365
+ }
366
+ return roleMap;
367
+ }
368
+
341
369
  async whichExist(references: FactReference[]): Promise<FactReference[]> {
342
370
  if (references.length === 0) {
343
371
  return [];