@nest-batch/typeorm 0.2.0 → 0.2.1
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/README.md +21 -25
- package/dist/src/entities/job-meta.entities.d.ts +2 -2
- package/dist/src/entities/job-meta.entities.d.ts.map +1 -1
- package/dist/src/entities/job-meta.entities.js +3 -4
- package/dist/src/entities/job-meta.entities.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +13 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/repository/typeorm-job-repository.d.ts +2 -2
- package/dist/src/repository/typeorm-job-repository.d.ts.map +1 -1
- package/dist/src/repository/typeorm-job-repository.js.map +1 -1
- package/package.json +3 -3
- package/src/entities/job-meta.entities.ts +5 -6
- package/src/index.ts +10 -6
- package/src/repository/typeorm-job-repository.ts +109 -75
- package/dist/src/migrations/1700000000000-CreateBatchMeta.d.ts +0 -28
- package/dist/src/migrations/1700000000000-CreateBatchMeta.d.ts.map +0 -1
- package/dist/src/migrations/1700000000000-CreateBatchMeta.js +0 -83
- package/dist/src/migrations/1700000000000-CreateBatchMeta.js.map +0 -1
- package/src/migrations/1700000000000-CreateBatchMeta.ts +0 -100
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# `@nest-batch/typeorm`
|
|
2
2
|
|
|
3
3
|
The TypeORM 1.0.0 adapter for [`@nest-batch/core`](../core). It owns
|
|
4
|
-
the same
|
|
4
|
+
the same five active batch meta-tables that
|
|
5
5
|
`@nest-batch/mikro-orm` ships, expressed as TypeORM 1.0 entities plus
|
|
6
|
-
|
|
6
|
+
an exported entity tuple. It exposes `TypeOrmJobRepository` and
|
|
7
7
|
`TypeOrmTransactionManager` and runs the shared core contract suite
|
|
8
8
|
against a real TypeORM `DataSource`.
|
|
9
9
|
|
|
@@ -36,9 +36,9 @@ Why 1.0.0 and not 0.3? Two reasons:
|
|
|
36
36
|
Maintaining 0.3 support would mean running the full codebase
|
|
37
37
|
through `Connection → DataSource` shims, which is a waste of
|
|
38
38
|
effort when 0.3 is on a separate support track.
|
|
39
|
-
2. The entity
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
2. The entity surface. A few decorators and metadata helpers moved
|
|
40
|
+
between 0.3 and 1.0. Supporting both means conditional entity
|
|
41
|
+
definitions, which is a smell.
|
|
42
42
|
|
|
43
43
|
The peer range is `^1.0.0`, so any 1.x release works. The boundary
|
|
44
44
|
test in core and a dedicated peer-range test in this package
|
|
@@ -72,25 +72,24 @@ without the host.
|
|
|
72
72
|
|
|
73
73
|
## Schema ownership
|
|
74
74
|
|
|
75
|
-
This package owns the same
|
|
75
|
+
This package owns the same five active batch meta tables as
|
|
76
76
|
`@nest-batch/mikro-orm`:
|
|
77
77
|
|
|
78
78
|
| Table | Purpose |
|
|
79
79
|
| ------------------------------ | ---------------------------------------------------------------------------------- |
|
|
80
80
|
| `batch_job_instance` | One row per logical job (unique on `job_name`+`job_key`). |
|
|
81
81
|
| `batch_job_execution` | One row per job run. Holds status, start/end, exit code/message. |
|
|
82
|
-
| `batch_job_execution_params` | Composite-keyed params (one row per param name). |
|
|
83
82
|
| `batch_step_execution` | One row per step run. Holds step status, exit code/message, last-chunk checkpoint. |
|
|
84
83
|
| `batch_job_execution_context` | JSON checkpoint + execution context (job-scoped). |
|
|
85
84
|
| `batch_step_execution_context` | JSON checkpoint + execution context (step-scoped). |
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
and
|
|
86
|
+
This package does not publish a runnable migration class. Apps that
|
|
87
|
+
already have a TypeORM migration directory should spread
|
|
88
|
+
`batchMetaEntities()` into their `entities` list, run TypeORM's
|
|
89
|
+
`migration:generate` / `migration:create` flow in the app
|
|
90
|
+
repository, and own the resulting migration file there.
|
|
92
91
|
|
|
93
|
-
> **Note:** The
|
|
92
|
+
> **Note:** The five batch meta entities are also exported as a
|
|
94
93
|
> single tuple under `batchMetaEntities` from the package root.
|
|
95
94
|
> Because the adapter no longer bootstraps the `DataSource` (the
|
|
96
95
|
> host owns the `TypeOrmModule.forRoot()` call), spreading
|
|
@@ -100,12 +99,11 @@ and renumber it to fit their own migration sequence.
|
|
|
100
99
|
> for the meta tables fail silently and the repository throws at
|
|
101
100
|
> first call.
|
|
102
101
|
|
|
103
|
-
> The
|
|
104
|
-
>
|
|
105
|
-
>
|
|
106
|
-
>
|
|
107
|
-
>
|
|
108
|
-
> production driver is PostgreSQL.
|
|
102
|
+
> The entities declare portable logical column types because the
|
|
103
|
+
> same contract is exercised against fast SQLite tests and real
|
|
104
|
+
> PostgreSQL/MySQL driver shells. Host-owned migrations may map
|
|
105
|
+
> those logical columns to dialect-specific types such as
|
|
106
|
+
> `timestamptz` or `datetime(6)`.
|
|
109
107
|
|
|
110
108
|
---
|
|
111
109
|
|
|
@@ -130,11 +128,7 @@ into its `entities` array, and pass the adapter's no-arg
|
|
|
130
128
|
import { Module } from '@nestjs/common';
|
|
131
129
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
132
130
|
import { NestBatchModule } from '@nest-batch/core';
|
|
133
|
-
import {
|
|
134
|
-
batchMetaEntities,
|
|
135
|
-
CreateBatchMeta1700000000000,
|
|
136
|
-
TypeOrmAdapter,
|
|
137
|
-
} from '@nest-batch/typeorm';
|
|
131
|
+
import { batchMetaEntities, TypeOrmAdapter } from '@nest-batch/typeorm';
|
|
138
132
|
import { ProductEntity } from './entities/product.entity';
|
|
139
133
|
|
|
140
134
|
@Module({
|
|
@@ -147,7 +141,9 @@ import { ProductEntity } from './entities/product.entity';
|
|
|
147
141
|
password: 'demo',
|
|
148
142
|
database: 'nest_batch_demo',
|
|
149
143
|
entities: [ProductEntity, ...batchMetaEntities()],
|
|
150
|
-
migrations: [
|
|
144
|
+
migrations: [
|
|
145
|
+
/* your app-owned migrations */
|
|
146
|
+
],
|
|
151
147
|
migrationsRun: true,
|
|
152
148
|
}),
|
|
153
149
|
NestBatchModule.forRoot({
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* One row per logical job instance. Uniqueness is enforced on
|
|
5
5
|
* (jobName, jobKey) so that the same canonical key resolves to the
|
|
6
6
|
* same instance across restarts. The composite unique index is
|
|
7
|
-
* declared on the entity
|
|
8
|
-
*
|
|
7
|
+
* declared on the entity so host-owned TypeORM migrations can
|
|
8
|
+
* generate the matching database constraint.
|
|
9
9
|
*/
|
|
10
10
|
export declare class JobInstanceEntity {
|
|
11
11
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-meta.entities.d.ts","sourceRoot":"","sources":["../../../src/entities/job-meta.entities.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,qBAEa,iBAAiB;IAE5B,EAAE,EAAG,MAAM,CAAC;IAGZ,OAAO,EAAG,MAAM,CAAC;IAGjB,MAAM,EAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"job-meta.entities.d.ts","sourceRoot":"","sources":["../../../src/entities/job-meta.entities.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,qBAEa,iBAAiB;IAE5B,EAAE,EAAG,MAAM,CAAC;IAGZ,OAAO,EAAG,MAAM,CAAC;IAGjB,MAAM,EAAG,MAAM,CAAC;IAWhB,SAAS,EAAE,IAAI,CAAc;CAC9B;AAED;;;;;;;;GAQG;AACH,qBAEa,kBAAkB;IAE7B,EAAE,EAAG,MAAM,CAAC;IAGZ,aAAa,EAAG,MAAM,CAAC;IAGvB,MAAM,EAAG,MAAM,CAAC;IAGhB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;IAG9B,OAAO,EAAE,IAAI,GAAG,IAAI,CAAQ;IAG5B,QAAQ,EAAG,MAAM,CAAC;IAGlB,WAAW,EAAG,MAAM,CAAC;IAErB;;;;;OAKG;IAEH,MAAM,EAAG,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,qBAEa,mBAAmB;IAE9B,EAAE,EAAG,MAAM,CAAC;IAGZ,cAAc,EAAG,MAAM,CAAC;IAGxB,QAAQ,EAAG,MAAM,CAAC;IAGlB,MAAM,EAAG,MAAM,CAAC;IAGhB,SAAS,EAAG,MAAM,CAAC;IAGnB,UAAU,EAAG,MAAM,CAAC;IAGpB,SAAS,EAAG,MAAM,CAAC;IAGnB,aAAa,EAAG,MAAM,CAAC;IAGvB,WAAW,EAAG,MAAM,CAAC;IAGrB,QAAQ,EAAG,MAAM,CAAC;IAGlB,WAAW,EAAG,MAAM,CAAC;IAOrB,SAAS,EAAE,IAAI,CAAc;CAC9B;AAED;;;;;GAKG;AACH,qBACa,yBAAyB;IAEpC,cAAc,EAAG,MAAM,CAAC;IAGxB,IAAI,EAAG,MAAM,CAAC;IAGd,OAAO,EAAG,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,qBACa,0BAA0B;IAErC,eAAe,EAAG,MAAM,CAAC;IAGzB,IAAI,EAAG,MAAM,CAAC;IAGd,OAAO,EAAG,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,iKAMtB,CAAC"}
|
|
@@ -71,10 +71,9 @@ _ts_decorate([
|
|
|
71
71
|
(0, _typeorm.Column)({
|
|
72
72
|
name: 'created_at',
|
|
73
73
|
// `datetime` is portable across PostgreSQL and SQLite (the test
|
|
74
|
-
// driver).
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// compared with sub-second precision in queries.
|
|
74
|
+
// driver). Hosts that generate PostgreSQL migrations can map the
|
|
75
|
+
// same logical column to timestamptz in their own migration
|
|
76
|
+
// pipeline.
|
|
78
77
|
type: 'datetime',
|
|
79
78
|
default: ()=>'CURRENT_TIMESTAMP'
|
|
80
79
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/entities/job-meta.entities.ts"],"sourcesContent":["import { Entity, PrimaryColumn, Column, Index } from 'typeorm';\n\n/**\n * `batch_job_instance` metadata row.\n *\n * One row per logical job instance. Uniqueness is enforced on\n * (jobName, jobKey) so that the same canonical key resolves to the\n * same instance across restarts. The composite unique index is\n * declared on the entity
|
|
1
|
+
{"version":3,"sources":["../../../src/entities/job-meta.entities.ts"],"sourcesContent":["import { Entity, PrimaryColumn, Column, Index } from 'typeorm';\n\n/**\n * `batch_job_instance` metadata row.\n *\n * One row per logical job instance. Uniqueness is enforced on\n * (jobName, jobKey) so that the same canonical key resolves to the\n * same instance across restarts. The composite unique index is\n * declared on the entity so host-owned TypeORM migrations can\n * generate the matching database constraint.\n */\n@Entity('batch_job_instance')\n@Index('batch_job_instance_job_name_job_key_unique', ['jobName', 'jobKey'], { unique: true })\nexport class JobInstanceEntity {\n @PrimaryColumn({ type: 'varchar', length: 255 })\n id!: string;\n\n @Column({ name: 'job_name', type: 'varchar', length: 255 })\n jobName!: string;\n\n @Column({ name: 'job_key', type: 'varchar', length: 255 })\n jobKey!: string;\n\n @Column({\n name: 'created_at',\n // `datetime` is portable across PostgreSQL and SQLite (the test\n // driver). Hosts that generate PostgreSQL migrations can map the\n // same logical column to timestamptz in their own migration\n // pipeline.\n type: 'datetime',\n default: () => 'CURRENT_TIMESTAMP',\n })\n createdAt: Date = new Date();\n}\n\n/**\n * `batch_job_execution` metadata row.\n *\n * One row per job run. `status` is the stringified JobStatus enum\n * (kept as a plain varchar — TypeORM enum support varies across\n * drivers and v1.0.0 dropped a few columns we don't need). The\n * `jobInstanceId` column is indexed because every contract lookup\n * (\"is this instance running?\") scans by it.\n */\n@Entity('batch_job_execution')\n@Index('batch_job_execution_job_instance_id_index', ['jobInstanceId'])\nexport class JobExecutionEntity {\n @PrimaryColumn({ type: 'varchar', length: 255 })\n id!: string;\n\n @Column({ name: 'job_instance_id', type: 'varchar', length: 255 })\n jobInstanceId!: string;\n\n @Column({ type: 'varchar', length: 20 })\n status!: string;\n\n @Column({ name: 'start_time', type: 'datetime', nullable: true })\n startTime: Date | null = null;\n\n @Column({ name: 'end_time', type: 'datetime', nullable: true })\n endTime: Date | null = null;\n\n @Column({ name: 'exit_code', type: 'varchar', length: 255, default: '' })\n exitCode!: string;\n\n @Column({ name: 'exit_message', type: 'text', default: '' })\n exitMessage!: string;\n\n /**\n * JSON-serialized `JobParameters` snapshot. Stored as `text` (not\n * native `jsonb`) so the adapter works uniformly across SQLite (used\n * in unit tests) and PostgreSQL/MySQL — the column is always a\n * serialized payload, never queried by the ORM.\n */\n @Column({ name: 'params', type: 'text', default: '{}' })\n params!: string;\n}\n\n/**\n * `batch_step_execution` metadata row.\n *\n * One row per step run. Counters default to 0 so the entity can\n * be persisted immediately upon creation, before any items are\n * processed. `createdAt` is stamped on insert and used by\n * `findLatestStepExecution` to resolve the most recently created\n * step for a given `(jobExecutionId, stepName)` pair — a v4 UUID\n * primary key does not preserve insertion order, so an explicit\n * monotonic column is required.\n */\n@Entity('batch_step_execution')\n@Index('batch_step_execution_job_execution_id_index', ['jobExecutionId'])\nexport class StepExecutionEntity {\n @PrimaryColumn({ type: 'varchar', length: 255 })\n id!: string;\n\n @Column({ name: 'job_execution_id', type: 'varchar', length: 255 })\n jobExecutionId!: string;\n\n @Column({ name: 'step_name', type: 'varchar', length: 255 })\n stepName!: string;\n\n @Column({ type: 'varchar', length: 20 })\n status!: string;\n\n @Column({ name: 'read_count', type: 'int', default: 0 })\n readCount!: number;\n\n @Column({ name: 'write_count', type: 'int', default: 0 })\n writeCount!: number;\n\n @Column({ name: 'skip_count', type: 'int', default: 0 })\n skipCount!: number;\n\n @Column({ name: 'rollback_count', type: 'int', default: 0 })\n rollbackCount!: number;\n\n @Column({ name: 'commit_count', type: 'int', default: 0 })\n commitCount!: number;\n\n @Column({ name: 'exit_code', type: 'varchar', length: 255, default: '' })\n exitCode!: string;\n\n @Column({ name: 'exit_message', type: 'text', default: '' })\n exitMessage!: string;\n\n @Column({\n name: 'created_at',\n type: 'datetime',\n default: () => 'CURRENT_TIMESTAMP',\n })\n createdAt: Date = new Date();\n}\n\n/**\n * `batch_job_execution_context` metadata row.\n *\n * `data` is a JSON-serialized ExecutionContext payload. `version`\n * guards against lost updates during concurrent writers.\n */\n@Entity('batch_job_execution_context')\nexport class JobExecutionContextEntity {\n @PrimaryColumn({ name: 'job_execution_id', type: 'varchar', length: 255 })\n jobExecutionId!: string;\n\n @Column({ type: 'text' })\n data!: string;\n\n @Column({ type: 'int', default: 0 })\n version!: number;\n}\n\n/**\n * `batch_step_execution_context` metadata row.\n *\n * Mirrors the job-level context table but scoped to a single\n * step execution. There is intentionally no params sibling table\n * for steps — step parameters are derivable from the parent job\n * execution params + the step execution context.\n */\n@Entity('batch_step_execution_context')\nexport class StepExecutionContextEntity {\n @PrimaryColumn({ name: 'step_execution_id', type: 'varchar', length: 255 })\n stepExecutionId!: string;\n\n @Column({ type: 'text' })\n data!: string;\n\n @Column({ type: 'int', default: 0 })\n version!: number;\n}\n\n/**\n * All batch meta entities owned by this package. Hand to\n * `DataSource#entityMetadatas` (or `entities:`) so TypeORM\n * discovers them through the standard decorator scan.\n */\nexport const BATCH_META_ENTITIES = [\n JobInstanceEntity,\n JobExecutionEntity,\n StepExecutionEntity,\n JobExecutionContextEntity,\n StepExecutionContextEntity,\n] as const;\n"],"names":["BATCH_META_ENTITIES","JobExecutionContextEntity","JobExecutionEntity","JobInstanceEntity","StepExecutionContextEntity","StepExecutionEntity","id","jobName","jobKey","createdAt","Date","type","length","name","default","unique","jobInstanceId","status","startTime","endTime","exitCode","exitMessage","params","nullable","jobExecutionId","stepName","readCount","writeCount","skipCount","rollbackCount","commitCount","data","version","stepExecutionId"],"mappings":";;;;;;;;;;;QAgLaA;eAAAA;;QApCAC;eAAAA;;QA9FAC;eAAAA;;QAjCAC;eAAAA;;QAmJAC;eAAAA;;QArEAC;eAAAA;;;yBA3FwC;;;;;;;;;;AAa9C,IAAA,AAAMF,oBAAN,MAAMA;IAEXG,GAAY;IAGZC,QAAiB;IAGjBC,OAAgB;IAWhBC,YAAkB,IAAIC,OAAO;AAC/B;;;QAnBmBC,MAAM;QAAWC,QAAQ;;;;;;QAGhCC,MAAM;QAAYF,MAAM;QAAWC,QAAQ;;;;;;QAG3CC,MAAM;QAAWF,MAAM;QAAWC,QAAQ;;;;;;QAIlDC,MAAM;QACN,gEAAgE;QAChE,iEAAiE;QACjE,4DAA4D;QAC5D,YAAY;QACZF,MAAM;QACNG,SAAS,IAAM;;;;;;;QAlBmC;QAAW;;QAAaC,QAAQ;;;AAkC/E,IAAA,AAAMb,qBAAN,MAAMA;IAEXI,GAAY;IAGZU,cAAuB;IAGvBC,OAAgB;IAGhBC,YAAyB,KAAK;IAG9BC,UAAuB,KAAK;IAG5BC,SAAkB;IAGlBC,YAAqB;IAErB;;;;;GAKC,GACD,AACAC,OAAgB;AAClB;;;QA7BmBX,MAAM;QAAWC,QAAQ;;;;;;QAGhCC,MAAM;QAAmBF,MAAM;QAAWC,QAAQ;;;;;;QAGlDD,MAAM;QAAWC,QAAQ;;;;;;QAGzBC,MAAM;QAAcF,MAAM;QAAYY,UAAU;;;;;;QAGhDV,MAAM;QAAYF,MAAM;QAAYY,UAAU;;;;;;QAG9CV,MAAM;QAAaF,MAAM;QAAWC,QAAQ;QAAKE,SAAS;;;;;;QAG1DD,MAAM;QAAgBF,MAAM;QAAQG,SAAS;;;;;;QAS7CD,MAAM;QAAUF,MAAM;QAAQG,SAAS;;;;;;;QA7BE;;;AA8C9C,IAAA,AAAMT,sBAAN,MAAMA;IAEXC,GAAY;IAGZkB,eAAwB;IAGxBC,SAAkB;IAGlBR,OAAgB;IAGhBS,UAAmB;IAGnBC,WAAoB;IAGpBC,UAAmB;IAGnBC,cAAuB;IAGvBC,YAAqB;IAGrBV,SAAkB;IAGlBC,YAAqB;IAOrBZ,YAAkB,IAAIC,OAAO;AAC/B;;;QAvCmBC,MAAM;QAAWC,QAAQ;;;;;;QAGhCC,MAAM;QAAoBF,MAAM;QAAWC,QAAQ;;;;;;QAGnDC,MAAM;QAAaF,MAAM;QAAWC,QAAQ;;;;;;QAG5CD,MAAM;QAAWC,QAAQ;;;;;;QAGzBC,MAAM;QAAcF,MAAM;QAAOG,SAAS;;;;;;QAG1CD,MAAM;QAAeF,MAAM;QAAOG,SAAS;;;;;;QAG3CD,MAAM;QAAcF,MAAM;QAAOG,SAAS;;;;;;QAG1CD,MAAM;QAAkBF,MAAM;QAAOG,SAAS;;;;;;QAG9CD,MAAM;QAAgBF,MAAM;QAAOG,SAAS;;;;;;QAG5CD,MAAM;QAAaF,MAAM;QAAWC,QAAQ;QAAKE,SAAS;;;;;;QAG1DD,MAAM;QAAgBF,MAAM;QAAQG,SAAS;;;;;;QAIrDD,MAAM;QACNF,MAAM;QACNG,SAAS,IAAM;;;;;;;QAtCoC;;;AAkDhD,IAAA,AAAMb,4BAAN,MAAMA;IAEXuB,eAAwB;IAGxBO,KAAc;IAGdC,QAAiB;AACnB;;;QARmBnB,MAAM;QAAoBF,MAAM;QAAWC,QAAQ;;;;;;QAG1DD,MAAM;;;;;;QAGNA,MAAM;QAAOG,SAAS;;;;;;;AAa3B,IAAA,AAAMV,6BAAN,MAAMA;IAEX6B,gBAAyB;IAGzBF,KAAc;IAGdC,QAAiB;AACnB;;;QARmBnB,MAAM;QAAqBF,MAAM;QAAWC,QAAQ;;;;;;QAG3DD,MAAM;;;;;;QAGNA,MAAM;QAAOG,SAAS;;;;;;;AAS3B,MAAMd,sBAAsB;IACjCG;IACAD;IACAG;IACAJ;IACAG;CACD"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { BATCH_META_ENTITIES } from './entities';
|
|
1
2
|
export { TypeOrmJobRepository } from './repository/typeorm-job-repository';
|
|
2
3
|
export type { TypeOrmTransactionContext } from './transaction/typeorm-transaction-manager';
|
|
3
4
|
export { TypeOrmTransactionManager } from './transaction/typeorm-transaction-manager';
|
|
4
5
|
export * from './adapters';
|
|
6
|
+
export { BATCH_META_ENTITIES } from './entities';
|
|
5
7
|
export * from './typeorm.driver-provider';
|
|
8
|
+
export declare const batchMetaEntities: () => typeof BATCH_META_ENTITIES;
|
|
6
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,YAAY,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AAC3F,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAE1C,eAAO,MAAM,iBAAiB,QAAO,OAAO,mBAA0C,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -29,12 +29,11 @@
|
|
|
29
29
|
// },
|
|
30
30
|
// });
|
|
31
31
|
//
|
|
32
|
-
// The
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// shape and the driver-provider token.
|
|
32
|
+
// The TypeORM entity tuple stays in this package because it is the
|
|
33
|
+
// schema contract consumed by a host-owned TypeORM DataSource. Apps
|
|
34
|
+
// generate and own their runnable migration files in their own
|
|
35
|
+
// migration workflow. Driver siblings bind the
|
|
36
|
+
// `TypeOrmDriverProvider` token to a concrete database connection.
|
|
38
37
|
"use strict";
|
|
39
38
|
Object.defineProperty(exports, "__esModule", {
|
|
40
39
|
value: true
|
|
@@ -46,13 +45,20 @@ function _export(target, all) {
|
|
|
46
45
|
});
|
|
47
46
|
}
|
|
48
47
|
_export(exports, {
|
|
48
|
+
get BATCH_META_ENTITIES () {
|
|
49
|
+
return _entities.BATCH_META_ENTITIES;
|
|
50
|
+
},
|
|
49
51
|
get TypeOrmJobRepository () {
|
|
50
52
|
return _typeormjobrepository.TypeOrmJobRepository;
|
|
51
53
|
},
|
|
52
54
|
get TypeOrmTransactionManager () {
|
|
53
55
|
return _typeormtransactionmanager.TypeOrmTransactionManager;
|
|
56
|
+
},
|
|
57
|
+
get batchMetaEntities () {
|
|
58
|
+
return batchMetaEntities;
|
|
54
59
|
}
|
|
55
60
|
});
|
|
61
|
+
const _entities = require("./entities");
|
|
56
62
|
const _typeormjobrepository = require("./repository/typeorm-job-repository");
|
|
57
63
|
const _typeormtransactionmanager = require("./transaction/typeorm-transaction-manager");
|
|
58
64
|
_export_star(require("./adapters"), exports);
|
|
@@ -70,5 +76,6 @@ function _export_star(from, to) {
|
|
|
70
76
|
});
|
|
71
77
|
return from;
|
|
72
78
|
}
|
|
79
|
+
const batchMetaEntities = ()=>_entities.BATCH_META_ENTITIES;
|
|
73
80
|
|
|
74
81
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["// Public API barrel for @nest-batch/typeorm.\n//\n// The package is a **driver-agnostic adapter SLOT**. It owns the\n// `TypeOrmAdapter` factory, the `TypeOrmJobRepository` /\n// `TypeOrmTransactionManager` interface shape, and the\n// `TypeOrmDriverProvider` injection token. It does NOT import\n// `@nestjs/typeorm` (which carries the Postgres driver) — the\n// driver implementation lives in the `@nest-batch/postgresql` (or\n// `@nest-batch/mysql`) sibling package, which binds the\n// `TypeOrmDriverProvider` token to the concrete `DataSource`\n// in its own `forRoot()` factory.\n//\n// Apps wire the persistence concern into `NestBatchModule.forRoot()`\n// via the new `BatchAdapter` factory pattern:\n//\n// import { NestBatchModule, InProcessAdapter } from '@nest-batch/core';\n// import { TypeOrmAdapter } from '@nest-batch/typeorm';\n// import { PostgresAdapter } from '@nest-batch/postgresql';\n//\n// // The host must also call\n// // `TypeOrmModule.forRoot({ ... })` in their `AppModule.imports`.\n// // The PostgresAdapter.forRoot() factory binds the\n// // TypeOrmDriverProvider token to the host's DataSource.\n//\n// NestBatchModule.forRoot({\n// adapters: {\n// persistence: PostgresAdapter.forRoot(),\n// transport: InProcessAdapter.forRoot(),\n// },\n// });\n//\n// The
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["// Public API barrel for @nest-batch/typeorm.\n//\n// The package is a **driver-agnostic adapter SLOT**. It owns the\n// `TypeOrmAdapter` factory, the `TypeOrmJobRepository` /\n// `TypeOrmTransactionManager` interface shape, and the\n// `TypeOrmDriverProvider` injection token. It does NOT import\n// `@nestjs/typeorm` (which carries the Postgres driver) — the\n// driver implementation lives in the `@nest-batch/postgresql` (or\n// `@nest-batch/mysql`) sibling package, which binds the\n// `TypeOrmDriverProvider` token to the concrete `DataSource`\n// in its own `forRoot()` factory.\n//\n// Apps wire the persistence concern into `NestBatchModule.forRoot()`\n// via the new `BatchAdapter` factory pattern:\n//\n// import { NestBatchModule, InProcessAdapter } from '@nest-batch/core';\n// import { TypeOrmAdapter } from '@nest-batch/typeorm';\n// import { PostgresAdapter } from '@nest-batch/postgresql';\n//\n// // The host must also call\n// // `TypeOrmModule.forRoot({ ... })` in their `AppModule.imports`.\n// // The PostgresAdapter.forRoot() factory binds the\n// // TypeOrmDriverProvider token to the host's DataSource.\n//\n// NestBatchModule.forRoot({\n// adapters: {\n// persistence: PostgresAdapter.forRoot(),\n// transport: InProcessAdapter.forRoot(),\n// },\n// });\n//\n// The TypeORM entity tuple stays in this package because it is the\n// schema contract consumed by a host-owned TypeORM DataSource. Apps\n// generate and own their runnable migration files in their own\n// migration workflow. Driver siblings bind the\n// `TypeOrmDriverProvider` token to a concrete database connection.\nimport { BATCH_META_ENTITIES } from './entities';\n\nexport { TypeOrmJobRepository } from './repository/typeorm-job-repository';\nexport type { TypeOrmTransactionContext } from './transaction/typeorm-transaction-manager';\nexport { TypeOrmTransactionManager } from './transaction/typeorm-transaction-manager';\nexport * from './adapters';\nexport { BATCH_META_ENTITIES } from './entities';\nexport * from './typeorm.driver-provider';\n\nexport const batchMetaEntities = (): typeof BATCH_META_ENTITIES => BATCH_META_ENTITIES;\n"],"names":["BATCH_META_ENTITIES","TypeOrmJobRepository","TypeOrmTransactionManager","batchMetaEntities"],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,iEAAiE;AACjE,yDAAyD;AACzD,uDAAuD;AACvD,8DAA8D;AAC9D,8DAA8D;AAC9D,kEAAkE;AAClE,wDAAwD;AACxD,6DAA6D;AAC7D,kCAAkC;AAClC,EAAE;AACF,qEAAqE;AACrE,8CAA8C;AAC9C,EAAE;AACF,0EAA0E;AAC1E,0DAA0D;AAC1D,8DAA8D;AAC9D,EAAE;AACF,+BAA+B;AAC/B,sEAAsE;AACtE,uDAAuD;AACvD,6DAA6D;AAC7D,EAAE;AACF,8BAA8B;AAC9B,kBAAkB;AAClB,gDAAgD;AAChD,+CAA+C;AAC/C,SAAS;AACT,QAAQ;AACR,EAAE;AACF,mEAAmE;AACnE,oEAAoE;AACpE,+DAA+D;AAC/D,+CAA+C;AAC/C,mEAAmE;;;;;;;;;;;;QAO1DA;eAAAA,6BAAmB;;QAJnBC;eAAAA,0CAAoB;;QAEpBC;eAAAA,oDAAyB;;QAKrBC;eAAAA;;;0BATuB;sCAEC;2CAEK;qBAC5B;qBAEA;;;;;;;;;;;;;;AAEP,MAAMA,oBAAoB,IAAkCH,6BAAmB"}
|
|
@@ -8,8 +8,8 @@ import type { JobInstance, JobExecution, JobExecutionPatch, JobParameters, StepE
|
|
|
8
8
|
* provided by the `@nest-batch/postgresql` (or future
|
|
9
9
|
* `@nest-batch/mysql`) driver sibling via the `TypeOrmDriverProvider`
|
|
10
10
|
* token. The repository itself uses raw SQL via `EntityManager.query`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* against the table contract represented by this package's exported
|
|
12
|
+
* TypeORM entities. The consuming app owns the runnable migration.
|
|
13
13
|
*
|
|
14
14
|
* The contract guarantees:
|
|
15
15
|
* - `getOrCreateJobInstance` is race-safe via the (jobName, jobKey)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeorm-job-repository.d.ts","sourceRoot":"","sources":["../../../src/repository/typeorm-job-repository.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAqB,MAAM,SAAS,CAAC;AACxD,OAAO,EACL,aAAa,EAOd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"typeorm-job-repository.d.ts","sourceRoot":"","sources":["../../../src/repository/typeorm-job-repository.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAqB,MAAM,SAAS,CAAC;AACxD,OAAO,EACL,aAAa,EAOd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AA6G1B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBACa,oBAAqB,SAAQ,aAAa;IACV,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,UAAU;IAIlF,OAAO,CAAC,EAAE;IAIJ,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAsC1E,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAoBvF,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,YAAY,CAAC;IA6DlB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgChF,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IASzD,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAWlE,gBAAgB,CAAC,MAAM,GAAE,iBAAsB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAsBxE,iBAAiB,CAAC,MAAM,GAAE,kBAAuB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAgCpF,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAY3E,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAWrF,mBAAmB,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA4CtF,gBAAgB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAS/D,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAWnF;;;;;;;;OAQG;IACG,uBAAuB,CAC3B,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAY1B,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4BrE,oBAAoB,CACxB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,gBAAgB,EACrB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;CA4CjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/repository/typeorm-job-repository.ts"],"sourcesContent":["import { Inject, Injectable } from '@nestjs/common';\nimport { randomUUID } from 'node:crypto';\nimport { DataSource, EntityManager, In } from 'typeorm';\nimport {\n JobRepository,\n JobExecutionAlreadyRunningError,\n assertJsonSerializable,\n serializeContext,\n deserializeContext,\n JobStatus,\n StepStatus,\n} from '@nest-batch/core';\nimport type {\n JobInstance,\n JobExecution,\n JobExecutionPatch,\n JobParameters,\n StepExecution,\n StepExecutionPatch,\n ExecutionContext,\n ExecutionScope,\n JobInstanceFilter,\n JobExecutionFilter,\n} from '@nest-batch/core';\nimport { TypeOrmDriverProvider } from '../typeorm.driver-provider';\n\nfunction scopeKey(scope: ExecutionScope): string {\n if ('jobExecutionId' in scope) return `job::${scope.jobExecutionId}`;\n return `step::${scope.stepExecutionId}`;\n}\n\nfunction deepClone<T>(value: T): T {\n if (value === null || typeof value !== 'object') return value;\n if (value instanceof Date) return new Date(value.getTime()) as unknown as T;\n if (Array.isArray(value)) return value.map((v) => deepClone(v)) as unknown as T;\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as Record<string, unknown>)) {\n out[k] = deepClone((value as Record<string, unknown>)[k]);\n }\n return out as T;\n}\n\ninterface JobInstanceRow {\n id: string;\n job_name: string;\n job_key: string;\n created_at: string | Date;\n}\n\ninterface JobExecutionRow {\n id: string;\n job_instance_id: string;\n status: string;\n start_time: string | Date | null;\n end_time: string | Date | null;\n exit_code: string;\n exit_message: string;\n params: string;\n}\n\ninterface StepExecutionRow {\n id: string;\n job_execution_id: string;\n step_name: string;\n status: string;\n read_count: number;\n write_count: number;\n skip_count: number;\n rollback_count: number;\n commit_count: number;\n exit_code: string;\n exit_message: string;\n created_at: string | Date;\n}\n\ninterface ContextRow {\n data: string;\n version: number;\n}\n\nfunction mapJobInstance(r: JobInstanceRow): JobInstance {\n return {\n id: r.id,\n jobName: r.job_name,\n jobKey: r.job_key,\n createdAt: r.created_at instanceof Date ? r.created_at : new Date(r.created_at),\n };\n}\n\nfunction mapJobExecution(r: JobExecutionRow): JobExecution {\n let params: JobParameters = {};\n if (r.params && r.params.length > 0) {\n try {\n params = deserializeContext<JobParameters>(r.params);\n } catch {\n params = {};\n }\n }\n return {\n id: r.id,\n jobInstanceId: r.job_instance_id,\n status: r.status as JobStatus,\n startTime: r.start_time ? (r.start_time instanceof Date ? r.start_time : new Date(r.start_time)) : null,\n endTime: r.end_time ? (r.end_time instanceof Date ? r.end_time : new Date(r.end_time)) : null,\n exitCode: r.exit_code,\n exitMessage: r.exit_message,\n params,\n };\n}\n\nfunction mapStepExecution(r: StepExecutionRow): StepExecution {\n return {\n id: r.id,\n jobExecutionId: r.job_execution_id,\n stepName: r.step_name,\n status: r.status as StepStatus,\n readCount: r.read_count,\n writeCount: r.write_count,\n skipCount: r.skip_count,\n rollbackCount: r.rollback_count,\n commitCount: r.commit_count,\n startTime: null,\n endTime: null,\n exitCode: r.exit_code,\n exitMessage: r.exit_message,\n };\n}\n\n/**\n * TypeORM 1.0.0-backed `JobRepository`.\n *\n * The package is driver-agnostic: the actual `DataSource` is\n * provided by the `@nest-batch/postgresql` (or future\n * `@nest-batch/mysql`) driver sibling via the `TypeOrmDriverProvider`\n * token. The repository itself uses raw SQL via `EntityManager.query`\n * so the column-shape contract is owned by the driver sibling\n * (the bundled 6-table migration).\n *\n * The contract guarantees:\n * - `getOrCreateJobInstance` is race-safe via the (jobName, jobKey)\n * unique index.\n * - `createExecutionAtomic` runs inside a single transaction that\n * (a) idempotently upserts the instance row, (b) acquires a row\n * lock with `SELECT ... FOR UPDATE SKIP LOCKED` (PostgreSQL) or\n * a plain select (SQLite test driver), and (c) rejects with\n * `JobExecutionAlreadyRunningError` if a STARTING/STARTED\n * execution already exists.\n * - `saveExecutionContext` deep-clones the data and auto-increments\n * the version counter when `version` is omitted.\n * - `findLatestStepExecution` orders by `created_at` descending.\n */\n@Injectable()\nexport class TypeOrmJobRepository extends JobRepository {\n constructor(\n @Inject(TypeOrmDriverProvider) private readonly dataSource: DataSource,\n ) {\n super();\n }\n\n private em(): EntityManager {\n return this.dataSource.manager;\n }\n\n async getOrCreateJobInstance(name: string, jobKey: string): Promise<JobInstance> {\n const existing = await this.em().query(\n `SELECT \"id\", \"job_name\", \"job_key\", \"created_at\"\n FROM \"batch_job_instance\"\n WHERE \"job_name\" = $1 AND \"job_key\" = $2\n LIMIT 1`,\n [name, jobKey],\n ) as JobInstanceRow[];\n if (existing.length > 0) return mapJobInstance(existing[0]!);\n\n const id = randomUUID();\n try {\n const inserted = await this.em().query(\n `INSERT INTO \"batch_job_instance\" (\"id\", \"job_name\", \"job_key\", \"created_at\")\n VALUES ($1, $2, $3, NOW())\n ON CONFLICT (\"job_name\", \"job_key\") DO NOTHING\n RETURNING \"id\", \"job_name\", \"job_key\", \"created_at\"`,\n [id, name, jobKey],\n ) as JobInstanceRow[];\n if (inserted.length > 0) return mapJobInstance(inserted[0]!);\n } catch {\n // Fall through to read-back.\n }\n const winner = await this.em().query(\n `SELECT \"id\", \"job_name\", \"job_key\", \"created_at\"\n FROM \"batch_job_instance\"\n WHERE \"job_name\" = $1 AND \"job_key\" = $2\n LIMIT 1`,\n [name, jobKey],\n ) as JobInstanceRow[];\n if (winner.length === 0) {\n throw new Error(\n `Failed to upsert JobInstance (${name}, ${jobKey}) and could not read it back`,\n );\n }\n return mapJobInstance(winner[0]!);\n }\n\n async createJobExecution(\n jobInstanceId: string,\n params: JobParameters,\n ): Promise<JobExecution> {\n const exec = {\n id: randomUUID(),\n job_instance_id: jobInstanceId,\n status: JobStatus.STARTING,\n start_time: null as Date | null,\n end_time: null as Date | null,\n exit_code: '',\n exit_message: '',\n params: serializeContext(deepClone(params)),\n };\n const rows = await this.em().query(\n `INSERT INTO \"batch_job_execution\" (\"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\")\n VALUES ($1, $2, $3, NULL, NULL, $4, $5, $6)\n RETURNING \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"`,\n [exec.id, exec.job_instance_id, exec.status, exec.exit_code, exec.exit_message, exec.params],\n ) as JobExecutionRow[];\n return mapJobExecution(rows[0]!);\n }\n\n async createExecutionAtomic(\n name: string,\n jobKey: string,\n params: JobParameters,\n ): Promise<JobExecution> {\n return this.dataSource.transaction(async (em) => {\n // 1. Idempotent INSERT.\n const instId = randomUUID();\n await em.query(\n `INSERT INTO \"batch_job_instance\" (\"id\", \"job_name\", \"job_key\", \"created_at\")\n VALUES ($1, $2, $3, NOW())\n ON CONFLICT (\"job_name\", \"job_key\") DO NOTHING`,\n [instId, name, jobKey],\n );\n\n // 2. Lock the instance row.\n const isSqlite = this.dataSource.options.type === 'better-sqlite3';\n let instanceId: string;\n if (isSqlite) {\n const rows = await em.query(\n `SELECT \"id\" FROM \"batch_job_instance\"\n WHERE \"job_name\" = $1 AND \"job_key\" = $2\n LIMIT 1`,\n [name, jobKey],\n ) as Array<{ id: string }>;\n if (rows.length === 0) {\n throw new JobExecutionAlreadyRunningError(name);\n }\n instanceId = rows[0]!.id;\n } else {\n const rows = await em.query(\n `SELECT \"id\" FROM \"batch_job_instance\"\n WHERE \"job_name\" = $1 AND \"job_key\" = $2\n FOR UPDATE SKIP LOCKED`,\n [name, jobKey],\n ) as Array<{ id: string }>;\n if (rows.length === 0) {\n throw new JobExecutionAlreadyRunningError(name);\n }\n instanceId = rows[0]!.id;\n }\n\n // 3. Under the lock, verify no running execution.\n const running = await em.query(\n `SELECT \"id\" FROM \"batch_job_execution\"\n WHERE \"job_instance_id\" = $1 AND \"status\" IN ($2, $3)\n LIMIT 1`,\n [instanceId, JobStatus.STARTING, JobStatus.STARTED],\n ) as Array<{ id: string }>;\n if (running.length > 0) {\n throw new JobExecutionAlreadyRunningError(running[0]!.id);\n }\n\n // 4. Create the new execution row.\n const execId = randomUUID();\n const inserted = await em.query(\n `INSERT INTO \"batch_job_execution\" (\"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\")\n VALUES ($1, $2, $3, NULL, NULL, '', '', $4)\n RETURNING \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"`,\n [execId, instanceId, JobStatus.STARTING, serializeContext(deepClone(params))],\n ) as JobExecutionRow[];\n return mapJobExecution(inserted[0]!);\n });\n }\n\n async updateJobExecution(executionId: string, patch: JobExecutionPatch): Promise<void> {\n const sets: string[] = [];\n const values: unknown[] = [];\n let i = 1;\n if (patch.status !== undefined) { sets.push(`\"status\" = $${i++}`); values.push(patch.status); }\n if (patch.startTime !== undefined) { sets.push(`\"start_time\" = $${i++}`); values.push(patch.startTime); }\n if (patch.endTime !== undefined) { sets.push(`\"end_time\" = $${i++}`); values.push(patch.endTime); }\n if (patch.exitCode !== undefined) { sets.push(`\"exit_code\" = $${i++}`); values.push(patch.exitCode); }\n if (patch.exitMessage !== undefined) { sets.push(`\"exit_message\" = $${i++}`); values.push(patch.exitMessage); }\n if (sets.length === 0) return;\n values.push(executionId);\n await this.em().query(\n `UPDATE \"batch_job_execution\" SET ${sets.join(', ')} WHERE \"id\" = $${i}`,\n values,\n );\n }\n\n async getJobExecution(executionId: string): Promise<JobExecution | null> {\n const rows = await this.em().query(\n `SELECT \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"\n FROM \"batch_job_execution\" WHERE \"id\" = $1 LIMIT 1`,\n [executionId],\n ) as JobExecutionRow[];\n return rows.length > 0 ? mapJobExecution(rows[0]!) : null;\n }\n\n override async getJobInstance(jobInstanceId: string): Promise<JobInstance | null> {\n const rows = await this.em().query(\n `SELECT \"id\", \"job_name\", \"job_key\", \"created_at\"\n FROM \"batch_job_instance\"\n WHERE \"id\" = $1\n LIMIT 1`,\n [jobInstanceId],\n ) as JobInstanceRow[];\n return rows.length > 0 ? mapJobInstance(rows[0]!) : null;\n }\n\n override async findJobInstances(filter: JobInstanceFilter = {}): Promise<JobInstance[]> {\n const where: string[] = [];\n const values: unknown[] = [];\n let i = 1;\n if (filter.jobName !== undefined) {\n where.push(`\"job_name\" = $${i++}`);\n values.push(filter.jobName);\n }\n if (filter.jobKey !== undefined) {\n where.push(`\"job_key\" = $${i++}`);\n values.push(filter.jobKey);\n }\n const rows = await this.em().query(\n `SELECT \"id\", \"job_name\", \"job_key\", \"created_at\"\n FROM \"batch_job_instance\"\n ${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}\n ORDER BY \"created_at\" ASC, \"id\" ASC`,\n values,\n ) as JobInstanceRow[];\n return rows.map(mapJobInstance);\n }\n\n override async findJobExecutions(filter: JobExecutionFilter = {}): Promise<JobExecution[]> {\n const where: string[] = [];\n const values: unknown[] = [];\n let i = 1;\n if (filter.jobInstanceId !== undefined) {\n where.push(`\"job_instance_id\" = $${i++}`);\n values.push(filter.jobInstanceId);\n }\n if (filter.status !== undefined) {\n const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];\n const placeholders = statuses.map(() => `$${i++}`);\n where.push(`\"status\" IN (${placeholders.join(', ')})`);\n values.push(...statuses);\n }\n if (filter.startedAfter !== undefined) {\n where.push(`\"start_time\" >= $${i++}`);\n values.push(filter.startedAfter);\n }\n if (filter.startedBefore !== undefined) {\n where.push(`\"start_time\" <= $${i++}`);\n values.push(filter.startedBefore);\n }\n const rows = await this.em().query(\n `SELECT \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"\n FROM \"batch_job_execution\"\n ${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}\n ORDER BY \"start_time\" DESC NULLS LAST, \"id\" DESC`,\n values,\n ) as JobExecutionRow[];\n return rows.map(mapJobExecution);\n }\n\n async getRunningJobExecution(jobInstanceId: string): Promise<JobExecution | null> {\n if (!jobInstanceId) return null;\n const rows = await this.em().query(\n `SELECT \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"\n FROM \"batch_job_execution\"\n WHERE \"job_instance_id\" = $1 AND \"status\" IN ($2, $3)\n ORDER BY \"start_time\" DESC NULLS LAST LIMIT 1`,\n [jobInstanceId, JobStatus.STARTING, JobStatus.STARTED],\n ) as JobExecutionRow[];\n return rows.length > 0 ? mapJobExecution(rows[0]!) : null;\n }\n\n async createStepExecution(\n jobExecutionId: string,\n stepName: string,\n ): Promise<StepExecution> {\n const stepId = randomUUID();\n const rows = await this.em().query(\n `INSERT INTO \"batch_step_execution\" (\"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\")\n VALUES ($1, $2, $3, $4, 0, 0, 0, 0, 0, '', '', NOW())\n RETURNING \"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\"`,\n [stepId, jobExecutionId, stepName, StepStatus.STARTING],\n ) as StepExecutionRow[];\n return mapStepExecution(rows[0]!);\n }\n\n async updateStepExecution(\n stepExecutionId: string,\n patch: StepExecutionPatch,\n ): Promise<void> {\n const sets: string[] = [];\n const values: unknown[] = [];\n let i = 1;\n if (patch.status !== undefined) { sets.push(`\"status\" = $${i++}`); values.push(patch.status); }\n if (patch.readCount !== undefined) { sets.push(`\"read_count\" = $${i++}`); values.push(patch.readCount); }\n if (patch.writeCount !== undefined) { sets.push(`\"write_count\" = $${i++}`); values.push(patch.writeCount); }\n if (patch.skipCount !== undefined) { sets.push(`\"skip_count\" = $${i++}`); values.push(patch.skipCount); }\n if (patch.rollbackCount !== undefined) { sets.push(`\"rollback_count\" = $${i++}`); values.push(patch.rollbackCount); }\n if (patch.commitCount !== undefined) { sets.push(`\"commit_count\" = $${i++}`); values.push(patch.commitCount); }\n if (patch.exitCode !== undefined) { sets.push(`\"exit_code\" = $${i++}`); values.push(patch.exitCode); }\n if (patch.exitMessage !== undefined) { sets.push(`\"exit_message\" = $${i++}`); values.push(patch.exitMessage); }\n if (sets.length === 0) return;\n values.push(stepExecutionId);\n await this.em().query(\n `UPDATE \"batch_step_execution\" SET ${sets.join(', ')} WHERE \"id\" = $${i}`,\n values,\n );\n }\n\n async getStepExecution(stepExecutionId: string): Promise<StepExecution | null> {\n const rows = await this.em().query(\n `SELECT \"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\"\n FROM \"batch_step_execution\" WHERE \"id\" = $1 LIMIT 1`,\n [stepExecutionId],\n ) as StepExecutionRow[];\n return rows.length > 0 ? mapStepExecution(rows[0]!) : null;\n }\n\n override async findStepExecutions(jobExecutionId: string): Promise<StepExecution[]> {\n const rows = await this.em().query(\n `SELECT \"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\"\n FROM \"batch_step_execution\"\n WHERE \"job_execution_id\" = $1\n ORDER BY \"created_at\" ASC, \"id\" ASC`,\n [jobExecutionId],\n ) as StepExecutionRow[];\n return rows.map(mapStepExecution);\n }\n\n /**\n * Find the most recently created step execution for the given\n * `(jobExecutionId, stepName)` pair, or `null` when none exists.\n * Insertion order is determined by the `created_at` column; the\n * primary key is a v4 UUID which is random, so a `id DESC` order\n * would not correspond to insertion time. The `created_at DESC,\n * id DESC` secondary order keeps the result stable when two rows\n * share the same `CURRENT_TIMESTAMP` resolution.\n */\n async findLatestStepExecution(\n jobExecutionId: string,\n stepName: string,\n ): Promise<StepExecution | null> {\n const rows = await this.em().query(\n `SELECT \"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\"\n FROM \"batch_step_execution\"\n WHERE \"job_execution_id\" = $1 AND \"step_name\" = $2\n ORDER BY \"created_at\" DESC, \"id\" DESC\n LIMIT 1`,\n [jobExecutionId, stepName],\n ) as StepExecutionRow[];\n return rows.length > 0 ? mapStepExecution(rows[0]!) : null;\n }\n\n async getExecutionContext(scope: ExecutionScope): Promise<ExecutionContext> {\n const key = scopeKey(scope);\n if (key.startsWith('job::')) {\n const rows = await this.em().query(\n `SELECT \"data\", \"version\" FROM \"batch_job_execution_context\" WHERE \"job_execution_id\" = $1 LIMIT 1`,\n [key.slice(5)],\n ) as ContextRow[];\n if (rows.length > 0) {\n return {\n data: rows[0]!.data.length > 0 ? deserializeContext(rows[0]!.data) : null,\n version: rows[0]!.version,\n };\n }\n } else {\n const rows = await this.em().query(\n `SELECT \"data\", \"version\" FROM \"batch_step_execution_context\" WHERE \"step_execution_id\" = $1 LIMIT 1`,\n [key.slice(6)],\n ) as ContextRow[];\n if (rows.length > 0) {\n return {\n data: rows[0]!.data.length > 0 ? deserializeContext(rows[0]!.data) : null,\n version: rows[0]!.version,\n };\n }\n }\n return { data: null, version: 0 };\n }\n\n async saveExecutionContext(\n scope: ExecutionScope,\n ctx: ExecutionContext,\n version?: number,\n ): Promise<void> {\n assertJsonSerializable(ctx.data);\n const key = scopeKey(scope);\n const serialized = serializeContext(deepClone(ctx.data));\n if (key.startsWith('job::')) {\n const jobExecutionId = key.slice(5);\n const existing = await this.em().query(\n `SELECT \"version\" FROM \"batch_job_execution_context\" WHERE \"job_execution_id\" = $1 LIMIT 1`,\n [jobExecutionId],\n ) as ContextRow[];\n const nextVersion = version !== undefined ? version : (existing.length > 0 ? existing[0]!.version + 1 : 0);\n if (existing.length > 0) {\n await this.em().query(\n `UPDATE \"batch_job_execution_context\" SET \"data\" = $1, \"version\" = $2 WHERE \"job_execution_id\" = $3`,\n [serialized, nextVersion, jobExecutionId],\n );\n } else {\n await this.em().query(\n `INSERT INTO \"batch_job_execution_context\" (\"job_execution_id\", \"data\", \"version\") VALUES ($1, $2, $3)`,\n [jobExecutionId, serialized, nextVersion],\n );\n }\n } else {\n const stepExecutionId = key.slice(6);\n const existing = await this.em().query(\n `SELECT \"version\" FROM \"batch_step_execution_context\" WHERE \"step_execution_id\" = $1 LIMIT 1`,\n [stepExecutionId],\n ) as ContextRow[];\n const nextVersion = version !== undefined ? version : (existing.length > 0 ? existing[0]!.version + 1 : 0);\n if (existing.length > 0) {\n await this.em().query(\n `UPDATE \"batch_step_execution_context\" SET \"data\" = $1, \"version\" = $2 WHERE \"step_execution_id\" = $3`,\n [serialized, nextVersion, stepExecutionId],\n );\n } else {\n await this.em().query(\n `INSERT INTO \"batch_step_execution_context\" (\"step_execution_id\", \"data\", \"version\") VALUES ($1, $2, $3)`,\n [stepExecutionId, serialized, nextVersion],\n );\n }\n }\n }\n}\n"],"names":["TypeOrmJobRepository","scopeKey","scope","jobExecutionId","stepExecutionId","deepClone","value","Date","getTime","Array","isArray","map","v","out","k","Object","keys","mapJobInstance","r","id","jobName","job_name","jobKey","job_key","createdAt","created_at","mapJobExecution","params","length","deserializeContext","jobInstanceId","job_instance_id","status","startTime","start_time","endTime","end_time","exitCode","exit_code","exitMessage","exit_message","mapStepExecution","job_execution_id","stepName","step_name","readCount","read_count","writeCount","write_count","skipCount","skip_count","rollbackCount","rollback_count","commitCount","commit_count","JobRepository","dataSource","em","manager","getOrCreateJobInstance","name","existing","query","randomUUID","inserted","winner","Error","createJobExecution","exec","JobStatus","STARTING","serializeContext","rows","createExecutionAtomic","transaction","instId","isSqlite","options","type","instanceId","JobExecutionAlreadyRunningError","running","STARTED","execId","updateJobExecution","executionId","patch","sets","values","i","undefined","push","join","getJobExecution","getJobInstance","findJobInstances","filter","where","findJobExecutions","statuses","placeholders","startedAfter","startedBefore","getRunningJobExecution","createStepExecution","stepId","StepStatus","updateStepExecution","getStepExecution","findStepExecutions","findLatestStepExecution","getExecutionContext","key","startsWith","slice","data","version","saveExecutionContext","ctx","assertJsonSerializable","serialized","nextVersion"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/repository/typeorm-job-repository.ts"],"sourcesContent":["import { Inject, Injectable } from '@nestjs/common';\nimport { randomUUID } from 'node:crypto';\nimport { DataSource, EntityManager, In } from 'typeorm';\nimport {\n JobRepository,\n JobExecutionAlreadyRunningError,\n assertJsonSerializable,\n serializeContext,\n deserializeContext,\n JobStatus,\n StepStatus,\n} from '@nest-batch/core';\nimport type {\n JobInstance,\n JobExecution,\n JobExecutionPatch,\n JobParameters,\n StepExecution,\n StepExecutionPatch,\n ExecutionContext,\n ExecutionScope,\n JobInstanceFilter,\n JobExecutionFilter,\n} from '@nest-batch/core';\nimport { TypeOrmDriverProvider } from '../typeorm.driver-provider';\n\nfunction scopeKey(scope: ExecutionScope): string {\n if ('jobExecutionId' in scope) return `job::${scope.jobExecutionId}`;\n return `step::${scope.stepExecutionId}`;\n}\n\nfunction deepClone<T>(value: T): T {\n if (value === null || typeof value !== 'object') return value;\n if (value instanceof Date) return new Date(value.getTime()) as unknown as T;\n if (Array.isArray(value)) return value.map((v) => deepClone(v)) as unknown as T;\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as Record<string, unknown>)) {\n out[k] = deepClone((value as Record<string, unknown>)[k]);\n }\n return out as T;\n}\n\ninterface JobInstanceRow {\n id: string;\n job_name: string;\n job_key: string;\n created_at: string | Date;\n}\n\ninterface JobExecutionRow {\n id: string;\n job_instance_id: string;\n status: string;\n start_time: string | Date | null;\n end_time: string | Date | null;\n exit_code: string;\n exit_message: string;\n params: string;\n}\n\ninterface StepExecutionRow {\n id: string;\n job_execution_id: string;\n step_name: string;\n status: string;\n read_count: number;\n write_count: number;\n skip_count: number;\n rollback_count: number;\n commit_count: number;\n exit_code: string;\n exit_message: string;\n created_at: string | Date;\n}\n\ninterface ContextRow {\n data: string;\n version: number;\n}\n\nfunction mapJobInstance(r: JobInstanceRow): JobInstance {\n return {\n id: r.id,\n jobName: r.job_name,\n jobKey: r.job_key,\n createdAt: r.created_at instanceof Date ? r.created_at : new Date(r.created_at),\n };\n}\n\nfunction mapJobExecution(r: JobExecutionRow): JobExecution {\n let params: JobParameters = {};\n if (r.params && r.params.length > 0) {\n try {\n params = deserializeContext<JobParameters>(r.params);\n } catch {\n params = {};\n }\n }\n return {\n id: r.id,\n jobInstanceId: r.job_instance_id,\n status: r.status as JobStatus,\n startTime: r.start_time\n ? r.start_time instanceof Date\n ? r.start_time\n : new Date(r.start_time)\n : null,\n endTime: r.end_time ? (r.end_time instanceof Date ? r.end_time : new Date(r.end_time)) : null,\n exitCode: r.exit_code,\n exitMessage: r.exit_message,\n params,\n };\n}\n\nfunction mapStepExecution(r: StepExecutionRow): StepExecution {\n return {\n id: r.id,\n jobExecutionId: r.job_execution_id,\n stepName: r.step_name,\n status: r.status as StepStatus,\n readCount: r.read_count,\n writeCount: r.write_count,\n skipCount: r.skip_count,\n rollbackCount: r.rollback_count,\n commitCount: r.commit_count,\n startTime: null,\n endTime: null,\n exitCode: r.exit_code,\n exitMessage: r.exit_message,\n };\n}\n\n/**\n * TypeORM 1.0.0-backed `JobRepository`.\n *\n * The package is driver-agnostic: the actual `DataSource` is\n * provided by the `@nest-batch/postgresql` (or future\n * `@nest-batch/mysql`) driver sibling via the `TypeOrmDriverProvider`\n * token. The repository itself uses raw SQL via `EntityManager.query`\n * against the table contract represented by this package's exported\n * TypeORM entities. The consuming app owns the runnable migration.\n *\n * The contract guarantees:\n * - `getOrCreateJobInstance` is race-safe via the (jobName, jobKey)\n * unique index.\n * - `createExecutionAtomic` runs inside a single transaction that\n * (a) idempotently upserts the instance row, (b) acquires a row\n * lock with `SELECT ... FOR UPDATE SKIP LOCKED` (PostgreSQL) or\n * a plain select (SQLite test driver), and (c) rejects with\n * `JobExecutionAlreadyRunningError` if a STARTING/STARTED\n * execution already exists.\n * - `saveExecutionContext` deep-clones the data and auto-increments\n * the version counter when `version` is omitted.\n * - `findLatestStepExecution` orders by `created_at` descending.\n */\n@Injectable()\nexport class TypeOrmJobRepository extends JobRepository {\n constructor(@Inject(TypeOrmDriverProvider) private readonly dataSource: DataSource) {\n super();\n }\n\n private em(): EntityManager {\n return this.dataSource.manager;\n }\n\n async getOrCreateJobInstance(name: string, jobKey: string): Promise<JobInstance> {\n const existing = (await this.em().query(\n `SELECT \"id\", \"job_name\", \"job_key\", \"created_at\"\n FROM \"batch_job_instance\"\n WHERE \"job_name\" = $1 AND \"job_key\" = $2\n LIMIT 1`,\n [name, jobKey],\n )) as JobInstanceRow[];\n if (existing.length > 0) return mapJobInstance(existing[0]!);\n\n const id = randomUUID();\n try {\n const inserted = (await this.em().query(\n `INSERT INTO \"batch_job_instance\" (\"id\", \"job_name\", \"job_key\", \"created_at\")\n VALUES ($1, $2, $3, NOW())\n ON CONFLICT (\"job_name\", \"job_key\") DO NOTHING\n RETURNING \"id\", \"job_name\", \"job_key\", \"created_at\"`,\n [id, name, jobKey],\n )) as JobInstanceRow[];\n if (inserted.length > 0) return mapJobInstance(inserted[0]!);\n } catch {\n // Fall through to read-back.\n }\n const winner = (await this.em().query(\n `SELECT \"id\", \"job_name\", \"job_key\", \"created_at\"\n FROM \"batch_job_instance\"\n WHERE \"job_name\" = $1 AND \"job_key\" = $2\n LIMIT 1`,\n [name, jobKey],\n )) as JobInstanceRow[];\n if (winner.length === 0) {\n throw new Error(\n `Failed to upsert JobInstance (${name}, ${jobKey}) and could not read it back`,\n );\n }\n return mapJobInstance(winner[0]!);\n }\n\n async createJobExecution(jobInstanceId: string, params: JobParameters): Promise<JobExecution> {\n const exec = {\n id: randomUUID(),\n job_instance_id: jobInstanceId,\n status: JobStatus.STARTING,\n start_time: null as Date | null,\n end_time: null as Date | null,\n exit_code: '',\n exit_message: '',\n params: serializeContext(deepClone(params)),\n };\n const rows = (await this.em().query(\n `INSERT INTO \"batch_job_execution\" (\"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\")\n VALUES ($1, $2, $3, NULL, NULL, $4, $5, $6)\n RETURNING \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"`,\n [exec.id, exec.job_instance_id, exec.status, exec.exit_code, exec.exit_message, exec.params],\n )) as JobExecutionRow[];\n return mapJobExecution(rows[0]!);\n }\n\n async createExecutionAtomic(\n name: string,\n jobKey: string,\n params: JobParameters,\n ): Promise<JobExecution> {\n return this.dataSource.transaction(async (em) => {\n // 1. Idempotent INSERT.\n const instId = randomUUID();\n await em.query(\n `INSERT INTO \"batch_job_instance\" (\"id\", \"job_name\", \"job_key\", \"created_at\")\n VALUES ($1, $2, $3, NOW())\n ON CONFLICT (\"job_name\", \"job_key\") DO NOTHING`,\n [instId, name, jobKey],\n );\n\n // 2. Lock the instance row.\n const isSqlite = this.dataSource.options.type === 'better-sqlite3';\n let instanceId: string;\n if (isSqlite) {\n const rows = (await em.query(\n `SELECT \"id\" FROM \"batch_job_instance\"\n WHERE \"job_name\" = $1 AND \"job_key\" = $2\n LIMIT 1`,\n [name, jobKey],\n )) as Array<{ id: string }>;\n if (rows.length === 0) {\n throw new JobExecutionAlreadyRunningError(name);\n }\n instanceId = rows[0]!.id;\n } else {\n const rows = (await em.query(\n `SELECT \"id\" FROM \"batch_job_instance\"\n WHERE \"job_name\" = $1 AND \"job_key\" = $2\n FOR UPDATE SKIP LOCKED`,\n [name, jobKey],\n )) as Array<{ id: string }>;\n if (rows.length === 0) {\n throw new JobExecutionAlreadyRunningError(name);\n }\n instanceId = rows[0]!.id;\n }\n\n // 3. Under the lock, verify no running execution.\n const running = (await em.query(\n `SELECT \"id\" FROM \"batch_job_execution\"\n WHERE \"job_instance_id\" = $1 AND \"status\" IN ($2, $3)\n LIMIT 1`,\n [instanceId, JobStatus.STARTING, JobStatus.STARTED],\n )) as Array<{ id: string }>;\n if (running.length > 0) {\n throw new JobExecutionAlreadyRunningError(running[0]!.id);\n }\n\n // 4. Create the new execution row.\n const execId = randomUUID();\n const inserted = (await em.query(\n `INSERT INTO \"batch_job_execution\" (\"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\")\n VALUES ($1, $2, $3, NULL, NULL, '', '', $4)\n RETURNING \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"`,\n [execId, instanceId, JobStatus.STARTING, serializeContext(deepClone(params))],\n )) as JobExecutionRow[];\n return mapJobExecution(inserted[0]!);\n });\n }\n\n async updateJobExecution(executionId: string, patch: JobExecutionPatch): Promise<void> {\n const sets: string[] = [];\n const values: unknown[] = [];\n let i = 1;\n if (patch.status !== undefined) {\n sets.push(`\"status\" = $${i++}`);\n values.push(patch.status);\n }\n if (patch.startTime !== undefined) {\n sets.push(`\"start_time\" = $${i++}`);\n values.push(patch.startTime);\n }\n if (patch.endTime !== undefined) {\n sets.push(`\"end_time\" = $${i++}`);\n values.push(patch.endTime);\n }\n if (patch.exitCode !== undefined) {\n sets.push(`\"exit_code\" = $${i++}`);\n values.push(patch.exitCode);\n }\n if (patch.exitMessage !== undefined) {\n sets.push(`\"exit_message\" = $${i++}`);\n values.push(patch.exitMessage);\n }\n if (sets.length === 0) return;\n values.push(executionId);\n await this.em().query(\n `UPDATE \"batch_job_execution\" SET ${sets.join(', ')} WHERE \"id\" = $${i}`,\n values,\n );\n }\n\n async getJobExecution(executionId: string): Promise<JobExecution | null> {\n const rows = (await this.em().query(\n `SELECT \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"\n FROM \"batch_job_execution\" WHERE \"id\" = $1 LIMIT 1`,\n [executionId],\n )) as JobExecutionRow[];\n return rows.length > 0 ? mapJobExecution(rows[0]!) : null;\n }\n\n override async getJobInstance(jobInstanceId: string): Promise<JobInstance | null> {\n const rows = (await this.em().query(\n `SELECT \"id\", \"job_name\", \"job_key\", \"created_at\"\n FROM \"batch_job_instance\"\n WHERE \"id\" = $1\n LIMIT 1`,\n [jobInstanceId],\n )) as JobInstanceRow[];\n return rows.length > 0 ? mapJobInstance(rows[0]!) : null;\n }\n\n override async findJobInstances(filter: JobInstanceFilter = {}): Promise<JobInstance[]> {\n const where: string[] = [];\n const values: unknown[] = [];\n let i = 1;\n if (filter.jobName !== undefined) {\n where.push(`\"job_name\" = $${i++}`);\n values.push(filter.jobName);\n }\n if (filter.jobKey !== undefined) {\n where.push(`\"job_key\" = $${i++}`);\n values.push(filter.jobKey);\n }\n const rows = (await this.em().query(\n `SELECT \"id\", \"job_name\", \"job_key\", \"created_at\"\n FROM \"batch_job_instance\"\n ${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}\n ORDER BY \"created_at\" ASC, \"id\" ASC`,\n values,\n )) as JobInstanceRow[];\n return rows.map(mapJobInstance);\n }\n\n override async findJobExecutions(filter: JobExecutionFilter = {}): Promise<JobExecution[]> {\n const where: string[] = [];\n const values: unknown[] = [];\n let i = 1;\n if (filter.jobInstanceId !== undefined) {\n where.push(`\"job_instance_id\" = $${i++}`);\n values.push(filter.jobInstanceId);\n }\n if (filter.status !== undefined) {\n const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];\n const placeholders = statuses.map(() => `$${i++}`);\n where.push(`\"status\" IN (${placeholders.join(', ')})`);\n values.push(...statuses);\n }\n if (filter.startedAfter !== undefined) {\n where.push(`\"start_time\" >= $${i++}`);\n values.push(filter.startedAfter);\n }\n if (filter.startedBefore !== undefined) {\n where.push(`\"start_time\" <= $${i++}`);\n values.push(filter.startedBefore);\n }\n const rows = (await this.em().query(\n `SELECT \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"\n FROM \"batch_job_execution\"\n ${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}\n ORDER BY \"start_time\" DESC NULLS LAST, \"id\" DESC`,\n values,\n )) as JobExecutionRow[];\n return rows.map(mapJobExecution);\n }\n\n async getRunningJobExecution(jobInstanceId: string): Promise<JobExecution | null> {\n if (!jobInstanceId) return null;\n const rows = (await this.em().query(\n `SELECT \"id\", \"job_instance_id\", \"status\", \"start_time\", \"end_time\", \"exit_code\", \"exit_message\", \"params\"\n FROM \"batch_job_execution\"\n WHERE \"job_instance_id\" = $1 AND \"status\" IN ($2, $3)\n ORDER BY \"start_time\" DESC NULLS LAST LIMIT 1`,\n [jobInstanceId, JobStatus.STARTING, JobStatus.STARTED],\n )) as JobExecutionRow[];\n return rows.length > 0 ? mapJobExecution(rows[0]!) : null;\n }\n\n async createStepExecution(jobExecutionId: string, stepName: string): Promise<StepExecution> {\n const stepId = randomUUID();\n const rows = (await this.em().query(\n `INSERT INTO \"batch_step_execution\" (\"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\")\n VALUES ($1, $2, $3, $4, 0, 0, 0, 0, 0, '', '', NOW())\n RETURNING \"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\"`,\n [stepId, jobExecutionId, stepName, StepStatus.STARTING],\n )) as StepExecutionRow[];\n return mapStepExecution(rows[0]!);\n }\n\n async updateStepExecution(stepExecutionId: string, patch: StepExecutionPatch): Promise<void> {\n const sets: string[] = [];\n const values: unknown[] = [];\n let i = 1;\n if (patch.status !== undefined) {\n sets.push(`\"status\" = $${i++}`);\n values.push(patch.status);\n }\n if (patch.readCount !== undefined) {\n sets.push(`\"read_count\" = $${i++}`);\n values.push(patch.readCount);\n }\n if (patch.writeCount !== undefined) {\n sets.push(`\"write_count\" = $${i++}`);\n values.push(patch.writeCount);\n }\n if (patch.skipCount !== undefined) {\n sets.push(`\"skip_count\" = $${i++}`);\n values.push(patch.skipCount);\n }\n if (patch.rollbackCount !== undefined) {\n sets.push(`\"rollback_count\" = $${i++}`);\n values.push(patch.rollbackCount);\n }\n if (patch.commitCount !== undefined) {\n sets.push(`\"commit_count\" = $${i++}`);\n values.push(patch.commitCount);\n }\n if (patch.exitCode !== undefined) {\n sets.push(`\"exit_code\" = $${i++}`);\n values.push(patch.exitCode);\n }\n if (patch.exitMessage !== undefined) {\n sets.push(`\"exit_message\" = $${i++}`);\n values.push(patch.exitMessage);\n }\n if (sets.length === 0) return;\n values.push(stepExecutionId);\n await this.em().query(\n `UPDATE \"batch_step_execution\" SET ${sets.join(', ')} WHERE \"id\" = $${i}`,\n values,\n );\n }\n\n async getStepExecution(stepExecutionId: string): Promise<StepExecution | null> {\n const rows = (await this.em().query(\n `SELECT \"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\"\n FROM \"batch_step_execution\" WHERE \"id\" = $1 LIMIT 1`,\n [stepExecutionId],\n )) as StepExecutionRow[];\n return rows.length > 0 ? mapStepExecution(rows[0]!) : null;\n }\n\n override async findStepExecutions(jobExecutionId: string): Promise<StepExecution[]> {\n const rows = (await this.em().query(\n `SELECT \"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\"\n FROM \"batch_step_execution\"\n WHERE \"job_execution_id\" = $1\n ORDER BY \"created_at\" ASC, \"id\" ASC`,\n [jobExecutionId],\n )) as StepExecutionRow[];\n return rows.map(mapStepExecution);\n }\n\n /**\n * Find the most recently created step execution for the given\n * `(jobExecutionId, stepName)` pair, or `null` when none exists.\n * Insertion order is determined by the `created_at` column; the\n * primary key is a v4 UUID which is random, so a `id DESC` order\n * would not correspond to insertion time. The `created_at DESC,\n * id DESC` secondary order keeps the result stable when two rows\n * share the same `CURRENT_TIMESTAMP` resolution.\n */\n async findLatestStepExecution(\n jobExecutionId: string,\n stepName: string,\n ): Promise<StepExecution | null> {\n const rows = (await this.em().query(\n `SELECT \"id\", \"job_execution_id\", \"step_name\", \"status\", \"read_count\", \"write_count\", \"skip_count\", \"rollback_count\", \"commit_count\", \"exit_code\", \"exit_message\", \"created_at\"\n FROM \"batch_step_execution\"\n WHERE \"job_execution_id\" = $1 AND \"step_name\" = $2\n ORDER BY \"created_at\" DESC, \"id\" DESC\n LIMIT 1`,\n [jobExecutionId, stepName],\n )) as StepExecutionRow[];\n return rows.length > 0 ? mapStepExecution(rows[0]!) : null;\n }\n\n async getExecutionContext(scope: ExecutionScope): Promise<ExecutionContext> {\n const key = scopeKey(scope);\n if (key.startsWith('job::')) {\n const rows = (await this.em().query(\n `SELECT \"data\", \"version\" FROM \"batch_job_execution_context\" WHERE \"job_execution_id\" = $1 LIMIT 1`,\n [key.slice(5)],\n )) as ContextRow[];\n if (rows.length > 0) {\n return {\n data: rows[0]!.data.length > 0 ? deserializeContext(rows[0]!.data) : null,\n version: rows[0]!.version,\n };\n }\n } else {\n const rows = (await this.em().query(\n `SELECT \"data\", \"version\" FROM \"batch_step_execution_context\" WHERE \"step_execution_id\" = $1 LIMIT 1`,\n [key.slice(6)],\n )) as ContextRow[];\n if (rows.length > 0) {\n return {\n data: rows[0]!.data.length > 0 ? deserializeContext(rows[0]!.data) : null,\n version: rows[0]!.version,\n };\n }\n }\n return { data: null, version: 0 };\n }\n\n async saveExecutionContext(\n scope: ExecutionScope,\n ctx: ExecutionContext,\n version?: number,\n ): Promise<void> {\n assertJsonSerializable(ctx.data);\n const key = scopeKey(scope);\n const serialized = serializeContext(deepClone(ctx.data));\n if (key.startsWith('job::')) {\n const jobExecutionId = key.slice(5);\n const existing = (await this.em().query(\n `SELECT \"version\" FROM \"batch_job_execution_context\" WHERE \"job_execution_id\" = $1 LIMIT 1`,\n [jobExecutionId],\n )) as ContextRow[];\n const nextVersion =\n version !== undefined ? version : existing.length > 0 ? existing[0]!.version + 1 : 0;\n if (existing.length > 0) {\n await this.em().query(\n `UPDATE \"batch_job_execution_context\" SET \"data\" = $1, \"version\" = $2 WHERE \"job_execution_id\" = $3`,\n [serialized, nextVersion, jobExecutionId],\n );\n } else {\n await this.em().query(\n `INSERT INTO \"batch_job_execution_context\" (\"job_execution_id\", \"data\", \"version\") VALUES ($1, $2, $3)`,\n [jobExecutionId, serialized, nextVersion],\n );\n }\n } else {\n const stepExecutionId = key.slice(6);\n const existing = (await this.em().query(\n `SELECT \"version\" FROM \"batch_step_execution_context\" WHERE \"step_execution_id\" = $1 LIMIT 1`,\n [stepExecutionId],\n )) as ContextRow[];\n const nextVersion =\n version !== undefined ? version : existing.length > 0 ? existing[0]!.version + 1 : 0;\n if (existing.length > 0) {\n await this.em().query(\n `UPDATE \"batch_step_execution_context\" SET \"data\" = $1, \"version\" = $2 WHERE \"step_execution_id\" = $3`,\n [serialized, nextVersion, stepExecutionId],\n );\n } else {\n await this.em().query(\n `INSERT INTO \"batch_step_execution_context\" (\"step_execution_id\", \"data\", \"version\") VALUES ($1, $2, $3)`,\n [stepExecutionId, serialized, nextVersion],\n );\n }\n }\n }\n}\n"],"names":["TypeOrmJobRepository","scopeKey","scope","jobExecutionId","stepExecutionId","deepClone","value","Date","getTime","Array","isArray","map","v","out","k","Object","keys","mapJobInstance","r","id","jobName","job_name","jobKey","job_key","createdAt","created_at","mapJobExecution","params","length","deserializeContext","jobInstanceId","job_instance_id","status","startTime","start_time","endTime","end_time","exitCode","exit_code","exitMessage","exit_message","mapStepExecution","job_execution_id","stepName","step_name","readCount","read_count","writeCount","write_count","skipCount","skip_count","rollbackCount","rollback_count","commitCount","commit_count","JobRepository","dataSource","em","manager","getOrCreateJobInstance","name","existing","query","randomUUID","inserted","winner","Error","createJobExecution","exec","JobStatus","STARTING","serializeContext","rows","createExecutionAtomic","transaction","instId","isSqlite","options","type","instanceId","JobExecutionAlreadyRunningError","running","STARTED","execId","updateJobExecution","executionId","patch","sets","values","i","undefined","push","join","getJobExecution","getJobInstance","findJobInstances","filter","where","findJobExecutions","statuses","placeholders","startedAfter","startedBefore","getRunningJobExecution","createStepExecution","stepId","StepStatus","updateStepExecution","getStepExecution","findStepExecutions","findLatestStepExecution","getExecutionContext","key","startsWith","slice","data","version","saveExecutionContext","ctx","assertJsonSerializable","serialized","nextVersion"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-batch/typeorm",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
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
5
|
"license": "MIT",
|
|
6
6
|
"author": "easdkr",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@nestjs/common": "^10 || ^11",
|
|
35
35
|
"typeorm": "^1.0.0",
|
|
36
|
-
"@nest-batch/core": "^0.2.
|
|
36
|
+
"@nest-batch/core": "^0.2.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"typeorm": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"typescript": "^5.5.0",
|
|
59
59
|
"unplugin-swc": "^1.5.0",
|
|
60
60
|
"vitest": "^2.0.0",
|
|
61
|
-
"@nest-batch/core": "0.2.
|
|
61
|
+
"@nest-batch/core": "0.2.2"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "swc src -d dist --config-file ../../.swcrc && tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
@@ -6,8 +6,8 @@ import { Entity, PrimaryColumn, Column, Index } from 'typeorm';
|
|
|
6
6
|
* One row per logical job instance. Uniqueness is enforced on
|
|
7
7
|
* (jobName, jobKey) so that the same canonical key resolves to the
|
|
8
8
|
* same instance across restarts. The composite unique index is
|
|
9
|
-
* declared on the entity
|
|
10
|
-
*
|
|
9
|
+
* declared on the entity so host-owned TypeORM migrations can
|
|
10
|
+
* generate the matching database constraint.
|
|
11
11
|
*/
|
|
12
12
|
@Entity('batch_job_instance')
|
|
13
13
|
@Index('batch_job_instance_job_name_job_key_unique', ['jobName', 'jobKey'], { unique: true })
|
|
@@ -24,10 +24,9 @@ export class JobInstanceEntity {
|
|
|
24
24
|
@Column({
|
|
25
25
|
name: 'created_at',
|
|
26
26
|
// `datetime` is portable across PostgreSQL and SQLite (the test
|
|
27
|
-
// driver).
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// compared with sub-second precision in queries.
|
|
27
|
+
// driver). Hosts that generate PostgreSQL migrations can map the
|
|
28
|
+
// same logical column to timestamptz in their own migration
|
|
29
|
+
// pipeline.
|
|
31
30
|
type: 'datetime',
|
|
32
31
|
default: () => 'CURRENT_TIMESTAMP',
|
|
33
32
|
})
|
package/src/index.ts
CHANGED
|
@@ -29,14 +29,18 @@
|
|
|
29
29
|
// },
|
|
30
30
|
// });
|
|
31
31
|
//
|
|
32
|
-
// The
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
32
|
+
// The TypeORM entity tuple stays in this package because it is the
|
|
33
|
+
// schema contract consumed by a host-owned TypeORM DataSource. Apps
|
|
34
|
+
// generate and own their runnable migration files in their own
|
|
35
|
+
// migration workflow. Driver siblings bind the
|
|
36
|
+
// `TypeOrmDriverProvider` token to a concrete database connection.
|
|
37
|
+
import { BATCH_META_ENTITIES } from './entities';
|
|
38
|
+
|
|
38
39
|
export { TypeOrmJobRepository } from './repository/typeorm-job-repository';
|
|
39
40
|
export type { TypeOrmTransactionContext } from './transaction/typeorm-transaction-manager';
|
|
40
41
|
export { TypeOrmTransactionManager } from './transaction/typeorm-transaction-manager';
|
|
41
42
|
export * from './adapters';
|
|
43
|
+
export { BATCH_META_ENTITIES } from './entities';
|
|
42
44
|
export * from './typeorm.driver-provider';
|
|
45
|
+
|
|
46
|
+
export const batchMetaEntities = (): typeof BATCH_META_ENTITIES => BATCH_META_ENTITIES;
|
|
@@ -100,7 +100,11 @@ function mapJobExecution(r: JobExecutionRow): JobExecution {
|
|
|
100
100
|
id: r.id,
|
|
101
101
|
jobInstanceId: r.job_instance_id,
|
|
102
102
|
status: r.status as JobStatus,
|
|
103
|
-
startTime: r.start_time
|
|
103
|
+
startTime: r.start_time
|
|
104
|
+
? r.start_time instanceof Date
|
|
105
|
+
? r.start_time
|
|
106
|
+
: new Date(r.start_time)
|
|
107
|
+
: null,
|
|
104
108
|
endTime: r.end_time ? (r.end_time instanceof Date ? r.end_time : new Date(r.end_time)) : null,
|
|
105
109
|
exitCode: r.exit_code,
|
|
106
110
|
exitMessage: r.exit_message,
|
|
@@ -133,8 +137,8 @@ function mapStepExecution(r: StepExecutionRow): StepExecution {
|
|
|
133
137
|
* provided by the `@nest-batch/postgresql` (or future
|
|
134
138
|
* `@nest-batch/mysql`) driver sibling via the `TypeOrmDriverProvider`
|
|
135
139
|
* token. The repository itself uses raw SQL via `EntityManager.query`
|
|
136
|
-
*
|
|
137
|
-
*
|
|
140
|
+
* against the table contract represented by this package's exported
|
|
141
|
+
* TypeORM entities. The consuming app owns the runnable migration.
|
|
138
142
|
*
|
|
139
143
|
* The contract guarantees:
|
|
140
144
|
* - `getOrCreateJobInstance` is race-safe via the (jobName, jobKey)
|
|
@@ -151,9 +155,7 @@ function mapStepExecution(r: StepExecutionRow): StepExecution {
|
|
|
151
155
|
*/
|
|
152
156
|
@Injectable()
|
|
153
157
|
export class TypeOrmJobRepository extends JobRepository {
|
|
154
|
-
constructor(
|
|
155
|
-
@Inject(TypeOrmDriverProvider) private readonly dataSource: DataSource,
|
|
156
|
-
) {
|
|
158
|
+
constructor(@Inject(TypeOrmDriverProvider) private readonly dataSource: DataSource) {
|
|
157
159
|
super();
|
|
158
160
|
}
|
|
159
161
|
|
|
@@ -162,35 +164,35 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
async getOrCreateJobInstance(name: string, jobKey: string): Promise<JobInstance> {
|
|
165
|
-
const existing = await this.em().query(
|
|
167
|
+
const existing = (await this.em().query(
|
|
166
168
|
`SELECT "id", "job_name", "job_key", "created_at"
|
|
167
169
|
FROM "batch_job_instance"
|
|
168
170
|
WHERE "job_name" = $1 AND "job_key" = $2
|
|
169
171
|
LIMIT 1`,
|
|
170
172
|
[name, jobKey],
|
|
171
|
-
) as JobInstanceRow[];
|
|
173
|
+
)) as JobInstanceRow[];
|
|
172
174
|
if (existing.length > 0) return mapJobInstance(existing[0]!);
|
|
173
175
|
|
|
174
176
|
const id = randomUUID();
|
|
175
177
|
try {
|
|
176
|
-
const inserted = await this.em().query(
|
|
178
|
+
const inserted = (await this.em().query(
|
|
177
179
|
`INSERT INTO "batch_job_instance" ("id", "job_name", "job_key", "created_at")
|
|
178
180
|
VALUES ($1, $2, $3, NOW())
|
|
179
181
|
ON CONFLICT ("job_name", "job_key") DO NOTHING
|
|
180
182
|
RETURNING "id", "job_name", "job_key", "created_at"`,
|
|
181
183
|
[id, name, jobKey],
|
|
182
|
-
) as JobInstanceRow[];
|
|
184
|
+
)) as JobInstanceRow[];
|
|
183
185
|
if (inserted.length > 0) return mapJobInstance(inserted[0]!);
|
|
184
186
|
} catch {
|
|
185
187
|
// Fall through to read-back.
|
|
186
188
|
}
|
|
187
|
-
const winner = await this.em().query(
|
|
189
|
+
const winner = (await this.em().query(
|
|
188
190
|
`SELECT "id", "job_name", "job_key", "created_at"
|
|
189
191
|
FROM "batch_job_instance"
|
|
190
192
|
WHERE "job_name" = $1 AND "job_key" = $2
|
|
191
193
|
LIMIT 1`,
|
|
192
194
|
[name, jobKey],
|
|
193
|
-
) as JobInstanceRow[];
|
|
195
|
+
)) as JobInstanceRow[];
|
|
194
196
|
if (winner.length === 0) {
|
|
195
197
|
throw new Error(
|
|
196
198
|
`Failed to upsert JobInstance (${name}, ${jobKey}) and could not read it back`,
|
|
@@ -199,10 +201,7 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
199
201
|
return mapJobInstance(winner[0]!);
|
|
200
202
|
}
|
|
201
203
|
|
|
202
|
-
async createJobExecution(
|
|
203
|
-
jobInstanceId: string,
|
|
204
|
-
params: JobParameters,
|
|
205
|
-
): Promise<JobExecution> {
|
|
204
|
+
async createJobExecution(jobInstanceId: string, params: JobParameters): Promise<JobExecution> {
|
|
206
205
|
const exec = {
|
|
207
206
|
id: randomUUID(),
|
|
208
207
|
job_instance_id: jobInstanceId,
|
|
@@ -213,12 +212,12 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
213
212
|
exit_message: '',
|
|
214
213
|
params: serializeContext(deepClone(params)),
|
|
215
214
|
};
|
|
216
|
-
const rows = await this.em().query(
|
|
215
|
+
const rows = (await this.em().query(
|
|
217
216
|
`INSERT INTO "batch_job_execution" ("id", "job_instance_id", "status", "start_time", "end_time", "exit_code", "exit_message", "params")
|
|
218
217
|
VALUES ($1, $2, $3, NULL, NULL, $4, $5, $6)
|
|
219
218
|
RETURNING "id", "job_instance_id", "status", "start_time", "end_time", "exit_code", "exit_message", "params"`,
|
|
220
219
|
[exec.id, exec.job_instance_id, exec.status, exec.exit_code, exec.exit_message, exec.params],
|
|
221
|
-
) as JobExecutionRow[];
|
|
220
|
+
)) as JobExecutionRow[];
|
|
222
221
|
return mapJobExecution(rows[0]!);
|
|
223
222
|
}
|
|
224
223
|
|
|
@@ -241,23 +240,23 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
241
240
|
const isSqlite = this.dataSource.options.type === 'better-sqlite3';
|
|
242
241
|
let instanceId: string;
|
|
243
242
|
if (isSqlite) {
|
|
244
|
-
const rows = await em.query(
|
|
243
|
+
const rows = (await em.query(
|
|
245
244
|
`SELECT "id" FROM "batch_job_instance"
|
|
246
245
|
WHERE "job_name" = $1 AND "job_key" = $2
|
|
247
246
|
LIMIT 1`,
|
|
248
247
|
[name, jobKey],
|
|
249
|
-
) as Array<{ id: string }>;
|
|
248
|
+
)) as Array<{ id: string }>;
|
|
250
249
|
if (rows.length === 0) {
|
|
251
250
|
throw new JobExecutionAlreadyRunningError(name);
|
|
252
251
|
}
|
|
253
252
|
instanceId = rows[0]!.id;
|
|
254
253
|
} else {
|
|
255
|
-
const rows = await em.query(
|
|
254
|
+
const rows = (await em.query(
|
|
256
255
|
`SELECT "id" FROM "batch_job_instance"
|
|
257
256
|
WHERE "job_name" = $1 AND "job_key" = $2
|
|
258
257
|
FOR UPDATE SKIP LOCKED`,
|
|
259
258
|
[name, jobKey],
|
|
260
|
-
) as Array<{ id: string }>;
|
|
259
|
+
)) as Array<{ id: string }>;
|
|
261
260
|
if (rows.length === 0) {
|
|
262
261
|
throw new JobExecutionAlreadyRunningError(name);
|
|
263
262
|
}
|
|
@@ -265,24 +264,24 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
265
264
|
}
|
|
266
265
|
|
|
267
266
|
// 3. Under the lock, verify no running execution.
|
|
268
|
-
const running = await em.query(
|
|
267
|
+
const running = (await em.query(
|
|
269
268
|
`SELECT "id" FROM "batch_job_execution"
|
|
270
269
|
WHERE "job_instance_id" = $1 AND "status" IN ($2, $3)
|
|
271
270
|
LIMIT 1`,
|
|
272
271
|
[instanceId, JobStatus.STARTING, JobStatus.STARTED],
|
|
273
|
-
) as Array<{ id: string }>;
|
|
272
|
+
)) as Array<{ id: string }>;
|
|
274
273
|
if (running.length > 0) {
|
|
275
274
|
throw new JobExecutionAlreadyRunningError(running[0]!.id);
|
|
276
275
|
}
|
|
277
276
|
|
|
278
277
|
// 4. Create the new execution row.
|
|
279
278
|
const execId = randomUUID();
|
|
280
|
-
const inserted = await em.query(
|
|
279
|
+
const inserted = (await em.query(
|
|
281
280
|
`INSERT INTO "batch_job_execution" ("id", "job_instance_id", "status", "start_time", "end_time", "exit_code", "exit_message", "params")
|
|
282
281
|
VALUES ($1, $2, $3, NULL, NULL, '', '', $4)
|
|
283
282
|
RETURNING "id", "job_instance_id", "status", "start_time", "end_time", "exit_code", "exit_message", "params"`,
|
|
284
283
|
[execId, instanceId, JobStatus.STARTING, serializeContext(deepClone(params))],
|
|
285
|
-
) as JobExecutionRow[];
|
|
284
|
+
)) as JobExecutionRow[];
|
|
286
285
|
return mapJobExecution(inserted[0]!);
|
|
287
286
|
});
|
|
288
287
|
}
|
|
@@ -291,11 +290,26 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
291
290
|
const sets: string[] = [];
|
|
292
291
|
const values: unknown[] = [];
|
|
293
292
|
let i = 1;
|
|
294
|
-
if (patch.status !== undefined) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (patch.
|
|
293
|
+
if (patch.status !== undefined) {
|
|
294
|
+
sets.push(`"status" = $${i++}`);
|
|
295
|
+
values.push(patch.status);
|
|
296
|
+
}
|
|
297
|
+
if (patch.startTime !== undefined) {
|
|
298
|
+
sets.push(`"start_time" = $${i++}`);
|
|
299
|
+
values.push(patch.startTime);
|
|
300
|
+
}
|
|
301
|
+
if (patch.endTime !== undefined) {
|
|
302
|
+
sets.push(`"end_time" = $${i++}`);
|
|
303
|
+
values.push(patch.endTime);
|
|
304
|
+
}
|
|
305
|
+
if (patch.exitCode !== undefined) {
|
|
306
|
+
sets.push(`"exit_code" = $${i++}`);
|
|
307
|
+
values.push(patch.exitCode);
|
|
308
|
+
}
|
|
309
|
+
if (patch.exitMessage !== undefined) {
|
|
310
|
+
sets.push(`"exit_message" = $${i++}`);
|
|
311
|
+
values.push(patch.exitMessage);
|
|
312
|
+
}
|
|
299
313
|
if (sets.length === 0) return;
|
|
300
314
|
values.push(executionId);
|
|
301
315
|
await this.em().query(
|
|
@@ -305,22 +319,22 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
305
319
|
}
|
|
306
320
|
|
|
307
321
|
async getJobExecution(executionId: string): Promise<JobExecution | null> {
|
|
308
|
-
const rows = await this.em().query(
|
|
322
|
+
const rows = (await this.em().query(
|
|
309
323
|
`SELECT "id", "job_instance_id", "status", "start_time", "end_time", "exit_code", "exit_message", "params"
|
|
310
324
|
FROM "batch_job_execution" WHERE "id" = $1 LIMIT 1`,
|
|
311
325
|
[executionId],
|
|
312
|
-
) as JobExecutionRow[];
|
|
326
|
+
)) as JobExecutionRow[];
|
|
313
327
|
return rows.length > 0 ? mapJobExecution(rows[0]!) : null;
|
|
314
328
|
}
|
|
315
329
|
|
|
316
330
|
override async getJobInstance(jobInstanceId: string): Promise<JobInstance | null> {
|
|
317
|
-
const rows = await this.em().query(
|
|
331
|
+
const rows = (await this.em().query(
|
|
318
332
|
`SELECT "id", "job_name", "job_key", "created_at"
|
|
319
333
|
FROM "batch_job_instance"
|
|
320
334
|
WHERE "id" = $1
|
|
321
335
|
LIMIT 1`,
|
|
322
336
|
[jobInstanceId],
|
|
323
|
-
) as JobInstanceRow[];
|
|
337
|
+
)) as JobInstanceRow[];
|
|
324
338
|
return rows.length > 0 ? mapJobInstance(rows[0]!) : null;
|
|
325
339
|
}
|
|
326
340
|
|
|
@@ -336,13 +350,13 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
336
350
|
where.push(`"job_key" = $${i++}`);
|
|
337
351
|
values.push(filter.jobKey);
|
|
338
352
|
}
|
|
339
|
-
const rows = await this.em().query(
|
|
353
|
+
const rows = (await this.em().query(
|
|
340
354
|
`SELECT "id", "job_name", "job_key", "created_at"
|
|
341
355
|
FROM "batch_job_instance"
|
|
342
356
|
${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}
|
|
343
357
|
ORDER BY "created_at" ASC, "id" ASC`,
|
|
344
358
|
values,
|
|
345
|
-
) as JobInstanceRow[];
|
|
359
|
+
)) as JobInstanceRow[];
|
|
346
360
|
return rows.map(mapJobInstance);
|
|
347
361
|
}
|
|
348
362
|
|
|
@@ -368,57 +382,75 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
368
382
|
where.push(`"start_time" <= $${i++}`);
|
|
369
383
|
values.push(filter.startedBefore);
|
|
370
384
|
}
|
|
371
|
-
const rows = await this.em().query(
|
|
385
|
+
const rows = (await this.em().query(
|
|
372
386
|
`SELECT "id", "job_instance_id", "status", "start_time", "end_time", "exit_code", "exit_message", "params"
|
|
373
387
|
FROM "batch_job_execution"
|
|
374
388
|
${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}
|
|
375
389
|
ORDER BY "start_time" DESC NULLS LAST, "id" DESC`,
|
|
376
390
|
values,
|
|
377
|
-
) as JobExecutionRow[];
|
|
391
|
+
)) as JobExecutionRow[];
|
|
378
392
|
return rows.map(mapJobExecution);
|
|
379
393
|
}
|
|
380
394
|
|
|
381
395
|
async getRunningJobExecution(jobInstanceId: string): Promise<JobExecution | null> {
|
|
382
396
|
if (!jobInstanceId) return null;
|
|
383
|
-
const rows = await this.em().query(
|
|
397
|
+
const rows = (await this.em().query(
|
|
384
398
|
`SELECT "id", "job_instance_id", "status", "start_time", "end_time", "exit_code", "exit_message", "params"
|
|
385
399
|
FROM "batch_job_execution"
|
|
386
400
|
WHERE "job_instance_id" = $1 AND "status" IN ($2, $3)
|
|
387
401
|
ORDER BY "start_time" DESC NULLS LAST LIMIT 1`,
|
|
388
402
|
[jobInstanceId, JobStatus.STARTING, JobStatus.STARTED],
|
|
389
|
-
) as JobExecutionRow[];
|
|
403
|
+
)) as JobExecutionRow[];
|
|
390
404
|
return rows.length > 0 ? mapJobExecution(rows[0]!) : null;
|
|
391
405
|
}
|
|
392
406
|
|
|
393
|
-
async createStepExecution(
|
|
394
|
-
jobExecutionId: string,
|
|
395
|
-
stepName: string,
|
|
396
|
-
): Promise<StepExecution> {
|
|
407
|
+
async createStepExecution(jobExecutionId: string, stepName: string): Promise<StepExecution> {
|
|
397
408
|
const stepId = randomUUID();
|
|
398
|
-
const rows = await this.em().query(
|
|
409
|
+
const rows = (await this.em().query(
|
|
399
410
|
`INSERT INTO "batch_step_execution" ("id", "job_execution_id", "step_name", "status", "read_count", "write_count", "skip_count", "rollback_count", "commit_count", "exit_code", "exit_message", "created_at")
|
|
400
411
|
VALUES ($1, $2, $3, $4, 0, 0, 0, 0, 0, '', '', NOW())
|
|
401
412
|
RETURNING "id", "job_execution_id", "step_name", "status", "read_count", "write_count", "skip_count", "rollback_count", "commit_count", "exit_code", "exit_message", "created_at"`,
|
|
402
413
|
[stepId, jobExecutionId, stepName, StepStatus.STARTING],
|
|
403
|
-
) as StepExecutionRow[];
|
|
414
|
+
)) as StepExecutionRow[];
|
|
404
415
|
return mapStepExecution(rows[0]!);
|
|
405
416
|
}
|
|
406
417
|
|
|
407
|
-
async updateStepExecution(
|
|
408
|
-
stepExecutionId: string,
|
|
409
|
-
patch: StepExecutionPatch,
|
|
410
|
-
): Promise<void> {
|
|
418
|
+
async updateStepExecution(stepExecutionId: string, patch: StepExecutionPatch): Promise<void> {
|
|
411
419
|
const sets: string[] = [];
|
|
412
420
|
const values: unknown[] = [];
|
|
413
421
|
let i = 1;
|
|
414
|
-
if (patch.status !== undefined) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (patch.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
+
if (patch.status !== undefined) {
|
|
423
|
+
sets.push(`"status" = $${i++}`);
|
|
424
|
+
values.push(patch.status);
|
|
425
|
+
}
|
|
426
|
+
if (patch.readCount !== undefined) {
|
|
427
|
+
sets.push(`"read_count" = $${i++}`);
|
|
428
|
+
values.push(patch.readCount);
|
|
429
|
+
}
|
|
430
|
+
if (patch.writeCount !== undefined) {
|
|
431
|
+
sets.push(`"write_count" = $${i++}`);
|
|
432
|
+
values.push(patch.writeCount);
|
|
433
|
+
}
|
|
434
|
+
if (patch.skipCount !== undefined) {
|
|
435
|
+
sets.push(`"skip_count" = $${i++}`);
|
|
436
|
+
values.push(patch.skipCount);
|
|
437
|
+
}
|
|
438
|
+
if (patch.rollbackCount !== undefined) {
|
|
439
|
+
sets.push(`"rollback_count" = $${i++}`);
|
|
440
|
+
values.push(patch.rollbackCount);
|
|
441
|
+
}
|
|
442
|
+
if (patch.commitCount !== undefined) {
|
|
443
|
+
sets.push(`"commit_count" = $${i++}`);
|
|
444
|
+
values.push(patch.commitCount);
|
|
445
|
+
}
|
|
446
|
+
if (patch.exitCode !== undefined) {
|
|
447
|
+
sets.push(`"exit_code" = $${i++}`);
|
|
448
|
+
values.push(patch.exitCode);
|
|
449
|
+
}
|
|
450
|
+
if (patch.exitMessage !== undefined) {
|
|
451
|
+
sets.push(`"exit_message" = $${i++}`);
|
|
452
|
+
values.push(patch.exitMessage);
|
|
453
|
+
}
|
|
422
454
|
if (sets.length === 0) return;
|
|
423
455
|
values.push(stepExecutionId);
|
|
424
456
|
await this.em().query(
|
|
@@ -428,22 +460,22 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
428
460
|
}
|
|
429
461
|
|
|
430
462
|
async getStepExecution(stepExecutionId: string): Promise<StepExecution | null> {
|
|
431
|
-
const rows = await this.em().query(
|
|
463
|
+
const rows = (await this.em().query(
|
|
432
464
|
`SELECT "id", "job_execution_id", "step_name", "status", "read_count", "write_count", "skip_count", "rollback_count", "commit_count", "exit_code", "exit_message", "created_at"
|
|
433
465
|
FROM "batch_step_execution" WHERE "id" = $1 LIMIT 1`,
|
|
434
466
|
[stepExecutionId],
|
|
435
|
-
) as StepExecutionRow[];
|
|
467
|
+
)) as StepExecutionRow[];
|
|
436
468
|
return rows.length > 0 ? mapStepExecution(rows[0]!) : null;
|
|
437
469
|
}
|
|
438
470
|
|
|
439
471
|
override async findStepExecutions(jobExecutionId: string): Promise<StepExecution[]> {
|
|
440
|
-
const rows = await this.em().query(
|
|
472
|
+
const rows = (await this.em().query(
|
|
441
473
|
`SELECT "id", "job_execution_id", "step_name", "status", "read_count", "write_count", "skip_count", "rollback_count", "commit_count", "exit_code", "exit_message", "created_at"
|
|
442
474
|
FROM "batch_step_execution"
|
|
443
475
|
WHERE "job_execution_id" = $1
|
|
444
476
|
ORDER BY "created_at" ASC, "id" ASC`,
|
|
445
477
|
[jobExecutionId],
|
|
446
|
-
) as StepExecutionRow[];
|
|
478
|
+
)) as StepExecutionRow[];
|
|
447
479
|
return rows.map(mapStepExecution);
|
|
448
480
|
}
|
|
449
481
|
|
|
@@ -460,24 +492,24 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
460
492
|
jobExecutionId: string,
|
|
461
493
|
stepName: string,
|
|
462
494
|
): Promise<StepExecution | null> {
|
|
463
|
-
const rows = await this.em().query(
|
|
495
|
+
const rows = (await this.em().query(
|
|
464
496
|
`SELECT "id", "job_execution_id", "step_name", "status", "read_count", "write_count", "skip_count", "rollback_count", "commit_count", "exit_code", "exit_message", "created_at"
|
|
465
497
|
FROM "batch_step_execution"
|
|
466
498
|
WHERE "job_execution_id" = $1 AND "step_name" = $2
|
|
467
499
|
ORDER BY "created_at" DESC, "id" DESC
|
|
468
500
|
LIMIT 1`,
|
|
469
501
|
[jobExecutionId, stepName],
|
|
470
|
-
) as StepExecutionRow[];
|
|
502
|
+
)) as StepExecutionRow[];
|
|
471
503
|
return rows.length > 0 ? mapStepExecution(rows[0]!) : null;
|
|
472
504
|
}
|
|
473
505
|
|
|
474
506
|
async getExecutionContext(scope: ExecutionScope): Promise<ExecutionContext> {
|
|
475
507
|
const key = scopeKey(scope);
|
|
476
508
|
if (key.startsWith('job::')) {
|
|
477
|
-
const rows = await this.em().query(
|
|
509
|
+
const rows = (await this.em().query(
|
|
478
510
|
`SELECT "data", "version" FROM "batch_job_execution_context" WHERE "job_execution_id" = $1 LIMIT 1`,
|
|
479
511
|
[key.slice(5)],
|
|
480
|
-
) as ContextRow[];
|
|
512
|
+
)) as ContextRow[];
|
|
481
513
|
if (rows.length > 0) {
|
|
482
514
|
return {
|
|
483
515
|
data: rows[0]!.data.length > 0 ? deserializeContext(rows[0]!.data) : null,
|
|
@@ -485,10 +517,10 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
485
517
|
};
|
|
486
518
|
}
|
|
487
519
|
} else {
|
|
488
|
-
const rows = await this.em().query(
|
|
520
|
+
const rows = (await this.em().query(
|
|
489
521
|
`SELECT "data", "version" FROM "batch_step_execution_context" WHERE "step_execution_id" = $1 LIMIT 1`,
|
|
490
522
|
[key.slice(6)],
|
|
491
|
-
) as ContextRow[];
|
|
523
|
+
)) as ContextRow[];
|
|
492
524
|
if (rows.length > 0) {
|
|
493
525
|
return {
|
|
494
526
|
data: rows[0]!.data.length > 0 ? deserializeContext(rows[0]!.data) : null,
|
|
@@ -509,11 +541,12 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
509
541
|
const serialized = serializeContext(deepClone(ctx.data));
|
|
510
542
|
if (key.startsWith('job::')) {
|
|
511
543
|
const jobExecutionId = key.slice(5);
|
|
512
|
-
const existing = await this.em().query(
|
|
544
|
+
const existing = (await this.em().query(
|
|
513
545
|
`SELECT "version" FROM "batch_job_execution_context" WHERE "job_execution_id" = $1 LIMIT 1`,
|
|
514
546
|
[jobExecutionId],
|
|
515
|
-
) as ContextRow[];
|
|
516
|
-
const nextVersion =
|
|
547
|
+
)) as ContextRow[];
|
|
548
|
+
const nextVersion =
|
|
549
|
+
version !== undefined ? version : existing.length > 0 ? existing[0]!.version + 1 : 0;
|
|
517
550
|
if (existing.length > 0) {
|
|
518
551
|
await this.em().query(
|
|
519
552
|
`UPDATE "batch_job_execution_context" SET "data" = $1, "version" = $2 WHERE "job_execution_id" = $3`,
|
|
@@ -527,11 +560,12 @@ export class TypeOrmJobRepository extends JobRepository {
|
|
|
527
560
|
}
|
|
528
561
|
} else {
|
|
529
562
|
const stepExecutionId = key.slice(6);
|
|
530
|
-
const existing = await this.em().query(
|
|
563
|
+
const existing = (await this.em().query(
|
|
531
564
|
`SELECT "version" FROM "batch_step_execution_context" WHERE "step_execution_id" = $1 LIMIT 1`,
|
|
532
565
|
[stepExecutionId],
|
|
533
|
-
) as ContextRow[];
|
|
534
|
-
const nextVersion =
|
|
566
|
+
)) as ContextRow[];
|
|
567
|
+
const nextVersion =
|
|
568
|
+
version !== undefined ? version : existing.length > 0 ? existing[0]!.version + 1 : 0;
|
|
535
569
|
if (existing.length > 0) {
|
|
536
570
|
await this.em().query(
|
|
537
571
|
`UPDATE "batch_step_execution_context" SET "data" = $1, "version" = $2 WHERE "step_execution_id" = $3`,
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
-
/**
|
|
3
|
-
* Creates the six batch meta-tables owned by
|
|
4
|
-
* this package:
|
|
5
|
-
*
|
|
6
|
-
* - batch_job_instance (root, unique on (job_name, job_key))
|
|
7
|
-
* - batch_job_execution (one per job run, indexed by instance)
|
|
8
|
-
* - batch_step_execution (one per step run, indexed by exec)
|
|
9
|
-
* - batch_job_execution_context (JSON payload + version, keyed by exec)
|
|
10
|
-
* - batch_step_execution_context (JSON payload + version, keyed by step)
|
|
11
|
-
*
|
|
12
|
-
* This migration intentionally uses generic ANSI/PostgreSQL
|
|
13
|
-
* syntax (varchar + text + timestamptz + int). The adapter
|
|
14
|
-
* package's `typeorm-job-repository` is portable across SQLite
|
|
15
|
-
* (test database) and PostgreSQL (production). Users targeting
|
|
16
|
-
* MySQL or another driver should adjust the `down` (and
|
|
17
|
-
* optionally `up`) to match their column types.
|
|
18
|
-
*
|
|
19
|
-
* The `params` column on `batch_job_execution` is a JSON
|
|
20
|
-
* snapshot — stored as `text` to keep the schema portable and
|
|
21
|
-
* always serialized, never queried structurally.
|
|
22
|
-
*/
|
|
23
|
-
export declare class CreateBatchMeta1700000000000 implements MigrationInterface {
|
|
24
|
-
name: string;
|
|
25
|
-
up(queryRunner: QueryRunner): Promise<void>;
|
|
26
|
-
down(queryRunner: QueryRunner): Promise<void>;
|
|
27
|
-
}
|
|
28
|
-
//# sourceMappingURL=1700000000000-CreateBatchMeta.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"1700000000000-CreateBatchMeta.d.ts","sourceRoot":"","sources":["../../../src/migrations/1700000000000-CreateBatchMeta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,4BAA6B,YAAW,kBAAkB;IACrE,IAAI,SAAkC;IAEzB,EAAE,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAkE3C,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;CAO3D"}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "CreateBatchMeta1700000000000", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return CreateBatchMeta1700000000000;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
let CreateBatchMeta1700000000000 = class CreateBatchMeta1700000000000 {
|
|
12
|
-
name = 'CreateBatchMeta1700000000000';
|
|
13
|
-
async up(queryRunner) {
|
|
14
|
-
await queryRunner.query(`
|
|
15
|
-
CREATE TABLE IF NOT EXISTS "batch_job_instance" (
|
|
16
|
-
"id" varchar(255) PRIMARY KEY,
|
|
17
|
-
"job_name" varchar(255) NOT NULL,
|
|
18
|
-
"job_key" varchar(255) NOT NULL,
|
|
19
|
-
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
20
|
-
CONSTRAINT "batch_job_instance_job_name_job_key_unique" UNIQUE ("job_name", "job_key")
|
|
21
|
-
)
|
|
22
|
-
`);
|
|
23
|
-
await queryRunner.query(`
|
|
24
|
-
CREATE TABLE IF NOT EXISTS "batch_job_execution" (
|
|
25
|
-
"id" varchar(255) PRIMARY KEY,
|
|
26
|
-
"job_instance_id" varchar(255) NOT NULL,
|
|
27
|
-
"status" varchar(20) NOT NULL,
|
|
28
|
-
"start_time" timestamptz NULL,
|
|
29
|
-
"end_time" timestamptz NULL,
|
|
30
|
-
"exit_code" varchar(255) NOT NULL DEFAULT '',
|
|
31
|
-
"exit_message" text NOT NULL DEFAULT '',
|
|
32
|
-
"params" text NOT NULL DEFAULT '{}'
|
|
33
|
-
)
|
|
34
|
-
`);
|
|
35
|
-
await queryRunner.query(`
|
|
36
|
-
CREATE INDEX IF NOT EXISTS "batch_job_execution_job_instance_id_index"
|
|
37
|
-
ON "batch_job_execution" ("job_instance_id")
|
|
38
|
-
`);
|
|
39
|
-
await queryRunner.query(`
|
|
40
|
-
CREATE TABLE IF NOT EXISTS "batch_step_execution" (
|
|
41
|
-
"id" varchar(255) PRIMARY KEY,
|
|
42
|
-
"job_execution_id" varchar(255) NOT NULL,
|
|
43
|
-
"step_name" varchar(255) NOT NULL,
|
|
44
|
-
"status" varchar(20) NOT NULL,
|
|
45
|
-
"read_count" int NOT NULL DEFAULT 0,
|
|
46
|
-
"write_count" int NOT NULL DEFAULT 0,
|
|
47
|
-
"skip_count" int NOT NULL DEFAULT 0,
|
|
48
|
-
"rollback_count" int NOT NULL DEFAULT 0,
|
|
49
|
-
"commit_count" int NOT NULL DEFAULT 0,
|
|
50
|
-
"exit_code" varchar(255) NOT NULL DEFAULT '',
|
|
51
|
-
"exit_message" text NOT NULL DEFAULT '',
|
|
52
|
-
"created_at" timestamptz NOT NULL DEFAULT now()
|
|
53
|
-
)
|
|
54
|
-
`);
|
|
55
|
-
await queryRunner.query(`
|
|
56
|
-
CREATE INDEX IF NOT EXISTS "batch_step_execution_job_execution_id_index"
|
|
57
|
-
ON "batch_step_execution" ("job_execution_id")
|
|
58
|
-
`);
|
|
59
|
-
await queryRunner.query(`
|
|
60
|
-
CREATE TABLE IF NOT EXISTS "batch_job_execution_context" (
|
|
61
|
-
"job_execution_id" varchar(255) PRIMARY KEY,
|
|
62
|
-
"data" text NOT NULL,
|
|
63
|
-
"version" int NOT NULL DEFAULT 0
|
|
64
|
-
)
|
|
65
|
-
`);
|
|
66
|
-
await queryRunner.query(`
|
|
67
|
-
CREATE TABLE IF NOT EXISTS "batch_step_execution_context" (
|
|
68
|
-
"step_execution_id" varchar(255) PRIMARY KEY,
|
|
69
|
-
"data" text NOT NULL,
|
|
70
|
-
"version" int NOT NULL DEFAULT 0
|
|
71
|
-
)
|
|
72
|
-
`);
|
|
73
|
-
}
|
|
74
|
-
async down(queryRunner) {
|
|
75
|
-
await queryRunner.query(`DROP TABLE IF EXISTS "batch_step_execution_context"`);
|
|
76
|
-
await queryRunner.query(`DROP TABLE IF EXISTS "batch_job_execution_context"`);
|
|
77
|
-
await queryRunner.query(`DROP TABLE IF EXISTS "batch_step_execution"`);
|
|
78
|
-
await queryRunner.query(`DROP TABLE IF EXISTS "batch_job_execution"`);
|
|
79
|
-
await queryRunner.query(`DROP TABLE IF EXISTS "batch_job_instance"`);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
//# sourceMappingURL=1700000000000-CreateBatchMeta.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/migrations/1700000000000-CreateBatchMeta.ts"],"sourcesContent":["import { MigrationInterface, QueryRunner } from 'typeorm';\n\n/**\n * Creates the six batch meta-tables owned by\n * this package:\n *\n * - batch_job_instance (root, unique on (job_name, job_key))\n * - batch_job_execution (one per job run, indexed by instance)\n * - batch_step_execution (one per step run, indexed by exec)\n * - batch_job_execution_context (JSON payload + version, keyed by exec)\n * - batch_step_execution_context (JSON payload + version, keyed by step)\n *\n * This migration intentionally uses generic ANSI/PostgreSQL\n * syntax (varchar + text + timestamptz + int). The adapter\n * package's `typeorm-job-repository` is portable across SQLite\n * (test database) and PostgreSQL (production). Users targeting\n * MySQL or another driver should adjust the `down` (and\n * optionally `up`) to match their column types.\n *\n * The `params` column on `batch_job_execution` is a JSON\n * snapshot — stored as `text` to keep the schema portable and\n * always serialized, never queried structurally.\n */\nexport class CreateBatchMeta1700000000000 implements MigrationInterface {\n name = 'CreateBatchMeta1700000000000';\n\n public async up(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`\n CREATE TABLE IF NOT EXISTS \"batch_job_instance\" (\n \"id\" varchar(255) PRIMARY KEY,\n \"job_name\" varchar(255) NOT NULL,\n \"job_key\" varchar(255) NOT NULL,\n \"created_at\" timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT \"batch_job_instance_job_name_job_key_unique\" UNIQUE (\"job_name\", \"job_key\")\n )\n `);\n\n await queryRunner.query(`\n CREATE TABLE IF NOT EXISTS \"batch_job_execution\" (\n \"id\" varchar(255) PRIMARY KEY,\n \"job_instance_id\" varchar(255) NOT NULL,\n \"status\" varchar(20) NOT NULL,\n \"start_time\" timestamptz NULL,\n \"end_time\" timestamptz NULL,\n \"exit_code\" varchar(255) NOT NULL DEFAULT '',\n \"exit_message\" text NOT NULL DEFAULT '',\n \"params\" text NOT NULL DEFAULT '{}'\n )\n `);\n await queryRunner.query(`\n CREATE INDEX IF NOT EXISTS \"batch_job_execution_job_instance_id_index\"\n ON \"batch_job_execution\" (\"job_instance_id\")\n `);\n\n await queryRunner.query(`\n CREATE TABLE IF NOT EXISTS \"batch_step_execution\" (\n \"id\" varchar(255) PRIMARY KEY,\n \"job_execution_id\" varchar(255) NOT NULL,\n \"step_name\" varchar(255) NOT NULL,\n \"status\" varchar(20) NOT NULL,\n \"read_count\" int NOT NULL DEFAULT 0,\n \"write_count\" int NOT NULL DEFAULT 0,\n \"skip_count\" int NOT NULL DEFAULT 0,\n \"rollback_count\" int NOT NULL DEFAULT 0,\n \"commit_count\" int NOT NULL DEFAULT 0,\n \"exit_code\" varchar(255) NOT NULL DEFAULT '',\n \"exit_message\" text NOT NULL DEFAULT '',\n \"created_at\" timestamptz NOT NULL DEFAULT now()\n )\n `);\n await queryRunner.query(`\n CREATE INDEX IF NOT EXISTS \"batch_step_execution_job_execution_id_index\"\n ON \"batch_step_execution\" (\"job_execution_id\")\n `);\n\n await queryRunner.query(`\n CREATE TABLE IF NOT EXISTS \"batch_job_execution_context\" (\n \"job_execution_id\" varchar(255) PRIMARY KEY,\n \"data\" text NOT NULL,\n \"version\" int NOT NULL DEFAULT 0\n )\n `);\n\n await queryRunner.query(`\n CREATE TABLE IF NOT EXISTS \"batch_step_execution_context\" (\n \"step_execution_id\" varchar(255) PRIMARY KEY,\n \"data\" text NOT NULL,\n \"version\" int NOT NULL DEFAULT 0\n )\n `);\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`DROP TABLE IF EXISTS \"batch_step_execution_context\"`);\n await queryRunner.query(`DROP TABLE IF EXISTS \"batch_job_execution_context\"`);\n await queryRunner.query(`DROP TABLE IF EXISTS \"batch_step_execution\"`);\n await queryRunner.query(`DROP TABLE IF EXISTS \"batch_job_execution\"`);\n await queryRunner.query(`DROP TABLE IF EXISTS \"batch_job_instance\"`);\n }\n}\n"],"names":["CreateBatchMeta1700000000000","name","up","queryRunner","query","down"],"mappings":";;;;+BAuBaA;;;eAAAA;;;AAAN,IAAA,AAAMA,+BAAN,MAAMA;IACXC,OAAO,+BAA+B;IAEtC,MAAaC,GAAGC,WAAwB,EAAiB;QACvD,MAAMA,YAAYC,KAAK,CAAC,CAAC;;;;;;;;IAQzB,CAAC;QAED,MAAMD,YAAYC,KAAK,CAAC,CAAC;;;;;;;;;;;IAWzB,CAAC;QACD,MAAMD,YAAYC,KAAK,CAAC,CAAC;;;IAGzB,CAAC;QAED,MAAMD,YAAYC,KAAK,CAAC,CAAC;;;;;;;;;;;;;;;IAezB,CAAC;QACD,MAAMD,YAAYC,KAAK,CAAC,CAAC;;;IAGzB,CAAC;QAED,MAAMD,YAAYC,KAAK,CAAC,CAAC;;;;;;IAMzB,CAAC;QAED,MAAMD,YAAYC,KAAK,CAAC,CAAC;;;;;;IAMzB,CAAC;IACH;IAEA,MAAaC,KAAKF,WAAwB,EAAiB;QACzD,MAAMA,YAAYC,KAAK,CAAC,CAAC,mDAAmD,CAAC;QAC7E,MAAMD,YAAYC,KAAK,CAAC,CAAC,kDAAkD,CAAC;QAC5E,MAAMD,YAAYC,KAAK,CAAC,CAAC,2CAA2C,CAAC;QACrE,MAAMD,YAAYC,KAAK,CAAC,CAAC,0CAA0C,CAAC;QACpE,MAAMD,YAAYC,KAAK,CAAC,CAAC,yCAAyC,CAAC;IACrE;AACF"}
|
|
@@ -1,100 +0,0 @@
|
|
|
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
|
-
}
|