@dbos-inc/kysely-datasource 4.3.6-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 +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +249 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/index.ts +326 -0
- package/jest.config.js +8 -0
- package/package.json +32 -0
- package/tests/config.test.ts +51 -0
- package/tests/datasource.test.ts +487 -0
- package/tests/test-helpers.ts +24 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
2
|
+
import { Client, Pool } from 'pg';
|
|
3
|
+
import { KyselyDataSource } from '..';
|
|
4
|
+
import { Database, dropDB, ensureDB } from './test-helpers';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
import SuperJSON from 'superjson';
|
|
7
|
+
import { sql } from 'kysely';
|
|
8
|
+
|
|
9
|
+
const config = { client: 'pg', connection: { user: 'postgres', database: 'kysely_ds_test_userdb' } };
|
|
10
|
+
const dataSource = new KyselyDataSource<Database>('app-db', config.connection);
|
|
11
|
+
|
|
12
|
+
interface transaction_completion {
|
|
13
|
+
workflow_id: string;
|
|
14
|
+
function_num: number;
|
|
15
|
+
output: string | null;
|
|
16
|
+
error: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('KyselyDataSource', () => {
|
|
20
|
+
const userDB = new Pool(config.connection);
|
|
21
|
+
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
await createDatabases(userDB, true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
await userDB.end();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
DBOS.setConfig({ name: 'kysely-ds-test' });
|
|
32
|
+
await DBOS.launch();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
await DBOS.shutdown();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('insert dataSource.register function', async () => {
|
|
40
|
+
const user = 'helloTest1';
|
|
41
|
+
|
|
42
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
43
|
+
const workflowID = randomUUID();
|
|
44
|
+
|
|
45
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject({
|
|
46
|
+
user,
|
|
47
|
+
greet_count: 1,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const { rows } = await userDB.query<transaction_completion>(
|
|
51
|
+
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
52
|
+
[workflowID],
|
|
53
|
+
);
|
|
54
|
+
expect(rows.length).toBe(1);
|
|
55
|
+
expect(rows[0].workflow_id).toBe(workflowID);
|
|
56
|
+
expect(rows[0].function_num).toBe(0);
|
|
57
|
+
expect(rows[0].output).not.toBeNull();
|
|
58
|
+
expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('rerun insert dataSource.register function', async () => {
|
|
62
|
+
const user = 'rerunTest1';
|
|
63
|
+
|
|
64
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
65
|
+
const workflowID = randomUUID();
|
|
66
|
+
|
|
67
|
+
const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user));
|
|
68
|
+
expect(result).toMatchObject({ user, greet_count: 1 });
|
|
69
|
+
|
|
70
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject(result);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('insert dataSource.runAsTx function', async () => {
|
|
74
|
+
const user = 'helloTest2';
|
|
75
|
+
|
|
76
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
77
|
+
const workflowID = randomUUID();
|
|
78
|
+
|
|
79
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user))).resolves.toMatchObject({
|
|
80
|
+
user,
|
|
81
|
+
greet_count: 1,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const { rows } = await userDB.query<transaction_completion>(
|
|
85
|
+
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
86
|
+
[workflowID],
|
|
87
|
+
);
|
|
88
|
+
expect(rows.length).toBe(1);
|
|
89
|
+
expect(rows[0].workflow_id).toBe(workflowID);
|
|
90
|
+
expect(rows[0].function_num).toBe(0);
|
|
91
|
+
expect(rows[0].output).not.toBeNull();
|
|
92
|
+
expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('rerun insert dataSource.runAsTx function', async () => {
|
|
96
|
+
const user = 'rerunTest2';
|
|
97
|
+
|
|
98
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
99
|
+
const workflowID = randomUUID();
|
|
100
|
+
|
|
101
|
+
const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user));
|
|
102
|
+
expect(result).toMatchObject({ user, greet_count: 1 });
|
|
103
|
+
|
|
104
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user))).resolves.toMatchObject(
|
|
105
|
+
result,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
async function throws<R>(func: () => Promise<R>): Promise<unknown> {
|
|
110
|
+
try {
|
|
111
|
+
await func();
|
|
112
|
+
fail('Expected function to throw an error');
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
test('error dataSource.register function', async () => {
|
|
119
|
+
const user = 'errorTest1';
|
|
120
|
+
|
|
121
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
122
|
+
await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
|
|
123
|
+
const workflowID = randomUUID();
|
|
124
|
+
|
|
125
|
+
const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
|
|
126
|
+
expect(error).toBeInstanceOf(Error);
|
|
127
|
+
expect((error as Error).message).toMatch(/^test error \d+$/);
|
|
128
|
+
|
|
129
|
+
const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
|
|
130
|
+
expect(rows.length).toBe(1);
|
|
131
|
+
expect(rows[0].greet_count).toBe(10);
|
|
132
|
+
|
|
133
|
+
const { rows: txOutput } = await userDB.query<transaction_completion>(
|
|
134
|
+
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
135
|
+
[workflowID],
|
|
136
|
+
);
|
|
137
|
+
expect(txOutput.length).toBe(1);
|
|
138
|
+
expect(txOutput[0].workflow_id).toBe(workflowID);
|
|
139
|
+
expect(txOutput[0].function_num).toBe(0);
|
|
140
|
+
expect(txOutput[0].output).toBeNull();
|
|
141
|
+
expect(txOutput[0].error).not.toBeNull();
|
|
142
|
+
const $error = SuperJSON.parse(txOutput[0].error!);
|
|
143
|
+
expect($error).toBeInstanceOf(Error);
|
|
144
|
+
expect(($error as Error).message).toMatch(/^test error \d+$/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('rerun error dataSource.register function', async () => {
|
|
148
|
+
const user = 'rerunErrorTest1';
|
|
149
|
+
|
|
150
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
151
|
+
await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
|
|
152
|
+
const workflowID = randomUUID();
|
|
153
|
+
|
|
154
|
+
const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
|
|
155
|
+
expect(error).toBeInstanceOf(Error);
|
|
156
|
+
expect((error as Error).message).toMatch(/^test error \d+$/);
|
|
157
|
+
|
|
158
|
+
const error2 = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
|
|
159
|
+
expect(error2).toBeInstanceOf(Error);
|
|
160
|
+
expect((error2 as Error).message).toMatch((error as Error).message);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('error dataSource.runAsTx function', async () => {
|
|
164
|
+
const user = 'errorTest2';
|
|
165
|
+
|
|
166
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
167
|
+
await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
|
|
168
|
+
const workflowID = randomUUID();
|
|
169
|
+
|
|
170
|
+
const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
|
|
171
|
+
expect(error).toBeInstanceOf(Error);
|
|
172
|
+
expect((error as Error).message).toMatch(/^test error \d+$/);
|
|
173
|
+
|
|
174
|
+
const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
|
|
175
|
+
expect(rows.length).toBe(1);
|
|
176
|
+
expect(rows[0].greet_count).toBe(10);
|
|
177
|
+
|
|
178
|
+
const { rows: txOutput } = await userDB.query<transaction_completion>(
|
|
179
|
+
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
180
|
+
[workflowID],
|
|
181
|
+
);
|
|
182
|
+
expect(txOutput.length).toBe(1);
|
|
183
|
+
expect(txOutput[0].workflow_id).toBe(workflowID);
|
|
184
|
+
expect(txOutput[0].function_num).toBe(0);
|
|
185
|
+
expect(txOutput[0].output).toBeNull();
|
|
186
|
+
expect(txOutput[0].error).not.toBeNull();
|
|
187
|
+
const $error = SuperJSON.parse(txOutput[0].error!);
|
|
188
|
+
expect($error).toBeInstanceOf(Error);
|
|
189
|
+
expect(($error as Error).message).toMatch(/^test error \d+$/);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('rerun error dataSource.runAsTx function', async () => {
|
|
193
|
+
const user = 'rerunErrorTest2';
|
|
194
|
+
|
|
195
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
196
|
+
await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
|
|
197
|
+
const workflowID = randomUUID();
|
|
198
|
+
|
|
199
|
+
const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
|
|
200
|
+
expect(error).toBeInstanceOf(Error);
|
|
201
|
+
expect((error as Error).message).toMatch(/^test error \d+$/);
|
|
202
|
+
|
|
203
|
+
const error2 = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
|
|
204
|
+
expect(error2).toBeInstanceOf(Error);
|
|
205
|
+
expect((error2 as Error).message).toMatch((error as Error).message);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('readonly dataSource.register function', async () => {
|
|
209
|
+
const user = 'readTest1';
|
|
210
|
+
|
|
211
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
212
|
+
await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
|
|
213
|
+
|
|
214
|
+
const workflowID = randomUUID();
|
|
215
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowReg(user))).resolves.toMatchObject({
|
|
216
|
+
user,
|
|
217
|
+
greet_count: 10,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const { rows } = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1', [
|
|
221
|
+
workflowID,
|
|
222
|
+
]);
|
|
223
|
+
expect(rows.length).toBe(0);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('readonly dataSource.runAsTx function', async () => {
|
|
227
|
+
const user = 'readTest2';
|
|
228
|
+
|
|
229
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
230
|
+
await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
|
|
231
|
+
|
|
232
|
+
const workflowID = randomUUID();
|
|
233
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowRunTx(user))).resolves.toMatchObject({
|
|
234
|
+
user,
|
|
235
|
+
greet_count: 10,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const { rows } = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1', [
|
|
239
|
+
workflowID,
|
|
240
|
+
]);
|
|
241
|
+
expect(rows.length).toBe(0);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('static dataSource.register methods', async () => {
|
|
245
|
+
const user = 'staticTest1';
|
|
246
|
+
|
|
247
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
248
|
+
|
|
249
|
+
const workflowID = randomUUID();
|
|
250
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regStaticWorkflow(user))).resolves.toMatchObject([
|
|
251
|
+
{ user, greet_count: 1 },
|
|
252
|
+
{ user, greet_count: 1 },
|
|
253
|
+
]);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('instance dataSource.register methods', async () => {
|
|
257
|
+
const user = 'instanceTest1';
|
|
258
|
+
|
|
259
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
260
|
+
|
|
261
|
+
const workflowID = randomUUID();
|
|
262
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regInstanceWorkflow(user))).resolves.toMatchObject([
|
|
263
|
+
{ user, greet_count: 1 },
|
|
264
|
+
{ user, greet_count: 1 },
|
|
265
|
+
]);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('invoke-reg-tx-fun-outside-wf', async () => {
|
|
269
|
+
const user = 'outsideWfUser' + Date.now();
|
|
270
|
+
const result = await regInsertFunction(user);
|
|
271
|
+
expect(result).toMatchObject({ user, greet_count: 1 });
|
|
272
|
+
|
|
273
|
+
const txResults = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE output LIKE $1', [
|
|
274
|
+
`%${user}%`,
|
|
275
|
+
]);
|
|
276
|
+
expect(txResults.rows.length).toBe(0);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('invoke-reg-tx-static-method-outside-wf', async () => {
|
|
280
|
+
const user = 'outsideWfUser' + Date.now();
|
|
281
|
+
const result = await StaticClass.insertFunction(user);
|
|
282
|
+
expect(result).toMatchObject({ user, greet_count: 1 });
|
|
283
|
+
|
|
284
|
+
const txResults = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE output LIKE $1', [
|
|
285
|
+
`%${user}%`,
|
|
286
|
+
]);
|
|
287
|
+
expect(txResults.rows.length).toBe(0);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('invoke-reg-tx-inst-method-outside-wf', async () => {
|
|
291
|
+
const user = 'outsideWfUser' + Date.now();
|
|
292
|
+
const instance = new InstanceClass();
|
|
293
|
+
const result = await instance.insertFunction(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
|
+
|
|
303
|
+
export interface greetings {
|
|
304
|
+
name: string;
|
|
305
|
+
greet_count: number;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function createDatabases(userDB: Pool, createTxCompletion: boolean) {
|
|
309
|
+
{
|
|
310
|
+
const client = new Client({ ...config.connection, database: 'postgres' });
|
|
311
|
+
try {
|
|
312
|
+
await client.connect();
|
|
313
|
+
await dropDB(client, 'kysely_ds_test', true);
|
|
314
|
+
await dropDB(client, 'kysely_ds_test_dbos_sys', true);
|
|
315
|
+
await dropDB(client, config.connection.database, true);
|
|
316
|
+
await ensureDB(client, config.connection.database);
|
|
317
|
+
} finally {
|
|
318
|
+
await client.end();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
{
|
|
323
|
+
const client = await userDB.connect();
|
|
324
|
+
try {
|
|
325
|
+
await client.query(
|
|
326
|
+
'CREATE TABLE greetings(name text NOT NULL, greet_count integer DEFAULT 0, PRIMARY KEY(name))',
|
|
327
|
+
);
|
|
328
|
+
} finally {
|
|
329
|
+
client.release();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (createTxCompletion) {
|
|
334
|
+
await KyselyDataSource.initializeDBOSSchema(config.connection);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function insertFunction(user: string) {
|
|
339
|
+
const result = await dataSource.client
|
|
340
|
+
.insertInto('greetings')
|
|
341
|
+
.values({ name: user, greet_count: 1 })
|
|
342
|
+
.onConflict((oc) => oc.column('name').doUpdateSet({ greet_count: sql`greetings.greet_count + 1` }))
|
|
343
|
+
.returning('greet_count')
|
|
344
|
+
.executeTakeFirst();
|
|
345
|
+
|
|
346
|
+
return { user, greet_count: result?.greet_count, now: Date.now() };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function errorFunction(user: string) {
|
|
350
|
+
await insertFunction(user);
|
|
351
|
+
throw new Error(`test error ${Date.now()}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function readFunction(user: string) {
|
|
355
|
+
const row = await dataSource.client
|
|
356
|
+
.selectFrom('greetings')
|
|
357
|
+
.select('greet_count')
|
|
358
|
+
.where('name', '=', user)
|
|
359
|
+
.executeTakeFirst();
|
|
360
|
+
return { user, greet_count: row?.greet_count, now: Date.now() };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const regInsertFunction = dataSource.registerTransaction(insertFunction);
|
|
364
|
+
const regErrorFunction = dataSource.registerTransaction(errorFunction);
|
|
365
|
+
const regReadFunction = dataSource.registerTransaction(readFunction, { readOnly: true });
|
|
366
|
+
|
|
367
|
+
class StaticClass {
|
|
368
|
+
static async insertFunction(user: string) {
|
|
369
|
+
return await insertFunction(user);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
static async readFunction(user: string) {
|
|
373
|
+
return await readFunction(user);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction);
|
|
378
|
+
StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, { readOnly: true });
|
|
379
|
+
|
|
380
|
+
class InstanceClass {
|
|
381
|
+
async insertFunction(user: string) {
|
|
382
|
+
return await insertFunction(user);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async readFunction(user: string) {
|
|
386
|
+
return await readFunction(user);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
InstanceClass.prototype.insertFunction = dataSource.registerTransaction(
|
|
391
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
392
|
+
InstanceClass.prototype.insertFunction,
|
|
393
|
+
);
|
|
394
|
+
InstanceClass.prototype.readFunction = dataSource.registerTransaction(
|
|
395
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
396
|
+
InstanceClass.prototype.readFunction,
|
|
397
|
+
{ readOnly: true },
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
async function insertWorkflowReg(user: string) {
|
|
401
|
+
return await regInsertFunction(user);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function insertWorkflowRunTx(user: string) {
|
|
405
|
+
return await dataSource.runTransaction(() => insertFunction(user), { name: 'insertFunction' });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function errorWorkflowReg(user: string) {
|
|
409
|
+
return await regErrorFunction(user);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function errorWorkflowRunTx(user: string) {
|
|
413
|
+
return await dataSource.runTransaction(() => errorFunction(user), { name: 'errorFunction' });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function readWorkflowReg(user: string) {
|
|
417
|
+
return await regReadFunction(user);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function readWorkflowRunTx(user: string) {
|
|
421
|
+
return await dataSource.runTransaction(() => readFunction(user), { name: 'readFunction', readOnly: true });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function staticWorkflow(user: string) {
|
|
425
|
+
const result = await StaticClass.insertFunction(user);
|
|
426
|
+
const readResult = await StaticClass.readFunction(user);
|
|
427
|
+
return [result, readResult];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function instanceWorkflow(user: string) {
|
|
431
|
+
const instance = new InstanceClass();
|
|
432
|
+
const result = await instance.insertFunction(user);
|
|
433
|
+
const readResult = await instance.readFunction(user);
|
|
434
|
+
return [result, readResult];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const regInsertWorkflowReg = DBOS.registerWorkflow(insertWorkflowReg);
|
|
438
|
+
const regInsertWorkflowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx);
|
|
439
|
+
const regErrorWorkflowReg = DBOS.registerWorkflow(errorWorkflowReg);
|
|
440
|
+
const regErrorWorkflowRunTx = DBOS.registerWorkflow(errorWorkflowRunTx);
|
|
441
|
+
const regReadWorkflowReg = DBOS.registerWorkflow(readWorkflowReg);
|
|
442
|
+
const regReadWorkflowRunTx = DBOS.registerWorkflow(readWorkflowRunTx);
|
|
443
|
+
const regStaticWorkflow = DBOS.registerWorkflow(staticWorkflow);
|
|
444
|
+
const regInstanceWorkflow = DBOS.registerWorkflow(instanceWorkflow);
|
|
445
|
+
|
|
446
|
+
describe('KyselyDataSourceCreateTxCompletion', () => {
|
|
447
|
+
const userDB = new Pool(config.connection);
|
|
448
|
+
|
|
449
|
+
beforeAll(async () => {
|
|
450
|
+
await createDatabases(userDB, false);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
afterAll(async () => {
|
|
454
|
+
await userDB.end();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
beforeEach(async () => {
|
|
458
|
+
DBOS.setConfig({ name: 'kysely-ds-test' });
|
|
459
|
+
await DBOS.launch();
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
afterEach(async () => {
|
|
463
|
+
await DBOS.shutdown();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('run wf auto register DS schema', async () => {
|
|
467
|
+
const user = 'helloTest1';
|
|
468
|
+
|
|
469
|
+
await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
|
|
470
|
+
const workflowID = randomUUID();
|
|
471
|
+
|
|
472
|
+
await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject({
|
|
473
|
+
user,
|
|
474
|
+
greet_count: 1,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const { rows } = await userDB.query<transaction_completion>(
|
|
478
|
+
'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
|
|
479
|
+
[workflowID],
|
|
480
|
+
);
|
|
481
|
+
expect(rows.length).toBe(1);
|
|
482
|
+
expect(rows[0].workflow_id).toBe(workflowID);
|
|
483
|
+
expect(rows[0].function_num).toBe(0);
|
|
484
|
+
expect(rows[0].output).not.toBeNull();
|
|
485
|
+
expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
|
|
486
|
+
});
|
|
487
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ColumnType } from 'kysely';
|
|
2
|
+
import { Client } from 'pg';
|
|
3
|
+
|
|
4
|
+
export async function ensureDB(client: Client, name: string) {
|
|
5
|
+
const results = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [name]);
|
|
6
|
+
if (results.rows.length === 0) {
|
|
7
|
+
await client.query(`CREATE DATABASE ${name}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function dropDB(client: Client, name: string, force: boolean = false) {
|
|
12
|
+
const withForce = force ? ' WITH (FORCE)' : '';
|
|
13
|
+
await client.query(`DROP DATABASE IF EXISTS ${name} ${withForce}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Kysely-specific database interface
|
|
17
|
+
export interface GreetingsTable {
|
|
18
|
+
name: string;
|
|
19
|
+
greet_count: ColumnType<number, number | undefined, number>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Database {
|
|
23
|
+
greetings: GreetingsTable;
|
|
24
|
+
}
|