@dbos-inc/postgres-datasource 3.0.8-preview → 3.0.8-preview.g493d2d1c2b

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/index.ts CHANGED
@@ -21,50 +21,40 @@ import { SuperJSON } from 'superjson';
21
21
  export { IsolationLevel, PostgresTransactionOptions };
22
22
 
23
23
  interface PostgresDataSourceContext {
24
- client: postgres.TransactionSql;
24
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
25
+ client: postgres.TransactionSql<{}>;
25
26
  }
26
27
 
27
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
28
- type Options = postgres.Options<{}>;
29
-
30
28
  const asyncLocalCtx = new AsyncLocalStorage<PostgresDataSourceContext>();
31
29
 
32
30
  class PostgresTransactionHandler implements DataSourceTransactionHandler {
33
31
  readonly dsType = 'PostgresDataSource';
34
- #dbField: Sql | undefined;
32
+ readonly #db: Sql;
35
33
 
36
34
  constructor(
37
35
  readonly name: string,
38
- private readonly options: Options = {},
39
- ) {}
40
-
41
- async initialize(): Promise<void> {
42
- const db = this.#dbField;
43
- this.#dbField = postgres(this.options);
44
- await db?.end();
36
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
37
+ options: postgres.Options<{}> = {},
38
+ ) {
39
+ this.#db = postgres(options);
45
40
  }
46
41
 
47
- async destroy(): Promise<void> {
48
- const db = this.#dbField;
49
- this.#dbField = undefined;
50
- await db?.end();
42
+ initialize(): Promise<void> {
43
+ return Promise.resolve();
51
44
  }
52
45
 
53
- get #db(): Sql {
54
- if (!this.#dbField) {
55
- throw new Error(`DataSource ${this.name} is not initialized.`);
56
- }
57
- return this.#dbField;
46
+ destroy(): Promise<void> {
47
+ return this.#db.end();
58
48
  }
59
49
 
60
50
  async #checkExecution(
61
51
  workflowID: string,
62
- stepID: number,
52
+ functionNum: number,
63
53
  ): Promise<{ output: string | null } | { error: string } | undefined> {
64
54
  type Result = { output: string | null; error: string | null };
65
55
  const result = await this.#db<Result[]>/*sql*/ `
66
56
  SELECT output, error FROM dbos.transaction_completion
67
- WHERE workflow_id = ${workflowID} AND function_num = ${stepID}`;
57
+ WHERE workflow_id = ${workflowID} AND function_num = ${functionNum}`;
68
58
  if (result.length === 0) {
69
59
  return undefined;
70
60
  }
@@ -73,15 +63,16 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
73
63
  }
74
64
 
75
65
  static async #recordOutput(
76
- client: postgres.TransactionSql,
66
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
67
+ client: postgres.TransactionSql<{}>,
77
68
  workflowID: string,
78
- stepID: number,
69
+ functionNum: number,
79
70
  output: string | null,
80
71
  ): Promise<void> {
81
72
  try {
82
73
  await client/*sql*/ `
83
74
  INSERT INTO dbos.transaction_completion (workflow_id, function_num, output)
84
- VALUES (${workflowID}, ${stepID}, ${output})`;
75
+ VALUES (${workflowID}, ${functionNum}, ${output})`;
85
76
  } catch (error) {
86
77
  if (isPGKeyConflictError(error)) {
87
78
  throw new DBOSWorkflowConflictError(workflowID);
@@ -91,11 +82,11 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
91
82
  }
92
83
  }
93
84
 
94
- async #recordError(workflowID: string, stepID: number, error: string): Promise<void> {
85
+ async #recordError(workflowID: string, functionNum: number, error: string): Promise<void> {
95
86
  try {
96
87
  await this.#db/*sql*/ `
97
88
  INSERT INTO dbos.transaction_completion (workflow_id, function_num, error)
98
- VALUES (${workflowID}, ${stepID}, ${error})`;
89
+ VALUES (${workflowID}, ${functionNum}, ${error})`;
99
90
  } catch (error) {
100
91
  if (isPGKeyConflictError(error)) {
101
92
  throw new DBOSWorkflowConflictError(workflowID);
@@ -112,24 +103,26 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
112
103
  ...args: Args
113
104
  ): Promise<Return> {
114
105
  const workflowID = DBOS.workflowID;
115
- const stepID = DBOS.stepID;
116
- if (workflowID !== undefined && stepID === undefined) {
117
- throw new Error('DBOS.stepID is undefined inside a workflow.');
106
+ if (workflowID === undefined) {
107
+ throw new Error('Workflow ID is not set.');
108
+ }
109
+ const functionNum = DBOS.stepID;
110
+ if (functionNum === undefined) {
111
+ throw new Error('Function Number is not set.');
118
112
  }
119
113
 
120
114
  const isolationLevel = config?.isolationLevel ? `ISOLATION LEVEL ${config.isolationLevel}` : '';
121
115
  const readOnly = config?.readOnly ?? false;
122
116
  const accessMode = config?.readOnly === undefined ? '' : readOnly ? 'READ ONLY' : 'READ WRITE';
123
- const saveResults = !readOnly && workflowID !== undefined;
117
+ const saveResults = !readOnly && workflowID;
124
118
 
125
- // Retry loop if appropriate
126
119
  let retryWaitMS = 1;
127
120
  const backoffFactor = 1.5;
128
- const maxRetryWaitMS = 2000; // Maximum wait 2 seconds.
121
+ const maxRetryWaitMS = 2000;
129
122
 
130
123
  while (true) {
131
124
  // Check to see if this tx has already been executed
132
- const previousResult = saveResults ? await this.#checkExecution(workflowID, stepID!) : undefined;
125
+ const previousResult = saveResults ? await this.#checkExecution(workflowID, functionNum) : undefined;
133
126
  if (previousResult) {
134
127
  DBOS.span?.setAttribute('cached', true);
135
128
 
@@ -148,7 +141,12 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
148
141
 
149
142
  // save the output of read/write transactions
150
143
  if (saveResults) {
151
- await PostgresTransactionHandler.#recordOutput(client, workflowID, stepID!, SuperJSON.stringify(result));
144
+ await PostgresTransactionHandler.#recordOutput(
145
+ client,
146
+ workflowID,
147
+ functionNum,
148
+ SuperJSON.stringify(result),
149
+ );
152
150
  }
153
151
 
154
152
  return result;
@@ -164,7 +162,7 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
164
162
  } else {
165
163
  if (saveResults) {
166
164
  const message = SuperJSON.stringify(error);
167
- await this.#recordError(workflowID, stepID!, message);
165
+ await this.#recordError(workflowID, functionNum, message);
168
166
  }
169
167
 
170
168
  throw error;
@@ -175,18 +173,20 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
175
173
  }
176
174
 
177
175
  export class PostgresDataSource implements DBOSDataSource<PostgresTransactionOptions> {
178
- static get client(): postgres.TransactionSql {
176
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
177
+ static get client(): postgres.TransactionSql<{}> {
179
178
  if (!DBOS.isInTransaction()) {
180
179
  throw new Error('invalid use of PostgresDataSource.client outside of a DBOS transaction.');
181
180
  }
182
181
  const ctx = asyncLocalCtx.getStore();
183
182
  if (!ctx) {
184
- throw new Error('invalid use of PostgresDataSource.client outside of a DBOS transaction.');
183
+ throw new Error('No async local context found.');
185
184
  }
186
185
  return ctx.client;
187
186
  }
188
187
 
189
- static async initializeInternalSchema(options: Options = {}): Promise<void> {
188
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
189
+ static async initializeInternalSchema(options: postgres.Options<{}> = {}): Promise<void> {
190
190
  const pg = postgres({ ...options, onnotice: () => {} });
191
191
  try {
192
192
  await pg.unsafe(createTransactionCompletionSchemaPG);
@@ -196,12 +196,12 @@ export class PostgresDataSource implements DBOSDataSource<PostgresTransactionOpt
196
196
  }
197
197
  }
198
198
 
199
+ readonly name: string;
199
200
  #provider: PostgresTransactionHandler;
200
201
 
201
- constructor(
202
- readonly name: string,
203
- options: Options = {},
204
- ) {
202
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
203
+ constructor(name: string, options: postgres.Options<{}> = {}) {
204
+ this.name = name;
205
205
  this.#provider = new PostgresTransactionHandler(name, options);
206
206
  registerDataSource(this.#provider);
207
207
  }
@@ -212,10 +212,10 @@ export class PostgresDataSource implements DBOSDataSource<PostgresTransactionOpt
212
212
 
213
213
  registerTransaction<This, Args extends unknown[], Return>(
214
214
  func: (this: This, ...args: Args) => Promise<Return>,
215
+ name: string,
215
216
  config?: PostgresTransactionOptions,
216
- name?: string,
217
217
  ): (this: This, ...args: Args) => Promise<Return> {
218
- return registerTransaction(this.name, func, { name: name ?? func.name }, config);
218
+ return registerTransaction(this.name, func, { name }, config);
219
219
  }
220
220
 
221
221
  transaction(config?: PostgresTransactionOptions) {
@@ -223,14 +223,14 @@ export class PostgresDataSource implements DBOSDataSource<PostgresTransactionOpt
223
223
  const ds = this;
224
224
  return function decorator<This, Args extends unknown[], Return>(
225
225
  _target: object,
226
- propertyKey: PropertyKey,
226
+ propertyKey: string,
227
227
  descriptor: TypedPropertyDescriptor<(this: This, ...args: Args) => Promise<Return>>,
228
228
  ) {
229
229
  if (!descriptor.value) {
230
230
  throw Error('Use of decorator when original method is undefined');
231
231
  }
232
232
 
233
- descriptor.value = ds.registerTransaction(descriptor.value, config, String(propertyKey));
233
+ descriptor.value = ds.registerTransaction(descriptor.value, propertyKey.toString(), config);
234
234
 
235
235
  return descriptor;
236
236
  };
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@dbos-inc/postgres-datasource",
3
- "version": "3.0.8-preview",
4
- "description": "DBOS DataSource library for Postgres database client",
3
+ "version": "3.0.8-preview.g493d2d1c2b",
4
+ "description": "",
5
5
  "license": "MIT",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "homepage": "https://docs.dbos.dev/",
9
6
  "repository": {
10
7
  "type": "git",
11
8
  "url": "https://github.com/dbos-inc/dbos-transact-ts",
12
- "directory": "packages/postgres-datasource"
9
+ "directory": "packages/knex-datasource"
13
10
  },
11
+ "homepage": "https://docs.dbos.dev/",
12
+ "main": "index.js",
14
13
  "scripts": {
15
14
  "build": "tsc --project tsconfig.json",
16
15
  "test": "jest --detectOpenHandles"
@@ -44,19 +44,13 @@ describe('PostgresDataSource', () => {
44
44
  }
45
45
 
46
46
  await PostgresDataSource.initializeInternalSchema(config);
47
- });
48
-
49
- afterAll(async () => {
50
- await userDB.end();
51
- });
52
-
53
- beforeEach(async () => {
54
47
  DBOS.setConfig({ name: 'pg-ds-test' });
55
48
  await DBOS.launch();
56
49
  });
57
50
 
58
- afterEach(async () => {
51
+ afterAll(async () => {
59
52
  await DBOS.shutdown();
53
+ await userDB.end();
60
54
  });
61
55
 
62
56
  test('insert dataSource.register function', async () => {
@@ -65,7 +59,7 @@ describe('PostgresDataSource', () => {
65
59
  await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
66
60
  const workflowID = randomUUID();
67
61
 
68
- await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject({
62
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorfklowReg(user))).resolves.toMatchObject({
69
63
  user,
70
64
  greet_count: 1,
71
65
  });
@@ -87,10 +81,10 @@ describe('PostgresDataSource', () => {
87
81
  await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
88
82
  const workflowID = randomUUID();
89
83
 
90
- const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user));
84
+ const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorfklowReg(user));
91
85
  expect(result).toMatchObject({ user, greet_count: 1 });
92
86
 
93
- await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject(result);
87
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorfklowReg(user))).resolves.toMatchObject(result);
94
88
  });
95
89
 
96
90
  test('insert dataSource.runAsTx function', async () => {
@@ -99,7 +93,7 @@ describe('PostgresDataSource', () => {
99
93
  await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
100
94
  const workflowID = randomUUID();
101
95
 
102
- await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user))).resolves.toMatchObject({
96
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorfklowRunTx(user))).resolves.toMatchObject({
103
97
  user,
104
98
  greet_count: 1,
105
99
  });
@@ -121,10 +115,10 @@ describe('PostgresDataSource', () => {
121
115
  await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
122
116
  const workflowID = randomUUID();
123
117
 
124
- const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user));
118
+ const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorfklowRunTx(user));
125
119
  expect(result).toMatchObject({ user, greet_count: 1 });
126
120
 
127
- await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user))).resolves.toMatchObject(
121
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorfklowRunTx(user))).resolves.toMatchObject(
128
122
  result,
129
123
  );
130
124
  });
@@ -287,40 +281,6 @@ describe('PostgresDataSource', () => {
287
281
  { user, greet_count: 1 },
288
282
  ]);
289
283
  });
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
- });
324
284
  });
325
285
 
326
286
  export interface greetings {
@@ -340,8 +300,9 @@ async function insertFunction(user: string) {
340
300
  }
341
301
 
342
302
  async function errorFunction(user: string) {
343
- const _result = await insertFunction(user);
303
+ const result = await insertFunction(user);
344
304
  throw new Error(`test error ${Date.now()}`);
305
+ return result;
345
306
  }
346
307
 
347
308
  async function readFunction(user: string) {
@@ -353,9 +314,9 @@ async function readFunction(user: string) {
353
314
  return { user, greet_count: row?.greet_count, now: Date.now() };
354
315
  }
355
316
 
356
- const regInsertFunction = dataSource.registerTransaction(insertFunction);
357
- const regErrorFunction = dataSource.registerTransaction(errorFunction);
358
- const regReadFunction = dataSource.registerTransaction(readFunction, { readOnly: true });
317
+ const regInsertFunction = dataSource.registerTransaction(insertFunction, 'insertFunction');
318
+ const regErrorFunction = dataSource.registerTransaction(errorFunction, 'errorFunction');
319
+ const regReadFunction = dataSource.registerTransaction(readFunction, 'readFunction', { readOnly: true });
359
320
 
360
321
  class StaticClass {
361
322
  static async insertFunction(user: string) {
@@ -367,8 +328,8 @@ class StaticClass {
367
328
  }
368
329
  }
369
330
 
370
- StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction);
371
- StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, { readOnly: true });
331
+ StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction, 'insertFunction');
332
+ StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, 'readFunction');
372
333
 
373
334
  class InstanceClass {
374
335
  async insertFunction(user: string) {
@@ -383,11 +344,12 @@ class InstanceClass {
383
344
  InstanceClass.prototype.insertFunction = dataSource.registerTransaction(
384
345
  // eslint-disable-next-line @typescript-eslint/unbound-method
385
346
  InstanceClass.prototype.insertFunction,
347
+ 'insertFunction',
386
348
  );
387
349
  InstanceClass.prototype.readFunction = dataSource.registerTransaction(
388
350
  // eslint-disable-next-line @typescript-eslint/unbound-method
389
351
  InstanceClass.prototype.readFunction,
390
- { readOnly: true },
352
+ 'readFunction',
391
353
  );
392
354
 
393
355
  async function insertWorkflowReg(user: string) {
@@ -427,8 +389,8 @@ async function instanceWorkflow(user: string) {
427
389
  return [result, readResult];
428
390
  }
429
391
 
430
- const regInsertWorkflowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
431
- const regInsertWorkflowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
392
+ const regInsertWorfklowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
393
+ const regInsertWorfklowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
432
394
  const regErrorWorkflowReg = DBOS.registerWorkflow(errorWorkflowReg, 'errorWorkflowReg');
433
395
  const regErrorWorkflowRunTx = DBOS.registerWorkflow(errorWorkflowRunTx, 'errorWorkflowRunTx');
434
396
  const regReadWorkflowReg = DBOS.registerWorkflow(readWorkflowReg, 'readWorkflowReg');