@dbos-inc/pgnotifier-receiver 3.0.35-preview

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.
@@ -0,0 +1,246 @@
1
+ import { DBOS, DBOSConfig } from '@dbos-inc/dbos-sdk';
2
+
3
+ import { DBTrigger, TriggerOperation } from '../src';
4
+ import { ClientBase, Pool, PoolClient } from 'pg';
5
+ import { KnexDataSource } from '@dbos-inc/knex-datasource';
6
+
7
+ const config = {
8
+ host: process.env.PGHOST || 'localhost',
9
+ port: parseInt(process.env.PGPORT || '5432'),
10
+ database: process.env.PGDATABASE || 'postgres',
11
+ user: process.env.PGUSER || 'postgres',
12
+ password: process.env.PGPASSWORD || 'dbos',
13
+ };
14
+ const pool = new Pool(config);
15
+
16
+ const kconfig = { client: 'pg', connection: config };
17
+
18
+ const knexds = new KnexDataSource('app', kconfig);
19
+
20
+ const trig = new DBTrigger({
21
+ connect: async () => {
22
+ const conn = pool.connect();
23
+ return conn;
24
+ },
25
+ disconnect: async (c: ClientBase) => {
26
+ (c as PoolClient).release();
27
+ return Promise.resolve();
28
+ },
29
+ query: async <R>(sql: string, params?: unknown[]) => {
30
+ return (await pool.query(sql, params)).rows as R[];
31
+ },
32
+ });
33
+
34
+ function sleepms(ms: number) {
35
+ return new Promise((r) => setTimeout(r, ms));
36
+ }
37
+
38
+ const testTableName = 'dbos_test_trig_seq';
39
+
40
+ class DBOSTriggerTestClassSN {
41
+ static nTSUpdates = 0;
42
+ static tsRecordMap: Map<number, TestTable> = new Map();
43
+
44
+ static nSNUpdates = 0;
45
+ static snRecordMap: Map<number, TestTable> = new Map();
46
+
47
+ static reset() {
48
+ DBOSTriggerTestClassSN.nTSUpdates = 0;
49
+ DBOSTriggerTestClassSN.tsRecordMap = new Map();
50
+
51
+ DBOSTriggerTestClassSN.nSNUpdates = 0;
52
+ DBOSTriggerTestClassSN.snRecordMap = new Map();
53
+ }
54
+
55
+ @trig.triggerWorkflow({
56
+ tableName: testTableName,
57
+ recordIDColumns: ['order_id'],
58
+ sequenceNumColumn: 'seqnum',
59
+ sequenceNumJitter: 2,
60
+ installDBTrigger: true,
61
+ })
62
+ @DBOS.workflow()
63
+ static async triggerWFBySeq(op: TriggerOperation, key: number[], rec: unknown) {
64
+ DBOS.logger.debug(`WFSN ${op} - ${JSON.stringify(key)} / ${JSON.stringify(rec)}`);
65
+ expect(op).toBe(TriggerOperation.RecordUpserted);
66
+ if (op === TriggerOperation.RecordUpserted) {
67
+ DBOSTriggerTestClassSN.snRecordMap.set(key[0], rec as TestTable);
68
+ ++DBOSTriggerTestClassSN.nSNUpdates;
69
+ }
70
+ return Promise.resolve();
71
+ }
72
+
73
+ @trig.triggerWorkflow({
74
+ tableName: testTableName,
75
+ recordIDColumns: ['order_id'],
76
+ timestampColumn: 'update_date',
77
+ timestampSkewMS: 60000,
78
+ installDBTrigger: true,
79
+ })
80
+ @DBOS.workflow()
81
+ static async triggerWFByTS(op: TriggerOperation, key: number[], rec: unknown) {
82
+ DBOS.logger.debug(`WFTS ${op} - ${JSON.stringify(key)} / ${JSON.stringify(rec)}`);
83
+ expect(op).toBe(TriggerOperation.RecordUpserted);
84
+ if (op === TriggerOperation.RecordUpserted) {
85
+ DBOSTriggerTestClassSN.tsRecordMap.set(key[0], rec as TestTable);
86
+ ++DBOSTriggerTestClassSN.nTSUpdates;
87
+ }
88
+ return Promise.resolve();
89
+ }
90
+
91
+ @knexds.transaction()
92
+ static async insertRecord(rec: TestTable) {
93
+ await knexds.client<TestTable>(testTableName).insert(rec);
94
+ }
95
+
96
+ @knexds.transaction()
97
+ static async deleteRecord(order_id: number) {
98
+ await knexds.client<TestTable>(testTableName).where({ order_id }).delete();
99
+ }
100
+
101
+ @knexds.transaction()
102
+ static async updateRecordStatus(order_id: number, status: string, seqnum: number, update_date: Date) {
103
+ await knexds.client<TestTable>(testTableName).where({ order_id }).update({ status, seqnum, update_date });
104
+ }
105
+ }
106
+
107
+ interface TestTable {
108
+ order_id: number;
109
+ seqnum: number;
110
+ update_date: Date;
111
+ price: number;
112
+ item: string;
113
+ status: string;
114
+ }
115
+
116
+ describe('test-db-triggers', () => {
117
+ beforeAll(async () => {});
118
+
119
+ beforeEach(async () => {
120
+ await KnexDataSource.initializeDBOSSchema(kconfig);
121
+ const config: DBOSConfig = {
122
+ name: 'dbtrig_seq',
123
+ };
124
+ DBOS.setConfig(config);
125
+ await trig.db.query(`DROP TABLE IF EXISTS ${testTableName};`);
126
+ await trig.db.query(`
127
+ CREATE TABLE IF NOT EXISTS ${testTableName}(
128
+ order_id SERIAL PRIMARY KEY,
129
+ seqnum INTEGER,
130
+ update_date TIMESTAMP,
131
+ price DECIMAL(10,2),
132
+ item TEXT,
133
+ status VARCHAR(10)
134
+ );`);
135
+ DBOSTriggerTestClassSN.reset();
136
+ await DBOS.launch();
137
+ });
138
+
139
+ afterEach(async () => {
140
+ await DBOS.shutdown();
141
+ await trig.db.query(`DROP TABLE IF EXISTS ${testTableName};`);
142
+ });
143
+
144
+ test('trigger-seqnum', async () => {
145
+ await DBOSTriggerTestClassSN.insertRecord({
146
+ order_id: 1,
147
+ seqnum: 1,
148
+ update_date: new Date('2024-01-01 11:11:11'),
149
+ price: 10,
150
+ item: 'Spacely Sprocket',
151
+ status: 'Ordered',
152
+ });
153
+ await DBOSTriggerTestClassSN.updateRecordStatus(1, 'Packed', 2, new Date('2024-01-01 11:11:12'));
154
+ while (DBOSTriggerTestClassSN.nSNUpdates < 2 || DBOSTriggerTestClassSN.nTSUpdates < 2) await sleepms(10);
155
+ expect(DBOSTriggerTestClassSN.nSNUpdates).toBe(2);
156
+ expect(DBOSTriggerTestClassSN.nTSUpdates).toBe(2);
157
+ expect(DBOSTriggerTestClassSN.snRecordMap.get(1)?.status).toBe('Packed');
158
+ expect(DBOSTriggerTestClassSN.tsRecordMap.get(1)?.status).toBe('Packed');
159
+
160
+ await DBOSTriggerTestClassSN.insertRecord({
161
+ order_id: 2,
162
+ seqnum: 3,
163
+ update_date: new Date('2024-01-01 11:11:13'),
164
+ price: 10,
165
+ item: 'Cogswell Cog',
166
+ status: 'Ordered',
167
+ });
168
+ await DBOSTriggerTestClassSN.updateRecordStatus(1, 'Shipped', 5, new Date('2024-01-01 11:11:15'));
169
+ while (DBOSTriggerTestClassSN.nSNUpdates < 4 || DBOSTriggerTestClassSN.nTSUpdates < 4) await sleepms(10);
170
+ expect(DBOSTriggerTestClassSN.nSNUpdates).toBe(4);
171
+ expect(DBOSTriggerTestClassSN.nTSUpdates).toBe(4);
172
+ expect(DBOSTriggerTestClassSN.snRecordMap.get(1)?.status).toBe('Shipped');
173
+ expect(DBOSTriggerTestClassSN.tsRecordMap.get(1)?.status).toBe('Shipped');
174
+ expect(DBOSTriggerTestClassSN.snRecordMap.get(2)?.status).toBe('Ordered');
175
+ expect(DBOSTriggerTestClassSN.tsRecordMap.get(2)?.status).toBe('Ordered');
176
+
177
+ // Take down
178
+ await DBOS.deactivateEventReceivers();
179
+
180
+ // Do more stuff
181
+ // Invalid record, won't show up because it is well out of sequence
182
+ await DBOSTriggerTestClassSN.insertRecord({
183
+ order_id: 999,
184
+ seqnum: -999,
185
+ update_date: new Date('1900-01-01 11:11:13'),
186
+ price: 10,
187
+ item: 'Cogswell Cog',
188
+ status: 'Ordered',
189
+ });
190
+
191
+ // A few more valid records, back in time a little
192
+ await DBOSTriggerTestClassSN.insertRecord({
193
+ order_id: 3,
194
+ seqnum: 4,
195
+ update_date: new Date('2024-01-01 11:11:14'),
196
+ price: 10,
197
+ item: 'Griswold Gear',
198
+ status: 'Ordered',
199
+ });
200
+ await DBOSTriggerTestClassSN.insertRecord({
201
+ order_id: 4,
202
+ seqnum: 6,
203
+ update_date: new Date('2024-01-01 11:11:16'),
204
+ price: 10,
205
+ item: 'Wallace Wheel',
206
+ status: 'Ordered',
207
+ });
208
+ await DBOSTriggerTestClassSN.updateRecordStatus(4, 'Shipped', 7, new Date('2024-01-01 11:11:17'));
209
+
210
+ // Test restore
211
+ console.log(
212
+ '************************************************** Restart *****************************************************',
213
+ );
214
+ DBOSTriggerTestClassSN.reset();
215
+
216
+ await DBOS.initEventReceivers();
217
+
218
+ console.log(
219
+ '************************************************** Restarted *****************************************************',
220
+ );
221
+ DBOSTriggerTestClassSN.reset();
222
+ // We had processed up to 5 before,
223
+ // The count of 7 is a bit confusing because we're sharing a table. We expect all 4 orders to be sent based on time, and 3 based on SN
224
+ while (DBOSTriggerTestClassSN.nSNUpdates < 7 || DBOSTriggerTestClassSN.nTSUpdates < 7) await sleepms(10);
225
+ await sleepms(100);
226
+
227
+ console.log(
228
+ '************************************************** Catchup Complete *****************************************************',
229
+ );
230
+
231
+ expect(DBOSTriggerTestClassSN.nSNUpdates).toBe(7);
232
+ // With 60 seconds, we will see all records again
233
+ expect(DBOSTriggerTestClassSN.nTSUpdates).toBe(7);
234
+
235
+ expect(DBOSTriggerTestClassSN.snRecordMap.get(1)?.status).toBe('Shipped');
236
+ expect(DBOSTriggerTestClassSN.tsRecordMap.get(1)?.status).toBe('Shipped');
237
+ expect(DBOSTriggerTestClassSN.snRecordMap.get(2)?.status).toBe('Ordered');
238
+ expect(DBOSTriggerTestClassSN.tsRecordMap.get(2)?.status).toBe('Ordered');
239
+ expect(DBOSTriggerTestClassSN.snRecordMap.get(3)?.status).toBe('Ordered');
240
+ expect(DBOSTriggerTestClassSN.tsRecordMap.get(3)?.status).toBe('Ordered');
241
+ expect(DBOSTriggerTestClassSN.snRecordMap.get(4)?.status).toBe('Shipped');
242
+ expect(DBOSTriggerTestClassSN.tsRecordMap.get(4)?.status).toBe('Shipped');
243
+ expect(DBOSTriggerTestClassSN.snRecordMap.get(999)?.status).toBeUndefined();
244
+ expect(DBOSTriggerTestClassSN.tsRecordMap.get(999)?.status).toBeUndefined();
245
+ }, 20000);
246
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ /* Visit https://aka.ms/tsconfig to read more about this file */
2
+ {
3
+ "extends": "../../tsconfig.shared.json",
4
+ "compilerOptions": {
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["src/"],
8
+ "exclude": ["dist"]
9
+ }