@stratal/framework 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/access-control/index.d.mts +180 -0
  2. package/dist/access-control/index.d.mts.map +1 -0
  3. package/dist/access-control/index.mjs +71 -0
  4. package/dist/access-control/index.mjs.map +1 -0
  5. package/dist/access.service-BjYVtUJw.mjs +145 -0
  6. package/dist/access.service-BjYVtUJw.mjs.map +1 -0
  7. package/dist/auth/index.d.mts +131 -6
  8. package/dist/auth/index.d.mts.map +1 -1
  9. package/dist/auth/index.mjs +341 -67
  10. package/dist/auth/index.mjs.map +1 -1
  11. package/dist/auth-context-6Li1JkIq.mjs +85 -0
  12. package/dist/auth-context-6Li1JkIq.mjs.map +1 -0
  13. package/dist/auth-context-B44CDZCt.d.mts +86 -0
  14. package/dist/auth-context-B44CDZCt.d.mts.map +1 -0
  15. package/dist/context/index.d.mts +2 -2
  16. package/dist/context/index.mjs +2 -2
  17. package/dist/database/index.d.mts +3 -3
  18. package/dist/database/index.mjs +54 -46
  19. package/dist/database/index.mjs.map +1 -1
  20. package/dist/{decorate-C12QolJF.mjs → decorate-CdfCRvAc.mjs} +1 -1
  21. package/dist/{decorateMetadata-rWbWGUuO.mjs → decorateMetadata-CqtSx3_1.mjs} +1 -1
  22. package/dist/decorateParam-Dc5DGEpb.mjs +18 -0
  23. package/dist/decorateParam-Dc5DGEpb.mjs.map +1 -0
  24. package/dist/{errors-C_KIIU1v.mjs → errors-B1vVXc1T.mjs} +1 -1
  25. package/dist/{errors-C_KIIU1v.mjs.map → errors-B1vVXc1T.mjs.map} +1 -1
  26. package/dist/factory/index.d.mts +1 -1
  27. package/dist/guards/index.d.mts +7 -6
  28. package/dist/guards/index.d.mts.map +1 -1
  29. package/dist/guards/index.mjs +38 -29
  30. package/dist/guards/index.mjs.map +1 -1
  31. package/dist/{index-B1iGBJcO.d.mts → index-CCDPF-1Y.d.mts} +30 -42
  32. package/dist/index-CCDPF-1Y.d.mts.map +1 -0
  33. package/dist/index.d.mts +2 -2
  34. package/dist/insufficient-permissions.error-CRnOHYvq.mjs +23 -0
  35. package/dist/insufficient-permissions.error-CRnOHYvq.mjs.map +1 -0
  36. package/dist/types-BLyu9dAd.d.mts +11 -0
  37. package/dist/types-BLyu9dAd.d.mts.map +1 -0
  38. package/dist/types-BZlcRR2M.d.mts +92 -0
  39. package/dist/types-BZlcRR2M.d.mts.map +1 -0
  40. package/package.json +23 -22
  41. package/dist/auth-context-BD2ApWg1.d.mts +0 -38
  42. package/dist/auth-context-BD2ApWg1.d.mts.map +0 -1
  43. package/dist/auth-context-BfekHvM9.mjs +0 -55
  44. package/dist/auth-context-BfekHvM9.mjs.map +0 -1
  45. package/dist/decorateParam-WGqsyT5s.mjs +0 -8
  46. package/dist/index-B1iGBJcO.d.mts.map +0 -1
  47. package/dist/rbac/index.d.mts +0 -206
  48. package/dist/rbac/index.d.mts.map +0 -1
  49. package/dist/rbac/index.mjs +0 -346
  50. package/dist/rbac/index.mjs.map +0 -1
  51. package/dist/tokens-Di1ofovy.mjs +0 -32
  52. package/dist/tokens-Di1ofovy.mjs.map +0 -1
  53. package/dist/types-Gjk0d2qB.d.mts +0 -47
  54. package/dist/types-Gjk0d2qB.d.mts.map +0 -1
@@ -1 +1 @@
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/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.plugin.ts","../../src/database/database.helpers.ts","../../src/database/database.tokens.ts","../../src/database/database.module.ts","../../src/database/decorators/inject-db.decorator.ts","../../src/database/i18n/en.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","import { type RuntimePlugin } from \"@zenstackhq/orm\"\nimport { type SchemaDef } from \"@zenstackhq/orm/schema\"\n\nexport interface SchemaSwitcherPluginOptions {\n schemaName: string\n}\n\n/**\n * ZenStack runtime plugin that sets PostgreSQL search_path before each query.\n * Used for tenant isolation in multi-tenant applications.\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [\n * new SchemaSwitcherPlugin({ schemaName: `tenant_${tenantId}` })\n * ]\n * })\n * ```\n */\nexport class SchemaSwitcherPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'schema-switcher'\n\n constructor(private options: SchemaSwitcherPluginOptions) { }\n\n onQuery = async ({ args, proceed, client }: {\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n client: { $executeRawUnsafe: (sql: string) => Promise<unknown> }\n }): Promise<unknown> => {\n // Set search_path before each query\n await client.$executeRawUnsafe(`SET search_path TO \"${this.options.schemaName}\"`)\n return proceed(args)\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})\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 super(conn.schema, { dialect, plugins })\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","import type { AnyPlugin, ClientOptions } from '@zenstackhq/orm'\nimport type { SchemaDef } from '@zenstackhq/schema'\nimport { DI_TOKENS, Scope, delay } from 'stratal/di'\nimport type { IEventRegistry } from 'stratal/events'\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 { DATABASE_TOKENS, connectionSymbol } from './database.tokens'\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\nexport interface DatabaseModuleConfig {\n default: DefaultConnectionName\n connections: DatabaseConnectionConfig[]\n}\n\n@Module({\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 Overload error\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","export const databaseI18n = {\n database: {\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 AppMessages {\n database: typeof databaseI18n['database']\n }\n}\n"],"mappings":"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"}
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,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.122.0/helpers/decorate.js
1
+ //#region \0@oxc-project+runtime@0.127.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);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.122.0/helpers/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.127.0/helpers/decorateMetadata.js
2
2
  function __decorateMetadata(k, v) {
3
3
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
4
  }
@@ -0,0 +1,18 @@
1
+ //#region src/access-control/tokens.ts
2
+ const AC_TOKENS = {
3
+ /** Request-scoped access service */
4
+ AccessService: Symbol.for("stratal:ac:service"),
5
+ /** Access control module options (ac, roles) */
6
+ Options: Symbol.for("stratal:ac:options")
7
+ };
8
+ //#endregion
9
+ //#region \0@oxc-project+runtime@0.127.0/helpers/decorateParam.js
10
+ function __decorateParam(paramIndex, decorator) {
11
+ return function(target, key) {
12
+ decorator(target, key, paramIndex);
13
+ };
14
+ }
15
+ //#endregion
16
+ export { AC_TOKENS as n, __decorateParam as t };
17
+
18
+ //# sourceMappingURL=decorateParam-Dc5DGEpb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorateParam-Dc5DGEpb.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"}
@@ -22,4 +22,4 @@ var UserNotAuthorizedError = class extends ApplicationError {
22
22
  //#endregion
23
23
  export { UserNotAuthenticatedError as n, ContextNotInitializedError as r, UserNotAuthorizedError as t };
24
24
 
25
- //# sourceMappingURL=errors-C_KIIU1v.mjs.map
25
+ //# sourceMappingURL=errors-B1vVXc1T.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors-C_KIIU1v.mjs","names":[],"sources":["../src/context/errors/context-not-initialized.error.ts","../src/context/errors/user-not-authenticated.error.ts","../src/context/errors/user-not-authorized.error.ts"],"sourcesContent":["import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class ContextNotInitializedError extends ApplicationError {\n constructor(contextType = 'Context') {\n super(\n 'errors.contextNotInitialized',\n ERROR_CODES.AUTH.CONTEXT_NOT_INITIALIZED,\n { contextType }\n )\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class UserNotAuthenticatedError extends ApplicationError {\n constructor() {\n super(\n 'errors.userNotAuthenticated',\n ERROR_CODES.AUTH.USER_NOT_AUTHENTICATED\n )\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class UserNotAuthorizedError extends ApplicationError {\n constructor() {\n super(\n 'errors.unauthorized',\n ERROR_CODES.AUTHZ.FORBIDDEN\n )\n }\n}\n"],"mappings":";;AAEA,IAAa,6BAAb,cAAgD,iBAAiB;CAC/D,YAAY,cAAc,WAAW;AACnC,QACE,gCACA,YAAY,KAAK,yBACjB,EAAE,aAAa,CAChB;;;;;ACNL,IAAa,4BAAb,cAA+C,iBAAiB;CAC9D,cAAc;AACZ,QACE,+BACA,YAAY,KAAK,uBAClB;;;;;ACLL,IAAa,yBAAb,cAA4C,iBAAiB;CAC3D,cAAc;AACZ,QACE,uBACA,YAAY,MAAM,UACnB"}
1
+ {"version":3,"file":"errors-B1vVXc1T.mjs","names":[],"sources":["../src/context/errors/context-not-initialized.error.ts","../src/context/errors/user-not-authenticated.error.ts","../src/context/errors/user-not-authorized.error.ts"],"sourcesContent":["import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class ContextNotInitializedError extends ApplicationError {\n constructor(contextType = 'Context') {\n super(\n 'errors.contextNotInitialized',\n ERROR_CODES.AUTH.CONTEXT_NOT_INITIALIZED,\n { contextType }\n )\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class UserNotAuthenticatedError extends ApplicationError {\n constructor() {\n super(\n 'errors.userNotAuthenticated',\n ERROR_CODES.AUTH.USER_NOT_AUTHENTICATED\n )\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class UserNotAuthorizedError extends ApplicationError {\n constructor() {\n super(\n 'errors.unauthorized',\n ERROR_CODES.AUTHZ.FORBIDDEN\n )\n }\n}\n"],"mappings":";;AAEA,IAAa,6BAAb,cAAgD,iBAAiB;CAC/D,YAAY,cAAc,WAAW;AACnC,QACE,gCACA,YAAY,KAAK,yBACjB,EAAE,aAAa,CAChB;;;;;ACNL,IAAa,4BAAb,cAA+C,iBAAiB;CAC9D,cAAc;AACZ,QACE,+BACA,YAAY,KAAK,uBAClB;;;;;ACLL,IAAa,yBAAb,cAA4C,iBAAiB;CAC3D,cAAc;AACZ,QACE,uBACA,YAAY,MAAM,UACnB"}
@@ -1,4 +1,4 @@
1
- import { N as DatabaseService } from "../index-B1iGBJcO.mjs";
1
+ import { M as DatabaseService } from "../index-CCDPF-1Y.mjs";
2
2
  import { Faker } from "@faker-js/faker";
3
3
 
4
4
  //#region src/factory/factory.d.ts
@@ -6,17 +6,17 @@ import { AuthGuardOptions, AuthGuardOptions as AuthGuardOptions$1, CanActivate,
6
6
  *
7
7
  * Creates a guard class that enforces authentication and optional authorization.
8
8
  *
9
- * **Authentication (no scopes):**
9
+ * **Authentication (no permissions):**
10
10
  * - Checks if user is authenticated via AuthContext.isAuthenticated()
11
11
  * - Throws UserNotAuthenticatedError (401) if not authenticated
12
12
  *
13
- * **Authorization (with scopes):**
13
+ * **Authorization (with permissions):**
14
14
  * - First verifies authentication
15
- * - Then checks permissions via CasbinService
15
+ * - Then checks permissions via AccessService (reads from AuthContext — no DB hit)
16
16
  * - Throws InsufficientPermissionsError (403) if unauthorized
17
17
  *
18
18
  * @param options - Configuration options
19
- * @param options.scopes - Required permissions for authorization
19
+ * @param options.permissions - Required permissions keyed by resource
20
20
  * @returns Guard class for use with @UseGuards decorator
21
21
  *
22
22
  * @example Authentication only
@@ -27,8 +27,9 @@ import { AuthGuardOptions, AuthGuardOptions as AuthGuardOptions$1, CanActivate,
27
27
  *
28
28
  * @example Authentication with permissions
29
29
  * ```typescript
30
- * @UseGuards(AuthGuard({ scopes: ['students:read'] }))
31
- * export class StudentsController { }
30
+ * @UseGuards(AuthGuard({ permissions: 'posts:update' }))
31
+ * @UseGuards(AuthGuard({ permissions: ['posts:update', 'posts:delete'] }))
32
+ * export class PostsController { }
32
33
  * ```
33
34
  */
34
35
  declare function AuthGuard(options?: AuthGuardOptions$1): GuardClass$1;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/guards/auth.guard.ts"],"mappings":";;;;;AAyCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAU,OAAA,GAAU,kBAAA,GAAmB,YAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/guards/auth.guard.ts"],"mappings":";;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAU,OAAA,GAAU,kBAAA,GAAmB,YAAA"}
@@ -1,29 +1,38 @@
1
- import { n as UserNotAuthenticatedError } from "../errors-C_KIIU1v.mjs";
2
- import { t as __decorate } from "../decorate-C12QolJF.mjs";
3
- import { t as __decorateMetadata } from "../decorateMetadata-rWbWGUuO.mjs";
4
- import { t as __decorateParam } from "../decorateParam-WGqsyT5s.mjs";
5
- import { n as InsufficientPermissionsError, t as RBAC_TOKENS } from "../tokens-Di1ofovy.mjs";
1
+ import { n as AC_TOKENS, t as __decorateParam } from "../decorateParam-Dc5DGEpb.mjs";
2
+ import { t as __decorateMetadata } from "../decorateMetadata-CqtSx3_1.mjs";
3
+ import { t as __decorate } from "../decorate-CdfCRvAc.mjs";
4
+ import { n as UserNotAuthenticatedError } from "../errors-B1vVXc1T.mjs";
5
+ import { t as InsufficientPermissionsError } from "../insufficient-permissions.error-CRnOHYvq.mjs";
6
6
  import { DI_TOKENS, Transient } from "stratal/di";
7
7
  import { LOGGER_TOKENS } from "stratal/logger";
8
- import { inject } from "tsyringe";
8
+ import { inject as inject$1 } from "tsyringe";
9
9
  import { GUARD_METADATA_KEY, GuardExecutionService, UseGuards, getControllerGuards, getMethodGuards } from "stratal/guards";
10
10
  //#region src/guards/auth.guard.ts
11
+ function parsePermissions(raw) {
12
+ return (Array.isArray(raw) ? raw : [raw]).reduce((acc, perm) => {
13
+ const colon = perm.indexOf(":");
14
+ const resource = colon === -1 ? perm : perm.slice(0, colon);
15
+ const action = colon === -1 ? "*" : perm.slice(colon + 1);
16
+ (acc[resource] ??= []).push(action);
17
+ return acc;
18
+ }, {});
19
+ }
11
20
  /**
12
21
  * AuthGuard Factory
13
22
  *
14
23
  * Creates a guard class that enforces authentication and optional authorization.
15
24
  *
16
- * **Authentication (no scopes):**
25
+ * **Authentication (no permissions):**
17
26
  * - Checks if user is authenticated via AuthContext.isAuthenticated()
18
27
  * - Throws UserNotAuthenticatedError (401) if not authenticated
19
28
  *
20
- * **Authorization (with scopes):**
29
+ * **Authorization (with permissions):**
21
30
  * - First verifies authentication
22
- * - Then checks permissions via CasbinService
31
+ * - Then checks permissions via AccessService (reads from AuthContext — no DB hit)
23
32
  * - Throws InsufficientPermissionsError (403) if unauthorized
24
33
  *
25
34
  * @param options - Configuration options
26
- * @param options.scopes - Required permissions for authorization
35
+ * @param options.permissions - Required permissions keyed by resource
27
36
  * @returns Guard class for use with @UseGuards decorator
28
37
  *
29
38
  * @example Authentication only
@@ -34,51 +43,51 @@ import { GUARD_METADATA_KEY, GuardExecutionService, UseGuards, getControllerGuar
34
43
  *
35
44
  * @example Authentication with permissions
36
45
  * ```typescript
37
- * @UseGuards(AuthGuard({ scopes: ['students:read'] }))
38
- * export class StudentsController { }
46
+ * @UseGuards(AuthGuard({ permissions: 'posts:update' }))
47
+ * @UseGuards(AuthGuard({ permissions: ['posts:update', 'posts:delete'] }))
48
+ * export class PostsController { }
39
49
  * ```
40
50
  */
41
51
  function AuthGuard(options) {
42
- const scopes = options?.scopes;
52
+ const rawPermissions = options?.permissions;
53
+ const permissions = rawPermissions ? parsePermissions(rawPermissions) : void 0;
43
54
  let ConfiguredAuthGuard = class ConfiguredAuthGuard {
44
- constructor(authContext, logger, casbinService) {
55
+ constructor(authContext, logger, accessService) {
45
56
  this.authContext = authContext;
46
57
  this.logger = logger;
47
- this.casbinService = casbinService;
58
+ this.accessService = accessService;
48
59
  }
49
- async canActivate(context) {
60
+ async canActivate(_context) {
50
61
  if (!this.authContext.isAuthenticated()) {
51
62
  this.logger.debug("Auth guard: User not authenticated");
52
63
  throw new UserNotAuthenticatedError();
53
64
  }
54
- if (!scopes || scopes.length === 0) {
55
- this.logger.debug("Auth guard: Authentication passed (no scopes required)");
65
+ if (!permissions || Object.keys(permissions).length === 0) {
66
+ this.logger.debug("Auth guard: Authentication passed (no permissions required)");
56
67
  return true;
57
68
  }
58
69
  const userId = this.authContext.getUserId();
59
70
  if (!userId) {
60
71
  this.logger.debug("Auth guard: No user ID in context");
61
- throw new InsufficientPermissionsError(scopes, void 0);
72
+ throw new InsufficientPermissionsError(rawPermissions, void 0);
62
73
  }
63
- const httpMethod = context.c.req.method.toLowerCase();
64
- if (this.casbinService) {
65
- const hasPermission = await this.casbinService.hasAnyPermission(userId, scopes, httpMethod);
74
+ if (this.accessService) {
75
+ const allowed = await this.accessService.hasPermission(userId, permissions);
66
76
  this.logger.debug("Auth guard: Authorization check", {
67
77
  userId,
68
- scopes,
69
- httpMethod,
70
- hasPermission
78
+ permissions,
79
+ allowed
71
80
  });
72
- if (!hasPermission) throw new InsufficientPermissionsError(scopes, userId);
81
+ if (!allowed) throw new InsufficientPermissionsError(rawPermissions, userId);
73
82
  }
74
83
  return true;
75
84
  }
76
85
  };
77
86
  ConfiguredAuthGuard = __decorate([
78
87
  Transient(),
79
- __decorateParam(0, inject(DI_TOKENS.AuthContext)),
80
- __decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
81
- __decorateParam(2, inject(RBAC_TOKENS.CasbinService, { isOptional: true })),
88
+ __decorateParam(0, inject$1(DI_TOKENS.AuthContext)),
89
+ __decorateParam(1, inject$1(LOGGER_TOKENS.LoggerService)),
90
+ __decorateParam(2, inject$1(AC_TOKENS.AccessService, { isOptional: true })),
82
91
  __decorateMetadata("design:paramtypes", [
83
92
  Object,
84
93
  Object,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/guards/auth.guard.ts"],"sourcesContent":["import { DI_TOKENS, Transient } from 'stratal/di'\nimport type { AuthGuardOptions, CanActivate, GuardClass } from 'stratal/guards'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { RouterContext } from 'stratal/router'\nimport { inject } from 'tsyringe'\nimport type { AuthContext } from '../context/auth-context'\nimport { UserNotAuthenticatedError } from '../context/errors'\nimport { InsufficientPermissionsError } from '../rbac/errors/insufficient-permissions.error'\nimport type { CasbinService } from '../rbac/services/casbin.service'\nimport { RBAC_TOKENS } from '../rbac/tokens'\n\n/**\n * AuthGuard Factory\n *\n * Creates a guard class that enforces authentication and optional authorization.\n *\n * **Authentication (no scopes):**\n * - Checks if user is authenticated via AuthContext.isAuthenticated()\n * - Throws UserNotAuthenticatedError (401) if not authenticated\n *\n * **Authorization (with scopes):**\n * - First verifies authentication\n * - Then checks permissions via CasbinService\n * - Throws InsufficientPermissionsError (403) if unauthorized\n *\n * @param options - Configuration options\n * @param options.scopes - Required permissions for authorization\n * @returns Guard class for use with @UseGuards decorator\n *\n * @example Authentication only\n * ```typescript\n * @UseGuards(AuthGuard())\n * export class ProfileController { }\n * ```\n *\n * @example Authentication with permissions\n * ```typescript\n * @UseGuards(AuthGuard({ scopes: ['students:read'] }))\n * export class StudentsController { }\n * ```\n */\nexport function AuthGuard(options?: AuthGuardOptions): GuardClass {\n const scopes = options?.scopes\n\n @Transient()\n class ConfiguredAuthGuard implements CanActivate {\n constructor(\n @inject(DI_TOKENS.AuthContext) private readonly authContext: AuthContext,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService,\n @inject(RBAC_TOKENS.CasbinService, { isOptional: true }) private readonly casbinService?: CasbinService\n ) { }\n\n async canActivate(context: RouterContext): Promise<boolean> {\n if (!this.authContext.isAuthenticated()) {\n this.logger.debug('Auth guard: User not authenticated')\n throw new UserNotAuthenticatedError()\n }\n\n if (!scopes || scopes.length === 0) {\n this.logger.debug('Auth guard: Authentication passed (no scopes required)')\n return true\n }\n\n const userId = this.authContext.getUserId()\n if (!userId) {\n this.logger.debug('Auth guard: No user ID in context')\n throw new InsufficientPermissionsError(scopes, undefined)\n }\n\n const httpMethod = context.c.req.method.toLowerCase()\n\n if (this.casbinService) {\n const hasPermission = await this.casbinService.hasAnyPermission(\n userId,\n scopes,\n httpMethod\n )\n\n this.logger.debug('Auth guard: Authorization check', {\n userId,\n scopes,\n httpMethod,\n hasPermission,\n })\n\n if (!hasPermission) {\n throw new InsufficientPermissionsError(scopes, userId)\n }\n }\n\n return true\n }\n }\n\n return ConfiguredAuthGuard\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,UAAU,SAAwC;CAChE,MAAM,SAAS,SAAS;CAExB,IAAA,sBAAA,MACM,oBAA2C;EAC/C,YACE,aACA,QACA,eACA;AAHgD,QAAA,cAAA;AACM,QAAA,SAAA;AACoB,QAAA,gBAAA;;EAG5E,MAAM,YAAY,SAA0C;AAC1D,OAAI,CAAC,KAAK,YAAY,iBAAiB,EAAE;AACvC,SAAK,OAAO,MAAM,qCAAqC;AACvD,UAAM,IAAI,2BAA2B;;AAGvC,OAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,SAAK,OAAO,MAAM,yDAAyD;AAC3E,WAAO;;GAGT,MAAM,SAAS,KAAK,YAAY,WAAW;AAC3C,OAAI,CAAC,QAAQ;AACX,SAAK,OAAO,MAAM,oCAAoC;AACtD,UAAM,IAAI,6BAA6B,QAAQ,KAAA,EAAU;;GAG3D,MAAM,aAAa,QAAQ,EAAE,IAAI,OAAO,aAAa;AAErD,OAAI,KAAK,eAAe;IACtB,MAAM,gBAAgB,MAAM,KAAK,cAAc,iBAC7C,QACA,QACA,WACD;AAED,SAAK,OAAO,MAAM,mCAAmC;KACnD;KACA;KACA;KACA;KACD,CAAC;AAEF,QAAI,CAAC,cACH,OAAM,IAAI,6BAA6B,QAAQ,OAAO;;AAI1D,UAAO;;;;EA9CV,WAAW;qBAGP,OAAO,UAAU,YAAY,CAAA;qBAC7B,OAAO,cAAc,cAAc,CAAA;qBACnC,OAAO,YAAY,eAAe,EAAE,YAAY,MAAM,CAAC,CAAA;;;;;;;AA6C5D,QAAO"}
1
+ {"version":3,"file":"index.mjs","names":["inject"],"sources":["../../src/guards/auth.guard.ts"],"sourcesContent":["import { DI_TOKENS, Transient } from 'stratal/di'\nimport type { AuthGuardOptions, CanActivate, GuardClass } from 'stratal/guards'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { RouterContext } from 'stratal/router'\nimport { inject } from 'tsyringe'\nimport { InsufficientPermissionsError } from '../access-control/errors/insufficient-permissions.error'\nimport type { AccessService } from '../access-control/services/access.service'\nimport { AC_TOKENS } from '../access-control/tokens'\nimport type { AuthContext } from '../context/auth-context'\nimport { UserNotAuthenticatedError } from '../context/errors'\n\nfunction parsePermissions(raw: string | string[]): Record<string, string[]> {\n const list = Array.isArray(raw) ? raw : [raw]\n return list.reduce<Record<string, string[]>>((acc, perm) => {\n const colon = perm.indexOf(':')\n const resource = colon === -1 ? perm : perm.slice(0, colon)\n const action = colon === -1 ? '*' : perm.slice(colon + 1)\n ;(acc[resource] ??= []).push(action)\n return acc\n }, {})\n}\n\n\n/**\n * AuthGuard Factory\n *\n * Creates a guard class that enforces authentication and optional authorization.\n *\n * **Authentication (no permissions):**\n * - Checks if user is authenticated via AuthContext.isAuthenticated()\n * - Throws UserNotAuthenticatedError (401) if not authenticated\n *\n * **Authorization (with permissions):**\n * - First verifies authentication\n * - Then checks permissions via AccessService (reads from AuthContext — no DB hit)\n * - Throws InsufficientPermissionsError (403) if unauthorized\n *\n * @param options - Configuration options\n * @param options.permissions - Required permissions keyed by resource\n * @returns Guard class for use with @UseGuards decorator\n *\n * @example Authentication only\n * ```typescript\n * @UseGuards(AuthGuard())\n * export class ProfileController { }\n * ```\n *\n * @example Authentication with permissions\n * ```typescript\n * @UseGuards(AuthGuard({ permissions: 'posts:update' }))\n * @UseGuards(AuthGuard({ permissions: ['posts:update', 'posts:delete'] }))\n * export class PostsController { }\n * ```\n */\nexport function AuthGuard(options?: AuthGuardOptions): GuardClass {\n const rawPermissions = options?.permissions\n const permissions = rawPermissions ? parsePermissions(rawPermissions) : undefined\n\n @Transient()\n class ConfiguredAuthGuard implements CanActivate {\n constructor(\n @inject(DI_TOKENS.AuthContext) private readonly authContext: AuthContext,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService,\n @inject(AC_TOKENS.AccessService, { isOptional: true }) private readonly accessService?: AccessService\n ) { }\n\n async canActivate(_context: RouterContext): Promise<boolean> {\n if (!this.authContext.isAuthenticated()) {\n this.logger.debug('Auth guard: User not authenticated')\n throw new UserNotAuthenticatedError()\n }\n\n if (!permissions || Object.keys(permissions).length === 0) {\n this.logger.debug('Auth guard: Authentication passed (no permissions required)')\n return true\n }\n\n const userId = this.authContext.getUserId()\n if (!userId) {\n this.logger.debug('Auth guard: No user ID in context')\n throw new InsufficientPermissionsError(rawPermissions!, undefined)\n }\n\n if (this.accessService) {\n const allowed = await this.accessService.hasPermission(userId, permissions)\n\n this.logger.debug('Auth guard: Authorization check', {\n userId,\n permissions,\n allowed,\n })\n\n if (!allowed) {\n throw new InsufficientPermissionsError(rawPermissions!, userId)\n }\n }\n\n return true\n }\n }\n\n return ConfiguredAuthGuard\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAS,iBAAiB,KAAkD;AAE1E,SADa,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI,EACjC,QAAkC,KAAK,SAAS;EAC1D,MAAM,QAAQ,KAAK,QAAQ,IAAI;EAC/B,MAAM,WAAW,UAAU,KAAK,OAAO,KAAK,MAAM,GAAG,MAAM;EAC3D,MAAM,SAAS,UAAU,KAAK,MAAM,KAAK,MAAM,QAAQ,EAAE;AACxD,GAAC,IAAI,cAAc,EAAE,EAAE,KAAK,OAAO;AACpC,SAAO;IACN,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCR,SAAgB,UAAU,SAAwC;CAChE,MAAM,iBAAiB,SAAS;CAChC,MAAM,cAAc,iBAAiB,iBAAiB,eAAe,GAAG,KAAA;CAExE,IAAA,sBAAA,MACM,oBAA2C;EAC/C,YACE,aACA,QACA,eACA;AAHgD,QAAA,cAAA;AACM,QAAA,SAAA;AACkB,QAAA,gBAAA;;EAG1E,MAAM,YAAY,UAA2C;AAC3D,OAAI,CAAC,KAAK,YAAY,iBAAiB,EAAE;AACvC,SAAK,OAAO,MAAM,qCAAqC;AACvD,UAAM,IAAI,2BAA2B;;AAGvC,OAAI,CAAC,eAAe,OAAO,KAAK,YAAY,CAAC,WAAW,GAAG;AACzD,SAAK,OAAO,MAAM,8DAA8D;AAChF,WAAO;;GAGT,MAAM,SAAS,KAAK,YAAY,WAAW;AAC3C,OAAI,CAAC,QAAQ;AACX,SAAK,OAAO,MAAM,oCAAoC;AACtD,UAAM,IAAI,6BAA6B,gBAAiB,KAAA,EAAU;;AAGpE,OAAI,KAAK,eAAe;IACtB,MAAM,UAAU,MAAM,KAAK,cAAc,cAAc,QAAQ,YAAY;AAE3E,SAAK,OAAO,MAAM,mCAAmC;KACnD;KACA;KACA;KACD,CAAC;AAEF,QAAI,CAAC,QACH,OAAM,IAAI,6BAA6B,gBAAiB,OAAO;;AAInE,UAAO;;;;EAvCV,WAAW;qBAGPA,SAAO,UAAU,YAAY,CAAA;qBAC7BA,SAAO,cAAc,cAAc,CAAA;qBACnCA,SAAO,UAAU,eAAe,EAAE,YAAY,MAAM,CAAC,CAAA;;;;;;;AAsC1D,QAAO"}
@@ -1,10 +1,10 @@
1
- import { i as InferConnectionSchema, n as DefaultConnectionName, r as InferAnySchema, t as ConnectionName } from "./types-Gjk0d2qB.mjs";
1
+ import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, r as InferAnySchema, t as ConnectionName } from "./types-BZlcRR2M.mjs";
2
+ import { MessageKeys } from "stratal/i18n";
2
3
  import { AsyncModuleOptions, DynamicModule, ModuleContext, OnInitialize, OnShutdown } from "stratal/module";
3
4
  import { ApplicationError, ErrorCode } from "stratal/errors";
4
5
  import { Command } from "stratal/quarry";
5
- import { AggregateArgs, AllCrudOperations, AnyPlugin, ClientContract, ClientOptions, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindFirstArgs, FindManyArgs, FindUniqueArgs, GroupByArgs, ModelResult, RuntimePlugin, UpdateArgs, UpdateManyArgs, UpsertArgs } from "@zenstackhq/orm";
6
+ import { AggregateArgs, AllCrudOperations, AnyPlugin, ClientContract, ClientOptions, ComputedFieldsOptions, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindFirstArgs, FindManyArgs, FindUniqueArgs, GroupByArgs, ModelResult, RuntimePlugin, UpdateArgs, UpdateManyArgs, UpsertArgs } from "@zenstackhq/orm";
6
7
  import { SchemaDef } from "@zenstackhq/schema";
7
- import { MessageKeys } from "stratal/i18n";
8
8
  import { SchemaDef as SchemaDef$1 } from "@zenstackhq/orm/schema";
9
9
  import { IEventRegistry } from "stratal/events";
10
10
 
@@ -14,6 +14,12 @@ interface DatabaseConnectionConfig<Schema extends SchemaDef = SchemaDef, Name ex
14
14
  schema: Schema;
15
15
  dialect: () => ClientOptions<SchemaDef>['dialect'];
16
16
  plugins?: AnyPlugin[];
17
+ /**
18
+ * Schema-level @computed field implementations. Required when the schema
19
+ * declares any `@computed` fields. Keyed by uncapitalized model name; values
20
+ * map field name to a Kysely-expression compute callback.
21
+ */
22
+ computedFields?: ComputedFieldsOptions<Schema>;
17
23
  }
18
24
  interface DatabaseModuleConfig {
19
25
  default: DefaultConnectionName;
@@ -30,18 +36,19 @@ declare class DatabaseModule implements OnInitialize, OnShutdown {
30
36
  /**
31
37
  * DatabaseService type
32
38
  *
33
- * Each connection has its own schema. The service is typed to the connection's schema.
39
+ * Each connection has its own schema and plugin extensions.
40
+ * Plugin extension types are automatically inferred from `StratalDatabase.plugins`.
34
41
  *
35
42
  * @example
36
43
  * ```typescript
37
- * // Typed to default connection
44
+ * // Typed to default connection (includes plugin extensions)
38
45
  * constructor(@inject(DI_TOKENS.Database) private db: DatabaseService) {}
39
46
  *
40
47
  * // Typed to a specific named connection
41
48
  * constructor(@InjectDB('analytics') private analytics: DatabaseService<'analytics'>) {}
42
49
  * ```
43
50
  */
44
- type DatabaseService<K extends ConnectionName = DefaultConnectionName> = ClientContract<InferConnectionSchema<K>, ClientOptions<InferConnectionSchema<K>>>;
51
+ type DatabaseService<K extends ConnectionName = DefaultConnectionName> = ClientContract<InferConnectionSchema<K>, ClientOptions<InferConnectionSchema<K>>, InferConnectionExtensions<K>['extQueryArgs'], InferConnectionExtensions<K>['extClientMembers'], InferConnectionExtensions<K>['extResult']>;
45
52
  //#endregion
46
53
  //#region src/database/database.tokens.d.ts
47
54
  declare const DATABASE_TOKENS: {
@@ -285,8 +292,8 @@ declare module 'stratal/events' {
285
292
  }
286
293
  //#endregion
287
294
  //#region src/database/i18n/en.d.ts
288
- declare const databaseI18n: {
289
- readonly database: {
295
+ declare const databaseMessages: {
296
+ readonly en: {
290
297
  readonly connectionNameRequired: "Connection name is required";
291
298
  readonly defaultConnectionRequired: "Default connection name is required";
292
299
  readonly connectionRequired: "At least one connection is required";
@@ -295,8 +302,8 @@ declare const databaseI18n: {
295
302
  };
296
303
  };
297
304
  declare module 'stratal/i18n' {
298
- interface AppMessages {
299
- database: typeof databaseI18n['database'];
305
+ interface AppMessageNamespaces {
306
+ database: typeof databaseMessages['en'];
300
307
  }
301
308
  } //# sourceMappingURL=en.d.ts.map
302
309
  //#endregion
@@ -363,39 +370,20 @@ declare class EventEmitterPlugin implements RuntimePlugin<SchemaDef$1, Record<st
363
370
  }) => Promise<unknown>;
364
371
  }
365
372
  //#endregion
366
- //#region src/database/plugins/schema-switcher.plugin.d.ts
367
- interface SchemaSwitcherPluginOptions {
368
- schemaName: string;
369
- }
373
+ //#region src/database/plugins/schema-switcher.d.ts
370
374
  /**
371
- * ZenStack runtime plugin that sets PostgreSQL search_path before each query.
372
- * Used for tenant isolation in multi-tenant applications.
375
+ * Switches the active schema on a ZenStack/Kysely database client by mutating
376
+ * `$schema.provider.defaultSchema`. This causes ZenStack's QueryNameMapper to
377
+ * generate fully-qualified table references (e.g. `"tenant_123"."User"`).
373
378
  *
374
- * @example
375
- * ```typescript
376
- * super(schema, {
377
- * dialect: new PostgresDialect({ pool }),
378
- * plugins: [
379
- * new SchemaSwitcherPlugin({ schemaName: `tenant_${tenantId}` })
380
- * ]
381
- * })
382
- * ```
379
+ * Must be called BEFORE any queries are made on the client.
380
+ *
381
+ * Note: The ZenStack RuntimePlugin `onQuery` hook fires after table names are
382
+ * already resolved, so a plugin-based approach cannot set the schema prefix.
383
+ * Direct client mutation is the only supported method.
383
384
  */
384
- declare class SchemaSwitcherPlugin implements RuntimePlugin<SchemaDef$1, Record<string, unknown>, Record<string, unknown>, {}> {
385
- private options;
386
- readonly id = "schema-switcher";
387
- constructor(options: SchemaSwitcherPluginOptions);
388
- onQuery: ({
389
- args,
390
- proceed,
391
- client
392
- }: {
393
- args: Record<string, unknown> | undefined;
394
- proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>;
395
- client: {
396
- $executeRawUnsafe: (sql: string) => Promise<unknown>;
397
- };
398
- }) => Promise<unknown>;
385
+ declare class SchemaSwitcher {
386
+ static apply<T>(client: T, schemaName: string): T;
399
387
  }
400
388
  //#endregion
401
389
  //#region src/database/commands/zenstack.command.d.ts
@@ -456,5 +444,5 @@ declare class MigrateStatusCommand extends ZenStackCommand {
456
444
  handle(): Promise<number>;
457
445
  }
458
446
  //#endregion
459
- export { InjectDB as A, fromZenStackError as C, ForeignKeyConstraintError as D, InvalidErrorCodeRangeError as E, DatabaseModule as F, DatabaseModuleConfig as I, connectionSymbol as M, DatabaseService as N, DatabaseConfigError as O, DatabaseConnectionConfig as P, ParseEvent as S, RecordNotFoundError as T, DatabaseOperation as _, DbPushCommand as a, GetResult as b, ZenStackCommand as c, EventEmitterPlugin as d, EventEmitterPluginOptions as f, DatabaseEvents as g, DatabaseEventName as h, MigrateDeployCommand as i, DATABASE_TOKENS as j, DatabaseError as k, SchemaSwitcherPlugin as l, databaseI18n as m, MigrateResetCommand as n, DbPullCommand as o, ErrorHandlerPlugin as p, MigrateDevCommand as r, DbGenerateCommand as s, MigrateStatusCommand as t, SchemaSwitcherPluginOptions as u, EventPhase as v, UniqueConstraintError as w, ModelName as x, GetData as y };
460
- //# sourceMappingURL=index-B1iGBJcO.d.mts.map
447
+ export { DATABASE_TOKENS as A, UniqueConstraintError as C, DatabaseConfigError as D, ForeignKeyConstraintError as E, DatabaseModuleConfig as F, DatabaseService as M, DatabaseConnectionConfig as N, DatabaseError as O, DatabaseModule as P, fromZenStackError as S, InvalidErrorCodeRangeError as T, EventPhase as _, DbPushCommand as a, ModelName as b, ZenStackCommand as c, EventEmitterPluginOptions as d, ErrorHandlerPlugin as f, DatabaseOperation as g, DatabaseEvents as h, MigrateDeployCommand as i, connectionSymbol as j, InjectDB as k, SchemaSwitcher as l, DatabaseEventName as m, MigrateResetCommand as n, DbPullCommand as o, databaseMessages as p, MigrateDevCommand as r, DbGenerateCommand as s, MigrateStatusCommand as t, EventEmitterPlugin as u, GetData as v, RecordNotFoundError as w, ParseEvent as x, GetResult as y };
448
+ //# sourceMappingURL=index-CCDPF-1Y.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CCDPF-1Y.d.mts","names":[],"sources":["../src/database/database.module.ts","../src/database/database.service.ts","../src/database/database.tokens.ts","../src/database/decorators/inject-db.decorator.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/invalid-error-code-range.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/event-types.ts","../src/database/i18n/en.ts","../src/database/plugins/error-handler.plugin.ts","../src/database/plugins/event-emitter.plugin.ts","../src/database/plugins/schema-switcher.ts","../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"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { i as InferConnectionSchema, n as DefaultConnectionName, o as StratalDatabase, r as InferAnySchema, t as ConnectionName } from "./types-Gjk0d2qB.mjs";
1
+ import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, r as InferAnySchema, s as StratalDatabase, t as ConnectionName } from "./types-BZlcRR2M.mjs";
2
2
  import { CustomEventRegistry, EventName } from "stratal/events";
3
- export { type ConnectionName, type CustomEventRegistry, type DefaultConnectionName, type EventName, type InferAnySchema, type InferConnectionSchema, type StratalDatabase };
3
+ export { type ConnectionName, type CustomEventRegistry, type DefaultConnectionName, type EventName, type InferAnySchema, type InferConnectionExtensions, type InferConnectionSchema, type StratalDatabase };
@@ -0,0 +1,23 @@
1
+ import { ApplicationError, ERROR_CODES } from "stratal/errors";
2
+ //#region src/access-control/errors/insufficient-permissions.error.ts
3
+ /**
4
+ * InsufficientPermissionsError
5
+ *
6
+ * Thrown when a user attempts to perform an action without the required permissions.
7
+ * Used by AuthGuard after an authorization check fails.
8
+ *
9
+ * HTTP Status: 403 Forbidden
10
+ */
11
+ var InsufficientPermissionsError = class extends ApplicationError {
12
+ constructor(requiredPermissions, userId) {
13
+ const summary = Array.isArray(requiredPermissions) ? requiredPermissions.join(", ") : requiredPermissions;
14
+ super("errors.insufficientPermissions", ERROR_CODES.AUTHZ.INSUFFICIENT_PERMISSIONS, {
15
+ requiredPermissions: summary,
16
+ userId: userId ?? "unknown"
17
+ });
18
+ }
19
+ };
20
+ //#endregion
21
+ export { InsufficientPermissionsError as t };
22
+
23
+ //# sourceMappingURL=insufficient-permissions.error-CRnOHYvq.mjs.map