@dbos-inc/knex-datasource 3.0.11-preview.gc9233b8190 → 3.0.16-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.
@@ -12,6 +12,7 @@ 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('KnexDataSource', () => {
@@ -22,9 +23,9 @@ describe('KnexDataSource', () => {
22
23
  const client = new Client({ ...config.connection, 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.connection.database);
26
+ await dropDB(client, 'knex_ds_test', true);
27
+ await dropDB(client, 'knex_ds_test_dbos_sys', true);
28
+ await dropDB(client, config.connection.database, true);
28
29
  await ensureDB(client, config.connection.database);
29
30
  } finally {
30
31
  await client.end();
@@ -42,14 +43,20 @@ describe('KnexDataSource', () => {
42
43
  }
43
44
  }
44
45
 
45
- await KnexDataSource.initializeInternalSchema(config);
46
+ await KnexDataSource.initializeDBOSSchema(config);
47
+ });
48
+
49
+ afterAll(async () => {
50
+ await userDB.end();
51
+ });
52
+
53
+ beforeEach(async () => {
46
54
  DBOS.setConfig({ name: 'knex-ds-test' });
47
55
  await DBOS.launch();
48
56
  });
49
57
 
50
- afterAll(async () => {
58
+ afterEach(async () => {
51
59
  await DBOS.shutdown();
52
- await userDB.end();
53
60
  });
54
61
 
55
62
  test('insert dataSource.register function', async () => {
@@ -58,7 +65,7 @@ describe('KnexDataSource', () => {
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('KnexDataSource', () => {
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('KnexDataSource', () => {
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('KnexDataSource', () => {
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 });
97
116
  });
98
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
+ );
130
+ });
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,17 +145,42 @@ describe('KnexDataSource', () => {
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');
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+$/);
151
+
152
+ const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
153
+ expect(rows.length).toBe(1);
154
+ expect(rows[0].greet_count).toBe(10);
107
155
 
108
156
  const { rows: txOutput } = await userDB.query<transaction_completion>(
109
157
  'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
110
158
  [workflowID],
111
159
  );
112
- expect(txOutput.length).toBe(0);
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
+ });
113
169
 
114
- const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
115
- expect(rows.length).toBe(1);
116
- expect(rows[0].greet_count).toBe(10);
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);
117
184
  });
118
185
 
119
186
  test('error dataSource.runAsTx function', async () => {
@@ -123,17 +190,42 @@ describe('KnexDataSource', () => {
123
190
  await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
124
191
  const workflowID = randomUUID();
125
192
 
126
- 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);
127
200
 
128
201
  const { rows: txOutput } = await userDB.query<transaction_completion>(
129
202
  'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
130
203
  [workflowID],
131
204
  );
132
- 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
+ });
133
214
 
134
- const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
135
- expect(rows.length).toBe(1);
136
- 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);
137
229
  });
138
230
 
139
231
  test('readonly dataSource.register function', async () => {
@@ -143,15 +235,14 @@ describe('KnexDataSource', () => {
143
235
  await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
144
236
 
145
237
  const workflowID = randomUUID();
146
- await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowReg(user))).resolves.toEqual({
238
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowReg(user))).resolves.toMatchObject({
147
239
  user,
148
240
  greet_count: 10,
149
241
  });
150
242
 
151
- const { rows } = await userDB.query<transaction_completion>(
152
- 'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
153
- [workflowID],
154
- );
243
+ const { rows } = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1', [
244
+ workflowID,
245
+ ]);
155
246
  expect(rows.length).toBe(0);
156
247
  });
157
248
 
@@ -162,7 +253,7 @@ describe('KnexDataSource', () => {
162
253
  await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
163
254
 
164
255
  const workflowID = randomUUID();
165
- await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowRunTx(user))).resolves.toEqual({
256
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowRunTx(user))).resolves.toMatchObject({
166
257
  user,
167
258
  greet_count: 10,
168
259
  });
@@ -179,7 +270,7 @@ describe('KnexDataSource', () => {
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('KnexDataSource', () => {
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 {
@@ -204,30 +329,30 @@ export interface greetings {
204
329
  }
205
330
 
206
331
  async function insertFunction(user: string) {
207
- const rows = await KnexDataSource.client<greetings>('greetings')
332
+ const rows = await dataSource
333
+ .client<greetings>('greetings')
208
334
  .insert({ name: user, greet_count: 1 })
209
335
  .onConflict('name')
210
- .merge({ greet_count: KnexDataSource.client.raw('greetings.greet_count + 1') })
336
+ .merge({ greet_count: dataSource.client.raw('greetings.greet_count + 1') })
211
337
  .returning('greet_count');
212
338
  const row = rows.length > 0 ? rows[0] : undefined;
213
339
 
214
- return { user, greet_count: row?.greet_count };
340
+ return { user, greet_count: row?.greet_count, now: Date.now() };
215
341
  }
216
342
 
217
343
  async function errorFunction(user: string) {
218
- const result = await insertFunction(user);
219
- throw new Error('test error');
220
- return result;
344
+ const _result = await insertFunction(user);
345
+ throw new Error(`test error ${Date.now()}`);
221
346
  }
222
347
 
223
348
  async function readFunction(user: string) {
224
349
  const row = await KnexDataSource.client<greetings>('greetings').select('greet_count').where('name', user).first();
225
- return { user, greet_count: row?.greet_count };
350
+ return { user, greet_count: row?.greet_count, now: Date.now() };
226
351
  }
227
352
 
228
- const regInsertFunction = dataSource.registerTransaction(insertFunction, 'insertFunction');
229
- const regErrorFunction = dataSource.registerTransaction(errorFunction, 'errorFunction');
230
- const regReadFunction = dataSource.registerTransaction(readFunction, 'readFunction', { readOnly: true });
353
+ const regInsertFunction = dataSource.registerTransaction(insertFunction);
354
+ const regErrorFunction = dataSource.registerTransaction(errorFunction);
355
+ const regReadFunction = dataSource.registerTransaction(readFunction, { readOnly: true });
231
356
 
232
357
  class StaticClass {
233
358
  static async insertFunction(user: string) {
@@ -239,8 +364,8 @@ class StaticClass {
239
364
  }
240
365
  }
241
366
 
242
- StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction, 'insertFunction');
243
- StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, 'readFunction');
367
+ StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction);
368
+ StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, { readOnly: true });
244
369
 
245
370
  class InstanceClass {
246
371
  async insertFunction(user: string) {
@@ -255,12 +380,11 @@ class InstanceClass {
255
380
  InstanceClass.prototype.insertFunction = dataSource.registerTransaction(
256
381
  // eslint-disable-next-line @typescript-eslint/unbound-method
257
382
  InstanceClass.prototype.insertFunction,
258
- 'insertFunction',
259
383
  );
260
384
  InstanceClass.prototype.readFunction = dataSource.registerTransaction(
261
385
  // eslint-disable-next-line @typescript-eslint/unbound-method
262
386
  InstanceClass.prototype.readFunction,
263
- 'readFunction',
387
+ { readOnly: true },
264
388
  );
265
389
 
266
390
  async function insertWorkflowReg(user: string) {
@@ -268,7 +392,7 @@ async function insertWorkflowReg(user: string) {
268
392
  }
269
393
 
270
394
  async function insertWorkflowRunTx(user: string) {
271
- return await dataSource.runTransaction(() => insertFunction(user), 'insertFunction');
395
+ return await dataSource.runTransaction(() => insertFunction(user), { name: 'insertFunction' });
272
396
  }
273
397
 
274
398
  async function errorWorkflowReg(user: string) {
@@ -276,7 +400,7 @@ async function errorWorkflowReg(user: string) {
276
400
  }
277
401
 
278
402
  async function errorWorkflowRunTx(user: string) {
279
- return await dataSource.runTransaction(() => errorFunction(user), 'errorFunction');
403
+ return await dataSource.runTransaction(() => errorFunction(user), { name: 'errorFunction' });
280
404
  }
281
405
 
282
406
  async function readWorkflowReg(user: string) {
@@ -284,7 +408,7 @@ async function readWorkflowReg(user: string) {
284
408
  }
285
409
 
286
410
  async function readWorkflowRunTx(user: string) {
287
- return await dataSource.runTransaction(() => readFunction(user), 'readFunction', { readOnly: true });
411
+ return await dataSource.runTransaction(() => readFunction(user), { name: 'readFunction', readOnly: true });
288
412
  }
289
413
 
290
414
  async function staticWorkflow(user: string) {
@@ -300,8 +424,8 @@ async function instanceWorkflow(user: string) {
300
424
  return [result, readResult];
301
425
  }
302
426
 
303
- const regInsertWorfklowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
304
- const regInsertWorfklowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
427
+ const regInsertWorkflowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
428
+ const regInsertWorkflowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
305
429
  const regErrorWorkflowReg = DBOS.registerWorkflow(errorWorkflowReg, 'errorWorkflowReg');
306
430
  const regErrorWorkflowRunTx = DBOS.registerWorkflow(errorWorkflowRunTx, 'errorWorkflowRunTx');
307
431
  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
  }