jinaga-server 1.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.
Files changed (127) hide show
  1. package/LICENSE +22 -0
  2. package/dist/authentication/authentication-device.d.ts +20 -0
  3. package/dist/authentication/authentication-device.js +61 -0
  4. package/dist/authentication/authentication-device.js.map +1 -0
  5. package/dist/authentication/authentication-impl.d.ts +21 -0
  6. package/dist/authentication/authentication-impl.js +61 -0
  7. package/dist/authentication/authentication-impl.js.map +1 -0
  8. package/dist/authentication/authentication-session.d.ts +23 -0
  9. package/dist/authentication/authentication-session.js +85 -0
  10. package/dist/authentication/authentication-session.js.map +1 -0
  11. package/dist/authentication/authentication.d.ts +5 -0
  12. package/dist/authentication/authentication.js +3 -0
  13. package/dist/authentication/authentication.js.map +1 -0
  14. package/dist/authentication.d.ts +17 -0
  15. package/dist/authentication.js +52 -0
  16. package/dist/authentication.js.map +1 -0
  17. package/dist/authorization/authorization-keystore.d.ts +13 -0
  18. package/dist/authorization/authorization-keystore.js +61 -0
  19. package/dist/authorization/authorization-keystore.js.map +1 -0
  20. package/dist/http/node-http.d.ts +7 -0
  21. package/dist/http/node-http.js +126 -0
  22. package/dist/http/node-http.js.map +1 -0
  23. package/dist/http/router.d.ts +20 -0
  24. package/dist/http/router.js +223 -0
  25. package/dist/http/router.js.map +1 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +6 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/jinaga-server.d.ts +18 -0
  30. package/dist/jinaga-server.js +101 -0
  31. package/dist/jinaga-server.js.map +1 -0
  32. package/dist/keystore.d.ts +9 -0
  33. package/dist/keystore.js +3 -0
  34. package/dist/keystore.js.map +1 -0
  35. package/dist/memory/memory-keystore.d.ts +16 -0
  36. package/dist/memory/memory-keystore.js +86 -0
  37. package/dist/memory/memory-keystore.js.map +1 -0
  38. package/dist/postgres/connection.d.ts +12 -0
  39. package/dist/postgres/connection.js +83 -0
  40. package/dist/postgres/connection.js.map +1 -0
  41. package/dist/postgres/edge-record.d.ts +9 -0
  42. package/dist/postgres/edge-record.js +30 -0
  43. package/dist/postgres/edge-record.js.map +1 -0
  44. package/dist/postgres/maps.d.ts +21 -0
  45. package/dist/postgres/maps.js +86 -0
  46. package/dist/postgres/maps.js.map +1 -0
  47. package/dist/postgres/postgres-keystore.d.ts +20 -0
  48. package/dist/postgres/postgres-keystore.js +168 -0
  49. package/dist/postgres/postgres-keystore.js.map +1 -0
  50. package/dist/postgres/postgres-store.d.ts +20 -0
  51. package/dist/postgres/postgres-store.js +717 -0
  52. package/dist/postgres/postgres-store.js.map +1 -0
  53. package/dist/postgres/query-description-builder.d.ts +13 -0
  54. package/dist/postgres/query-description-builder.js +104 -0
  55. package/dist/postgres/query-description-builder.js.map +1 -0
  56. package/dist/postgres/query-description.d.ts +71 -0
  57. package/dist/postgres/query-description.js +256 -0
  58. package/dist/postgres/query-description.js.map +1 -0
  59. package/dist/postgres/specification-result-sql.d.ts +41 -0
  60. package/dist/postgres/specification-result-sql.js +260 -0
  61. package/dist/postgres/specification-result-sql.js.map +1 -0
  62. package/dist/postgres/specification-sql.d.ts +3 -0
  63. package/dist/postgres/specification-sql.js +119 -0
  64. package/dist/postgres/specification-sql.js.map +1 -0
  65. package/dist/postgres/sql.d.ts +10 -0
  66. package/dist/postgres/sql.js +396 -0
  67. package/dist/postgres/sql.js.map +1 -0
  68. package/dist/util/fn.d.ts +6 -0
  69. package/dist/util/fn.js +81 -0
  70. package/dist/util/fn.js.map +1 -0
  71. package/dist/util/promise.d.ts +1 -0
  72. package/dist/util/promise.js +10 -0
  73. package/dist/util/promise.js.map +1 -0
  74. package/docs/registrations.sql +22 -0
  75. package/docs/specification.txt +41 -0
  76. package/docs/sql-generator.gv +25 -0
  77. package/integration-test/docker-compose.yaml +13 -0
  78. package/integration-test/jinaga-test/Dockerfile +12 -0
  79. package/integration-test/jinaga-test/jinaga.test.js +425 -0
  80. package/integration-test/jinaga-test/package-lock.json +6767 -0
  81. package/integration-test/jinaga-test/package.json +25 -0
  82. package/integration-test/postgres/0-init-app-db.sh +16 -0
  83. package/integration-test/postgres/1-setup.sql +219 -0
  84. package/integration-test/postgres/2-grant.sh +17 -0
  85. package/integration-test/postgres/Dockerfile +5 -0
  86. package/integration-test/run.sh +17 -0
  87. package/integration-test/start_db.sh +2 -0
  88. package/integration-test/stop_db.sh +3 -0
  89. package/integration-test/test.sh +8 -0
  90. package/interactive/index.js +56 -0
  91. package/jest.config.js +12 -0
  92. package/package.json +51 -0
  93. package/scripts/combined.sql +46 -0
  94. package/scripts/cte.sql +55 -0
  95. package/scripts/separate.sql +27 -0
  96. package/setup.sh +34 -0
  97. package/setup.sql +329 -0
  98. package/src/authentication/authentication-device.ts +68 -0
  99. package/src/authentication/authentication-impl.ts +59 -0
  100. package/src/authentication/authentication-session.ts +95 -0
  101. package/src/authentication/authentication.ts +6 -0
  102. package/src/authentication.ts +45 -0
  103. package/src/authorization/authorization-keystore.ts +67 -0
  104. package/src/http/node-http.ts +106 -0
  105. package/src/http/router.ts +237 -0
  106. package/src/index.ts +1 -0
  107. package/src/jinaga-server.ts +125 -0
  108. package/src/keystore.ts +10 -0
  109. package/src/memory/memory-keystore.ts +93 -0
  110. package/src/postgres/connection.ts +73 -0
  111. package/src/postgres/edge-record.ts +36 -0
  112. package/src/postgres/maps.ts +89 -0
  113. package/src/postgres/postgres-keystore.ts +167 -0
  114. package/src/postgres/postgres-store.ts +874 -0
  115. package/src/postgres/query-description-builder.ts +116 -0
  116. package/src/postgres/query-description.ts +385 -0
  117. package/src/postgres/specification-result-sql.ts +362 -0
  118. package/src/postgres/specification-sql.ts +138 -0
  119. package/src/postgres/sql.ts +482 -0
  120. package/src/util/fn.ts +63 -0
  121. package/src/util/promise.ts +5 -0
  122. package/test/authorization/authorizationSpec.ts +209 -0
  123. package/test/postgres/postgresSpec.ts +385 -0
  124. package/test/postgres/postgresSpecificationSpec.ts +742 -0
  125. package/test/reporter.js +25 -0
  126. package/tsconfig.json +25 -0
  127. package/upgrade.sh +21 -0
@@ -0,0 +1,106 @@
1
+ import * as http from "http";
2
+ import * as https from "https";
3
+ import { HttpConnection, HttpResponse } from "jinaga";
4
+ import * as url from "url";
5
+
6
+ export class NodeHttpConnection implements HttpConnection {
7
+ constructor(
8
+ private url: string) {
9
+ }
10
+
11
+ get(path: string): Promise<{}> {
12
+ return new Promise<{}>((resolve, reject) => {
13
+ const { protocol } = url.parse(this.url + path);
14
+ const get = protocol === "https:" ? https.get : http.get;
15
+ let data = "";
16
+ get(this.url + path, response => {
17
+ response.on("data", chunk => {
18
+ data += chunk;
19
+ });
20
+
21
+ response.on("end", () => {
22
+ try {
23
+ const result = JSON.parse(data);
24
+ resolve(result);
25
+ }
26
+ catch (x) {
27
+ reject(x);
28
+ }
29
+ });
30
+ }).on("error", err => reject(err));
31
+ });
32
+ }
33
+
34
+ post(tail: string, body: {}, timeoutSeconds: number): Promise<HttpResponse> {
35
+ return new Promise<HttpResponse>((resolve, reject) => {
36
+ const { protocol, host, port, path } = url.parse(this.url + tail);
37
+ const request = protocol === "https:" ? https.request : http.request;
38
+ let response = "";
39
+ const req = request({
40
+ method: "POST",
41
+ host, port, path,
42
+ headers: {
43
+ "Accept": "application/json",
44
+ "Content-Type": "application/json"
45
+ },
46
+ timeout: timeoutSeconds * 1000
47
+ }, res => {
48
+ res.on("data", chunk => {
49
+ if (chunk instanceof Buffer) {
50
+ response += chunk.toString();
51
+ }
52
+ else {
53
+ response += chunk;
54
+ }
55
+ });
56
+ res.on("error", err => {
57
+ resolve({
58
+ result: "failure",
59
+ error: err.message
60
+ });
61
+ });
62
+ res.on("end", () => {
63
+ if (res.statusCode === 403) {
64
+ reject(response);
65
+ }
66
+ else if (res.statusCode>= 400) {
67
+ resolve({
68
+ result: "retry",
69
+ error: response
70
+ });
71
+ }
72
+ else if (isJsonResult(res.headers["content-type"])) {
73
+ try {
74
+ const json = JSON.parse(response);
75
+ resolve({
76
+ result: "success",
77
+ response: json
78
+ });
79
+ }
80
+ catch (x) {
81
+ reject(x);
82
+ }
83
+ }
84
+ else {
85
+ reject(response);
86
+ }
87
+ });
88
+ });
89
+
90
+ req.on("error", err => {
91
+ resolve({
92
+ result: "failure",
93
+ error: err.message
94
+ });
95
+ });
96
+
97
+ req.write(JSON.stringify(body));
98
+ req.end();
99
+ });
100
+ }
101
+ }
102
+
103
+ function isJsonResult(contentType: string) {
104
+ const json = contentType.split(";").indexOf("application/json");
105
+ return json >= 0;
106
+ }
@@ -0,0 +1,237 @@
1
+ import { Handler, Router } from "express";
2
+ import {
3
+ Authorization,
4
+ Declaration,
5
+ FactRecord,
6
+ Forbidden,
7
+ fromDescriptiveString,
8
+ LoadMessage,
9
+ LoadResponse,
10
+ ProfileMessage,
11
+ QueryMessage,
12
+ QueryResponse,
13
+ SaveMessage,
14
+ SaveResponse,
15
+ SpecificationParser,
16
+ Trace,
17
+ UserIdentity,
18
+ } from "jinaga";
19
+
20
+ function get<U>(method: ((req: RequestUser) => Promise<U>)): Handler {
21
+ return (req, res, next) => {
22
+ const user = <RequestUser>req.user;
23
+ if (!user) {
24
+ res.sendStatus(401);
25
+ }
26
+ else {
27
+ method(user)
28
+ .then(response => {
29
+ res.setHeader('Content-Type', 'application/json');
30
+ res.send(JSON.stringify(response));
31
+ next();
32
+ })
33
+ .catch(error => {
34
+ Trace.error(error);
35
+ res.sendStatus(500);
36
+ next();
37
+ });
38
+ }
39
+ };
40
+ }
41
+
42
+ function post<T, U>(method: (user: RequestUser, message: T) => Promise<U>): Handler {
43
+ return (req, res, next) => {
44
+ const user = <RequestUser>req.user;
45
+ const message = <T>req.body;
46
+ if (!message) {
47
+ throw new Error('Ensure that you have called app.use(express.json()).');
48
+ }
49
+ method(user, message)
50
+ .then(response => {
51
+ res.setHeader('Content-Type', 'application/json');
52
+ res.send(JSON.stringify(response));
53
+ next();
54
+ })
55
+ .catch(error => {
56
+ if (error instanceof Forbidden) {
57
+ Trace.warn(error.message);
58
+ res.setHeader('Content-Type', 'text/plain');
59
+ res.status(403).send(error.message);
60
+ }
61
+ else {
62
+ Trace.error(error);
63
+ res.setHeader('Content-Type', 'text/plain');
64
+ res.sendStatus(500).send(error.message);
65
+ }
66
+ next();
67
+ });
68
+ };
69
+ }
70
+
71
+ function postString<U>(method: (user: RequestUser, message: string) => Promise<U>): Handler {
72
+ return (req, res, next) => {
73
+ const user = <RequestUser>req.user;
74
+ const input = <string>req.body;
75
+ if (!input || typeof(input) !== 'string') {
76
+ res.setHeader('Content-Type', 'text/plain');
77
+ res.status(500).send('Expected Content-Type text/plain. Ensure that you have called app.use(express.text()).');
78
+ }
79
+ else {
80
+ method(user, input)
81
+ .then(response => {
82
+ res.setHeader('Content-Type', 'application/json');
83
+ res.send(JSON.stringify(response, null, 2));
84
+ next();
85
+ })
86
+ .catch(error => {
87
+ if (error instanceof Forbidden) {
88
+ Trace.warn(error.message);
89
+ res.setHeader('Content-Type', 'text/plain');
90
+ res.status(403).send(error.message);
91
+ }
92
+ else {
93
+ Trace.error(error);
94
+ res.setHeader('Content-Type', 'text/plain');
95
+ res.status(400).send(error.message);
96
+ }
97
+ next();
98
+ });
99
+ }
100
+ };
101
+ }
102
+
103
+ function serializeUserIdentity(user: RequestUser) : UserIdentity {
104
+ if (!user) {
105
+ return null;
106
+ }
107
+ return {
108
+ provider: user.provider,
109
+ id: user.id
110
+ };
111
+ }
112
+
113
+ export interface RequestUser {
114
+ provider: string;
115
+ id: string;
116
+ profile: ProfileMessage;
117
+ }
118
+
119
+ export class HttpRouter {
120
+ handler: Handler;
121
+
122
+ constructor(private authorization: Authorization) {
123
+ const router = Router();
124
+ router.get('/login', get(user => this.login(user)));
125
+ router.post('/query', post((user, queryMessage: QueryMessage) => this.query(user, queryMessage)));
126
+ router.post('/load', post((user, loadMessage: LoadMessage) => this.load(user, loadMessage)));
127
+ router.post('/save', post((user, saveMessage: SaveMessage) => this.save(user, saveMessage)));
128
+
129
+ 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)));
132
+
133
+ this.handler = router;
134
+ }
135
+
136
+ private async login(user: RequestUser) {
137
+ const userFact = await this.authorization.getOrCreateUserFact({
138
+ provider: user.provider,
139
+ id: user.id
140
+ });
141
+ return {
142
+ userFact: userFact,
143
+ profile: user.profile
144
+ };
145
+ }
146
+
147
+ private async query(user: RequestUser, queryMessage: QueryMessage) : Promise<QueryResponse> {
148
+ const userIdentity = serializeUserIdentity(user);
149
+ const query = fromDescriptiveString(queryMessage.query);
150
+ const result = await this.authorization.query(userIdentity, queryMessage.start, query);
151
+ return {
152
+ results: result
153
+ };
154
+ }
155
+
156
+ private async load(user: RequestUser, loadMessage: LoadMessage) : Promise<LoadResponse> {
157
+ const userIdentity = serializeUserIdentity(user);
158
+ const result = await this.authorization.load(userIdentity, loadMessage.references);
159
+ return {
160
+ facts: result
161
+ };
162
+ }
163
+
164
+ private async save(user: RequestUser, saveMessage: SaveMessage) : Promise<SaveResponse> {
165
+ const userIdentity = serializeUserIdentity(user);
166
+ await this.authorization.save(userIdentity, saveMessage.facts);
167
+ return {};
168
+ }
169
+
170
+ private async read(user: RequestUser, input: string): Promise<any[]> {
171
+ const knownFacts = await this.getKnownFacts(user);
172
+ const parser = new SpecificationParser(input);
173
+ 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
+ });
185
+
186
+ const userIdentity = serializeUserIdentity(user);
187
+ const results = await this.authorization.read(userIdentity, start, specification);
188
+ return results;
189
+ }
190
+
191
+ private async write(user: RequestUser, input: string): Promise<SaveResponse> {
192
+ const knownFacts = await this.getKnownFacts(user);
193
+ const parser = new SpecificationParser(input);
194
+ parser.skipWhitespace();
195
+ var declaration = parser.parseDeclaration(knownFacts);
196
+
197
+ const factRecords: FactRecord[] = [];
198
+ for (const value of declaration) {
199
+ if (!value.declared.fact) {
200
+ throw new Error("References are not allowed while saving.");
201
+ }
202
+ factRecords.push(value.declared.fact);
203
+ }
204
+
205
+ const userIdentity = serializeUserIdentity(user);
206
+ await this.authorization.save(userIdentity, factRecords);
207
+ return {};
208
+ }
209
+
210
+ private feed(user: RequestUser, input: string): Promise<string> {
211
+ throw new Error("Method not implemented.");
212
+ }
213
+
214
+ private async getKnownFacts(user: RequestUser): Promise<Declaration> {
215
+ if (user) {
216
+ const userFact = await this.authorization.getOrCreateUserFact({
217
+ provider: user.provider,
218
+ id: user.id
219
+ });
220
+ return [
221
+ {
222
+ name: "me",
223
+ declared: {
224
+ fact: userFact,
225
+ reference: {
226
+ type: userFact.type,
227
+ hash: userFact.hash
228
+ }
229
+ }
230
+ }
231
+ ];
232
+ }
233
+ else {
234
+ return [];
235
+ }
236
+ }
237
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { JinagaServer, JinagaServerConfig, JinagaServerInstance } from "./jinaga-server";
@@ -0,0 +1,125 @@
1
+ import { Handler, Request } from "express";
2
+ import {
3
+ Authorization,
4
+ AuthorizationNoOp,
5
+ AuthorizationRules,
6
+ Cache,
7
+ Feed,
8
+ FeedImpl,
9
+ Fork,
10
+ Jinaga,
11
+ MemoryStore,
12
+ PassThroughFork,
13
+ Storage,
14
+ SyncStatusNotifier,
15
+ TransientFork,
16
+ UserIdentity,
17
+ WebClient,
18
+ } from "jinaga";
19
+
20
+ import { AuthenticationDevice } from "./authentication/authentication-device";
21
+ import { AuthenticationSession } from "./authentication/authentication-session";
22
+ import { AuthorizationKeystore } from "./authorization/authorization-keystore";
23
+ import { NodeHttpConnection } from "./http/node-http";
24
+ import { HttpRouter, RequestUser } from "./http/router";
25
+ import { Keystore } from "./keystore";
26
+ import { PostgresKeystore } from "./postgres/postgres-keystore";
27
+ import { PostgresStore } from "./postgres/postgres-store";
28
+
29
+
30
+ export type JinagaServerConfig = {
31
+ pgStore?: string,
32
+ pgKeystore?: string,
33
+ httpEndpoint?: string,
34
+ authorization?: (a: AuthorizationRules) => AuthorizationRules,
35
+ httpTimeoutSeconds?: number
36
+ };
37
+
38
+ export type JinagaServerInstance = {
39
+ handler: Handler,
40
+ j: Jinaga,
41
+ withSession: (req: Request, callback: ((j: Jinaga) => Promise<void>)) => Promise<void>,
42
+ close: () => Promise<void>
43
+ };
44
+
45
+ const localDeviceIdentity = {
46
+ provider: 'jinaga',
47
+ id: 'local'
48
+ };
49
+
50
+ export class JinagaServer {
51
+ static create(config: JinagaServerConfig): JinagaServerInstance {
52
+ const syncStatusNotifier = new SyncStatusNotifier();
53
+ const store = createStore(config);
54
+ const feed = new FeedImpl(store);
55
+ const fork = createFork(config, feed, syncStatusNotifier);
56
+ const keystore = new PostgresKeystore(config.pgKeystore);
57
+ const authorizationRules = config.authorization ? config.authorization(new AuthorizationRules()) : null;
58
+ const authorization = createAuthorization(authorizationRules, fork, keystore);
59
+ const router = new HttpRouter(authorization);
60
+ const authentication = new AuthenticationDevice(fork, keystore, localDeviceIdentity);
61
+ const memory = new MemoryStore();
62
+ const j: Jinaga = new Jinaga(authentication, memory, syncStatusNotifier);
63
+
64
+ async function close() {
65
+ await keystore.close();
66
+ await store.close();
67
+ }
68
+ return {
69
+ handler: router.handler,
70
+ j,
71
+ withSession: (req, callback) => {
72
+ return withSession(feed, keystore, authorizationRules, req, callback);
73
+ },
74
+ close
75
+ }
76
+ }
77
+ }
78
+
79
+ function createStore(config: JinagaServerConfig): Storage {
80
+ if (config.pgStore) {
81
+ const store = new PostgresStore(config.pgStore);
82
+ const cache = new Cache(store);
83
+ return cache;
84
+ }
85
+ else {
86
+ return new MemoryStore();
87
+ }
88
+ }
89
+
90
+ function createFork(config: JinagaServerConfig, feed: Feed, syncStatusNotifier: SyncStatusNotifier): Fork {
91
+ if (config.httpEndpoint) {
92
+ const httpConnection = new NodeHttpConnection(config.httpEndpoint);
93
+ const httpTimeoutSeconds = config.httpTimeoutSeconds || 5;
94
+ const webClient = new WebClient(httpConnection, syncStatusNotifier, {
95
+ timeoutSeconds: httpTimeoutSeconds
96
+ });
97
+ const fork = new TransientFork(feed, webClient);
98
+ return fork;
99
+ }
100
+ else {
101
+ return new PassThroughFork(feed);
102
+ }
103
+ }
104
+
105
+ function createAuthorization(authorizationRules: AuthorizationRules | null, feed: Feed, keystore: Keystore | null): Authorization {
106
+ if (keystore) {
107
+ const authorization = new AuthorizationKeystore(feed, keystore, authorizationRules);
108
+ return authorization;
109
+ }
110
+ else {
111
+ return new AuthorizationNoOp(feed);
112
+ }
113
+ }
114
+
115
+ async function withSession(feed: Feed, keystore: Keystore, authorizationRules: AuthorizationRules | null, req: Request, callback: ((j: Jinaga) => Promise<void>)) {
116
+ const user = <RequestUser>req.user;
117
+ const userIdentity: UserIdentity = {
118
+ provider: user.provider,
119
+ id: user.id
120
+ }
121
+ const authentication = new AuthenticationSession(feed, keystore, authorizationRules, userIdentity, user.profile.displayName, localDeviceIdentity);
122
+ const syncStatusNotifier = new SyncStatusNotifier();
123
+ const j = new Jinaga(authentication, new MemoryStore(), syncStatusNotifier);
124
+ await callback(j);
125
+ }
@@ -0,0 +1,10 @@
1
+ import { FactEnvelope, FactRecord, UserIdentity } from "jinaga";
2
+
3
+ export interface Keystore {
4
+ close(): Promise<void>;
5
+ getOrCreateUserFact(userIdentity: UserIdentity): Promise<FactRecord>;
6
+ getOrCreateDeviceFact(userIdentity: UserIdentity): Promise<FactRecord>;
7
+ getUserFact(userIdentity: UserIdentity): Promise<FactRecord>;
8
+ getDeviceFact(userIdentity: UserIdentity): Promise<FactRecord>;
9
+ signFacts(userIdentity: UserIdentity, facts: FactRecord[]): Promise<FactEnvelope[]>;
10
+ }
@@ -0,0 +1,93 @@
1
+ import { computeHash, FactEnvelope, FactRecord, PredecessorCollection, UserIdentity } from "jinaga";
2
+ import { pki } from "node-forge";
3
+
4
+ import { Keystore } from "../keystore";
5
+
6
+ export class MemoryKeystore implements Keystore {
7
+ private keyPairs: { [key: string]: { publicKey: string, privateKey: string }} = {};
8
+
9
+ close(): Promise<void> {
10
+ return Promise.resolve();
11
+ }
12
+
13
+ getOrCreateUserFact(userIdentity: UserIdentity): Promise<FactRecord> {
14
+ return Promise.resolve(this.getOrCreateIdentityFact('Jinaga.User', userIdentity));
15
+ }
16
+
17
+ getOrCreateDeviceFact(userIdentity: UserIdentity): Promise<FactRecord> {
18
+ return Promise.resolve(this.getOrCreateIdentityFact('Jinaga.Device', userIdentity));
19
+ }
20
+
21
+ getUserFact(userIdentity: UserIdentity): Promise<FactRecord> {
22
+ return Promise.resolve(this.getIdentityFact('Jinaga.User', userIdentity));
23
+ }
24
+
25
+ getDeviceFact(userIdentity: UserIdentity): Promise<FactRecord> {
26
+ return Promise.resolve(this.getIdentityFact('Jinaga.Device', userIdentity));
27
+ }
28
+
29
+ signFacts(userIdentity: UserIdentity, facts: FactRecord[]): Promise<FactEnvelope[]> {
30
+ return Promise.resolve(facts.map(fact => {
31
+ return {
32
+ fact,
33
+ signatures: []
34
+ };
35
+ }));
36
+ }
37
+
38
+ private getOrCreateIdentityFact(type: string, identity: UserIdentity): FactRecord {
39
+ if (!identity) {
40
+ return null;
41
+ }
42
+ const publicKey = this.getOrCreatePublicKey(identity);
43
+ const predecessors: PredecessorCollection = {};
44
+ const fields = {
45
+ publicKey: publicKey
46
+ };
47
+ const hash = computeHash(fields, predecessors);
48
+ return { type, hash, predecessors, fields };
49
+ }
50
+
51
+ private getIdentityFact(type: string, identity: UserIdentity): FactRecord {
52
+ if (!identity) {
53
+ return null;
54
+ }
55
+ const publicKey = this.getPublicKey(identity);
56
+ const predecessors: PredecessorCollection = {};
57
+ const fields = {
58
+ publicKey: publicKey
59
+ };
60
+ const hash = computeHash(fields, predecessors);
61
+ return { type, hash, predecessors, fields };
62
+ }
63
+
64
+ private getOrCreatePublicKey(userIdentity: UserIdentity): string {
65
+ const key = `${userIdentity.provider}:${userIdentity.id}`;
66
+ const keyPair = this.keyPairs[key];
67
+ if (keyPair) {
68
+ return keyPair.publicKey;
69
+ }
70
+ else {
71
+ return this.generateKeyPair(key);
72
+ }
73
+ }
74
+
75
+ private getPublicKey(userIdentity: UserIdentity): string {
76
+ const key = `${userIdentity.provider}:${userIdentity.id}`;
77
+ const keyPair = this.keyPairs[key];
78
+ if (keyPair) {
79
+ return keyPair.publicKey;
80
+ }
81
+ else {
82
+ throw new Error("Public key not found");
83
+ }
84
+ }
85
+
86
+ private generateKeyPair(key: string) {
87
+ const keypair = pki.rsa.generateKeyPair({ bits: 1024 });
88
+ const privateKey = pki.privateKeyToPem(keypair.privateKey);
89
+ const publicKey = pki.publicKeyToPem(keypair.publicKey);
90
+ this.keyPairs[key] = { publicKey, privateKey };
91
+ return publicKey;
92
+ }
93
+ }
@@ -0,0 +1,73 @@
1
+ import { Pool, PoolClient } from 'pg';
2
+ import { delay } from "../util/promise";
3
+
4
+ export type Row = { [key: string]: any };
5
+
6
+ export class ConnectionFactory {
7
+ private postgresPool: Pool;
8
+
9
+ constructor (postgresUri: string) {
10
+ this.postgresPool = new Pool({
11
+ connectionString: postgresUri
12
+ });
13
+ }
14
+
15
+ async close() {
16
+ await this.postgresPool.end();
17
+ }
18
+
19
+ withTransaction<T>(callback: (connection: PoolClient) => Promise<T>) {
20
+ return this.with(async connection => {
21
+ try {
22
+ await connection.query('BEGIN');
23
+ const result = await callback(connection);
24
+ await connection.query('COMMIT');
25
+ return result;
26
+ }
27
+ catch (e) {
28
+ await connection.query('ROLLBACK');
29
+ throw e;
30
+ }
31
+ })
32
+ }
33
+
34
+ async with<T>(callback: (connection: PoolClient) => Promise<T>) {
35
+ let attempt = 0;
36
+ const pause = [0, 0, 1000, 5000, 15000, 30000];
37
+ while (attempt < pause.length) {
38
+ try {
39
+ const client = await this.createClient();
40
+ try {
41
+ return await callback(client);
42
+ }
43
+ finally {
44
+ client.release();
45
+ }
46
+ }
47
+ catch (e) {
48
+ if (!isTransientError(e)) {
49
+ throw e;
50
+ }
51
+ attempt++;
52
+ if (attempt === pause.length) {
53
+ throw e;
54
+ }
55
+ }
56
+ if (pause[attempt] > 0) {
57
+ await delay(pause[attempt]);
58
+ }
59
+ }
60
+ }
61
+
62
+ private async createClient() {
63
+ return await this.postgresPool.connect();
64
+ }
65
+ }
66
+
67
+ function isTransientError(e: any) {
68
+ if (e.code === 'ECONNREFUSED') {
69
+ return true;
70
+ }
71
+ console.error("Postgres error:", e);
72
+ return false;
73
+ }
@@ -0,0 +1,36 @@
1
+ import { FactRecord, FactReference } from "jinaga";
2
+
3
+ export type EdgeRecord = {
4
+ predecessor_hash: string,
5
+ predecessor_type: string,
6
+ successor_hash: string,
7
+ successor_type: string,
8
+ role: string
9
+ };
10
+
11
+ function makeEdgeRecord(predecessor: FactReference, successor: FactRecord, role: string): EdgeRecord {
12
+ if (!predecessor.hash || !predecessor.type || !successor.hash || !successor.type) {
13
+ throw new Error('Attempting to save edge with null hash or type.');
14
+ }
15
+ return {
16
+ predecessor_hash: predecessor.hash,
17
+ predecessor_type: predecessor.type,
18
+ successor_hash: successor.hash,
19
+ successor_type: successor.type,
20
+ role
21
+ };
22
+ }
23
+
24
+ export function makeEdgeRecords(fact: FactRecord): EdgeRecord[] {
25
+ let records: EdgeRecord[] = [];
26
+ for (const role in fact.predecessors) {
27
+ const predecessor = fact.predecessors[role];
28
+ if (Array.isArray(predecessor)) {
29
+ records = records.concat(predecessor.map(p => makeEdgeRecord(p, fact, role)));
30
+ }
31
+ else {
32
+ records.push(makeEdgeRecord(predecessor, fact, role));
33
+ }
34
+ }
35
+ return records;
36
+ }