@shipfox/api-workspaces 10.2.0 → 12.0.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 (167) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +27 -4
  4. package/dist/core/entities/workspace.d.ts +1 -0
  5. package/dist/core/entities/workspace.d.ts.map +1 -1
  6. package/dist/core/entities/workspace.js.map +1 -1
  7. package/dist/core/errors.d.ts +3 -0
  8. package/dist/core/errors.d.ts.map +1 -1
  9. package/dist/core/errors.js +6 -0
  10. package/dist/core/errors.js.map +1 -1
  11. package/dist/core/index.d.ts +2 -2
  12. package/dist/core/index.d.ts.map +1 -1
  13. package/dist/core/index.js +2 -2
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/core/invitations.d.ts.map +1 -1
  16. package/dist/core/invitations.js +7 -4
  17. package/dist/core/invitations.js.map +1 -1
  18. package/dist/core/rate-limit.d.ts +39 -0
  19. package/dist/core/rate-limit.d.ts.map +1 -0
  20. package/dist/core/rate-limit.js +70 -0
  21. package/dist/core/rate-limit.js.map +1 -0
  22. package/dist/core/workspaces.d.ts +8 -0
  23. package/dist/core/workspaces.d.ts.map +1 -1
  24. package/dist/core/workspaces.js +47 -7
  25. package/dist/core/workspaces.js.map +1 -1
  26. package/dist/db/db.d.ts +320 -0
  27. package/dist/db/db.d.ts.map +1 -1
  28. package/dist/db/db.js +3 -1
  29. package/dist/db/db.js.map +1 -1
  30. package/dist/db/index.d.ts +4 -1
  31. package/dist/db/index.d.ts.map +1 -1
  32. package/dist/db/index.js +3 -1
  33. package/dist/db/index.js.map +1 -1
  34. package/dist/db/memberships.d.ts +1 -0
  35. package/dist/db/memberships.d.ts.map +1 -1
  36. package/dist/db/memberships.js +2 -0
  37. package/dist/db/memberships.js.map +1 -1
  38. package/dist/db/rate-limits.d.ts +9 -0
  39. package/dist/db/rate-limits.d.ts.map +1 -0
  40. package/dist/db/rate-limits.js +43 -0
  41. package/dist/db/rate-limits.js.map +1 -0
  42. package/dist/db/schema/rate-limits.d.ts +145 -0
  43. package/dist/db/schema/rate-limits.d.ts.map +1 -0
  44. package/dist/db/schema/rate-limits.js +25 -0
  45. package/dist/db/schema/rate-limits.js.map +1 -0
  46. package/dist/db/schema/workspaces.d.ts +17 -0
  47. package/dist/db/schema/workspaces.d.ts.map +1 -1
  48. package/dist/db/schema/workspaces.js +5 -2
  49. package/dist/db/schema/workspaces.js.map +1 -1
  50. package/dist/db/workspaces.d.ts +11 -1
  51. package/dist/db/workspaces.d.ts.map +1 -1
  52. package/dist/db/workspaces.js +12 -3
  53. package/dist/db/workspaces.js.map +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1 -1
  57. package/dist/index.js.map +1 -1
  58. package/dist/metrics/instance.d.ts +6 -0
  59. package/dist/metrics/instance.d.ts.map +1 -1
  60. package/dist/metrics/instance.js +12 -0
  61. package/dist/metrics/instance.js.map +1 -1
  62. package/dist/presentation/dto/membership.d.ts.map +1 -1
  63. package/dist/presentation/dto/membership.js +1 -0
  64. package/dist/presentation/dto/membership.js.map +1 -1
  65. package/dist/presentation/dto/workspace.d.ts.map +1 -1
  66. package/dist/presentation/dto/workspace.js +1 -0
  67. package/dist/presentation/dto/workspace.js.map +1 -1
  68. package/dist/presentation/e2eRoutes/create-workspace.d.ts.map +1 -1
  69. package/dist/presentation/e2eRoutes/create-workspace.js +11 -1
  70. package/dist/presentation/e2eRoutes/create-workspace.js.map +1 -1
  71. package/dist/presentation/inter-module.d.ts.map +1 -1
  72. package/dist/presentation/inter-module.js +3 -2
  73. package/dist/presentation/inter-module.js.map +1 -1
  74. package/dist/presentation/routes/index.d.ts.map +1 -1
  75. package/dist/presentation/routes/index.js +3 -1
  76. package/dist/presentation/routes/index.js.map +1 -1
  77. package/dist/presentation/routes/invitations/create.d.ts.map +1 -1
  78. package/dist/presentation/routes/invitations/create.js +5 -1
  79. package/dist/presentation/routes/invitations/create.js.map +1 -1
  80. package/dist/presentation/routes/invitations/list.d.ts.map +1 -1
  81. package/dist/presentation/routes/invitations/list.js +5 -1
  82. package/dist/presentation/routes/invitations/list.js.map +1 -1
  83. package/dist/presentation/routes/invitations/revoke.d.ts.map +1 -1
  84. package/dist/presentation/routes/invitations/revoke.js +5 -1
  85. package/dist/presentation/routes/invitations/revoke.js.map +1 -1
  86. package/dist/presentation/routes/members/list.d.ts.map +1 -1
  87. package/dist/presentation/routes/members/list.js +5 -1
  88. package/dist/presentation/routes/members/list.js.map +1 -1
  89. package/dist/presentation/routes/members/remove.d.ts.map +1 -1
  90. package/dist/presentation/routes/members/remove.js +5 -1
  91. package/dist/presentation/routes/members/remove.js.map +1 -1
  92. package/dist/presentation/routes/rate-limit.d.ts +3 -0
  93. package/dist/presentation/routes/rate-limit.d.ts.map +1 -0
  94. package/dist/presentation/routes/rate-limit.js +42 -0
  95. package/dist/presentation/routes/rate-limit.js.map +1 -0
  96. package/dist/presentation/routes/workspaces/create.d.ts.map +1 -1
  97. package/dist/presentation/routes/workspaces/create.js +11 -1
  98. package/dist/presentation/routes/workspaces/create.js.map +1 -1
  99. package/dist/presentation/routes/workspaces/index.d.ts +2 -0
  100. package/dist/presentation/routes/workspaces/index.d.ts.map +1 -1
  101. package/dist/presentation/routes/workspaces/index.js +2 -0
  102. package/dist/presentation/routes/workspaces/index.js.map +1 -1
  103. package/dist/presentation/routes/workspaces/slug-availability.d.ts +2 -0
  104. package/dist/presentation/routes/workspaces/slug-availability.d.ts.map +1 -0
  105. package/dist/presentation/routes/workspaces/slug-availability.js +31 -0
  106. package/dist/presentation/routes/workspaces/slug-availability.js.map +1 -0
  107. package/dist/presentation/routes/workspaces/update.d.ts +2 -0
  108. package/dist/presentation/routes/workspaces/update.d.ts.map +1 -0
  109. package/dist/presentation/routes/workspaces/update.js +49 -0
  110. package/dist/presentation/routes/workspaces/update.js.map +1 -0
  111. package/dist/tsconfig.test.tsbuildinfo +1 -1
  112. package/drizzle/0000_initial.sql +2 -0
  113. package/drizzle/0003_dapper_talos.sql +13 -0
  114. package/drizzle/meta/0000_snapshot.json +23 -1
  115. package/drizzle/meta/0003_snapshot.json +689 -0
  116. package/drizzle/meta/_journal.json +7 -0
  117. package/package.json +14 -12
  118. package/src/core/entities/workspace.ts +1 -0
  119. package/src/core/errors.ts +7 -0
  120. package/src/core/index.ts +3 -0
  121. package/src/core/invitations.test.ts +8 -8
  122. package/src/core/invitations.ts +4 -1
  123. package/src/core/rate-limit.test.ts +113 -0
  124. package/src/core/rate-limit.ts +119 -0
  125. package/src/core/workspaces.test.ts +31 -3
  126. package/src/core/workspaces.ts +51 -4
  127. package/src/db/db.ts +2 -0
  128. package/src/db/index.ts +10 -0
  129. package/src/db/memberships.ts +3 -0
  130. package/src/db/rate-limits.test.ts +74 -0
  131. package/src/db/rate-limits.ts +59 -0
  132. package/src/db/schema/rate-limits.ts +27 -0
  133. package/src/db/schema/workspaces.ts +7 -2
  134. package/src/db/workspaces.ts +21 -1
  135. package/src/index.test.ts +2 -0
  136. package/src/index.ts +1 -0
  137. package/src/metrics/instance.ts +26 -0
  138. package/src/presentation/dto/membership.ts +1 -0
  139. package/src/presentation/dto/workspace.ts +1 -0
  140. package/src/presentation/e2eRoutes/create-workspace.ts +9 -1
  141. package/src/presentation/inter-module.test.ts +69 -0
  142. package/src/presentation/inter-module.ts +7 -4
  143. package/src/presentation/routes/index.ts +8 -1
  144. package/src/presentation/routes/invitations/create.test.ts +15 -0
  145. package/src/presentation/routes/invitations/create.ts +2 -1
  146. package/src/presentation/routes/invitations/list.test.ts +30 -0
  147. package/src/presentation/routes/invitations/list.ts +2 -1
  148. package/src/presentation/routes/invitations/revoke.test.ts +14 -0
  149. package/src/presentation/routes/invitations/revoke.ts +2 -1
  150. package/src/presentation/routes/members/list.test.ts +29 -1
  151. package/src/presentation/routes/members/list.ts +2 -1
  152. package/src/presentation/routes/members/remove.test.ts +14 -0
  153. package/src/presentation/routes/members/remove.ts +2 -1
  154. package/src/presentation/routes/rate-limit.ts +45 -0
  155. package/src/presentation/routes/workspaces/create.test.ts +33 -3
  156. package/src/presentation/routes/workspaces/create.ts +9 -1
  157. package/src/presentation/routes/workspaces/index.ts +2 -0
  158. package/src/presentation/routes/workspaces/list.test.ts +1 -0
  159. package/src/presentation/routes/workspaces/slug-availability.test.ts +159 -0
  160. package/src/presentation/routes/workspaces/slug-availability.ts +30 -0
  161. package/src/presentation/routes/workspaces/update.test.ts +114 -0
  162. package/src/presentation/routes/workspaces/update.ts +41 -0
  163. package/test/env.ts +1 -0
  164. package/test/factories/workspace.ts +3 -1
  165. package/test/globalSetup.ts +1 -1
  166. package/test/routes.ts +26 -7
  167. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/memberships.ts"],"sourcesContent":["import {and, eq, sql} from 'drizzle-orm';\nimport type {Membership} from '#core/entities/membership.js';\nimport type {Workspace} from '#core/entities/workspace.js';\nimport {LastMemberError} from '#core/errors.js';\nimport {recordWorkspaceMembershipChanged} from '#metrics/instance.js';\nimport {db} from './db.js';\nimport {memberships, toMembership} from './schema/memberships.js';\nimport {workspaces} from './schema/workspaces.js';\n\nexport interface CreateMembershipParams {\n userId: string;\n userEmail?: string | undefined;\n userName?: string | null | undefined;\n workspaceId: string;\n}\n\nexport interface EnsureMembershipParams {\n userId: string;\n userEmail: string;\n userName: string | null;\n workspaceId: string;\n}\n\nexport function membershipValues(params: CreateMembershipParams) {\n return {\n userId: params.userId,\n userEmail: params.userEmail ?? `user-${params.userId}@example.local`,\n userName: params.userName ?? null,\n workspaceId: params.workspaceId,\n };\n}\n\nexport async function createMembership(params: CreateMembershipParams): Promise<Membership> {\n const rows = await db().insert(memberships).values(membershipValues(params)).returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n recordWorkspaceMembershipChanged('added');\n return toMembership(row);\n}\n\nexport async function ensureMembership(params: EnsureMembershipParams): Promise<Membership> {\n const rows = await db()\n .insert(memberships)\n .values(membershipValues(params))\n .onConflictDoNothing({target: [memberships.userId, memberships.workspaceId]})\n .returning();\n\n const row = rows[0];\n if (row) {\n recordWorkspaceMembershipChanged('added');\n return toMembership(row);\n }\n\n const existing = await findMembership(params);\n if (existing) return existing;\n throw new Error('Membership conflict returned no membership');\n}\n\nexport interface MembershipWithWorkspace extends Membership {\n workspaceName: string;\n workspaceStatus: Workspace['status'];\n}\n\nexport async function listMembershipsByUser(params: {\n userId: string;\n}): Promise<MembershipWithWorkspace[]> {\n const rows = await db()\n .select({\n membership: memberships,\n workspaceName: workspaces.name,\n workspaceStatus: workspaces.status,\n })\n .from(memberships)\n .innerJoin(workspaces, eq(workspaces.id, memberships.workspaceId))\n .where(eq(memberships.userId, params.userId))\n .orderBy(workspaces.name);\n\n return rows.map((row) => ({\n ...toMembership(row.membership),\n workspaceName: row.workspaceName,\n workspaceStatus: row.workspaceStatus,\n }));\n}\n\nexport interface MembershipWithUser extends Membership {\n userEmail: string;\n userName: string | null;\n}\n\nexport async function listMembershipsByWorkspace(params: {\n workspaceId: string;\n}): Promise<MembershipWithUser[]> {\n const rows = await db()\n .select()\n .from(memberships)\n .where(eq(memberships.workspaceId, params.workspaceId));\n\n return rows.map(toMembership);\n}\n\nexport async function findMembership(params: {\n userId: string;\n workspaceId: string;\n}): Promise<Membership | undefined> {\n const rows = await db()\n .select()\n .from(memberships)\n .where(\n and(eq(memberships.userId, params.userId), eq(memberships.workspaceId, params.workspaceId)),\n )\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toMembership(row);\n}\n\nexport interface RemoveMembershipParams {\n userId: string;\n workspaceId: string;\n}\n\nexport async function removeMembership(params: RemoveMembershipParams): Promise<void> {\n const removed = await db().transaction(async (tx) => {\n const countResult = await tx\n .select({count: sql<number>`count(*)::int`})\n .from(memberships)\n .where(eq(memberships.workspaceId, params.workspaceId));\n const total = countResult[0]?.count ?? 0;\n if (total <= 1) {\n throw new LastMemberError(params.workspaceId);\n }\n\n const deleted = await tx\n .delete(memberships)\n .where(\n and(eq(memberships.userId, params.userId), eq(memberships.workspaceId, params.workspaceId)),\n )\n .returning({id: memberships.id});\n\n return deleted.length > 0;\n });\n\n if (removed) recordWorkspaceMembershipChanged('removed');\n}\n"],"names":["and","eq","sql","LastMemberError","recordWorkspaceMembershipChanged","db","memberships","toMembership","workspaces","membershipValues","params","userId","userEmail","userName","workspaceId","createMembership","rows","insert","values","returning","row","Error","ensureMembership","onConflictDoNothing","target","existing","findMembership","listMembershipsByUser","select","membership","workspaceName","name","workspaceStatus","status","from","innerJoin","id","where","orderBy","map","listMembershipsByWorkspace","limit","undefined","removeMembership","removed","transaction","tx","countResult","count","total","deleted","delete","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/memberships.ts"],"sourcesContent":["import {and, eq, sql} from 'drizzle-orm';\nimport type {Membership} from '#core/entities/membership.js';\nimport type {Workspace} from '#core/entities/workspace.js';\nimport {LastMemberError} from '#core/errors.js';\nimport {recordWorkspaceMembershipChanged} from '#metrics/instance.js';\nimport {db} from './db.js';\nimport {memberships, toMembership} from './schema/memberships.js';\nimport {workspaces} from './schema/workspaces.js';\n\nexport interface CreateMembershipParams {\n userId: string;\n userEmail?: string | undefined;\n userName?: string | null | undefined;\n workspaceId: string;\n}\n\nexport interface EnsureMembershipParams {\n userId: string;\n userEmail: string;\n userName: string | null;\n workspaceId: string;\n}\n\nexport function membershipValues(params: CreateMembershipParams) {\n return {\n userId: params.userId,\n userEmail: params.userEmail ?? `user-${params.userId}@example.local`,\n userName: params.userName ?? null,\n workspaceId: params.workspaceId,\n };\n}\n\nexport async function createMembership(params: CreateMembershipParams): Promise<Membership> {\n const rows = await db().insert(memberships).values(membershipValues(params)).returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n recordWorkspaceMembershipChanged('added');\n return toMembership(row);\n}\n\nexport async function ensureMembership(params: EnsureMembershipParams): Promise<Membership> {\n const rows = await db()\n .insert(memberships)\n .values(membershipValues(params))\n .onConflictDoNothing({target: [memberships.userId, memberships.workspaceId]})\n .returning();\n\n const row = rows[0];\n if (row) {\n recordWorkspaceMembershipChanged('added');\n return toMembership(row);\n }\n\n const existing = await findMembership(params);\n if (existing) return existing;\n throw new Error('Membership conflict returned no membership');\n}\n\nexport interface MembershipWithWorkspace extends Membership {\n workspaceName: string;\n workspaceSlug: string;\n workspaceStatus: Workspace['status'];\n}\n\nexport async function listMembershipsByUser(params: {\n userId: string;\n}): Promise<MembershipWithWorkspace[]> {\n const rows = await db()\n .select({\n membership: memberships,\n workspaceName: workspaces.name,\n workspaceSlug: workspaces.slug,\n workspaceStatus: workspaces.status,\n })\n .from(memberships)\n .innerJoin(workspaces, eq(workspaces.id, memberships.workspaceId))\n .where(eq(memberships.userId, params.userId))\n .orderBy(workspaces.name);\n\n return rows.map((row) => ({\n ...toMembership(row.membership),\n workspaceName: row.workspaceName,\n workspaceSlug: row.workspaceSlug,\n workspaceStatus: row.workspaceStatus,\n }));\n}\n\nexport interface MembershipWithUser extends Membership {\n userEmail: string;\n userName: string | null;\n}\n\nexport async function listMembershipsByWorkspace(params: {\n workspaceId: string;\n}): Promise<MembershipWithUser[]> {\n const rows = await db()\n .select()\n .from(memberships)\n .where(eq(memberships.workspaceId, params.workspaceId));\n\n return rows.map(toMembership);\n}\n\nexport async function findMembership(params: {\n userId: string;\n workspaceId: string;\n}): Promise<Membership | undefined> {\n const rows = await db()\n .select()\n .from(memberships)\n .where(\n and(eq(memberships.userId, params.userId), eq(memberships.workspaceId, params.workspaceId)),\n )\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toMembership(row);\n}\n\nexport interface RemoveMembershipParams {\n userId: string;\n workspaceId: string;\n}\n\nexport async function removeMembership(params: RemoveMembershipParams): Promise<void> {\n const removed = await db().transaction(async (tx) => {\n const countResult = await tx\n .select({count: sql<number>`count(*)::int`})\n .from(memberships)\n .where(eq(memberships.workspaceId, params.workspaceId));\n const total = countResult[0]?.count ?? 0;\n if (total <= 1) {\n throw new LastMemberError(params.workspaceId);\n }\n\n const deleted = await tx\n .delete(memberships)\n .where(\n and(eq(memberships.userId, params.userId), eq(memberships.workspaceId, params.workspaceId)),\n )\n .returning({id: memberships.id});\n\n return deleted.length > 0;\n });\n\n if (removed) recordWorkspaceMembershipChanged('removed');\n}\n"],"names":["and","eq","sql","LastMemberError","recordWorkspaceMembershipChanged","db","memberships","toMembership","workspaces","membershipValues","params","userId","userEmail","userName","workspaceId","createMembership","rows","insert","values","returning","row","Error","ensureMembership","onConflictDoNothing","target","existing","findMembership","listMembershipsByUser","select","membership","workspaceName","name","workspaceSlug","slug","workspaceStatus","status","from","innerJoin","id","where","orderBy","map","listMembershipsByWorkspace","limit","undefined","removeMembership","removed","transaction","tx","countResult","count","total","deleted","delete","length"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type ConsumeRateLimitParams, type ConsumeRateLimitResult } from '@shipfox/node-rate-limit';
2
+ export type ConsumeWorkspacesRateLimitParams = ConsumeRateLimitParams<string, string>;
3
+ export type ConsumeWorkspacesRateLimitResult = ConsumeRateLimitResult;
4
+ export declare const consumeWorkspacesRateLimit: (params: ConsumeRateLimitParams<string, string>) => Promise<ConsumeRateLimitResult>;
5
+ export declare const pruneExpiredWorkspacesRateLimits: (params?: {
6
+ now?: Date | undefined;
7
+ minIntervalMs?: number | undefined;
8
+ }) => Promise<number | undefined>;
9
+ //# sourceMappingURL=rate-limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limits.d.ts","sourceRoot":"","sources":["../../src/db/rate-limits.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAE5B,MAAM,0BAA0B,CAAC;AAmDlC,MAAM,MAAM,gCAAgC,GAAG,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACtF,MAAM,MAAM,gCAAgC,GAAG,sBAAsB,CAAC;AACtE,eAAO,MAAM,0BAA0B,qFAAsB,CAAC;AAC9D,eAAO,MAAM,gCAAgC;;;iCAAoB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { createRateLimitPersistence } from '@shipfox/node-rate-limit';
2
+ import { lt, sql } from 'drizzle-orm';
3
+ import { db } from './db.js';
4
+ import { workspacesRateLimits } from './schema/rate-limits.js';
5
+ const persistence = createRateLimitPersistence({
6
+ transaction: (callback)=>db().transaction(callback),
7
+ setStatementTimeout: async (transaction, timeoutMs)=>{
8
+ await transaction.execute(sql`select set_config('statement_timeout', ${`${timeoutMs}ms`}, true)`);
9
+ },
10
+ consume: async (transaction, params)=>{
11
+ const rows = await transaction.insert(workspacesRateLimits).values({
12
+ action: params.action,
13
+ scope: params.scope,
14
+ identifierHmac: params.identifierHmac,
15
+ windowStart: params.windowStart,
16
+ count: 1,
17
+ expiresAt: params.expiresAt
18
+ }).onConflictDoUpdate({
19
+ target: [
20
+ workspacesRateLimits.action,
21
+ workspacesRateLimits.scope,
22
+ workspacesRateLimits.identifierHmac,
23
+ workspacesRateLimits.windowStart
24
+ ],
25
+ set: {
26
+ count: sql`${workspacesRateLimits.count} + 1`,
27
+ updatedAt: sql`now()`
28
+ }
29
+ }).returning({
30
+ count: workspacesRateLimits.count,
31
+ expiresAt: workspacesRateLimits.expiresAt
32
+ });
33
+ return rows[0];
34
+ },
35
+ prune: async (now)=>{
36
+ const result = await db().delete(workspacesRateLimits).where(lt(workspacesRateLimits.expiresAt, now));
37
+ return result.rowCount ?? 0;
38
+ }
39
+ });
40
+ export const consumeWorkspacesRateLimit = persistence.consume;
41
+ export const pruneExpiredWorkspacesRateLimits = persistence.prune;
42
+
43
+ //# sourceMappingURL=rate-limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/db/rate-limits.ts"],"sourcesContent":["import {\n type ConsumeRateLimitParams,\n type ConsumeRateLimitResult,\n createRateLimitPersistence,\n} from '@shipfox/node-rate-limit';\nimport {lt, sql} from 'drizzle-orm';\nimport {db} from './db.js';\nimport {workspacesRateLimits} from './schema/rate-limits.js';\n\ntype WorkspacesRateLimitTransaction = Parameters<\n Parameters<ReturnType<typeof db>['transaction']>[0]\n>[0];\n\nconst persistence = createRateLimitPersistence<WorkspacesRateLimitTransaction>({\n transaction: (callback) => db().transaction(callback),\n setStatementTimeout: async (transaction, timeoutMs) => {\n await transaction.execute(\n sql`select set_config('statement_timeout', ${`${timeoutMs}ms`}, true)`,\n );\n },\n consume: async (transaction, params) => {\n const rows = await transaction\n .insert(workspacesRateLimits)\n .values({\n action: params.action,\n scope: params.scope,\n identifierHmac: params.identifierHmac,\n windowStart: params.windowStart,\n count: 1,\n expiresAt: params.expiresAt,\n })\n .onConflictDoUpdate({\n target: [\n workspacesRateLimits.action,\n workspacesRateLimits.scope,\n workspacesRateLimits.identifierHmac,\n workspacesRateLimits.windowStart,\n ],\n set: {\n count: sql`${workspacesRateLimits.count} + 1`,\n updatedAt: sql`now()`,\n },\n })\n .returning({count: workspacesRateLimits.count, expiresAt: workspacesRateLimits.expiresAt});\n\n return rows[0];\n },\n prune: async (now) => {\n const result = await db()\n .delete(workspacesRateLimits)\n .where(lt(workspacesRateLimits.expiresAt, now));\n return result.rowCount ?? 0;\n },\n});\n\nexport type ConsumeWorkspacesRateLimitParams = ConsumeRateLimitParams<string, string>;\nexport type ConsumeWorkspacesRateLimitResult = ConsumeRateLimitResult;\nexport const consumeWorkspacesRateLimit = persistence.consume;\nexport const pruneExpiredWorkspacesRateLimits = persistence.prune;\n"],"names":["createRateLimitPersistence","lt","sql","db","workspacesRateLimits","persistence","transaction","callback","setStatementTimeout","timeoutMs","execute","consume","params","rows","insert","values","action","scope","identifierHmac","windowStart","count","expiresAt","onConflictDoUpdate","target","set","updatedAt","returning","prune","now","result","delete","where","rowCount","consumeWorkspacesRateLimit","pruneExpiredWorkspacesRateLimits"],"mappings":"AAAA,SAGEA,0BAA0B,QACrB,2BAA2B;AAClC,SAAQC,EAAE,EAAEC,GAAG,QAAO,cAAc;AACpC,SAAQC,EAAE,QAAO,UAAU;AAC3B,SAAQC,oBAAoB,QAAO,0BAA0B;AAM7D,MAAMC,cAAcL,2BAA2D;IAC7EM,aAAa,CAACC,WAAaJ,KAAKG,WAAW,CAACC;IAC5CC,qBAAqB,OAAOF,aAAaG;QACvC,MAAMH,YAAYI,OAAO,CACvBR,GAAG,CAAC,uCAAuC,EAAE,GAAGO,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC;IAE1E;IACAE,SAAS,OAAOL,aAAaM;QAC3B,MAAMC,OAAO,MAAMP,YAChBQ,MAAM,CAACV,sBACPW,MAAM,CAAC;YACNC,QAAQJ,OAAOI,MAAM;YACrBC,OAAOL,OAAOK,KAAK;YACnBC,gBAAgBN,OAAOM,cAAc;YACrCC,aAAaP,OAAOO,WAAW;YAC/BC,OAAO;YACPC,WAAWT,OAAOS,SAAS;QAC7B,GACCC,kBAAkB,CAAC;YAClBC,QAAQ;gBACNnB,qBAAqBY,MAAM;gBAC3BZ,qBAAqBa,KAAK;gBAC1Bb,qBAAqBc,cAAc;gBACnCd,qBAAqBe,WAAW;aACjC;YACDK,KAAK;gBACHJ,OAAOlB,GAAG,CAAC,EAAEE,qBAAqBgB,KAAK,CAAC,IAAI,CAAC;gBAC7CK,WAAWvB,GAAG,CAAC,KAAK,CAAC;YACvB;QACF,GACCwB,SAAS,CAAC;YAACN,OAAOhB,qBAAqBgB,KAAK;YAAEC,WAAWjB,qBAAqBiB,SAAS;QAAA;QAE1F,OAAOR,IAAI,CAAC,EAAE;IAChB;IACAc,OAAO,OAAOC;QACZ,MAAMC,SAAS,MAAM1B,KAClB2B,MAAM,CAAC1B,sBACP2B,KAAK,CAAC9B,GAAGG,qBAAqBiB,SAAS,EAAEO;QAC5C,OAAOC,OAAOG,QAAQ,IAAI;IAC5B;AACF;AAIA,OAAO,MAAMC,6BAA6B5B,YAAYM,OAAO,CAAC;AAC9D,OAAO,MAAMuB,mCAAmC7B,YAAYsB,KAAK,CAAC"}
@@ -0,0 +1,145 @@
1
+ export declare const workspacesRateLimits: import("drizzle-orm/pg-core").PgTableWithColumns<{
2
+ name: "rate_limits";
3
+ schema: undefined;
4
+ columns: {
5
+ action: import("drizzle-orm/pg-core").PgColumn<{
6
+ name: "action";
7
+ tableName: "rate_limits";
8
+ dataType: "string";
9
+ columnType: "PgText";
10
+ data: string;
11
+ driverParam: string;
12
+ notNull: true;
13
+ hasDefault: false;
14
+ isPrimaryKey: false;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: [string, ...string[]];
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ scope: import("drizzle-orm/pg-core").PgColumn<{
23
+ name: "scope";
24
+ tableName: "rate_limits";
25
+ dataType: "string";
26
+ columnType: "PgText";
27
+ data: string;
28
+ driverParam: string;
29
+ notNull: true;
30
+ hasDefault: false;
31
+ isPrimaryKey: false;
32
+ isAutoincrement: false;
33
+ hasRuntimeDefault: false;
34
+ enumValues: [string, ...string[]];
35
+ baseColumn: never;
36
+ identity: undefined;
37
+ generated: undefined;
38
+ }, {}, {}>;
39
+ identifierHmac: import("drizzle-orm/pg-core").PgColumn<{
40
+ name: "identifier_hmac";
41
+ tableName: "rate_limits";
42
+ dataType: "string";
43
+ columnType: "PgText";
44
+ data: string;
45
+ driverParam: string;
46
+ notNull: true;
47
+ hasDefault: false;
48
+ isPrimaryKey: false;
49
+ isAutoincrement: false;
50
+ hasRuntimeDefault: false;
51
+ enumValues: [string, ...string[]];
52
+ baseColumn: never;
53
+ identity: undefined;
54
+ generated: undefined;
55
+ }, {}, {}>;
56
+ windowStart: import("drizzle-orm/pg-core").PgColumn<{
57
+ name: "window_start";
58
+ tableName: "rate_limits";
59
+ dataType: "date";
60
+ columnType: "PgTimestamp";
61
+ data: Date;
62
+ driverParam: string;
63
+ notNull: true;
64
+ hasDefault: false;
65
+ isPrimaryKey: false;
66
+ isAutoincrement: false;
67
+ hasRuntimeDefault: false;
68
+ enumValues: undefined;
69
+ baseColumn: never;
70
+ identity: undefined;
71
+ generated: undefined;
72
+ }, {}, {}>;
73
+ count: import("drizzle-orm/pg-core").PgColumn<{
74
+ name: "count";
75
+ tableName: "rate_limits";
76
+ dataType: "number";
77
+ columnType: "PgInteger";
78
+ data: number;
79
+ driverParam: string | number;
80
+ notNull: true;
81
+ hasDefault: true;
82
+ isPrimaryKey: false;
83
+ isAutoincrement: false;
84
+ hasRuntimeDefault: false;
85
+ enumValues: undefined;
86
+ baseColumn: never;
87
+ identity: undefined;
88
+ generated: undefined;
89
+ }, {}, {}>;
90
+ expiresAt: import("drizzle-orm/pg-core").PgColumn<{
91
+ name: "expires_at";
92
+ tableName: "rate_limits";
93
+ dataType: "date";
94
+ columnType: "PgTimestamp";
95
+ data: Date;
96
+ driverParam: string;
97
+ notNull: true;
98
+ hasDefault: false;
99
+ isPrimaryKey: false;
100
+ isAutoincrement: false;
101
+ hasRuntimeDefault: false;
102
+ enumValues: undefined;
103
+ baseColumn: never;
104
+ identity: undefined;
105
+ generated: undefined;
106
+ }, {}, {}>;
107
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
108
+ name: "created_at";
109
+ tableName: "rate_limits";
110
+ dataType: "date";
111
+ columnType: "PgTimestamp";
112
+ data: Date;
113
+ driverParam: string;
114
+ notNull: true;
115
+ hasDefault: true;
116
+ isPrimaryKey: false;
117
+ isAutoincrement: false;
118
+ hasRuntimeDefault: false;
119
+ enumValues: undefined;
120
+ baseColumn: never;
121
+ identity: undefined;
122
+ generated: undefined;
123
+ }, {}, {}>;
124
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
125
+ name: "updated_at";
126
+ tableName: "rate_limits";
127
+ dataType: "date";
128
+ columnType: "PgTimestamp";
129
+ data: Date;
130
+ driverParam: string;
131
+ notNull: true;
132
+ hasDefault: true;
133
+ isPrimaryKey: false;
134
+ isAutoincrement: false;
135
+ hasRuntimeDefault: false;
136
+ enumValues: undefined;
137
+ baseColumn: never;
138
+ identity: undefined;
139
+ generated: undefined;
140
+ }, {}, {}>;
141
+ };
142
+ dialect: "pg";
143
+ }>;
144
+ export type WorkspaceRateLimitDb = typeof workspacesRateLimits.$inferSelect;
145
+ //# sourceMappingURL=rate-limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limits.d.ts","sourceRoot":"","sources":["../../../src/db/schema/rate-limits.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqBhC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,YAAY,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { index, integer, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core';
2
+ import { pgTable } from './common.js';
3
+ export const workspacesRateLimits = pgTable('rate_limits', {
4
+ action: text('action').notNull(),
5
+ scope: text('scope').notNull(),
6
+ identifierHmac: text('identifier_hmac').notNull(),
7
+ windowStart: timestamp('window_start', {
8
+ withTimezone: true
9
+ }).notNull(),
10
+ count: integer('count').notNull().default(1),
11
+ expiresAt: timestamp('expires_at', {
12
+ withTimezone: true
13
+ }).notNull(),
14
+ createdAt: timestamp('created_at', {
15
+ withTimezone: true
16
+ }).notNull().defaultNow(),
17
+ updatedAt: timestamp('updated_at', {
18
+ withTimezone: true
19
+ }).notNull().defaultNow()
20
+ }, (table)=>[
21
+ uniqueIndex('workspaces_rate_limits_window_unique').on(table.action, table.scope, table.identifierHmac, table.windowStart),
22
+ index('workspaces_rate_limits_expires_at_idx').on(table.expiresAt)
23
+ ]);
24
+
25
+ //# sourceMappingURL=rate-limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/db/schema/rate-limits.ts"],"sourcesContent":["import {index, integer, text, timestamp, uniqueIndex} from 'drizzle-orm/pg-core';\nimport {pgTable} from './common.js';\n\nexport const workspacesRateLimits = pgTable(\n 'rate_limits',\n {\n action: text('action').notNull(),\n scope: text('scope').notNull(),\n identifierHmac: text('identifier_hmac').notNull(),\n windowStart: timestamp('window_start', {withTimezone: true}).notNull(),\n count: integer('count').notNull().default(1),\n expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('workspaces_rate_limits_window_unique').on(\n table.action,\n table.scope,\n table.identifierHmac,\n table.windowStart,\n ),\n index('workspaces_rate_limits_expires_at_idx').on(table.expiresAt),\n ],\n);\n\nexport type WorkspaceRateLimitDb = typeof workspacesRateLimits.$inferSelect;\n"],"names":["index","integer","text","timestamp","uniqueIndex","pgTable","workspacesRateLimits","action","notNull","scope","identifierHmac","windowStart","withTimezone","count","default","expiresAt","createdAt","defaultNow","updatedAt","table","on"],"mappings":"AAAA,SAAQA,KAAK,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,QAAO,sBAAsB;AACjF,SAAQC,OAAO,QAAO,cAAc;AAEpC,OAAO,MAAMC,uBAAuBD,QAClC,eACA;IACEE,QAAQL,KAAK,UAAUM,OAAO;IAC9BC,OAAOP,KAAK,SAASM,OAAO;IAC5BE,gBAAgBR,KAAK,mBAAmBM,OAAO;IAC/CG,aAAaR,UAAU,gBAAgB;QAACS,cAAc;IAAI,GAAGJ,OAAO;IACpEK,OAAOZ,QAAQ,SAASO,OAAO,GAAGM,OAAO,CAAC;IAC1CC,WAAWZ,UAAU,cAAc;QAACS,cAAc;IAAI,GAAGJ,OAAO;IAChEQ,WAAWb,UAAU,cAAc;QAACS,cAAc;IAAI,GAAGJ,OAAO,GAAGS,UAAU;IAC7EC,WAAWf,UAAU,cAAc;QAACS,cAAc;IAAI,GAAGJ,OAAO,GAAGS,UAAU;AAC/E,GACA,CAACE,QAAU;QACTf,YAAY,wCAAwCgB,EAAE,CACpDD,MAAMZ,MAAM,EACZY,MAAMV,KAAK,EACXU,MAAMT,cAAc,EACpBS,MAAMR,WAAW;QAEnBX,MAAM,yCAAyCoB,EAAE,CAACD,MAAMJ,SAAS;KAClE,EACD"}
@@ -38,6 +38,23 @@ export declare const workspaces: import("drizzle-orm/pg-core").PgTableWithColumn
38
38
  identity: undefined;
39
39
  generated: undefined;
40
40
  }, {}, {}>;
41
+ slug: import("drizzle-orm/pg-core").PgColumn<{
42
+ name: "slug";
43
+ tableName: "workspaces";
44
+ dataType: "string";
45
+ columnType: "PgText";
46
+ data: string;
47
+ driverParam: string;
48
+ notNull: true;
49
+ hasDefault: false;
50
+ isPrimaryKey: false;
51
+ isAutoincrement: false;
52
+ hasRuntimeDefault: false;
53
+ enumValues: [string, ...string[]];
54
+ baseColumn: never;
55
+ identity: undefined;
56
+ generated: undefined;
57
+ }, {}, {}>;
41
58
  status: import("drizzle-orm/pg-core").PgColumn<{
42
59
  name: "status";
43
60
  tableName: "workspaces";
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../../src/db/schema/workspaces.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,SAAS,EAAkB,MAAM,6BAA6B,CAAC;AAG5E,eAAO,MAAM,mBAAmB,0EAI9B,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYtB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AAE/D,wBAAgB,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,SAAS,CAUvD"}
1
+ {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../../src/db/schema/workspaces.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,SAAS,EAAkB,MAAM,6BAA6B,CAAC;AAG5E,eAAO,MAAM,mBAAmB,0EAI9B,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBtB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AAE/D,wBAAgB,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,SAAS,CAWvD"}
@@ -1,5 +1,5 @@
1
1
  import { uuidv7PrimaryKey } from '@shipfox/node-drizzle';
2
- import { index, jsonb, pgEnum, text, timestamp, uuid } from 'drizzle-orm/pg-core';
2
+ import { index, jsonb, pgEnum, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
3
3
  import { pgTable } from './common.js';
4
4
  export const workspaceStatusEnum = pgEnum('workspaces_workspace_status', [
5
5
  'active',
@@ -9,6 +9,7 @@ export const workspaceStatusEnum = pgEnum('workspaces_workspace_status', [
9
9
  export const workspaces = pgTable('workspaces', {
10
10
  id: uuidv7PrimaryKey(),
11
11
  name: text('name').notNull(),
12
+ slug: text('slug').notNull(),
12
13
  status: workspaceStatusEnum('status').notNull().default('active'),
13
14
  settings: jsonb('settings').notNull().default({}),
14
15
  createdBy: uuid('created_by'),
@@ -19,12 +20,14 @@ export const workspaces = pgTable('workspaces', {
19
20
  withTimezone: true
20
21
  }).notNull().defaultNow()
21
22
  }, (table)=>[
22
- index('workspaces_name_id_idx').on(table.name, table.id)
23
+ index('workspaces_name_id_idx').on(table.name, table.id),
24
+ uniqueIndex('workspaces_slug_unique').on(table.slug)
23
25
  ]);
24
26
  export function toWorkspace(row) {
25
27
  return {
26
28
  id: row.id,
27
29
  name: row.name,
30
+ slug: row.slug,
28
31
  status: row.status,
29
32
  settings: row.settings,
30
33
  createdBy: row.createdBy,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/db/schema/workspaces.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {index, jsonb, pgEnum, text, timestamp, uuid} from 'drizzle-orm/pg-core';\nimport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nimport {pgTable} from './common.js';\n\nexport const workspaceStatusEnum = pgEnum('workspaces_workspace_status', [\n 'active',\n 'suspended',\n 'deleted',\n]);\n\nexport const workspaces = pgTable(\n 'workspaces',\n {\n id: uuidv7PrimaryKey(),\n name: text('name').notNull(),\n status: workspaceStatusEnum('status').notNull().default('active'),\n settings: jsonb('settings').notNull().default({}),\n createdBy: uuid('created_by'),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [index('workspaces_name_id_idx').on(table.name, table.id)],\n);\n\nexport type WorkspaceDb = typeof workspaces.$inferSelect;\nexport type WorkspaceCreateDb = typeof workspaces.$inferInsert;\n\nexport function toWorkspace(row: WorkspaceDb): Workspace {\n return {\n id: row.id,\n name: row.name,\n status: row.status as WorkspaceStatus,\n settings: row.settings as Record<string, unknown>,\n createdBy: row.createdBy,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","index","jsonb","pgEnum","text","timestamp","uuid","pgTable","workspaceStatusEnum","workspaces","id","name","notNull","status","default","settings","createdBy","createdAt","withTimezone","defaultNow","updatedAt","table","on","toWorkspace","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,KAAK,EAAEC,MAAM,EAAEC,IAAI,EAAEC,SAAS,EAAEC,IAAI,QAAO,sBAAsB;AAEhF,SAAQC,OAAO,QAAO,cAAc;AAEpC,OAAO,MAAMC,sBAAsBL,OAAO,+BAA+B;IACvE;IACA;IACA;CACD,EAAE;AAEH,OAAO,MAAMM,aAAaF,QACxB,cACA;IACEG,IAAIV;IACJW,MAAMP,KAAK,QAAQQ,OAAO;IAC1BC,QAAQL,oBAAoB,UAAUI,OAAO,GAAGE,OAAO,CAAC;IACxDC,UAAUb,MAAM,YAAYU,OAAO,GAAGE,OAAO,CAAC,CAAC;IAC/CE,WAAWV,KAAK;IAChBW,WAAWZ,UAAU,cAAc;QAACa,cAAc;IAAI,GAAGN,OAAO,GAAGO,UAAU;IAC7EC,WAAWf,UAAU,cAAc;QAACa,cAAc;IAAI,GAAGN,OAAO,GAAGO,UAAU;AAC/E,GACA,CAACE,QAAU;QAACpB,MAAM,0BAA0BqB,EAAE,CAACD,MAAMV,IAAI,EAAEU,MAAMX,EAAE;KAAE,EACrE;AAKF,OAAO,SAASa,YAAYC,GAAgB;IAC1C,OAAO;QACLd,IAAIc,IAAId,EAAE;QACVC,MAAMa,IAAIb,IAAI;QACdE,QAAQW,IAAIX,MAAM;QAClBE,UAAUS,IAAIT,QAAQ;QACtBC,WAAWQ,IAAIR,SAAS;QACxBC,WAAWO,IAAIP,SAAS;QACxBG,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
1
+ {"version":3,"sources":["../../../src/db/schema/workspaces.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {index, jsonb, pgEnum, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nimport {pgTable} from './common.js';\n\nexport const workspaceStatusEnum = pgEnum('workspaces_workspace_status', [\n 'active',\n 'suspended',\n 'deleted',\n]);\n\nexport const workspaces = pgTable(\n 'workspaces',\n {\n id: uuidv7PrimaryKey(),\n name: text('name').notNull(),\n slug: text('slug').notNull(),\n status: workspaceStatusEnum('status').notNull().default('active'),\n settings: jsonb('settings').notNull().default({}),\n createdBy: uuid('created_by'),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n index('workspaces_name_id_idx').on(table.name, table.id),\n uniqueIndex('workspaces_slug_unique').on(table.slug),\n ],\n);\n\nexport type WorkspaceDb = typeof workspaces.$inferSelect;\nexport type WorkspaceCreateDb = typeof workspaces.$inferInsert;\n\nexport function toWorkspace(row: WorkspaceDb): Workspace {\n return {\n id: row.id,\n name: row.name,\n slug: row.slug,\n status: row.status as WorkspaceStatus,\n settings: row.settings as Record<string, unknown>,\n createdBy: row.createdBy,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","index","jsonb","pgEnum","text","timestamp","uniqueIndex","uuid","pgTable","workspaceStatusEnum","workspaces","id","name","notNull","slug","status","default","settings","createdBy","createdAt","withTimezone","defaultNow","updatedAt","table","on","toWorkspace","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,KAAK,EAAEC,MAAM,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE7F,SAAQC,OAAO,QAAO,cAAc;AAEpC,OAAO,MAAMC,sBAAsBN,OAAO,+BAA+B;IACvE;IACA;IACA;CACD,EAAE;AAEH,OAAO,MAAMO,aAAaF,QACxB,cACA;IACEG,IAAIX;IACJY,MAAMR,KAAK,QAAQS,OAAO;IAC1BC,MAAMV,KAAK,QAAQS,OAAO;IAC1BE,QAAQN,oBAAoB,UAAUI,OAAO,GAAGG,OAAO,CAAC;IACxDC,UAAUf,MAAM,YAAYW,OAAO,GAAGG,OAAO,CAAC,CAAC;IAC/CE,WAAWX,KAAK;IAChBY,WAAWd,UAAU,cAAc;QAACe,cAAc;IAAI,GAAGP,OAAO,GAAGQ,UAAU;IAC7EC,WAAWjB,UAAU,cAAc;QAACe,cAAc;IAAI,GAAGP,OAAO,GAAGQ,UAAU;AAC/E,GACA,CAACE,QAAU;QACTtB,MAAM,0BAA0BuB,EAAE,CAACD,MAAMX,IAAI,EAAEW,MAAMZ,EAAE;QACvDL,YAAY,0BAA0BkB,EAAE,CAACD,MAAMT,IAAI;KACpD,EACD;AAKF,OAAO,SAASW,YAAYC,GAAgB;IAC1C,OAAO;QACLf,IAAIe,IAAIf,EAAE;QACVC,MAAMc,IAAId,IAAI;QACdE,MAAMY,IAAIZ,IAAI;QACdC,QAAQW,IAAIX,MAAM;QAClBE,UAAUS,IAAIT,QAAQ;QACtBC,WAAWQ,IAAIR,SAAS;QACxBC,WAAWO,IAAIP,SAAS;QACxBG,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
@@ -1,17 +1,26 @@
1
1
  import type { StringIdCursor } from '@shipfox/node-drizzle';
2
2
  import type { Workspace, WorkspaceStatus } from '#core/entities/workspace.js';
3
+ import { db } from './db.js';
3
4
  export interface CreateWorkspaceParams {
4
5
  name: string;
6
+ /** Existing database-only fixtures predate the required API slug. */
7
+ slug?: string | undefined;
5
8
  }
6
9
  export declare function createWorkspace(params: CreateWorkspaceParams): Promise<Workspace>;
7
10
  export interface UpdateWorkspaceParams {
8
11
  id: string;
9
12
  name?: string | undefined;
13
+ slug?: string | undefined;
10
14
  status?: WorkspaceStatus | undefined;
11
15
  settings?: Record<string, unknown> | undefined;
12
16
  }
13
- export declare function updateWorkspace(params: UpdateWorkspaceParams): Promise<Workspace | undefined>;
17
+ type WorkspaceDatabase = ReturnType<typeof db>;
18
+ type WorkspaceTransaction = Parameters<Parameters<WorkspaceDatabase['transaction']>[0]>[0];
19
+ export declare function updateWorkspace(params: UpdateWorkspaceParams, options?: {
20
+ tx?: WorkspaceDatabase | WorkspaceTransaction | undefined;
21
+ }): Promise<Workspace | undefined>;
14
22
  export declare function getWorkspaceById(id: string): Promise<Workspace | undefined>;
23
+ export declare function isWorkspaceSlugAvailable(slug: string): Promise<boolean>;
15
24
  export interface AdminWorkspaceRow extends Workspace {
16
25
  memberCount: number;
17
26
  }
@@ -33,4 +42,5 @@ export interface WorkspaceServiceMetrics {
33
42
  openInvitations: number;
34
43
  }
35
44
  export declare function getWorkspaceServiceMetrics(): Promise<WorkspaceServiceMetrics>;
45
+ export {};
36
46
  //# sourceMappingURL=workspaces.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/db/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,6BAA6B,CAAC;AAO5E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAYvF;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAChD;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAehC;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAKjF;AAED,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;CACnC;AAUD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAwCnC;AAMD,MAAM,WAAW,uBAAuB;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAenF"}
1
+ {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/db/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,6BAA6B,CAAC;AAE5E,OAAO,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAK3B,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAavF;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAChD;AAED,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;AAC/C,KAAK,oBAAoB,GAAG,UAAU,CAAC,UAAU,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3F,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE;IAAC,EAAE,CAAC,EAAE,iBAAiB,GAAG,oBAAoB,GAAG,SAAS,CAAA;CAAM,GACxE,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAiBhC;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAKjF;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ7E;AAED,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;CACnC;AAUD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAwCnC;AAMD,MAAM,WAAW,uBAAuB;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAenF"}
@@ -6,21 +6,24 @@ import { memberships } from './schema/memberships.js';
6
6
  import { toWorkspace, workspaces } from './schema/workspaces.js';
7
7
  export async function createWorkspace(params) {
8
8
  const rows = await db().insert(workspaces).values({
9
- name: params.name
9
+ name: params.name,
10
+ slug: params.slug ?? `workspace-${crypto.randomUUID().slice(0, 8)}`
10
11
  }).returning();
11
12
  const row = rows[0];
12
13
  if (!row) throw new Error('Insert returned no rows');
13
14
  recordWorkspaceCreated();
14
15
  return toWorkspace(row);
15
16
  }
16
- export async function updateWorkspace(params) {
17
+ export async function updateWorkspace(params, options = {}) {
18
+ const executor = options.tx ?? db();
17
19
  const set = {
18
20
  updatedAt: sql`NOW()`
19
21
  };
20
22
  if (params.name !== undefined) set.name = params.name;
23
+ if (params.slug !== undefined) set.slug = params.slug;
21
24
  if (params.status !== undefined) set.status = params.status;
22
25
  if (params.settings !== undefined) set.settings = params.settings;
23
- const rows = await db().update(workspaces).set(set).where(eq(workspaces.id, params.id)).returning();
26
+ const rows = await executor.update(workspaces).set(set).where(eq(workspaces.id, params.id)).returning();
24
27
  const row = rows[0];
25
28
  if (!row) return undefined;
26
29
  return toWorkspace(row);
@@ -31,6 +34,12 @@ export async function getWorkspaceById(id) {
31
34
  if (!row) return undefined;
32
35
  return toWorkspace(row);
33
36
  }
37
+ export async function isWorkspaceSlugAvailable(slug) {
38
+ const rows = await db().select({
39
+ id: workspaces.id
40
+ }).from(workspaces).where(eq(workspaces.slug, slug)).limit(1);
41
+ return rows.length === 0;
42
+ }
34
43
  function adminWorkspaceCursorWhere(cursor) {
35
44
  if (!cursor) return undefined;
36
45
  return or(gt(workspaces.name, cursor.value), and(eq(workspaces.name, cursor.value), gt(workspaces.id, cursor.id)));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/workspaces.ts"],"sourcesContent":["import type {StringIdCursor} from '@shipfox/node-drizzle';\nimport {and, asc, count, eq, gt, ilike, inArray, isNull, or, type SQL, sql} from 'drizzle-orm';\nimport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nimport {recordWorkspaceCreated} from '#metrics/instance.js';\nimport {db} from './db.js';\nimport {invitations} from './schema/invitations.js';\nimport {memberships} from './schema/memberships.js';\nimport {toWorkspace, workspaces} from './schema/workspaces.js';\n\nexport interface CreateWorkspaceParams {\n name: string;\n}\n\nexport async function createWorkspace(params: CreateWorkspaceParams): Promise<Workspace> {\n const rows = await db()\n .insert(workspaces)\n .values({\n name: params.name,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n recordWorkspaceCreated();\n return toWorkspace(row);\n}\n\nexport interface UpdateWorkspaceParams {\n id: string;\n name?: string | undefined;\n status?: WorkspaceStatus | undefined;\n settings?: Record<string, unknown> | undefined;\n}\n\nexport async function updateWorkspace(\n params: UpdateWorkspaceParams,\n): Promise<Workspace | undefined> {\n const set: Record<string, unknown> = {updatedAt: sql`NOW()`};\n if (params.name !== undefined) set.name = params.name;\n if (params.status !== undefined) set.status = params.status;\n if (params.settings !== undefined) set.settings = params.settings;\n\n const rows = await db()\n .update(workspaces)\n .set(set)\n .where(eq(workspaces.id, params.id))\n .returning();\n\n const row = rows[0];\n if (!row) return undefined;\n return toWorkspace(row);\n}\n\nexport async function getWorkspaceById(id: string): Promise<Workspace | undefined> {\n const rows = await db().select().from(workspaces).where(eq(workspaces.id, id)).limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toWorkspace(row);\n}\n\nexport interface AdminWorkspaceRow extends Workspace {\n memberCount: number;\n}\n\nexport interface ListAdminWorkspaceParams {\n workspaceId?: string | undefined;\n search?: string | undefined;\n status?: WorkspaceStatus | undefined;\n limit: number;\n cursor?: StringIdCursor | undefined;\n}\n\nexport interface ListAdminWorkspaceResult {\n workspaces: AdminWorkspaceRow[];\n nextCursor: StringIdCursor | null;\n}\n\nfunction adminWorkspaceCursorWhere(cursor: StringIdCursor | undefined): SQL | undefined {\n if (!cursor) return undefined;\n return or(\n gt(workspaces.name, cursor.value),\n and(eq(workspaces.name, cursor.value), gt(workspaces.id, cursor.id)),\n );\n}\n\nexport async function listAdminWorkspaces(\n params: ListAdminWorkspaceParams,\n): Promise<ListAdminWorkspaceResult> {\n const conditions = [] as SQL[];\n if (params.workspaceId) conditions.push(eq(workspaces.id, params.workspaceId));\n if (params.search) {\n conditions.push(ilike(workspaces.name, `%${escapeIlikePattern(params.search)}%`));\n }\n if (params.status) conditions.push(eq(workspaces.status, params.status));\n const cursor = adminWorkspaceCursorWhere(params.cursor);\n if (cursor) conditions.push(cursor);\n\n const rows = await db()\n .select({workspace: workspaces})\n .from(workspaces)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(asc(workspaces.name), asc(workspaces.id))\n .limit(params.limit + 1);\n\n const hasMore = rows.length > params.limit;\n const pageRows = hasMore ? rows.slice(0, params.limit) : rows;\n const last = pageRows.at(-1);\n const workspaceIds = pageRows.map((row) => row.workspace.id);\n const memberCountRows =\n workspaceIds.length > 0\n ? await db()\n .select({workspaceId: memberships.workspaceId, memberCount: count(memberships.id)})\n .from(memberships)\n .where(inArray(memberships.workspaceId, workspaceIds))\n .groupBy(memberships.workspaceId)\n : [];\n const memberCounts = new Map(\n memberCountRows.map((row) => [row.workspaceId, Number(row.memberCount)]),\n );\n\n return {\n workspaces: pageRows.map((row) => ({\n ...toWorkspace(row.workspace),\n memberCount: memberCounts.get(row.workspace.id) ?? 0,\n })),\n nextCursor: hasMore && last ? {value: last.workspace.name, id: last.workspace.id} : null,\n };\n}\n\nfunction escapeIlikePattern(value: string): string {\n return value.replace(/[\\\\%_]/g, (char) => `\\\\${char}`);\n}\n\nexport interface WorkspaceServiceMetrics {\n activeWorkspaces: number;\n memberships: number;\n openInvitations: number;\n}\n\nexport async function getWorkspaceServiceMetrics(): Promise<WorkspaceServiceMetrics> {\n const [workspaceRows, membershipRows, invitationRows] = await Promise.all([\n db().select({value: count()}).from(workspaces).where(eq(workspaces.status, 'active')),\n db().select({value: count()}).from(memberships),\n db()\n .select({value: count()})\n .from(invitations)\n .where(and(isNull(invitations.acceptedAt), gt(invitations.expiresAt, sql`now()`))),\n ]);\n\n return {\n activeWorkspaces: workspaceRows[0]?.value ?? 0,\n memberships: membershipRows[0]?.value ?? 0,\n openInvitations: invitationRows[0]?.value ?? 0,\n };\n}\n"],"names":["and","asc","count","eq","gt","ilike","inArray","isNull","or","sql","recordWorkspaceCreated","db","invitations","memberships","toWorkspace","workspaces","createWorkspace","params","rows","insert","values","name","returning","row","Error","updateWorkspace","set","updatedAt","undefined","status","settings","update","where","id","getWorkspaceById","select","from","limit","adminWorkspaceCursorWhere","cursor","value","listAdminWorkspaces","conditions","workspaceId","push","search","escapeIlikePattern","workspace","length","orderBy","hasMore","pageRows","slice","last","at","workspaceIds","map","memberCountRows","memberCount","groupBy","memberCounts","Map","Number","get","nextCursor","replace","char","getWorkspaceServiceMetrics","workspaceRows","membershipRows","invitationRows","Promise","all","acceptedAt","expiresAt","activeWorkspaces","openInvitations"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/workspaces.ts"],"sourcesContent":["import type {StringIdCursor} from '@shipfox/node-drizzle';\nimport {and, asc, count, eq, gt, ilike, inArray, isNull, or, type SQL, sql} from 'drizzle-orm';\nimport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nimport {recordWorkspaceCreated} from '#metrics/instance.js';\nimport {db} from './db.js';\nimport {invitations} from './schema/invitations.js';\nimport {memberships} from './schema/memberships.js';\nimport {toWorkspace, workspaces} from './schema/workspaces.js';\n\nexport interface CreateWorkspaceParams {\n name: string;\n /** Existing database-only fixtures predate the required API slug. */\n slug?: string | undefined;\n}\n\nexport async function createWorkspace(params: CreateWorkspaceParams): Promise<Workspace> {\n const rows = await db()\n .insert(workspaces)\n .values({\n name: params.name,\n slug: params.slug ?? `workspace-${crypto.randomUUID().slice(0, 8)}`,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n recordWorkspaceCreated();\n return toWorkspace(row);\n}\n\nexport interface UpdateWorkspaceParams {\n id: string;\n name?: string | undefined;\n slug?: string | undefined;\n status?: WorkspaceStatus | undefined;\n settings?: Record<string, unknown> | undefined;\n}\n\ntype WorkspaceDatabase = ReturnType<typeof db>;\ntype WorkspaceTransaction = Parameters<Parameters<WorkspaceDatabase['transaction']>[0]>[0];\n\nexport async function updateWorkspace(\n params: UpdateWorkspaceParams,\n options: {tx?: WorkspaceDatabase | WorkspaceTransaction | undefined} = {},\n): Promise<Workspace | undefined> {\n const executor = options.tx ?? db();\n const set: Record<string, unknown> = {updatedAt: sql`NOW()`};\n if (params.name !== undefined) set.name = params.name;\n if (params.slug !== undefined) set.slug = params.slug;\n if (params.status !== undefined) set.status = params.status;\n if (params.settings !== undefined) set.settings = params.settings;\n\n const rows = await executor\n .update(workspaces)\n .set(set)\n .where(eq(workspaces.id, params.id))\n .returning();\n\n const row = rows[0];\n if (!row) return undefined;\n return toWorkspace(row);\n}\n\nexport async function getWorkspaceById(id: string): Promise<Workspace | undefined> {\n const rows = await db().select().from(workspaces).where(eq(workspaces.id, id)).limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toWorkspace(row);\n}\n\nexport async function isWorkspaceSlugAvailable(slug: string): Promise<boolean> {\n const rows = await db()\n .select({id: workspaces.id})\n .from(workspaces)\n .where(eq(workspaces.slug, slug))\n .limit(1);\n\n return rows.length === 0;\n}\n\nexport interface AdminWorkspaceRow extends Workspace {\n memberCount: number;\n}\n\nexport interface ListAdminWorkspaceParams {\n workspaceId?: string | undefined;\n search?: string | undefined;\n status?: WorkspaceStatus | undefined;\n limit: number;\n cursor?: StringIdCursor | undefined;\n}\n\nexport interface ListAdminWorkspaceResult {\n workspaces: AdminWorkspaceRow[];\n nextCursor: StringIdCursor | null;\n}\n\nfunction adminWorkspaceCursorWhere(cursor: StringIdCursor | undefined): SQL | undefined {\n if (!cursor) return undefined;\n return or(\n gt(workspaces.name, cursor.value),\n and(eq(workspaces.name, cursor.value), gt(workspaces.id, cursor.id)),\n );\n}\n\nexport async function listAdminWorkspaces(\n params: ListAdminWorkspaceParams,\n): Promise<ListAdminWorkspaceResult> {\n const conditions = [] as SQL[];\n if (params.workspaceId) conditions.push(eq(workspaces.id, params.workspaceId));\n if (params.search) {\n conditions.push(ilike(workspaces.name, `%${escapeIlikePattern(params.search)}%`));\n }\n if (params.status) conditions.push(eq(workspaces.status, params.status));\n const cursor = adminWorkspaceCursorWhere(params.cursor);\n if (cursor) conditions.push(cursor);\n\n const rows = await db()\n .select({workspace: workspaces})\n .from(workspaces)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(asc(workspaces.name), asc(workspaces.id))\n .limit(params.limit + 1);\n\n const hasMore = rows.length > params.limit;\n const pageRows = hasMore ? rows.slice(0, params.limit) : rows;\n const last = pageRows.at(-1);\n const workspaceIds = pageRows.map((row) => row.workspace.id);\n const memberCountRows =\n workspaceIds.length > 0\n ? await db()\n .select({workspaceId: memberships.workspaceId, memberCount: count(memberships.id)})\n .from(memberships)\n .where(inArray(memberships.workspaceId, workspaceIds))\n .groupBy(memberships.workspaceId)\n : [];\n const memberCounts = new Map(\n memberCountRows.map((row) => [row.workspaceId, Number(row.memberCount)]),\n );\n\n return {\n workspaces: pageRows.map((row) => ({\n ...toWorkspace(row.workspace),\n memberCount: memberCounts.get(row.workspace.id) ?? 0,\n })),\n nextCursor: hasMore && last ? {value: last.workspace.name, id: last.workspace.id} : null,\n };\n}\n\nfunction escapeIlikePattern(value: string): string {\n return value.replace(/[\\\\%_]/g, (char) => `\\\\${char}`);\n}\n\nexport interface WorkspaceServiceMetrics {\n activeWorkspaces: number;\n memberships: number;\n openInvitations: number;\n}\n\nexport async function getWorkspaceServiceMetrics(): Promise<WorkspaceServiceMetrics> {\n const [workspaceRows, membershipRows, invitationRows] = await Promise.all([\n db().select({value: count()}).from(workspaces).where(eq(workspaces.status, 'active')),\n db().select({value: count()}).from(memberships),\n db()\n .select({value: count()})\n .from(invitations)\n .where(and(isNull(invitations.acceptedAt), gt(invitations.expiresAt, sql`now()`))),\n ]);\n\n return {\n activeWorkspaces: workspaceRows[0]?.value ?? 0,\n memberships: membershipRows[0]?.value ?? 0,\n openInvitations: invitationRows[0]?.value ?? 0,\n };\n}\n"],"names":["and","asc","count","eq","gt","ilike","inArray","isNull","or","sql","recordWorkspaceCreated","db","invitations","memberships","toWorkspace","workspaces","createWorkspace","params","rows","insert","values","name","slug","crypto","randomUUID","slice","returning","row","Error","updateWorkspace","options","executor","tx","set","updatedAt","undefined","status","settings","update","where","id","getWorkspaceById","select","from","limit","isWorkspaceSlugAvailable","length","adminWorkspaceCursorWhere","cursor","value","listAdminWorkspaces","conditions","workspaceId","push","search","escapeIlikePattern","workspace","orderBy","hasMore","pageRows","last","at","workspaceIds","map","memberCountRows","memberCount","groupBy","memberCounts","Map","Number","get","nextCursor","replace","char","getWorkspaceServiceMetrics","workspaceRows","membershipRows","invitationRows","Promise","all","acceptedAt","expiresAt","activeWorkspaces","openInvitations"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import type { ShipfoxModule } from '@shipfox/node-module';
5
5
  export type { Invitation } from '#core/entities/invitation.js';
6
6
  export type { Membership } from '#core/entities/membership.js';
7
7
  export type { Workspace, WorkspaceStatus } from '#core/entities/workspace.js';
8
- export { InvitationEmailMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, WorkspaceNotFoundError, } from '#core/errors.js';
8
+ export { InvitationEmailMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, WorkspaceNotFoundError, WorkspaceSlugConflictError, } from '#core/errors.js';
9
9
  export { acceptWorkspaceInvitation, peekInvitationByRawToken, reconcileWorkspaceInvitationAcceptance, type WorkspaceInvitationReconciliation, } from '#core/invitations.js';
10
10
  export { type EnsureMembershipParams, ensureMembership } from '#core/memberships.js';
11
11
  export { getWorkspace, requireWorkspaceMembership } from '#core/workspaces.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oCAAoC,CAAC;AAE9E,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wCAAwC,CAAC;AACjF,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uCAAuC,CAAC;AAMpF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AASxD,YAAY,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,sCAAsC,EACtC,KAAK,iCAAiC,GACvC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAC,KAAK,sBAAsB,EAAE,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAC,YAAY,EAAE,0BAA0B,EAAC,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAC,EAAE,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAChD,OAAO,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAC,sBAAsB,EAAE,gBAAgB,IAAI,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAQjG,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,aAAa,CAkBtF;AAED,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,EAAE,aAgB9B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oCAAoC,CAAC;AAE9E,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wCAAwC,CAAC;AACjF,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uCAAuC,CAAC;AAMpF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AASxD,YAAY,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,sCAAsC,EACtC,KAAK,iCAAiC,GACvC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAC,KAAK,sBAAsB,EAAE,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAC,YAAY,EAAE,0BAA0B,EAAC,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAC,EAAE,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAChD,OAAO,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAC,sBAAsB,EAAE,gBAAgB,IAAI,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAQjG,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,aAAa,CAkBtF;AAED,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,EAAE,aAgB9B,CAAC"}
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { workspacesE2eRoutes } from '#presentation/e2eRoutes/index.js';
7
7
  import { createWorkspacesInterModulePresentation } from '#presentation/inter-module.js';
8
8
  import { createWorkspacesRoutes, workspacesRoutes } from '#presentation/routes/index.js';
9
9
  import { onInvitationSendRequested } from '#presentation/subscribers/index.js';
10
- export { InvitationEmailMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, WorkspaceNotFoundError } from '#core/errors.js';
10
+ export { InvitationEmailMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, WorkspaceNotFoundError, WorkspaceSlugConflictError } from '#core/errors.js';
11
11
  export { acceptWorkspaceInvitation, peekInvitationByRawToken, reconcileWorkspaceInvitationAcceptance } from '#core/invitations.js';
12
12
  export { ensureMembership } from '#core/memberships.js';
13
13
  export { getWorkspace, requireWorkspaceMembership } from '#core/workspaces.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport {administrationActionEventSchemas} from '@shipfox/api-common-dto';\nimport type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';\nimport type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';\nimport {\n WORKSPACES_INVITATION_SEND_REQUESTED,\n type WorkspacesEventMap,\n workspacesEventSchemas,\n} from '@shipfox/api-workspaces-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {db, migrationsPath, workspacesOutbox} from '#db/index.js';\nimport {registerWorkspacesServiceMetrics} from '#metrics/index.js';\nimport {workspacesE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createWorkspacesInterModulePresentation} from '#presentation/inter-module.js';\nimport {createWorkspacesRoutes, workspacesRoutes} from '#presentation/routes/index.js';\nimport {onInvitationSendRequested} from '#presentation/subscribers/index.js';\n\nexport type {Invitation} from '#core/entities/invitation.js';\nexport type {Membership} from '#core/entities/membership.js';\nexport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nexport {\n InvitationEmailMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nexport {\n acceptWorkspaceInvitation,\n peekInvitationByRawToken,\n reconcileWorkspaceInvitationAcceptance,\n type WorkspaceInvitationReconciliation,\n} from '#core/invitations.js';\nexport {type EnsureMembershipParams, ensureMembership} from '#core/memberships.js';\nexport {getWorkspace, requireWorkspaceMembership} from '#core/workspaces.js';\nexport {db, migrationsPath} from '#db/index.js';\nexport {listMembershipsByUser} from '#db/memberships.js';\nexport {createWorkspacesRoutes, workspacesRoutes as routes} from '#presentation/routes/index.js';\n\nconst subscriber = subscriberFactory<WorkspacesEventMap>();\nconst workspacesPublisherEventSchemas = {\n ...workspacesEventSchemas,\n ...administrationActionEventSchemas,\n};\n\nexport interface WorkspacesModuleOptions {\n auth: AuthInterModuleClient;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}\n\nexport function createWorkspacesModule(options: WorkspacesModuleOptions): ShipfoxModule {\n return {\n name: 'workspaces',\n database: {db, migrationsPath, databaseNamespace: 'workspaces'},\n routes: createWorkspacesRoutes(options),\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {\n name: 'workspaces',\n table: workspacesOutbox,\n db,\n eventSchemas: workspacesPublisherEventSchemas,\n },\n ],\n subscribers: [subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)],\n metrics: registerWorkspacesServiceMetrics,\n interModulePresentations: [createWorkspacesInterModulePresentation()],\n };\n}\n\n/** @deprecated Use createWorkspacesModule from the application composition root. */\nexport const workspacesModule: ShipfoxModule = {\n name: 'workspaces',\n database: {db, migrationsPath, databaseNamespace: 'workspaces'},\n routes: workspacesRoutes,\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {\n name: 'workspaces',\n table: workspacesOutbox,\n db,\n eventSchemas: workspacesPublisherEventSchemas,\n },\n ],\n subscribers: [subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)],\n metrics: registerWorkspacesServiceMetrics,\n interModulePresentations: [createWorkspacesInterModulePresentation()],\n};\n"],"names":["administrationActionEventSchemas","WORKSPACES_INVITATION_SEND_REQUESTED","workspacesEventSchemas","subscriberFactory","db","migrationsPath","workspacesOutbox","registerWorkspacesServiceMetrics","workspacesE2eRoutes","createWorkspacesInterModulePresentation","createWorkspacesRoutes","workspacesRoutes","onInvitationSendRequested","InvitationEmailMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","WorkspaceNotFoundError","acceptWorkspaceInvitation","peekInvitationByRawToken","reconcileWorkspaceInvitationAcceptance","ensureMembership","getWorkspace","requireWorkspaceMembership","listMembershipsByUser","routes","subscriber","workspacesPublisherEventSchemas","createWorkspacesModule","options","name","database","databaseNamespace","e2eRoutes","publishers","table","eventSchemas","subscribers","metrics","interModulePresentations","workspacesModule"],"mappings":"AACA,SAAQA,gCAAgC,QAAO,0BAA0B;AAGzE,SACEC,oCAAoC,EAEpCC,sBAAsB,QACjB,8BAA8B;AAErC,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,EAAE,EAAEC,cAAc,EAAEC,gBAAgB,QAAO,eAAe;AAClE,SAAQC,gCAAgC,QAAO,oBAAoB;AACnE,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,uCAAuC,QAAO,gCAAgC;AACtF,SAAQC,sBAAsB,EAAEC,gBAAgB,QAAO,gCAAgC;AACvF,SAAQC,yBAAyB,QAAO,qCAAqC;AAK7E,SACEC,4BAA4B,EAC5BC,qBAAqB,EACrBC,iBAAiB,EACjBC,iBAAiB,EACjBC,sBAAsB,QACjB,kBAAkB;AACzB,SACEC,yBAAyB,EACzBC,wBAAwB,EACxBC,sCAAsC,QAEjC,uBAAuB;AAC9B,SAAqCC,gBAAgB,QAAO,uBAAuB;AACnF,SAAQC,YAAY,EAAEC,0BAA0B,QAAO,sBAAsB;AAC7E,SAAQnB,EAAE,EAAEC,cAAc,QAAO,eAAe;AAChD,SAAQmB,qBAAqB,QAAO,qBAAqB;AACzD,SAAQd,sBAAsB,EAAEC,oBAAoBc,MAAM,QAAO,gCAAgC;AAEjG,MAAMC,aAAavB;AACnB,MAAMwB,kCAAkC;IACtC,GAAGzB,sBAAsB;IACzB,GAAGF,gCAAgC;AACrC;AAQA,OAAO,SAAS4B,uBAAuBC,OAAgC;IACrE,OAAO;QACLC,MAAM;QACNC,UAAU;YAAC3B;YAAIC;YAAgB2B,mBAAmB;QAAY;QAC9DP,QAAQf,uBAAuBmB;QAC/BI,WAAW;YAACzB;SAAoB;QAChC0B,YAAY;YACV;gBACEJ,MAAM;gBACNK,OAAO7B;gBACPF;gBACAgC,cAAcT;YAChB;SACD;QACDU,aAAa;YAACX,WAAWzB,sCAAsCW;SAA2B;QAC1F0B,SAAS/B;QACTgC,0BAA0B;YAAC9B;SAA0C;IACvE;AACF;AAEA,kFAAkF,GAClF,OAAO,MAAM+B,mBAAkC;IAC7CV,MAAM;IACNC,UAAU;QAAC3B;QAAIC;QAAgB2B,mBAAmB;IAAY;IAC9DP,QAAQd;IACRsB,WAAW;QAACzB;KAAoB;IAChC0B,YAAY;QACV;YACEJ,MAAM;YACNK,OAAO7B;YACPF;YACAgC,cAAcT;QAChB;KACD;IACDU,aAAa;QAACX,WAAWzB,sCAAsCW;KAA2B;IAC1F0B,SAAS/B;IACTgC,0BAA0B;QAAC9B;KAA0C;AACvE,EAAE"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport {administrationActionEventSchemas} from '@shipfox/api-common-dto';\nimport type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';\nimport type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';\nimport {\n WORKSPACES_INVITATION_SEND_REQUESTED,\n type WorkspacesEventMap,\n workspacesEventSchemas,\n} from '@shipfox/api-workspaces-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {db, migrationsPath, workspacesOutbox} from '#db/index.js';\nimport {registerWorkspacesServiceMetrics} from '#metrics/index.js';\nimport {workspacesE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createWorkspacesInterModulePresentation} from '#presentation/inter-module.js';\nimport {createWorkspacesRoutes, workspacesRoutes} from '#presentation/routes/index.js';\nimport {onInvitationSendRequested} from '#presentation/subscribers/index.js';\n\nexport type {Invitation} from '#core/entities/invitation.js';\nexport type {Membership} from '#core/entities/membership.js';\nexport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nexport {\n InvitationEmailMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n WorkspaceNotFoundError,\n WorkspaceSlugConflictError,\n} from '#core/errors.js';\nexport {\n acceptWorkspaceInvitation,\n peekInvitationByRawToken,\n reconcileWorkspaceInvitationAcceptance,\n type WorkspaceInvitationReconciliation,\n} from '#core/invitations.js';\nexport {type EnsureMembershipParams, ensureMembership} from '#core/memberships.js';\nexport {getWorkspace, requireWorkspaceMembership} from '#core/workspaces.js';\nexport {db, migrationsPath} from '#db/index.js';\nexport {listMembershipsByUser} from '#db/memberships.js';\nexport {createWorkspacesRoutes, workspacesRoutes as routes} from '#presentation/routes/index.js';\n\nconst subscriber = subscriberFactory<WorkspacesEventMap>();\nconst workspacesPublisherEventSchemas = {\n ...workspacesEventSchemas,\n ...administrationActionEventSchemas,\n};\n\nexport interface WorkspacesModuleOptions {\n auth: AuthInterModuleClient;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}\n\nexport function createWorkspacesModule(options: WorkspacesModuleOptions): ShipfoxModule {\n return {\n name: 'workspaces',\n database: {db, migrationsPath, databaseNamespace: 'workspaces'},\n routes: createWorkspacesRoutes(options),\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {\n name: 'workspaces',\n table: workspacesOutbox,\n db,\n eventSchemas: workspacesPublisherEventSchemas,\n },\n ],\n subscribers: [subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)],\n metrics: registerWorkspacesServiceMetrics,\n interModulePresentations: [createWorkspacesInterModulePresentation()],\n };\n}\n\n/** @deprecated Use createWorkspacesModule from the application composition root. */\nexport const workspacesModule: ShipfoxModule = {\n name: 'workspaces',\n database: {db, migrationsPath, databaseNamespace: 'workspaces'},\n routes: workspacesRoutes,\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {\n name: 'workspaces',\n table: workspacesOutbox,\n db,\n eventSchemas: workspacesPublisherEventSchemas,\n },\n ],\n subscribers: [subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)],\n metrics: registerWorkspacesServiceMetrics,\n interModulePresentations: [createWorkspacesInterModulePresentation()],\n};\n"],"names":["administrationActionEventSchemas","WORKSPACES_INVITATION_SEND_REQUESTED","workspacesEventSchemas","subscriberFactory","db","migrationsPath","workspacesOutbox","registerWorkspacesServiceMetrics","workspacesE2eRoutes","createWorkspacesInterModulePresentation","createWorkspacesRoutes","workspacesRoutes","onInvitationSendRequested","InvitationEmailMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","WorkspaceNotFoundError","WorkspaceSlugConflictError","acceptWorkspaceInvitation","peekInvitationByRawToken","reconcileWorkspaceInvitationAcceptance","ensureMembership","getWorkspace","requireWorkspaceMembership","listMembershipsByUser","routes","subscriber","workspacesPublisherEventSchemas","createWorkspacesModule","options","name","database","databaseNamespace","e2eRoutes","publishers","table","eventSchemas","subscribers","metrics","interModulePresentations","workspacesModule"],"mappings":"AACA,SAAQA,gCAAgC,QAAO,0BAA0B;AAGzE,SACEC,oCAAoC,EAEpCC,sBAAsB,QACjB,8BAA8B;AAErC,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,EAAE,EAAEC,cAAc,EAAEC,gBAAgB,QAAO,eAAe;AAClE,SAAQC,gCAAgC,QAAO,oBAAoB;AACnE,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,uCAAuC,QAAO,gCAAgC;AACtF,SAAQC,sBAAsB,EAAEC,gBAAgB,QAAO,gCAAgC;AACvF,SAAQC,yBAAyB,QAAO,qCAAqC;AAK7E,SACEC,4BAA4B,EAC5BC,qBAAqB,EACrBC,iBAAiB,EACjBC,iBAAiB,EACjBC,sBAAsB,EACtBC,0BAA0B,QACrB,kBAAkB;AACzB,SACEC,yBAAyB,EACzBC,wBAAwB,EACxBC,sCAAsC,QAEjC,uBAAuB;AAC9B,SAAqCC,gBAAgB,QAAO,uBAAuB;AACnF,SAAQC,YAAY,EAAEC,0BAA0B,QAAO,sBAAsB;AAC7E,SAAQpB,EAAE,EAAEC,cAAc,QAAO,eAAe;AAChD,SAAQoB,qBAAqB,QAAO,qBAAqB;AACzD,SAAQf,sBAAsB,EAAEC,oBAAoBe,MAAM,QAAO,gCAAgC;AAEjG,MAAMC,aAAaxB;AACnB,MAAMyB,kCAAkC;IACtC,GAAG1B,sBAAsB;IACzB,GAAGF,gCAAgC;AACrC;AAQA,OAAO,SAAS6B,uBAAuBC,OAAgC;IACrE,OAAO;QACLC,MAAM;QACNC,UAAU;YAAC5B;YAAIC;YAAgB4B,mBAAmB;QAAY;QAC9DP,QAAQhB,uBAAuBoB;QAC/BI,WAAW;YAAC1B;SAAoB;QAChC2B,YAAY;YACV;gBACEJ,MAAM;gBACNK,OAAO9B;gBACPF;gBACAiC,cAAcT;YAChB;SACD;QACDU,aAAa;YAACX,WAAW1B,sCAAsCW;SAA2B;QAC1F2B,SAAShC;QACTiC,0BAA0B;YAAC/B;SAA0C;IACvE;AACF;AAEA,kFAAkF,GAClF,OAAO,MAAMgC,mBAAkC;IAC7CV,MAAM;IACNC,UAAU;QAAC5B;QAAIC;QAAgB4B,mBAAmB;IAAY;IAC9DP,QAAQf;IACRuB,WAAW;QAAC1B;KAAoB;IAChC2B,YAAY;QACV;YACEJ,MAAM;YACNK,OAAO9B;YACPF;YACAiC,cAAcT;QAChB;KACD;IACDU,aAAa;QAACX,WAAW1B,sCAAsCW;KAA2B;IAC1F2B,SAAShC;IACTiC,0BAA0B;QAAC/B;KAA0C;AACvE,EAAE"}
@@ -1,8 +1,14 @@
1
1
  export type WorkspacesInvitationEmailRequested = 'requested' | 'skipped';
2
2
  export type WorkspacesMembershipChangeAction = 'added' | 'removed';
3
3
  export type WorkspacesInvitationAcceptOutcome = 'added' | 'already_member';
4
+ export type WorkspacesRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';
4
5
  export declare function recordWorkspaceCreated(): void;
5
6
  export declare function recordWorkspaceMembershipChanged(action: WorkspacesMembershipChangeAction): void;
6
7
  export declare function recordWorkspaceInvitationCreated(emailRequested: WorkspacesInvitationEmailRequested): void;
7
8
  export declare function recordWorkspaceInvitationAccepted(outcome: WorkspacesInvitationAcceptOutcome): void;
9
+ export declare function recordWorkspaceRateLimitCheck(params: {
10
+ action: 'slug-availability';
11
+ outcome: WorkspacesRateLimitOutcome;
12
+ }): void;
13
+ export declare function recordWorkspaceRateLimitPruneFailure(): void;
8
14
  //# sourceMappingURL=instance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kCAAkC,GAAG,WAAW,GAAG,SAAS,CAAC;AACzE,MAAM,MAAM,gCAAgC,GAAG,OAAO,GAAG,SAAS,CAAC;AACnE,MAAM,MAAM,iCAAiC,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAkC3E,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,gCAAgC,GAAG,IAAI,CAE/F;AAED,wBAAgB,gCAAgC,CAC9C,cAAc,EAAE,kCAAkC,GACjD,IAAI,CAEN;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,iCAAiC,GACzC,IAAI,CAEN"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kCAAkC,GAAG,WAAW,GAAG,SAAS,CAAC;AACzE,MAAM,MAAM,gCAAgC,GAAG,OAAO,GAAG,SAAS,CAAC;AACnE,MAAM,MAAM,iCAAiC,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAC3E,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAgD/E,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,gCAAgC,GAAG,IAAI,CAE/F;AAED,wBAAgB,gCAAgC,CAC9C,cAAc,EAAE,kCAAkC,GACjD,IAAI,CAEN;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,iCAAiC,GACzC,IAAI,CAEN;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,0BAA0B,CAAC;CACrC,GAAG,IAAI,CAEP;AAED,wBAAgB,oCAAoC,IAAI,IAAI,CAE3D"}