@simple-auth-kit/cli 1.4.3 → 1.5.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 (133) hide show
  1. package/package.json +1 -1
  2. package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  3. package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
  4. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  5. package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
  6. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  7. package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  8. package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
  9. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  10. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
  11. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
  12. package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  13. package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
  14. package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
  15. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
  16. package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
  17. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
  18. package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  19. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
  20. package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
  21. package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
  22. package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
  23. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
  24. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
  25. package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
  26. package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  27. package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
  28. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  29. package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
  30. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  31. package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  32. package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  33. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  34. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
  35. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
  36. package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  37. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
  38. package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  39. package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
  40. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
  41. package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
  42. package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
  43. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
  44. package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  45. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
  46. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
  47. package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  48. package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
  49. package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
  50. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
  51. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
  52. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
  53. package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
  54. package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  55. package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  56. package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  57. package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  58. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
  59. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
  60. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  61. package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  62. package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
  63. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  64. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
  65. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
  66. package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
  67. package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  68. package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
  69. package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
  70. package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
  71. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
  72. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
  73. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  74. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  75. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
  76. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
  77. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
  78. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
  79. package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  80. package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  81. package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  82. package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  83. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
  84. package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
  85. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  86. package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  87. package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  88. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  89. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
  90. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
  91. package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
  92. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
  93. package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  94. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
  95. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  96. package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
  97. package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
  98. package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
  99. package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
  100. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
  101. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
  102. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
  103. package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
  104. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  105. package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  106. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
  107. package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  108. package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
  109. package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
  110. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
  111. package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
  112. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
  113. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
  114. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
  115. package/registry.json +4 -4
  116. package/simple-auth-kit.ts +29 -2
  117. package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
  118. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
  119. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
  120. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
  121. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
  122. package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
  123. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
  124. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
  125. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
  126. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
  127. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
  128. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
  129. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
  130. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
  131. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
  132. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
  133. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
@@ -1,10 +1,19 @@
1
1
  import { and, asc, eq, inArray } from "drizzle-orm";
2
- import type { Database } from "./db.js";
3
- import { provisionDefaultRoles, WORKSPACE_CREATOR_ROLES } from "./rbac.defaults.js";
2
+ import type { Database } from "../db.js";
3
+ import {
4
+ provisionDefaultRoles,
5
+ WORKSPACE_CREATOR_ROLES,
6
+ } from "../rbac.defaults.js";
4
7
  import { RbacRepository } from "./rbac.repository.js";
5
- import { roleMember, roles, users, workspaceMembers, workspaces } from "./schema.js";
6
- import { HttpError } from "./http-error.js";
7
- import { toId } from "./id.helper.js";
8
+ import {
9
+ roleMember,
10
+ roles,
11
+ users,
12
+ workspaceMembers,
13
+ workspaces,
14
+ } from "../schema.js";
15
+ import { HttpError } from "../http-error.js";
16
+ import { toId } from "../id.helper.js";
8
17
 
9
18
  export interface WorkspaceSummary {
10
19
  id: string;
@@ -42,16 +51,34 @@ export class WorkspaceRepository {
42
51
  async create(userId: string, name: string): Promise<WorkspaceSummary> {
43
52
  const userIdBig = toId(userId);
44
53
  const workspace = await this.db.transaction(async (tx) => {
45
- const [created] = await tx.insert(workspaces).values({ name, createdBy: userIdBig, updatedBy: userIdBig }).returning();
54
+ const [created] = await tx
55
+ .insert(workspaces)
56
+ .values({ name, createdBy: userIdBig, updatedBy: userIdBig })
57
+ .returning();
46
58
  await provisionDefaultRoles(tx, created.id);
47
59
 
48
60
  const creatorRoles = await tx
49
61
  .select({ id: roles.id })
50
62
  .from(roles)
51
- .where(and(eq(roles.workspaceId, created.id), inArray(roles.slug, WORKSPACE_CREATOR_ROLES)));
52
- const [member] = await tx.insert(workspaceMembers).values({ workspaceId: created.id, userId: userIdBig }).returning({ id: workspaceMembers.id });
63
+ .where(
64
+ and(
65
+ eq(roles.workspaceId, created.id),
66
+ inArray(roles.slug, WORKSPACE_CREATOR_ROLES),
67
+ ),
68
+ );
69
+ const [member] = await tx
70
+ .insert(workspaceMembers)
71
+ .values({ workspaceId: created.id, userId: userIdBig })
72
+ .returning({ id: workspaceMembers.id });
53
73
  if (creatorRoles.length) {
54
- await tx.insert(roleMember).values(creatorRoles.map((role) => ({ memberId: member.id, roleId: role.id })));
74
+ await tx
75
+ .insert(roleMember)
76
+ .values(
77
+ creatorRoles.map((role) => ({
78
+ memberId: member.id,
79
+ roleId: role.id,
80
+ })),
81
+ );
55
82
  }
56
83
  return created;
57
84
  });
@@ -73,11 +100,19 @@ export class WorkspaceRepository {
73
100
  createdAt: workspaces.createdAt,
74
101
  })
75
102
  .from(workspaceMembers)
76
- .innerJoin(workspaces, and(eq(workspaces.id, workspaceMembers.workspaceId), eq(workspaces.isDeleted, false)))
103
+ .innerJoin(
104
+ workspaces,
105
+ and(
106
+ eq(workspaces.id, workspaceMembers.workspaceId),
107
+ eq(workspaces.isDeleted, false),
108
+ ),
109
+ )
77
110
  .where(eq(workspaceMembers.userId, toId(userId)))
78
111
  .orderBy(asc(workspaceMembers.createdAt));
79
112
 
80
- const rolesByMember = await this.rolesByMember(memberships.map((m) => m.memberId));
113
+ const rolesByMember = await this.rolesByMember(
114
+ memberships.map((m) => m.memberId),
115
+ );
81
116
  return memberships.map((m) => ({
82
117
  id: m.id.toString(),
83
118
  name: m.name,
@@ -95,7 +130,10 @@ export class WorkspaceRepository {
95
130
  email: users.email,
96
131
  })
97
132
  .from(workspaceMembers)
98
- .innerJoin(users, and(eq(users.id, workspaceMembers.userId), eq(users.isDeleted, false)))
133
+ .innerJoin(
134
+ users,
135
+ and(eq(users.id, workspaceMembers.userId), eq(users.isDeleted, false)),
136
+ )
99
137
  .where(eq(workspaceMembers.workspaceId, toId(workspaceId)))
100
138
  .orderBy(asc(workspaceMembers.createdAt));
101
139
 
@@ -115,17 +153,33 @@ export class WorkspaceRepository {
115
153
  * Shared by `addMember` (an existing account) and `createMember` (a brand-new one), so both
116
154
  * fail the same way against an unknown role slug.
117
155
  */
118
- private async resolveMemberRoles(workspaceIdBig: bigint, roleSlugs?: string[]): Promise<Array<{ id: bigint; slug: string }>> {
156
+ private async resolveMemberRoles(
157
+ workspaceIdBig: bigint,
158
+ roleSlugs?: string[],
159
+ ): Promise<Array<{ id: bigint; slug: string }>> {
119
160
  const granted = await this.db
120
161
  .select({ id: roles.id, slug: roles.slug })
121
162
  .from(roles)
122
163
  .where(
123
164
  roleSlugs
124
- ? and(eq(roles.workspaceId, workspaceIdBig), inArray(roles.slug, roleSlugs))
125
- : and(eq(roles.workspaceId, workspaceIdBig), eq(roles.isDefault, true), eq(roles.isActive, true)),
165
+ ? and(
166
+ eq(roles.workspaceId, workspaceIdBig),
167
+ inArray(roles.slug, roleSlugs),
168
+ )
169
+ : and(
170
+ eq(roles.workspaceId, workspaceIdBig),
171
+ eq(roles.isDefault, true),
172
+ eq(roles.isActive, true),
173
+ ),
174
+ );
175
+ const unknown = (roleSlugs ?? []).filter(
176
+ (slug) => !granted.some((role) => role.slug === slug),
177
+ );
178
+ if (unknown.length)
179
+ throw new HttpError(
180
+ 404,
181
+ `role(s) not defined in this workspace: ${unknown.join(", ")}`,
126
182
  );
127
- const unknown = (roleSlugs ?? []).filter((slug) => !granted.some((role) => role.slug === slug));
128
- if (unknown.length) throw new HttpError(404, `role(s) not defined in this workspace: ${unknown.join(", ")}`);
129
183
  return granted;
130
184
  }
131
185
 
@@ -134,23 +188,45 @@ export class WorkspaceRepository {
134
188
  * consuming app's job. With no roles named, the new membership gets whichever of this
135
189
  * workspace's roles are flagged `isDefault`, not a slug spelled in code.
136
190
  */
137
- async addMember(workspaceId: string, email: string, roleSlugs?: string[]): Promise<MembershipSummary> {
191
+ async addMember(
192
+ workspaceId: string,
193
+ email: string,
194
+ roleSlugs?: string[],
195
+ ): Promise<MembershipSummary> {
138
196
  const workspaceIdBig = toId(workspaceId);
139
- const [user] = await this.db.select({ id: users.id, email: users.email }).from(users).where(eq(users.email, email)).limit(1);
197
+ const [user] = await this.db
198
+ .select({ id: users.id, email: users.email })
199
+ .from(users)
200
+ .where(eq(users.email, email))
201
+ .limit(1);
140
202
  if (!user) throw new HttpError(404, "no user with that email");
141
203
 
142
204
  const [existing] = await this.db
143
205
  .select({ id: workspaceMembers.id })
144
206
  .from(workspaceMembers)
145
- .where(and(eq(workspaceMembers.userId, user.id), eq(workspaceMembers.workspaceId, workspaceIdBig)))
207
+ .where(
208
+ and(
209
+ eq(workspaceMembers.userId, user.id),
210
+ eq(workspaceMembers.workspaceId, workspaceIdBig),
211
+ ),
212
+ )
146
213
  .limit(1);
147
- if (existing) throw new HttpError(409, "already a member of this workspace");
214
+ if (existing)
215
+ throw new HttpError(409, "already a member of this workspace");
148
216
 
149
217
  const granted = await this.resolveMemberRoles(workspaceIdBig, roleSlugs);
150
218
 
151
219
  const member = await this.db.transaction(async (tx) => {
152
- const [created] = await tx.insert(workspaceMembers).values({ workspaceId: workspaceIdBig, userId: user.id }).returning();
153
- if (granted.length) await tx.insert(roleMember).values(granted.map((role) => ({ memberId: created.id, roleId: role.id })));
220
+ const [created] = await tx
221
+ .insert(workspaceMembers)
222
+ .values({ workspaceId: workspaceIdBig, userId: user.id })
223
+ .returning();
224
+ if (granted.length)
225
+ await tx
226
+ .insert(roleMember)
227
+ .values(
228
+ granted.map((role) => ({ memberId: created.id, roleId: role.id })),
229
+ );
154
230
  return created;
155
231
  });
156
232
 
@@ -183,7 +259,11 @@ export class WorkspaceRepository {
183
259
  actorUserId: string | null,
184
260
  ): Promise<MembershipSummary> {
185
261
  const workspaceIdBig = toId(workspaceId);
186
- const [existing] = await this.db.select({ id: users.id }).from(users).where(eq(users.email, input.email)).limit(1);
262
+ const [existing] = await this.db
263
+ .select({ id: users.id })
264
+ .from(users)
265
+ .where(eq(users.email, input.email))
266
+ .limit(1);
187
267
  if (existing) throw new HttpError(409, "email already registered");
188
268
 
189
269
  const granted = await this.resolveMemberRoles(workspaceIdBig, input.roles);
@@ -202,8 +282,16 @@ export class WorkspaceRepository {
202
282
  createdBy: actorUserId ? toId(actorUserId) : null,
203
283
  })
204
284
  .returning();
205
- const [created] = await tx.insert(workspaceMembers).values({ workspaceId: workspaceIdBig, userId: user.id }).returning();
206
- if (granted.length) await tx.insert(roleMember).values(granted.map((role) => ({ memberId: created.id, roleId: role.id })));
285
+ const [created] = await tx
286
+ .insert(workspaceMembers)
287
+ .values({ workspaceId: workspaceIdBig, userId: user.id })
288
+ .returning();
289
+ if (granted.length)
290
+ await tx
291
+ .insert(roleMember)
292
+ .values(
293
+ granted.map((role) => ({ memberId: created.id, roleId: role.id })),
294
+ );
207
295
  return { member: created, user };
208
296
  });
209
297
 
@@ -220,23 +308,32 @@ export class WorkspaceRepository {
220
308
  const workspaceIdBig = toId(workspaceId);
221
309
  const memberIdBig = toId(memberId);
222
310
  const [member] = await this.db
223
- .select({ id: workspaceMembers.id, userId: workspaceMembers.userId, workspaceId: workspaceMembers.workspaceId })
311
+ .select({
312
+ id: workspaceMembers.id,
313
+ userId: workspaceMembers.userId,
314
+ workspaceId: workspaceMembers.workspaceId,
315
+ })
224
316
  .from(workspaceMembers)
225
317
  .where(eq(workspaceMembers.id, memberIdBig))
226
318
  .limit(1);
227
- if (!member || member.workspaceId !== workspaceIdBig) throw new HttpError(404, "member not found in this workspace");
319
+ if (!member || member.workspaceId !== workspaceIdBig)
320
+ throw new HttpError(404, "member not found in this workspace");
228
321
 
229
322
  // Role assignments and direct grants belong to the membership, so they go with it — that is
230
323
  // the point of hanging them off the member row. `onDelete: "cascade"` on both join tables is
231
324
  // what makes that a database guarantee rather than two deletes someone has to remember.
232
- await this.db.delete(workspaceMembers).where(eq(workspaceMembers.id, memberIdBig));
325
+ await this.db
326
+ .delete(workspaceMembers)
327
+ .where(eq(workspaceMembers.id, memberIdBig));
233
328
  // A removed member must lose access on their very next request, so their entry has to become
234
329
  // unreachable — the resolution that replaces it returns "not a member", which is never cached.
235
330
  await this.rbac.invalidateMember(member.userId.toString(), workspaceId);
236
331
  }
237
332
 
238
333
  /** One read for a page's role assignments rather than one per membership. */
239
- private async rolesByMember(memberIds: bigint[]): Promise<Map<string, string[]>> {
334
+ private async rolesByMember(
335
+ memberIds: bigint[],
336
+ ): Promise<Map<string, string[]>> {
240
337
  if (!memberIds.length) return new Map();
241
338
  const rows = await this.db
242
339
  .select({ memberId: roleMember.memberId, slug: roles.slug })