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,425 @@
1
+ import { encode } from "@stablelib/base64";
2
+
3
+ import { ensure, Jinaga, JinagaServer } from "./jinaga";
4
+
5
+ const host = "db";
6
+ // const host = "localhost";
7
+ const connectionString = `postgresql://dev:devpw@${host}:5432/integrationtest`;
8
+
9
+ describe("Jinaga as a device", () => {
10
+ let j;
11
+ let close;
12
+
13
+ beforeEach(() => {
14
+ ({ j, close } = JinagaServer.create({
15
+ pgKeystore: connectionString,
16
+ pgStore: connectionString
17
+ }));
18
+ });
19
+
20
+ afterEach(async () => {
21
+ await close();
22
+ });
23
+
24
+ it("should save a fact", async () => {
25
+ const root = await j.fact(randomRoot());
26
+
27
+ expect(root.type).toEqual("IntegrationTest.Root");
28
+ });
29
+
30
+ it("should save a fact twice", async () => {
31
+ const root = randomRoot();
32
+ await j.fact(root);
33
+ await j.fact(root);
34
+
35
+ expect(root.type).toEqual("IntegrationTest.Root");
36
+ });
37
+
38
+ it("should save a successor fact", async () => {
39
+ const root = await j.fact(randomRoot());
40
+
41
+ const successor = await j.fact({
42
+ type: "IntegrationTest.Successor",
43
+ identifier: "test-successor",
44
+ predecessor: root
45
+ });
46
+
47
+ expect(successor.identifier).toEqual("test-successor");
48
+ expect(successor.predecessor).toEqual(root);
49
+ });
50
+
51
+ it("should query a successor fact", async () => {
52
+ const root = await j.fact(randomRoot());
53
+
54
+ const successor = await j.fact({
55
+ type: "IntegrationTest.Successor",
56
+ identifier: "test-successor",
57
+ predecessor: root
58
+ });
59
+ const successors = await j.query(root, j.for(successorsOfRoot));
60
+
61
+ expect(successors).toEqual([successor]);
62
+ })
63
+
64
+ it("should query a type that has never been seen", async () => {
65
+ const root = await j.fact(randomRoot());
66
+ const unknown = await j.query(root, j.for(unknownOfRoot));
67
+
68
+ expect(unknown).toEqual([]);
69
+ });
70
+
71
+ it("should save a successor fact twice", async () => {
72
+ const root = await j.fact(randomRoot());
73
+
74
+ await j.fact({
75
+ type: "IntegrationTest.Successor",
76
+ identifier: "test-successor",
77
+ predecessor: root
78
+ });
79
+ const successor = await j.fact({
80
+ type: "IntegrationTest.Successor",
81
+ identifier: "test-successor",
82
+ predecessor: root
83
+ });
84
+
85
+ expect(successor.identifier).toEqual("test-successor");
86
+ expect(successor.predecessor).toEqual(root);
87
+ });
88
+
89
+
90
+ it("should save multiple facts", async () => {
91
+ const successor = await j.fact({
92
+ type: "IntegrationTest.Successor",
93
+ identifier: "test-successor",
94
+ predecessor: randomRoot()
95
+ });
96
+
97
+ expect(successor.identifier).toEqual("test-successor");
98
+ expect(successor.predecessor.type).toEqual("IntegrationTest.Root");
99
+ });
100
+
101
+ it("should get the device identity", async () => {
102
+ const device = await j.local();
103
+
104
+ expect(device.type).toEqual("Jinaga.Device");
105
+ });
106
+
107
+ it("should get device information", async () => {
108
+ const device = await j.local();
109
+
110
+ await j.fact({
111
+ type: "Configuration",
112
+ from: device
113
+ });
114
+
115
+ await check(async j => {
116
+ const checkDevice = await j.local();
117
+ expect(checkDevice).toEqual(device);
118
+
119
+ const configurations = await j.query(checkDevice, j.for(configurationFromDevice));
120
+
121
+ expect(configurations.length).toEqual(1);
122
+ expect(configurations[0].type).toEqual("Configuration");
123
+ expect(configurations[0].from.type).toEqual("Jinaga.Device");
124
+ expect(configurations[0].from.publicKey).toEqual(checkDevice.publicKey);
125
+ });
126
+ });
127
+ });
128
+
129
+ describe("Jinaga as a user", () => {
130
+ let j;
131
+ let jDevice;
132
+ let close;
133
+ let done;
134
+ let session;
135
+
136
+ beforeEach(() => {
137
+ const promise = new Promise((resolve) => {
138
+ done = resolve;
139
+ });
140
+ ({ j: jDevice, close, withSession } = JinagaServer.create({
141
+ pgKeystore: connectionString,
142
+ pgStore: connectionString,
143
+ authorization
144
+ }));
145
+ session = withSession({ user: {
146
+ provider: "test",
147
+ id: "test-user",
148
+ profile: {
149
+ displayName: "Test User"
150
+ }
151
+ } }, async jUser => {
152
+ j = jUser;
153
+ await promise;
154
+ });
155
+ });
156
+
157
+ afterEach(async () => {
158
+ done();
159
+ await session;
160
+ await close();
161
+ });
162
+
163
+ it("should get the user identity", async () => {
164
+ const user = await j.login();
165
+
166
+ expect(user.userFact.type).toEqual("Jinaga.User");
167
+ });
168
+
169
+ it("should not allow an unauthorized fact", async () => {
170
+ try {
171
+ await j.fact({
172
+ type: "IntegrationTest.Unauthorized",
173
+ identifier: "test-unauthorized"
174
+ });
175
+ throw new Error("Expected fact to be rejected");
176
+ }
177
+ catch (e) {
178
+ expect(e.message).toEqual("Rejected 1 fact of type IntegrationTest.Unauthorized.");
179
+ }
180
+ });
181
+
182
+ it("should save user name", async () => {
183
+ const { userFact: user, profile } = await j.login();
184
+
185
+ const userName = {
186
+ type: "MyApplication.UserName",
187
+ value: profile.displayName,
188
+ from: user,
189
+ prior: []
190
+ };
191
+ await j.fact(userName);
192
+
193
+ const userNames = await jDevice.query(user, Jinaga.for(namesOfUser));
194
+
195
+ expect(userNames.length).toEqual(1);
196
+ expect(userNames[0].value).toEqual("Test User");
197
+ });
198
+
199
+ it("should set default tenant", async () => {
200
+ const { userFact: user } = await j.login();
201
+ const device = await j.local();
202
+
203
+ const defaultTenant = await j.fact({
204
+ type: "MyApplication.DefaultTenant",
205
+ tenant: {
206
+ type: "MyApplication.Tenant",
207
+ identifier: "test-tenant",
208
+ creator: user
209
+ },
210
+ device
211
+ });
212
+
213
+ const defaultTenants = await jDevice.query(device, Jinaga.for(defaultTenantsOfDevice));
214
+ expect(defaultTenants).toEqual([defaultTenant]);
215
+ });
216
+
217
+ it("should find no memberships", async () => {
218
+ const { userFact: user } = await j.login();
219
+
220
+ const memberships = await j.query(user, Jinaga.for(membershipsForUser));
221
+ expect(memberships).toEqual([]);
222
+ });
223
+
224
+ it("should find assigned membership", async () => {
225
+ const { userFact: user } = await j.login();
226
+
227
+ const membership = await j.fact({
228
+ type: "MyApplication.Membership",
229
+ tenant: {
230
+ type: "MyApplication.Tenant",
231
+ identifier: "test-tenant",
232
+ creator: user
233
+ },
234
+ user
235
+ });
236
+
237
+ const memberships = await j.query(user, Jinaga.for(membershipsForUser));
238
+ expect(memberships).toEqual([membership]);
239
+ });
240
+
241
+ it("should not find deleted membership", async () => {
242
+ const { userFact: user } = await j.login();
243
+
244
+ const membership = await j.fact({
245
+ type: "MyApplication.Membership",
246
+ tenant: {
247
+ type: "MyApplication.Tenant",
248
+ identifier: "test-tenant",
249
+ creator: user
250
+ },
251
+ user
252
+ });
253
+ await j.fact({
254
+ type: "MyApplication.Membership.Deleted",
255
+ membership
256
+ });
257
+
258
+ const memberships = await j.query(user, Jinaga.for(membershipsForUser));
259
+ expect(memberships).toEqual([]);
260
+ });
261
+
262
+ it("should find restored membership", async () => {
263
+ const { userFact: user } = await j.login();
264
+
265
+ const membership = await j.fact({
266
+ type: "MyApplication.Membership",
267
+ tenant: {
268
+ type: "MyApplication.Tenant",
269
+ identifier: "test-tenant",
270
+ creator: user
271
+ },
272
+ user
273
+ });
274
+ const deleted = await j.fact({
275
+ type: "MyApplication.Membership.Deleted",
276
+ membership
277
+ });
278
+ await j.fact({
279
+ type: "MyApplication.Membership.Restored",
280
+ deleted
281
+ });
282
+
283
+ const memberships = await j.query(user, Jinaga.for(membershipsForUser));
284
+ expect(memberships).toEqual([membership]);
285
+ });
286
+ })
287
+
288
+ function randomRoot() {
289
+ const num = Math.random();
290
+ const identifier = encode(num);
291
+
292
+ return {
293
+ type: "IntegrationTest.Root",
294
+ identifier
295
+ };
296
+ }
297
+
298
+ function successorsOfRoot(root) {
299
+ return Jinaga.match({
300
+ type: "IntegrationTest.Successor",
301
+ predecessor: root
302
+ });
303
+ }
304
+
305
+ function unknownOfRoot(root) {
306
+ return Jinaga.match({
307
+ type: "IntegrationTest.UnknownType",
308
+ predecessor: root
309
+ });
310
+ }
311
+
312
+ function configurationFromDevice(device) {
313
+ return Jinaga.match({
314
+ type: "Configuration",
315
+ from: device
316
+ });
317
+ }
318
+
319
+ function namesOfUser(user) {
320
+ return Jinaga.match({
321
+ type: "MyApplication.UserName",
322
+ from: user
323
+ }).suchThat(nameIsCurrent);
324
+ }
325
+
326
+ function nameIsCurrent(name) {
327
+ return Jinaga.notExists({
328
+ type: "MyApplication.UserName",
329
+ prior: [name]
330
+ });
331
+ }
332
+
333
+ function nameUser(name) {
334
+ ensure(name).has("from", "Jinaga.User");
335
+ return Jinaga.match(name.from);
336
+ }
337
+
338
+ function defaultTenantIsCurrent(defaultTenant) {
339
+ return Jinaga.notExists({
340
+ type: "MyApplication.DefaultTenant",
341
+ prior: [defaultTenant]
342
+ });
343
+ }
344
+
345
+ function defaultTenantsOfDevice(device) {
346
+ return Jinaga.match({
347
+ type: "MyApplication.DefaultTenant",
348
+ device
349
+ }).suchThat(defaultTenantIsCurrent);
350
+ }
351
+
352
+ function tenantCreator(tenant) {
353
+ ensure(tenant).has("creator", "Jinaga.User");
354
+ return Jinaga.match(tenant.creator);
355
+ }
356
+
357
+ function defaultTenantCreator(defaultTenant) {
358
+ ensure(defaultTenant)
359
+ .has("tenant", "MyApplication.Tenant")
360
+ .has("creator", "Jinaga.User");
361
+ return Jinaga.match(defaultTenant.tenant.creator);
362
+ }
363
+
364
+ function membershipsForUser(user) {
365
+ return Jinaga.match({
366
+ type: "MyApplication.Membership",
367
+ user
368
+ }).suchThat(Jinaga.not(membershipIsDeleted));
369
+ }
370
+
371
+ function membershipIsDeleted(membership) {
372
+ return Jinaga.exists({
373
+ type: "MyApplication.Membership.Deleted",
374
+ membership
375
+ }).suchThat(Jinaga.not(membershipIsRestored));
376
+ }
377
+
378
+ function membershipIsRestored(deleted) {
379
+ return Jinaga.exists({
380
+ type: "MyApplication.Membership.Restored",
381
+ deleted
382
+ });
383
+ }
384
+
385
+ function tenantOfMembership(membership) {
386
+ ensure(membership).has("tenant", "MyApplication.Tenant");
387
+ return Jinaga.match(membership.tenant);
388
+ }
389
+
390
+ function deletedMembership(deleted) {
391
+ ensure(deleted).has("membership", "MyApplication.Membership");
392
+ return Jinaga.match(deleted.membership);
393
+ }
394
+
395
+ function restoredMembership(restored) {
396
+ ensure(restored)
397
+ .has("deleted", "MyApplication.Membership.Deleted")
398
+ .has("membership", "MyApplication.Membership");
399
+ return Jinaga.match(restored.deleted.membership);
400
+ }
401
+
402
+ async function check(callback) {
403
+ const { j, close } = JinagaServer.create({
404
+ pgKeystore: connectionString,
405
+ pgStore: connectionString
406
+ });
407
+
408
+ try {
409
+ await callback(j);
410
+ }
411
+ finally {
412
+ await close();
413
+ }
414
+ }
415
+
416
+ function authorization(a) {
417
+ return a
418
+ .type("MyApplication.UserName", Jinaga.for(nameUser))
419
+ .type("MyApplication.Tenant", Jinaga.for(tenantCreator))
420
+ .type("MyApplication.DefaultTenant", Jinaga.for(defaultTenantCreator))
421
+ .type("MyApplication.Membership", Jinaga.for(tenantOfMembership).then(tenantCreator))
422
+ .type("MyApplication.Membership.Deleted", Jinaga.for(deletedMembership).then(tenantOfMembership).then(tenantCreator))
423
+ .type("MyApplication.Membership.Restored", Jinaga.for(restoredMembership).then(tenantOfMembership).then(tenantCreator))
424
+ ;
425
+ }