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.
- package/README.md +103 -0
- package/dist/authentication/authentication-device.d.ts +3 -1
- package/dist/authentication/authentication-device.js +3 -0
- package/dist/authentication/authentication-device.js.map +1 -1
- package/dist/authentication/authentication-impl.d.ts +3 -2
- package/dist/authentication/authentication-impl.js +3 -0
- package/dist/authentication/authentication-impl.js.map +1 -1
- package/dist/authentication/authentication-session.d.ts +4 -2
- package/dist/authentication/authentication-session.js +7 -2
- package/dist/authentication/authentication-session.js.map +1 -1
- package/dist/authentication.d.ts +5 -4
- package/dist/authentication.js +3 -0
- package/dist/authentication.js.map +1 -1
- package/dist/authorization/authorization-keystore.d.ts +5 -3
- package/dist/authorization/authorization-keystore.js +12 -9
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/http/feed-cache.d.ts +5 -0
- package/dist/http/feed-cache.js +3 -0
- package/dist/http/feed-cache.js.map +1 -0
- package/dist/http/node-http.js +4 -1
- package/dist/http/node-http.js.map +1 -1
- package/dist/http/router.d.ts +5 -1
- package/dist/http/router.js +157 -24
- package/dist/http/router.js.map +1 -1
- package/dist/jinaga-server.js +6 -4
- package/dist/jinaga-server.js.map +1 -1
- package/dist/memory/memory-feed-cache.d.ts +7 -0
- package/dist/memory/memory-feed-cache.js +17 -0
- package/dist/memory/memory-feed-cache.js.map +1 -0
- package/dist/postgres/postgres-store.d.ts +5 -2
- package/dist/postgres/postgres-store.js +53 -28
- package/dist/postgres/postgres-store.js.map +1 -1
- package/dist/postgres/specification-result-sql.d.ts +11 -4
- package/dist/postgres/specification-result-sql.js +302 -5
- package/dist/postgres/specification-result-sql.js.map +1 -1
- package/dist/postgres/specification-sql.d.ts +14 -3
- package/dist/postgres/specification-sql.js +233 -100
- package/dist/postgres/specification-sql.js.map +1 -1
- package/package.json +2 -2
- package/src/authentication/authentication-device.ts +6 -0
- package/src/authentication/authentication-impl.ts +6 -3
- package/src/authentication/authentication-session.ts +11 -3
- package/src/authentication.ts +18 -5
- package/src/authorization/authorization-keystore.ts +14 -8
- package/src/http/feed-cache.ts +6 -0
- package/src/http/node-http.ts +5 -2
- package/src/http/router.ts +179 -31
- package/src/jinaga-server.ts +11 -9
- package/src/memory/memory-feed-cache.ts +15 -0
- package/src/postgres/postgres-store.ts +59 -31
- package/src/postgres/specification-result-sql.ts +450 -10
- package/src/postgres/specification-sql.ts +335 -115
- package/test/authorization/authorizationSpec.ts +11 -2
- package/test/postgres/postgresSpec.ts +31 -25
- package/test/postgres/postgresSpecificationSpec.ts +40 -68
- package/dist/postgres/query-description-builder.d.ts +0 -13
- package/dist/postgres/query-description-builder.js +0 -104
- package/dist/postgres/query-description-builder.js.map +0 -1
- package/dist/postgres/query-description.d.ts +0 -71
- package/dist/postgres/query-description.js +0 -256
- package/dist/postgres/query-description.js.map +0 -1
- package/src/postgres/query-description-builder.ts +0 -116
- package/src/postgres/query-description.ts +0 -385
package/src/http/node-http.ts
CHANGED
|
@@ -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
|
}
|
package/src/http/router.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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',
|
|
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',
|
|
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',
|
|
131
|
-
router.post('/
|
|
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<
|
|
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
|
-
|
|
175
|
-
|
|
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<
|
|
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
|
|
211
|
-
|
|
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
|
+
}
|
package/src/jinaga-server.ts
CHANGED
|
@@ -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
|
|
55
|
-
const fork = createFork(config,
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
251
|
-
const factTypes = await this.
|
|
252
|
-
const roleMap = await this.
|
|
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
|
|
255
|
-
|
|
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
|
-
|
|
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 [];
|