@dbos-inc/typeorm-datasource 5.3.4-preview → 5.3.5-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/package.json +3 -3
- package/index.ts +0 -424
- package/jest.config.js +0 -8
- package/tests/config.test.ts +0 -60
- package/tests/datasource.test.ts +0 -746
- package/tests/demoentities/Photo.ts +0 -14
- package/tests/demoentities/User.ts +0 -14
- package/tests/test-helpers.ts +0 -13
- package/tests/usersuppliedds.test.ts +0 -95
- package/tsconfig.json +0 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbos-inc/typeorm-datasource",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.5-preview",
|
|
4
4
|
"description": "DBOS DataSource library for TypeORM with PostgreSQL support",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"test": "npm run build && jest --detectOpenHandles"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"typeorm": "^0.3.24",
|
|
20
19
|
"pg": "^8.11.3",
|
|
21
|
-
"superjson": "^1.13"
|
|
20
|
+
"superjson": "^1.13",
|
|
21
|
+
"typeorm": "^0.3.24"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"@dbos-inc/dbos-sdk": "*"
|
package/index.ts
DELETED
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
import { PoolConfig } from 'pg';
|
|
2
|
-
import { DBOS, FunctionName } from '@dbos-inc/dbos-sdk';
|
|
3
|
-
import {
|
|
4
|
-
type DataSourceTransactionHandler,
|
|
5
|
-
isPGRetriableTransactionError,
|
|
6
|
-
DBOSError,
|
|
7
|
-
DBOSStepAlreadyRecordedError,
|
|
8
|
-
DBOSWorkflowConflictError,
|
|
9
|
-
assertStillOwnsWorkflow,
|
|
10
|
-
replayRecordedStep,
|
|
11
|
-
registerTransaction,
|
|
12
|
-
runTransaction,
|
|
13
|
-
DBOSDataSource,
|
|
14
|
-
registerDataSource,
|
|
15
|
-
PGTransactionConfig,
|
|
16
|
-
DataSourceMigrationOptions,
|
|
17
|
-
DataSourceSQLExecutor,
|
|
18
|
-
initializeDataSourceSchemaPG,
|
|
19
|
-
migrateDataSourcePG,
|
|
20
|
-
verifyDataSourcePG,
|
|
21
|
-
} from '@dbos-inc/dbos-sdk/datasource';
|
|
22
|
-
import { DataSource, EntityManager } from 'typeorm';
|
|
23
|
-
import type { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
|
|
24
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
|
25
|
-
import { SuperJSON } from 'superjson';
|
|
26
|
-
|
|
27
|
-
export interface TypeORMTransactionConfig extends PGTransactionConfig {
|
|
28
|
-
name?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface DBOSTypeOrmLocalCtx {
|
|
32
|
-
entityManager: EntityManager;
|
|
33
|
-
owner: TypeOrmTransactionHandler;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const asyncLocalCtx = new AsyncLocalStorage<DBOSTypeOrmLocalCtx>();
|
|
37
|
-
|
|
38
|
-
function typeOrmExecutor(db: DataSource | EntityManager): DataSourceSQLExecutor {
|
|
39
|
-
return async (sql) => await db.query<Record<string, unknown>[]>(sql);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface transaction_completion {
|
|
43
|
-
workflow_id: string;
|
|
44
|
-
function_num: number;
|
|
45
|
-
output: string | null;
|
|
46
|
-
error: string | null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
class TypeOrmTransactionHandler implements DataSourceTransactionHandler {
|
|
50
|
-
#createdDataSource: DataSource | undefined;
|
|
51
|
-
readonly schemaName: string;
|
|
52
|
-
|
|
53
|
-
constructor(
|
|
54
|
-
readonly name: string,
|
|
55
|
-
private readonly config?: PoolConfig,
|
|
56
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
57
|
-
private readonly entities?: Function[],
|
|
58
|
-
private readonly providedDataSource?: DataSource,
|
|
59
|
-
schemaName: string = 'dbos',
|
|
60
|
-
private readonly options: DataSourceMigrationOptions = {},
|
|
61
|
-
) {
|
|
62
|
-
this.schemaName = schemaName;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
static async createDataSource(
|
|
66
|
-
config: PoolConfig,
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
68
|
-
entities: Function[],
|
|
69
|
-
): Promise<DataSource> {
|
|
70
|
-
const ds = new DataSource({
|
|
71
|
-
type: 'postgres',
|
|
72
|
-
entities: entities,
|
|
73
|
-
url: config.connectionString,
|
|
74
|
-
host: config.host,
|
|
75
|
-
port: config.port,
|
|
76
|
-
username: config.user,
|
|
77
|
-
// TypeORM passes password and ssl to pg unchanged but types them more narrowly than pg does.
|
|
78
|
-
password: config.password as PostgresConnectionOptions['password'],
|
|
79
|
-
database: config.database,
|
|
80
|
-
ssl: config.ssl as PostgresConnectionOptions['ssl'],
|
|
81
|
-
connectTimeoutMS: config.connectionTimeoutMillis,
|
|
82
|
-
poolSize: config.max,
|
|
83
|
-
});
|
|
84
|
-
await ds.initialize();
|
|
85
|
-
return ds;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
get dataSource(): DataSource {
|
|
89
|
-
const ds = this.providedDataSource ?? this.#createdDataSource;
|
|
90
|
-
if (!ds) {
|
|
91
|
-
throw new Error(`DataSource ${this.name} is not initialized.`);
|
|
92
|
-
}
|
|
93
|
-
return ds;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async initialize(): Promise<void> {
|
|
97
|
-
if (!this.providedDataSource) {
|
|
98
|
-
const ds = this.#createdDataSource;
|
|
99
|
-
this.#createdDataSource = await TypeOrmTransactionHandler.createDataSource(this.config!, this.entities!);
|
|
100
|
-
await ds?.destroy();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (this.options.runMigrations === false) {
|
|
104
|
-
await verifyDataSourcePG(typeOrmExecutor(this.dataSource), this.schemaName, this.name);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
await this.dataSource.transaction((em) => migrateDataSourcePG(typeOrmExecutor(em), this.schemaName));
|
|
110
|
-
} catch (err) {
|
|
111
|
-
throw new Error(
|
|
112
|
-
`In initialization of 'TypeOrmDataSource' ${this.name}: The '${this.schemaName}' transaction schema could not be migrated: ${(err as Error).message}. This should be added to your database migrations.
|
|
113
|
-
See: https://docs.dbos.dev/typescript/tutorials/transaction-tutorial#installing-the-dbos-schema`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async destroy(): Promise<void> {
|
|
119
|
-
const ds = this.#createdDataSource;
|
|
120
|
-
this.#createdDataSource = undefined;
|
|
121
|
-
await ds?.destroy();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async deleteCheckpoints(workflowID: string, startStep: number, beforeCommit?: () => Promise<void>): Promise<void> {
|
|
125
|
-
await this.dataSource.transaction(async (entityManager: EntityManager) => {
|
|
126
|
-
await entityManager.query(
|
|
127
|
-
`DELETE FROM "${this.schemaName}".transaction_completion
|
|
128
|
-
WHERE workflow_id=$1 AND function_num>=$2;`,
|
|
129
|
-
[workflowID, startStep],
|
|
130
|
-
);
|
|
131
|
-
await beforeCommit?.();
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async #checkExecution(
|
|
136
|
-
workflowID: string,
|
|
137
|
-
stepID: number,
|
|
138
|
-
): Promise<{ output: string | null } | { error: string } | undefined> {
|
|
139
|
-
type TxOutputRow = Pick<transaction_completion, 'output' | 'error'>;
|
|
140
|
-
const rows = await this.dataSource.query<TxOutputRow[]>(
|
|
141
|
-
`SELECT output, error FROM "${this.schemaName}".transaction_completion
|
|
142
|
-
WHERE workflow_id=$1 AND function_num=$2;`,
|
|
143
|
-
[workflowID, stepID],
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
if (rows.length !== 1) {
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const { output, error } = rows[0];
|
|
151
|
-
return error !== null ? { error } : { output };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
static async #recordOutput(
|
|
155
|
-
entityManager: EntityManager,
|
|
156
|
-
workflowID: string,
|
|
157
|
-
stepID: number,
|
|
158
|
-
output: string,
|
|
159
|
-
schemaName: string,
|
|
160
|
-
): Promise<void> {
|
|
161
|
-
const rows = await entityManager.query<{ workflow_id: string }[]>(
|
|
162
|
-
`INSERT INTO "${schemaName}".transaction_completion (workflow_id, function_num, output)
|
|
163
|
-
VALUES ($1, $2, $3)
|
|
164
|
-
ON CONFLICT (workflow_id, function_num) DO NOTHING
|
|
165
|
-
RETURNING workflow_id`,
|
|
166
|
-
[workflowID, stepID, output],
|
|
167
|
-
);
|
|
168
|
-
if (rows.length === 0) {
|
|
169
|
-
throw new DBOSStepAlreadyRecordedError(workflowID, stepID);
|
|
170
|
-
}
|
|
171
|
-
// Holding this step's row, so a later owner's insert waits on our commit.
|
|
172
|
-
await assertStillOwnsWorkflow(workflowID);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async #recordError(workflowID: string, stepID: number, error: string): Promise<void> {
|
|
176
|
-
const rows = await this.dataSource.query<{ workflow_id: string }[]>(
|
|
177
|
-
`INSERT INTO "${this.schemaName}".transaction_completion (workflow_id, function_num, error)
|
|
178
|
-
VALUES ($1, $2, $3)
|
|
179
|
-
ON CONFLICT (workflow_id, function_num) DO NOTHING
|
|
180
|
-
RETURNING workflow_id`,
|
|
181
|
-
[workflowID, stepID, error],
|
|
182
|
-
);
|
|
183
|
-
if (rows.length === 0) {
|
|
184
|
-
throw new DBOSStepAlreadyRecordedError(workflowID, stepID);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// A duplicate execution won the race, so its recorded outcome is the durable one.
|
|
189
|
-
async #replayConflictingStep<Return>(workflowID: string, stepID: number): Promise<Return> {
|
|
190
|
-
const recorded = await this.#checkExecution(workflowID, stepID);
|
|
191
|
-
if (recorded === undefined) {
|
|
192
|
-
throw new DBOSError(
|
|
193
|
-
`Step ${stepID} of workflow ${workflowID} conflicted with a concurrent execution, but no recorded outcome was found`,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
return replayRecordedStep<Return>(recorded);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/* Required by base class */
|
|
200
|
-
async invokeTransactionFunction<This, Args extends unknown[], Return>(
|
|
201
|
-
config: PGTransactionConfig | undefined,
|
|
202
|
-
target: This,
|
|
203
|
-
func: (this: This, ...args: Args) => Promise<Return>,
|
|
204
|
-
...args: Args
|
|
205
|
-
): Promise<Return> {
|
|
206
|
-
const workflowID = DBOS.workflowID;
|
|
207
|
-
const stepID = DBOS.stepID;
|
|
208
|
-
if (workflowID !== undefined && stepID === undefined) {
|
|
209
|
-
throw new Error('DBOS.stepID is undefined inside a workflow.');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const isolationLevel = config?.isolationLevel ?? 'READ COMMITTED';
|
|
213
|
-
const readOnly = config?.readOnly ? true : false;
|
|
214
|
-
const saveResults = !readOnly && workflowID !== undefined;
|
|
215
|
-
|
|
216
|
-
// Retry loop if appropriate
|
|
217
|
-
let retryWaitMS = 1;
|
|
218
|
-
const backoffFactor = 1.5;
|
|
219
|
-
const maxRetryWaitMS = 2000; // Maximum wait 2 seconds.
|
|
220
|
-
|
|
221
|
-
while (true) {
|
|
222
|
-
// Check to see if this tx has already been executed
|
|
223
|
-
const previousResult = saveResults ? await this.#checkExecution(workflowID, stepID!) : undefined;
|
|
224
|
-
if (previousResult) {
|
|
225
|
-
return replayRecordedStep<Return>(previousResult);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
try {
|
|
229
|
-
const result = await this.dataSource.transaction(isolationLevel, async (entityManager: EntityManager) => {
|
|
230
|
-
if (readOnly) {
|
|
231
|
-
await entityManager.query('SET TRANSACTION READ ONLY');
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const result = await asyncLocalCtx.run({ entityManager, owner: this }, async () => {
|
|
235
|
-
return await func.call(target, ...args);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// save the output of read/write transactions
|
|
239
|
-
if (saveResults) {
|
|
240
|
-
await TypeOrmTransactionHandler.#recordOutput(
|
|
241
|
-
entityManager,
|
|
242
|
-
workflowID,
|
|
243
|
-
stepID!,
|
|
244
|
-
SuperJSON.stringify(result),
|
|
245
|
-
this.schemaName,
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return result;
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
return result;
|
|
253
|
-
} catch (error) {
|
|
254
|
-
if (saveResults && error instanceof DBOSStepAlreadyRecordedError) {
|
|
255
|
-
return await this.#replayConflictingStep<Return>(workflowID, stepID!);
|
|
256
|
-
}
|
|
257
|
-
// The new owner wins; recording an error here would replay it as this step's outcome.
|
|
258
|
-
if (error instanceof DBOSWorkflowConflictError) throw error;
|
|
259
|
-
if (isPGRetriableTransactionError(error)) {
|
|
260
|
-
DBOS.span?.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMS }, performance.now());
|
|
261
|
-
// Retry serialization failures.
|
|
262
|
-
await new Promise((resolve) => setTimeout(resolve, retryWaitMS));
|
|
263
|
-
retryWaitMS = Math.min(retryWaitMS * backoffFactor, maxRetryWaitMS);
|
|
264
|
-
continue;
|
|
265
|
-
} else {
|
|
266
|
-
if (saveResults) {
|
|
267
|
-
const message = SuperJSON.stringify(error);
|
|
268
|
-
try {
|
|
269
|
-
await this.#recordError(workflowID, stepID!, message);
|
|
270
|
-
} catch (recordError) {
|
|
271
|
-
if (recordError instanceof DBOSStepAlreadyRecordedError) {
|
|
272
|
-
return await this.#replayConflictingStep<Return>(workflowID, stepID!);
|
|
273
|
-
}
|
|
274
|
-
throw recordError;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
throw error;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export class TypeOrmDataSource implements DBOSDataSource<TypeORMTransactionConfig> {
|
|
286
|
-
// User calls this... DBOS not directly involved...
|
|
287
|
-
static #getEntityManager(p?: TypeOrmTransactionHandler): EntityManager {
|
|
288
|
-
if (!DBOS.isInTransaction()) {
|
|
289
|
-
throw new Error('Invalid use of TypeOrmDataSource.entityManager outside of a DBOS transaction');
|
|
290
|
-
}
|
|
291
|
-
const ctx = asyncLocalCtx.getStore();
|
|
292
|
-
if (!ctx) {
|
|
293
|
-
throw new Error('Invalid use of TypeOrmDataSource.entityManager outside of a DBOS transaction');
|
|
294
|
-
}
|
|
295
|
-
if (p && p !== ctx.owner) {
|
|
296
|
-
throw new Error('Invalid retrieval of `TypeOrmDataSource.entityManager` from the wrong instance');
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return ctx.entityManager;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
static get entityManager() {
|
|
303
|
-
return TypeOrmDataSource.#getEntityManager(undefined);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
get entityManager() {
|
|
307
|
-
return TypeOrmDataSource.#getEntityManager(this.#provider);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Create or migrate the DBOS transaction schema, typically with a privileged role,
|
|
312
|
-
* optionally granting `applicationRole` the minimal permissions to use it.
|
|
313
|
-
*/
|
|
314
|
-
static async initializeDBOSSchema(
|
|
315
|
-
config: PoolConfig,
|
|
316
|
-
schemaName: string = 'dbos',
|
|
317
|
-
options: { applicationRole?: string } = {},
|
|
318
|
-
): Promise<void> {
|
|
319
|
-
const ds = await TypeOrmTransactionHandler.createDataSource(config, []);
|
|
320
|
-
try {
|
|
321
|
-
await ds.transaction((em) =>
|
|
322
|
-
initializeDataSourceSchemaPG(typeOrmExecutor(em), schemaName, options.applicationRole),
|
|
323
|
-
);
|
|
324
|
-
} finally {
|
|
325
|
-
await ds.destroy();
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
#provider: TypeOrmTransactionHandler;
|
|
330
|
-
|
|
331
|
-
private constructor(
|
|
332
|
-
readonly name: string,
|
|
333
|
-
config?: PoolConfig,
|
|
334
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
335
|
-
entities?: Function[],
|
|
336
|
-
dataSource?: DataSource,
|
|
337
|
-
schemaName: string = 'dbos',
|
|
338
|
-
options: DataSourceMigrationOptions = {},
|
|
339
|
-
) {
|
|
340
|
-
if (config && entities) {
|
|
341
|
-
this.#provider = new TypeOrmTransactionHandler(name, config, entities, undefined, schemaName, options);
|
|
342
|
-
registerDataSource(this.#provider);
|
|
343
|
-
} else if (dataSource) {
|
|
344
|
-
this.#provider = new TypeOrmTransactionHandler(name, undefined, undefined, dataSource, schemaName, options);
|
|
345
|
-
registerDataSource(this.#provider);
|
|
346
|
-
} else {
|
|
347
|
-
throw new TypeError(
|
|
348
|
-
'TypeOrmDataSource must be provided with the underlying `DataSource` or with a configuration and entity list',
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
static createFromConfig(
|
|
354
|
-
name: string,
|
|
355
|
-
config: PoolConfig,
|
|
356
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
357
|
-
entities: Function[],
|
|
358
|
-
schemaName: string = 'dbos',
|
|
359
|
-
options: DataSourceMigrationOptions = {},
|
|
360
|
-
) {
|
|
361
|
-
return new TypeOrmDataSource(name, config, entities, undefined, schemaName, options);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
static createFromDataSource(
|
|
365
|
-
name: string,
|
|
366
|
-
ds: DataSource,
|
|
367
|
-
schemaName: string = 'dbos',
|
|
368
|
-
options: DataSourceMigrationOptions = {},
|
|
369
|
-
) {
|
|
370
|
-
return new TypeOrmDataSource(name, undefined, undefined, ds, schemaName, options);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Run `func` as a transaction against this DataSource
|
|
375
|
-
* @param func Function to run within a transactional context
|
|
376
|
-
* @param funcName Name to record for the transaction
|
|
377
|
-
* @param config Transaction configuration (isolation, etc)
|
|
378
|
-
* @returns Return value from `func`
|
|
379
|
-
*/
|
|
380
|
-
async runTransaction<T>(func: () => Promise<T>, config?: TypeORMTransactionConfig) {
|
|
381
|
-
return await runTransaction(func, config?.name ?? func.name, { dsName: this.name, config });
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Register function as DBOS transaction, to be called within the context
|
|
386
|
-
* of a transaction on this data source.
|
|
387
|
-
*
|
|
388
|
-
* @param func Function to wrap
|
|
389
|
-
* @param target Name of function
|
|
390
|
-
* @param config Transaction settings
|
|
391
|
-
* @returns Wrapped function, to be called instead of `func`
|
|
392
|
-
*/
|
|
393
|
-
registerTransaction<This, Args extends unknown[], Return>(
|
|
394
|
-
func: (this: This, ...args: Args) => Promise<Return>,
|
|
395
|
-
config?: TypeORMTransactionConfig & FunctionName,
|
|
396
|
-
): (this: This, ...args: Args) => Promise<Return> {
|
|
397
|
-
return registerTransaction(this.name, func, config);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Decorator establishing function as a transaction
|
|
402
|
-
*/
|
|
403
|
-
transaction(config?: TypeORMTransactionConfig) {
|
|
404
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
405
|
-
const ds = this;
|
|
406
|
-
return function decorator<This, Args extends unknown[], Return>(
|
|
407
|
-
target: object,
|
|
408
|
-
propertyKey: PropertyKey,
|
|
409
|
-
descriptor: TypedPropertyDescriptor<(this: This, ...args: Args) => Promise<Return>>,
|
|
410
|
-
) {
|
|
411
|
-
if (!descriptor.value) {
|
|
412
|
-
throw new Error('Use of decorator when original method is undefined');
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
descriptor.value = ds.registerTransaction(descriptor.value, {
|
|
416
|
-
...config,
|
|
417
|
-
name: config?.name ?? String(propertyKey),
|
|
418
|
-
ctorOrProto: target,
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
return descriptor;
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
}
|
package/jest.config.js
DELETED
package/tests/config.test.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { Client } from 'pg';
|
|
2
|
-
import { TypeOrmDataSource } from '../index';
|
|
3
|
-
import { dropDB, ensureDB } from './test-helpers';
|
|
4
|
-
|
|
5
|
-
describe('TypeOrmDataSource.configure', () => {
|
|
6
|
-
const config = { user: 'postgres', database: 'typeorm_ds_config_test' };
|
|
7
|
-
|
|
8
|
-
beforeAll(async () => {
|
|
9
|
-
const client = new Client({ ...config, database: 'postgres' });
|
|
10
|
-
try {
|
|
11
|
-
await client.connect();
|
|
12
|
-
await dropDB(client, config.database, true);
|
|
13
|
-
await ensureDB(client, config.database);
|
|
14
|
-
} finally {
|
|
15
|
-
await client.end();
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('configure creates tx outputs table', async () => {
|
|
20
|
-
await TypeOrmDataSource.initializeDBOSSchema(config);
|
|
21
|
-
|
|
22
|
-
const client = new Client(config);
|
|
23
|
-
try {
|
|
24
|
-
await client.connect();
|
|
25
|
-
const result = await client.query('SELECT workflow_id, function_num, output FROM dbos.transaction_completion');
|
|
26
|
-
expect(result.rows.length).toBe(0);
|
|
27
|
-
} finally {
|
|
28
|
-
await client.end();
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('connection settings', () => {
|
|
33
|
-
const password = process.env['PGPASSWORD'] || 'dbos';
|
|
34
|
-
const savedEnv = { PGPASSWORD: process.env['PGPASSWORD'], PGPASSFILE: process.env['PGPASSFILE'] };
|
|
35
|
-
|
|
36
|
-
// Hide the password from the environment and ~/.pgpass, so only the configured one can authenticate.
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
delete process.env['PGPASSWORD'];
|
|
39
|
-
process.env['PGPASSFILE'] = '/nonexistent/pgpass';
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
for (const [key, value] of Object.entries(savedEnv)) {
|
|
44
|
-
if (value === undefined) delete process.env[key];
|
|
45
|
-
else process.env[key] = value;
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('uses the configured password', async () => {
|
|
50
|
-
await expect(TypeOrmDataSource.initializeDBOSSchema({ ...config, password })).resolves.toBeUndefined();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('uses the configured ssl setting', async () => {
|
|
54
|
-
// Fails either way SSL is attempted: a server without SSL refuses it, and a local server's certificate is self-signed.
|
|
55
|
-
await expect(
|
|
56
|
-
TypeOrmDataSource.initializeDBOSSchema({ ...config, password, ssl: { rejectUnauthorized: true } }),
|
|
57
|
-
).rejects.toThrow(/SSL|certificate/);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
});
|