jinaga-server 3.1.5 → 3.3.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 (47) hide show
  1. package/.github/workflows/main.yml +1 -1
  2. package/.github/workflows/publish.yml +3 -3
  3. package/dist/http/router.js +37 -84
  4. package/dist/http/router.js.map +1 -1
  5. package/dist/jinaga-server.d.ts +2 -1
  6. package/dist/jinaga-server.js +18 -4
  7. package/dist/jinaga-server.js.map +1 -1
  8. package/dist/postgres/postgres-store.d.ts +2 -0
  9. package/dist/postgres/postgres-store.js +34 -0
  10. package/dist/postgres/postgres-store.js.map +1 -1
  11. package/dist/postgres/purge-sql.d.ts +7 -0
  12. package/dist/postgres/purge-sql.js +151 -0
  13. package/dist/postgres/purge-sql.js.map +1 -0
  14. package/dist/postgres/query-description.d.ts +1 -1
  15. package/dist/postgres/query-description.js +3 -3
  16. package/dist/postgres/query-description.js.map +1 -1
  17. package/dist/postgres/specification-result-sql.js +1 -1
  18. package/dist/postgres/specification-result-sql.js.map +1 -1
  19. package/dist/postgres/specification-sql.js +2 -2
  20. package/dist/postgres/specification-sql.js.map +1 -1
  21. package/integration-test/docker-compose.yaml +0 -1
  22. package/integration-test/jinaga-test/Dockerfile +2 -0
  23. package/integration-test/jinaga-test/orderModel.js +104 -0
  24. package/integration-test/jinaga-test/package-lock.json +147 -86
  25. package/integration-test/jinaga-test/package.json +3 -3
  26. package/integration-test/jinaga-test/purge.test.js +224 -0
  27. package/integration-test/postgres/1-setup.sql +8 -3
  28. package/integration-test/postgres/2-grant.sh +4 -4
  29. package/integration-test/postgres/Dockerfile +1 -1
  30. package/package.json +5 -4
  31. package/replicator/package-lock.json +139 -71
  32. package/replicator/package.json +2 -2
  33. package/scripts/migrate_purge.sql +45 -0
  34. package/scripts/purge.sql +33 -0
  35. package/setup.sh +4 -4
  36. package/setup.sql +59 -3
  37. package/src/http/router.ts +38 -84
  38. package/src/jinaga-server.ts +27 -5
  39. package/src/postgres/postgres-store.ts +37 -0
  40. package/src/postgres/purge-sql.ts +176 -0
  41. package/src/postgres/query-description.ts +1 -1
  42. package/src/postgres/specification-result-sql.ts +3 -3
  43. package/src/postgres/specification-sql.ts +4 -4
  44. package/test/authorization/authorizationSpec.ts +1 -1
  45. package/test/models/blog.ts +92 -0
  46. package/test/postgres/purgeDescendantsSqlSpec.ts +41 -0
  47. package/test/postgres/purgeSqlSpec.ts +246 -0
@@ -14,11 +14,11 @@
14
14
  "license": "MIT",
15
15
  "dependencies": {
16
16
  "@stablelib/base64": "^1.0.1",
17
- "express": "^4.19.2",
17
+ "express": "^4.21.1",
18
18
  "jest": "^29.7.0",
19
19
  "jest-compact-reporter": "^1.2.9",
20
- "jinaga": "^6.1.7",
20
+ "jinaga": "^6.5.0",
21
21
  "node-forge": "^1.3.0",
22
- "pg": "^8.12.0"
22
+ "pg": "^8.13.1"
23
23
  }
24
24
  }
@@ -0,0 +1,224 @@
1
+ const { JinagaServer } = require("./jinaga-server");
2
+ const { createModel, Item, Order, OrderCancelled, OrderCancelledReason, Product, Store } = require("./orderModel");
3
+
4
+ const host = "db";
5
+ // const host = "localhost";
6
+ const connectionString = `postgresql://dev:devpw@${host}:5432/integrationtest`;
7
+
8
+ const model = createModel();
9
+
10
+ describe("After-the-fact purge", () => {
11
+ let jInit, jPurge;
12
+ let closeInit, closePurge;
13
+
14
+ beforeEach(() => {
15
+ ({ j: jInit, close: closeInit } = JinagaServer.create({
16
+ pgKeystore: connectionString,
17
+ pgStore: connectionString
18
+ }));
19
+
20
+ ({ j: jPurge, close: closePurge } = JinagaServer.create({
21
+ pgKeystore: connectionString,
22
+ pgStore: connectionString,
23
+ purgeConditions: p => p
24
+ .whenExists(model.given(Order).match((order, facts) => facts.ofType(OrderCancelled)
25
+ .join(orderCancelled => orderCancelled.order, order)
26
+ ))
27
+ }));
28
+ });
29
+
30
+ afterEach(async () => {
31
+ await closeInit();
32
+ await closePurge();
33
+ });
34
+
35
+ it("Should find descendants if purge condition is not met", async () => {
36
+ const store = await jInit.fact(new Store("storeId"));
37
+ const order = await jInit.fact(new Order(store, new Date()));
38
+ const item1 = await jInit.fact(new Item(order, new Product(store, "product1"), 1));
39
+ const item2 = await jInit.fact(new Item(order, new Product(store, "product2"), 1));
40
+
41
+ const itemsInOrder = model.given(Order).match(order =>
42
+ order.successors(Item, item => item.order)
43
+ );
44
+
45
+ await jPurge.purge();
46
+ const items = await jPurge.query(itemsInOrder, order);
47
+ expect(items).toEqual([item1, item2]);
48
+ });
49
+
50
+ it("Should purge descendants when condition is met", async () => {
51
+ const store = await jInit.fact(new Store("storeId"));
52
+ const order = await jInit.fact(new Order(store, new Date()));
53
+ const item1 = await jInit.fact(new Item(order, new Product(store, "product1"), 1));
54
+ const item2 = await jInit.fact(new Item(order, new Product(store, "product2"), 1));
55
+ const orderCancelled = await jInit.fact(new OrderCancelled(order, new Date()));
56
+
57
+ const itemsInOrder = model.given(Order).match(order =>
58
+ order.successors(Item, item => item.order)
59
+ );
60
+
61
+ await jPurge.purge();
62
+ const items = await jPurge.query(itemsInOrder, order);
63
+ expect(items).toEqual([]);
64
+ });
65
+
66
+ it("Should not purge the trigger fact", async () => {
67
+ const store = await jInit.fact(new Store("storeId"));
68
+ const order = await jInit.fact(new Order(store, new Date()));
69
+ const item1 = await jInit.fact(new Item(order, new Product(store, "product1"), 1));
70
+ const item2 = await jInit.fact(new Item(order, new Product(store, "product2"), 1));
71
+ const orderCancelled = await jInit.fact(new OrderCancelled(order, new Date()));
72
+
73
+ const cancelOfOrder = model.given(Order).match(order =>
74
+ order.successors(OrderCancelled, cancelled => cancelled.order)
75
+ );
76
+
77
+ await jPurge.purge();
78
+ const cancels = await jPurge.query(cancelOfOrder, order);
79
+ expect(cancels).toEqual([orderCancelled]);
80
+ });
81
+ });
82
+
83
+ describe("Real-time purge", () => {
84
+ let j;
85
+ let close;
86
+
87
+ beforeEach(() => {
88
+ ({ j, close } = JinagaServer.create({
89
+ pgKeystore: connectionString,
90
+ pgStore: connectionString,
91
+ purgeConditions: p => p
92
+ .whenExists(model.given(Order).match((order, facts) => facts.ofType(OrderCancelled)
93
+ .join(orderCancelled => orderCancelled.order, order)
94
+ ))
95
+ }));
96
+ });
97
+
98
+ afterEach(async () => {
99
+ await close();
100
+ });
101
+
102
+ it("Should find descendants if purge condition is not met", async () => {
103
+ const store = await j.fact(new Store("storeId"));
104
+ const order = await j.fact(new Order(store, new Date()));
105
+ const item1 = await j.fact(new Item(order, new Product(store, "product1"), 1));
106
+ const item2 = await j.fact(new Item(order, new Product(store, "product2"), 1));
107
+
108
+ const itemsInOrder = model.given(Order).match(order =>
109
+ order.successors(Item, item => item.order)
110
+ );
111
+
112
+ const items = await j.query(itemsInOrder, order);
113
+ expect(items).toEqual([item1, item2]);
114
+ });
115
+
116
+ it("Should purge descendants when condition is met", async () => {
117
+ const store = await j.fact(new Store("storeId"));
118
+ const order = await j.fact(new Order(store, new Date()));
119
+ const item1 = await j.fact(new Item(order, new Product(store, "product1"), 1));
120
+ const item2 = await j.fact(new Item(order, new Product(store, "product2"), 1));
121
+ const orderCancelled = await j.fact(new OrderCancelled(order, new Date()));
122
+
123
+ const itemsInOrder = model.given(Order).match(order =>
124
+ order.successors(Item, item => item.order)
125
+ );
126
+
127
+ const items = await j.query(itemsInOrder, order);
128
+ expect(items).toEqual([]);
129
+ });
130
+
131
+ it("Should not purge the trigger fact", async () => {
132
+ const store = await j.fact(new Store("storeId"));
133
+ const order = await j.fact(new Order(store, new Date()));
134
+ const item1 = await j.fact(new Item(order, new Product(store, "product1"), 1));
135
+ const item2 = await j.fact(new Item(order, new Product(store, "product2"), 1));
136
+ const orderCancelled = await j.fact(new OrderCancelled(order, new Date()));
137
+
138
+ const cancelOfOrder = model.given(Order).match(order =>
139
+ order.successors(OrderCancelled, cancelled => cancelled.order)
140
+ );
141
+
142
+ const cancels = await j.query(cancelOfOrder, order);
143
+ expect(cancels).toEqual([orderCancelled]);
144
+ });
145
+ });
146
+
147
+ describe("After-the-fact purge with deep trigger", () => {
148
+ let jInit, jPurge;
149
+ let closeInit, closePurge;
150
+
151
+ beforeEach(() => {
152
+ ({ j: jInit, close: closeInit } = JinagaServer.create({
153
+ pgKeystore: connectionString,
154
+ pgStore: connectionString
155
+ }));
156
+
157
+ ({ j: jPurge, close: closePurge } = JinagaServer.create({
158
+ pgKeystore: connectionString,
159
+ pgStore: connectionString,
160
+ purgeConditions: p => p
161
+ .whenExists(model.given(Order).match((order, facts) => facts.ofType(OrderCancelledReason)
162
+ .join(orderCancelledReason => orderCancelledReason.orderCancelled.order, order)
163
+ ))
164
+ }));
165
+ });
166
+
167
+ afterEach(async () => {
168
+ await closeInit();
169
+ await closePurge();
170
+ });
171
+
172
+ it("Should not purge ancestors of the trigger fact", async () => {
173
+ const store = await jInit.fact(new Store("storeId"));
174
+ const order = await jInit.fact(new Order(store, new Date()));
175
+ const item1 = await jInit.fact(new Item(order, new Product(store, "product1"), 1));
176
+ const item2 = await jInit.fact(new Item(order, new Product(store, "product2"), 1));
177
+ const orderCancelled = await jInit.fact(new OrderCancelled(order, new Date()));
178
+ const reason = await jInit.fact(new OrderCancelledReason(orderCancelled, "reason"));
179
+
180
+ const cancelOfOrder = model.given(Order).match(order =>
181
+ order.successors(OrderCancelled, cancelled => cancelled.order)
182
+ );
183
+
184
+ await jPurge.purge();
185
+ const cancels = await jPurge.query(cancelOfOrder, order);
186
+ expect(cancels).toEqual([orderCancelled]);
187
+ });
188
+ });
189
+
190
+ describe("Real-time purge with deep trigger", () => {
191
+ let j;
192
+ let close;
193
+
194
+ beforeEach(() => {
195
+ ({ j, close } = JinagaServer.create({
196
+ pgKeystore: connectionString,
197
+ pgStore: connectionString,
198
+ purgeConditions: p => p
199
+ .whenExists(model.given(Order).match((order, facts) => facts.ofType(OrderCancelledReason)
200
+ .join(orderCancelledReason => orderCancelledReason.orderCancelled.order, order)
201
+ ))
202
+ }));
203
+ });
204
+
205
+ afterEach(async () => {
206
+ await close();
207
+ });
208
+
209
+ it("Should not purge ancestors of the trigger fact", async () => {
210
+ const store = await j.fact(new Store("storeId"));
211
+ const order = await j.fact(new Order(store, new Date()));
212
+ const item1 = await j.fact(new Item(order, new Product(store, "product1"), 1));
213
+ const item2 = await j.fact(new Item(order, new Product(store, "product2"), 1));
214
+ const orderCancelled = await j.fact(new OrderCancelled(order, new Date()));
215
+ const reason = await j.fact(new OrderCancelledReason(orderCancelled, "reason"));
216
+
217
+ const cancelOfOrder = model.given(Order).match(order =>
218
+ order.successors(OrderCancelled, cancelled => cancelled.order)
219
+ );
220
+
221
+ const cancels = await j.query(cancelOfOrder, order);
222
+ expect(cancels).toEqual([orderCancelled]);
223
+ });
224
+ });
@@ -106,11 +106,13 @@ IF (SELECT to_regclass('public.edge') IS NULL) THEN
106
106
  successor_fact_id integer NOT NULL,
107
107
  CONSTRAINT fk_successor_fact_id
108
108
  FOREIGN KEY (successor_fact_id)
109
- REFERENCES fact (fact_id),
109
+ REFERENCES fact (fact_id)
110
+ ON DELETE CASCADE,
110
111
  predecessor_fact_id integer NOT NULL,
111
112
  CONSTRAINT fk_predecessor_fact_id
112
113
  FOREIGN KEY (predecessor_fact_id)
113
114
  REFERENCES fact (fact_id)
115
+ ON DELETE CASCADE
114
116
  );
115
117
 
116
118
 
@@ -135,11 +137,13 @@ IF (SELECT to_regclass('public.ancestor') IS NULL) THEN
135
137
  fact_id integer NOT NULL,
136
138
  CONSTRAINT fk_fact_id
137
139
  FOREIGN KEY (fact_id)
138
- REFERENCES fact (fact_id),
140
+ REFERENCES fact (fact_id)
141
+ ON DELETE CASCADE,
139
142
  ancestor_fact_id integer NOT NULL,
140
143
  CONSTRAINT fk_ancestor_fact_id
141
144
  FOREIGN KEY (ancestor_fact_id)
142
145
  REFERENCES fact (fact_id)
146
+ ON DELETE CASCADE
143
147
  );
144
148
 
145
149
 
@@ -177,7 +181,8 @@ IF (SELECT to_regclass('public.signature') IS NULL) THEN
177
181
  fact_id integer NOT NULL,
178
182
  CONSTRAINT fk_fact_id
179
183
  FOREIGN KEY (fact_id)
180
- REFERENCES fact (fact_id),
184
+ REFERENCES fact (fact_id)
185
+ ON DELETE CASCADE,
181
186
  public_key_id integer NOT NULL,
182
187
  CONSTRAINT fk_public_key_id
183
188
  FOREIGN KEY (public_key_id)
@@ -6,12 +6,12 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$APP_DATABASE" <<-
6
6
  GRANT SELECT, USAGE ON SEQUENCE fact_type_fact_type_id_seq TO $APP_USERNAME;
7
7
  GRANT SELECT,INSERT ON TABLE public.role TO $APP_USERNAME;
8
8
  GRANT SELECT, USAGE ON SEQUENCE role_role_id_seq TO $APP_USERNAME;
9
- GRANT SELECT,INSERT ON TABLE public.fact TO $APP_USERNAME;
9
+ GRANT SELECT,INSERT,DELETE ON TABLE public.fact TO $APP_USERNAME;
10
10
  GRANT SELECT, USAGE ON SEQUENCE fact_fact_id_seq TO $APP_USERNAME;
11
- GRANT SELECT,INSERT ON TABLE public.edge TO $APP_USERNAME;
12
- GRANT SELECT,INSERT ON TABLE public.ancestor TO $APP_USERNAME;
11
+ GRANT SELECT,INSERT,DELETE ON TABLE public.edge TO $APP_USERNAME;
12
+ GRANT SELECT,INSERT,DELETE ON TABLE public.ancestor TO $APP_USERNAME;
13
13
  GRANT SELECT,INSERT ON TABLE public.public_key TO $APP_USERNAME;
14
14
  GRANT SELECT, USAGE ON SEQUENCE public_key_public_key_id_seq TO $APP_USERNAME;
15
- GRANT SELECT,INSERT ON TABLE public.signature TO $APP_USERNAME;
15
+ GRANT SELECT,INSERT,DELETE ON TABLE public.signature TO $APP_USERNAME;
16
16
  GRANT SELECT,INSERT ON TABLE public.user TO $APP_USERNAME;
17
17
  EOSQL
@@ -1,4 +1,4 @@
1
- FROM postgres:17beta3-bookworm
1
+ FROM postgres:17.1-bookworm
2
2
 
3
3
  COPY ./0-init-app-db.sh /docker-entrypoint-initdb.d/
4
4
  COPY ./1-setup.sql /docker-entrypoint-initdb.d/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinaga-server",
3
- "version": "3.1.5",
3
+ "version": "3.3.0",
4
4
  "description": "Data management for web and mobile applications.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,6 +10,7 @@
10
10
  "test": "tsc --noEmit --project tsconfig.test.json && jest",
11
11
  "test:watch": "jest --watch",
12
12
  "debug": "node ./dist/debug.js",
13
+ "prepack": "npm run clean && npm run build && npm run test",
13
14
  "prepublishOnly": "npm run clean && npm run build && npm run test",
14
15
  "integration": "npm run clean && npm run build && cd ./integration-test && ./run.sh",
15
16
  "replicator": "npm run build && cd ./replicator && npm run build && npm run start && cd .."
@@ -46,8 +47,8 @@
46
47
  "typescript": "^4.7.2"
47
48
  },
48
49
  "dependencies": {
49
- "express": "^4.19.2",
50
- "jinaga": "^6.1.7",
51
- "pg": "^8.12.0"
50
+ "express": "^4.21.1",
51
+ "jinaga": "^6.5.0",
52
+ "pg": "^8.13.0"
52
53
  }
53
54
  }