@dbos-inc/node-pg-datasource 3.0.8-preview.g493d2d1c2b → 3.0.10-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.
@@ -5,13 +5,14 @@ import { dropDB, ensureDB } from './test-helpers';
5
5
  import { randomUUID } from 'crypto';
6
6
  import { SuperJSON } from 'superjson';
7
7
 
8
- const config = { user: 'postgres', database: 'nodepg_ds_test_userdb' };
8
+ const config = { user: 'postgres', database: 'node_pg_ds_test_datasource' };
9
9
  const dataSource = new NodePostgresDataSource('app-db', config);
10
10
 
11
11
  interface transaction_completion {
12
12
  workflow_id: string;
13
13
  function_num: number;
14
14
  output: string | null;
15
+ error: string | null;
15
16
  }
16
17
 
17
18
  describe('NodePostgresDataSource', () => {
@@ -22,9 +23,9 @@ describe('NodePostgresDataSource', () => {
22
23
  const client = new Client({ ...config, database: 'postgres' });
23
24
  try {
24
25
  await client.connect();
25
- await dropDB(client, 'knex_ds_test');
26
- await dropDB(client, 'knex_ds_test_dbos_sys');
27
- await dropDB(client, config.database);
26
+ await dropDB(client, 'node_pg_ds_test', true);
27
+ await dropDB(client, 'node_pg_ds_test_dbos_sys', true);
28
+ await dropDB(client, config.database, true);
28
29
  await ensureDB(client, config.database);
29
30
  } finally {
30
31
  await client.end();
@@ -43,22 +44,28 @@ describe('NodePostgresDataSource', () => {
43
44
  }
44
45
 
45
46
  await NodePostgresDataSource.initializeInternalSchema(config);
46
- DBOS.setConfig({ name: 'knex-ds-test' });
47
- await DBOS.launch();
48
47
  });
49
48
 
50
49
  afterAll(async () => {
51
- await DBOS.shutdown();
52
50
  await userDB.end();
53
51
  });
54
52
 
53
+ beforeEach(async () => {
54
+ DBOS.setConfig({ name: 'node-pg-ds-test' });
55
+ await DBOS.launch();
56
+ });
57
+
58
+ afterEach(async () => {
59
+ await DBOS.shutdown();
60
+ });
61
+
55
62
  test('insert dataSource.register function', async () => {
56
63
  const user = 'helloTest1';
57
64
 
58
65
  await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
59
66
  const workflowID = randomUUID();
60
67
 
61
- await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorfklowReg(user))).resolves.toEqual({
68
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject({
62
69
  user,
63
70
  greet_count: 1,
64
71
  });
@@ -71,7 +78,19 @@ describe('NodePostgresDataSource', () => {
71
78
  expect(rows[0].workflow_id).toBe(workflowID);
72
79
  expect(rows[0].function_num).toBe(0);
73
80
  expect(rows[0].output).not.toBeNull();
74
- expect(SuperJSON.parse(rows[0].output!)).toEqual({ user, greet_count: 1 });
81
+ expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
82
+ });
83
+
84
+ test('rerun insert dataSource.register function', async () => {
85
+ const user = 'rerunTest1';
86
+
87
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
88
+ const workflowID = randomUUID();
89
+
90
+ const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user));
91
+ expect(result).toMatchObject({ user, greet_count: 1 });
92
+
93
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject(result);
75
94
  });
76
95
 
77
96
  test('insert dataSource.runAsTx function', async () => {
@@ -80,7 +99,7 @@ describe('NodePostgresDataSource', () => {
80
99
  await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
81
100
  const workflowID = randomUUID();
82
101
 
83
- await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorfklowRunTx(user))).resolves.toEqual({
102
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user))).resolves.toMatchObject({
84
103
  user,
85
104
  greet_count: 1,
86
105
  });
@@ -93,9 +112,32 @@ describe('NodePostgresDataSource', () => {
93
112
  expect(rows[0].workflow_id).toBe(workflowID);
94
113
  expect(rows[0].function_num).toBe(0);
95
114
  expect(rows[0].output).not.toBeNull();
96
- expect(SuperJSON.parse(rows[0].output!)).toEqual({ user, greet_count: 1 });
115
+ expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
116
+ });
117
+
118
+ test('rerun insert dataSource.runAsTx function', async () => {
119
+ const user = 'rerunTest2';
120
+
121
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
122
+ const workflowID = randomUUID();
123
+
124
+ const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user));
125
+ expect(result).toMatchObject({ user, greet_count: 1 });
126
+
127
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user))).resolves.toMatchObject(
128
+ result,
129
+ );
97
130
  });
98
131
 
132
+ async function throws<R>(func: () => Promise<R>): Promise<unknown> {
133
+ try {
134
+ await func();
135
+ fail('Expected function to throw an error');
136
+ } catch (error) {
137
+ return error;
138
+ }
139
+ }
140
+
99
141
  test('error dataSource.register function', async () => {
100
142
  const user = 'errorTest1';
101
143
 
@@ -103,16 +145,42 @@ describe('NodePostgresDataSource', () => {
103
145
  await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
104
146
  const workflowID = randomUUID();
105
147
 
106
- await expect(DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user))).rejects.toThrow('test error');
107
-
108
- const { rows: txOutput } = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1', [
109
- workflowID,
110
- ]);
111
- expect(txOutput.length).toBe(0);
148
+ const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
149
+ expect(error).toBeInstanceOf(Error);
150
+ expect((error as Error).message).toMatch(/^test error \d+$/);
112
151
 
113
152
  const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
114
153
  expect(rows.length).toBe(1);
115
154
  expect(rows[0].greet_count).toBe(10);
155
+
156
+ const { rows: txOutput } = await userDB.query<transaction_completion>(
157
+ 'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
158
+ [workflowID],
159
+ );
160
+ expect(txOutput.length).toBe(1);
161
+ expect(txOutput[0].workflow_id).toBe(workflowID);
162
+ expect(txOutput[0].function_num).toBe(0);
163
+ expect(txOutput[0].output).toBeNull();
164
+ expect(txOutput[0].error).not.toBeNull();
165
+ const $error = SuperJSON.parse(txOutput[0].error!);
166
+ expect($error).toBeInstanceOf(Error);
167
+ expect(($error as Error).message).toMatch(/^test error \d+$/);
168
+ });
169
+
170
+ test('rerun error dataSource.register function', async () => {
171
+ const user = 'rerunErrorTest1';
172
+
173
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
174
+ await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
175
+ const workflowID = randomUUID();
176
+
177
+ const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
178
+ expect(error).toBeInstanceOf(Error);
179
+ expect((error as Error).message).toMatch(/^test error \d+$/);
180
+
181
+ const error2 = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
182
+ expect(error2).toBeInstanceOf(Error);
183
+ expect((error2 as Error).message).toMatch((error as Error).message);
116
184
  });
117
185
 
118
186
  test('error dataSource.runAsTx function', async () => {
@@ -122,17 +190,42 @@ describe('NodePostgresDataSource', () => {
122
190
  await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
123
191
  const workflowID = randomUUID();
124
192
 
125
- await expect(DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user))).rejects.toThrow('test error');
193
+ const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
194
+ expect(error).toBeInstanceOf(Error);
195
+ expect((error as Error).message).toMatch(/^test error \d+$/);
196
+
197
+ const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
198
+ expect(rows.length).toBe(1);
199
+ expect(rows[0].greet_count).toBe(10);
126
200
 
127
201
  const { rows: txOutput } = await userDB.query<transaction_completion>(
128
202
  'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
129
203
  [workflowID],
130
204
  );
131
- expect(txOutput.length).toBe(0);
205
+ expect(txOutput.length).toBe(1);
206
+ expect(txOutput[0].workflow_id).toBe(workflowID);
207
+ expect(txOutput[0].function_num).toBe(0);
208
+ expect(txOutput[0].output).toBeNull();
209
+ expect(txOutput[0].error).not.toBeNull();
210
+ const $error = SuperJSON.parse(txOutput[0].error!);
211
+ expect($error).toBeInstanceOf(Error);
212
+ expect(($error as Error).message).toMatch(/^test error \d+$/);
213
+ });
132
214
 
133
- const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
134
- expect(rows.length).toBe(1);
135
- expect(rows[0].greet_count).toBe(10);
215
+ test('rerun error dataSource.runAsTx function', async () => {
216
+ const user = 'rerunErrorTest2';
217
+
218
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
219
+ await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
220
+ const workflowID = randomUUID();
221
+
222
+ const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
223
+ expect(error).toBeInstanceOf(Error);
224
+ expect((error as Error).message).toMatch(/^test error \d+$/);
225
+
226
+ const error2 = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
227
+ expect(error2).toBeInstanceOf(Error);
228
+ expect((error2 as Error).message).toMatch((error as Error).message);
136
229
  });
137
230
 
138
231
  test('readonly dataSource.register function', async () => {
@@ -142,15 +235,14 @@ describe('NodePostgresDataSource', () => {
142
235
  await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
143
236
 
144
237
  const workflowID = randomUUID();
145
- await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowReg(user))).resolves.toEqual({
238
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowReg(user))).resolves.toMatchObject({
146
239
  user,
147
240
  greet_count: 10,
148
241
  });
149
242
 
150
- const { rows } = await userDB.query<transaction_completion>(
151
- 'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
152
- [workflowID],
153
- );
243
+ const { rows } = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1', [
244
+ workflowID,
245
+ ]);
154
246
  expect(rows.length).toBe(0);
155
247
  });
156
248
 
@@ -161,15 +253,14 @@ describe('NodePostgresDataSource', () => {
161
253
  await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
162
254
 
163
255
  const workflowID = randomUUID();
164
- await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowRunTx(user))).resolves.toEqual({
256
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowRunTx(user))).resolves.toMatchObject({
165
257
  user,
166
258
  greet_count: 10,
167
259
  });
168
260
 
169
- const { rows } = await userDB.query<transaction_completion>(
170
- 'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
171
- [workflowID],
172
- );
261
+ const { rows } = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1', [
262
+ workflowID,
263
+ ]);
173
264
  expect(rows.length).toBe(0);
174
265
  });
175
266
 
@@ -179,7 +270,7 @@ describe('NodePostgresDataSource', () => {
179
270
  await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
180
271
 
181
272
  const workflowID = randomUUID();
182
- await expect(DBOS.withNextWorkflowID(workflowID, () => regStaticWorkflow(user))).resolves.toEqual([
273
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regStaticWorkflow(user))).resolves.toMatchObject([
183
274
  { user, greet_count: 1 },
184
275
  { user, greet_count: 1 },
185
276
  ]);
@@ -191,11 +282,45 @@ describe('NodePostgresDataSource', () => {
191
282
  await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
192
283
 
193
284
  const workflowID = randomUUID();
194
- await expect(DBOS.withNextWorkflowID(workflowID, () => regInstanceWorkflow(user))).resolves.toEqual([
285
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInstanceWorkflow(user))).resolves.toMatchObject([
195
286
  { user, greet_count: 1 },
196
287
  { user, greet_count: 1 },
197
288
  ]);
198
289
  });
290
+
291
+ test('invoke-reg-tx-fun-outside-wf', async () => {
292
+ const user = 'outsideWfUser' + Date.now();
293
+ const result = await regInsertFunction(user);
294
+ expect(result).toMatchObject({ user, greet_count: 1 });
295
+
296
+ const txResults = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE output LIKE $1', [
297
+ `%${user}%`,
298
+ ]);
299
+ expect(txResults.rows.length).toBe(0);
300
+ });
301
+
302
+ test('invoke-reg-tx-static-method-outside-wf', async () => {
303
+ const user = 'outsideWfUser' + Date.now();
304
+ const result = await StaticClass.insertFunction(user);
305
+ expect(result).toMatchObject({ user, greet_count: 1 });
306
+
307
+ const txResults = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE output LIKE $1', [
308
+ `%${user}%`,
309
+ ]);
310
+ expect(txResults.rows.length).toBe(0);
311
+ });
312
+
313
+ test('invoke-reg-tx-inst-method-outside-wf', async () => {
314
+ const user = 'outsideWfUser' + Date.now();
315
+ const instance = new InstanceClass();
316
+ const result = await instance.insertFunction(user);
317
+ expect(result).toMatchObject({ user, greet_count: 1 });
318
+
319
+ const txResults = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE output LIKE $1', [
320
+ `%${user}%`,
321
+ ]);
322
+ expect(txResults.rows.length).toBe(0);
323
+ });
199
324
  });
200
325
 
201
326
  export interface greetings {
@@ -205,39 +330,36 @@ export interface greetings {
205
330
 
206
331
  async function insertFunction(user: string) {
207
332
  const { rows } = await NodePostgresDataSource.client.query<Pick<greetings, 'greet_count'>>(
208
- `
209
- INSERT INTO greetings(name, greet_count)
210
- VALUES($1, 1)
211
- ON CONFLICT(name)
212
- DO UPDATE SET greet_count = greetings.greet_count + 1
213
- RETURNING greet_count`,
333
+ `INSERT INTO greetings(name, greet_count)
334
+ VALUES($1, 1)
335
+ ON CONFLICT(name)
336
+ DO UPDATE SET greet_count = greetings.greet_count + 1
337
+ RETURNING greet_count`,
214
338
  [user],
215
339
  );
216
340
  const row = rows.length > 0 ? rows[0] : undefined;
217
- return { user, greet_count: row?.greet_count };
341
+ return { user, greet_count: row?.greet_count, now: Date.now() };
218
342
  }
219
343
 
220
344
  async function errorFunction(user: string) {
221
- const result = await insertFunction(user);
222
- throw new Error('test error');
223
- return result;
345
+ const _result = await insertFunction(user);
346
+ throw new Error(`test error ${Date.now()}`);
224
347
  }
225
348
 
226
349
  async function readFunction(user: string) {
227
350
  const { rows } = await NodePostgresDataSource.client.query<Pick<greetings, 'greet_count'>>(
228
- `
229
- SELECT greet_count
230
- FROM greetings
231
- WHERE name = $1`,
351
+ `SELECT greet_count
352
+ FROM greetings
353
+ WHERE name = $1`,
232
354
  [user],
233
355
  );
234
356
  const row = rows.length > 0 ? rows[0] : undefined;
235
- return { user, greet_count: row?.greet_count };
357
+ return { user, greet_count: row?.greet_count, now: Date.now() };
236
358
  }
237
359
 
238
- const regInsertFunction = dataSource.registerTransaction(insertFunction, 'insertFunction');
239
- const regErrorFunction = dataSource.registerTransaction(errorFunction, 'errorFunction');
240
- const regReadFunction = dataSource.registerTransaction(readFunction, 'readFunction', { readOnly: true });
360
+ const regInsertFunction = dataSource.registerTransaction(insertFunction);
361
+ const regErrorFunction = dataSource.registerTransaction(errorFunction);
362
+ const regReadFunction = dataSource.registerTransaction(readFunction, { readOnly: true });
241
363
 
242
364
  class StaticClass {
243
365
  static async insertFunction(user: string) {
@@ -249,8 +371,8 @@ class StaticClass {
249
371
  }
250
372
  }
251
373
 
252
- StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction, 'insertFunction');
253
- StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, 'readFunction');
374
+ StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction);
375
+ StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, { readOnly: true });
254
376
 
255
377
  class InstanceClass {
256
378
  async insertFunction(user: string) {
@@ -265,12 +387,11 @@ class InstanceClass {
265
387
  InstanceClass.prototype.insertFunction = dataSource.registerTransaction(
266
388
  // eslint-disable-next-line @typescript-eslint/unbound-method
267
389
  InstanceClass.prototype.insertFunction,
268
- 'insertFunction',
269
390
  );
270
391
  InstanceClass.prototype.readFunction = dataSource.registerTransaction(
271
392
  // eslint-disable-next-line @typescript-eslint/unbound-method
272
393
  InstanceClass.prototype.readFunction,
273
- 'readFunction',
394
+ { readOnly: true },
274
395
  );
275
396
 
276
397
  async function insertWorkflowReg(user: string) {
@@ -310,8 +431,8 @@ async function instanceWorkflow(user: string) {
310
431
  return [result, readResult];
311
432
  }
312
433
 
313
- const regInsertWorfklowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
314
- const regInsertWorfklowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
434
+ const regInsertWorkflowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
435
+ const regInsertWorkflowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
315
436
  const regErrorWorkflowReg = DBOS.registerWorkflow(errorWorkflowReg, 'errorWorkflowReg');
316
437
  const regErrorWorkflowRunTx = DBOS.registerWorkflow(errorWorkflowRunTx, 'errorWorkflowRunTx');
317
438
  const regReadWorkflowReg = DBOS.registerWorkflow(readWorkflowReg, 'readWorkflowReg');
@@ -7,6 +7,7 @@ export async function ensureDB(client: Client, name: string) {
7
7
  }
8
8
  }
9
9
 
10
- export async function dropDB(client: Client, name: string) {
11
- await client.query(`DROP DATABASE IF EXISTS ${name}`);
10
+ export async function dropDB(client: Client, name: string, force: boolean = false) {
11
+ const withForce = force ? ' WITH (FORCE)' : '';
12
+ await client.query(`DROP DATABASE IF EXISTS ${name} ${withForce}`);
12
13
  }