jinaga-server 3.1.0 → 3.1.2

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 (46) hide show
  1. package/README.md +2 -0
  2. package/dist/http/line-reader.d.ts +2 -0
  3. package/dist/http/line-reader.js +68 -0
  4. package/dist/http/line-reader.js.map +1 -0
  5. package/dist/http/router.d.ts +4 -1
  6. package/dist/http/router.js +122 -15
  7. package/dist/http/router.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +3 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/jinaga-server.d.ts +1 -0
  12. package/dist/jinaga-server.js +1 -1
  13. package/dist/jinaga-server.js.map +1 -1
  14. package/dist/memory/memory-keystore.js +5 -8
  15. package/dist/memory/memory-keystore.js.map +1 -1
  16. package/dist/postgres/postgres-keystore.js +5 -32
  17. package/dist/postgres/postgres-keystore.js.map +1 -1
  18. package/http/sans-auth/Feeds options.http +1 -0
  19. package/http/sans-auth/Save graph.http +6 -0
  20. package/http/sans-auth/Save json.http +13 -0
  21. package/http/with-auth/Create post with auth.http +24 -0
  22. package/http/with-auth/Define authorization rules.http +25 -0
  23. package/http/with-auth/Define distribution rules.http +30 -0
  24. package/http/with-auth/Feed sites.http +3 -0
  25. package/http/with-auth/Feeds options.http +1 -0
  26. package/http/with-auth/Feeds sites.http +32 -0
  27. package/http/with-auth/Get posts with auth.http +32 -0
  28. package/http/with-auth/Load titles.http +21 -0
  29. package/integration-test/jinaga-test/Dockerfile +1 -1
  30. package/integration-test/jinaga-test/package-lock.json +6 -5
  31. package/integration-test/jinaga-test/package.json +1 -1
  32. package/package.json +3 -5
  33. package/replicator/index.ts +0 -2
  34. package/replicator/package-lock.json +29 -75
  35. package/replicator/package.json +2 -4
  36. package/src/http/line-reader.ts +69 -0
  37. package/src/http/router.ts +152 -20
  38. package/src/index.ts +1 -0
  39. package/src/jinaga-server.ts +2 -1
  40. package/src/memory/memory-keystore.ts +7 -10
  41. package/src/postgres/postgres-keystore.ts +6 -38
  42. package/dist/http/serializer.d.ts +0 -11
  43. package/dist/http/serializer.js +0 -68
  44. package/dist/http/serializer.js.map +0 -1
  45. package/src/http/serializer.ts +0 -81
  46. package/test/http/serializerSpec.ts +0 -140
@@ -1,4 +1,4 @@
1
- import { Handler, MediaType, Response, Router } from "express";
1
+ import { Handler, NextFunction, Request, Response, Router } from "express";
2
2
  import {
3
3
  Authorization,
4
4
  Declaration,
@@ -11,12 +11,14 @@ import {
11
11
  FeedResponse,
12
12
  FeedsResponse,
13
13
  Forbidden,
14
+ GraphDeserializer,
15
+ GraphSerializer,
16
+ GraphSource,
14
17
  LoadMessage,
15
18
  LoadResponse,
16
19
  ProfileMessage,
17
20
  ProjectedResult,
18
21
  ReferencesByName,
19
- SaveMessage,
20
22
  Specification,
21
23
  SpecificationParser,
22
24
  Trace,
@@ -26,11 +28,11 @@ import {
26
28
  computeTupleSubsetHash,
27
29
  invertSpecification,
28
30
  parseLoadMessage,
29
- parseSaveMessage
31
+ parseSaveMessage,
32
+ verifyEnvelopes
30
33
  } from "jinaga";
31
-
32
34
  import { Stream } from "./stream";
33
- import { GraphSerializer } from "./serializer";
35
+ import { createLineReader } from "./line-reader";
34
36
 
35
37
  interface ParsedQs { [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[] }
36
38
 
@@ -203,15 +205,12 @@ function postString<U>(method: (user: RequestUser, message: string) => Promise<U
203
205
  }
204
206
 
205
207
  function postCreate<T>(
206
- parse: (input: any) => T,
208
+ parse: (request: Request) => T,
207
209
  method: (user: RequestUser, message: T) => Promise<void>
208
210
  ): Handler {
209
211
  return (req, res, next) => {
210
212
  const user = <RequestUser>req.user;
211
- const message = parse(req.body);
212
- if (!message) {
213
- throw new Error('Ensure that you have called app.use(express.json()).');
214
- }
213
+ const message = parse(req);
215
214
  method(user, message)
216
215
  .then(_ => {
217
216
  res.sendStatus(201);
@@ -274,6 +273,29 @@ function serializeUserIdentity(user: RequestUser | null): UserIdentity | null {
274
273
  };
275
274
  }
276
275
 
276
+ function inputSaveMessage(req: Request): GraphSource {
277
+ if (req.is('application/x-jinaga-graph-v1')) {
278
+ // Convert the request into a function that reads one line at a time.
279
+ const readLine = createLineReader(req);
280
+
281
+ return new GraphDeserializer(readLine);
282
+ }
283
+ else {
284
+ return {
285
+ read: async (onEnvelopes) => {
286
+ const message = parseSaveMessage(req.body);
287
+ if (!message) {
288
+ throw new Error('Ensure that you have called app.use(express.json()).');
289
+ }
290
+ await onEnvelopes(message.facts.map(fact => ({
291
+ fact: fact,
292
+ signatures: []
293
+ })));
294
+ }
295
+ };
296
+ }
297
+ }
298
+
277
299
  function outputGraph(result: FactEnvelope[], res: Response, accepts: (type: string) => string | false) {
278
300
  if (accepts("application/x-jinaga-graph-v1")) {
279
301
  res.type("application/x-jinaga-graph-v1");
@@ -306,7 +328,12 @@ export interface RequestUser {
306
328
  export class HttpRouter {
307
329
  handler: Handler;
308
330
 
309
- constructor(private factManager: FactManager, private authorization: Authorization, private feedCache: FeedCache) {
331
+ constructor(
332
+ private factManager: FactManager,
333
+ private authorization: Authorization,
334
+ private feedCache: FeedCache,
335
+ private allowedOrigin: string | string[] | ((origin: string, callback: (err: Error | null, allow?: boolean) => void) => void)
336
+ ) {
310
337
  const router = Router();
311
338
  router.get('/login', getAuthenticate(user => this.login(user)));
312
339
  router.post('/load', post(
@@ -315,8 +342,8 @@ export class HttpRouter {
315
342
  outputGraph
316
343
  ));
317
344
  router.post('/save', postCreate(
318
- parseSaveMessage,
319
- (user, saveMessage) => this.save(user, saveMessage)
345
+ inputSaveMessage,
346
+ (user, graphSource) => this.save(user, graphSource)
320
347
  ));
321
348
 
322
349
  router.post('/read', postString((user, input: string) => this.read(user, input)));
@@ -330,6 +357,30 @@ export class HttpRouter {
330
357
  (user, params, query) => this.feed(user, params, query),
331
358
  (user, params, query) => this.streamFeed(user, params, query)));
332
359
 
360
+ // Respond to OPTIONS requests to describe the methods and content types
361
+ // that are supported.
362
+ this.setOptions(router, '/login')
363
+ .intendedForGet()
364
+ .returningContent();
365
+ this.setOptions(router, '/load')
366
+ .intendedForPost('application/json')
367
+ .returningContent();
368
+ this.setOptions(router, '/save')
369
+ .intendedForPost('application/json', 'application/x-jinaga-graph-v1')
370
+ .returningNoContent();
371
+ this.setOptions(router, '/read')
372
+ .intendedForPost('text/plain')
373
+ .returningContent();
374
+ this.setOptions(router, '/write')
375
+ .intendedForPost('text/plain')
376
+ .returningNoContent();
377
+ this.setOptions(router, '/feeds')
378
+ .intendedForPost('text/plain')
379
+ .returningContent();
380
+ this.setOptions(router, '/feeds/:hash')
381
+ .intendedForGet()
382
+ .returningContent();
383
+
333
384
  this.handler = router;
334
385
  }
335
386
 
@@ -347,17 +398,17 @@ export class HttpRouter {
347
398
  private async load(user: RequestUser, loadMessage: LoadMessage): Promise<FactEnvelope[]> {
348
399
  const userIdentity = serializeUserIdentity(user);
349
400
  const result = await this.authorization.load(userIdentity, loadMessage.references);
350
- const facts = result.map(r => r.fact);
351
401
  return result;
352
402
  }
353
403
 
354
- private async save(user: RequestUser | null, saveMessage: SaveMessage): Promise<void> {
404
+ private async save(user: RequestUser | null, graphSource: GraphSource): Promise<void> {
355
405
  const userIdentity = serializeUserIdentity(user);
356
- await this.authorization.save(userIdentity, saveMessage.facts
357
- .map(fact => ({
358
- fact: fact,
359
- signatures: []
360
- })));
406
+ await graphSource.read(async (envelopes) => {
407
+ if (!verifyEnvelopes(envelopes)) {
408
+ throw new Forbidden("The signatures on the facts are invalid.");
409
+ }
410
+ await this.authorization.save(userIdentity, envelopes);
411
+ });
361
412
  }
362
413
 
363
414
  private async read(user: RequestUser | null, input: string): Promise<any[]> {
@@ -550,6 +601,77 @@ export class HttpRouter {
550
601
  return declaredFact.declared.reference;
551
602
  });
552
603
  }
604
+
605
+ private setOptions(router: Router, path: string): OptionsConfiguration {
606
+ const addOptions = (allowedMethods: string[], allowedHeaders: string[], allowedTypes: string[]) => {
607
+ router.options(path, this.applyAllowOrigin((req, res) => {
608
+ res.set('Allow', allowedMethods.join(', '));
609
+ res.set('Access-Control-Allow-Methods', allowedMethods.join(', '));
610
+ res.set('Access-Control-Allow-Headers', allowedHeaders.join(', '));
611
+ if (allowedTypes.length > 0) {
612
+ res.set('Accept-Post', allowedTypes.join(', '));
613
+ }
614
+ res.status(204).send();
615
+ }));
616
+ }
617
+
618
+ return {
619
+ intendedForGet: () => {
620
+ addOptions(['GET', 'OPTIONS'], ['Accept', 'Authorization'], []);
621
+ return {
622
+ returningContent: () => { },
623
+ returningNoContent: () => { }
624
+ };
625
+ },
626
+ intendedForPost: (...contentTypes: string[]) => {
627
+ return {
628
+ returningContent: () => {
629
+ addOptions(
630
+ ['POST', 'OPTIONS'],
631
+ ['Content-Type', 'Accept', 'Authorization'],
632
+ contentTypes);
633
+ },
634
+ returningNoContent: () => {
635
+ addOptions(
636
+ ['POST', 'OPTIONS'],
637
+ ['Content-Type', 'Authorization'],
638
+ contentTypes);
639
+ }
640
+ };
641
+ }
642
+ };
643
+ }
644
+
645
+ private applyAllowOrigin(sendResponse: (req: Request, res: Response) => void) {
646
+ // Specify the allowed origins.
647
+ return (req: Request, res: Response, next: NextFunction) => {
648
+ let requestOrigin = req.get('Origin');
649
+
650
+ if (typeof this.allowedOrigin === 'string') {
651
+ // If allowedOrigin is a string, use it as the value of the Access-Control-Allow-Origin header
652
+ res.set('Access-Control-Allow-Origin', this.allowedOrigin);
653
+ sendResponse(req, res);
654
+ } else if (Array.isArray(this.allowedOrigin)) {
655
+ // If allowedOrigin is an array, check if the request's origin is in the array
656
+ if (requestOrigin && this.allowedOrigin.includes(requestOrigin)) {
657
+ res.set('Access-Control-Allow-Origin', requestOrigin);
658
+ }
659
+ sendResponse(req, res);
660
+ } else if (typeof this.allowedOrigin === 'function' && requestOrigin) {
661
+ // If allowedOrigin is a function, call it with the request's origin
662
+ this.allowedOrigin(requestOrigin, (err, allow) => {
663
+ if (err) {
664
+ next(err);
665
+ } else if (allow) {
666
+ res.set('Access-Control-Allow-Origin', requestOrigin);
667
+ }
668
+ sendResponse(req, res);
669
+ });
670
+ } else {
671
+ sendResponse(req, res);
672
+ }
673
+ };
674
+ }
553
675
  }
554
676
 
555
677
  function parseString(input: any): string {
@@ -573,4 +695,14 @@ function extractResults(obj: any): any {
573
695
  else {
574
696
  return obj;
575
697
  }
698
+ }
699
+
700
+ interface OptionsConfiguration {
701
+ intendedForGet(): ResponseConfiguration;
702
+ intendedForPost(...contentTypes: string[]): ResponseConfiguration;
703
+ }
704
+
705
+ interface ResponseConfiguration {
706
+ returningContent(): void;
707
+ returningNoContent(): void;
576
708
  }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export { AuthenticationDevice } from "./authentication/authentication-device";
2
2
  export { AuthenticationSession } from "./authentication/authentication-session";
3
3
  export { AuthorizationKeystore } from "./authorization/authorization-keystore";
4
4
  export { DistributedFactCache } from "./authorization/distributed-fact-cache";
5
+ export { createLineReader } from "./http/line-reader";
5
6
  export { HttpRouter, RequestUser } from "./http/router";
6
7
  export { Stream } from "./http/stream";
7
8
  export { JinagaServer, JinagaServerConfig, JinagaServerInstance, tracePool } from "./jinaga-server";
@@ -38,6 +38,7 @@ export type JinagaServerConfig = {
38
38
  model?: Model,
39
39
  authorization?: (a: AuthorizationRules) => AuthorizationRules,
40
40
  distribution?: (d: DistributionRules) => DistributionRules,
41
+ origin?: string | string[] | ((origin: string, callback: (err: Error | null, allow?: boolean) => void) => void),
41
42
  };
42
43
 
43
44
  export type JinagaServerInstance = {
@@ -67,7 +68,7 @@ export class JinagaServer {
67
68
  const network = new NetworkNoOp();
68
69
  const factManager = new FactManager(fork, source, store, network);
69
70
  const authorization = createAuthorization(authorizationRules, distributionRules, factManager, store, keystore);
70
- const router = new HttpRouter(factManager, authorization, feedCache);
71
+ const router = new HttpRouter(factManager, authorization, feedCache, config.origin || '*');
71
72
  const j: Jinaga = new Jinaga(authentication, factManager, syncStatusNotifier);
72
73
 
73
74
  async function close() {
@@ -1,10 +1,9 @@
1
- import { computeHash, FactEnvelope, FactRecord, PredecessorCollection, UserIdentity } from "jinaga";
2
- import { pki } from "node-forge";
1
+ import { computeHash, FactEnvelope, FactRecord, generateKeyPair, KeyPair, PredecessorCollection, UserIdentity } from "jinaga";
3
2
 
4
3
  import { Keystore } from "../keystore";
5
4
 
6
5
  export class MemoryKeystore implements Keystore {
7
- private keyPairs: { [key: string]: { publicKey: string, privateKey: string }} = {};
6
+ private keyPairs: { [key: string]: KeyPair} = {};
8
7
 
9
8
  getOrCreateUserFact(userIdentity: UserIdentity): Promise<FactRecord> {
10
9
  return Promise.resolve(this.getOrCreateIdentityFact('Jinaga.User', userIdentity));
@@ -55,7 +54,7 @@ export class MemoryKeystore implements Keystore {
55
54
  const key = `${userIdentity.provider}:${userIdentity.id}`;
56
55
  const keyPair = this.keyPairs[key];
57
56
  if (keyPair) {
58
- return keyPair.publicKey;
57
+ return keyPair.publicPem;
59
58
  }
60
59
  else {
61
60
  return this.generateKeyPair(key);
@@ -66,7 +65,7 @@ export class MemoryKeystore implements Keystore {
66
65
  const key = `${userIdentity.provider}:${userIdentity.id}`;
67
66
  const keyPair = this.keyPairs[key];
68
67
  if (keyPair) {
69
- return keyPair.publicKey;
68
+ return keyPair.publicPem;
70
69
  }
71
70
  else {
72
71
  throw new Error("Public key not found");
@@ -74,10 +73,8 @@ export class MemoryKeystore implements Keystore {
74
73
  }
75
74
 
76
75
  private generateKeyPair(key: string) {
77
- const keypair = pki.rsa.generateKeyPair({ bits: 1024 });
78
- const privateKey = pki.privateKeyToPem(keypair.privateKey);
79
- const publicKey = pki.publicKeyToPem(keypair.publicKey);
80
- this.keyPairs[key] = { publicKey, privateKey };
81
- return publicKey;
76
+ const keyPair = generateKeyPair();
77
+ this.keyPairs[key] = keyPair;
78
+ return keyPair.privatePem;
82
79
  }
83
80
  }
@@ -1,15 +1,9 @@
1
- import { canonicalizeFact, computeHash, FactEnvelope, FactRecord, PredecessorCollection, Trace, UserIdentity } from "jinaga";
2
- import { md, pki, util } from "node-forge";
1
+ import { computeHash, FactEnvelope, FactRecord, generateKeyPair, KeyPair, PredecessorCollection, signFacts, UserIdentity } from "jinaga";
3
2
  import { Pool, PoolClient } from "pg";
4
3
 
5
4
  import { Keystore } from "../keystore";
6
5
  import { ConnectionFactory } from "./connection";
7
6
 
8
- interface KeyPair {
9
- publicPem: string;
10
- privatePem: string;
11
- };
12
-
13
7
  export class PostgresKeystore implements Keystore {
14
8
  private connectionFactory: ConnectionFactory;
15
9
  private cache: Map<string, KeyPair> = new Map();
@@ -39,10 +33,8 @@ export class PostgresKeystore implements Keystore {
39
33
  return facts.map(fact => ({ fact, signatures: [] }));
40
34
  }
41
35
 
42
- const { publicPem, privatePem } = await this.getKeyPair(userIdentity);
43
- const privateKey = <pki.rsa.PrivateKey>pki.privateKeyFromPem(privatePem);
44
- const envelopes: FactEnvelope[] = facts.map(fact => signFact(fact, publicPem, privateKey));
45
- return envelopes;
36
+ const keyPair = await this.getKeyPair(userIdentity);
37
+ return signFacts(keyPair, facts);
46
38
  }
47
39
 
48
40
  private async getOrCreateIdentityFact(type: string, identity: UserIdentity): Promise<FactRecord> {
@@ -121,37 +113,13 @@ export class PostgresKeystore implements Keystore {
121
113
  }
122
114
 
123
115
  private async generateKeyPair(connection: PoolClient, userIdentity: UserIdentity): Promise<KeyPair> {
124
- const keypair = pki.rsa.generateKeyPair({ bits: 2048 });
125
- const privatePem = pki.privateKeyToPem(keypair.privateKey);
126
- const publicPem = pki.publicKeyToPem(keypair.publicKey);
116
+ const keyPair = generateKeyPair();
127
117
  await connection.query(`INSERT INTO ${this.schema}.user (provider, user_identifier, private_key, public_key) VALUES ($1, $2, $3, $4)`,
128
- [userIdentity.provider, userIdentity.id, privatePem, publicPem]);
129
- return { publicPem, privatePem };
118
+ [userIdentity.provider, userIdentity.id, keyPair.privatePem, keyPair.publicPem]);
119
+ return keyPair;
130
120
  }
131
121
  }
132
122
 
133
123
  function getUserIdentityKey(userIdentity: UserIdentity) {
134
124
  return `${userIdentity.provider}-${userIdentity.id}`;
135
125
  }
136
-
137
- function signFact(fact: FactRecord, publicPem: string, privateKey: pki.rsa.PrivateKey): FactEnvelope {
138
- const canonicalString = canonicalizeFact(fact.fields, fact.predecessors);
139
- const encodedString = util.encodeUtf8(canonicalString);
140
- const digest = md.sha512.create().update(encodedString);
141
- const hash = util.encode64(digest.digest().getBytes());
142
- if (fact.hash !== hash) {
143
- Trace.error(`Hash does not match. "${fact.hash}" !== "${hash}"\nFact: ${canonicalString}`);
144
- return {
145
- fact,
146
- signatures: []
147
- };
148
- }
149
- const signature = util.encode64(privateKey.sign(digest));
150
- return {
151
- fact,
152
- signatures: [{
153
- signature,
154
- publicKey: publicPem
155
- }]
156
- };
157
- }
@@ -1,11 +0,0 @@
1
- import { FactEnvelope } from "jinaga";
2
- export declare class GraphSerializer {
3
- private readonly write;
4
- private index;
5
- private indexByFactReference;
6
- private publicKeys;
7
- constructor(write: (chunk: string) => void);
8
- serialize(result: FactEnvelope[]): void;
9
- private getPredecessorIndexes;
10
- private getFactIndex;
11
- }
@@ -1,68 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GraphSerializer = void 0;
4
- class GraphSerializer {
5
- constructor(write) {
6
- this.write = write;
7
- this.index = 0;
8
- this.indexByFactReference = {};
9
- this.publicKeys = [];
10
- }
11
- serialize(result) {
12
- // Write the facts
13
- for (const fact of result) {
14
- // Skip facts that have already been written
15
- const key = fact.fact.type + ":" + fact.fact.hash;
16
- if (this.indexByFactReference.hasOwnProperty(key)) {
17
- continue;
18
- }
19
- // Write any new public keys
20
- for (const signature of fact.signatures) {
21
- if (!this.publicKeys.includes(signature.publicKey)) {
22
- const pkIndex = this.publicKeys.length;
23
- const publicKey = JSON.stringify(signature.publicKey);
24
- this.write(`PK${pkIndex.toString()}\n${publicKey}\n\n`);
25
- this.publicKeys.push(signature.publicKey);
26
- }
27
- }
28
- // Write the fact
29
- const factType = JSON.stringify(fact.fact.type);
30
- const predecessorIndexes = JSON.stringify(this.getPredecessorIndexes(fact.fact.predecessors));
31
- const factFields = JSON.stringify(fact.fact.fields);
32
- let output = `${factType}\n${predecessorIndexes}\n${factFields}`;
33
- // Write the signatures
34
- for (const signature of fact.signatures) {
35
- const publicKeyIndex = this.publicKeys.indexOf(signature.publicKey);
36
- const publicKey = `PK${publicKeyIndex.toString()}`;
37
- const signatureString = JSON.stringify(signature.signature);
38
- output += `\n${publicKey}\n${signatureString}`;
39
- }
40
- output += "\n\n";
41
- this.write(output);
42
- this.indexByFactReference[key] = this.index;
43
- this.index++;
44
- }
45
- }
46
- getPredecessorIndexes(predecessors) {
47
- const result = {};
48
- for (const role in predecessors) {
49
- const reference = predecessors[role];
50
- if (Array.isArray(reference)) {
51
- result[role] = reference.map(r => this.getFactIndex(r));
52
- }
53
- else {
54
- result[role] = this.getFactIndex(reference);
55
- }
56
- }
57
- return result;
58
- }
59
- getFactIndex(reference) {
60
- const key = reference.type + ":" + reference.hash;
61
- if (!this.indexByFactReference.hasOwnProperty(key)) {
62
- throw new Error(`Fact reference not found in graph: ${key}`);
63
- }
64
- return this.indexByFactReference[key];
65
- }
66
- }
67
- exports.GraphSerializer = GraphSerializer;
68
- //# sourceMappingURL=serializer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../src/http/serializer.ts"],"names":[],"mappings":"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"}
@@ -1,81 +0,0 @@
1
- import { FactEnvelope, FactReference, PredecessorCollection } from "jinaga";
2
-
3
- type IndexPredecessorCollection = {
4
- [role: string]: number | number[];
5
- };
6
-
7
- export class GraphSerializer
8
- {
9
- private index = 0;
10
- private indexByFactReference: { [key: string]: number } = {};
11
- private publicKeys: string[] = [];
12
-
13
- constructor(
14
- private readonly write: (chunk: string) => void
15
- ) {}
16
-
17
- serialize(result: FactEnvelope[]) {
18
- // Write the facts
19
- for (const fact of result) {
20
- // Skip facts that have already been written
21
- const key = fact.fact.type + ":" + fact.fact.hash;
22
- if (this.indexByFactReference.hasOwnProperty(key)) {
23
- continue;
24
- }
25
-
26
- // Write any new public keys
27
- for (const signature of fact.signatures) {
28
- if (!this.publicKeys.includes(signature.publicKey)) {
29
- const pkIndex = this.publicKeys.length;
30
- const publicKey = JSON.stringify(signature.publicKey);
31
- this.write(`PK${pkIndex.toString()}\n${publicKey}\n\n`);
32
- this.publicKeys.push(signature.publicKey);
33
- }
34
- }
35
-
36
- // Write the fact
37
- const factType = JSON.stringify(fact.fact.type);
38
- const predecessorIndexes = JSON.stringify(this.getPredecessorIndexes(fact.fact.predecessors));
39
- const factFields = JSON.stringify(fact.fact.fields);
40
-
41
- let output = `${factType}\n${predecessorIndexes}\n${factFields}`;
42
-
43
- // Write the signatures
44
- for (const signature of fact.signatures) {
45
- const publicKeyIndex = this.publicKeys.indexOf(signature.publicKey);
46
- const publicKey = `PK${publicKeyIndex.toString()}`;
47
- const signatureString = JSON.stringify(signature.signature);
48
-
49
- output += `\n${publicKey}\n${signatureString}`;
50
- }
51
-
52
- output += "\n\n";
53
-
54
- this.write(output);
55
-
56
- this.indexByFactReference[key] = this.index;
57
- this.index++;
58
- }
59
- }
60
-
61
- private getPredecessorIndexes(predecessors: PredecessorCollection): IndexPredecessorCollection {
62
- const result: IndexPredecessorCollection = {};
63
- for (const role in predecessors) {
64
- const reference = predecessors[role];
65
- if (Array.isArray(reference)) {
66
- result[role] = reference.map(r => this.getFactIndex(r));
67
- } else {
68
- result[role] = this.getFactIndex(reference);
69
- }
70
- }
71
- return result;
72
- }
73
-
74
- private getFactIndex(reference: FactReference): number {
75
- const key = reference.type + ":" + reference.hash;
76
- if (!this.indexByFactReference.hasOwnProperty(key)) {
77
- throw new Error(`Fact reference not found in graph: ${key}`);
78
- }
79
- return this.indexByFactReference[key];
80
- }
81
- }