@stratal/framework 0.0.18 → 0.0.19

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 (52) 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 +122 -4
  8. package/dist/auth/index.d.mts.map +1 -1
  9. package/dist/auth/index.mjs +237 -65
  10. package/dist/auth/index.mjs.map +1 -1
  11. package/dist/{auth-context-BD2ApWg1.d.mts → auth-context-BXSkiJ56.d.mts} +14 -1
  12. package/dist/auth-context-BXSkiJ56.d.mts.map +1 -0
  13. package/dist/{auth-context-BfekHvM9.mjs → auth-context-BberoPal.mjs} +25 -4
  14. package/dist/auth-context-BberoPal.mjs.map +1 -0
  15. package/dist/context/index.d.mts +1 -1
  16. package/dist/context/index.mjs +2 -2
  17. package/dist/database/index.d.mts +3 -3
  18. package/dist/database/index.mjs +49 -43
  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-CpFBG0Ws.d.mts} +23 -41
  32. package/dist/index-CpFBG0Ws.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 +22 -22
  41. package/dist/auth-context-BD2ApWg1.d.mts.map +0 -1
  42. package/dist/auth-context-BfekHvM9.mjs.map +0 -1
  43. package/dist/decorateParam-WGqsyT5s.mjs +0 -8
  44. package/dist/index-B1iGBJcO.d.mts.map +0 -1
  45. package/dist/rbac/index.d.mts +0 -206
  46. package/dist/rbac/index.d.mts.map +0 -1
  47. package/dist/rbac/index.mjs +0 -346
  48. package/dist/rbac/index.mjs.map +0 -1
  49. package/dist/tokens-Di1ofovy.mjs +0 -32
  50. package/dist/tokens-Di1ofovy.mjs.map +0 -1
  51. package/dist/types-Gjk0d2qB.d.mts +0 -47
  52. package/dist/types-Gjk0d2qB.d.mts.map +0 -1
@@ -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-CpFBG0Ws.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
6
  import { AggregateArgs, AllCrudOperations, AnyPlugin, ClientContract, ClientOptions, 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
 
@@ -30,18 +30,19 @@ declare class DatabaseModule implements OnInitialize, OnShutdown {
30
30
  /**
31
31
  * DatabaseService type
32
32
  *
33
- * Each connection has its own schema. The service is typed to the connection's schema.
33
+ * Each connection has its own schema and plugin extensions.
34
+ * Plugin extension types are automatically inferred from `StratalDatabase.plugins`.
34
35
  *
35
36
  * @example
36
37
  * ```typescript
37
- * // Typed to default connection
38
+ * // Typed to default connection (includes plugin extensions)
38
39
  * constructor(@inject(DI_TOKENS.Database) private db: DatabaseService) {}
39
40
  *
40
41
  * // Typed to a specific named connection
41
42
  * constructor(@InjectDB('analytics') private analytics: DatabaseService<'analytics'>) {}
42
43
  * ```
43
44
  */
44
- type DatabaseService<K extends ConnectionName = DefaultConnectionName> = ClientContract<InferConnectionSchema<K>, ClientOptions<InferConnectionSchema<K>>>;
45
+ type DatabaseService<K extends ConnectionName = DefaultConnectionName> = ClientContract<InferConnectionSchema<K>, ClientOptions<InferConnectionSchema<K>>, InferConnectionExtensions<K>['extQueryArgs'], InferConnectionExtensions<K>['extClientMembers'], InferConnectionExtensions<K>['extResult']>;
45
46
  //#endregion
46
47
  //#region src/database/database.tokens.d.ts
47
48
  declare const DATABASE_TOKENS: {
@@ -285,8 +286,8 @@ declare module 'stratal/events' {
285
286
  }
286
287
  //#endregion
287
288
  //#region src/database/i18n/en.d.ts
288
- declare const databaseI18n: {
289
- readonly database: {
289
+ declare const databaseMessages: {
290
+ readonly en: {
290
291
  readonly connectionNameRequired: "Connection name is required";
291
292
  readonly defaultConnectionRequired: "Default connection name is required";
292
293
  readonly connectionRequired: "At least one connection is required";
@@ -295,8 +296,8 @@ declare const databaseI18n: {
295
296
  };
296
297
  };
297
298
  declare module 'stratal/i18n' {
298
- interface AppMessages {
299
- database: typeof databaseI18n['database'];
299
+ interface AppMessageNamespaces {
300
+ database: typeof databaseMessages['en'];
300
301
  }
301
302
  } //# sourceMappingURL=en.d.ts.map
302
303
  //#endregion
@@ -363,39 +364,20 @@ declare class EventEmitterPlugin implements RuntimePlugin<SchemaDef$1, Record<st
363
364
  }) => Promise<unknown>;
364
365
  }
365
366
  //#endregion
366
- //#region src/database/plugins/schema-switcher.plugin.d.ts
367
- interface SchemaSwitcherPluginOptions {
368
- schemaName: string;
369
- }
367
+ //#region src/database/plugins/schema-switcher.d.ts
370
368
  /**
371
- * ZenStack runtime plugin that sets PostgreSQL search_path before each query.
372
- * Used for tenant isolation in multi-tenant applications.
369
+ * Switches the active schema on a ZenStack/Kysely database client by mutating
370
+ * `$schema.provider.defaultSchema`. This causes ZenStack's QueryNameMapper to
371
+ * generate fully-qualified table references (e.g. `"tenant_123"."User"`).
373
372
  *
374
- * @example
375
- * ```typescript
376
- * super(schema, {
377
- * dialect: new PostgresDialect({ pool }),
378
- * plugins: [
379
- * new SchemaSwitcherPlugin({ schemaName: `tenant_${tenantId}` })
380
- * ]
381
- * })
382
- * ```
373
+ * Must be called BEFORE any queries are made on the client.
374
+ *
375
+ * Note: The ZenStack RuntimePlugin `onQuery` hook fires after table names are
376
+ * already resolved, so a plugin-based approach cannot set the schema prefix.
377
+ * Direct client mutation is the only supported method.
383
378
  */
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>;
379
+ declare class SchemaSwitcher {
380
+ static apply<T>(client: T, schemaName: string): T;
399
381
  }
400
382
  //#endregion
401
383
  //#region src/database/commands/zenstack.command.d.ts
@@ -456,5 +438,5 @@ declare class MigrateStatusCommand extends ZenStackCommand {
456
438
  handle(): Promise<number>;
457
439
  }
458
440
  //#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
441
+ 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 };
442
+ //# sourceMappingURL=index-CpFBG0Ws.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CpFBG0Ws.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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insufficient-permissions.error-CRnOHYvq.mjs","names":[],"sources":["../src/access-control/errors/insufficient-permissions.error.ts"],"sourcesContent":["import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\n/**\n * InsufficientPermissionsError\n *\n * Thrown when a user attempts to perform an action without the required permissions.\n * Used by AuthGuard after an authorization check fails.\n *\n * HTTP Status: 403 Forbidden\n */\nexport class InsufficientPermissionsError extends ApplicationError {\n constructor(requiredPermissions: string | string[], userId?: string) {\n const summary = Array.isArray(requiredPermissions)\n ? requiredPermissions.join(', ')\n : requiredPermissions\n super('errors.insufficientPermissions', ERROR_CODES.AUTHZ.INSUFFICIENT_PERMISSIONS, {\n requiredPermissions: summary,\n userId: userId ?? 'unknown',\n })\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,IAAa,+BAAb,cAAkD,iBAAiB;CACjE,YAAY,qBAAwC,QAAiB;EACnE,MAAM,UAAU,MAAM,QAAQ,oBAAoB,GAC9C,oBAAoB,KAAK,KAAK,GAC9B;AACJ,QAAM,kCAAkC,YAAY,MAAM,0BAA0B;GAClF,qBAAqB;GACrB,QAAQ,UAAU;GACnB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { AccessControl, Role, Statements } from "better-auth/plugins/access";
2
+
3
+ //#region src/access-control/types.d.ts
4
+ type RolePermissions<TStatements extends Statements> = { [K in keyof TStatements]?: readonly TStatements[K][number][] };
5
+ interface AccessControlOptions<TStatements extends Statements = Statements, TRoles extends Record<string, RolePermissions<TStatements>> = Record<string, RolePermissions<TStatements>>> {
6
+ ac: AccessControl;
7
+ roles: { [K in keyof TRoles]: Role };
8
+ }
9
+ //#endregion
10
+ export { RolePermissions as n, AccessControlOptions as t };
11
+ //# sourceMappingURL=types-BLyu9dAd.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-BLyu9dAd.d.mts","names":[],"sources":["../src/access-control/types.ts"],"mappings":";;;KAEY,eAAA,qBAAoC,UAAA,kBAClC,WAAA,aAAwB,WAAA,CAAY,CAAA;AAAA,UAGjC,oBAAA,qBAAyC,UAAA,GAAa,UAAA,iBAA2B,MAAA,SAAe,eAAA,CAAgB,WAAA,KAAgB,MAAA,SAAe,eAAA,CAAgB,WAAA;EAC9K,EAAA,EAAI,aAAA;EACJ,KAAA,gBAAqB,MAAA,GAAS,IAAA;AAAA"}
@@ -0,0 +1,92 @@
1
+ import { RuntimePlugin } from "@zenstackhq/orm";
2
+ import { SchemaDef } from "@zenstackhq/schema";
3
+
4
+ //#region src/database/types.d.ts
5
+ /**
6
+ * Augment with per-connection schemas, default connection, and plugin types.
7
+ *
8
+ * Each property can be augmented in a separate file — TypeScript merges them.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // db/schema.ts
13
+ * declare module '@stratal/framework/database' {
14
+ * interface StratalDatabase {
15
+ * schemas: {
16
+ * main: typeof schema
17
+ * tenant: typeof tenantSchema
18
+ * }
19
+ * defaultConnection: 'main'
20
+ * }
21
+ * }
22
+ *
23
+ * // db/plugins.ts
24
+ * declare module '@stratal/framework/database' {
25
+ * interface StratalDatabase {
26
+ * plugins: {
27
+ * main: [typeof queryResultPlugin, typeof cachePlugin]
28
+ * }
29
+ * }
30
+ * }
31
+ * ```
32
+ */
33
+ interface StratalDatabase {}
34
+ /** Extract `ExtQueryArgs` from a `RuntimePlugin` */
35
+ type ExtractPluginQueryArgs<P> = P extends RuntimePlugin<infer _S, infer Q, infer _M, infer _R> ? Q : {};
36
+ /** Extract `ExtClientMembers` from a `RuntimePlugin` */
37
+ type ExtractPluginClientMembers<P> = P extends RuntimePlugin<infer _S, infer _Q, infer M, infer _R> ? M : {};
38
+ /** Extract `ExtResult` from a `RuntimePlugin` */
39
+ type ExtractPluginResult<P> = P extends RuntimePlugin<infer _S, infer _Q, infer _M, infer R> ? R : {};
40
+ /** Recursively intersect extension types from a tuple of plugins */
41
+ type MergePlugins<Plugins extends unknown[]> = Plugins extends [infer P, ...infer Rest] ? {
42
+ extQueryArgs: ExtractPluginQueryArgs<P> & MergePlugins<Rest>['extQueryArgs'];
43
+ extClientMembers: ExtractPluginClientMembers<P> & MergePlugins<Rest>['extClientMembers'];
44
+ extResult: ExtractPluginResult<P> & MergePlugins<Rest>['extResult'];
45
+ } : {
46
+ extQueryArgs: {};
47
+ extClientMembers: {};
48
+ extResult: {};
49
+ };
50
+ /** Infer merged plugin extensions for a connection */
51
+ type InferConnectionExtensions<K extends string> = StratalDatabase extends {
52
+ plugins: infer P;
53
+ } ? K extends keyof P ? P[K] extends unknown[] ? MergePlugins<P[K]> : {
54
+ extQueryArgs: {};
55
+ extClientMembers: {};
56
+ extResult: {};
57
+ } : {
58
+ extQueryArgs: {};
59
+ extClientMembers: {};
60
+ extResult: {};
61
+ } : {
62
+ extQueryArgs: {};
63
+ extClientMembers: {};
64
+ extResult: {};
65
+ };
66
+ /** Infer schema type for a specific connection */
67
+ type InferConnectionSchema<K extends string> = StratalDatabase extends {
68
+ schemas: infer R;
69
+ } ? K extends keyof R ? R[K] extends SchemaDef ? R[K] : SchemaDef : SchemaDef : SchemaDef;
70
+ /** Union of ALL schemas across connections (for events) */
71
+ type InferAnySchema = StratalDatabase extends {
72
+ schemas: infer R;
73
+ } ? R[keyof R] extends SchemaDef ? R[keyof R] : SchemaDef : SchemaDef;
74
+ /** Connection name — derived from schemas keys */
75
+ type ConnectionName = StratalDatabase extends {
76
+ schemas: infer R;
77
+ } ? keyof R extends never ? string : Extract<keyof R, string> : string;
78
+ /** Default connection name */
79
+ type DefaultConnectionName = StratalDatabase extends {
80
+ defaultConnection: infer N extends string;
81
+ } ? N : string;
82
+ /**
83
+ * Internal context used by database service for dynamic event emission
84
+ * @internal
85
+ */
86
+ interface InternalDatabaseEventContext {
87
+ data: unknown;
88
+ result?: unknown;
89
+ }
90
+ //#endregion
91
+ export { InferConnectionSchema as a, InferConnectionExtensions as i, DefaultConnectionName as n, InternalDatabaseEventContext as o, InferAnySchema as r, StratalDatabase as s, ConnectionName as t };
92
+ //# sourceMappingURL=types-BZlcRR2M.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-BZlcRR2M.d.mts","names":[],"sources":["../src/database/types.ts"],"mappings":";;;;;;AA+BA;;;;;AAAmC;;;;;;;;;;;;;;AAIiC;;;;;;;UAJnD,eAAA;;KAGZ,sBAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,0BAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,mBAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,YAAA,8BACH,OAAA;EAEM,YAAA,EAAc,sBAAA,CAAuB,CAAA,IAAK,YAAA,CAAa,IAAA;EACvD,gBAAA,EAAkB,0BAAA,CAA2B,CAAA,IAAK,YAAA,CAAa,IAAA;EAC/D,SAAA,EAAW,mBAAA,CAAoB,CAAA,IAAK,YAAA,CAAa,IAAA;AAAA;EAEjD,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;;KAGpC,yBAAA,qBACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,eAAgB,CAAA,GACd,CAAA,CAAE,CAAA,sBACA,YAAA,CAAa,CAAA,CAAE,CAAA;EACb,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;EAC1C,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;EAC1C,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;;KAGpC,qBAAA,qBACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,eAAgB,CAAA,GAAI,CAAA,CAAE,CAAA,UAAW,SAAA,GAAY,CAAA,CAAE,CAAA,IAAK,SAAA,GAAY,SAAA,GAChE,SAAA;;KAGM,cAAA,GACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,OAAQ,CAAA,UAAW,SAAA,GAAY,CAAA,OAAQ,CAAA,IAAK,SAAA,GAC5C,SAAA;;KAGM,cAAA,GACV,eAAA;EAA0B,OAAA;AAAA,UAChB,CAAA,0BAA2B,OAAA,OAAc,CAAA;;KAIzC,qBAAA,GACV,eAAA;EAA0B,iBAAA;AAAA,IAA8C,CAAA;;;;;UAMzD,4BAAA;EACf,IAAA;EACA,MAAA;AAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratal/framework",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Temitayo Fadojutimi",
@@ -26,6 +26,10 @@
26
26
  "types": "./dist/index.d.mts",
27
27
  "import": "./dist/index.mjs"
28
28
  },
29
+ "./access-control": {
30
+ "types": "./dist/access-control/index.d.mts",
31
+ "import": "./dist/access-control/index.mjs"
32
+ },
29
33
  "./auth": {
30
34
  "types": "./dist/auth/index.d.mts",
31
35
  "import": "./dist/auth/index.mjs"
@@ -46,10 +50,6 @@
46
50
  "types": "./dist/guards/index.d.mts",
47
51
  "import": "./dist/guards/index.mjs"
48
52
  },
49
- "./rbac": {
50
- "types": "./dist/rbac/index.d.mts",
51
- "import": "./dist/rbac/index.mjs"
52
- },
53
53
  "./package.json": "./package.json"
54
54
  },
55
55
  "scripts": {
@@ -67,36 +67,36 @@
67
67
  "lint:fix": "npx oxlint --fix ."
68
68
  },
69
69
  "dependencies": {
70
- "@better-auth/core": "^1.5.6",
70
+ "@better-auth/core": "^1.6.9",
71
71
  "@faker-js/faker": "^10.4.0",
72
- "@zenstackhq/cli": "^3.5.1",
73
- "@zenstackhq/orm": "^3.5.1",
74
- "better-auth": "^1.5.6",
75
- "casbin": "^5.49.0",
72
+ "@zenstackhq/cli": "^3.6.3",
73
+ "@zenstackhq/orm": "^3.6.3",
74
+ "better-auth": "^1.6.9",
76
75
  "postgres-array": "^3.0.4"
77
76
  },
78
77
  "peerDependencies": {
79
78
  "pg": "^8.0.0",
80
79
  "reflect-metadata": "^0.2.2",
81
- "stratal": "^0.0.18"
80
+ "stratal": "^0.0.19"
82
81
  },
83
82
  "devDependencies": {
84
- "@cloudflare/vitest-pool-workers": "^0.13.5",
85
- "@cloudflare/workers-types": "4.20260317.1",
83
+ "@cloudflare/vitest-pool-workers": "^0.15.0",
84
+ "@cloudflare/workers-types": "4.20260426.1",
86
85
  "@stratal/testing": "workspace:^",
87
- "@types/node": "^25.5.0",
86
+ "@types/node": "^25.6.0",
88
87
  "@types/pg": "^8.20.0",
89
- "@vitest/coverage-istanbul": "~4.1.2",
90
- "@vitest/runner": "~4.1.2",
91
- "@vitest/snapshot": "~4.1.2",
92
- "@zenstackhq/better-auth": "^3.5.1",
88
+ "@vitest/coverage-istanbul": "~4.1.5",
89
+ "@vitest/runner": "~4.1.5",
90
+ "@vitest/snapshot": "~4.1.5",
91
+ "@zenstackhq/better-auth": "^3.6.3",
93
92
  "dotenv-cli": "^11.0.0",
93
+ "kysely": "0.28.16",
94
94
  "pg": "^8.20.0",
95
95
  "reflect-metadata": "^0.2.2",
96
96
  "stratal": "workspace:*",
97
- "tsdown": "^0.21.6",
98
- "typescript": "^6.0.2",
99
- "vitest": "~4.1.2",
100
- "wrangler": "^4.78.0"
97
+ "tsdown": "^0.21.10",
98
+ "typescript": "^6.0.3",
99
+ "vitest": "~4.1.5",
100
+ "wrangler": "^4.85.0"
101
101
  }
102
102
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-context-BD2ApWg1.d.mts","names":[],"sources":["../src/context/auth-context.ts"],"mappings":";UAMiB,QAAA;EACf,MAAA;AAAA;AAAA,cAIW,WAAA;EAAA,UACD,MAAA;EALJ;AAGR;;;EAQE,cAAA,CAAe,IAAA,EAAM,QAAA;EANX;;;;EAcV,SAAA,CAAA;EAQA;;;;EAAA,aAAA,CAAA;EA+BgB;;;EApBhB,cAAA,CAAA,GAAkB,QAAA;;;;EAYlB,eAAA,CAAA;;;;;EAQA,gBAAA,CAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-context-BfekHvM9.mjs","names":[],"sources":["../src/context/auth-context.ts"],"sourcesContent":["import { Transient, DI_TOKENS } from 'stratal/di'\nimport {\n ContextNotInitializedError,\n UserNotAuthenticatedError\n} from './errors'\n\nexport interface AuthInfo {\n userId?: string\n}\n\n@Transient(DI_TOKENS.AuthContext)\nexport class AuthContext {\n protected userId?: string\n\n /**\n * Set authentication context.\n * This should be called once per request with user information.\n */\n setAuthContext(info: AuthInfo): void {\n this.userId = info.userId\n }\n\n /**\n * Get user ID if available.\n * Returns undefined if no user is authenticated.\n */\n getUserId(): string | undefined {\n return this.userId\n }\n\n /**\n * Get user ID or throw if not authenticated.\n * Use this when authentication is required.\n */\n requireUserId(): string {\n const userId = this.getUserId()\n if (!userId) {\n throw new UserNotAuthenticatedError()\n }\n return userId\n }\n\n /**\n * Get full authentication context or throw if not initialized.\n */\n getAuthContext(): AuthInfo {\n if (!this.userId) {\n throw new ContextNotInitializedError('Authentication')\n }\n return {\n userId: this.userId\n }\n }\n\n /**\n * Check if user is authenticated.\n */\n isAuthenticated(): boolean {\n return !!this.userId\n }\n\n /**\n * Clear authentication context.\n * Useful for testing or cleanup.\n */\n clearAuthContext(): void {\n this.userId = undefined\n }\n}\n"],"mappings":";;;;AAWO,IAAA,cAAA,MAAM,YAAY;CACvB;;;;;CAMA,eAAe,MAAsB;AACnC,OAAK,SAAS,KAAK;;;;;;CAOrB,YAAgC;AAC9B,SAAO,KAAK;;;;;;CAOd,gBAAwB;EACtB,MAAM,SAAS,KAAK,WAAW;AAC/B,MAAI,CAAC,OACH,OAAM,IAAI,2BAA2B;AAEvC,SAAO;;;;;CAMT,iBAA2B;AACzB,MAAI,CAAC,KAAK,OACR,OAAM,IAAI,2BAA2B,iBAAiB;AAExD,SAAO,EACL,QAAQ,KAAK,QACd;;;;;CAMH,kBAA2B;AACzB,SAAO,CAAC,CAAC,KAAK;;;;;;CAOhB,mBAAyB;AACvB,OAAK,SAAS,KAAA;;;0BAxDjB,UAAU,UAAU,YAAY,CAAA,EAAA,YAAA"}