@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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -0
  3. package/dist/src/adapters/index.d.ts +18 -0
  4. package/dist/src/adapters/index.d.ts.map +1 -0
  5. package/dist/src/adapters/index.js +35 -0
  6. package/dist/src/adapters/index.js.map +1 -0
  7. package/dist/src/adapters/typeorm.adapter.d.ts +42 -0
  8. package/dist/src/adapters/typeorm.adapter.d.ts.map +1 -0
  9. package/dist/src/adapters/typeorm.adapter.js +85 -0
  10. package/dist/src/adapters/typeorm.adapter.js.map +1 -0
  11. package/dist/src/entities/index.d.ts +2 -0
  12. package/dist/src/entities/index.d.ts.map +1 -0
  13. package/dist/src/entities/index.js +20 -0
  14. package/dist/src/entities/index.js.map +1 -0
  15. package/dist/src/entities/job-meta.entities.d.ts +96 -0
  16. package/dist/src/entities/job-meta.entities.d.ts.map +1 -0
  17. package/dist/src/entities/job-meta.entities.js +357 -0
  18. package/dist/src/entities/job-meta.entities.js.map +1 -0
  19. package/dist/src/index.d.ts +6 -0
  20. package/dist/src/index.d.ts.map +1 -0
  21. package/dist/src/index.js +74 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/migrations/1700000000000-CreateBatchMeta.d.ts +28 -0
  24. package/dist/src/migrations/1700000000000-CreateBatchMeta.d.ts.map +1 -0
  25. package/dist/src/migrations/1700000000000-CreateBatchMeta.js +83 -0
  26. package/dist/src/migrations/1700000000000-CreateBatchMeta.js.map +1 -0
  27. package/dist/src/repository/typeorm-job-repository.d.ts +57 -0
  28. package/dist/src/repository/typeorm-job-repository.d.ts.map +1 -0
  29. package/dist/src/repository/typeorm-job-repository.js +489 -0
  30. package/dist/src/repository/typeorm-job-repository.js.map +1 -0
  31. package/dist/src/transaction/typeorm-transaction-manager.d.ts +24 -0
  32. package/dist/src/transaction/typeorm-transaction-manager.d.ts.map +1 -0
  33. package/dist/src/transaction/typeorm-transaction-manager.js +55 -0
  34. package/dist/src/transaction/typeorm-transaction-manager.js.map +1 -0
  35. package/dist/src/typeorm.driver-provider.d.ts +22 -0
  36. package/dist/src/typeorm.driver-provider.d.ts.map +1 -0
  37. package/dist/src/typeorm.driver-provider.js +32 -0
  38. package/dist/src/typeorm.driver-provider.js.map +1 -0
  39. package/package.json +69 -0
  40. package/src/adapters/index.ts +17 -0
  41. package/src/adapters/typeorm.adapter.ts +82 -0
  42. package/src/entities/index.ts +1 -0
  43. package/src/entities/job-meta.entities.ts +184 -0
  44. package/src/index.ts +42 -0
  45. package/src/migrations/1700000000000-CreateBatchMeta.ts +100 -0
  46. package/src/repository/typeorm-job-repository.ts +548 -0
  47. package/src/transaction/typeorm-transaction-manager.ts +47 -0
  48. 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
+ }