@dbos-inc/knex-datasource 3.0.7-preview.gfe77addda7 → 3.0.8-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();
@@ -43,13 +44,19 @@ describe('KnexDataSource', () => {
43
44
  }
44
45
 
45
46
  await KnexDataSource.initializeInternalSchema(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 {
@@ -211,23 +336,22 @@ async function insertFunction(user: string) {
211
336
  .returning('greet_count');
212
337
  const row = rows.length > 0 ? rows[0] : undefined;
213
338
 
214
- return { user, greet_count: row?.greet_count };
339
+ return { user, greet_count: row?.greet_count, now: Date.now() };
215
340
  }
216
341
 
217
342
  async function errorFunction(user: string) {
218
- const result = await insertFunction(user);
219
- throw new Error('test error');
220
- return result;
343
+ const _result = await insertFunction(user);
344
+ throw new Error(`test error ${Date.now()}`);
221
345
  }
222
346
 
223
347
  async function readFunction(user: string) {
224
348
  const row = await KnexDataSource.client<greetings>('greetings').select('greet_count').where('name', user).first();
225
- return { user, greet_count: row?.greet_count };
349
+ return { user, greet_count: row?.greet_count, now: Date.now() };
226
350
  }
227
351
 
228
- const regInsertFunction = dataSource.registerTransaction(insertFunction, 'insertFunction');
229
- const regErrorFunction = dataSource.registerTransaction(errorFunction, 'errorFunction');
230
- const regReadFunction = dataSource.registerTransaction(readFunction, 'readFunction', { readOnly: true });
352
+ const regInsertFunction = dataSource.registerTransaction(insertFunction);
353
+ const regErrorFunction = dataSource.registerTransaction(errorFunction);
354
+ const regReadFunction = dataSource.registerTransaction(readFunction, { readOnly: true });
231
355
 
232
356
  class StaticClass {
233
357
  static async insertFunction(user: string) {
@@ -239,8 +363,8 @@ class StaticClass {
239
363
  }
240
364
  }
241
365
 
242
- StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction, 'insertFunction');
243
- StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, 'readFunction');
366
+ StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction);
367
+ StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, { readOnly: true });
244
368
 
245
369
  class InstanceClass {
246
370
  async insertFunction(user: string) {
@@ -255,12 +379,11 @@ class InstanceClass {
255
379
  InstanceClass.prototype.insertFunction = dataSource.registerTransaction(
256
380
  // eslint-disable-next-line @typescript-eslint/unbound-method
257
381
  InstanceClass.prototype.insertFunction,
258
- 'insertFunction',
259
382
  );
260
383
  InstanceClass.prototype.readFunction = dataSource.registerTransaction(
261
384
  // eslint-disable-next-line @typescript-eslint/unbound-method
262
385
  InstanceClass.prototype.readFunction,
263
- 'readFunction',
386
+ { readOnly: true },
264
387
  );
265
388
 
266
389
  async function insertWorkflowReg(user: string) {
@@ -300,8 +423,8 @@ async function instanceWorkflow(user: string) {
300
423
  return [result, readResult];
301
424
  }
302
425
 
303
- const regInsertWorfklowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
304
- const regInsertWorfklowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
426
+ const regInsertWorkflowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
427
+ const regInsertWorkflowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
305
428
  const regErrorWorkflowReg = DBOS.registerWorkflow(errorWorkflowReg, 'errorWorkflowReg');
306
429
  const regErrorWorkflowRunTx = DBOS.registerWorkflow(errorWorkflowRunTx, 'errorWorkflowRunTx');
307
430
  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
  }