@simple-auth-kit/cli 1.4.3 → 1.5.1

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