@nest-batch/typeorm 0.2.0
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/LICENSE +21 -0
- package/README.md +263 -0
- package/dist/src/adapters/index.d.ts +18 -0
- package/dist/src/adapters/index.d.ts.map +1 -0
- package/dist/src/adapters/index.js +35 -0
- package/dist/src/adapters/index.js.map +1 -0
- package/dist/src/adapters/typeorm.adapter.d.ts +42 -0
- package/dist/src/adapters/typeorm.adapter.d.ts.map +1 -0
- package/dist/src/adapters/typeorm.adapter.js +85 -0
- package/dist/src/adapters/typeorm.adapter.js.map +1 -0
- package/dist/src/entities/index.d.ts +2 -0
- package/dist/src/entities/index.d.ts.map +1 -0
- package/dist/src/entities/index.js +20 -0
- package/dist/src/entities/index.js.map +1 -0
- package/dist/src/entities/job-meta.entities.d.ts +96 -0
- package/dist/src/entities/job-meta.entities.d.ts.map +1 -0
- package/dist/src/entities/job-meta.entities.js +357 -0
- package/dist/src/entities/job-meta.entities.js.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +74 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/migrations/1700000000000-CreateBatchMeta.d.ts +28 -0
- package/dist/src/migrations/1700000000000-CreateBatchMeta.d.ts.map +1 -0
- package/dist/src/migrations/1700000000000-CreateBatchMeta.js +83 -0
- package/dist/src/migrations/1700000000000-CreateBatchMeta.js.map +1 -0
- package/dist/src/repository/typeorm-job-repository.d.ts +57 -0
- package/dist/src/repository/typeorm-job-repository.d.ts.map +1 -0
- package/dist/src/repository/typeorm-job-repository.js +489 -0
- package/dist/src/repository/typeorm-job-repository.js.map +1 -0
- package/dist/src/transaction/typeorm-transaction-manager.d.ts +24 -0
- package/dist/src/transaction/typeorm-transaction-manager.d.ts.map +1 -0
- package/dist/src/transaction/typeorm-transaction-manager.js +55 -0
- package/dist/src/transaction/typeorm-transaction-manager.js.map +1 -0
- package/dist/src/typeorm.driver-provider.d.ts +22 -0
- package/dist/src/typeorm.driver-provider.d.ts.map +1 -0
- package/dist/src/typeorm.driver-provider.js +32 -0
- package/dist/src/typeorm.driver-provider.js.map +1 -0
- package/package.json +69 -0
- package/src/adapters/index.ts +17 -0
- package/src/adapters/typeorm.adapter.ts +82 -0
- package/src/entities/index.ts +1 -0
- package/src/entities/job-meta.entities.ts +184 -0
- package/src/index.ts +42 -0
- package/src/migrations/1700000000000-CreateBatchMeta.ts +100 -0
- package/src/repository/typeorm-job-repository.ts +548 -0
- package/src/transaction/typeorm-transaction-manager.ts +47 -0
- package/src/typeorm.driver-provider.ts +23 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/transaction/typeorm-transaction-manager.ts"],"sourcesContent":["import { Inject, Injectable } from '@nestjs/common';\nimport { randomUUID } from 'node:crypto';\nimport { DataSource, EntityManager } from 'typeorm';\nimport {\n TransactionManager,\n type TransactionContext,\n} from '@nest-batch/core';\nimport { TypeOrmDriverProvider } from '../typeorm.driver-provider';\n\nexport interface TypeOrmTransactionContext extends TransactionContext {\n readonly isActive: true;\n readonly id: string;\n readonly entityManager: EntityManager;\n}\n\n/**\n * TransactionManager bound to TypeORM 1.0.0's `DataSource.transaction()`.\n *\n * Wraps the user callback in a real DB transaction. On success the\n * transaction commits; if `fn(ctx)` throws, the transaction rolls back.\n *\n * The transactional EM is the one passed to the callback —\n * consumers should use that `entityManager` (not any\n * globally-injected one) so that all reads and writes are part of\n * the same transaction.\n */\n@Injectable()\nexport class TypeOrmTransactionManager extends TransactionManager {\n constructor(\n @Inject(TypeOrmDriverProvider) private readonly dataSource: DataSource,\n ) {\n super();\n }\n\n async withTransaction<T>(\n fn: (ctx: TypeOrmTransactionContext) => Promise<T>,\n ): Promise<T> {\n return this.dataSource.transaction(async (txEm) => {\n const ctx: TypeOrmTransactionContext = {\n isActive: true,\n id: randomUUID(),\n entityManager: txEm,\n };\n return fn(ctx);\n });\n }\n}\n"],"names":["TypeOrmTransactionManager","TransactionManager","dataSource","withTransaction","fn","transaction","txEm","ctx","isActive","id","randomUUID","entityManager"],"mappings":";;;;+BA2BaA;;;eAAAA;;;wBA3BsB;4BACR;yBACe;sBAInC;uCAC+B;;;;;;;;;;;;;;;AAoB/B,IAAA,AAAMA,4BAAN,MAAMA,kCAAkCC,wBAAkB;;IAC/D,YACE,AAAgDC,UAAsB,CACtE;QACA,KAAK,SAF2CA,aAAAA;IAGlD;IAEA,MAAMC,gBACJC,EAAkD,EACtC;QACZ,OAAO,IAAI,CAACF,UAAU,CAACG,WAAW,CAAC,OAAOC;YACxC,MAAMC,MAAiC;gBACrCC,UAAU;gBACVC,IAAIC,IAAAA,sBAAU;gBACdC,eAAeL;YACjB;YACA,OAAOF,GAAGG;QACZ;IACF;AACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `TypeOrmDriverProvider` — the abstract injection token the
|
|
3
|
+
* `@nest-batch/postgresql` (and future `@nest-batch/mysql`) driver
|
|
4
|
+
* sibling packages bind to the concrete `DataSource` for the chosen
|
|
5
|
+
* database.
|
|
6
|
+
*
|
|
7
|
+
* This package (`@nest-batch/typeorm`) is **driver-agnostic**: it
|
|
8
|
+
* does not import `@nestjs/typeorm` (which carries the Postgres
|
|
9
|
+
* driver) or any MySQL-specific `@nestjs/typeorm` companion. Instead,
|
|
10
|
+
* it exports the `TypeOrmDriverProvider` symbol as a `Provider`
|
|
11
|
+
* token; the driver sibling package binds the token to a concrete
|
|
12
|
+
* `DataSource` in its own `forRoot()` factory.
|
|
13
|
+
*
|
|
14
|
+
* The `TypeOrmJobRepository` / `TypeOrmTransactionManager` classes
|
|
15
|
+
* inject the token via the standard `@Inject(TypeOrmDriverProvider)`
|
|
16
|
+
* decorator and cast the resolved value to the host-owned
|
|
17
|
+
* `DataSource` shape. This mirrors the
|
|
18
|
+
* `@nestjs/typeorm` pattern of "host owns the connection, adapter
|
|
19
|
+
* owns the repository".
|
|
20
|
+
*/
|
|
21
|
+
export declare const TypeOrmDriverProvider: symbol;
|
|
22
|
+
//# sourceMappingURL=typeorm.driver-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeorm.driver-provider.d.ts","sourceRoot":"","sources":["../../src/typeorm.driver-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAEnC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `TypeOrmDriverProvider` — the abstract injection token the
|
|
3
|
+
* `@nest-batch/postgresql` (and future `@nest-batch/mysql`) driver
|
|
4
|
+
* sibling packages bind to the concrete `DataSource` for the chosen
|
|
5
|
+
* database.
|
|
6
|
+
*
|
|
7
|
+
* This package (`@nest-batch/typeorm`) is **driver-agnostic**: it
|
|
8
|
+
* does not import `@nestjs/typeorm` (which carries the Postgres
|
|
9
|
+
* driver) or any MySQL-specific `@nestjs/typeorm` companion. Instead,
|
|
10
|
+
* it exports the `TypeOrmDriverProvider` symbol as a `Provider`
|
|
11
|
+
* token; the driver sibling package binds the token to a concrete
|
|
12
|
+
* `DataSource` in its own `forRoot()` factory.
|
|
13
|
+
*
|
|
14
|
+
* The `TypeOrmJobRepository` / `TypeOrmTransactionManager` classes
|
|
15
|
+
* inject the token via the standard `@Inject(TypeOrmDriverProvider)`
|
|
16
|
+
* decorator and cast the resolved value to the host-owned
|
|
17
|
+
* `DataSource` shape. This mirrors the
|
|
18
|
+
* `@nestjs/typeorm` pattern of "host owns the connection, adapter
|
|
19
|
+
* owns the repository".
|
|
20
|
+
*/ "use strict";
|
|
21
|
+
Object.defineProperty(exports, "__esModule", {
|
|
22
|
+
value: true
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "TypeOrmDriverProvider", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function() {
|
|
27
|
+
return TypeOrmDriverProvider;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const TypeOrmDriverProvider = Symbol.for('@nest-batch/typeorm/TypeOrmDriverProvider');
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=typeorm.driver-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/typeorm.driver-provider.ts"],"sourcesContent":["/**\n * `TypeOrmDriverProvider` — the abstract injection token the\n * `@nest-batch/postgresql` (and future `@nest-batch/mysql`) driver\n * sibling packages bind to the concrete `DataSource` for the chosen\n * database.\n *\n * This package (`@nest-batch/typeorm`) is **driver-agnostic**: it\n * does not import `@nestjs/typeorm` (which carries the Postgres\n * driver) or any MySQL-specific `@nestjs/typeorm` companion. Instead,\n * it exports the `TypeOrmDriverProvider` symbol as a `Provider`\n * token; the driver sibling package binds the token to a concrete\n * `DataSource` in its own `forRoot()` factory.\n *\n * The `TypeOrmJobRepository` / `TypeOrmTransactionManager` classes\n * inject the token via the standard `@Inject(TypeOrmDriverProvider)`\n * decorator and cast the resolved value to the host-owned\n * `DataSource` shape. This mirrors the\n * `@nestjs/typeorm` pattern of \"host owns the connection, adapter\n * owns the repository\".\n */\nexport const TypeOrmDriverProvider: symbol = Symbol.for(\n '@nest-batch/typeorm/TypeOrmDriverProvider',\n);\n"],"names":["TypeOrmDriverProvider","Symbol","for"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;CAmBC;;;;+BACYA;;;eAAAA;;;AAAN,MAAMA,wBAAgCC,OAAOC,GAAG,CACrD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nest-batch/typeorm",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TypeORM 1.0.0 adapter SLOT for @nest-batch/core — JobRepository and TransactionManager interface shape, paired with @nest-batch/postgresql (Postgres driver) or @nest-batch/mysql (MySQL driver).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "easdkr",
|
|
7
|
+
"homepage": "https://github.com/easdkr/nest-batch/tree/main/packages/typeorm#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/easdkr/nest-batch.git",
|
|
11
|
+
"directory": "packages/typeorm"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/easdkr/nest-batch/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"nestjs",
|
|
18
|
+
"batch",
|
|
19
|
+
"typeorm",
|
|
20
|
+
"job-repository",
|
|
21
|
+
"persistence"
|
|
22
|
+
],
|
|
23
|
+
"main": "dist/src/index.js",
|
|
24
|
+
"types": "dist/src/index.d.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist/src",
|
|
27
|
+
"src",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@nestjs/common": "^10 || ^11",
|
|
35
|
+
"typeorm": "^1.0.0",
|
|
36
|
+
"@nest-batch/core": "^0.2.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"typeorm": {
|
|
40
|
+
"optional": false
|
|
41
|
+
},
|
|
42
|
+
"@nest-batch/core": {
|
|
43
|
+
"optional": false
|
|
44
|
+
},
|
|
45
|
+
"@nestjs/common": {
|
|
46
|
+
"optional": false
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@nestjs/common": "^11.0.0",
|
|
51
|
+
"@swc/cli": "^0.7.0",
|
|
52
|
+
"@swc/core": "^1.10.7",
|
|
53
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
54
|
+
"@types/node": "^22.0.0",
|
|
55
|
+
"better-sqlite3": "^12.0.0",
|
|
56
|
+
"reflect-metadata": "^0.2.2",
|
|
57
|
+
"typeorm": "^1.0.0",
|
|
58
|
+
"typescript": "^5.5.0",
|
|
59
|
+
"unplugin-swc": "^1.5.0",
|
|
60
|
+
"vitest": "^2.0.0",
|
|
61
|
+
"@nest-batch/core": "0.2.0"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "swc src -d dist --config-file ../../.swcrc && tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest",
|
|
67
|
+
"typecheck": "tsc --noEmit"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface for the `adapters/` package directory.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the `TypeOrmAdapter` factory so consumers can
|
|
5
|
+
* import it via `@nest-batch/typeorm` (the root barrel pulls
|
|
6
|
+
* this file in) without having to know the internal directory
|
|
7
|
+
* layout.
|
|
8
|
+
*
|
|
9
|
+
* The factory is the recommended entry point for wiring
|
|
10
|
+
* TypeORM 1.0.0 as the `@nest-batch/core` persistence backend.
|
|
11
|
+
* The legacy `NestBatchTypeOrmModule` (mentioned in
|
|
12
|
+
* `packages/typeorm/README.md`) was never actually implemented
|
|
13
|
+
* in code; this adapter is the canonical replacement and the
|
|
14
|
+
* shape that lines up with the new factory-pattern API
|
|
15
|
+
* (`NestBatchModule.forRoot({ adapters: { persistence, ... } })`).
|
|
16
|
+
*/
|
|
17
|
+
export * from './typeorm.adapter';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
JOB_REPOSITORY_TOKEN,
|
|
5
|
+
TRANSACTION_MANAGER_TOKEN,
|
|
6
|
+
type BatchAdapter,
|
|
7
|
+
} from '@nest-batch/core';
|
|
8
|
+
|
|
9
|
+
import { TypeOrmJobRepository } from '../repository/typeorm-job-repository';
|
|
10
|
+
import { TypeOrmTransactionManager } from '../transaction/typeorm-transaction-manager';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Empty Nest module class that owns the TypeORM batch adapter
|
|
14
|
+
* providers.
|
|
15
|
+
*
|
|
16
|
+
* The class has no body on purpose: it is purely a `DynamicModule`
|
|
17
|
+
* carrier for the `forRoot()` factory below. Nest's module system
|
|
18
|
+
* requires *some* class to identify the module — the empty class
|
|
19
|
+
* is the minimum possible surface and keeps the runtime allocation
|
|
20
|
+
* at one class (no decorators, no lifecycle hooks, no metadata).
|
|
21
|
+
*/
|
|
22
|
+
@Module({})
|
|
23
|
+
export class TypeOrmBatchModule {}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `TypeOrmAdapter` — the TypeORM 1.0.0 persistence adapter for
|
|
27
|
+
* `@nest-batch/core`.
|
|
28
|
+
*
|
|
29
|
+
* It owns the `JOB_REPOSITORY_TOKEN` and `TRANSACTION_MANAGER_TOKEN`
|
|
30
|
+
* bindings to the TypeORM-backed `TypeOrmJobRepository` /
|
|
31
|
+
* `TypeOrmTransactionManager` implementations. This adapter does
|
|
32
|
+
* not call `TypeOrmModule.forRoot()` — the host must call it in
|
|
33
|
+
* `AppModule.imports` (and register the six batch meta entities
|
|
34
|
+
* on the resulting `DataSource`).
|
|
35
|
+
*/
|
|
36
|
+
export class TypeOrmAdapter {
|
|
37
|
+
/**
|
|
38
|
+
* Build the `BatchAdapter` value the new factory-pattern
|
|
39
|
+
* `NestBatchModule.forRoot({ adapters: { persistence, ... } })`
|
|
40
|
+
* expects.
|
|
41
|
+
*
|
|
42
|
+
* No options are accepted on purpose — the host already owns
|
|
43
|
+
* the `TypeOrmModule.forRoot(...)` call. The adapter only needs
|
|
44
|
+
* to declare its own provider / export / `globalProviders`
|
|
45
|
+
* surface; the `DataSource` itself is the host's responsibility.
|
|
46
|
+
*
|
|
47
|
+
* @returns A `BatchAdapter` whose `module` is a `global: true`
|
|
48
|
+
* `DynamicModule` exposing `JOB_REPOSITORY_TOKEN` and
|
|
49
|
+
* `TRANSACTION_MANAGER_TOKEN` to the host application.
|
|
50
|
+
*/
|
|
51
|
+
static forRoot(): BatchAdapter {
|
|
52
|
+
return {
|
|
53
|
+
name: 'typeorm',
|
|
54
|
+
module: {
|
|
55
|
+
module: TypeOrmBatchModule,
|
|
56
|
+
global: true,
|
|
57
|
+
providers: [
|
|
58
|
+
TypeOrmJobRepository,
|
|
59
|
+
TypeOrmTransactionManager,
|
|
60
|
+
{
|
|
61
|
+
provide: JOB_REPOSITORY_TOKEN,
|
|
62
|
+
useExisting: TypeOrmJobRepository,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
provide: TRANSACTION_MANAGER_TOKEN,
|
|
66
|
+
useExisting: TypeOrmTransactionManager,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
exports: [JOB_REPOSITORY_TOKEN, TRANSACTION_MANAGER_TOKEN],
|
|
70
|
+
},
|
|
71
|
+
globalProviders: [
|
|
72
|
+
{ provide: JOB_REPOSITORY_TOKEN, useClass: TypeOrmJobRepository },
|
|
73
|
+
{
|
|
74
|
+
provide: TRANSACTION_MANAGER_TOKEN,
|
|
75
|
+
useClass: TypeOrmTransactionManager,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './job-meta.entities';
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Entity, PrimaryColumn, Column, Index } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `batch_job_instance` metadata row.
|
|
5
|
+
*
|
|
6
|
+
* One row per logical job instance. Uniqueness is enforced on
|
|
7
|
+
* (jobName, jobKey) so that the same canonical key resolves to the
|
|
8
|
+
* same instance across restarts. The composite unique index is
|
|
9
|
+
* declared on the entity and is also reified in the bundled
|
|
10
|
+
* migration under the same name.
|
|
11
|
+
*/
|
|
12
|
+
@Entity('batch_job_instance')
|
|
13
|
+
@Index('batch_job_instance_job_name_job_key_unique', ['jobName', 'jobKey'], { unique: true })
|
|
14
|
+
export class JobInstanceEntity {
|
|
15
|
+
@PrimaryColumn({ type: 'varchar', length: 255 })
|
|
16
|
+
id!: string;
|
|
17
|
+
|
|
18
|
+
@Column({ name: 'job_name', type: 'varchar', length: 255 })
|
|
19
|
+
jobName!: string;
|
|
20
|
+
|
|
21
|
+
@Column({ name: 'job_key', type: 'varchar', length: 255 })
|
|
22
|
+
jobKey!: string;
|
|
23
|
+
|
|
24
|
+
@Column({
|
|
25
|
+
name: 'created_at',
|
|
26
|
+
// `datetime` is portable across PostgreSQL and SQLite (the test
|
|
27
|
+
// driver). The bundled migration uses timestamptz on
|
|
28
|
+
// PostgreSQL by hand; SQLite loses the timezone qualifier,
|
|
29
|
+
// which is acceptable for a creation-time stamp that is never
|
|
30
|
+
// compared with sub-second precision in queries.
|
|
31
|
+
type: 'datetime',
|
|
32
|
+
default: () => 'CURRENT_TIMESTAMP',
|
|
33
|
+
})
|
|
34
|
+
createdAt: Date = new Date();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `batch_job_execution` metadata row.
|
|
39
|
+
*
|
|
40
|
+
* One row per job run. `status` is the stringified JobStatus enum
|
|
41
|
+
* (kept as a plain varchar — TypeORM enum support varies across
|
|
42
|
+
* drivers and v1.0.0 dropped a few columns we don't need). The
|
|
43
|
+
* `jobInstanceId` column is indexed because every contract lookup
|
|
44
|
+
* ("is this instance running?") scans by it.
|
|
45
|
+
*/
|
|
46
|
+
@Entity('batch_job_execution')
|
|
47
|
+
@Index('batch_job_execution_job_instance_id_index', ['jobInstanceId'])
|
|
48
|
+
export class JobExecutionEntity {
|
|
49
|
+
@PrimaryColumn({ type: 'varchar', length: 255 })
|
|
50
|
+
id!: string;
|
|
51
|
+
|
|
52
|
+
@Column({ name: 'job_instance_id', type: 'varchar', length: 255 })
|
|
53
|
+
jobInstanceId!: string;
|
|
54
|
+
|
|
55
|
+
@Column({ type: 'varchar', length: 20 })
|
|
56
|
+
status!: string;
|
|
57
|
+
|
|
58
|
+
@Column({ name: 'start_time', type: 'datetime', nullable: true })
|
|
59
|
+
startTime: Date | null = null;
|
|
60
|
+
|
|
61
|
+
@Column({ name: 'end_time', type: 'datetime', nullable: true })
|
|
62
|
+
endTime: Date | null = null;
|
|
63
|
+
|
|
64
|
+
@Column({ name: 'exit_code', type: 'varchar', length: 255, default: '' })
|
|
65
|
+
exitCode!: string;
|
|
66
|
+
|
|
67
|
+
@Column({ name: 'exit_message', type: 'text', default: '' })
|
|
68
|
+
exitMessage!: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* JSON-serialized `JobParameters` snapshot. Stored as `text` (not
|
|
72
|
+
* native `jsonb`) so the adapter works uniformly across SQLite (used
|
|
73
|
+
* in unit tests) and PostgreSQL/MySQL — the column is always a
|
|
74
|
+
* serialized payload, never queried by the ORM.
|
|
75
|
+
*/
|
|
76
|
+
@Column({ name: 'params', type: 'text', default: '{}' })
|
|
77
|
+
params!: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* `batch_step_execution` metadata row.
|
|
82
|
+
*
|
|
83
|
+
* One row per step run. Counters default to 0 so the entity can
|
|
84
|
+
* be persisted immediately upon creation, before any items are
|
|
85
|
+
* processed. `createdAt` is stamped on insert and used by
|
|
86
|
+
* `findLatestStepExecution` to resolve the most recently created
|
|
87
|
+
* step for a given `(jobExecutionId, stepName)` pair — a v4 UUID
|
|
88
|
+
* primary key does not preserve insertion order, so an explicit
|
|
89
|
+
* monotonic column is required.
|
|
90
|
+
*/
|
|
91
|
+
@Entity('batch_step_execution')
|
|
92
|
+
@Index('batch_step_execution_job_execution_id_index', ['jobExecutionId'])
|
|
93
|
+
export class StepExecutionEntity {
|
|
94
|
+
@PrimaryColumn({ type: 'varchar', length: 255 })
|
|
95
|
+
id!: string;
|
|
96
|
+
|
|
97
|
+
@Column({ name: 'job_execution_id', type: 'varchar', length: 255 })
|
|
98
|
+
jobExecutionId!: string;
|
|
99
|
+
|
|
100
|
+
@Column({ name: 'step_name', type: 'varchar', length: 255 })
|
|
101
|
+
stepName!: string;
|
|
102
|
+
|
|
103
|
+
@Column({ type: 'varchar', length: 20 })
|
|
104
|
+
status!: string;
|
|
105
|
+
|
|
106
|
+
@Column({ name: 'read_count', type: 'int', default: 0 })
|
|
107
|
+
readCount!: number;
|
|
108
|
+
|
|
109
|
+
@Column({ name: 'write_count', type: 'int', default: 0 })
|
|
110
|
+
writeCount!: number;
|
|
111
|
+
|
|
112
|
+
@Column({ name: 'skip_count', type: 'int', default: 0 })
|
|
113
|
+
skipCount!: number;
|
|
114
|
+
|
|
115
|
+
@Column({ name: 'rollback_count', type: 'int', default: 0 })
|
|
116
|
+
rollbackCount!: number;
|
|
117
|
+
|
|
118
|
+
@Column({ name: 'commit_count', type: 'int', default: 0 })
|
|
119
|
+
commitCount!: number;
|
|
120
|
+
|
|
121
|
+
@Column({ name: 'exit_code', type: 'varchar', length: 255, default: '' })
|
|
122
|
+
exitCode!: string;
|
|
123
|
+
|
|
124
|
+
@Column({ name: 'exit_message', type: 'text', default: '' })
|
|
125
|
+
exitMessage!: string;
|
|
126
|
+
|
|
127
|
+
@Column({
|
|
128
|
+
name: 'created_at',
|
|
129
|
+
type: 'datetime',
|
|
130
|
+
default: () => 'CURRENT_TIMESTAMP',
|
|
131
|
+
})
|
|
132
|
+
createdAt: Date = new Date();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* `batch_job_execution_context` metadata row.
|
|
137
|
+
*
|
|
138
|
+
* `data` is a JSON-serialized ExecutionContext payload. `version`
|
|
139
|
+
* guards against lost updates during concurrent writers.
|
|
140
|
+
*/
|
|
141
|
+
@Entity('batch_job_execution_context')
|
|
142
|
+
export class JobExecutionContextEntity {
|
|
143
|
+
@PrimaryColumn({ name: 'job_execution_id', type: 'varchar', length: 255 })
|
|
144
|
+
jobExecutionId!: string;
|
|
145
|
+
|
|
146
|
+
@Column({ type: 'text' })
|
|
147
|
+
data!: string;
|
|
148
|
+
|
|
149
|
+
@Column({ type: 'int', default: 0 })
|
|
150
|
+
version!: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* `batch_step_execution_context` metadata row.
|
|
155
|
+
*
|
|
156
|
+
* Mirrors the job-level context table but scoped to a single
|
|
157
|
+
* step execution. There is intentionally no params sibling table
|
|
158
|
+
* for steps — step parameters are derivable from the parent job
|
|
159
|
+
* execution params + the step execution context.
|
|
160
|
+
*/
|
|
161
|
+
@Entity('batch_step_execution_context')
|
|
162
|
+
export class StepExecutionContextEntity {
|
|
163
|
+
@PrimaryColumn({ name: 'step_execution_id', type: 'varchar', length: 255 })
|
|
164
|
+
stepExecutionId!: string;
|
|
165
|
+
|
|
166
|
+
@Column({ type: 'text' })
|
|
167
|
+
data!: string;
|
|
168
|
+
|
|
169
|
+
@Column({ type: 'int', default: 0 })
|
|
170
|
+
version!: number;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* All batch meta entities owned by this package. Hand to
|
|
175
|
+
* `DataSource#entityMetadatas` (or `entities:`) so TypeORM
|
|
176
|
+
* discovers them through the standard decorator scan.
|
|
177
|
+
*/
|
|
178
|
+
export const BATCH_META_ENTITIES = [
|
|
179
|
+
JobInstanceEntity,
|
|
180
|
+
JobExecutionEntity,
|
|
181
|
+
StepExecutionEntity,
|
|
182
|
+
JobExecutionContextEntity,
|
|
183
|
+
StepExecutionContextEntity,
|
|
184
|
+
] as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Public API barrel for @nest-batch/typeorm.
|
|
2
|
+
//
|
|
3
|
+
// The package is a **driver-agnostic adapter SLOT**. It owns the
|
|
4
|
+
// `TypeOrmAdapter` factory, the `TypeOrmJobRepository` /
|
|
5
|
+
// `TypeOrmTransactionManager` interface shape, and the
|
|
6
|
+
// `TypeOrmDriverProvider` injection token. It does NOT import
|
|
7
|
+
// `@nestjs/typeorm` (which carries the Postgres driver) — the
|
|
8
|
+
// driver implementation lives in the `@nest-batch/postgresql` (or
|
|
9
|
+
// `@nest-batch/mysql`) sibling package, which binds the
|
|
10
|
+
// `TypeOrmDriverProvider` token to the concrete `DataSource`
|
|
11
|
+
// in its own `forRoot()` factory.
|
|
12
|
+
//
|
|
13
|
+
// Apps wire the persistence concern into `NestBatchModule.forRoot()`
|
|
14
|
+
// via the new `BatchAdapter` factory pattern:
|
|
15
|
+
//
|
|
16
|
+
// import { NestBatchModule, InProcessAdapter } from '@nest-batch/core';
|
|
17
|
+
// import { TypeOrmAdapter } from '@nest-batch/typeorm';
|
|
18
|
+
// import { PostgresAdapter } from '@nest-batch/postgresql';
|
|
19
|
+
//
|
|
20
|
+
// // The host must also call
|
|
21
|
+
// // `TypeOrmModule.forRoot({ ... })` in their `AppModule.imports`.
|
|
22
|
+
// // The PostgresAdapter.forRoot() factory binds the
|
|
23
|
+
// // TypeOrmDriverProvider token to the host's DataSource.
|
|
24
|
+
//
|
|
25
|
+
// NestBatchModule.forRoot({
|
|
26
|
+
// adapters: {
|
|
27
|
+
// persistence: PostgresAdapter.forRoot(),
|
|
28
|
+
// transport: InProcessAdapter.forRoot(),
|
|
29
|
+
// },
|
|
30
|
+
// });
|
|
31
|
+
//
|
|
32
|
+
// The original `batchMetaEntities()` factory and the bundled
|
|
33
|
+
// `CreateBatchMeta1700000000000` migration moved to
|
|
34
|
+
// `@nest-batch/postgresql/src/migrations/`. The driver sibling owns
|
|
35
|
+
// the TypeORM-specific entity classes and the migration scripts;
|
|
36
|
+
// this package owns only the repository / transaction manager
|
|
37
|
+
// shape and the driver-provider token.
|
|
38
|
+
export { TypeOrmJobRepository } from './repository/typeorm-job-repository';
|
|
39
|
+
export type { TypeOrmTransactionContext } from './transaction/typeorm-transaction-manager';
|
|
40
|
+
export { TypeOrmTransactionManager } from './transaction/typeorm-transaction-manager';
|
|
41
|
+
export * from './adapters';
|
|
42
|
+
export * from './typeorm.driver-provider';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates the six batch meta-tables owned by
|
|
5
|
+
* this package:
|
|
6
|
+
*
|
|
7
|
+
* - batch_job_instance (root, unique on (job_name, job_key))
|
|
8
|
+
* - batch_job_execution (one per job run, indexed by instance)
|
|
9
|
+
* - batch_step_execution (one per step run, indexed by exec)
|
|
10
|
+
* - batch_job_execution_context (JSON payload + version, keyed by exec)
|
|
11
|
+
* - batch_step_execution_context (JSON payload + version, keyed by step)
|
|
12
|
+
*
|
|
13
|
+
* This migration intentionally uses generic ANSI/PostgreSQL
|
|
14
|
+
* syntax (varchar + text + timestamptz + int). The adapter
|
|
15
|
+
* package's `typeorm-job-repository` is portable across SQLite
|
|
16
|
+
* (test database) and PostgreSQL (production). Users targeting
|
|
17
|
+
* MySQL or another driver should adjust the `down` (and
|
|
18
|
+
* optionally `up`) to match their column types.
|
|
19
|
+
*
|
|
20
|
+
* The `params` column on `batch_job_execution` is a JSON
|
|
21
|
+
* snapshot — stored as `text` to keep the schema portable and
|
|
22
|
+
* always serialized, never queried structurally.
|
|
23
|
+
*/
|
|
24
|
+
export class CreateBatchMeta1700000000000 implements MigrationInterface {
|
|
25
|
+
name = 'CreateBatchMeta1700000000000';
|
|
26
|
+
|
|
27
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
28
|
+
await queryRunner.query(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS "batch_job_instance" (
|
|
30
|
+
"id" varchar(255) PRIMARY KEY,
|
|
31
|
+
"job_name" varchar(255) NOT NULL,
|
|
32
|
+
"job_key" varchar(255) NOT NULL,
|
|
33
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
34
|
+
CONSTRAINT "batch_job_instance_job_name_job_key_unique" UNIQUE ("job_name", "job_key")
|
|
35
|
+
)
|
|
36
|
+
`);
|
|
37
|
+
|
|
38
|
+
await queryRunner.query(`
|
|
39
|
+
CREATE TABLE IF NOT EXISTS "batch_job_execution" (
|
|
40
|
+
"id" varchar(255) PRIMARY KEY,
|
|
41
|
+
"job_instance_id" varchar(255) NOT NULL,
|
|
42
|
+
"status" varchar(20) NOT NULL,
|
|
43
|
+
"start_time" timestamptz NULL,
|
|
44
|
+
"end_time" timestamptz NULL,
|
|
45
|
+
"exit_code" varchar(255) NOT NULL DEFAULT '',
|
|
46
|
+
"exit_message" text NOT NULL DEFAULT '',
|
|
47
|
+
"params" text NOT NULL DEFAULT '{}'
|
|
48
|
+
)
|
|
49
|
+
`);
|
|
50
|
+
await queryRunner.query(`
|
|
51
|
+
CREATE INDEX IF NOT EXISTS "batch_job_execution_job_instance_id_index"
|
|
52
|
+
ON "batch_job_execution" ("job_instance_id")
|
|
53
|
+
`);
|
|
54
|
+
|
|
55
|
+
await queryRunner.query(`
|
|
56
|
+
CREATE TABLE IF NOT EXISTS "batch_step_execution" (
|
|
57
|
+
"id" varchar(255) PRIMARY KEY,
|
|
58
|
+
"job_execution_id" varchar(255) NOT NULL,
|
|
59
|
+
"step_name" varchar(255) NOT NULL,
|
|
60
|
+
"status" varchar(20) NOT NULL,
|
|
61
|
+
"read_count" int NOT NULL DEFAULT 0,
|
|
62
|
+
"write_count" int NOT NULL DEFAULT 0,
|
|
63
|
+
"skip_count" int NOT NULL DEFAULT 0,
|
|
64
|
+
"rollback_count" int NOT NULL DEFAULT 0,
|
|
65
|
+
"commit_count" int NOT NULL DEFAULT 0,
|
|
66
|
+
"exit_code" varchar(255) NOT NULL DEFAULT '',
|
|
67
|
+
"exit_message" text NOT NULL DEFAULT '',
|
|
68
|
+
"created_at" timestamptz NOT NULL DEFAULT now()
|
|
69
|
+
)
|
|
70
|
+
`);
|
|
71
|
+
await queryRunner.query(`
|
|
72
|
+
CREATE INDEX IF NOT EXISTS "batch_step_execution_job_execution_id_index"
|
|
73
|
+
ON "batch_step_execution" ("job_execution_id")
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
await queryRunner.query(`
|
|
77
|
+
CREATE TABLE IF NOT EXISTS "batch_job_execution_context" (
|
|
78
|
+
"job_execution_id" varchar(255) PRIMARY KEY,
|
|
79
|
+
"data" text NOT NULL,
|
|
80
|
+
"version" int NOT NULL DEFAULT 0
|
|
81
|
+
)
|
|
82
|
+
`);
|
|
83
|
+
|
|
84
|
+
await queryRunner.query(`
|
|
85
|
+
CREATE TABLE IF NOT EXISTS "batch_step_execution_context" (
|
|
86
|
+
"step_execution_id" varchar(255) PRIMARY KEY,
|
|
87
|
+
"data" text NOT NULL,
|
|
88
|
+
"version" int NOT NULL DEFAULT 0
|
|
89
|
+
)
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
94
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "batch_step_execution_context"`);
|
|
95
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "batch_job_execution_context"`);
|
|
96
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "batch_step_execution"`);
|
|
97
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "batch_job_execution"`);
|
|
98
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "batch_job_instance"`);
|
|
99
|
+
}
|
|
100
|
+
}
|