@m5kdev/backend 0.5.0 → 0.7.0

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 (53) hide show
  1. package/dist/src/modules/ai/ai.service.d.ts +11 -13
  2. package/dist/src/modules/ai/ai.service.js +6 -6
  3. package/dist/src/modules/ai/ai.trpc.d.ts +1 -1
  4. package/dist/src/modules/auth/auth.lib.d.ts +8 -12
  5. package/dist/src/modules/auth/auth.lib.js +2 -2
  6. package/dist/src/modules/auth/auth.service.d.ts +17 -47
  7. package/dist/src/modules/auth/auth.service.js +79 -66
  8. package/dist/src/modules/auth/auth.trpc.d.ts +16 -16
  9. package/dist/src/modules/base/base.abstract.d.ts +3 -2
  10. package/dist/src/modules/base/base.abstract.js +10 -1
  11. package/dist/src/modules/base/base.actor.d.ts +68 -0
  12. package/dist/src/modules/base/base.actor.js +99 -0
  13. package/dist/src/modules/base/base.actor.test.d.ts +1 -0
  14. package/dist/src/modules/base/base.actor.test.js +58 -0
  15. package/dist/src/modules/base/base.grants.d.ts +3 -7
  16. package/dist/src/modules/base/base.grants.js +22 -10
  17. package/dist/src/modules/base/base.grants.test.js +16 -45
  18. package/dist/src/modules/base/base.procedure.d.ts +109 -0
  19. package/dist/src/modules/base/base.procedure.js +301 -0
  20. package/dist/src/modules/base/base.repository.d.ts +1 -0
  21. package/dist/src/modules/base/base.repository.js +12 -2
  22. package/dist/src/modules/base/base.service.d.ts +23 -23
  23. package/dist/src/modules/base/base.service.js +26 -12
  24. package/dist/src/modules/base/base.service.test.d.ts +1 -0
  25. package/dist/src/modules/base/base.service.test.js +443 -0
  26. package/dist/src/modules/billing/billing.service.d.ts +4 -25
  27. package/dist/src/modules/billing/billing.service.js +6 -6
  28. package/dist/src/modules/billing/billing.trpc.d.ts +2 -2
  29. package/dist/src/modules/billing/billing.trpc.js +4 -6
  30. package/dist/src/modules/connect/connect.repository.d.ts +3 -3
  31. package/dist/src/modules/connect/connect.service.d.ts +21 -13
  32. package/dist/src/modules/connect/connect.service.js +10 -8
  33. package/dist/src/modules/connect/connect.trpc.d.ts +2 -2
  34. package/dist/src/modules/recurrence/recurrence.service.d.ts +59 -8
  35. package/dist/src/modules/recurrence/recurrence.service.js +16 -14
  36. package/dist/src/modules/recurrence/recurrence.trpc.d.ts +3 -3
  37. package/dist/src/modules/recurrence/recurrence.trpc.js +1 -1
  38. package/dist/src/modules/social/social.service.d.ts +3 -4
  39. package/dist/src/modules/social/social.service.js +3 -3
  40. package/dist/src/modules/tag/tag.repository.js +27 -26
  41. package/dist/src/modules/tag/tag.service.d.ts +90 -15
  42. package/dist/src/modules/tag/tag.service.js +20 -12
  43. package/dist/src/modules/tag/tag.trpc.d.ts +3 -3
  44. package/dist/src/modules/workflow/workflow.service.d.ts +48 -8
  45. package/dist/src/modules/workflow/workflow.service.js +6 -6
  46. package/dist/src/modules/workflow/workflow.trpc.d.ts +2 -2
  47. package/dist/src/types.d.ts +19 -19
  48. package/dist/src/utils/trpc.d.ts +31 -41
  49. package/dist/src/utils/trpc.js +95 -0
  50. package/dist/src/utils/trpc.test.d.ts +1 -0
  51. package/dist/src/utils/trpc.test.js +154 -0
  52. package/dist/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  import { type TRPCMethods } from "../../utils/trpc";
2
2
  import type { AuthService } from "./auth.service";
3
3
  export declare function createAuthTRPC({ router, publicProcedure, privateProcedure: procedure, adminProcedure }: TRPCMethods, authService: AuthService): import("@trpc/server").TRPCBuiltRouter<{
4
- ctx: import("./auth.lib").Context;
4
+ ctx: import("../../utils/trpc").Context;
5
5
  meta: any;
6
6
  errorShape: import("@trpc/server").TRPCDefaultErrorShape;
7
7
  transformer: true;
@@ -49,10 +49,10 @@ export declare function createAuthTRPC({ router, publicProcedure, privateProcedu
49
49
  input: void;
50
50
  output: {
51
51
  id: string;
52
- status: string;
53
52
  createdAt: Date;
54
53
  updatedAt: Date | null;
55
54
  expiresAt: Date | null;
55
+ status: string;
56
56
  claimUserId: string | null;
57
57
  claimedAt: Date | null;
58
58
  claimedEmail: string | null;
@@ -67,11 +67,11 @@ export declare function createAuthTRPC({ router, publicProcedure, privateProcedu
67
67
  output: {
68
68
  id: string;
69
69
  email: string;
70
- url: string;
71
70
  createdAt: Date;
72
- userId: string;
73
71
  expiresAt: Date | null;
72
+ userId: string;
74
73
  claimId: string;
74
+ url: string;
75
75
  };
76
76
  meta: any;
77
77
  }>;
@@ -82,11 +82,11 @@ export declare function createAuthTRPC({ router, publicProcedure, privateProcedu
82
82
  output: {
83
83
  id: string;
84
84
  email: string;
85
- url: string;
86
85
  createdAt: Date;
87
- userId: string;
88
86
  expiresAt: Date | null;
87
+ userId: string;
89
88
  claimId: string;
89
+ url: string;
90
90
  }[];
91
91
  meta: any;
92
92
  }>;
@@ -139,11 +139,11 @@ export declare function createAuthTRPC({ router, publicProcedure, privateProcedu
139
139
  input: void;
140
140
  output: {
141
141
  id: string;
142
- email: string | null;
143
142
  name: string | null;
144
- status: string;
143
+ email: string | null;
145
144
  createdAt: Date;
146
145
  updatedAt: Date | null;
146
+ status: string;
147
147
  }[];
148
148
  meta: any;
149
149
  }>;
@@ -153,11 +153,11 @@ export declare function createAuthTRPC({ router, publicProcedure, privateProcedu
153
153
  };
154
154
  output: {
155
155
  id: string;
156
- email: string | null;
157
156
  name: string | null;
158
- status: string;
157
+ email: string | null;
159
158
  createdAt: Date;
160
159
  updatedAt: Date | null;
160
+ status: string;
161
161
  };
162
162
  meta: any;
163
163
  }>;
@@ -184,11 +184,11 @@ export declare function createAuthTRPC({ router, publicProcedure, privateProcedu
184
184
  };
185
185
  output: {
186
186
  id: string;
187
- email: string | null;
188
187
  name: string | null;
189
- status: string;
188
+ email: string | null;
190
189
  createdAt: Date;
191
190
  updatedAt: Date | null;
191
+ status: string;
192
192
  };
193
193
  meta: any;
194
194
  }>;
@@ -198,11 +198,11 @@ export declare function createAuthTRPC({ router, publicProcedure, privateProcedu
198
198
  };
199
199
  output: {
200
200
  id: string;
201
- email: string | null;
202
201
  name: string | null;
203
- status: string;
202
+ email: string | null;
204
203
  createdAt: Date;
205
204
  updatedAt: Date | null;
205
+ status: string;
206
206
  };
207
207
  meta: any;
208
208
  }>;
@@ -212,11 +212,11 @@ export declare function createAuthTRPC({ router, publicProcedure, privateProcedu
212
212
  };
213
213
  output: {
214
214
  id: string;
215
- email: string | null;
216
215
  name: string | null;
217
- status: string;
216
+ email: string | null;
218
217
  createdAt: Date;
219
218
  updatedAt: Date | null;
219
+ status: string;
220
220
  };
221
221
  meta: any;
222
222
  }>;
@@ -1,8 +1,8 @@
1
1
  import type { TRPC_ERROR_CODE_KEY } from "@trpc/server";
2
- import type { ServerResult, ServerResultAsync } from "./base.dto";
3
- import type { ServerErrorLayer } from "./base.types";
4
2
  import { ServerError } from "../../utils/errors";
5
3
  import { logger } from "../../utils/logger";
4
+ import type { ServerResult, ServerResultAsync } from "./base.dto";
5
+ import type { ServerErrorLayer } from "./base.types";
6
6
  export declare abstract class Base {
7
7
  layer: ServerErrorLayer;
8
8
  logger: ReturnType<typeof logger.child>;
@@ -15,4 +15,5 @@ export declare abstract class Base {
15
15
  handleUnknownError(error: unknown): ServerError;
16
16
  throwable<T>(fn: () => ServerResult<T>): ServerResult<T>;
17
17
  throwableAsync<T>(fn: () => ServerResultAsync<T>): ServerResultAsync<T>;
18
+ throwablePromise<T>(fn: () => Promise<T>, errorHandler?: (error: unknown) => ServerError): ServerResultAsync<T>;
18
19
  }
@@ -43,11 +43,20 @@ class Base {
43
43
  }
44
44
  async throwableAsync(fn) {
45
45
  try {
46
- return fn();
46
+ return await fn();
47
47
  }
48
48
  catch (error) {
49
49
  return (0, neverthrow_1.err)(this.handleUnknownError(error));
50
50
  }
51
51
  }
52
+ async throwablePromise(fn, errorHandler) {
53
+ try {
54
+ const result = await fn();
55
+ return (0, neverthrow_1.ok)(result);
56
+ }
57
+ catch (error) {
58
+ return (0, neverthrow_1.err)(errorHandler ? errorHandler(error) : this.handleUnknownError(error));
59
+ }
60
+ }
52
61
  }
53
62
  exports.Base = Base;
@@ -0,0 +1,68 @@
1
+ import type { Session, User } from "../auth/auth.lib";
2
+ export type UserActor = {
3
+ userId: string;
4
+ userRole: string;
5
+ organizationId: string | null;
6
+ organizationRole: string | null;
7
+ teamId: string | null;
8
+ teamRole: string | null;
9
+ };
10
+ export type OrganizationActor = {
11
+ userId: string;
12
+ userRole: string;
13
+ organizationId: string;
14
+ organizationRole: string;
15
+ teamId: string | null;
16
+ teamRole: string | null;
17
+ };
18
+ export type TeamActor = {
19
+ userId: string;
20
+ userRole: string;
21
+ organizationId: string;
22
+ organizationRole: string;
23
+ teamId: string;
24
+ teamRole: string;
25
+ };
26
+ export type AuthenticatedActor = UserActor | OrganizationActor | TeamActor;
27
+ /** @deprecated Prefer `AuthenticatedActor` — kept for grants and legacy call sites */
28
+ export type ServiceActor = AuthenticatedActor;
29
+ export type Actor = {
30
+ user: UserActor;
31
+ organization: OrganizationActor;
32
+ team: TeamActor;
33
+ authenticated: AuthenticatedActor;
34
+ };
35
+ export type ActorScope = "user" | "organization" | "team";
36
+ export type RequiredServiceActor<Scope extends ActorScope> = Actor[Scope];
37
+ /** Claims shape used by tests and factories */
38
+ export type ServiceActorClaims = {
39
+ userId: string;
40
+ userRole: string;
41
+ organizationId?: string | null;
42
+ organizationRole?: string | null;
43
+ teamId?: string | null;
44
+ teamRole?: string | null;
45
+ };
46
+ /** @deprecated Prefer `OrganizationActor` */
47
+ export type ServiceOrganizationActor = OrganizationActor;
48
+ /** @deprecated Prefer `TeamActor` */
49
+ export type ServiceTeamActor = TeamActor;
50
+ export declare function createActorFromContext(context: {
51
+ user: User;
52
+ session: Session;
53
+ }, scope: "team"): TeamActor;
54
+ export declare function createActorFromContext(context: {
55
+ user: User;
56
+ session: Session;
57
+ }, scope: "organization"): OrganizationActor;
58
+ export declare function createActorFromContext(context: {
59
+ user: User;
60
+ session: Session;
61
+ }, scope: "user"): UserActor;
62
+ export declare function validateActor(actor: AuthenticatedActor, scope: ActorScope): boolean;
63
+ /**
64
+ * Builds a flat actor for tests / grants without session. Validates that team scope implies organization.
65
+ */
66
+ export declare function createServiceActor(claims: ServiceActorClaims): AuthenticatedActor;
67
+ export declare function getServiceActorScope(actor: AuthenticatedActor): ActorScope;
68
+ export declare function hasServiceActorScope(actor: AuthenticatedActor, scope: ActorScope): boolean;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createActorFromContext = createActorFromContext;
4
+ exports.validateActor = validateActor;
5
+ exports.createServiceActor = createServiceActor;
6
+ exports.getServiceActorScope = getServiceActorScope;
7
+ exports.hasServiceActorScope = hasServiceActorScope;
8
+ const errors_1 = require("../../utils/errors");
9
+ function createActorFromContext(context, scope) {
10
+ if (!context.user.role) {
11
+ throw new errors_1.ServerError({
12
+ code: "BAD_REQUEST",
13
+ message: "User role not found in context",
14
+ layer: "controller",
15
+ layerName: "ActorValidation",
16
+ });
17
+ }
18
+ if (scope === "organization") {
19
+ if (!context.session.activeOrganizationId || !context.session.activeOrganizationRole) {
20
+ throw new errors_1.ServerError({
21
+ code: "FORBIDDEN",
22
+ message: "Active organization context required",
23
+ layer: "controller",
24
+ layerName: "ActorValidation",
25
+ });
26
+ }
27
+ }
28
+ if (scope === "team") {
29
+ if (!context.session.activeOrganizationId || !context.session.activeOrganizationRole) {
30
+ throw new errors_1.ServerError({
31
+ code: "FORBIDDEN",
32
+ message: "Active organization context required for team scope",
33
+ layer: "controller",
34
+ layerName: "ActorValidation",
35
+ });
36
+ }
37
+ if (!context.session.activeTeamId || !context.session.activeTeamRole) {
38
+ throw new errors_1.ServerError({
39
+ code: "FORBIDDEN",
40
+ message: "Active team context required",
41
+ layer: "controller",
42
+ layerName: "ActorValidation",
43
+ });
44
+ }
45
+ }
46
+ return {
47
+ userId: context.user.id,
48
+ userRole: context.user.role,
49
+ organizationId: context.session.activeOrganizationId,
50
+ organizationRole: context.session.activeOrganizationRole,
51
+ teamId: context.session.activeTeamId,
52
+ teamRole: context.session.activeTeamRole,
53
+ };
54
+ }
55
+ function validateActor(actor, scope) {
56
+ if (!actor.userId || !actor.userRole)
57
+ return false;
58
+ if (scope === "user")
59
+ return true;
60
+ if (scope === "organization") {
61
+ return Boolean(actor.organizationId && actor.organizationRole);
62
+ }
63
+ return Boolean(actor.organizationId && actor.organizationRole && actor.teamId && actor.teamRole);
64
+ }
65
+ /**
66
+ * Builds a flat actor for tests / grants without session. Validates that team scope implies organization.
67
+ */
68
+ function createServiceActor(claims) {
69
+ const organizationId = claims.organizationId ?? null;
70
+ const organizationRole = claims.organizationRole ?? null;
71
+ const teamId = claims.teamId ?? null;
72
+ const teamRole = claims.teamRole ?? null;
73
+ if ((teamId || teamRole) && (!organizationId || !organizationRole)) {
74
+ throw new Error("organization access before team access");
75
+ }
76
+ return {
77
+ userId: claims.userId,
78
+ userRole: claims.userRole,
79
+ organizationId,
80
+ organizationRole,
81
+ teamId,
82
+ teamRole,
83
+ };
84
+ }
85
+ function getServiceActorScope(actor) {
86
+ if (validateActor(actor, "team"))
87
+ return "team";
88
+ if (validateActor(actor, "organization"))
89
+ return "organization";
90
+ return "user";
91
+ }
92
+ function hasServiceActorScope(actor, scope) {
93
+ if (scope === "team")
94
+ return validateActor(actor, "team");
95
+ if (scope === "organization") {
96
+ return validateActor(actor, "organization") || validateActor(actor, "team");
97
+ }
98
+ return validateActor(actor, "user");
99
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const base_actor_1 = require("./base.actor");
4
+ describe("base.actor", () => {
5
+ it("creates a user actor when only user claims are present", () => {
6
+ const actor = (0, base_actor_1.createServiceActor)({
7
+ userId: "user-1",
8
+ userRole: "member",
9
+ });
10
+ expect(actor).toEqual({
11
+ userId: "user-1",
12
+ userRole: "member",
13
+ organizationId: null,
14
+ organizationRole: null,
15
+ teamId: null,
16
+ teamRole: null,
17
+ });
18
+ });
19
+ it("derives the highest available scope", () => {
20
+ expect((0, base_actor_1.getServiceActorScope)({
21
+ userId: "user-1",
22
+ userRole: "member",
23
+ organizationId: "org-1",
24
+ organizationRole: "owner",
25
+ teamId: null,
26
+ teamRole: null,
27
+ })).toBe("organization");
28
+ expect((0, base_actor_1.getServiceActorScope)({
29
+ userId: "user-1",
30
+ userRole: "member",
31
+ organizationId: "org-1",
32
+ organizationRole: "owner",
33
+ teamId: "team-1",
34
+ teamRole: "manager",
35
+ })).toBe("team");
36
+ });
37
+ it("validates hierarchy before building a team actor", () => {
38
+ expect(() => (0, base_actor_1.createServiceActor)({
39
+ userId: "user-1",
40
+ userRole: "member",
41
+ teamId: "team-1",
42
+ teamRole: "manager",
43
+ })).toThrow("organization access before team access");
44
+ });
45
+ it("checks required scope against broader actors", () => {
46
+ const actor = (0, base_actor_1.createServiceActor)({
47
+ userId: "user-1",
48
+ userRole: "member",
49
+ organizationId: "org-1",
50
+ organizationRole: "owner",
51
+ teamId: "team-1",
52
+ teamRole: "manager",
53
+ });
54
+ expect((0, base_actor_1.hasServiceActorScope)(actor, "user")).toBe(true);
55
+ expect((0, base_actor_1.hasServiceActorScope)(actor, "organization")).toBe(true);
56
+ expect((0, base_actor_1.hasServiceActorScope)(actor, "team")).toBe(true);
57
+ });
58
+ });
@@ -1,4 +1,4 @@
1
- import type { Session, User } from "../auth/auth.lib";
1
+ import type { ServiceActor } from "./base.actor";
2
2
  import type { ServerResultAsync } from "./base.dto";
3
3
  type Level = "user" | "team" | "organization";
4
4
  type Access = "all" | "own";
@@ -19,10 +19,6 @@ export type NestedGrants = Record<string, Partial<Record<Level, Record<string, R
19
19
  export type ResourceGrant = Omit<Grant, "resource">;
20
20
  export type ResourceActionGrant = Omit<ResourceGrant, "action">;
21
21
  export declare function flattenNestedGrants(nestedGrants: NestedGrants): Grant[];
22
- interface PermissionContext {
23
- session: Session;
24
- user: User;
25
- }
26
- export declare function checkPermissionSync<T extends Entity>(ctx: PermissionContext, grants: ResourceActionGrant[], entities?: T | T[]): boolean;
27
- export declare function checkPermissionAsync<T extends Entity>(ctx: PermissionContext, grants: ResourceActionGrant[], getEntities: () => ServerResultAsync<T | T[] | undefined>): ServerResultAsync<boolean>;
22
+ export declare function checkPermissionSync<T extends Entity>(actor: ServiceActor, grants: ResourceActionGrant[], entities?: T | T[]): boolean;
23
+ export declare function checkPermissionAsync<T extends Entity>(actor: ServiceActor, grants: ResourceActionGrant[], getEntities: () => ServerResultAsync<T | T[] | undefined>): ServerResultAsync<boolean>;
28
24
  export {};
@@ -92,26 +92,38 @@ function checkOwnAccess(grants, roles, contextValues, entities) {
92
92
  }
93
93
  return false;
94
94
  }
95
- function checkPermissionSync(ctx, grants, entities) {
95
+ function checkPermissionSync(actor, grants, entities) {
96
96
  if (!grants || grants.length === 0)
97
97
  return false;
98
- const { id: userId, role: userRole } = ctx.user;
99
- const { activeOrganizationRole: organizationRole, activeTeamRole: teamRole, activeOrganizationId: organizationId, activeTeamId: teamId, } = ctx.session;
100
- const roles = { userRole, teamRole, organizationRole };
101
- const contextValues = { userId, teamId, organizationId };
98
+ const roles = {
99
+ userRole: actor.userRole,
100
+ teamRole: actor.teamRole,
101
+ organizationRole: actor.organizationRole,
102
+ };
103
+ const contextValues = {
104
+ userId: actor.userId,
105
+ teamId: actor.teamId,
106
+ organizationId: actor.organizationId,
107
+ };
102
108
  // Pass 1: Check for "all" access first (no ownership check needed)
103
109
  if (hasAllAccess(grants, roles))
104
110
  return true;
105
111
  // Pass 2: Check "own" access with ownership validation
106
112
  return checkOwnAccess(grants, roles, contextValues, entities);
107
113
  }
108
- async function checkPermissionAsync(ctx, grants, getEntities) {
114
+ async function checkPermissionAsync(actor, grants, getEntities) {
109
115
  if (!grants || grants.length === 0)
110
116
  return (0, neverthrow_1.ok)(false);
111
- const { id: userId, role: userRole } = ctx.user;
112
- const { activeOrganizationRole: organizationRole, activeTeamRole: teamRole, activeOrganizationId: organizationId, activeTeamId: teamId, } = ctx.session;
113
- const roles = { userRole, teamRole, organizationRole };
114
- const contextValues = { userId, teamId, organizationId };
117
+ const roles = {
118
+ userRole: actor.userRole,
119
+ teamRole: actor.teamRole,
120
+ organizationRole: actor.organizationRole,
121
+ };
122
+ const contextValues = {
123
+ userId: actor.userId,
124
+ teamId: actor.teamId,
125
+ organizationId: actor.organizationId,
126
+ };
115
127
  // Pass 1: Check for "all" access first (no entity fetch needed)
116
128
  if (hasAllAccess(grants, roles))
117
129
  return (0, neverthrow_1.ok)(true);
@@ -1,53 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const neverthrow_1 = require("neverthrow");
4
- const base_grants_1 = require("./base.grants");
5
4
  const errors_1 = require("../../utils/errors");
6
- // ============================================
7
- // Mock Factories
8
- // ============================================
9
- function createMockUser(overrides = {}) {
10
- return {
11
- id: "user-123",
12
- role: "member",
13
- email: "test@example.com",
14
- emailVerified: true,
15
- name: "Test User",
16
- createdAt: new Date(),
17
- updatedAt: new Date(),
18
- image: null,
19
- onboarding: null,
20
- preferences: null,
21
- flags: null,
22
- stripeCustomerId: null,
23
- paymentCustomerId: null,
24
- paymentPlanTier: null,
25
- paymentPlanExpiresAt: null,
26
- ...overrides,
27
- };
28
- }
29
- function createMockSession(overrides = {}) {
30
- return {
31
- id: "session-123",
32
- userId: "user-123",
33
- token: "token-123",
34
- expiresAt: new Date(Date.now() + 86400000),
35
- createdAt: new Date(),
36
- updatedAt: new Date(),
37
- ipAddress: null,
38
- userAgent: null,
39
- activeOrganizationId: null,
40
- activeTeamId: null,
41
- activeOrganizationRole: null,
42
- activeTeamRole: null,
43
- ...overrides,
44
- };
45
- }
5
+ const base_actor_1 = require("./base.actor");
6
+ const base_grants_1 = require("./base.grants");
46
7
  function createMockContext(userOverrides = {}, sessionOverrides = {}) {
47
- return {
48
- user: createMockUser(userOverrides),
49
- session: createMockSession(sessionOverrides),
50
- };
8
+ const organizationId = sessionOverrides.activeOrganizationId ?? (sessionOverrides.activeTeamId ? "org-123" : null);
9
+ const organizationRole = sessionOverrides.activeOrganizationRole ?? (sessionOverrides.activeTeamRole ? "member" : null);
10
+ const actor = (0, base_actor_1.createServiceActor)({
11
+ userId: userOverrides.id ?? "user-123",
12
+ userRole: userOverrides.role ?? "member",
13
+ organizationId,
14
+ organizationRole,
15
+ teamId: sessionOverrides.activeTeamId ?? null,
16
+ teamRole: sessionOverrides.activeTeamRole ?? null,
17
+ });
18
+ if (!actor) {
19
+ throw new Error("Expected actor");
20
+ }
21
+ return actor;
51
22
  }
52
23
  function createMockEntity(overrides = {}) {
53
24
  return {
@@ -0,0 +1,109 @@
1
+ import type { QueryInput } from "@m5kdev/commons/modules/schemas/query.schema";
2
+ import type { TRPC_ERROR_CODE_KEY } from "@trpc/server";
3
+ import type { ServerError } from "../../utils/errors";
4
+ import type { logger } from "../../utils/logger";
5
+ import type { Base } from "./base.abstract";
6
+ import { type Actor, type ActorScope, type AuthenticatedActor } from "./base.actor";
7
+ import type { ServerResult, ServerResultAsync } from "./base.dto";
8
+ import type { Entity, ResourceActionGrant } from "./base.grants";
9
+ type ServiceLogger = ReturnType<typeof logger.child>;
10
+ type RepositoryMap = Record<string, Base>;
11
+ type ServiceMap = Record<string, Base>;
12
+ export type ServiceProcedureContext = {
13
+ actor?: AuthenticatedActor | null;
14
+ } & Record<string, unknown>;
15
+ export type ServiceProcedureState = Record<string, unknown>;
16
+ export type ServiceProcedureStoredValue<T> = [T] extends [undefined] ? undefined : Awaited<T>;
17
+ export type ServiceProcedureResultLike<T> = T | ServerResult<T> | Promise<T | ServerResult<T>>;
18
+ export type ServiceProcedureContextFilterScope = ActorScope;
19
+ export type ServiceProcedureContextFilteredInput<TInput> = Extract<NonNullable<TInput>, QueryInput>;
20
+ type ServiceProcedureAuthContext<Scope extends ActorScope> = {
21
+ actor: Actor[Scope];
22
+ };
23
+ type ServiceProcedureRequiredScopeFromFilter<TInclude extends readonly ServiceProcedureContextFilterScope[] | undefined> = TInclude extends readonly ServiceProcedureContextFilterScope[] ? "team" extends TInclude[number] ? "team" : "organization" extends TInclude[number] ? "organization" : "user" : "user";
24
+ export type ServiceProcedure<TInput, TCtx extends ServiceProcedureContext, TOutput> = (input: TInput, ctx: TCtx) => ServerResultAsync<TOutput>;
25
+ export type ServiceProcedureArgs<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState> = {
26
+ input: TInput;
27
+ ctx: TCtx;
28
+ state: State;
29
+ repository: Repositories;
30
+ service: Services;
31
+ logger: ServiceLogger;
32
+ };
33
+ export type ServiceProcedureStep<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState, TOutput = undefined> = (args: ServiceProcedureArgs<TInput, TCtx, Repositories, Services, State>) => ServiceProcedureResultLike<ServiceProcedureStoredValue<TOutput>>;
34
+ export type ServiceProcedureInputMapper<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState, TNextInput> = (args: ServiceProcedureArgs<TInput, TCtx, Repositories, Services, State>) => ServiceProcedureResultLike<ServiceProcedureStoredValue<TNextInput>>;
35
+ export type ServiceProcedureHandler<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState, TOutput> = (args: ServiceProcedureArgs<TInput, TCtx, Repositories, Services, State>) => ServiceProcedureResultLike<TOutput>;
36
+ export type ServiceProcedureEntityResolver<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState, TEntities extends Entity | Entity[] | undefined> = TEntities | ((args: ServiceProcedureArgs<TInput, TCtx, Repositories, Services, State>) => ServiceProcedureResultLike<TEntities>);
37
+ type ServiceProcedureAccessBaseConfig = {
38
+ action: string;
39
+ grants?: ResourceActionGrant[];
40
+ };
41
+ export type ServiceProcedureEntityStepName<State extends ServiceProcedureState> = Extract<{
42
+ [Key in keyof State]: State[Key] extends Entity | Entity[] | undefined ? Key : never;
43
+ }[keyof State], string>;
44
+ export type ServiceProcedureAccessEntitiesConfig<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState, TEntities extends Entity | Entity[] | undefined = undefined> = ServiceProcedureAccessBaseConfig & {
45
+ entities?: ServiceProcedureEntityResolver<TInput, TCtx, Repositories, Services, State, TEntities>;
46
+ entityStep?: never;
47
+ };
48
+ export type ServiceProcedureAccessStateConfig<State extends ServiceProcedureState, StepName extends ServiceProcedureEntityStepName<State>> = ServiceProcedureAccessBaseConfig & {
49
+ entityStep: StepName;
50
+ entities?: never;
51
+ };
52
+ export type ServiceProcedureAccessConfig<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState, TEntities extends Entity | Entity[] | undefined = undefined> = ServiceProcedureAccessEntitiesConfig<TInput, TCtx, Repositories, Services, State, TEntities> | ServiceProcedureAccessStateConfig<State, ServiceProcedureEntityStepName<State>>;
53
+ export interface ServiceProcedureBuilder<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState = Record<string, never>> {
54
+ use<StepName extends string, TOutput = void>(stepName: StepName, step: ServiceProcedureStep<TInput, TCtx, Repositories, Services, State, TOutput>): ServiceProcedureBuilder<TInput, TCtx, Repositories, Services, State & Record<StepName, ServiceProcedureStoredValue<TOutput>>>;
55
+ mapInput<StepName extends string, TNextInput>(stepName: StepName, step: ServiceProcedureInputMapper<TInput, TCtx, Repositories, Services, State, TNextInput>): ServiceProcedureBuilder<ServiceProcedureStoredValue<TNextInput>, TCtx, Repositories, Services, State & Record<StepName, ServiceProcedureStoredValue<TNextInput>>>;
56
+ addContextFilter<TInclude extends readonly ServiceProcedureContextFilterScope[] | undefined = undefined>(include?: TInclude): ServiceProcedureBuilder<ServiceProcedureContextFilteredInput<TInput>, TCtx & ServiceProcedureAuthContext<ServiceProcedureRequiredScopeFromFilter<TInclude>>, Repositories, Services, State & {
57
+ contextFilter: ServiceProcedureContextFilteredInput<TInput>;
58
+ }>;
59
+ requireAuth<Scope extends ActorScope = "user">(scope?: Scope): ServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<Scope>, Repositories, Services, State>;
60
+ handle<TOutput>(handler: ServiceProcedureHandler<TInput, TCtx, Repositories, Services, State, TOutput>): ServiceProcedure<TInput, TCtx, TOutput>;
61
+ }
62
+ export interface PermissionServiceProcedureBuilder<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState = Record<string, never>> extends ServiceProcedureBuilder<TInput, TCtx, Repositories, Services, State> {
63
+ use<StepName extends string, TOutput = void>(stepName: StepName, step: ServiceProcedureStep<TInput, TCtx, Repositories, Services, State, TOutput>): PermissionServiceProcedureBuilder<TInput, TCtx, Repositories, Services, State & Record<StepName, ServiceProcedureStoredValue<TOutput>>>;
64
+ mapInput<StepName extends string, TNextInput>(stepName: StepName, step: ServiceProcedureInputMapper<TInput, TCtx, Repositories, Services, State, TNextInput>): PermissionServiceProcedureBuilder<ServiceProcedureStoredValue<TNextInput>, TCtx, Repositories, Services, State & Record<StepName, ServiceProcedureStoredValue<TNextInput>>>;
65
+ addContextFilter<TInclude extends readonly ServiceProcedureContextFilterScope[] | undefined = undefined>(include?: TInclude): PermissionServiceProcedureBuilder<ServiceProcedureContextFilteredInput<TInput>, TCtx & ServiceProcedureAuthContext<ServiceProcedureRequiredScopeFromFilter<TInclude>>, Repositories, Services, State & {
66
+ contextFilter: ServiceProcedureContextFilteredInput<TInput>;
67
+ }>;
68
+ requireAuth<Scope extends ActorScope = "user">(scope?: Scope): PermissionServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<Scope>, Repositories, Services, State>;
69
+ access(config: ServiceProcedureAccessEntitiesConfig<TInput, TCtx, Repositories, Services, State>): PermissionServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<"user">, Repositories, Services, State>;
70
+ access<TEntities extends Entity | Entity[] | undefined>(config: ServiceProcedureAccessEntitiesConfig<TInput, TCtx, Repositories, Services, State, TEntities>): PermissionServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<"user">, Repositories, Services, State & {
71
+ access: TEntities;
72
+ }>;
73
+ access<StepName extends ServiceProcedureEntityStepName<State>>(config: ServiceProcedureAccessStateConfig<State, StepName>): PermissionServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<"user">, Repositories, Services, State & {
74
+ access: State[StepName];
75
+ }>;
76
+ }
77
+ type BaseServiceProcedureHost<Repositories extends RepositoryMap, Services extends ServiceMap> = {
78
+ repository: Repositories;
79
+ service: Services;
80
+ logger: ServiceLogger;
81
+ addContextFilter(actor: AuthenticatedActor, include?: {
82
+ user?: boolean;
83
+ organization?: boolean;
84
+ team?: boolean;
85
+ }, query?: QueryInput): QueryInput;
86
+ error(code: TRPC_ERROR_CODE_KEY, message?: string, options?: {
87
+ cause?: unknown;
88
+ clientMessage?: string;
89
+ log?: boolean;
90
+ }): ServerResult<never>;
91
+ throwableAsync<T>(fn: () => ServerResultAsync<T>): ServerResultAsync<T>;
92
+ handleUnknownError(error: unknown): ServerError;
93
+ };
94
+ type PermissionServiceProcedureHost<Repositories extends RepositoryMap, Services extends ServiceMap> = BaseServiceProcedureHost<Repositories, Services> & {
95
+ checkPermission<T extends Entity>(actor: AuthenticatedActor, action: string, entities?: T | T[], grants?: ResourceActionGrant[]): boolean;
96
+ checkPermissionAsync<T extends Entity>(actor: AuthenticatedActor, action: string, getEntities: () => ServerResultAsync<T | T[] | undefined>, grants?: ResourceActionGrant[]): ServerResultAsync<boolean>;
97
+ };
98
+ type ProcedureRuntimeStep<Repositories extends RepositoryMap, Services extends ServiceMap> = {
99
+ stage: "use" | "input" | "auth" | "access";
100
+ stepName: string;
101
+ run: (args: ServiceProcedureArgs<unknown, ServiceProcedureContext, Repositories, Services, ServiceProcedureState>) => Promise<ServerResult<unknown>>;
102
+ };
103
+ type ProcedureBuilderConfig<Repositories extends RepositoryMap, Services extends ServiceMap> = {
104
+ name: string;
105
+ steps: ProcedureRuntimeStep<Repositories, Services>[];
106
+ };
107
+ export declare function createServiceProcedureBuilder<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState = Record<string, never>>(host: BaseServiceProcedureHost<Repositories, Services>, config: ProcedureBuilderConfig<Repositories, Services>): ServiceProcedureBuilder<TInput, TCtx, Repositories, Services, State>;
108
+ export declare function createPermissionServiceProcedureBuilder<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState = Record<string, never>>(host: PermissionServiceProcedureHost<Repositories, Services>, config: ProcedureBuilderConfig<Repositories, Services>): PermissionServiceProcedureBuilder<TInput, TCtx, Repositories, Services, State>;
109
+ export {};