@c9up/atlas 0.2.0 → 0.2.2
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 +8 -0
- package/db.darwin-arm64.node +0 -0
- package/db.darwin-x64.node +0 -0
- package/db.linux-arm64-gnu.node +0 -0
- package/db.linux-x64-gnu.node +0 -0
- package/db.win32-x64-msvc.node +0 -0
- package/dist/AtlasProvider.d.ts +60 -7
- package/dist/AtlasProvider.d.ts.map +1 -1
- package/dist/AtlasProvider.js +204 -38
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/BaseEntity.d.ts +17 -0
- package/dist/BaseEntity.d.ts.map +1 -1
- package/dist/BaseEntity.js.map +1 -1
- package/dist/BaseModel.d.ts +63 -27
- package/dist/BaseModel.d.ts.map +1 -1
- package/dist/BaseModel.js +108 -51
- package/dist/BaseModel.js.map +1 -1
- package/dist/BaseRepository.d.ts +11 -1
- package/dist/BaseRepository.d.ts.map +1 -1
- package/dist/BaseRepository.js +95 -20
- package/dist/BaseRepository.js.map +1 -1
- package/dist/ConnectionManager.d.ts +106 -0
- package/dist/ConnectionManager.d.ts.map +1 -0
- package/dist/ConnectionManager.js +228 -0
- package/dist/ConnectionManager.js.map +1 -0
- package/dist/ModelQuery.d.ts +256 -43
- package/dist/ModelQuery.d.ts.map +1 -1
- package/dist/ModelQuery.js +899 -231
- package/dist/ModelQuery.js.map +1 -1
- package/dist/Transaction.d.ts +54 -1
- package/dist/Transaction.d.ts.map +1 -1
- package/dist/Transaction.js +121 -59
- package/dist/Transaction.js.map +1 -1
- package/dist/adapters/NapiDbAdapter.d.ts +11 -0
- package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
- package/dist/adapters/NapiDbAdapter.js +35 -7
- package/dist/adapters/NapiDbAdapter.js.map +1 -1
- package/dist/console/contract.d.ts +62 -0
- package/dist/console/contract.d.ts.map +1 -0
- package/dist/console/contract.js +38 -0
- package/dist/console/contract.js.map +1 -0
- package/dist/console/factoryCommands.d.ts +23 -0
- package/dist/console/factoryCommands.d.ts.map +1 -0
- package/dist/console/factoryCommands.js +62 -0
- package/dist/console/factoryCommands.js.map +1 -0
- package/dist/console/migrationCommands.d.ts +38 -8
- package/dist/console/migrationCommands.d.ts.map +1 -1
- package/dist/console/migrationCommands.js +157 -50
- package/dist/console/migrationCommands.js.map +1 -1
- package/dist/console/schemaCheckCommand.d.ts +11 -17
- package/dist/console/schemaCheckCommand.d.ts.map +1 -1
- package/dist/console/schemaCheckCommand.js +21 -15
- package/dist/console/schemaCheckCommand.js.map +1 -1
- package/dist/console/schemaDumpCommand.d.ts +30 -0
- package/dist/console/schemaDumpCommand.d.ts.map +1 -0
- package/dist/console/schemaDumpCommand.js +69 -0
- package/dist/console/schemaDumpCommand.js.map +1 -0
- package/dist/console/schemaGenerateCommand.d.ts +100 -0
- package/dist/console/schemaGenerateCommand.d.ts.map +1 -0
- package/dist/console/schemaGenerateCommand.js +246 -0
- package/dist/console/schemaGenerateCommand.js.map +1 -0
- package/dist/console/seederCommands.d.ts +46 -0
- package/dist/console/seederCommands.d.ts.map +1 -0
- package/dist/console/seederCommands.js +136 -0
- package/dist/console/seederCommands.js.map +1 -0
- package/dist/decorators/entity.d.ts +13 -2
- package/dist/decorators/entity.d.ts.map +1 -1
- package/dist/decorators/entity.js +10 -0
- package/dist/decorators/entity.js.map +1 -1
- package/dist/decorators/hooks.d.ts +3 -3
- package/dist/decorators/hooks.d.ts.map +1 -1
- package/dist/decorators/hooks.js.map +1 -1
- package/dist/events.d.ts +5 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/factories.d.ts +8 -0
- package/dist/factories.d.ts.map +1 -0
- package/dist/factories.js +8 -0
- package/dist/factories.js.map +1 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/lucid-schema.d.ts +15 -0
- package/dist/lucid-schema.d.ts.map +1 -0
- package/dist/lucid-schema.js +16 -0
- package/dist/lucid-schema.js.map +1 -0
- package/dist/orm.d.ts +14 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/orm.js +14 -0
- package/dist/orm.js.map +1 -0
- package/dist/query/DatabaseQueryBuilder.d.ts +557 -0
- package/dist/query/DatabaseQueryBuilder.d.ts.map +1 -0
- package/dist/query/DatabaseQueryBuilder.js +1798 -0
- package/dist/query/DatabaseQueryBuilder.js.map +1 -0
- package/dist/query/DmlBuilder.d.ts +62 -0
- package/dist/query/DmlBuilder.d.ts.map +1 -0
- package/dist/query/DmlBuilder.js +84 -0
- package/dist/query/DmlBuilder.js.map +1 -0
- package/dist/query/QueryBuilder.d.ts +5 -0
- package/dist/query/QueryBuilder.d.ts.map +1 -1
- package/dist/query/QueryBuilder.js +7 -0
- package/dist/query/QueryBuilder.js.map +1 -1
- package/dist/query/RawQueryBuilder.d.ts +47 -0
- package/dist/query/RawQueryBuilder.d.ts.map +1 -0
- package/dist/query/RawQueryBuilder.js +138 -0
- package/dist/query/RawQueryBuilder.js.map +1 -0
- package/dist/query/interpolate.d.ts +45 -0
- package/dist/query/interpolate.d.ts.map +1 -0
- package/dist/query/interpolate.js +51 -0
- package/dist/query/interpolate.js.map +1 -0
- package/dist/query/native.d.ts +8 -0
- package/dist/query/native.d.ts.map +1 -1
- package/dist/query/native.js +14 -0
- package/dist/query/native.js.map +1 -1
- package/dist/query/operators.d.ts +4 -0
- package/dist/query/operators.d.ts.map +1 -0
- package/dist/query/operators.js +27 -0
- package/dist/query/operators.js.map +1 -0
- package/dist/schema/Migration.d.ts +31 -7
- package/dist/schema/Migration.d.ts.map +1 -1
- package/dist/schema/Migration.js +34 -7
- package/dist/schema/Migration.js.map +1 -1
- package/dist/schema/MigrationRunner.d.ts +56 -6
- package/dist/schema/MigrationRunner.d.ts.map +1 -1
- package/dist/schema/MigrationRunner.js +382 -112
- package/dist/schema/MigrationRunner.js.map +1 -1
- package/dist/schema/Schema.d.ts +59 -2
- package/dist/schema/Schema.d.ts.map +1 -1
- package/dist/schema/Schema.js +143 -9
- package/dist/schema/Schema.js.map +1 -1
- package/dist/schema/SchemaDumper.d.ts +121 -0
- package/dist/schema/SchemaDumper.d.ts.map +1 -0
- package/dist/schema/SchemaDumper.js +365 -0
- package/dist/schema/SchemaDumper.js.map +1 -0
- package/dist/schema/Seeder.d.ts +13 -0
- package/dist/schema/Seeder.d.ts.map +1 -1
- package/dist/schema/Seeder.js +28 -5
- package/dist/schema/Seeder.js.map +1 -1
- package/dist/schema/TableBuilder.d.ts +27 -8
- package/dist/schema/TableBuilder.d.ts.map +1 -1
- package/dist/schema/TableBuilder.js +50 -12
- package/dist/schema/TableBuilder.js.map +1 -1
- package/dist/schema/catalog.d.ts +25 -1
- package/dist/schema/catalog.d.ts.map +1 -1
- package/dist/schema/catalog.js +69 -22
- package/dist/schema/catalog.js.map +1 -1
- package/dist/schema/introspect.d.ts +1 -1
- package/dist/schema/introspect.d.ts.map +1 -1
- package/dist/schema/introspect.js +26 -11
- package/dist/schema/introspect.js.map +1 -1
- package/dist/seeders.d.ts +7 -0
- package/dist/seeders.d.ts.map +1 -0
- package/dist/seeders.js +7 -0
- package/dist/seeders.js.map +1 -0
- package/dist/services/db.d.ts +120 -9
- package/dist/services/db.d.ts.map +1 -1
- package/dist/services/db.js +182 -26
- package/dist/services/db.js.map +1 -1
- package/dist/testing/DatabaseCleanup.d.ts +29 -6
- package/dist/testing/DatabaseCleanup.d.ts.map +1 -1
- package/dist/testing/DatabaseCleanup.js +44 -17
- package/dist/testing/DatabaseCleanup.js.map +1 -1
- package/dist/testing/DbAssertions.d.ts +54 -0
- package/dist/testing/DbAssertions.d.ts.map +1 -0
- package/dist/testing/DbAssertions.js +156 -0
- package/dist/testing/DbAssertions.js.map +1 -0
- package/dist/testing/Factory.d.ts +111 -25
- package/dist/testing/Factory.d.ts.map +1 -1
- package/dist/testing/Factory.js +331 -56
- package/dist/testing/Factory.js.map +1 -1
- package/dist/testing/TestUtils.d.ts +49 -0
- package/dist/testing/TestUtils.d.ts.map +1 -0
- package/dist/testing/TestUtils.js +79 -0
- package/dist/testing/TestUtils.js.map +1 -0
- package/dist/testing/index.d.ts +3 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types/relations.d.ts +31 -0
- package/dist/types/relations.d.ts.map +1 -0
- package/dist/types/relations.js +20 -0
- package/dist/types/relations.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +30 -2
- package/src/AtlasProvider.ts +297 -51
- package/src/BaseEntity.ts +29 -0
- package/src/BaseModel.ts +174 -37
- package/src/BaseRepository.ts +136 -17
- package/src/ConnectionManager.ts +298 -0
- package/src/ModelQuery.ts +1309 -260
- package/src/Transaction.ts +194 -60
- package/src/adapters/NapiDbAdapter.ts +72 -14
- package/src/console/contract.ts +96 -0
- package/src/console/factoryCommands.ts +73 -0
- package/src/console/migrationCommands.ts +253 -63
- package/src/console/schemaCheckCommand.ts +26 -22
- package/src/console/schemaDumpCommand.ts +96 -0
- package/src/console/schemaGenerateCommand.ts +380 -0
- package/src/console/seederCommands.ts +174 -0
- package/src/decorators/entity.ts +18 -2
- package/src/decorators/hooks.ts +5 -3
- package/src/events.ts +5 -0
- package/src/factories.ts +10 -0
- package/src/index.ts +50 -3
- package/src/lucid-schema.ts +22 -0
- package/src/orm.ts +13 -0
- package/src/query/DatabaseQueryBuilder.ts +2724 -0
- package/src/query/DmlBuilder.ts +131 -0
- package/src/query/QueryBuilder.ts +8 -0
- package/src/query/RawQueryBuilder.ts +173 -0
- package/src/query/interpolate.ts +73 -0
- package/src/query/native.ts +29 -0
- package/src/query/operators.ts +27 -0
- package/src/schema/Migration.ts +36 -7
- package/src/schema/MigrationRunner.ts +483 -119
- package/src/schema/Schema.ts +168 -19
- package/src/schema/SchemaDumper.ts +518 -0
- package/src/schema/Seeder.ts +47 -10
- package/src/schema/TableBuilder.ts +48 -11
- package/src/schema/catalog.ts +94 -24
- package/src/schema/introspect.ts +26 -9
- package/src/seeders.ts +16 -0
- package/src/services/db.ts +343 -31
- package/src/testing/DatabaseCleanup.ts +73 -21
- package/src/testing/DbAssertions.ts +220 -0
- package/src/testing/Factory.ts +559 -79
- package/src/testing/TestUtils.ts +123 -0
- package/src/testing/index.ts +12 -1
- package/src/types/relations.ts +43 -0
package/src/Transaction.ts
CHANGED
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { EventEmitter } from "node:events";
|
|
8
9
|
import type { TransactionOptions } from "./adapters/NapiDbAdapter.js";
|
|
9
10
|
import type { DatabaseConnection } from "./BaseRepository.js";
|
|
11
|
+
import {
|
|
12
|
+
makeTransactionQueryBuilders,
|
|
13
|
+
type TransactionQueryBuilders,
|
|
14
|
+
} from "./query/DatabaseQueryBuilder.js";
|
|
15
|
+
import { getAtlasDialect } from "./query/native.js";
|
|
10
16
|
import {
|
|
11
17
|
isTransactionClient,
|
|
12
18
|
TRANSACTION_BRAND,
|
|
@@ -15,7 +21,9 @@ import {
|
|
|
15
21
|
/** A post-commit / post-rollback side effect (Lucid `trx.after(...)`). */
|
|
16
22
|
export type AfterHook = () => void | Promise<void>;
|
|
17
23
|
|
|
18
|
-
export interface TransactionClient
|
|
24
|
+
export interface TransactionClient
|
|
25
|
+
extends Omit<DatabaseConnection, "query">,
|
|
26
|
+
TransactionQueryBuilders {
|
|
19
27
|
commit(): Promise<void>;
|
|
20
28
|
rollback(): Promise<void>;
|
|
21
29
|
/**
|
|
@@ -27,10 +35,134 @@ export interface TransactionClient extends DatabaseConnection {
|
|
|
27
35
|
* succeed).
|
|
28
36
|
*/
|
|
29
37
|
after(event: "commit" | "rollback", cb: AfterHook): void;
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to the client's `commit`/`rollback` EventEmitter (Lucid — the trx
|
|
40
|
+
* client IS a Node EventEmitter). These are SYNCHRONOUS, pre-hook notifications
|
|
41
|
+
* fired the moment this client commits/rolls back — DISTINCT from {@link after},
|
|
42
|
+
* which are durable post-commit hooks that forward to the root when nested.
|
|
43
|
+
*/
|
|
44
|
+
on(event: "commit" | "rollback", cb: AfterHook): this;
|
|
45
|
+
/** One-shot {@link on} (Lucid/Node EventEmitter `once`). */
|
|
46
|
+
once(event: "commit" | "rollback", cb: AfterHook): this;
|
|
47
|
+
/** Remove a listener added via {@link on}/{@link once} (Node EventEmitter `off`). */
|
|
48
|
+
off(event: "commit" | "rollback", cb: AfterHook): this;
|
|
49
|
+
/**
|
|
50
|
+
* Open a nested transaction (Lucid `const sp = await trx.transaction()`),
|
|
51
|
+
* implemented as a SAVEPOINT on the same pinned connection. Managed when given
|
|
52
|
+
* a callback (auto RELEASE / ROLLBACK TO), manual otherwise. Works on all three
|
|
53
|
+
* dialects — MySQL's SAVEPOINT statements (which can't be prepared) route
|
|
54
|
+
* through the text protocol in the napi layer.
|
|
55
|
+
*/
|
|
56
|
+
transaction(): Promise<TransactionClient>;
|
|
57
|
+
transaction(options: TransactionOptions): Promise<TransactionClient>;
|
|
58
|
+
transaction<T>(
|
|
59
|
+
callback: (trx: TransactionClient) => Promise<T> | T,
|
|
60
|
+
options?: TransactionOptions,
|
|
61
|
+
): Promise<T>;
|
|
30
62
|
readonly isNested: boolean;
|
|
31
63
|
readonly [TRANSACTION_BRAND]: true;
|
|
32
64
|
}
|
|
33
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Build the overloaded `transaction()` method for a client. Managed when given a
|
|
68
|
+
* callback (auto RELEASE / ROLLBACK TO), manual otherwise — both open a SAVEPOINT
|
|
69
|
+
* on `getParent()`. The impl signature is deliberately broader than the overloads.
|
|
70
|
+
*/
|
|
71
|
+
export function makeNestedTransactionFn(getParent: () => TransactionClient) {
|
|
72
|
+
function tx(): Promise<TransactionClient>;
|
|
73
|
+
function tx(options: TransactionOptions): Promise<TransactionClient>;
|
|
74
|
+
function tx<T>(
|
|
75
|
+
callback: (trx: TransactionClient) => Promise<T> | T,
|
|
76
|
+
options?: TransactionOptions,
|
|
77
|
+
): Promise<T>;
|
|
78
|
+
function tx(
|
|
79
|
+
arg1?:
|
|
80
|
+
| TransactionOptions
|
|
81
|
+
| ((trx: TransactionClient) => Promise<unknown> | unknown),
|
|
82
|
+
arg2?: TransactionOptions,
|
|
83
|
+
): Promise<unknown> {
|
|
84
|
+
const parent = getParent();
|
|
85
|
+
const callback = typeof arg1 === "function" ? arg1 : undefined;
|
|
86
|
+
const options = typeof arg1 === "function" ? arg2 : arg1;
|
|
87
|
+
return callback
|
|
88
|
+
? runManagedSavepoint(parent, callback, options)
|
|
89
|
+
: openSavepoint(parent);
|
|
90
|
+
}
|
|
91
|
+
return tx;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Managed nested savepoint: open, run the callback, RELEASE on success / ROLLBACK TO on throw. */
|
|
95
|
+
async function runManagedSavepoint<T>(
|
|
96
|
+
parent: TransactionClient,
|
|
97
|
+
callback: (trx: TransactionClient) => Promise<T> | T,
|
|
98
|
+
_options?: TransactionOptions,
|
|
99
|
+
): Promise<T> {
|
|
100
|
+
const sp = await openSavepoint(parent);
|
|
101
|
+
try {
|
|
102
|
+
const result = await callback(sp);
|
|
103
|
+
await sp.commit();
|
|
104
|
+
return result;
|
|
105
|
+
} catch (err) {
|
|
106
|
+
try {
|
|
107
|
+
await sp.rollback();
|
|
108
|
+
} catch {
|
|
109
|
+
/* best-effort */
|
|
110
|
+
}
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Open a SAVEPOINT-backed nested transaction client on `parent`. Shared by the
|
|
117
|
+
* standalone {@link transaction} helper, `trx.transaction()`, and the napi pinned
|
|
118
|
+
* client. Commit RELEASEs the savepoint and forwards hooks to the parent (a
|
|
119
|
+
* nested commit isn't durable until the root commits); rollback does ROLLBACK TO
|
|
120
|
+
* + RELEASE and fires the local rollback hooks.
|
|
121
|
+
*/
|
|
122
|
+
export async function openSavepoint(
|
|
123
|
+
parent: TransactionClient,
|
|
124
|
+
): Promise<TransactionClient> {
|
|
125
|
+
const name = `sp_${randomBytes(6).toString("hex")}`;
|
|
126
|
+
await parent.execute(`SAVEPOINT ${name}`, []);
|
|
127
|
+
const commitHooks: AfterHook[] = [];
|
|
128
|
+
const rollbackHooks: AfterHook[] = [];
|
|
129
|
+
const evt = makeTrxEvents();
|
|
130
|
+
const base = {
|
|
131
|
+
execute: parent.execute.bind(parent),
|
|
132
|
+
query: parent.query.bind(parent),
|
|
133
|
+
async commit(): Promise<void> {
|
|
134
|
+
await parent.execute(`RELEASE SAVEPOINT ${name}`, []);
|
|
135
|
+
evt.emit("commit"); // synchronous EventEmitter notification (this savepoint)
|
|
136
|
+
for (const hook of commitHooks) parent.after("commit", hook);
|
|
137
|
+
for (const hook of rollbackHooks) parent.after("rollback", hook);
|
|
138
|
+
},
|
|
139
|
+
async rollback(): Promise<void> {
|
|
140
|
+
await parent.execute(`ROLLBACK TO SAVEPOINT ${name}`, []);
|
|
141
|
+
try {
|
|
142
|
+
await parent.execute(`RELEASE SAVEPOINT ${name}`, []);
|
|
143
|
+
} catch {
|
|
144
|
+
/* best-effort — the savepoint is already logically unwound */
|
|
145
|
+
}
|
|
146
|
+
evt.emit("rollback");
|
|
147
|
+
await runAfterHooks(rollbackHooks);
|
|
148
|
+
},
|
|
149
|
+
after(event: "commit" | "rollback", cb: AfterHook): void {
|
|
150
|
+
(event === "commit" ? commitHooks : rollbackHooks).push(cb);
|
|
151
|
+
},
|
|
152
|
+
on: evt.on,
|
|
153
|
+
once: evt.once,
|
|
154
|
+
off: evt.off,
|
|
155
|
+
isNested: true,
|
|
156
|
+
[TRANSACTION_BRAND]: true as const,
|
|
157
|
+
};
|
|
158
|
+
const trx: TransactionClient = Object.assign(
|
|
159
|
+
base,
|
|
160
|
+
makeTransactionQueryBuilders(base, getAtlasDialect()),
|
|
161
|
+
{ transaction: makeNestedTransactionFn(() => trx) },
|
|
162
|
+
);
|
|
163
|
+
return trx;
|
|
164
|
+
}
|
|
165
|
+
|
|
34
166
|
/**
|
|
35
167
|
* Run every registered after-hook, swallowing errors — a post-commit side
|
|
36
168
|
* effect must never surface a failure on a transaction the caller already saw
|
|
@@ -46,68 +178,59 @@ export async function runAfterHooks(hooks: AfterHook[]): Promise<void> {
|
|
|
46
178
|
}
|
|
47
179
|
}
|
|
48
180
|
|
|
181
|
+
/**
|
|
182
|
+
* The `on`/`once`/`off` EventEmitter surface for a transaction client, plus an
|
|
183
|
+
* internal `emit` the client fires at commit/rollback. Distinct from `after`
|
|
184
|
+
* hooks: these are synchronous, per-client, pre-hook notifications (Lucid — the
|
|
185
|
+
* trx client is a Node EventEmitter). `on`/`once`/`off` return the client for
|
|
186
|
+
* chaining via their `this` binding.
|
|
187
|
+
*/
|
|
188
|
+
export function makeTrxEvents(): {
|
|
189
|
+
on(
|
|
190
|
+
this: TransactionClient,
|
|
191
|
+
e: "commit" | "rollback",
|
|
192
|
+
cb: AfterHook,
|
|
193
|
+
): TransactionClient;
|
|
194
|
+
once(
|
|
195
|
+
this: TransactionClient,
|
|
196
|
+
e: "commit" | "rollback",
|
|
197
|
+
cb: AfterHook,
|
|
198
|
+
): TransactionClient;
|
|
199
|
+
off(
|
|
200
|
+
this: TransactionClient,
|
|
201
|
+
e: "commit" | "rollback",
|
|
202
|
+
cb: AfterHook,
|
|
203
|
+
): TransactionClient;
|
|
204
|
+
emit(e: "commit" | "rollback"): void;
|
|
205
|
+
} {
|
|
206
|
+
const ee = new EventEmitter();
|
|
207
|
+
return {
|
|
208
|
+
on(e, cb) {
|
|
209
|
+
ee.on(e, cb);
|
|
210
|
+
return this;
|
|
211
|
+
},
|
|
212
|
+
once(e, cb) {
|
|
213
|
+
ee.once(e, cb);
|
|
214
|
+
return this;
|
|
215
|
+
},
|
|
216
|
+
off(e, cb) {
|
|
217
|
+
ee.off(e, cb);
|
|
218
|
+
return this;
|
|
219
|
+
},
|
|
220
|
+
emit(e) {
|
|
221
|
+
ee.emit(e);
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
49
226
|
export async function transaction<T>(
|
|
50
227
|
db: DatabaseConnection,
|
|
51
228
|
callback: (trx: TransactionClient) => Promise<T> | T,
|
|
52
229
|
options?: TransactionOptions,
|
|
53
230
|
): Promise<T> {
|
|
231
|
+
// Nested (the caller passed a live trx) → a SAVEPOINT-backed managed savepoint.
|
|
54
232
|
if (isTransactionClient(db)) {
|
|
55
|
-
|
|
56
|
-
const name = `sp_${randomBytes(6).toString("hex")}`;
|
|
57
|
-
await parent.execute(`SAVEPOINT ${name}`, []);
|
|
58
|
-
|
|
59
|
-
const commitHooks: AfterHook[] = [];
|
|
60
|
-
const rollbackHooks: AfterHook[] = [];
|
|
61
|
-
|
|
62
|
-
const trx: TransactionClient = {
|
|
63
|
-
execute: parent.execute.bind(parent),
|
|
64
|
-
query: parent.query.bind(parent),
|
|
65
|
-
async commit() {
|
|
66
|
-
await parent.execute(`RELEASE SAVEPOINT ${name}`, []);
|
|
67
|
-
// A nested commit is NOT durable until the root commits — forward BOTH
|
|
68
|
-
// hook sets to the parent. Commit hooks fire on the real (root) commit
|
|
69
|
-
// and drop if the outer later rolls back. Rollback hooks must ALSO
|
|
70
|
-
// forward: this savepoint's released work is folded into the parent, so
|
|
71
|
-
// an outer rollback undoes it too — dropping them here would strand any
|
|
72
|
-
// in-memory restoration a nested caller registered on `after('rollback')`
|
|
73
|
-
// (the work IS rolled back, just by the parent). The parent fires exactly
|
|
74
|
-
// one of its two hook sets, so no double-run.
|
|
75
|
-
for (const hook of commitHooks) parent.after("commit", hook);
|
|
76
|
-
for (const hook of rollbackHooks) parent.after("rollback", hook);
|
|
77
|
-
},
|
|
78
|
-
async rollback() {
|
|
79
|
-
await parent.execute(`ROLLBACK TO SAVEPOINT ${name}`, []);
|
|
80
|
-
// ROLLBACK TO leaves the savepoint ESTABLISHED — release it so it doesn't
|
|
81
|
-
// stay stacked on the connection through a long outer transaction with
|
|
82
|
-
// many nested failures. Best-effort: the rollback already unwound the
|
|
83
|
-
// work, so a RELEASE failure must not surface. (PG/MySQL/SQLite all
|
|
84
|
-
// accept RELEASE after ROLLBACK TO.)
|
|
85
|
-
try {
|
|
86
|
-
await parent.execute(`RELEASE SAVEPOINT ${name}`, []);
|
|
87
|
-
} catch {
|
|
88
|
-
/* best-effort — the savepoint is already logically unwound */
|
|
89
|
-
}
|
|
90
|
-
await runAfterHooks(rollbackHooks);
|
|
91
|
-
},
|
|
92
|
-
after(event, cb) {
|
|
93
|
-
(event === "commit" ? commitHooks : rollbackHooks).push(cb);
|
|
94
|
-
},
|
|
95
|
-
isNested: true,
|
|
96
|
-
[TRANSACTION_BRAND]: true,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const result = await callback(trx);
|
|
101
|
-
await trx.commit();
|
|
102
|
-
return result;
|
|
103
|
-
} catch (err) {
|
|
104
|
-
try {
|
|
105
|
-
await trx.rollback();
|
|
106
|
-
} catch {
|
|
107
|
-
/* best-effort */
|
|
108
|
-
}
|
|
109
|
-
throw err;
|
|
110
|
-
}
|
|
233
|
+
return runManagedSavepoint(db, callback, options);
|
|
111
234
|
}
|
|
112
235
|
|
|
113
236
|
// Pinned interactive transaction (napi-backed): db.transaction() acquires ONE
|
|
@@ -125,24 +248,35 @@ export async function transaction<T>(
|
|
|
125
248
|
|
|
126
249
|
const commitHooks: AfterHook[] = [];
|
|
127
250
|
const rollbackHooks: AfterHook[] = [];
|
|
251
|
+
const evt = makeTrxEvents();
|
|
128
252
|
|
|
129
|
-
const
|
|
253
|
+
const base = {
|
|
130
254
|
execute: db.execute.bind(db),
|
|
131
255
|
query: db.query.bind(db),
|
|
132
256
|
async commit() {
|
|
133
257
|
await db.execute("COMMIT", []);
|
|
258
|
+
evt.emit("commit"); // synchronous EventEmitter notification
|
|
134
259
|
await runAfterHooks(commitHooks);
|
|
135
260
|
},
|
|
136
261
|
async rollback() {
|
|
137
262
|
await db.execute("ROLLBACK", []);
|
|
263
|
+
evt.emit("rollback");
|
|
138
264
|
await runAfterHooks(rollbackHooks);
|
|
139
265
|
},
|
|
140
|
-
after(event, cb) {
|
|
266
|
+
after(event: "commit" | "rollback", cb: AfterHook) {
|
|
141
267
|
(event === "commit" ? commitHooks : rollbackHooks).push(cb);
|
|
142
268
|
},
|
|
269
|
+
on: evt.on,
|
|
270
|
+
once: evt.once,
|
|
271
|
+
off: evt.off,
|
|
143
272
|
isNested: false,
|
|
144
|
-
[TRANSACTION_BRAND]: true,
|
|
273
|
+
[TRANSACTION_BRAND]: true as const,
|
|
145
274
|
};
|
|
275
|
+
const trx: TransactionClient = Object.assign(
|
|
276
|
+
base,
|
|
277
|
+
makeTransactionQueryBuilders(base, getAtlasDialect()),
|
|
278
|
+
{ transaction: makeNestedTransactionFn(() => trx) },
|
|
279
|
+
);
|
|
146
280
|
|
|
147
281
|
try {
|
|
148
282
|
const result = await callback(trx);
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { emitDbQuery, hasDbQueryListeners } from "../events.js";
|
|
6
|
+
import { makeTransactionQueryBuilders } from "../query/DatabaseQueryBuilder.js";
|
|
6
7
|
import {
|
|
7
8
|
type AfterHook,
|
|
9
|
+
makeNestedTransactionFn,
|
|
10
|
+
makeTrxEvents,
|
|
8
11
|
runAfterHooks,
|
|
9
12
|
type TransactionClient,
|
|
10
13
|
} from "../Transaction.js";
|
|
@@ -86,6 +89,17 @@ export interface QueryMeta {
|
|
|
86
89
|
* `debug: false` — this is what `ModelQuery.debug()` sets.
|
|
87
90
|
*/
|
|
88
91
|
debug?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Arbitrary metadata attached to the `db:query` event (Adonis Lucid
|
|
94
|
+
* `reporterData`) — request id, user id, feature flag, … — for listeners.
|
|
95
|
+
*/
|
|
96
|
+
reporterData?: Record<string, unknown>;
|
|
97
|
+
/**
|
|
98
|
+
* Server-side statement timeout in ms (Lucid `timeout(ms, { cancel: true })`).
|
|
99
|
+
* Routes to the driver's `statement_timeout` (Postgres) / `MAX_EXECUTION_TIME`
|
|
100
|
+
* (MySQL SELECT) so the server aborts the query — not just the client race.
|
|
101
|
+
*/
|
|
102
|
+
serverTimeoutMs?: number;
|
|
89
103
|
}
|
|
90
104
|
|
|
91
105
|
/** Per-connection observability settings (Lucid's `debug` connection option). */
|
|
@@ -149,7 +163,7 @@ export interface AsyncDatabaseConnection {
|
|
|
149
163
|
/** Shape of the NAPI ReamTransaction handle returned by `ReamDatabase.begin()`. */
|
|
150
164
|
interface NapiReamTransaction {
|
|
151
165
|
query(sql: string, paramsJson: string): Promise<string>;
|
|
152
|
-
execute(sql: string, paramsJson: string): Promise<
|
|
166
|
+
execute(sql: string, paramsJson: string): Promise<string>;
|
|
153
167
|
commit(): Promise<void>;
|
|
154
168
|
rollback(): Promise<void>;
|
|
155
169
|
}
|
|
@@ -157,7 +171,19 @@ interface NapiReamTransaction {
|
|
|
157
171
|
/** Shape of the NAPI ReamDatabase class. */
|
|
158
172
|
interface NapiReamDatabase {
|
|
159
173
|
query(sql: string, paramsJson: string): Promise<string>;
|
|
160
|
-
execute(sql: string, paramsJson: string): Promise<
|
|
174
|
+
execute(sql: string, paramsJson: string): Promise<string>;
|
|
175
|
+
/** query with a server-side statement timeout (Lucid `timeout(ms,{cancel:true})`). */
|
|
176
|
+
queryTimed(
|
|
177
|
+
sql: string,
|
|
178
|
+
paramsJson: string,
|
|
179
|
+
timeoutMs: number,
|
|
180
|
+
): Promise<string>;
|
|
181
|
+
/** execute with a server-side statement timeout (Postgres). */
|
|
182
|
+
executeTimed(
|
|
183
|
+
sql: string,
|
|
184
|
+
paramsJson: string,
|
|
185
|
+
timeoutMs: number,
|
|
186
|
+
): Promise<string>;
|
|
161
187
|
runInTransaction(batchJson: string): Promise<number>;
|
|
162
188
|
begin(isolationLevel?: string): Promise<NapiReamTransaction>;
|
|
163
189
|
close(): Promise<void>;
|
|
@@ -267,16 +293,22 @@ export async function createNapiConnection(
|
|
|
267
293
|
// COMMIT / ROLLBACK is durable (Lucid `trx.after(...)`), errors swallowed.
|
|
268
294
|
const commitHooks: AfterHook[] = [];
|
|
269
295
|
const rollbackHooks: AfterHook[] = [];
|
|
270
|
-
|
|
296
|
+
const evt = makeTrxEvents();
|
|
297
|
+
const base = {
|
|
271
298
|
async execute(
|
|
272
299
|
sql: string,
|
|
273
300
|
params: unknown[] = [],
|
|
274
|
-
): Promise<{ rowsAffected: number }> {
|
|
275
|
-
const
|
|
301
|
+
): Promise<{ rowsAffected: number; lastInsertId?: number }> {
|
|
302
|
+
const json = await native.execute(
|
|
276
303
|
sql,
|
|
277
304
|
JSON.stringify(params, napiReplacer),
|
|
278
305
|
);
|
|
279
|
-
|
|
306
|
+
const r = JSON.parse(json);
|
|
307
|
+
return {
|
|
308
|
+
rowsAffected: Number(r.rows_affected),
|
|
309
|
+
lastInsertId:
|
|
310
|
+
r.last_insert_id == null ? undefined : Number(r.last_insert_id),
|
|
311
|
+
};
|
|
280
312
|
},
|
|
281
313
|
async query<T = Record<string, unknown>>(
|
|
282
314
|
sql: string,
|
|
@@ -290,18 +322,32 @@ export async function createNapiConnection(
|
|
|
290
322
|
},
|
|
291
323
|
async commit(): Promise<void> {
|
|
292
324
|
await native.commit();
|
|
325
|
+
evt.emit("commit"); // synchronous EventEmitter notification (Lucid trx.on)
|
|
293
326
|
await runAfterHooks(commitHooks);
|
|
294
327
|
},
|
|
295
328
|
async rollback(): Promise<void> {
|
|
296
329
|
await native.rollback();
|
|
330
|
+
evt.emit("rollback");
|
|
297
331
|
await runAfterHooks(rollbackHooks);
|
|
298
332
|
},
|
|
299
333
|
after(event: "commit" | "rollback", cb: AfterHook): void {
|
|
300
334
|
(event === "commit" ? commitHooks : rollbackHooks).push(cb);
|
|
301
335
|
},
|
|
336
|
+
on: evt.on,
|
|
337
|
+
once: evt.once,
|
|
338
|
+
off: evt.off,
|
|
302
339
|
isNested: false,
|
|
303
|
-
[TRANSACTION_BRAND]: true,
|
|
340
|
+
[TRANSACTION_BRAND]: true as const,
|
|
304
341
|
};
|
|
342
|
+
// Lucid: the transaction client is also a query-builder entry point
|
|
343
|
+
// (trx.table()/from()/insertQuery()), routed through THIS pinned connection.
|
|
344
|
+
// `trx.transaction()` opens a SAVEPOINT-backed nested client on this one.
|
|
345
|
+
const client: TransactionClient = Object.assign(
|
|
346
|
+
base,
|
|
347
|
+
makeTransactionQueryBuilders(base, dialect),
|
|
348
|
+
{ transaction: makeNestedTransactionFn(() => client) },
|
|
349
|
+
);
|
|
350
|
+
return client;
|
|
305
351
|
}
|
|
306
352
|
|
|
307
353
|
// Lucid-compatible `transaction`: managed when given a callback (auto
|
|
@@ -362,6 +408,7 @@ export async function createNapiConnection(
|
|
|
362
408
|
method: meta?.method,
|
|
363
409
|
ddl: meta?.ddl,
|
|
364
410
|
inTransaction: false,
|
|
411
|
+
reporterData: meta?.reporterData,
|
|
365
412
|
});
|
|
366
413
|
return result;
|
|
367
414
|
} catch (error) {
|
|
@@ -375,6 +422,7 @@ export async function createNapiConnection(
|
|
|
375
422
|
ddl: meta?.ddl,
|
|
376
423
|
inTransaction: false,
|
|
377
424
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
425
|
+
reporterData: meta?.reporterData,
|
|
378
426
|
});
|
|
379
427
|
throw error;
|
|
380
428
|
}
|
|
@@ -389,7 +437,11 @@ export async function createNapiConnection(
|
|
|
389
437
|
meta?: QueryMeta,
|
|
390
438
|
): Promise<T[]> {
|
|
391
439
|
return observed(sql, params, meta, async () => {
|
|
392
|
-
const
|
|
440
|
+
const paramsJson = JSON.stringify(params, napiReplacer);
|
|
441
|
+
const json =
|
|
442
|
+
meta?.serverTimeoutMs != null
|
|
443
|
+
? await db.queryTimed(sql, paramsJson, meta.serverTimeoutMs)
|
|
444
|
+
: await db.query(sql, paramsJson);
|
|
393
445
|
return JSON.parse(json, napiReviver) as T[];
|
|
394
446
|
});
|
|
395
447
|
},
|
|
@@ -398,13 +450,19 @@ export async function createNapiConnection(
|
|
|
398
450
|
sql: string,
|
|
399
451
|
params: unknown[] = [],
|
|
400
452
|
meta?: QueryMeta,
|
|
401
|
-
): Promise<{ rowsAffected: number }> {
|
|
453
|
+
): Promise<{ rowsAffected: number; lastInsertId?: number }> {
|
|
402
454
|
return observed(sql, params, meta, async () => {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
455
|
+
const paramsJson = JSON.stringify(params, napiReplacer);
|
|
456
|
+
const json =
|
|
457
|
+
meta?.serverTimeoutMs != null
|
|
458
|
+
? await db.executeTimed(sql, paramsJson, meta.serverTimeoutMs)
|
|
459
|
+
: await db.execute(sql, paramsJson);
|
|
460
|
+
const r = JSON.parse(json);
|
|
461
|
+
return {
|
|
462
|
+
rowsAffected: Number(r.rows_affected),
|
|
463
|
+
lastInsertId:
|
|
464
|
+
r.last_insert_id == null ? undefined : Number(r.last_insert_id),
|
|
465
|
+
};
|
|
408
466
|
});
|
|
409
467
|
},
|
|
410
468
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The console command contract, declared locally.
|
|
3
|
+
*
|
|
4
|
+
* Atlas stays framework-agnostic: it must not import `@c9up/ream`, so it
|
|
5
|
+
* describes the shape Ream's console kernel dispatches against rather than
|
|
6
|
+
* importing it. Same reasoning as the previous `AtlasCommand` interface — only
|
|
7
|
+
* the shape changed, from a plain object to a class carrying its inputs as
|
|
8
|
+
* metadata.
|
|
9
|
+
*
|
|
10
|
+
* Ream's decorators (`@args` / `@flags`) live in the framework, so the helpers
|
|
11
|
+
* below build the same metadata without them.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface CommandOptions {
|
|
15
|
+
/** Boot the application before `run()`. Off by default. */
|
|
16
|
+
startApp?: boolean;
|
|
17
|
+
staysAlive?: boolean;
|
|
18
|
+
allowUnknownFlags?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ArgumentMetaData {
|
|
22
|
+
type: "string" | "spread";
|
|
23
|
+
propertyName: string;
|
|
24
|
+
argumentName: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
required: boolean;
|
|
27
|
+
default?: string | string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface FlagMetaData {
|
|
31
|
+
type: "string" | "boolean" | "number" | "array";
|
|
32
|
+
propertyName: string;
|
|
33
|
+
flagName: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
alias: string[];
|
|
36
|
+
default?: string | string[] | number | boolean;
|
|
37
|
+
required: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The static side the kernel reads. */
|
|
41
|
+
export interface AtlasCommandClass {
|
|
42
|
+
new (): { run(): Promise<void> | void };
|
|
43
|
+
commandName: string;
|
|
44
|
+
description: string;
|
|
45
|
+
options?: CommandOptions;
|
|
46
|
+
args?: readonly ArgumentMetaData[];
|
|
47
|
+
flags?: readonly FlagMetaData[];
|
|
48
|
+
help?: string | string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** `startServer` → `start-server`, matching the framework's decorators. */
|
|
52
|
+
function dashCase(value: string): string {
|
|
53
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function flag(
|
|
57
|
+
propertyName: string,
|
|
58
|
+
type: FlagMetaData["type"],
|
|
59
|
+
options: {
|
|
60
|
+
flagName?: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
alias?: string[];
|
|
63
|
+
default?: FlagMetaData["default"];
|
|
64
|
+
required?: boolean;
|
|
65
|
+
} = {},
|
|
66
|
+
): FlagMetaData {
|
|
67
|
+
return {
|
|
68
|
+
type,
|
|
69
|
+
propertyName,
|
|
70
|
+
flagName: options.flagName ?? dashCase(propertyName),
|
|
71
|
+
description: options.description,
|
|
72
|
+
alias: options.alias ?? [],
|
|
73
|
+
default: options.default,
|
|
74
|
+
required: options.required ?? false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function argument(
|
|
79
|
+
propertyName: string,
|
|
80
|
+
options: {
|
|
81
|
+
type?: ArgumentMetaData["type"];
|
|
82
|
+
argumentName?: string;
|
|
83
|
+
description?: string;
|
|
84
|
+
required?: boolean;
|
|
85
|
+
default?: ArgumentMetaData["default"];
|
|
86
|
+
} = {},
|
|
87
|
+
): ArgumentMetaData {
|
|
88
|
+
return {
|
|
89
|
+
type: options.type ?? "string",
|
|
90
|
+
propertyName,
|
|
91
|
+
argumentName: options.argumentName ?? dashCase(propertyName),
|
|
92
|
+
description: options.description,
|
|
93
|
+
required: options.required ?? options.default === undefined,
|
|
94
|
+
default: options.default,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory console command — `make:factory` (Adonis Lucid). Scaffolds a model
|
|
3
|
+
* factory file. Same shape as the migration/seeder commands: a plain
|
|
4
|
+
* `{ name, description, run }` object registered in `reamrc.commands`.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* // commands/make-factory.ts
|
|
8
|
+
* import { makeFactoryCommand } from '@c9up/atlas'
|
|
9
|
+
* export default makeFactoryCommand({ factoriesDir: 'database/factories' })
|
|
10
|
+
* // run: <console-entry> make:factory User
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fsp from "node:fs/promises";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { camelToSnake } from "../utils/casing.js";
|
|
16
|
+
import { assertSafeName } from "../utils/safePath.js";
|
|
17
|
+
import { type AtlasCommandClass, argument } from "./contract.js";
|
|
18
|
+
|
|
19
|
+
export interface FactoryCommandOptions {
|
|
20
|
+
/** Directory the factory files are scaffolded into. */
|
|
21
|
+
factoriesDir: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Scaffold body for a fresh factory (`make:factory <Model>`). */
|
|
25
|
+
function factoryStub(model: string): string {
|
|
26
|
+
return `import { factory } from '@c9up/atlas'
|
|
27
|
+
import ${model} from '#models/${model.toLowerCase()}'
|
|
28
|
+
|
|
29
|
+
export const ${model}Factory = factory(${model}, ({ faker }) => ({
|
|
30
|
+
// email: faker.internet.email(),
|
|
31
|
+
}))
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `make:factory <Model>` — scaffold `<Model>Factory.ts` in `factoriesDir`. The
|
|
37
|
+
* name is validated (no path separators / traversal) and written with `wx` so an
|
|
38
|
+
* existing factory is never clobbered.
|
|
39
|
+
*/
|
|
40
|
+
export function makeFactoryCommand(
|
|
41
|
+
options: FactoryCommandOptions,
|
|
42
|
+
): AtlasCommandClass {
|
|
43
|
+
return class MakeFactory {
|
|
44
|
+
static commandName = "make:factory";
|
|
45
|
+
static description = "Scaffold a new model factory file";
|
|
46
|
+
// Pure filesystem work: no reason to boot the app and open a connection.
|
|
47
|
+
static options = { startApp: false };
|
|
48
|
+
static args = [
|
|
49
|
+
argument("model", { description: "Model the factory is built for" }),
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
declare model: string;
|
|
53
|
+
|
|
54
|
+
async run(): Promise<void> {
|
|
55
|
+
// A missing model is caught by the kernel before `run()` — it reports
|
|
56
|
+
// the required argument by name, so there is no usage check here.
|
|
57
|
+
// Lucid convention: `make:factory User` → `user_factory.ts`
|
|
58
|
+
// (`BlogPost` → `blog_post_factory.ts`).
|
|
59
|
+
const fileName = `${camelToSnake(this.model)}_factory.ts`;
|
|
60
|
+
try {
|
|
61
|
+
assertSafeName(fileName, "FACTORY_INVALID", "factory");
|
|
62
|
+
} catch {
|
|
63
|
+
console.error(`[atlas] invalid factory name: ${this.model}`);
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const filePath = path.join(options.factoriesDir, fileName);
|
|
68
|
+
await fsp.mkdir(options.factoriesDir, { recursive: true });
|
|
69
|
+
await fsp.writeFile(filePath, factoryStub(this.model), { flag: "wx" });
|
|
70
|
+
console.log(`Created ${filePath}`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|