@dbos-inc/postgres-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.
- package/dist/index.d.ts +12 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -42
- package/dist/index.js.map +1 -1
- package/index.ts +71 -53
- package/package.json +6 -5
- package/tests/config.test.ts +1 -1
- package/tests/datasource.test.ts +62 -24
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import postgres from 'postgres';
|
|
2
|
-
import { PGIsolationLevel as IsolationLevel, PGTransactionConfig
|
|
2
|
+
import { PGIsolationLevel as IsolationLevel, PGTransactionConfig, DBOSDataSource } from '@dbos-inc/dbos-sdk/datasource';
|
|
3
|
+
interface PostgresTransactionOptions extends PGTransactionConfig {
|
|
4
|
+
name?: string;
|
|
5
|
+
}
|
|
3
6
|
export { IsolationLevel, PostgresTransactionOptions };
|
|
7
|
+
type Options = postgres.Options<{}>;
|
|
4
8
|
export declare class PostgresDataSource implements DBOSDataSource<PostgresTransactionOptions> {
|
|
5
9
|
#private;
|
|
6
|
-
static get client(): postgres.TransactionSql<{}>;
|
|
7
|
-
static initializeInternalSchema(options?: postgres.Options<{}>): Promise<void>;
|
|
8
10
|
readonly name: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
static get client(): postgres.TransactionSql<{}>;
|
|
12
|
+
get client(): postgres.TransactionSql<{}>;
|
|
13
|
+
static initializeDBOSSchema(options?: Options): Promise<void>;
|
|
14
|
+
constructor(name: string, options?: Options);
|
|
15
|
+
runTransaction<T>(func: () => Promise<T>, config?: PostgresTransactionOptions): Promise<T>;
|
|
16
|
+
registerTransaction<This, Args extends unknown[], Return>(func: (this: This, ...args: Args) => Promise<Return>, config?: PostgresTransactionOptions): (this: This, ...args: Args) => Promise<Return>;
|
|
17
|
+
transaction(config?: PostgresTransactionOptions): <This, Args extends unknown[], Return>(_target: object, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor<(this: This, ...args: Args) => Promise<Return>>) => TypedPropertyDescriptor<(this: This, ...args: Args) => Promise<Return>>;
|
|
13
18
|
}
|
|
14
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,QAAsB,MAAM,UAAU,CAAC;AAE9C,OAAO,EAQL,gBAAgB,IAAI,cAAc,EAClC,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,QAAsB,MAAM,UAAU,CAAC;AAE9C,OAAO,EAQL,gBAAgB,IAAI,cAAc,EAClC,mBAAmB,EACnB,cAAc,EAEf,MAAM,+BAA+B,CAAC;AAIvC,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC;AAQtD,KAAK,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAqJpC,qBAAa,kBAAmB,YAAW,cAAc,CAAC,0BAA0B,CAAC;;IAoCjF,QAAQ,CAAC,IAAI,EAAE,MAAM;IArBvB,MAAM,KAAK,MAAM,gCAEhB;IAED,IAAI,MAAM,gCAET;WAEY,oBAAoB,CAAC,OAAO,GAAE,OAAY,GAAG,OAAO,CAAC,IAAI,CAAC;gBAa5D,IAAI,EAAE,MAAM,EACrB,OAAO,GAAE,OAAY;IAMjB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,0BAA0B;IAInF,mBAAmB,CAAC,IAAI,EAAE,IAAI,SAAS,OAAO,EAAE,EAAE,MAAM,EACtD,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,EACpD,MAAM,CAAC,EAAE,0BAA0B,GAClC,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC;IAIjD,WAAW,CAAC,MAAM,CAAC,EAAE,0BAA0B,mDAIlC,MAAM,eACF,WAAW,cACZ,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,QAAQ,MAAM,CAAC,CAAC,oCAAxC,IAAI,WAAW,IAAI,KAAK,QAAQ,MAAM,CAAC;CAcvF"}
|
package/dist/index.js
CHANGED
|
@@ -14,37 +14,44 @@ const superjson_1 = require("superjson");
|
|
|
14
14
|
const asyncLocalCtx = new node_async_hooks_1.AsyncLocalStorage();
|
|
15
15
|
class PostgresTransactionHandler {
|
|
16
16
|
name;
|
|
17
|
+
options;
|
|
17
18
|
dsType = 'PostgresDataSource';
|
|
18
|
-
#
|
|
19
|
-
constructor(name,
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
21
|
-
options = {}) {
|
|
19
|
+
#dbField;
|
|
20
|
+
constructor(name, options = {}) {
|
|
22
21
|
this.name = name;
|
|
23
|
-
this
|
|
22
|
+
this.options = options;
|
|
23
|
+
}
|
|
24
|
+
async initialize() {
|
|
25
|
+
const db = this.#dbField;
|
|
26
|
+
this.#dbField = (0, postgres_1.default)(this.options);
|
|
27
|
+
await db?.end();
|
|
24
28
|
}
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
async destroy() {
|
|
30
|
+
const db = this.#dbField;
|
|
31
|
+
this.#dbField = undefined;
|
|
32
|
+
await db?.end();
|
|
27
33
|
}
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
get #db() {
|
|
35
|
+
if (!this.#dbField) {
|
|
36
|
+
throw new Error(`DataSource ${this.name} is not initialized.`);
|
|
37
|
+
}
|
|
38
|
+
return this.#dbField;
|
|
30
39
|
}
|
|
31
|
-
async #checkExecution(workflowID,
|
|
40
|
+
async #checkExecution(workflowID, stepID) {
|
|
32
41
|
const result = await this.#db `
|
|
33
42
|
SELECT output, error FROM dbos.transaction_completion
|
|
34
|
-
WHERE workflow_id = ${workflowID} AND function_num = ${
|
|
43
|
+
WHERE workflow_id = ${workflowID} AND function_num = ${stepID}`;
|
|
35
44
|
if (result.length === 0) {
|
|
36
45
|
return undefined;
|
|
37
46
|
}
|
|
38
47
|
const { output, error } = result[0];
|
|
39
48
|
return error !== null ? { error } : { output };
|
|
40
49
|
}
|
|
41
|
-
static async #recordOutput(
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
43
|
-
client, workflowID, functionNum, output) {
|
|
50
|
+
static async #recordOutput(client, workflowID, stepID, output) {
|
|
44
51
|
try {
|
|
45
52
|
await client /*sql*/ `
|
|
46
53
|
INSERT INTO dbos.transaction_completion (workflow_id, function_num, output)
|
|
47
|
-
VALUES (${workflowID}, ${
|
|
54
|
+
VALUES (${workflowID}, ${stepID}, ${output})`;
|
|
48
55
|
}
|
|
49
56
|
catch (error) {
|
|
50
57
|
if ((0, datasource_1.isPGKeyConflictError)(error)) {
|
|
@@ -55,11 +62,11 @@ class PostgresTransactionHandler {
|
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
|
-
async #recordError(workflowID,
|
|
65
|
+
async #recordError(workflowID, stepID, error) {
|
|
59
66
|
try {
|
|
60
67
|
await this.#db /*sql*/ `
|
|
61
68
|
INSERT INTO dbos.transaction_completion (workflow_id, function_num, error)
|
|
62
|
-
VALUES (${workflowID}, ${
|
|
69
|
+
VALUES (${workflowID}, ${stepID}, ${error})`;
|
|
63
70
|
}
|
|
64
71
|
catch (error) {
|
|
65
72
|
if ((0, datasource_1.isPGKeyConflictError)(error)) {
|
|
@@ -72,23 +79,21 @@ class PostgresTransactionHandler {
|
|
|
72
79
|
}
|
|
73
80
|
async invokeTransactionFunction(config, target, func, ...args) {
|
|
74
81
|
const workflowID = dbos_sdk_1.DBOS.workflowID;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const functionNum = dbos_sdk_1.DBOS.stepID;
|
|
79
|
-
if (functionNum === undefined) {
|
|
80
|
-
throw new Error('Function Number is not set.');
|
|
82
|
+
const stepID = dbos_sdk_1.DBOS.stepID;
|
|
83
|
+
if (workflowID !== undefined && stepID === undefined) {
|
|
84
|
+
throw new Error('DBOS.stepID is undefined inside a workflow.');
|
|
81
85
|
}
|
|
82
86
|
const isolationLevel = config?.isolationLevel ? `ISOLATION LEVEL ${config.isolationLevel}` : '';
|
|
83
87
|
const readOnly = config?.readOnly ?? false;
|
|
84
88
|
const accessMode = config?.readOnly === undefined ? '' : readOnly ? 'READ ONLY' : 'READ WRITE';
|
|
85
|
-
const saveResults = !readOnly && workflowID;
|
|
89
|
+
const saveResults = !readOnly && workflowID !== undefined;
|
|
90
|
+
// Retry loop if appropriate
|
|
86
91
|
let retryWaitMS = 1;
|
|
87
92
|
const backoffFactor = 1.5;
|
|
88
|
-
const maxRetryWaitMS = 2000;
|
|
93
|
+
const maxRetryWaitMS = 2000; // Maximum wait 2 seconds.
|
|
89
94
|
while (true) {
|
|
90
95
|
// Check to see if this tx has already been executed
|
|
91
|
-
const previousResult = saveResults ? await this.#checkExecution(workflowID,
|
|
96
|
+
const previousResult = saveResults ? await this.#checkExecution(workflowID, stepID) : undefined;
|
|
92
97
|
if (previousResult) {
|
|
93
98
|
dbos_sdk_1.DBOS.span?.setAttribute('cached', true);
|
|
94
99
|
if ('error' in previousResult) {
|
|
@@ -99,12 +104,12 @@ class PostgresTransactionHandler {
|
|
|
99
104
|
try {
|
|
100
105
|
const result = await this.#db.begin(`${isolationLevel} ${accessMode}`, async (client) => {
|
|
101
106
|
// execute user's transaction function
|
|
102
|
-
const result = await asyncLocalCtx.run({ client }, async () => {
|
|
107
|
+
const result = await asyncLocalCtx.run({ client, owner: this }, async () => {
|
|
103
108
|
return (await func.call(target, ...args));
|
|
104
109
|
});
|
|
105
110
|
// save the output of read/write transactions
|
|
106
111
|
if (saveResults) {
|
|
107
|
-
await PostgresTransactionHandler.#recordOutput(client, workflowID,
|
|
112
|
+
await PostgresTransactionHandler.#recordOutput(client, workflowID, stepID, superjson_1.SuperJSON.stringify(result));
|
|
108
113
|
}
|
|
109
114
|
return result;
|
|
110
115
|
});
|
|
@@ -121,7 +126,7 @@ class PostgresTransactionHandler {
|
|
|
121
126
|
else {
|
|
122
127
|
if (saveResults) {
|
|
123
128
|
const message = superjson_1.SuperJSON.stringify(error);
|
|
124
|
-
await this.#recordError(workflowID,
|
|
129
|
+
await this.#recordError(workflowID, stepID, message);
|
|
125
130
|
}
|
|
126
131
|
throw error;
|
|
127
132
|
}
|
|
@@ -130,19 +135,27 @@ class PostgresTransactionHandler {
|
|
|
130
135
|
}
|
|
131
136
|
}
|
|
132
137
|
class PostgresDataSource {
|
|
133
|
-
|
|
134
|
-
static
|
|
138
|
+
name;
|
|
139
|
+
static #getClient(p) {
|
|
135
140
|
if (!dbos_sdk_1.DBOS.isInTransaction()) {
|
|
136
|
-
throw new Error('
|
|
141
|
+
throw new Error('Invalid use of PostgresDataSource.client outside of a DBOS transaction.');
|
|
137
142
|
}
|
|
138
143
|
const ctx = asyncLocalCtx.getStore();
|
|
139
144
|
if (!ctx) {
|
|
140
|
-
throw new Error('
|
|
145
|
+
throw new Error('Invalid use of PostgresDataSource.client outside of a DBOS transaction.');
|
|
146
|
+
}
|
|
147
|
+
if (p && p !== ctx.owner) {
|
|
148
|
+
throw new Error('Invalid retrieval of `PostgresDataSource.client` from the wrong instance.');
|
|
141
149
|
}
|
|
142
150
|
return ctx.client;
|
|
143
151
|
}
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
static get client() {
|
|
153
|
+
return PostgresDataSource.#getClient(undefined);
|
|
154
|
+
}
|
|
155
|
+
get client() {
|
|
156
|
+
return PostgresDataSource.#getClient(this.#provider);
|
|
157
|
+
}
|
|
158
|
+
static async initializeDBOSSchema(options = {}) {
|
|
146
159
|
const pg = (0, postgres_1.default)({ ...options, onnotice: () => { } });
|
|
147
160
|
try {
|
|
148
161
|
await pg.unsafe(datasource_1.createTransactionCompletionSchemaPG);
|
|
@@ -152,19 +165,17 @@ class PostgresDataSource {
|
|
|
152
165
|
await pg.end();
|
|
153
166
|
}
|
|
154
167
|
}
|
|
155
|
-
name;
|
|
156
168
|
#provider;
|
|
157
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
158
169
|
constructor(name, options = {}) {
|
|
159
170
|
this.name = name;
|
|
160
171
|
this.#provider = new PostgresTransactionHandler(name, options);
|
|
161
172
|
(0, datasource_1.registerDataSource)(this.#provider);
|
|
162
173
|
}
|
|
163
|
-
async runTransaction(
|
|
164
|
-
return await (0, datasource_1.runTransaction)(
|
|
174
|
+
async runTransaction(func, config) {
|
|
175
|
+
return await (0, datasource_1.runTransaction)(func, config?.name ?? func.name, { dsName: this.name, config });
|
|
165
176
|
}
|
|
166
|
-
registerTransaction(func,
|
|
167
|
-
return (0, datasource_1.registerTransaction)(this.name, func, { name }, config);
|
|
177
|
+
registerTransaction(func, config) {
|
|
178
|
+
return (0, datasource_1.registerTransaction)(this.name, func, { name: config?.name ?? func.name }, config);
|
|
168
179
|
}
|
|
169
180
|
transaction(config) {
|
|
170
181
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
@@ -173,7 +184,10 @@ class PostgresDataSource {
|
|
|
173
184
|
if (!descriptor.value) {
|
|
174
185
|
throw Error('Use of decorator when original method is undefined');
|
|
175
186
|
}
|
|
176
|
-
descriptor.value = ds.registerTransaction(descriptor.value,
|
|
187
|
+
descriptor.value = ds.registerTransaction(descriptor.value, {
|
|
188
|
+
...config,
|
|
189
|
+
name: config?.name ?? String(propertyKey),
|
|
190
|
+
});
|
|
177
191
|
return descriptor;
|
|
178
192
|
};
|
|
179
193
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";AAAA,6CAA6C;;;;;;AAE7C,wDAA8C;AAC9C,iDAAqE;AACrE,8DAYuC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
package/index.ts
CHANGED
|
@@ -11,50 +11,65 @@ import {
|
|
|
11
11
|
registerTransaction,
|
|
12
12
|
runTransaction,
|
|
13
13
|
PGIsolationLevel as IsolationLevel,
|
|
14
|
-
PGTransactionConfig
|
|
14
|
+
PGTransactionConfig,
|
|
15
15
|
DBOSDataSource,
|
|
16
16
|
registerDataSource,
|
|
17
17
|
} from '@dbos-inc/dbos-sdk/datasource';
|
|
18
18
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
19
19
|
import { SuperJSON } from 'superjson';
|
|
20
20
|
|
|
21
|
+
interface PostgresTransactionOptions extends PGTransactionConfig {
|
|
22
|
+
name?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
export { IsolationLevel, PostgresTransactionOptions };
|
|
22
26
|
|
|
23
27
|
interface PostgresDataSourceContext {
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
client: postgres.TransactionSql;
|
|
29
|
+
owner: PostgresTransactionHandler;
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
33
|
+
type Options = postgres.Options<{}>;
|
|
34
|
+
|
|
28
35
|
const asyncLocalCtx = new AsyncLocalStorage<PostgresDataSourceContext>();
|
|
29
36
|
|
|
30
37
|
class PostgresTransactionHandler implements DataSourceTransactionHandler {
|
|
31
38
|
readonly dsType = 'PostgresDataSource';
|
|
32
|
-
|
|
39
|
+
#dbField: Sql | undefined;
|
|
33
40
|
|
|
34
41
|
constructor(
|
|
35
42
|
readonly name: string,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
private readonly options: Options = {},
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
async initialize(): Promise<void> {
|
|
47
|
+
const db = this.#dbField;
|
|
48
|
+
this.#dbField = postgres(this.options);
|
|
49
|
+
await db?.end();
|
|
40
50
|
}
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
async destroy(): Promise<void> {
|
|
53
|
+
const db = this.#dbField;
|
|
54
|
+
this.#dbField = undefined;
|
|
55
|
+
await db?.end();
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
get #db(): Sql {
|
|
59
|
+
if (!this.#dbField) {
|
|
60
|
+
throw new Error(`DataSource ${this.name} is not initialized.`);
|
|
61
|
+
}
|
|
62
|
+
return this.#dbField;
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
async #checkExecution(
|
|
51
66
|
workflowID: string,
|
|
52
|
-
|
|
67
|
+
stepID: number,
|
|
53
68
|
): Promise<{ output: string | null } | { error: string } | undefined> {
|
|
54
69
|
type Result = { output: string | null; error: string | null };
|
|
55
70
|
const result = await this.#db<Result[]>/*sql*/ `
|
|
56
71
|
SELECT output, error FROM dbos.transaction_completion
|
|
57
|
-
WHERE workflow_id = ${workflowID} AND function_num = ${
|
|
72
|
+
WHERE workflow_id = ${workflowID} AND function_num = ${stepID}`;
|
|
58
73
|
if (result.length === 0) {
|
|
59
74
|
return undefined;
|
|
60
75
|
}
|
|
@@ -63,16 +78,15 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
|
|
|
63
78
|
}
|
|
64
79
|
|
|
65
80
|
static async #recordOutput(
|
|
66
|
-
|
|
67
|
-
client: postgres.TransactionSql<{}>,
|
|
81
|
+
client: postgres.TransactionSql,
|
|
68
82
|
workflowID: string,
|
|
69
|
-
|
|
83
|
+
stepID: number,
|
|
70
84
|
output: string | null,
|
|
71
85
|
): Promise<void> {
|
|
72
86
|
try {
|
|
73
87
|
await client/*sql*/ `
|
|
74
88
|
INSERT INTO dbos.transaction_completion (workflow_id, function_num, output)
|
|
75
|
-
VALUES (${workflowID}, ${
|
|
89
|
+
VALUES (${workflowID}, ${stepID}, ${output})`;
|
|
76
90
|
} catch (error) {
|
|
77
91
|
if (isPGKeyConflictError(error)) {
|
|
78
92
|
throw new DBOSWorkflowConflictError(workflowID);
|
|
@@ -82,11 +96,11 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
|
|
|
82
96
|
}
|
|
83
97
|
}
|
|
84
98
|
|
|
85
|
-
async #recordError(workflowID: string,
|
|
99
|
+
async #recordError(workflowID: string, stepID: number, error: string): Promise<void> {
|
|
86
100
|
try {
|
|
87
101
|
await this.#db/*sql*/ `
|
|
88
102
|
INSERT INTO dbos.transaction_completion (workflow_id, function_num, error)
|
|
89
|
-
VALUES (${workflowID}, ${
|
|
103
|
+
VALUES (${workflowID}, ${stepID}, ${error})`;
|
|
90
104
|
} catch (error) {
|
|
91
105
|
if (isPGKeyConflictError(error)) {
|
|
92
106
|
throw new DBOSWorkflowConflictError(workflowID);
|
|
@@ -103,26 +117,24 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
|
|
|
103
117
|
...args: Args
|
|
104
118
|
): Promise<Return> {
|
|
105
119
|
const workflowID = DBOS.workflowID;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const functionNum = DBOS.stepID;
|
|
110
|
-
if (functionNum === undefined) {
|
|
111
|
-
throw new Error('Function Number is not set.');
|
|
120
|
+
const stepID = DBOS.stepID;
|
|
121
|
+
if (workflowID !== undefined && stepID === undefined) {
|
|
122
|
+
throw new Error('DBOS.stepID is undefined inside a workflow.');
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
const isolationLevel = config?.isolationLevel ? `ISOLATION LEVEL ${config.isolationLevel}` : '';
|
|
115
126
|
const readOnly = config?.readOnly ?? false;
|
|
116
127
|
const accessMode = config?.readOnly === undefined ? '' : readOnly ? 'READ ONLY' : 'READ WRITE';
|
|
117
|
-
const saveResults = !readOnly && workflowID;
|
|
128
|
+
const saveResults = !readOnly && workflowID !== undefined;
|
|
118
129
|
|
|
130
|
+
// Retry loop if appropriate
|
|
119
131
|
let retryWaitMS = 1;
|
|
120
132
|
const backoffFactor = 1.5;
|
|
121
|
-
const maxRetryWaitMS = 2000;
|
|
133
|
+
const maxRetryWaitMS = 2000; // Maximum wait 2 seconds.
|
|
122
134
|
|
|
123
135
|
while (true) {
|
|
124
136
|
// Check to see if this tx has already been executed
|
|
125
|
-
const previousResult = saveResults ? await this.#checkExecution(workflowID,
|
|
137
|
+
const previousResult = saveResults ? await this.#checkExecution(workflowID, stepID!) : undefined;
|
|
126
138
|
if (previousResult) {
|
|
127
139
|
DBOS.span?.setAttribute('cached', true);
|
|
128
140
|
|
|
@@ -135,18 +147,13 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
|
|
|
135
147
|
try {
|
|
136
148
|
const result = await this.#db.begin<Return>(`${isolationLevel} ${accessMode}`, async (client) => {
|
|
137
149
|
// execute user's transaction function
|
|
138
|
-
const result = await asyncLocalCtx.run({ client }, async () => {
|
|
150
|
+
const result = await asyncLocalCtx.run({ client, owner: this }, async () => {
|
|
139
151
|
return (await func.call(target, ...args)) as Return;
|
|
140
152
|
});
|
|
141
153
|
|
|
142
154
|
// save the output of read/write transactions
|
|
143
155
|
if (saveResults) {
|
|
144
|
-
await PostgresTransactionHandler.#recordOutput(
|
|
145
|
-
client,
|
|
146
|
-
workflowID,
|
|
147
|
-
functionNum,
|
|
148
|
-
SuperJSON.stringify(result),
|
|
149
|
-
);
|
|
156
|
+
await PostgresTransactionHandler.#recordOutput(client, workflowID, stepID!, SuperJSON.stringify(result));
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
return result;
|
|
@@ -162,7 +169,7 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
|
|
|
162
169
|
} else {
|
|
163
170
|
if (saveResults) {
|
|
164
171
|
const message = SuperJSON.stringify(error);
|
|
165
|
-
await this.#recordError(workflowID,
|
|
172
|
+
await this.#recordError(workflowID, stepID!, message);
|
|
166
173
|
}
|
|
167
174
|
|
|
168
175
|
throw error;
|
|
@@ -173,20 +180,29 @@ class PostgresTransactionHandler implements DataSourceTransactionHandler {
|
|
|
173
180
|
}
|
|
174
181
|
|
|
175
182
|
export class PostgresDataSource implements DBOSDataSource<PostgresTransactionOptions> {
|
|
176
|
-
|
|
177
|
-
static get client(): postgres.TransactionSql<{}> {
|
|
183
|
+
static #getClient(p?: PostgresTransactionHandler): postgres.TransactionSql {
|
|
178
184
|
if (!DBOS.isInTransaction()) {
|
|
179
|
-
throw new Error('
|
|
185
|
+
throw new Error('Invalid use of PostgresDataSource.client outside of a DBOS transaction.');
|
|
180
186
|
}
|
|
181
187
|
const ctx = asyncLocalCtx.getStore();
|
|
182
188
|
if (!ctx) {
|
|
183
|
-
throw new Error('
|
|
189
|
+
throw new Error('Invalid use of PostgresDataSource.client outside of a DBOS transaction.');
|
|
190
|
+
}
|
|
191
|
+
if (p && p !== ctx.owner) {
|
|
192
|
+
throw new Error('Invalid retrieval of `PostgresDataSource.client` from the wrong instance.');
|
|
184
193
|
}
|
|
185
194
|
return ctx.client;
|
|
186
195
|
}
|
|
187
196
|
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
static get client() {
|
|
198
|
+
return PostgresDataSource.#getClient(undefined);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get client() {
|
|
202
|
+
return PostgresDataSource.#getClient(this.#provider);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static async initializeDBOSSchema(options: Options = {}): Promise<void> {
|
|
190
206
|
const pg = postgres({ ...options, onnotice: () => {} });
|
|
191
207
|
try {
|
|
192
208
|
await pg.unsafe(createTransactionCompletionSchemaPG);
|
|
@@ -196,26 +212,25 @@ export class PostgresDataSource implements DBOSDataSource<PostgresTransactionOpt
|
|
|
196
212
|
}
|
|
197
213
|
}
|
|
198
214
|
|
|
199
|
-
readonly name: string;
|
|
200
215
|
#provider: PostgresTransactionHandler;
|
|
201
216
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
217
|
+
constructor(
|
|
218
|
+
readonly name: string,
|
|
219
|
+
options: Options = {},
|
|
220
|
+
) {
|
|
205
221
|
this.#provider = new PostgresTransactionHandler(name, options);
|
|
206
222
|
registerDataSource(this.#provider);
|
|
207
223
|
}
|
|
208
224
|
|
|
209
|
-
async runTransaction<T>(
|
|
210
|
-
return await runTransaction(
|
|
225
|
+
async runTransaction<T>(func: () => Promise<T>, config?: PostgresTransactionOptions) {
|
|
226
|
+
return await runTransaction(func, config?.name ?? func.name, { dsName: this.name, config });
|
|
211
227
|
}
|
|
212
228
|
|
|
213
229
|
registerTransaction<This, Args extends unknown[], Return>(
|
|
214
230
|
func: (this: This, ...args: Args) => Promise<Return>,
|
|
215
|
-
name: string,
|
|
216
231
|
config?: PostgresTransactionOptions,
|
|
217
232
|
): (this: This, ...args: Args) => Promise<Return> {
|
|
218
|
-
return registerTransaction(this.name, func, { name }, config);
|
|
233
|
+
return registerTransaction(this.name, func, { name: config?.name ?? func.name }, config);
|
|
219
234
|
}
|
|
220
235
|
|
|
221
236
|
transaction(config?: PostgresTransactionOptions) {
|
|
@@ -223,14 +238,17 @@ export class PostgresDataSource implements DBOSDataSource<PostgresTransactionOpt
|
|
|
223
238
|
const ds = this;
|
|
224
239
|
return function decorator<This, Args extends unknown[], Return>(
|
|
225
240
|
_target: object,
|
|
226
|
-
propertyKey:
|
|
241
|
+
propertyKey: PropertyKey,
|
|
227
242
|
descriptor: TypedPropertyDescriptor<(this: This, ...args: Args) => Promise<Return>>,
|
|
228
243
|
) {
|
|
229
244
|
if (!descriptor.value) {
|
|
230
245
|
throw Error('Use of decorator when original method is undefined');
|
|
231
246
|
}
|
|
232
247
|
|
|
233
|
-
descriptor.value = ds.registerTransaction(descriptor.value,
|
|
248
|
+
descriptor.value = ds.registerTransaction(descriptor.value, {
|
|
249
|
+
...config,
|
|
250
|
+
name: config?.name ?? String(propertyKey),
|
|
251
|
+
});
|
|
234
252
|
|
|
235
253
|
return descriptor;
|
|
236
254
|
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbos-inc/postgres-datasource",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "3.0.16-preview",
|
|
4
|
+
"description": "DBOS DataSource library for Postgres database client",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"homepage": "https://docs.dbos.dev/",
|
|
6
9
|
"repository": {
|
|
7
10
|
"type": "git",
|
|
8
11
|
"url": "https://github.com/dbos-inc/dbos-transact-ts",
|
|
9
|
-
"directory": "packages/
|
|
12
|
+
"directory": "packages/postgres-datasource"
|
|
10
13
|
},
|
|
11
|
-
"homepage": "https://docs.dbos.dev/",
|
|
12
|
-
"main": "index.js",
|
|
13
14
|
"scripts": {
|
|
14
15
|
"build": "tsc --project tsconfig.json",
|
|
15
16
|
"test": "jest --detectOpenHandles"
|
package/tests/config.test.ts
CHANGED
|
@@ -17,7 +17,7 @@ describe('PostgresDataSource.configure', () => {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test('configure creates tx outputs table', async () => {
|
|
20
|
-
await PostgresDataSource.
|
|
20
|
+
await PostgresDataSource.initializeDBOSSchema(config);
|
|
21
21
|
|
|
22
22
|
const client = new Client(config);
|
|
23
23
|
try {
|