@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.
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -57
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/index.ts +90 -71
- package/package.json +6 -5
- package/tests/config.test.ts +1 -1
- package/tests/datasource.test.ts +164 -41
- package/tests/test-helpers.ts +3 -2
package/tests/datasource.test.ts
CHANGED
|
@@ -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
|
-
|
|
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, () =>
|
|
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!)).
|
|
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, () =>
|
|
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!)).
|
|
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
|
|
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(
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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.
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
219
|
-
throw new 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
|
|
229
|
-
const regErrorFunction = dataSource.registerTransaction(errorFunction
|
|
230
|
-
const regReadFunction = dataSource.registerTransaction(readFunction,
|
|
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
|
|
243
|
-
StaticClass.readFunction = dataSource.registerTransaction(StaticClass.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
|
-
|
|
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
|
|
304
|
-
const
|
|
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');
|
package/tests/test-helpers.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|