@stratal/framework 0.0.20 → 0.0.22
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/dist/access-control/index.d.mts +8 -14
- package/dist/access-control/index.d.mts.map +1 -1
- package/dist/access-control/index.mjs +3 -3
- package/dist/access-control/index.mjs.map +1 -1
- package/dist/{access.service-BjYVtUJw.mjs → access.service-DWRsDLJq.mjs} +9 -12
- package/dist/access.service-DWRsDLJq.mjs.map +1 -0
- package/dist/auth/index.d.mts +60 -133
- package/dist/auth/index.d.mts.map +1 -1
- package/dist/auth/index.mjs +123 -200
- package/dist/auth/index.mjs.map +1 -1
- package/dist/{auth-context-6Li1JkIq.mjs → auth-context-CObJcW0t.mjs} +7 -6
- package/dist/auth-context-CObJcW0t.mjs.map +1 -0
- package/dist/{auth-context-B44CDZCt.d.mts → auth-context-D70ktWUf.d.mts} +8 -9
- package/dist/auth-context-D70ktWUf.d.mts.map +1 -0
- package/dist/context/index.d.mts +5 -10
- package/dist/context/index.d.mts.map +1 -1
- package/dist/context/index.mjs +3 -3
- package/dist/database/index.d.mts +3 -3
- package/dist/database/index.mjs +45 -179
- package/dist/database/index.mjs.map +1 -1
- package/dist/{decorate-CdfCRvAc.mjs → decorate-7CAoTBu4.mjs} +1 -1
- package/dist/{decorateParam-Dc5DGEpb.mjs → decorateParam-C_dJ_dIO.mjs} +2 -2
- package/dist/{decorateParam-Dc5DGEpb.mjs.map → decorateParam-C_dJ_dIO.mjs.map} +1 -1
- package/dist/errors-MCyrn_V2.mjs +18 -0
- package/dist/errors-MCyrn_V2.mjs.map +1 -0
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -0
- package/dist/factory/index.mjs.map +1 -1
- package/dist/guards/index.d.mts.map +1 -1
- package/dist/guards/index.mjs +12 -16
- package/dist/guards/index.mjs.map +1 -1
- package/dist/{index-CCDPF-1Y.d.mts → index-DJ_DCIul.d.mts} +11 -93
- package/dist/index-DJ_DCIul.d.mts.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/insufficient-permissions.error-GwrkWnEM.mjs +16 -0
- package/dist/insufficient-permissions.error-GwrkWnEM.mjs.map +1 -0
- package/dist/{types-BZlcRR2M.d.mts → types-4uX3XKRM.d.mts} +1 -1
- package/dist/{types-BZlcRR2M.d.mts.map → types-4uX3XKRM.d.mts.map} +1 -1
- package/dist/{types-BLyu9dAd.d.mts → types-tu9pTehB.d.mts} +1 -1
- package/dist/{types-BLyu9dAd.d.mts.map → types-tu9pTehB.d.mts.map} +1 -1
- package/package.json +16 -14
- package/dist/access.service-BjYVtUJw.mjs.map +0 -1
- package/dist/auth-context-6Li1JkIq.mjs.map +0 -1
- package/dist/auth-context-B44CDZCt.d.mts.map +0 -1
- package/dist/decorateMetadata-CqtSx3_1.mjs +0 -6
- package/dist/errors-B1vVXc1T.mjs +0 -25
- package/dist/errors-B1vVXc1T.mjs.map +0 -1
- package/dist/index-CCDPF-1Y.d.mts.map +0 -1
- package/dist/insufficient-permissions.error-CRnOHYvq.mjs +0 -23
- package/dist/insufficient-permissions.error-CRnOHYvq.mjs.map +0 -1
package/dist/database/index.mjs
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
3
|
-
import { DI_TOKENS, Scope, Transient, delay } from "stratal/di";
|
|
4
|
-
import { I18nModule } from "stratal/i18n";
|
|
1
|
+
import { t as __decorate } from "../decorate-7CAoTBu4.mjs";
|
|
2
|
+
import { DI_TOKENS, Transient, inject, lazy } from "stratal/di";
|
|
5
3
|
import { Module } from "stratal/module";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { DatabaseError, HttpException } from "stratal/errors";
|
|
5
|
+
import { I18nModule } from "stratal/i18n";
|
|
8
6
|
import { Command } from "stratal/quarry";
|
|
9
7
|
import { ORMError, ORMErrorReason, ZenStackClient } from "@zenstackhq/orm";
|
|
10
|
-
import {
|
|
8
|
+
import { withZodI18n, z } from "stratal/validation";
|
|
11
9
|
//#region src/database/commands/zenstack.command.ts
|
|
12
10
|
/**
|
|
13
11
|
* Base command for ZenStack CLI wrappers.
|
|
@@ -124,185 +122,53 @@ var MigrateStatusCommand = class extends ZenStackCommand {
|
|
|
124
122
|
}
|
|
125
123
|
};
|
|
126
124
|
//#endregion
|
|
127
|
-
//#region src/database/errors/invalid-error-code-range.error.ts
|
|
128
|
-
/**
|
|
129
|
-
* InvalidErrorCodeRangeError
|
|
130
|
-
*
|
|
131
|
-
* Thrown when a DatabaseError subclass is constructed with an error code
|
|
132
|
-
* outside the valid database error range (2000-2999).
|
|
133
|
-
* This is a developer-facing error to enforce error code conventions.
|
|
134
|
-
*/
|
|
135
|
-
var InvalidErrorCodeRangeError = class extends ApplicationError {
|
|
136
|
-
constructor(code, expectedRange) {
|
|
137
|
-
super("errors.invalidErrorCodeRange", ERROR_CODES.SYSTEM.INVALID_ERROR_CODE_RANGE, {
|
|
138
|
-
code,
|
|
139
|
-
expectedRange
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
//#endregion
|
|
144
|
-
//#region src/database/errors/database-error.ts
|
|
145
|
-
/**
|
|
146
|
-
* DatabaseError
|
|
147
|
-
*
|
|
148
|
-
* Generic database error thrown when a database operation fails
|
|
149
|
-
* and doesn't fit into a more specific error category.
|
|
150
|
-
*
|
|
151
|
-
* This is the base class for all database-related errors.
|
|
152
|
-
*/
|
|
153
|
-
var DatabaseError = class extends ApplicationError {
|
|
154
|
-
constructor(messageKey = "errors.databaseGeneric", code = ERROR_CODES.DATABASE.GENERIC, metadata) {
|
|
155
|
-
if (code < 2e3 || code >= 3e3) throw new InvalidErrorCodeRangeError(code, "2000-2999");
|
|
156
|
-
super(messageKey, code, metadata);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
//#endregion
|
|
160
|
-
//#region src/database/errors/database-config.error.ts
|
|
161
|
-
var DatabaseConfigError = class extends DatabaseError {
|
|
162
|
-
constructor(details) {
|
|
163
|
-
super("errors.databaseGeneric", ERROR_CODES.DATABASE.GENERIC, { details });
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
//#endregion
|
|
167
|
-
//#region src/database/errors/foreign-key-constraint.error.ts
|
|
168
|
-
/**
|
|
169
|
-
* ForeignKeyConstraintError
|
|
170
|
-
*
|
|
171
|
-
* Thrown when a database foreign key constraint is violated.
|
|
172
|
-
* This typically occurs when:
|
|
173
|
-
* - Trying to insert a record with a foreign key that doesn't exist
|
|
174
|
-
* - Trying to delete a record that is referenced by other records
|
|
175
|
-
* - Trying to update a foreign key to a non-existent value
|
|
176
|
-
*/
|
|
177
|
-
var ForeignKeyConstraintError = class extends DatabaseError {
|
|
178
|
-
constructor(field) {
|
|
179
|
-
super("errors.databaseForeignKeyConstraint", ERROR_CODES.DATABASE.FOREIGN_KEY_CONSTRAINT, { field });
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
//#endregion
|
|
183
125
|
//#region src/database/errors/record-not-found.error.ts
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
* returns null, or when a required record doesn't exist.
|
|
190
|
-
*
|
|
191
|
-
* Services should catch this and optionally refine it to a more specific
|
|
192
|
-
* domain error (e.g., NoteNotFoundError, UserNotFoundError).
|
|
193
|
-
*/
|
|
194
|
-
var RecordNotFoundError = class extends DatabaseError {
|
|
195
|
-
constructor(details) {
|
|
196
|
-
super("errors.databaseRecordNotFound", ERROR_CODES.DATABASE.RECORD_NOT_FOUND, { details });
|
|
126
|
+
var RecordNotFoundError = class extends HttpException {
|
|
127
|
+
details;
|
|
128
|
+
constructor(details, cause) {
|
|
129
|
+
super(404, "Record not found", cause);
|
|
130
|
+
this.details = details;
|
|
197
131
|
}
|
|
198
132
|
};
|
|
199
133
|
//#endregion
|
|
200
134
|
//#region src/database/errors/unique-constraint.error.ts
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
* with a value that already exists in a unique column.
|
|
207
|
-
*
|
|
208
|
-
* Services should catch this and optionally refine it to a more specific
|
|
209
|
-
* domain error (e.g., UserEmailAlreadyExistsError).
|
|
210
|
-
*/
|
|
211
|
-
var UniqueConstraintError = class extends DatabaseError {
|
|
212
|
-
constructor(fields) {
|
|
213
|
-
super("errors.databaseUniqueConstraint", ERROR_CODES.DATABASE.UNIQUE_CONSTRAINT, { fields });
|
|
135
|
+
var UniqueConstraintError = class extends HttpException {
|
|
136
|
+
fields;
|
|
137
|
+
constructor(fields, cause) {
|
|
138
|
+
super(409, "Record already exists", cause);
|
|
139
|
+
this.fields = fields;
|
|
214
140
|
}
|
|
215
141
|
};
|
|
216
142
|
//#endregion
|
|
217
143
|
//#region src/database/errors/from-zenstack-error.ts
|
|
218
|
-
/**
|
|
219
|
-
* Transform ZenStack ORM errors into ApplicationError instances
|
|
220
|
-
*
|
|
221
|
-
* This function maps ORMError codes to generic database error classes.
|
|
222
|
-
* Services can catch these generic errors and optionally refine them to
|
|
223
|
-
* more specific domain errors if needed.
|
|
224
|
-
*
|
|
225
|
-
* @param error - The error thrown by ZenStack ORM
|
|
226
|
-
* @returns An ApplicationError instance
|
|
227
|
-
*
|
|
228
|
-
* @example
|
|
229
|
-
* ```typescript
|
|
230
|
-
* try {
|
|
231
|
-
* await db.user.create({ data: { email: 'existing@example.com' } })
|
|
232
|
-
* } catch (error) {
|
|
233
|
-
* throw fromZenStackError(error) // Becomes UniqueConstraintError or other
|
|
234
|
-
* }
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
144
|
function fromZenStackError(error) {
|
|
238
|
-
if (error instanceof ORMError) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
message: ormError.message,
|
|
247
|
-
reason: "Operation not supported"
|
|
248
|
-
});
|
|
249
|
-
case ORMErrorReason.INTERNAL_ERROR: return new DatabaseError("errors.databaseGeneric", ERROR_CODES.DATABASE.GENERIC, { message: ormError.message });
|
|
250
|
-
default: return new DatabaseError("errors.databaseGeneric", ERROR_CODES.DATABASE.GENERIC, {
|
|
251
|
-
message: ormError.message,
|
|
252
|
-
reason: ormError.reason
|
|
253
|
-
});
|
|
254
|
-
}
|
|
145
|
+
if (error instanceof ORMError) switch (error.reason) {
|
|
146
|
+
case ORMErrorReason.NOT_FOUND: return new RecordNotFoundError(error.model, error);
|
|
147
|
+
case ORMErrorReason.DB_QUERY_ERROR: return parseDatabaseError(error);
|
|
148
|
+
case ORMErrorReason.INVALID_INPUT: return new DatabaseError("Invalid database query", error);
|
|
149
|
+
case ORMErrorReason.CONFIG_ERROR: return new DatabaseError("Database configuration error", error);
|
|
150
|
+
case ORMErrorReason.NOT_SUPPORTED: return new DatabaseError("Operation not supported", error);
|
|
151
|
+
case ORMErrorReason.INTERNAL_ERROR: return new DatabaseError("Database internal error", error);
|
|
152
|
+
default: return new DatabaseError("Database error", error);
|
|
255
153
|
}
|
|
256
|
-
return new DatabaseError("
|
|
154
|
+
return new DatabaseError("Database error", error);
|
|
257
155
|
}
|
|
258
|
-
/**
|
|
259
|
-
* Parse database-specific errors from the dbErrorCode field
|
|
260
|
-
*/
|
|
261
156
|
function parseDatabaseError(error) {
|
|
262
157
|
const dbErrorCode = error.dbErrorCode;
|
|
263
158
|
if (dbErrorCode) {
|
|
264
|
-
if (dbErrorCode === "23505") return new UniqueConstraintError([error.model ?? "unknown"]);
|
|
265
|
-
if (dbErrorCode === "23503") return new
|
|
266
|
-
if (dbErrorCode === "23502") return new DatabaseError("
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (dbErrorCode
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (dbErrorCode
|
|
275
|
-
if (dbErrorCode === "42P01") return new DatabaseError("errors.databaseTableNotFound", ERROR_CODES.DATABASE.GENERIC, {
|
|
276
|
-
dbErrorCode,
|
|
277
|
-
message: error.dbErrorMessage
|
|
278
|
-
});
|
|
279
|
-
if (dbErrorCode === "42703") return new DatabaseError("errors.databaseColumnNotFound", ERROR_CODES.DATABASE.GENERIC, {
|
|
280
|
-
dbErrorCode,
|
|
281
|
-
message: error.dbErrorMessage
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
if (dbErrorCode.startsWith("08")) return new DatabaseError("errors.databaseConnectionFailed", ERROR_CODES.DATABASE.CONNECTION_FAILED, {
|
|
285
|
-
dbErrorCode,
|
|
286
|
-
message: error.dbErrorMessage
|
|
287
|
-
});
|
|
288
|
-
if (dbErrorCode === "57014") return new DatabaseError("errors.databaseTimeout", ERROR_CODES.DATABASE.TIMEOUT, {
|
|
289
|
-
dbErrorCode,
|
|
290
|
-
message: error.dbErrorMessage
|
|
291
|
-
});
|
|
292
|
-
if (dbErrorCode.startsWith("40")) return new DatabaseError("errors.databaseTransactionConflict", ERROR_CODES.DATABASE.TRANSACTION_CONFLICT, {
|
|
293
|
-
dbErrorCode,
|
|
294
|
-
message: error.dbErrorMessage
|
|
295
|
-
});
|
|
296
|
-
if (dbErrorCode === "53300") return new DatabaseError("errors.databaseTooManyConnections", ERROR_CODES.DATABASE.TOO_MANY_CONNECTIONS, {
|
|
297
|
-
dbErrorCode,
|
|
298
|
-
message: error.dbErrorMessage
|
|
299
|
-
});
|
|
159
|
+
if (dbErrorCode === "23505") return new UniqueConstraintError([error.model ?? "unknown"], error);
|
|
160
|
+
if (dbErrorCode === "23503") return new DatabaseError("Foreign key constraint violation", error);
|
|
161
|
+
if (dbErrorCode === "23502") return new DatabaseError("Required field is missing", error);
|
|
162
|
+
if (dbErrorCode === "23514") return new DatabaseError("Database constraint violated", error);
|
|
163
|
+
if (dbErrorCode === "42P01") return new DatabaseError("Table does not exist", error);
|
|
164
|
+
if (dbErrorCode === "42703") return new DatabaseError("Column does not exist", error);
|
|
165
|
+
if (dbErrorCode.startsWith("42")) return new DatabaseError("Database syntax or access error", error);
|
|
166
|
+
if (dbErrorCode.startsWith("08")) return new DatabaseError("Database connection failed", error);
|
|
167
|
+
if (dbErrorCode === "57014") return new DatabaseError("Database query timeout", error);
|
|
168
|
+
if (dbErrorCode.startsWith("40")) return new DatabaseError("Transaction conflict or deadlock", error);
|
|
169
|
+
if (dbErrorCode === "53300") return new DatabaseError("Too many database connections", error);
|
|
300
170
|
}
|
|
301
|
-
return new DatabaseError("
|
|
302
|
-
dbErrorCode,
|
|
303
|
-
dbErrorMessage: error.dbErrorMessage,
|
|
304
|
-
sql: error.sql
|
|
305
|
-
});
|
|
171
|
+
return new DatabaseError("Database error", error);
|
|
306
172
|
}
|
|
307
173
|
//#endregion
|
|
308
174
|
//#region src/database/plugins/error-handler.plugin.ts
|
|
@@ -349,6 +215,7 @@ var ErrorHandlerPlugin = class {
|
|
|
349
215
|
* ```
|
|
350
216
|
*/
|
|
351
217
|
var EventEmitterPlugin = class {
|
|
218
|
+
options;
|
|
352
219
|
id = "event-emitter";
|
|
353
220
|
constructor(options) {
|
|
354
221
|
this.options = options;
|
|
@@ -396,19 +263,19 @@ var SchemaSwitcher = class {
|
|
|
396
263
|
//#endregion
|
|
397
264
|
//#region src/database/database.helpers.ts
|
|
398
265
|
const databaseConnectionSchema = z.object({
|
|
399
|
-
name: z.string().min(1,
|
|
266
|
+
name: z.string().min(1, withZodI18n("database.connectionNameRequired")),
|
|
400
267
|
schema: z.object({}).loose(),
|
|
401
268
|
dialect: z.function(),
|
|
402
269
|
plugins: z.array(z.object({}).loose()).optional(),
|
|
403
270
|
computedFields: z.object({}).loose().optional()
|
|
404
271
|
});
|
|
405
272
|
z.object({
|
|
406
|
-
default: z.string().min(1,
|
|
407
|
-
connections: z.array(databaseConnectionSchema).min(1,
|
|
273
|
+
default: z.string().min(1, withZodI18n("database.defaultConnectionRequired")),
|
|
274
|
+
connections: z.array(databaseConnectionSchema).min(1, withZodI18n("database.connectionRequired"))
|
|
408
275
|
}).refine((config) => {
|
|
409
276
|
const names = config.connections.map((c) => c.name);
|
|
410
277
|
return new Set(names).size === names.length;
|
|
411
|
-
},
|
|
278
|
+
}, withZodI18n("database.duplicateConnections")).refine((config) => config.connections.some((c) => c.name === config.default), withZodI18n("database.defaultConnectionNotFound"));
|
|
412
279
|
function createDatabaseService(conn, eventRegistry) {
|
|
413
280
|
const plugins = [
|
|
414
281
|
new ErrorHandlerPlugin(),
|
|
@@ -425,7 +292,7 @@ function createDatabaseService(conn, eventRegistry) {
|
|
|
425
292
|
});
|
|
426
293
|
}
|
|
427
294
|
};
|
|
428
|
-
DatabaseClient = __decorate([Transient()
|
|
295
|
+
DatabaseClient = __decorate([Transient()], DatabaseClient);
|
|
429
296
|
return DatabaseClient;
|
|
430
297
|
}
|
|
431
298
|
//#endregion
|
|
@@ -472,10 +339,9 @@ let DatabaseModule = _DatabaseModule = class DatabaseModule {
|
|
|
472
339
|
onInitialize(context) {
|
|
473
340
|
const config = context.container.resolve(DATABASE_TOKENS.Options);
|
|
474
341
|
const eventRegistry = context.container.resolve(DI_TOKENS.EventRegistry);
|
|
475
|
-
const container = context.container.getTsyringeContainer();
|
|
476
342
|
for (const conn of config.connections) {
|
|
477
343
|
const Service = createDatabaseService(conn, eventRegistry);
|
|
478
|
-
container.register(connectionSymbol(conn.name),
|
|
344
|
+
context.container.register(connectionSymbol(conn.name), lazy(() => Service));
|
|
479
345
|
}
|
|
480
346
|
context.container.registerExisting(DI_TOKENS.Database, connectionSymbol(config.default));
|
|
481
347
|
context.logger.info("DatabaseModule initialized");
|
|
@@ -499,9 +365,9 @@ DatabaseModule = _DatabaseModule = __decorate([Module({
|
|
|
499
365
|
//#endregion
|
|
500
366
|
//#region src/database/decorators/inject-db.decorator.ts
|
|
501
367
|
function InjectDB(name) {
|
|
502
|
-
return inject
|
|
368
|
+
return inject(connectionSymbol(name));
|
|
503
369
|
}
|
|
504
370
|
//#endregion
|
|
505
|
-
export { DATABASE_TOKENS,
|
|
371
|
+
export { DATABASE_TOKENS, DatabaseModule, DbGenerateCommand, DbPullCommand, DbPushCommand, ErrorHandlerPlugin, EventEmitterPlugin, InjectDB, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, RecordNotFoundError, SchemaSwitcher, UniqueConstraintError, ZenStackCommand, connectionSymbol, databaseMessages, fromZenStackError };
|
|
506
372
|
|
|
507
373
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["inject"],"sources":["../../src/database/commands/zenstack.command.ts","../../src/database/commands/db-generate.command.ts","../../src/database/commands/db-pull.command.ts","../../src/database/commands/db-push.command.ts","../../src/database/commands/migrate-deploy.command.ts","../../src/database/commands/migrate-dev.command.ts","../../src/database/commands/migrate-reset.command.ts","../../src/database/commands/migrate-status.command.ts","../../src/database/errors/invalid-error-code-range.error.ts","../../src/database/errors/database-error.ts","../../src/database/errors/database-config.error.ts","../../src/database/errors/foreign-key-constraint.error.ts","../../src/database/errors/record-not-found.error.ts","../../src/database/errors/unique-constraint.error.ts","../../src/database/errors/from-zenstack-error.ts","../../src/database/plugins/error-handler.plugin.ts","../../src/database/plugins/event-emitter.plugin.ts","../../src/database/plugins/schema-switcher.ts","../../src/database/database.helpers.ts","../../src/database/database.tokens.ts","../../src/database/i18n/en.ts","../../src/database/database.module.ts","../../src/database/decorators/inject-db.decorator.ts"],"sourcesContent":["import { Command } from 'stratal/quarry'\n\n/**\n * Base command for ZenStack CLI wrappers.\n * Uses execFileSync with array arguments to prevent shell injection.\n */\nexport abstract class ZenStackCommand extends Command {\n protected async zenstack(args: string[]): Promise<number> {\n // Dynamic import — node:child_process is only available in the Quarry CLI (Node) context\n const { execFileSync } = await import('node:child_process')\n\n try {\n const output = execFileSync('npx', ['zenstack', ...args], {\n encoding: 'utf-8',\n stdio: 'pipe',\n })\n if (output) this.info(output.trim())\n return 0\n } catch (err) {\n const error = err as { stderr?: string; stdout?: string; status?: number }\n if (error.stderr) this.error(error.stderr.trim())\n if (error.stdout) this.info(error.stdout.trim())\n return error.status ?? 1\n }\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbGenerateCommand extends ZenStackCommand {\n static command = 'db:generate {--schema= : Path to schema file} {--watch : Enable watch mode}'\n static description = 'Generate ZenStack ORM client'\n\n async handle(): Promise<number> {\n const args = ['generate']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('watch')) args.push('--watch')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbPullCommand extends ZenStackCommand {\n static command = 'db:pull {--schema= : Path to schema file}'\n static description = 'Introspect database and generate schema'\n\n async handle(): Promise<number> {\n const args = ['db', 'pull']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbPushCommand extends ZenStackCommand {\n static command = 'db:push {--schema= : Path to schema file} {--accept-data-loss : Accept data loss} {--force-reset : Force reset database}'\n static description = 'Push database schema changes'\n\n async handle(): Promise<number> {\n const args = ['db', 'push']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('accept-data-loss')) args.push('--accept-data-loss')\n if (this.boolean('force-reset')) args.push('--force-reset')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateDeployCommand extends ZenStackCommand {\n static command = 'migrate:deploy {--schema= : Path to schema file}'\n static description = 'Deploy pending migrations'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'deploy']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateDevCommand extends ZenStackCommand {\n static command = 'migrate:dev {--schema= : Path to schema file} {--name= : Migration name} {--create-only : Create without applying}'\n static description = 'Create and apply migration'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'dev']\n const schema = this.string('schema')\n const name = this.string('name')\n\n if (schema) args.push('--schema', schema)\n if (name) args.push('--name', name)\n if (this.boolean('create-only')) args.push('--create-only')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateResetCommand extends ZenStackCommand {\n static command = 'migrate:reset {--schema= : Path to schema file} {--force : Skip confirmation} {--skip-seed : Skip seeding}'\n static description = 'Reset database'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'reset']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('force')) args.push('--force')\n if (this.boolean('skip-seed')) args.push('--skip-seed')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateStatusCommand extends ZenStackCommand {\n static command = 'migrate:status {--schema= : Path to schema file}'\n static description = 'Check migration status'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'status']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\n/**\n * InvalidErrorCodeRangeError\n *\n * Thrown when a DatabaseError subclass is constructed with an error code\n * outside the valid database error range (2000-2999).\n * This is a developer-facing error to enforce error code conventions.\n */\nexport class InvalidErrorCodeRangeError extends ApplicationError {\n constructor(code: number, expectedRange: string) {\n super(\n 'errors.invalidErrorCodeRange',\n ERROR_CODES.SYSTEM.INVALID_ERROR_CODE_RANGE,\n { code, expectedRange }\n )\n }\n}\n","import type { MessageKeys } from 'stratal/i18n'\nimport { ApplicationError, ERROR_CODES, type ErrorCode } from 'stratal/errors'\nimport { InvalidErrorCodeRangeError } from './invalid-error-code-range.error'\n\n/**\n * DatabaseError\n *\n * Generic database error thrown when a database operation fails\n * and doesn't fit into a more specific error category.\n *\n * This is the base class for all database-related errors.\n */\nexport class DatabaseError extends ApplicationError {\n constructor(\n messageKey: MessageKeys = 'errors.databaseGeneric',\n code: ErrorCode = ERROR_CODES.DATABASE.GENERIC,\n metadata?: Record<string, unknown>\n ) {\n // Validate that code is in the database error range\n if (code < 2000 || code >= 3000) {\n throw new InvalidErrorCodeRangeError(code, '2000-2999')\n }\n\n super(messageKey, code, metadata)\n }\n}\n","import { ERROR_CODES } from 'stratal/errors'\nimport { DatabaseError } from './database-error'\n\nexport class DatabaseConfigError extends DatabaseError {\n constructor(details: string) {\n super('errors.databaseGeneric', ERROR_CODES.DATABASE.GENERIC, { details })\n }\n}\n","import { ERROR_CODES } from 'stratal/errors'\nimport { DatabaseError } from './database-error'\n\n/**\n * ForeignKeyConstraintError\n *\n * Thrown when a database foreign key constraint is violated.\n * This typically occurs when:\n * - Trying to insert a record with a foreign key that doesn't exist\n * - Trying to delete a record that is referenced by other records\n * - Trying to update a foreign key to a non-existent value\n */\nexport class ForeignKeyConstraintError extends DatabaseError {\n constructor(field?: string) {\n super('errors.databaseForeignKeyConstraint', ERROR_CODES.DATABASE.FOREIGN_KEY_CONSTRAINT, {\n field,\n })\n }\n}\n","import { ERROR_CODES } from 'stratal/errors'\nimport { DatabaseError } from './database-error'\n\n/**\n * RecordNotFoundError\n *\n * Generic error thrown when a database record is not found.\n * This is typically thrown when a findUnique or findFirst operation\n * returns null, or when a required record doesn't exist.\n *\n * Services should catch this and optionally refine it to a more specific\n * domain error (e.g., NoteNotFoundError, UserNotFoundError).\n */\nexport class RecordNotFoundError extends DatabaseError {\n constructor(details?: string) {\n super('errors.databaseRecordNotFound', ERROR_CODES.DATABASE.RECORD_NOT_FOUND, {\n details,\n })\n }\n}\n","import { ERROR_CODES } from 'stratal/errors'\nimport { DatabaseError } from './database-error'\n\n/**\n * UniqueConstraintError\n *\n * Thrown when a database unique constraint is violated.\n * This typically occurs when trying to insert or update a record\n * with a value that already exists in a unique column.\n *\n * Services should catch this and optionally refine it to a more specific\n * domain error (e.g., UserEmailAlreadyExistsError).\n */\nexport class UniqueConstraintError extends DatabaseError {\n constructor(fields?: string[]) {\n super('errors.databaseUniqueConstraint', ERROR_CODES.DATABASE.UNIQUE_CONSTRAINT, {\n fields,\n })\n }\n}\n","import { ORMError, ORMErrorReason } from '@zenstackhq/orm'\nimport { ERROR_CODES } from 'stratal/errors'\nimport { DatabaseError } from './database-error'\nimport { ForeignKeyConstraintError } from './foreign-key-constraint.error'\nimport { RecordNotFoundError } from './record-not-found.error'\nimport { UniqueConstraintError } from './unique-constraint.error'\n\n/**\n * Transform ZenStack ORM errors into ApplicationError instances\n *\n * This function maps ORMError codes to generic database error classes.\n * Services can catch these generic errors and optionally refine them to\n * more specific domain errors if needed.\n *\n * @param error - The error thrown by ZenStack ORM\n * @returns An ApplicationError instance\n *\n * @example\n * ```typescript\n * try {\n * await db.user.create({ data: { email: 'existing@example.com' } })\n * } catch (error) {\n * throw fromZenStackError(error) // Becomes UniqueConstraintError or other\n * }\n * ```\n */\nexport function fromZenStackError(error: unknown): DatabaseError {\n // Handle ZenStack ORM Errors\n if (error instanceof ORMError) {\n const ormError = error\n\n switch (ormError.reason) {\n case ORMErrorReason.NOT_FOUND:\n return new RecordNotFoundError(ormError.model)\n\n case ORMErrorReason.DB_QUERY_ERROR:\n // Parse database-specific error codes\n return parseDatabaseError(ormError)\n\n case ORMErrorReason.INVALID_INPUT:\n return new DatabaseError(\n 'errors.databaseInvalidQuery',\n ERROR_CODES.DATABASE.GENERIC,\n { message: ormError.message }\n )\n\n case ORMErrorReason.CONFIG_ERROR:\n return new DatabaseError(\n 'errors.databaseConnectionFailed',\n ERROR_CODES.DATABASE.CONNECTION_FAILED,\n { message: ormError.message }\n )\n\n case ORMErrorReason.NOT_SUPPORTED:\n return new DatabaseError(\n 'errors.databaseGeneric',\n ERROR_CODES.DATABASE.GENERIC,\n { message: ormError.message, reason: 'Operation not supported' }\n )\n\n case ORMErrorReason.INTERNAL_ERROR:\n return new DatabaseError(\n 'errors.databaseGeneric',\n ERROR_CODES.DATABASE.GENERIC,\n { message: ormError.message }\n )\n\n default:\n return new DatabaseError(\n 'errors.databaseGeneric',\n ERROR_CODES.DATABASE.GENERIC,\n { message: ormError.message, reason: ormError.reason }\n )\n }\n }\n\n // Handle unknown errors\n return new DatabaseError(\n 'errors.databaseGeneric',\n ERROR_CODES.DATABASE.GENERIC,\n { originalError: String(error) }\n )\n}\n\n/**\n * Parse database-specific errors from the dbErrorCode field\n */\nfunction parseDatabaseError(error: ORMError): DatabaseError {\n // Cast dbErrorCode to string since ZenStack types it loosely\n const dbErrorCode = error.dbErrorCode as string | undefined\n\n // PostgreSQL error codes\n // https://www.postgresql.org/docs/current/errcodes-appendix.html\n if (dbErrorCode) {\n // Class 23 - Integrity Constraint Violation\n if (dbErrorCode === '23505') {\n // Unique violation\n return new UniqueConstraintError([error.model ?? 'unknown'])\n }\n\n if (dbErrorCode === '23503') {\n // Foreign key violation\n return new ForeignKeyConstraintError(error.model ?? 'unknown')\n }\n\n if (dbErrorCode === '23502') {\n // Not null violation\n return new DatabaseError(\n 'errors.databaseNullConstraint',\n ERROR_CODES.DATABASE.NULL_CONSTRAINT,\n { dbErrorCode, message: error.dbErrorMessage }\n )\n }\n\n if (dbErrorCode === '23514') {\n // Check constraint violation\n return new DatabaseError(\n 'errors.databaseConstraintFailed',\n ERROR_CODES.DATABASE.GENERIC,\n { dbErrorCode, message: error.dbErrorMessage }\n )\n }\n\n // Class 42 - Syntax Error or Access Rule Violation\n if (dbErrorCode.startsWith('42')) {\n if (dbErrorCode === '42P01') {\n // Undefined table\n return new DatabaseError(\n 'errors.databaseTableNotFound',\n ERROR_CODES.DATABASE.GENERIC,\n { dbErrorCode, message: error.dbErrorMessage }\n )\n }\n\n if (dbErrorCode === '42703') {\n // Undefined column\n return new DatabaseError(\n 'errors.databaseColumnNotFound',\n ERROR_CODES.DATABASE.GENERIC,\n { dbErrorCode, message: error.dbErrorMessage }\n )\n }\n }\n\n // Class 08 - Connection Exception\n if (dbErrorCode.startsWith('08')) {\n return new DatabaseError(\n 'errors.databaseConnectionFailed',\n ERROR_CODES.DATABASE.CONNECTION_FAILED,\n { dbErrorCode, message: error.dbErrorMessage }\n )\n }\n\n // Class 57 - Operator Intervention\n if (dbErrorCode === '57014') {\n // Query cancelled\n return new DatabaseError(\n 'errors.databaseTimeout',\n ERROR_CODES.DATABASE.TIMEOUT,\n { dbErrorCode, message: error.dbErrorMessage }\n )\n }\n\n // Class 40 - Transaction Rollback\n if (dbErrorCode.startsWith('40')) {\n return new DatabaseError(\n 'errors.databaseTransactionConflict',\n ERROR_CODES.DATABASE.TRANSACTION_CONFLICT,\n { dbErrorCode, message: error.dbErrorMessage }\n )\n }\n\n // Class 53 - Insufficient Resources\n if (dbErrorCode === '53300') {\n // Too many connections\n return new DatabaseError(\n 'errors.databaseTooManyConnections',\n ERROR_CODES.DATABASE.TOO_MANY_CONNECTIONS,\n { dbErrorCode, message: error.dbErrorMessage }\n )\n }\n }\n\n // Default database error\n return new DatabaseError(\n 'errors.databaseGeneric',\n ERROR_CODES.DATABASE.GENERIC,\n {\n dbErrorCode,\n dbErrorMessage: error.dbErrorMessage,\n sql: error.sql,\n }\n )\n}\n","import { type RuntimePlugin } from '@zenstackhq/orm'\nimport { type SchemaDef } from '@zenstackhq/orm/schema'\nimport { fromZenStackError } from '../errors'\n\n/**\n * ZenStack runtime plugin that transforms ORM errors into ApplicationError instances.\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [new ErrorHandlerPlugin()]\n * })\n * ```\n */\nexport class ErrorHandlerPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'error-handler'\n\n onQuery = async ({ args, proceed }: {\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n }): Promise<unknown> => {\n try {\n return await proceed(args)\n } catch (error) {\n throw fromZenStackError(error)\n }\n }\n}\n","import { type RuntimePlugin } from '@zenstackhq/orm'\nimport { type SchemaDef } from '@zenstackhq/orm/schema'\nimport type { EventName, IEventRegistry } from 'stratal/events'\n\nexport interface EventEmitterPluginOptions {\n eventRegistry: IEventRegistry\n}\n\n/**\n * ZenStack runtime plugin that emits before/after events for database operations.\n *\n * Emits events in the format:\n * - `before.{Model}.{operation}` - Before the database operation\n * - `after.{Model}.{operation}` - After the database operation\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [\n * new EventEmitterPlugin({\n * eventRegistry,\n * })\n * ]\n * })\n * ```\n */\nexport class EventEmitterPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'event-emitter'\n\n constructor(private options: EventEmitterPluginOptions) { }\n\n onQuery = async ({ model, operation, args, proceed }: {\n model: string\n operation: string\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n }): Promise<unknown> => {\n const { eventRegistry } = this.options\n const eventBase = `${model}.${operation}`\n\n // Emit BEFORE event\n await eventRegistry.emit(`before.${eventBase}` as EventName, {\n data: args,\n })\n\n // Execute the actual database operation\n const result = await proceed(args)\n\n // Emit AFTER event\n await eventRegistry.emit(`after.${eventBase}` as EventName, {\n data: args,\n result,\n })\n\n return result\n }\n}\n","interface SwitchableClient {\n $schema: { provider: { defaultSchema: string } } & Record<string, unknown>\n schema: unknown\n}\n\n/**\n * Switches the active schema on a ZenStack/Kysely database client by mutating\n * `$schema.provider.defaultSchema`. This causes ZenStack's QueryNameMapper to\n * generate fully-qualified table references (e.g. `\"tenant_123\".\"User\"`).\n *\n * Must be called BEFORE any queries are made on the client.\n *\n * Note: The ZenStack RuntimePlugin `onQuery` hook fires after table names are\n * already resolved, so a plugin-based approach cannot set the schema prefix.\n * Direct client mutation is the only supported method.\n */\nexport class SchemaSwitcher {\n static apply<T>(client: T, schemaName: string): T {\n const c = client as unknown as SwitchableClient\n const switched = {\n ...c.$schema,\n provider: { ...c.$schema.provider, defaultSchema: schemaName },\n }\n c.$schema = switched\n c.schema = switched\n return client\n }\n}\n","import { ZenStackClient, type AnyPlugin } from '@zenstackhq/orm'\nimport { Transient } from 'stratal/di'\nimport type { IEventRegistry } from 'stratal/events'\nimport { withI18n, z } from 'stratal/validation'\nimport type { DatabaseConnectionConfig } from './database.module'\nimport { ErrorHandlerPlugin, EventEmitterPlugin } from './plugins'\n\nconst databaseConnectionSchema = z.object({\n name: z.string().min(1, withI18n('database.connectionNameRequired')),\n schema: z.object({}).loose(),\n dialect: z.function(),\n plugins: z.array(z.object({}).loose()).optional(),\n computedFields: z.object({}).loose().optional(),\n})\n\nexport const databaseModuleConfigSchema = z.object({\n default: z.string().min(1, withI18n('database.defaultConnectionRequired')),\n connections: z.array(databaseConnectionSchema).min(1, withI18n('database.connectionRequired')),\n}).refine(\n (config) => {\n const names = config.connections.map(c => c.name)\n return new Set(names).size === names.length\n },\n withI18n('database.duplicateConnections')\n).refine(\n (config) => config.connections.some(c => c.name === config.default),\n withI18n('database.defaultConnectionNotFound')\n)\n\nexport function createDatabaseService(\n conn: DatabaseConnectionConfig,\n eventRegistry: IEventRegistry,\n): new () => InstanceType<typeof ZenStackClient> {\n const plugins: AnyPlugin[] = [\n new ErrorHandlerPlugin(),\n new EventEmitterPlugin({\n eventRegistry,\n }),\n ...(conn.plugins ?? []),\n ]\n\n @Transient()\n class DatabaseClient extends ZenStackClient<typeof conn.schema> {\n constructor() {\n const dialect = conn.dialect()\n // ZenStack 3+ requires `computedFields` whenever the schema declares any\n // `@computed` fields, so pass them through when the consumer provides them.\n super(conn.schema, {\n dialect,\n plugins,\n // @ts-expect-error - ZenStack 3+ requires `computedFields` whenever the schema declares any `@computed` fields, so pass them through when the consumer provides them.\n computedFields: conn.computedFields\n })\n }\n }\n\n return DatabaseClient\n}\n","export const DATABASE_TOKENS = {\n Options: Symbol.for('stratal:database:options'),\n Services: Symbol.for('stratal:database:services'),\n} as const\n\nimport type { ConnectionName } from './types'\n\nexport function connectionSymbol(name: ConnectionName): symbol {\n return Symbol.for(`stratal:database:connection:${name}`)\n}\n","export const databaseMessages = {\n en: {\n connectionNameRequired: 'Connection name is required',\n defaultConnectionRequired: 'Default connection name is required',\n connectionRequired: 'At least one connection is required',\n duplicateConnections: 'Duplicate connection names found',\n defaultConnectionNotFound: 'Default connection not found in connections',\n },\n} as const\n\ndeclare module 'stratal/i18n' {\n interface AppMessageNamespaces {\n database: typeof databaseMessages['en']\n }\n}\n","import type { AnyPlugin, ClientOptions, ComputedFieldsOptions } from '@zenstackhq/orm'\nimport type { SchemaDef } from '@zenstackhq/schema'\nimport { delay, DI_TOKENS, Scope } from 'stratal/di'\nimport type { IEventRegistry } from 'stratal/events'\nimport { I18nModule } from 'stratal/i18n'\nimport {\n Module,\n type AsyncModuleOptions,\n type DynamicModule,\n type InjectionToken,\n type ModuleContext,\n type OnInitialize,\n type OnShutdown,\n} from 'stratal/module'\nimport { DbGenerateCommand } from './commands/db-generate.command'\nimport { DbPullCommand } from './commands/db-pull.command'\nimport { DbPushCommand } from './commands/db-push.command'\nimport { MigrateDeployCommand } from './commands/migrate-deploy.command'\nimport { MigrateDevCommand } from './commands/migrate-dev.command'\nimport { MigrateResetCommand } from './commands/migrate-reset.command'\nimport { MigrateStatusCommand } from './commands/migrate-status.command'\nimport { createDatabaseService } from './database.helpers'\nimport { connectionSymbol, DATABASE_TOKENS } from './database.tokens'\nimport { databaseMessages } from './i18n'\nimport type { ConnectionName, DefaultConnectionName } from './types'\n\nexport interface DatabaseConnectionConfig<\n Schema extends SchemaDef = SchemaDef,\n Name extends ConnectionName = ConnectionName,\n> {\n name: Name\n schema: Schema\n dialect: () => ClientOptions<SchemaDef>['dialect']\n plugins?: AnyPlugin[]\n /**\n * Schema-level @computed field implementations. Required when the schema\n * declares any `@computed` fields. Keyed by uncapitalized model name; values\n * map field name to a Kysely-expression compute callback.\n */\n computedFields?: ComputedFieldsOptions<Schema>\n}\n\nexport interface DatabaseModuleConfig {\n default: DefaultConnectionName\n connections: DatabaseConnectionConfig[]\n}\n\n@Module({\n imports: [\n I18nModule.registerMessages({ en: { database: databaseMessages.en } }),\n ],\n providers: [\n DbGenerateCommand,\n DbPushCommand,\n DbPullCommand,\n MigrateDevCommand,\n MigrateDeployCommand,\n MigrateStatusCommand,\n MigrateResetCommand,\n ],\n})\nexport class DatabaseModule implements OnInitialize, OnShutdown {\n static forRoot(config: DatabaseModuleConfig): DynamicModule {\n return {\n module: DatabaseModule,\n providers: [\n { provide: DATABASE_TOKENS.Options, useValue: config as unknown as object },\n ],\n }\n }\n\n static forRootAsync(options: AsyncModuleOptions<DatabaseModuleConfig>): DynamicModule {\n return {\n module: DatabaseModule,\n providers: [\n {\n provide: DATABASE_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n\n onInitialize(context: ModuleContext): void {\n const config = context.container.resolve<DatabaseModuleConfig>(DATABASE_TOKENS.Options)\n const eventRegistry = context.container.resolve<IEventRegistry>(DI_TOKENS.EventRegistry)\n const container = context.container.getTsyringeContainer();\n\n for (const conn of config.connections) {\n const Service = createDatabaseService(conn, eventRegistry);\n\n container.register(connectionSymbol(conn.name) as InjectionToken<symbol>,\n // @ts-expect-error Dynamic class type mismatch\n delay(() => Service),\n { lifecycle: Scope.Request })\n }\n\n context.container.registerExisting(DI_TOKENS.Database, connectionSymbol(config.default))\n\n context.logger.info('DatabaseModule initialized')\n }\n\n onShutdown(context: ModuleContext): void {\n context.logger.info('DatabaseModule shutdown')\n }\n}\n","import { inject } from 'tsyringe'\nimport type { ConnectionName } from '../types'\nimport { connectionSymbol } from '../database.tokens'\n\nexport function InjectDB(name: ConnectionName): ParameterDecorator {\n return inject(connectionSymbol(name))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/database/commands/zenstack.command.ts","../../src/database/commands/db-generate.command.ts","../../src/database/commands/db-pull.command.ts","../../src/database/commands/db-push.command.ts","../../src/database/commands/migrate-deploy.command.ts","../../src/database/commands/migrate-dev.command.ts","../../src/database/commands/migrate-reset.command.ts","../../src/database/commands/migrate-status.command.ts","../../src/database/errors/record-not-found.error.ts","../../src/database/errors/unique-constraint.error.ts","../../src/database/errors/from-zenstack-error.ts","../../src/database/plugins/error-handler.plugin.ts","../../src/database/plugins/event-emitter.plugin.ts","../../src/database/plugins/schema-switcher.ts","../../src/database/database.helpers.ts","../../src/database/database.tokens.ts","../../src/database/i18n/en.ts","../../src/database/database.module.ts","../../src/database/decorators/inject-db.decorator.ts"],"sourcesContent":["import { Command } from 'stratal/quarry'\n\n/**\n * Base command for ZenStack CLI wrappers.\n * Uses execFileSync with array arguments to prevent shell injection.\n */\nexport abstract class ZenStackCommand extends Command {\n protected async zenstack(args: string[]): Promise<number> {\n // Dynamic import — node:child_process is only available in the Quarry CLI (Node) context\n const { execFileSync } = await import('node:child_process')\n\n try {\n const output = execFileSync('npx', ['zenstack', ...args], {\n encoding: 'utf-8',\n stdio: 'pipe',\n })\n if (output) this.info(output.trim())\n return 0\n } catch (err) {\n const error = err as { stderr?: string; stdout?: string; status?: number }\n if (error.stderr) this.error(error.stderr.trim())\n if (error.stdout) this.info(error.stdout.trim())\n return error.status ?? 1\n }\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbGenerateCommand extends ZenStackCommand {\n static command = 'db:generate {--schema= : Path to schema file} {--watch : Enable watch mode}'\n static description = 'Generate ZenStack ORM client'\n\n async handle(): Promise<number> {\n const args = ['generate']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('watch')) args.push('--watch')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbPullCommand extends ZenStackCommand {\n static command = 'db:pull {--schema= : Path to schema file}'\n static description = 'Introspect database and generate schema'\n\n async handle(): Promise<number> {\n const args = ['db', 'pull']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbPushCommand extends ZenStackCommand {\n static command = 'db:push {--schema= : Path to schema file} {--accept-data-loss : Accept data loss} {--force-reset : Force reset database}'\n static description = 'Push database schema changes'\n\n async handle(): Promise<number> {\n const args = ['db', 'push']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('accept-data-loss')) args.push('--accept-data-loss')\n if (this.boolean('force-reset')) args.push('--force-reset')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateDeployCommand extends ZenStackCommand {\n static command = 'migrate:deploy {--schema= : Path to schema file}'\n static description = 'Deploy pending migrations'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'deploy']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateDevCommand extends ZenStackCommand {\n static command = 'migrate:dev {--schema= : Path to schema file} {--name= : Migration name} {--create-only : Create without applying}'\n static description = 'Create and apply migration'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'dev']\n const schema = this.string('schema')\n const name = this.string('name')\n\n if (schema) args.push('--schema', schema)\n if (name) args.push('--name', name)\n if (this.boolean('create-only')) args.push('--create-only')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateResetCommand extends ZenStackCommand {\n static command = 'migrate:reset {--schema= : Path to schema file} {--force : Skip confirmation} {--skip-seed : Skip seeding}'\n static description = 'Reset database'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'reset']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('force')) args.push('--force')\n if (this.boolean('skip-seed')) args.push('--skip-seed')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateStatusCommand extends ZenStackCommand {\n static command = 'migrate:status {--schema= : Path to schema file}'\n static description = 'Check migration status'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'status']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class RecordNotFoundError extends HttpException {\n constructor(public readonly details?: string, cause?: unknown) {\n super(404, 'Record not found', cause)\n }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UniqueConstraintError extends HttpException {\n constructor(public readonly fields?: string[], cause?: unknown) {\n super(409, 'Record already exists', cause)\n }\n}\n","import { ORMError, ORMErrorReason } from '@zenstackhq/orm';\nimport { type ApplicationError, DatabaseError } from 'stratal/errors';\nimport { RecordNotFoundError } from './record-not-found.error';\nimport { UniqueConstraintError } from './unique-constraint.error';\n\nexport function fromZenStackError(error: unknown): ApplicationError {\n if (error instanceof ORMError) {\n switch (error.reason) {\n case ORMErrorReason.NOT_FOUND:\n return new RecordNotFoundError(error.model, error)\n case ORMErrorReason.DB_QUERY_ERROR:\n return parseDatabaseError(error)\n case ORMErrorReason.INVALID_INPUT:\n return new DatabaseError('Invalid database query', error)\n case ORMErrorReason.CONFIG_ERROR:\n return new DatabaseError('Database configuration error', error)\n case ORMErrorReason.NOT_SUPPORTED:\n return new DatabaseError('Operation not supported', error)\n case ORMErrorReason.INTERNAL_ERROR:\n return new DatabaseError('Database internal error', error)\n default:\n return new DatabaseError('Database error', error)\n }\n }\n return new DatabaseError('Database error', error)\n}\n\nfunction parseDatabaseError(error: ORMError): ApplicationError {\n const dbErrorCode = error.dbErrorCode as string | undefined\n if (dbErrorCode) {\n if (dbErrorCode === '23505') return new UniqueConstraintError([error.model ?? 'unknown'], error)\n if (dbErrorCode === '23503') return new DatabaseError('Foreign key constraint violation', error)\n if (dbErrorCode === '23502') return new DatabaseError('Required field is missing', error)\n if (dbErrorCode === '23514') return new DatabaseError('Database constraint violated', error)\n if (dbErrorCode === '42P01') return new DatabaseError('Table does not exist', error)\n if (dbErrorCode === '42703') return new DatabaseError('Column does not exist', error)\n if (dbErrorCode.startsWith('42')) return new DatabaseError('Database syntax or access error', error)\n if (dbErrorCode.startsWith('08')) return new DatabaseError('Database connection failed', error)\n if (dbErrorCode === '57014') return new DatabaseError('Database query timeout', error)\n if (dbErrorCode.startsWith('40')) return new DatabaseError('Transaction conflict or deadlock', error)\n if (dbErrorCode === '53300') return new DatabaseError('Too many database connections', error)\n }\n return new DatabaseError('Database error', error)\n}\n","import { type RuntimePlugin } from '@zenstackhq/orm'\nimport { type SchemaDef } from '@zenstackhq/orm/schema'\nimport { fromZenStackError } from '../errors'\n\n/**\n * ZenStack runtime plugin that transforms ORM errors into ApplicationError instances.\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [new ErrorHandlerPlugin()]\n * })\n * ```\n */\nexport class ErrorHandlerPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'error-handler'\n\n onQuery = async ({ args, proceed }: {\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n }): Promise<unknown> => {\n try {\n return await proceed(args)\n } catch (error) {\n throw fromZenStackError(error)\n }\n }\n}\n","import { type RuntimePlugin } from '@zenstackhq/orm'\nimport { type SchemaDef } from '@zenstackhq/orm/schema'\nimport type { EventName, IEventRegistry } from 'stratal/events'\n\nexport interface EventEmitterPluginOptions {\n eventRegistry: IEventRegistry\n}\n\n/**\n * ZenStack runtime plugin that emits before/after events for database operations.\n *\n * Emits events in the format:\n * - `before.{Model}.{operation}` - Before the database operation\n * - `after.{Model}.{operation}` - After the database operation\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [\n * new EventEmitterPlugin({\n * eventRegistry,\n * })\n * ]\n * })\n * ```\n */\nexport class EventEmitterPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'event-emitter'\n\n constructor(private options: EventEmitterPluginOptions) { }\n\n onQuery = async ({ model, operation, args, proceed }: {\n model: string\n operation: string\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n }): Promise<unknown> => {\n const { eventRegistry } = this.options\n const eventBase = `${model}.${operation}`\n\n // Emit BEFORE event\n await eventRegistry.emit(`before.${eventBase}` as EventName, {\n data: args,\n })\n\n // Execute the actual database operation\n const result = await proceed(args)\n\n // Emit AFTER event\n await eventRegistry.emit(`after.${eventBase}` as EventName, {\n data: args,\n result,\n })\n\n return result\n }\n}\n","interface SwitchableClient {\n $schema: { provider: { defaultSchema: string } } & Record<string, unknown>\n schema: unknown\n}\n\n/**\n * Switches the active schema on a ZenStack/Kysely database client by mutating\n * `$schema.provider.defaultSchema`. This causes ZenStack's QueryNameMapper to\n * generate fully-qualified table references (e.g. `\"tenant_123\".\"User\"`).\n *\n * Must be called BEFORE any queries are made on the client.\n *\n * Note: The ZenStack RuntimePlugin `onQuery` hook fires after table names are\n * already resolved, so a plugin-based approach cannot set the schema prefix.\n * Direct client mutation is the only supported method.\n */\nexport class SchemaSwitcher {\n static apply<T>(client: T, schemaName: string): T {\n const c = client as unknown as SwitchableClient\n const switched = {\n ...c.$schema,\n provider: { ...c.$schema.provider, defaultSchema: schemaName },\n }\n c.$schema = switched\n c.schema = switched\n return client\n }\n}\n","import { ZenStackClient, type AnyPlugin } from '@zenstackhq/orm'\nimport { Transient } from 'stratal/di'\nimport type { IEventRegistry } from 'stratal/events'\nimport { withZodI18n, z } from 'stratal/validation'\nimport type { DatabaseConnectionConfig } from './database.module'\nimport { ErrorHandlerPlugin, EventEmitterPlugin } from './plugins'\n\nconst databaseConnectionSchema = z.object({\n name: z.string().min(1, withZodI18n('database.connectionNameRequired')),\n schema: z.object({}).loose(),\n dialect: z.function(),\n plugins: z.array(z.object({}).loose()).optional(),\n computedFields: z.object({}).loose().optional(),\n})\n\nexport const databaseModuleConfigSchema = z.object({\n default: z.string().min(1, withZodI18n('database.defaultConnectionRequired')),\n connections: z.array(databaseConnectionSchema).min(1, withZodI18n('database.connectionRequired')),\n}).refine(\n (config) => {\n const names = config.connections.map(c => c.name)\n return new Set(names).size === names.length\n },\n withZodI18n('database.duplicateConnections')\n).refine(\n (config) => config.connections.some(c => c.name === config.default),\n withZodI18n('database.defaultConnectionNotFound')\n)\n\nexport function createDatabaseService(\n conn: DatabaseConnectionConfig,\n eventRegistry: IEventRegistry,\n): new () => InstanceType<typeof ZenStackClient> {\n const plugins: AnyPlugin[] = [\n new ErrorHandlerPlugin(),\n new EventEmitterPlugin({\n eventRegistry,\n }),\n ...(conn.plugins ?? []),\n ]\n\n @Transient()\n class DatabaseClient extends ZenStackClient<typeof conn.schema> {\n constructor() {\n const dialect = conn.dialect()\n // ZenStack 3+ requires `computedFields` whenever the schema declares any\n // `@computed` fields, so pass them through when the consumer provides them.\n super(conn.schema, {\n dialect,\n plugins,\n // @ts-expect-error - ZenStack 3+ requires `computedFields` whenever the schema declares any `@computed` fields, so pass them through when the consumer provides them.\n computedFields: conn.computedFields\n })\n }\n }\n\n return DatabaseClient\n}\n","export const DATABASE_TOKENS = {\n Options: Symbol.for('stratal:database:options'),\n Services: Symbol.for('stratal:database:services'),\n} as const\n\nimport type { ConnectionName } from './types'\n\nexport function connectionSymbol(name: ConnectionName): symbol {\n return Symbol.for(`stratal:database:connection:${name}`)\n}\n","export const databaseMessages = {\n en: {\n connectionNameRequired: 'Connection name is required',\n defaultConnectionRequired: 'Default connection name is required',\n connectionRequired: 'At least one connection is required',\n duplicateConnections: 'Duplicate connection names found',\n defaultConnectionNotFound: 'Default connection not found in connections',\n },\n} as const\n\ndeclare module 'stratal/i18n' {\n interface AppMessageNamespaces {\n database: typeof databaseMessages['en']\n }\n}\n","import type { AnyPlugin, ClientOptions, ComputedFieldsOptions } from '@zenstackhq/orm';\nimport type { SchemaDef } from '@zenstackhq/schema';\nimport { DI_TOKENS, lazy } from 'stratal/di';\nimport type { IEventRegistry } from 'stratal/events';\nimport { I18nModule } from 'stratal/i18n';\nimport {\n Module,\n type AsyncModuleOptions,\n type DynamicModule,\n type ModuleContext,\n type OnInitialize,\n type OnShutdown,\n} from 'stratal/module';\nimport { DbGenerateCommand } from './commands/db-generate.command';\nimport { DbPullCommand } from './commands/db-pull.command';\nimport { DbPushCommand } from './commands/db-push.command';\nimport { MigrateDeployCommand } from './commands/migrate-deploy.command';\nimport { MigrateDevCommand } from './commands/migrate-dev.command';\nimport { MigrateResetCommand } from './commands/migrate-reset.command';\nimport { MigrateStatusCommand } from './commands/migrate-status.command';\nimport { createDatabaseService } from './database.helpers';\nimport { connectionSymbol, DATABASE_TOKENS } from './database.tokens';\nimport { databaseMessages } from './i18n';\nimport type { ConnectionName, DefaultConnectionName } from './types';\n\nexport interface DatabaseConnectionConfig<\n Schema extends SchemaDef = SchemaDef,\n Name extends ConnectionName = ConnectionName,\n> {\n name: Name\n schema: Schema\n dialect: () => ClientOptions<SchemaDef>['dialect']\n plugins?: AnyPlugin[]\n /**\n * Schema-level @computed field implementations. Required when the schema\n * declares any `@computed` fields. Keyed by uncapitalized model name; values\n * map field name to a Kysely-expression compute callback.\n */\n computedFields?: ComputedFieldsOptions<Schema>\n}\n\nexport interface DatabaseModuleConfig {\n default: DefaultConnectionName\n connections: DatabaseConnectionConfig[]\n}\n\n@Module({\n imports: [\n I18nModule.registerMessages({ en: { database: databaseMessages.en } }),\n ],\n providers: [\n DbGenerateCommand,\n DbPushCommand,\n DbPullCommand,\n MigrateDevCommand,\n MigrateDeployCommand,\n MigrateStatusCommand,\n MigrateResetCommand,\n ],\n})\nexport class DatabaseModule implements OnInitialize, OnShutdown {\n static forRoot(config: DatabaseModuleConfig): DynamicModule {\n return {\n module: DatabaseModule,\n providers: [\n { provide: DATABASE_TOKENS.Options, useValue: config as unknown as object },\n ],\n }\n }\n\n static forRootAsync(options: AsyncModuleOptions<DatabaseModuleConfig>): DynamicModule {\n return {\n module: DatabaseModule,\n providers: [\n {\n provide: DATABASE_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n\n onInitialize(context: ModuleContext): void {\n const config = context.container.resolve<DatabaseModuleConfig>(DATABASE_TOKENS.Options)\n const eventRegistry = context.container.resolve<IEventRegistry>(DI_TOKENS.EventRegistry)\n for (const conn of config.connections) {\n const Service = createDatabaseService(conn, eventRegistry);\n\n context.container.register(connectionSymbol(conn.name), lazy(() => Service))\n }\n\n context.container.registerExisting(DI_TOKENS.Database, connectionSymbol(config.default))\n\n context.logger.info('DatabaseModule initialized')\n }\n\n onShutdown(context: ModuleContext): void {\n context.logger.info('DatabaseModule shutdown')\n }\n}\n","import { inject } from 'stratal/di'\nimport type { ConnectionName } from '../types'\nimport { connectionSymbol } from '../database.tokens'\n\nexport function InjectDB(name: ConnectionName): ParameterDecorator {\n return inject(connectionSymbol(name))\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
1
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/decorate.js
|
|
2
2
|
function __decorate(decorators, target, key, desc) {
|
|
3
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -6,7 +6,7 @@ const AC_TOKENS = {
|
|
|
6
6
|
Options: Symbol.for("stratal:ac:options")
|
|
7
7
|
};
|
|
8
8
|
//#endregion
|
|
9
|
-
//#region \0@oxc-project+runtime@0.
|
|
9
|
+
//#region \0@oxc-project+runtime@0.129.0/helpers/decorateParam.js
|
|
10
10
|
function __decorateParam(paramIndex, decorator) {
|
|
11
11
|
return function(target, key) {
|
|
12
12
|
decorator(target, key, paramIndex);
|
|
@@ -15,4 +15,4 @@ function __decorateParam(paramIndex, decorator) {
|
|
|
15
15
|
//#endregion
|
|
16
16
|
export { AC_TOKENS as n, __decorateParam as t };
|
|
17
17
|
|
|
18
|
-
//# sourceMappingURL=decorateParam-
|
|
18
|
+
//# sourceMappingURL=decorateParam-C_dJ_dIO.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorateParam-
|
|
1
|
+
{"version":3,"file":"decorateParam-C_dJ_dIO.mjs","names":[],"sources":["../src/access-control/tokens.ts"],"sourcesContent":["export const AC_TOKENS = {\n /** Request-scoped access service */\n AccessService: Symbol.for('stratal:ac:service'),\n /** Access control module options (ac, roles) */\n Options: Symbol.for('stratal:ac:options'),\n} as const\n"],"mappings":";AAAA,MAAa,YAAY;;CAEvB,eAAe,OAAO,IAAI,qBAAqB;;CAE/C,SAAS,OAAO,IAAI,qBAAqB;CAC1C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HttpException } from "stratal/errors";
|
|
2
|
+
//#region src/context/errors/user-not-authenticated.error.ts
|
|
3
|
+
var UserNotAuthenticatedError = class extends HttpException {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(401, "User is not authenticated");
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/context/errors/user-not-authorized.error.ts
|
|
10
|
+
var UserNotAuthorizedError = class extends HttpException {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(403, "Unauthorized");
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { UserNotAuthenticatedError as n, UserNotAuthorizedError as t };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=errors-MCyrn_V2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-MCyrn_V2.mjs","names":[],"sources":["../src/context/errors/user-not-authenticated.error.ts","../src/context/errors/user-not-authorized.error.ts"],"sourcesContent":["import { HttpException } from 'stratal/errors'\n\nexport class UserNotAuthenticatedError extends HttpException {\n constructor() { super(401, 'User is not authenticated') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UserNotAuthorizedError extends HttpException {\n constructor() { super(403, 'Unauthorized') }\n}\n"],"mappings":";;AAEA,IAAa,4BAAb,cAA+C,cAAc;CAC3D,cAAc;EAAE,MAAM,KAAK,4BAA4B;;;;;ACDzD,IAAa,yBAAb,cAA4C,cAAc;CACxD,cAAc;EAAE,MAAM,KAAK,eAAe"}
|
package/dist/factory/index.d.mts
CHANGED
package/dist/factory/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/factory/factory.ts","../../src/factory/sequence.ts"],"sourcesContent":["import { faker, type Faker } from '@faker-js/faker'\nimport type { DatabaseService } from '../database'\n\n/**\n * Factory\n *\n * Abstract base class for creating test data.\n * Integrates with Faker.js for data generation.\n *\n * @example Define a factory\n * ```typescript\n * import { Factory } from '@stratal/framework/factory'\n * import type { User, UserCreateInput } from '@your-app/db'\n *\n * export class UserFactory extends Factory<User, UserCreateInput> {\n * protected model = 'user'\n *\n * protected definition(): UserCreateInput {\n * return {\n * email: this.faker.internet.email(),\n * firstName: this.faker.person.firstName(),\n * lastName: this.faker.person.lastName(),\n * emailVerified: true,\n * }\n * }\n *\n * admin() {\n * return this.state(attrs => ({ ...attrs, role: 'admin' }))\n * }\n * }\n * ```\n *\n * @example Usage\n * ```typescript\n * const user = await new UserFactory().create(ctx.db)\n * const admin = await new UserFactory().admin().create(ctx.db)\n * const users = await new UserFactory().count(10).createManyAndReturn(ctx.db)\n * ```\n */\nexport abstract class Factory<TModel, TCreateInput> {\n protected readonly faker: Faker = faker\n protected abstract model: string\n protected abstract definition(): TCreateInput\n\n private states: ((attrs: TCreateInput) => Partial<TCreateInput>)[] = []\n private _count = 1\n\n state(modifier: (attrs: TCreateInput) => Partial<TCreateInput>): this {\n const clone = this.clone()\n clone.states.push(modifier)\n return clone\n }\n\n count(n: number): this {\n const clone = this.clone()\n clone._count = n\n return clone\n }\n\n make(): TCreateInput {\n let attrs = this.definition()\n for (const modifier of this.states) {\n attrs = { ...attrs, ...modifier(attrs) }\n }\n return attrs\n }\n\n makeMany(count?: number): TCreateInput[] {\n const n = count ?? this._count\n return Array.from({ length: n }, () => this.make())\n }\n\n async create(db: DatabaseService): Promise<TModel> {\n const data = this.make()\n const model = (db as unknown as Record<string, { create: (args: { data: TCreateInput }) => Promise<TModel> }>)[this.model]\n return model.create({ data })\n }\n\n async createMany(db: DatabaseService, count?: number): Promise<{ count: number }> {\n const data = this.makeMany(count)\n const model = (db as unknown as Record<string, { createMany: (args: { data: TCreateInput[] }) => Promise<{ count: number }> }>)[this.model]\n return model.createMany({ data })\n }\n\n async createManyAndReturn(db: DatabaseService, count?: number): Promise<TModel[]> {\n const data = this.makeMany(count)\n const model = (db as unknown as Record<string, { createManyAndReturn: (args: { data: TCreateInput[] }) => Promise<TModel[]> }>)[this.model]\n return model.createManyAndReturn({ data })\n }\n\n protected clone(): this {\n const FactoryClass = this.constructor as new () => this\n const clone = new FactoryClass()\n clone.states = [...this.states]\n clone._count = this._count\n return clone\n }\n}\n","/**\n * Sequence\n *\n * Auto-incrementing sequence generator for creating unique values.\n *\n * @example Basic usage\n * ```typescript\n * const emailSeq = new Sequence((n) => `user${n}@example.com`)\n *\n * emailSeq.next() // 'user1@example.com'\n * emailSeq.next() // 'user2@example.com'\n * emailSeq.reset()\n * emailSeq.next() // 'user1@example.com'\n * ```\n *\n * @example With factory\n * ```typescript\n * const orderSeq = new Sequence((n) => `ORD-${String(n).padStart(6, '0')}`)\n *\n * export class OrderFactory extends Factory<Order, OrderCreateInput> {\n * protected definition() {\n * return {\n * orderNumber: orderSeq.next(),\n * // ...\n * }\n * }\n * }\n * ```\n */\nexport class Sequence<T = number> {\n private current = 0\n\n constructor(private readonly generator?: (n: number) => T) {}\n\n next(): T {\n this.current++\n if (this.generator) {\n return this.generator(this.current)\n }\n return this.current as T\n }\n\n peek(): T {\n const value = this.current + 1\n if (this.generator) {\n return this.generator(value)\n }\n return value as T\n }\n\n reset(): void {\n this.current = 0\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAsB,UAAtB,MAAoD;CAClD,QAAkC;CAIlC,SAAqE,EAAE;CACvE,SAAiB;CAEjB,MAAM,UAAgE;EACpE,MAAM,QAAQ,KAAK,OAAO;
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/factory/factory.ts","../../src/factory/sequence.ts"],"sourcesContent":["import { faker, type Faker } from '@faker-js/faker'\nimport type { DatabaseService } from '../database'\n\n/**\n * Factory\n *\n * Abstract base class for creating test data.\n * Integrates with Faker.js for data generation.\n *\n * @example Define a factory\n * ```typescript\n * import { Factory } from '@stratal/framework/factory'\n * import type { User, UserCreateInput } from '@your-app/db'\n *\n * export class UserFactory extends Factory<User, UserCreateInput> {\n * protected model = 'user'\n *\n * protected definition(): UserCreateInput {\n * return {\n * email: this.faker.internet.email(),\n * firstName: this.faker.person.firstName(),\n * lastName: this.faker.person.lastName(),\n * emailVerified: true,\n * }\n * }\n *\n * admin() {\n * return this.state(attrs => ({ ...attrs, role: 'admin' }))\n * }\n * }\n * ```\n *\n * @example Usage\n * ```typescript\n * const user = await new UserFactory().create(ctx.db)\n * const admin = await new UserFactory().admin().create(ctx.db)\n * const users = await new UserFactory().count(10).createManyAndReturn(ctx.db)\n * ```\n */\nexport abstract class Factory<TModel, TCreateInput> {\n protected readonly faker: Faker = faker\n protected abstract model: string\n protected abstract definition(): TCreateInput\n\n private states: ((attrs: TCreateInput) => Partial<TCreateInput>)[] = []\n private _count = 1\n\n state(modifier: (attrs: TCreateInput) => Partial<TCreateInput>): this {\n const clone = this.clone()\n clone.states.push(modifier)\n return clone\n }\n\n count(n: number): this {\n const clone = this.clone()\n clone._count = n\n return clone\n }\n\n make(): TCreateInput {\n let attrs = this.definition()\n for (const modifier of this.states) {\n attrs = { ...attrs, ...modifier(attrs) }\n }\n return attrs\n }\n\n makeMany(count?: number): TCreateInput[] {\n const n = count ?? this._count\n return Array.from({ length: n }, () => this.make())\n }\n\n async create(db: DatabaseService): Promise<TModel> {\n const data = this.make()\n const model = (db as unknown as Record<string, { create: (args: { data: TCreateInput }) => Promise<TModel> }>)[this.model]\n return model.create({ data })\n }\n\n async createMany(db: DatabaseService, count?: number): Promise<{ count: number }> {\n const data = this.makeMany(count)\n const model = (db as unknown as Record<string, { createMany: (args: { data: TCreateInput[] }) => Promise<{ count: number }> }>)[this.model]\n return model.createMany({ data })\n }\n\n async createManyAndReturn(db: DatabaseService, count?: number): Promise<TModel[]> {\n const data = this.makeMany(count)\n const model = (db as unknown as Record<string, { createManyAndReturn: (args: { data: TCreateInput[] }) => Promise<TModel[]> }>)[this.model]\n return model.createManyAndReturn({ data })\n }\n\n protected clone(): this {\n const FactoryClass = this.constructor as new () => this\n const clone = new FactoryClass()\n clone.states = [...this.states]\n clone._count = this._count\n return clone\n }\n}\n","/**\n * Sequence\n *\n * Auto-incrementing sequence generator for creating unique values.\n *\n * @example Basic usage\n * ```typescript\n * const emailSeq = new Sequence((n) => `user${n}@example.com`)\n *\n * emailSeq.next() // 'user1@example.com'\n * emailSeq.next() // 'user2@example.com'\n * emailSeq.reset()\n * emailSeq.next() // 'user1@example.com'\n * ```\n *\n * @example With factory\n * ```typescript\n * const orderSeq = new Sequence((n) => `ORD-${String(n).padStart(6, '0')}`)\n *\n * export class OrderFactory extends Factory<Order, OrderCreateInput> {\n * protected definition() {\n * return {\n * orderNumber: orderSeq.next(),\n * // ...\n * }\n * }\n * }\n * ```\n */\nexport class Sequence<T = number> {\n private current = 0\n\n constructor(private readonly generator?: (n: number) => T) {}\n\n next(): T {\n this.current++\n if (this.generator) {\n return this.generator(this.current)\n }\n return this.current as T\n }\n\n peek(): T {\n const value = this.current + 1\n if (this.generator) {\n return this.generator(value)\n }\n return value as T\n }\n\n reset(): void {\n this.current = 0\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAsB,UAAtB,MAAoD;CAClD,QAAkC;CAIlC,SAAqE,EAAE;CACvE,SAAiB;CAEjB,MAAM,UAAgE;EACpE,MAAM,QAAQ,KAAK,OAAO;EAC1B,MAAM,OAAO,KAAK,SAAS;EAC3B,OAAO;;CAGT,MAAM,GAAiB;EACrB,MAAM,QAAQ,KAAK,OAAO;EAC1B,MAAM,SAAS;EACf,OAAO;;CAGT,OAAqB;EACnB,IAAI,QAAQ,KAAK,YAAY;EAC7B,KAAK,MAAM,YAAY,KAAK,QAC1B,QAAQ;GAAE,GAAG;GAAO,GAAG,SAAS,MAAM;GAAE;EAE1C,OAAO;;CAGT,SAAS,OAAgC;EACvC,MAAM,IAAI,SAAS,KAAK;EACxB,OAAO,MAAM,KAAK,EAAE,QAAQ,GAAG,QAAQ,KAAK,MAAM,CAAC;;CAGrD,MAAM,OAAO,IAAsC;EACjD,MAAM,OAAO,KAAK,MAAM;EAExB,OADe,GAAgG,KAAK,OACvG,OAAO,EAAE,MAAM,CAAC;;CAG/B,MAAM,WAAW,IAAqB,OAA4C;EAChF,MAAM,OAAO,KAAK,SAAS,MAAM;EAEjC,OADe,GAAiH,KAAK,OACxH,WAAW,EAAE,MAAM,CAAC;;CAGnC,MAAM,oBAAoB,IAAqB,OAAmC;EAChF,MAAM,OAAO,KAAK,SAAS,MAAM;EAEjC,OADe,GAAiH,KAAK,OACxH,oBAAoB,EAAE,MAAM,CAAC;;CAG5C,QAAwB;EACtB,MAAM,eAAe,KAAK;EAC1B,MAAM,QAAQ,IAAI,cAAc;EAChC,MAAM,SAAS,CAAC,GAAG,KAAK,OAAO;EAC/B,MAAM,SAAS,KAAK;EACpB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AClEX,IAAa,WAAb,MAAkC;CAGH;CAF7B,UAAkB;CAElB,YAAY,WAA+C;EAA9B,KAAA,YAAA;;CAE7B,OAAU;EACR,KAAK;EACL,IAAI,KAAK,WACP,OAAO,KAAK,UAAU,KAAK,QAAQ;EAErC,OAAO,KAAK;;CAGd,OAAU;EACR,MAAM,QAAQ,KAAK,UAAU;EAC7B,IAAI,KAAK,WACP,OAAO,KAAK,UAAU,MAAM;EAE9B,OAAO;;CAGT,QAAc;EACZ,KAAK,UAAU"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/guards/auth.guard.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/guards/auth.guard.ts"],"mappings":";;;;;AAqDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAU,OAAA,GAAU,kBAAA,GAAmB,YAAA"}
|
package/dist/guards/index.mjs
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { n as AC_TOKENS, t as __decorateParam } from "../decorateParam-
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { DI_TOKENS, Transient } from "stratal/di";
|
|
1
|
+
import { n as AC_TOKENS, t as __decorateParam } from "../decorateParam-C_dJ_dIO.mjs";
|
|
2
|
+
import { t as __decorate } from "../decorate-7CAoTBu4.mjs";
|
|
3
|
+
import { n as UserNotAuthenticatedError } from "../errors-MCyrn_V2.mjs";
|
|
4
|
+
import { t as InsufficientPermissionsError } from "../insufficient-permissions.error-GwrkWnEM.mjs";
|
|
5
|
+
import { DI_TOKENS, Transient, inject } from "stratal/di";
|
|
7
6
|
import { LOGGER_TOKENS } from "stratal/logger";
|
|
8
|
-
import { inject as inject$1 } from "tsyringe";
|
|
9
7
|
import { GUARD_METADATA_KEY, GuardExecutionService, UseGuards, getControllerGuards, getMethodGuards } from "stratal/guards";
|
|
10
8
|
//#region src/guards/auth.guard.ts
|
|
11
9
|
function parsePermissions(raw) {
|
|
@@ -52,6 +50,9 @@ function AuthGuard(options) {
|
|
|
52
50
|
const rawPermissions = options?.permissions;
|
|
53
51
|
const permissions = rawPermissions ? parsePermissions(rawPermissions) : void 0;
|
|
54
52
|
let ConfiguredAuthGuard = class ConfiguredAuthGuard {
|
|
53
|
+
authContext;
|
|
54
|
+
logger;
|
|
55
|
+
accessService;
|
|
55
56
|
constructor(authContext, logger, accessService) {
|
|
56
57
|
this.authContext = authContext;
|
|
57
58
|
this.logger = logger;
|
|
@@ -69,7 +70,7 @@ function AuthGuard(options) {
|
|
|
69
70
|
const userId = this.authContext.getUserId();
|
|
70
71
|
if (!userId) {
|
|
71
72
|
this.logger.debug("Auth guard: No user ID in context");
|
|
72
|
-
throw new InsufficientPermissionsError(rawPermissions
|
|
73
|
+
throw new InsufficientPermissionsError(rawPermissions);
|
|
73
74
|
}
|
|
74
75
|
if (this.accessService) {
|
|
75
76
|
const allowed = await this.accessService.hasPermission(userId, permissions);
|
|
@@ -85,14 +86,9 @@ function AuthGuard(options) {
|
|
|
85
86
|
};
|
|
86
87
|
ConfiguredAuthGuard = __decorate([
|
|
87
88
|
Transient(),
|
|
88
|
-
__decorateParam(0, inject
|
|
89
|
-
__decorateParam(1, inject
|
|
90
|
-
__decorateParam(2, inject
|
|
91
|
-
__decorateMetadata("design:paramtypes", [
|
|
92
|
-
Object,
|
|
93
|
-
Object,
|
|
94
|
-
Object
|
|
95
|
-
])
|
|
89
|
+
__decorateParam(0, inject(DI_TOKENS.AuthContext)),
|
|
90
|
+
__decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
|
|
91
|
+
__decorateParam(2, inject(AC_TOKENS.AccessService, { isOptional: true }))
|
|
96
92
|
], ConfiguredAuthGuard);
|
|
97
93
|
return ConfiguredAuthGuard;
|
|
98
94
|
}
|