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.
- package/dist/authentication/authentication-device.js +27 -6
- package/dist/authentication/authentication-device.js.map +1 -1
- package/dist/authentication/authentication-session.js +27 -6
- package/dist/authentication/authentication-session.js.map +1 -1
- package/dist/authorization/authorization-keystore.d.ts +4 -5
- package/dist/authorization/authorization-keystore.js +42 -18
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/http/router.d.ts +1 -2
- package/dist/http/router.js +13 -17
- package/dist/http/router.js.map +1 -1
- package/dist/jinaga-server.js +5 -6
- package/dist/jinaga-server.js.map +1 -1
- package/dist/postgres/postgres-store.d.ts +2 -5
- package/dist/postgres/postgres-store.js +44 -121
- package/dist/postgres/postgres-store.js.map +1 -1
- package/integration-test/jinaga-test/jinaga.test.js +247 -231
- package/integration-test/jinaga-test/package-lock.json +9 -52
- package/integration-test/jinaga-test/package.json +2 -5
- package/package.json +2 -3
- package/src/authentication/authentication-device.ts +29 -7
- package/src/authentication/authentication-session.ts +29 -7
- package/src/authorization/authorization-keystore.ts +42 -22
- package/src/http/router.ts +13 -23
- package/src/jinaga-server.ts +5 -6
- package/src/postgres/postgres-store.ts +63 -149
- package/test/authorization/authorizationSpec.ts +75 -89
- package/dist/postgres/sql.d.ts +0 -10
- package/dist/postgres/sql.js +0 -408
- package/dist/postgres/sql.js.map +0 -1
- package/src/postgres/sql.ts +0 -498
- package/test/postgres/postgresSpec.ts +0 -391
|
@@ -17,11 +17,8 @@
|
|
|
17
17
|
"express": "^4.17.2",
|
|
18
18
|
"jest": "^29.7.0",
|
|
19
19
|
"jest-compact-reporter": "^1.2.9",
|
|
20
|
-
"jinaga": "^
|
|
20
|
+
"jinaga": "^6.0.0",
|
|
21
21
|
"node-forge": "^1.3.0",
|
|
22
|
-
"
|
|
23
|
-
"pg": "^8.11.3",
|
|
24
|
-
"tweetnacl": "^1.0.3",
|
|
25
|
-
"tweetnacl-util": "^0.15.1"
|
|
22
|
+
"pg": "^8.11.3"
|
|
26
23
|
}
|
|
27
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jinaga-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Data management for web and mobile applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -48,9 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"express": "^4.18.2",
|
|
51
|
-
"jinaga": "^
|
|
51
|
+
"jinaga": "^6.0.0",
|
|
52
52
|
"node-forge": "^1.3.1",
|
|
53
|
-
"passport": "^0.6.0",
|
|
54
53
|
"pg": "^8.11.3"
|
|
55
54
|
}
|
|
56
55
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Authentication, AuthorizationEngine, AuthorizationRules, FactEnvelope, FactRecord, LoginResponse, Storage, UserIdentity } from "jinaga";
|
|
1
|
+
import { Authentication, AuthorizationEngine, AuthorizationRules, FactEnvelope, FactRecord, LoginResponse, Storage, UserIdentity, factReferenceEquals } from "jinaga";
|
|
2
2
|
|
|
3
3
|
import { Keystore } from "../keystore";
|
|
4
4
|
|
|
@@ -33,11 +33,33 @@ export class AuthenticationDevice implements Authentication {
|
|
|
33
33
|
|
|
34
34
|
async authorize(envelopes: FactEnvelope[]): Promise<FactEnvelope[]> {
|
|
35
35
|
const deviceFact = await this.keystore.getOrCreateDeviceFact(this.localDeviceIdentity);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
:
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
|
|
37
|
+
if (this.authorizationEngine) {
|
|
38
|
+
const results = await this.authorizationEngine.authorizeFacts(envelopes, deviceFact);
|
|
39
|
+
const authorizedEnvelopes: FactEnvelope[] = results.map(r => {
|
|
40
|
+
const isFact = factReferenceEquals(r.fact);
|
|
41
|
+
const envelope = envelopes.find(e => isFact(e.fact));
|
|
42
|
+
if (!envelope) {
|
|
43
|
+
throw new Error("Fact not found in envelopes.");
|
|
44
|
+
}
|
|
45
|
+
if (r.verdict === "Accept") {
|
|
46
|
+
return {
|
|
47
|
+
fact: r.fact,
|
|
48
|
+
signatures: envelope.signatures
|
|
49
|
+
.filter(s => r.newPublicKeys.includes(s.publicKey))
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
else if (r.verdict === "Existing") {
|
|
53
|
+
return envelope;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
throw new Error("Unexpected verdict.");
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return authorizedEnvelopes;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
return envelopes;
|
|
63
|
+
}
|
|
42
64
|
}
|
|
43
65
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Authentication, AuthorizationEngine, AuthorizationRules, FactEnvelope, FactRecord, LoginResponse, Storage, UserIdentity } from "jinaga";
|
|
1
|
+
import { Authentication, AuthorizationEngine, AuthorizationRules, FactEnvelope, FactRecord, LoginResponse, Storage, UserIdentity, factReferenceEquals } from "jinaga";
|
|
2
2
|
|
|
3
3
|
import { Keystore } from "../keystore";
|
|
4
4
|
|
|
@@ -41,11 +41,33 @@ export class AuthenticationSession implements Authentication {
|
|
|
41
41
|
|
|
42
42
|
async authorize(envelopes: FactEnvelope[]): Promise<FactEnvelope[]> {
|
|
43
43
|
const userFact = await this.keystore.getUserFact(this.userIdentity);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
:
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
|
|
45
|
+
if (this.authorizationEngine) {
|
|
46
|
+
const results = await this.authorizationEngine.authorizeFacts(envelopes, userFact);
|
|
47
|
+
const authorizedEnvelopes: FactEnvelope[] = results.map(r => {
|
|
48
|
+
const isFact = factReferenceEquals(r.fact);
|
|
49
|
+
const envelope = envelopes.find(e => isFact(e.fact));
|
|
50
|
+
if (!envelope) {
|
|
51
|
+
throw new Error("Fact not found in envelopes.");
|
|
52
|
+
}
|
|
53
|
+
if (r.verdict === "Accept") {
|
|
54
|
+
return {
|
|
55
|
+
fact: r.fact,
|
|
56
|
+
signatures: envelope.signatures
|
|
57
|
+
.filter(s => r.newPublicKeys.includes(s.publicKey))
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
else if (r.verdict === "Existing") {
|
|
61
|
+
return envelope;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw new Error("Unexpected verdict.");
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return authorizedEnvelopes;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return envelopes;
|
|
71
|
+
}
|
|
50
72
|
}
|
|
51
73
|
}
|
|
@@ -7,10 +7,8 @@ import {
|
|
|
7
7
|
FactEnvelope,
|
|
8
8
|
FactFeed,
|
|
9
9
|
FactManager,
|
|
10
|
-
FactRecord,
|
|
11
10
|
FactReference,
|
|
12
11
|
Forbidden,
|
|
13
|
-
Query,
|
|
14
12
|
ReferencesByName,
|
|
15
13
|
Specification,
|
|
16
14
|
Storage,
|
|
@@ -52,10 +50,6 @@ export class AuthorizationKeystore implements Authorization {
|
|
|
52
50
|
return userFact;
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
query(userIdentity: UserIdentity | null, start: FactReference, query: Query) {
|
|
56
|
-
return this.factManager.query(start, query);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
53
|
async read(userIdentity: UserIdentity | null, start: FactReference[], specification: Specification) {
|
|
60
54
|
if (this.distributionEngine) {
|
|
61
55
|
const userReference: FactReference | null = userIdentity
|
|
@@ -111,24 +105,50 @@ export class AuthorizationKeystore implements Authorization {
|
|
|
111
105
|
return await this.factManager.load(references);
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
async save(userIdentity: UserIdentity | null,
|
|
115
|
-
if (
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
108
|
+
async save(userIdentity: UserIdentity | null, envelopes: FactEnvelope[]): Promise<FactEnvelope[]> {
|
|
109
|
+
if (this.authorizationEngine) {
|
|
110
|
+
const userFact = userIdentity ? await this.keystore.getUserFact(userIdentity) : null;
|
|
111
|
+
const results = await this.authorizationEngine.authorizeFacts(envelopes, userFact);
|
|
112
|
+
const userKeys : string[] = (userFact && userFact.fields.hasOwnProperty("publicKey"))
|
|
113
|
+
? [ userFact.fields.publicKey ]
|
|
114
|
+
: [];
|
|
115
|
+
const factsToSign = results
|
|
116
|
+
.filter(r => r.verdict === "Accept" && r.newPublicKeys.some(k => userKeys.includes(k)))
|
|
117
|
+
.map(r => r.fact);
|
|
118
|
+
const signedFacts = (userIdentity && factsToSign.length > 0)
|
|
119
|
+
? await this.keystore.signFacts(userIdentity, factsToSign)
|
|
120
|
+
: [];
|
|
121
|
+
const authorizedEnvelopes: FactEnvelope[] = results.map(r => {
|
|
122
|
+
const isFact = factReferenceEquals(r.fact);
|
|
123
|
+
const envelope = envelopes.find(e => isFact(e.fact));
|
|
124
|
+
if (!envelope) {
|
|
125
|
+
throw new Error("Fact not found in envelopes.");
|
|
126
|
+
}
|
|
127
|
+
if (r.verdict === "Accept") {
|
|
128
|
+
const signedFact = signedFacts.find(f => isFact(f.fact));
|
|
129
|
+
const userSignatures = signedFact
|
|
130
|
+
? signedFact.signatures
|
|
131
|
+
: [];
|
|
132
|
+
return {
|
|
133
|
+
fact: r.fact,
|
|
134
|
+
signatures: envelope.signatures
|
|
135
|
+
.filter(s => r.newPublicKeys.includes(s.publicKey))
|
|
136
|
+
.concat(userSignatures)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
else if (r.verdict === "Existing") {
|
|
140
|
+
return envelope;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
throw new Error("Unexpected verdict.");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
const savedEnvelopes = await this.factManager.save(authorizedEnvelopes);
|
|
147
|
+
return savedEnvelopes;
|
|
129
148
|
}
|
|
130
149
|
else {
|
|
131
|
-
|
|
150
|
+
const savedEnvelopes = await this.factManager.save(envelopes);
|
|
151
|
+
return savedEnvelopes;
|
|
132
152
|
}
|
|
133
153
|
}
|
|
134
154
|
|
package/src/http/router.ts
CHANGED
|
@@ -14,8 +14,6 @@ import {
|
|
|
14
14
|
LoadResponse,
|
|
15
15
|
ProfileMessage,
|
|
16
16
|
ProjectedResult,
|
|
17
|
-
QueryMessage,
|
|
18
|
-
QueryResponse,
|
|
19
17
|
ReferencesByName,
|
|
20
18
|
SaveMessage,
|
|
21
19
|
Specification,
|
|
@@ -25,10 +23,8 @@ import {
|
|
|
25
23
|
buildFeeds,
|
|
26
24
|
computeObjectHash,
|
|
27
25
|
computeTupleSubsetHash,
|
|
28
|
-
fromDescriptiveString,
|
|
29
26
|
invertSpecification,
|
|
30
27
|
parseLoadMessage,
|
|
31
|
-
parseQueryMessage,
|
|
32
28
|
parseSaveMessage
|
|
33
29
|
} from "jinaga";
|
|
34
30
|
|
|
@@ -285,15 +281,9 @@ export interface RequestUser {
|
|
|
285
281
|
export class HttpRouter {
|
|
286
282
|
handler: Handler;
|
|
287
283
|
|
|
288
|
-
constructor(private factManager: FactManager, private authorization: Authorization, private feedCache: FeedCache
|
|
284
|
+
constructor(private factManager: FactManager, private authorization: Authorization, private feedCache: FeedCache) {
|
|
289
285
|
const router = Router();
|
|
290
286
|
router.get('/login', getAuthenticate(user => this.login(user)));
|
|
291
|
-
if (backwardCompatible) {
|
|
292
|
-
router.post('/query', post(
|
|
293
|
-
parseQueryMessage,
|
|
294
|
-
(user, queryMessage) => this.query(user, queryMessage)
|
|
295
|
-
));
|
|
296
|
-
}
|
|
297
287
|
router.post('/load', post(
|
|
298
288
|
parseLoadMessage,
|
|
299
289
|
(user, loadMessage) => this.load(user, loadMessage)
|
|
@@ -327,26 +317,22 @@ export class HttpRouter {
|
|
|
327
317
|
};
|
|
328
318
|
}
|
|
329
319
|
|
|
330
|
-
private async query(user: RequestUser | null, queryMessage: QueryMessage): Promise<QueryResponse> {
|
|
331
|
-
const userIdentity = serializeUserIdentity(user);
|
|
332
|
-
const query = fromDescriptiveString(queryMessage.query);
|
|
333
|
-
const result = await this.authorization.query(userIdentity, queryMessage.start, query);
|
|
334
|
-
return {
|
|
335
|
-
results: result
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
320
|
private async load(user: RequestUser, loadMessage: LoadMessage): Promise<LoadResponse> {
|
|
340
321
|
const userIdentity = serializeUserIdentity(user);
|
|
341
322
|
const result = await this.authorization.load(userIdentity, loadMessage.references);
|
|
323
|
+
const facts = result.map(r => r.fact);
|
|
342
324
|
return {
|
|
343
|
-
facts
|
|
325
|
+
facts
|
|
344
326
|
};
|
|
345
327
|
}
|
|
346
328
|
|
|
347
329
|
private async save(user: RequestUser | null, saveMessage: SaveMessage): Promise<void> {
|
|
348
330
|
const userIdentity = serializeUserIdentity(user);
|
|
349
|
-
await this.authorization.save(userIdentity, saveMessage.facts
|
|
331
|
+
await this.authorization.save(userIdentity, saveMessage.facts
|
|
332
|
+
.map(fact => ({
|
|
333
|
+
fact: fact,
|
|
334
|
+
signatures: []
|
|
335
|
+
})));
|
|
350
336
|
}
|
|
351
337
|
|
|
352
338
|
private async read(user: RequestUser | null, input: string): Promise<any[]> {
|
|
@@ -379,7 +365,11 @@ export class HttpRouter {
|
|
|
379
365
|
}
|
|
380
366
|
|
|
381
367
|
const userIdentity = serializeUserIdentity(user);
|
|
382
|
-
await this.authorization.save(userIdentity, factRecords
|
|
368
|
+
await this.authorization.save(userIdentity, factRecords
|
|
369
|
+
.map(fact => ({
|
|
370
|
+
fact: fact,
|
|
371
|
+
signatures: []
|
|
372
|
+
})));
|
|
383
373
|
}
|
|
384
374
|
|
|
385
375
|
private async feeds(user: RequestUser | null, input: string): Promise<FeedsResponse> {
|
package/src/jinaga-server.ts
CHANGED
|
@@ -65,11 +65,10 @@ export class JinagaServer {
|
|
|
65
65
|
const feedCache = new FeedCache();
|
|
66
66
|
const authentication = createAuthentication(store, keystore, authorizationRules);
|
|
67
67
|
const network = new NetworkNoOp();
|
|
68
|
-
const factManager = new FactManager(
|
|
68
|
+
const factManager = new FactManager(fork, source, store, network);
|
|
69
69
|
const authorization = createAuthorization(authorizationRules, distributionRules, factManager, store, keystore);
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const j: Jinaga = new Jinaga(factManager, syncStatusNotifier);
|
|
70
|
+
const router = new HttpRouter(factManager, authorization, feedCache);
|
|
71
|
+
const j: Jinaga = new Jinaga(authentication, factManager, syncStatusNotifier);
|
|
73
72
|
|
|
74
73
|
async function close() {
|
|
75
74
|
for (const pool of Object.values(pools)) {
|
|
@@ -187,8 +186,8 @@ async function withSession(store: Storage, keystore: Keystore | null, authorizat
|
|
|
187
186
|
const fork = new PassThroughFork(store);
|
|
188
187
|
const observableSource = new ObservableSource(store);
|
|
189
188
|
const network = new NetworkNoOp();
|
|
190
|
-
const factManager = new FactManager(
|
|
191
|
-
const j = new Jinaga(factManager, syncStatusNotifier);
|
|
189
|
+
const factManager = new FactManager(fork, observableSource, store, network);
|
|
190
|
+
const j = new Jinaga(authentication, factManager, syncStatusNotifier);
|
|
192
191
|
await callback(j);
|
|
193
192
|
}
|
|
194
193
|
|
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
canonicalPredecessors,
|
|
3
|
-
Direction,
|
|
4
|
-
ExistentialCondition,
|
|
5
3
|
FactEnvelope,
|
|
6
4
|
FactFeed,
|
|
7
|
-
FactPath,
|
|
8
5
|
FactRecord,
|
|
9
6
|
FactReference,
|
|
10
7
|
factReferenceEquals,
|
|
8
|
+
FactSignature,
|
|
11
9
|
FactTuple,
|
|
12
10
|
getAllFactTypes,
|
|
13
11
|
getAllRoles,
|
|
14
|
-
Join,
|
|
15
12
|
PredecessorCollection,
|
|
16
13
|
ProjectedResult,
|
|
17
|
-
PropertyCondition,
|
|
18
|
-
Query,
|
|
19
14
|
Specification,
|
|
20
|
-
|
|
21
|
-
Storage,
|
|
22
|
-
TopologicalSorter
|
|
15
|
+
Storage
|
|
23
16
|
} from "jinaga";
|
|
24
17
|
import { Pool, PoolClient } from "pg";
|
|
25
18
|
|
|
@@ -51,7 +44,6 @@ import {
|
|
|
51
44
|
} from "./maps";
|
|
52
45
|
import { ResultSetFact, ResultSetRow, ResultSetTree, resultSqlFromSpecification, SqlQueryTree } from "./specification-result-sql";
|
|
53
46
|
import { sqlFromFeed } from "./specification-sql";
|
|
54
|
-
import { sqlFromSteps } from "./sql";
|
|
55
47
|
|
|
56
48
|
interface FactTypeResult {
|
|
57
49
|
rows: {
|
|
@@ -96,6 +88,8 @@ interface AncestorResult {
|
|
|
96
88
|
name: string;
|
|
97
89
|
hash: string;
|
|
98
90
|
data: string;
|
|
91
|
+
public_key: string;
|
|
92
|
+
signature: string;
|
|
99
93
|
}[];
|
|
100
94
|
}
|
|
101
95
|
|
|
@@ -125,21 +119,6 @@ function loadFactTuple(labels: SpecificationLabel[], row: Row): FactTuple {
|
|
|
125
119
|
};
|
|
126
120
|
}
|
|
127
121
|
|
|
128
|
-
function loadFactPath(pathLength: number, factTypeNames: string[], r: Row): FactPath {
|
|
129
|
-
let path: FactPath = [];
|
|
130
|
-
for (let i = 0; i < pathLength; i++) {
|
|
131
|
-
const hash = r['hash' + (i + 2)];
|
|
132
|
-
if (!hash) {
|
|
133
|
-
throw new Error(`Cannot find column 'hash${i + 2}'`);
|
|
134
|
-
}
|
|
135
|
-
path.push({
|
|
136
|
-
type: factTypeNames[i],
|
|
137
|
-
hash: hash
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
return path;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
122
|
export class PostgresStore implements Storage {
|
|
144
123
|
private connectionFactory: ConnectionFactory;
|
|
145
124
|
private factTypeMap: FactTypeMap = emptyFactTypeMap();
|
|
@@ -204,32 +183,6 @@ export class PostgresStore implements Storage {
|
|
|
204
183
|
}
|
|
205
184
|
}
|
|
206
185
|
|
|
207
|
-
async query(start: FactReference, query: Query): Promise<FactPath[]> {
|
|
208
|
-
if (query.steps.length === 0) {
|
|
209
|
-
return [[start]];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
const factTypes = await this.loadFactTypesFromSteps(query.steps, start.type);
|
|
214
|
-
const roleMap = await this.loadRolesFromSteps(query.steps, factTypes, start.type);
|
|
215
|
-
|
|
216
|
-
const sqlQuery = sqlFromSteps(start, query.steps, factTypes, roleMap, this.schema);
|
|
217
|
-
if (!sqlQuery) {
|
|
218
|
-
throw new Error(`Could not generate SQL for query "${query.toDescriptiveString()}" starting at "${start.type}"`);
|
|
219
|
-
}
|
|
220
|
-
if (sqlQuery.empty) {
|
|
221
|
-
return [];
|
|
222
|
-
}
|
|
223
|
-
const { rows } = await this.connectionFactory.with(async (connection) => {
|
|
224
|
-
return await connection.query(sqlQuery.sql, sqlQuery.parameters);
|
|
225
|
-
});
|
|
226
|
-
return rows.map(row => loadFactPath(sqlQuery.pathLength, sqlQuery.factTypeNames, row));
|
|
227
|
-
}
|
|
228
|
-
catch (e) {
|
|
229
|
-
throw new Error(`Could not generate SQL for query "${query.toDescriptiveString()}" starting at "${start.type}": ${e}`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
186
|
async read(start: FactReference[], specification: Specification): Promise<ProjectedResult[]> {
|
|
234
187
|
const factTypes = await this.loadFactTypesFromSpecification(specification);
|
|
235
188
|
const roleMap = await this.loadRolesFromSpecification(specification, factTypes);
|
|
@@ -253,7 +206,8 @@ export class PostgresStore implements Storage {
|
|
|
253
206
|
const factReferences: FactReference[] = composer.findFactReferences(resultSets);
|
|
254
207
|
|
|
255
208
|
// Load the references into a fact tree
|
|
256
|
-
const
|
|
209
|
+
const factEnvelopes = await this.load(factReferences);
|
|
210
|
+
const factRecords = factEnvelopes.map(envelope => envelope.fact);
|
|
257
211
|
|
|
258
212
|
return composer.compose(resultSets, factRecords);
|
|
259
213
|
}
|
|
@@ -279,22 +233,6 @@ export class PostgresStore implements Storage {
|
|
|
279
233
|
};
|
|
280
234
|
}
|
|
281
235
|
|
|
282
|
-
private async loadFactTypesFromSteps(steps: Step[], startType: string): Promise<FactTypeMap> {
|
|
283
|
-
const factTypes = this.factTypeMap;
|
|
284
|
-
const unknownFactTypes = [...allFactTypes(steps), startType]
|
|
285
|
-
.filter(factType => !factTypes.has(factType))
|
|
286
|
-
.filter(distinct);
|
|
287
|
-
if (unknownFactTypes.length > 0) {
|
|
288
|
-
const loadedFactTypes = await this.connectionFactory.with(async (connection) => {
|
|
289
|
-
return await loadFactTypes(unknownFactTypes, connection, this.schema);
|
|
290
|
-
});
|
|
291
|
-
const merged = mergeFactTypes(this.factTypeMap, loadedFactTypes);
|
|
292
|
-
this.factTypeMap = merged;
|
|
293
|
-
return merged;
|
|
294
|
-
}
|
|
295
|
-
return factTypes;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
236
|
async loadFactTypesFromFeed(feed: Specification): Promise<FactTypeMap> {
|
|
299
237
|
const factTypes = this.factTypeMap;
|
|
300
238
|
const unknownFactTypes = getAllFactTypes(feed)
|
|
@@ -325,21 +263,6 @@ export class PostgresStore implements Storage {
|
|
|
325
263
|
return factTypes;
|
|
326
264
|
}
|
|
327
265
|
|
|
328
|
-
private async loadRolesFromSteps(steps: Step[], factTypes: FactTypeMap, initialType: string) {
|
|
329
|
-
const roleMap = this.roleMap;
|
|
330
|
-
const unknownRoles = allRoles(steps, factTypes, getFactTypeId(factTypes, initialType))
|
|
331
|
-
.filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
|
|
332
|
-
if (unknownRoles.length > 0) {
|
|
333
|
-
const loadedRoles = await this.connectionFactory.with(async (connection) => {
|
|
334
|
-
return await loadRoles(unknownRoles, roleMap, connection, this.schema);
|
|
335
|
-
});
|
|
336
|
-
const merged = mergeRoleMaps(this.roleMap, loadedRoles);
|
|
337
|
-
this.roleMap = merged;
|
|
338
|
-
return merged;
|
|
339
|
-
}
|
|
340
|
-
return roleMap;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
266
|
async loadRolesFromSpecification(specification: Specification, factTypes: FactTypeMap): Promise<RoleMap> {
|
|
344
267
|
const roleMap = this.roleMap;
|
|
345
268
|
const unknownRoles = getAllRoles(specification)
|
|
@@ -407,7 +330,7 @@ export class PostgresStore implements Storage {
|
|
|
407
330
|
return existing;
|
|
408
331
|
}
|
|
409
332
|
|
|
410
|
-
async load(references: FactReference[]): Promise<
|
|
333
|
+
async load(references: FactReference[]): Promise<FactEnvelope[]> {
|
|
411
334
|
if (references.length === 0) {
|
|
412
335
|
return [];
|
|
413
336
|
}
|
|
@@ -419,23 +342,30 @@ export class PostgresStore implements Storage {
|
|
|
419
342
|
const factParameters = flatten(references, (f) =>
|
|
420
343
|
[f.hash, factTypes.get(f.type)]);
|
|
421
344
|
const sql =
|
|
422
|
-
'SELECT f.fact_type_id,
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
345
|
+
'SELECT f.fact_type_id, f.name, f.hash, f.data, s.public_key, s.signature ' +
|
|
346
|
+
'FROM (SELECT f.fact_id, f.fact_type_id, t.name, f.hash, f.data ' +
|
|
347
|
+
`FROM ${this.schema}.fact f ` +
|
|
348
|
+
`JOIN ${this.schema}.fact_type t ` +
|
|
349
|
+
' ON f.fact_type_id = t.fact_type_id ' +
|
|
350
|
+
'JOIN (VALUES ' + factValues.join(', ') + ') AS v (hash, fact_type_id) ' +
|
|
351
|
+
' ON v.fact_type_id = f.fact_type_id AND v.hash = f.hash ' +
|
|
352
|
+
'UNION ' +
|
|
353
|
+
'SELECT f2.fact_id, f2.fact_type_id, t.name, f2.hash, f2.data ' +
|
|
354
|
+
`FROM ${this.schema}.fact f1 ` +
|
|
355
|
+
'JOIN (VALUES ' + factValues.join(', ') + ') AS v (hash, fact_type_id) ' +
|
|
356
|
+
' ON v.fact_type_id = f1.fact_type_id AND v.hash = f1.hash ' +
|
|
357
|
+
`JOIN ${this.schema}.ancestor a ` +
|
|
358
|
+
' ON a.fact_id = f1.fact_id ' +
|
|
359
|
+
`JOIN ${this.schema}.fact f2 ` +
|
|
360
|
+
' ON f2.fact_id = a.ancestor_fact_id ' +
|
|
361
|
+
`JOIN ${this.schema}.fact_type t ` +
|
|
362
|
+
' ON t.fact_type_id = f2.fact_type_id) as f ' +
|
|
363
|
+
'LEFT JOIN (SELECT p.public_key, s.signature, s.fact_id ' +
|
|
364
|
+
`FROM ${this.schema}.signature s ` +
|
|
365
|
+
`JOIN ${this.schema}.public_key p ` +
|
|
366
|
+
' ON p.public_key_id = s.public_key_id) as s ' +
|
|
367
|
+
'ON s.fact_id = f.fact_id ' +
|
|
368
|
+
'ORDER BY f.fact_id, s.public_key;';
|
|
439
369
|
const result: AncestorResult = await this.connectionFactory.with(async (connection) => {
|
|
440
370
|
return await connection.query(sql, factParameters);
|
|
441
371
|
})
|
|
@@ -444,17 +374,46 @@ export class PostgresStore implements Storage {
|
|
|
444
374
|
emptyFactTypeMap()
|
|
445
375
|
);
|
|
446
376
|
this.factTypeMap = mergeFactTypes(this.factTypeMap, resultFactTypes);
|
|
447
|
-
const sorter = new TopologicalSorter<FactRecord>();
|
|
448
377
|
const records = result.rows.map((r) => {
|
|
449
378
|
const { fields, predecessors }: { fields: {}, predecessors: PredecessorCollection } = r.data as any;
|
|
450
|
-
|
|
379
|
+
const fact: FactRecord = {
|
|
451
380
|
type: r.name,
|
|
452
381
|
hash: r.hash,
|
|
453
382
|
fields,
|
|
454
383
|
predecessors
|
|
384
|
+
};
|
|
385
|
+
const signature: FactSignature = {
|
|
386
|
+
publicKey: r.public_key,
|
|
387
|
+
signature: r.signature
|
|
388
|
+
};
|
|
389
|
+
return {
|
|
390
|
+
fact,
|
|
391
|
+
signature
|
|
455
392
|
}
|
|
456
393
|
});
|
|
457
|
-
|
|
394
|
+
// Group the records by fact type and hash.
|
|
395
|
+
const envelopes: FactEnvelope[] = records.reduce((acc: FactEnvelope[], record) => {
|
|
396
|
+
// Because the records are sorted by fact ID, we can just look at the last envelope.
|
|
397
|
+
const last = acc.length > 0 ? acc[acc.length - 1] : undefined;
|
|
398
|
+
if (last && last.fact.type === record.fact.type && last.fact.hash === record.fact.hash) {
|
|
399
|
+
last.signatures.push(record.signature);
|
|
400
|
+
}
|
|
401
|
+
else if (record.signature.signature === null) {
|
|
402
|
+
acc.push({
|
|
403
|
+
fact: record.fact,
|
|
404
|
+
signatures: []
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
acc.push({
|
|
409
|
+
fact: record.fact,
|
|
410
|
+
signatures: [record.signature]
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
return acc;
|
|
414
|
+
}, []);
|
|
415
|
+
// Assuming that the facts were inserted in topological order, they don't need to be sorted.
|
|
416
|
+
return envelopes;
|
|
458
417
|
}
|
|
459
418
|
|
|
460
419
|
private async loadFactTypesFromReferences(references: FactReference[]): Promise<FactTypeMap> {
|
|
@@ -894,51 +853,6 @@ async function insertSignatures(envelopes: FactEnvelope[], allFacts: FactMap, fa
|
|
|
894
853
|
}
|
|
895
854
|
}
|
|
896
855
|
|
|
897
|
-
function allFactTypes(steps: Step[]): string[] {
|
|
898
|
-
const factTypes = steps
|
|
899
|
-
.filter(step => step instanceof PropertyCondition && step.name === 'type')
|
|
900
|
-
.map(step => (step as PropertyCondition).value);
|
|
901
|
-
const childFactTypes = steps
|
|
902
|
-
.filter(step => step instanceof ExistentialCondition)
|
|
903
|
-
.flatMap(step => allFactTypes((step as ExistentialCondition).steps));
|
|
904
|
-
return [...factTypes, ...childFactTypes].filter(distinct);
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
function allRoles(steps: Step[], factTypes: FactTypeMap, initialTypeId: number | undefined) {
|
|
908
|
-
let roles: { successor_type_id: number; role: string }[] = [];
|
|
909
|
-
let successor_type_id = initialTypeId;
|
|
910
|
-
let role: string | undefined = undefined;
|
|
911
|
-
|
|
912
|
-
for (const step of steps) {
|
|
913
|
-
if (step instanceof PropertyCondition) {
|
|
914
|
-
if (step.name === 'type') {
|
|
915
|
-
successor_type_id = getFactTypeId(factTypes, step.value);
|
|
916
|
-
if (successor_type_id && role) {
|
|
917
|
-
roles.push({ successor_type_id: successor_type_id, role });
|
|
918
|
-
}
|
|
919
|
-
role = undefined;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
else if (step instanceof Join) {
|
|
923
|
-
if (step.direction === Direction.Predecessor) {
|
|
924
|
-
if (successor_type_id) {
|
|
925
|
-
roles.push({ successor_type_id: successor_type_id, role: step.role });
|
|
926
|
-
}
|
|
927
|
-
role = undefined;
|
|
928
|
-
}
|
|
929
|
-
else {
|
|
930
|
-
role = step.role;
|
|
931
|
-
}
|
|
932
|
-
successor_type_id = undefined;
|
|
933
|
-
}
|
|
934
|
-
else if (step instanceof ExistentialCondition) {
|
|
935
|
-
roles = roles.concat(allRoles(step.steps, factTypes, successor_type_id));
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
return roles;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
856
|
function predecessorsOf(fact: FactRecord): FactReference[] {
|
|
943
857
|
const references = Object.values(fact.predecessors).flatMap(predecessor =>
|
|
944
858
|
Array.isArray(predecessor) ? predecessor : [predecessor]
|