permdock 0.0.0 → 0.1.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 (290) hide show
  1. package/README.md +268 -1
  2. package/dist/a2a/index.d.ts +178 -0
  3. package/dist/a2a/index.js +271 -0
  4. package/dist/ai-sdk/index.d.ts +74 -0
  5. package/dist/ai-sdk/index.js +137 -0
  6. package/dist/approval-required-TzNB9oQA.js +6 -0
  7. package/dist/approvals/index.d.ts +3 -0
  8. package/dist/approvals/index.js +4 -0
  9. package/dist/approvals-DaxY-Bdg.js +218 -0
  10. package/dist/approvers-aH0PJkmf.js +20 -0
  11. package/dist/arazzo-CTeVuXr5.js +70 -0
  12. package/dist/arazzo-aoTkqYsJ.js +54 -0
  13. package/dist/authzen/index.d.ts +48 -0
  14. package/dist/authzen/index.js +336 -0
  15. package/dist/better-auth/index.d.ts +92 -0
  16. package/dist/better-auth/index.js +324 -0
  17. package/dist/budget-JqcRqyhK.d.ts +11 -0
  18. package/dist/budget-NbEtbJC_.js +11 -0
  19. package/dist/capability-B68OFnZx.js +171 -0
  20. package/dist/capability-BDG_nkxt.d.ts +99 -0
  21. package/dist/catalog/index.d.ts +12 -0
  22. package/dist/catalog/index.js +2 -0
  23. package/dist/catalog-B0E6bggc.js +48 -0
  24. package/dist/catalog-H-vFGwWK.js +55 -0
  25. package/dist/catalog-doc-BSo_Q_-6.js +298 -0
  26. package/dist/catalog-idsHc0vo.js +2 -0
  27. package/dist/catalog-path-CnI0XTFK.js +12 -0
  28. package/dist/claude-agent/index.d.ts +81 -0
  29. package/dist/claude-agent/index.js +83 -0
  30. package/dist/clerk/index.d.ts +56 -0
  31. package/dist/clerk/index.js +280 -0
  32. package/dist/cli/bin.d.ts +1 -0
  33. package/dist/cli/bin.js +20 -0
  34. package/dist/cli/index.d.ts +46 -0
  35. package/dist/cli/index.js +5 -0
  36. package/dist/cloud/index.d.ts +91 -0
  37. package/dist/cloud/index.js +390 -0
  38. package/dist/cloud-BNQTrmrr.js +50 -0
  39. package/dist/cloud-Cuxr6Czw.js +117 -0
  40. package/dist/collect-B7_TIfmg.js +49 -0
  41. package/dist/collect-O-tOtbnk.js +350 -0
  42. package/dist/collect-fRPAQIWb.js +2 -0
  43. package/dist/commands-BMh_Etm2.js +93 -0
  44. package/dist/compact-y6vfu2_f.js +19 -0
  45. package/dist/compare-pfyVnBjE.js +12 -0
  46. package/dist/compile-CQbvyIBr.d.ts +30 -0
  47. package/dist/compile-D22iwvuV.js +558 -0
  48. package/dist/config-BPYmjTOp.js +26 -0
  49. package/dist/connection-JvqwHODC.js +194 -0
  50. package/dist/context-CKEAabd7.js +73 -0
  51. package/dist/context-gtAwAILx.js +6 -0
  52. package/dist/convex/index.d.ts +40 -0
  53. package/dist/convex/index.js +55 -0
  54. package/dist/create-BKF3dgFk.d.ts +162 -0
  55. package/dist/create-CSkhDU9T.js +222 -0
  56. package/dist/credential-B_rkUNlY.js +326 -0
  57. package/dist/credentials-BgRX3WEg.js +268 -0
  58. package/dist/describe-Bt1YD5uS.js +99 -0
  59. package/dist/describe-DernyItf.d.ts +37 -0
  60. package/dist/diff-BUzf5Poi.js +48 -0
  61. package/dist/diff-x5Il8LVa.js +418 -0
  62. package/dist/digest-B3kc_PgQ.d.ts +19 -0
  63. package/dist/digest-BI-6aa6d.js +27 -0
  64. package/dist/doctor-BGle8Gqi.js +1314 -0
  65. package/dist/doctor-Bqo6FwUa.js +53 -0
  66. package/dist/drizzle/index.d.ts +72 -0
  67. package/dist/drizzle/index.js +160 -0
  68. package/dist/duration-xK2jGFVc.js +25 -0
  69. package/dist/elysia/index.d.ts +71 -0
  70. package/dist/elysia/index.js +103 -0
  71. package/dist/errors-DSMnAVNa.js +43 -0
  72. package/dist/errors-DbZ1YOnb.d.ts +138 -0
  73. package/dist/errors-Iy71s7Oj.js +177 -0
  74. package/dist/errors-m3u9TmDu.js +27 -0
  75. package/dist/evaluations-DsAU9ppl.js +479 -0
  76. package/dist/eve/index.d.ts +95 -0
  77. package/dist/eve/index.js +181 -0
  78. package/dist/express/index.d.ts +46 -0
  79. package/dist/express/index.js +108 -0
  80. package/dist/fastify/index.d.ts +56 -0
  81. package/dist/fastify/index.js +141 -0
  82. package/dist/files-B_wAGYmt.js +121 -0
  83. package/dist/fixtures-Cvaw0gDG.js +64 -0
  84. package/dist/freeze-y1_jQL4v.js +31 -0
  85. package/dist/from-snapshot-BMGnhmgv.js +552 -0
  86. package/dist/generate-CH7ThJ14.js +173 -0
  87. package/dist/grantee-Bv04_Vzf.js +521 -0
  88. package/dist/headers-B5RRv3Xm.js +6 -0
  89. package/dist/headers-C8SHQccD.d.ts +6 -0
  90. package/dist/helpers-DnSOrhB9.js +682 -0
  91. package/dist/hono/index.d.ts +70 -0
  92. package/dist/hono/index.js +131 -0
  93. package/dist/hosted-BwKQ1BTA.js +265 -0
  94. package/dist/http-DPuDaChb.d.ts +13 -0
  95. package/dist/http-GgVApX1k.js +111 -0
  96. package/dist/index-CltcQ4Bf.d.ts +138 -0
  97. package/dist/index-LF_hS-yn.d.ts +177 -0
  98. package/dist/index-fG6NSXlc.d.ts +9 -0
  99. package/dist/index-xIxJLwOQ.d.ts +415 -0
  100. package/dist/index.d.ts +11 -0
  101. package/dist/index.js +21 -0
  102. package/dist/jwt/index.d.ts +87 -0
  103. package/dist/jwt/index.js +571 -0
  104. package/dist/kernel-YOGCWNLf.js +211 -0
  105. package/dist/kysely/index.d.ts +77 -0
  106. package/dist/kysely/index.js +129 -0
  107. package/dist/load-BZ24CpZP.js +69 -0
  108. package/dist/load-QOOm1z70.js +2 -0
  109. package/dist/load-jose-BL6LH4bL.js +11 -0
  110. package/dist/lru-8q4WZjBV.js +28 -0
  111. package/dist/manifest-DDQbC6K0.d.ts +118 -0
  112. package/dist/map-DowSO96j.js +158 -0
  113. package/dist/map-claims-B_6I1Rn-.js +360 -0
  114. package/dist/map-error-RJ3ZsUyA.js +16 -0
  115. package/dist/markers-B0hYLvMD.js +60 -0
  116. package/dist/may-use-CnFEjVEp.js +37 -0
  117. package/dist/mcp/index.d.ts +127 -0
  118. package/dist/mcp/index.js +563 -0
  119. package/dist/nest/index.d.ts +80 -0
  120. package/dist/nest/index.js +270 -0
  121. package/dist/next/client.d.ts +24 -0
  122. package/dist/next/client.js +31 -0
  123. package/dist/next/index.d.ts +110 -0
  124. package/dist/next/index.js +195 -0
  125. package/dist/next/plugin.d.ts +14 -0
  126. package/dist/next/plugin.js +2 -0
  127. package/dist/node/index.d.ts +43 -0
  128. package/dist/node/index.js +66 -0
  129. package/dist/openai/index.d.ts +71 -0
  130. package/dist/openai/index.js +105 -0
  131. package/dist/openapi/index.d.ts +93 -0
  132. package/dist/openapi/index.js +3 -0
  133. package/dist/openapi-CwePaF7C.js +164 -0
  134. package/dist/openapi-DWD0Uc_4.js +250 -0
  135. package/dist/openapi-DcqM8nU7.js +306 -0
  136. package/dist/openapi-import-BWRUNNeW.js +5959 -0
  137. package/dist/openapi-schema-D8j_T9tO.js +6902 -0
  138. package/dist/orpc/index.d.ts +64 -0
  139. package/dist/orpc/index.js +188 -0
  140. package/dist/otel/index.d.ts +13 -0
  141. package/dist/otel/index.js +202 -0
  142. package/dist/package-root-zGkxO3oN.js +30 -0
  143. package/dist/parse-DPfIvFMU.js +135 -0
  144. package/dist/pdp/index.d.ts +22 -0
  145. package/dist/pdp/index.js +795 -0
  146. package/dist/peer-BOr_kJE1.js +31 -0
  147. package/dist/permdock-yoZ7EjxX.js +2956 -0
  148. package/dist/permissions-B4apnahQ.d.ts +185 -0
  149. package/dist/permissions-B8vCg_bI.d.ts +52 -0
  150. package/dist/permissions-CofRqesP.js +142 -0
  151. package/dist/permissions-DC7GROPA.js +600 -0
  152. package/dist/pg-4GJWDUxG.js +953 -0
  153. package/dist/pins-CqE1jZuF.js +12 -0
  154. package/dist/plugin-4gr7IxFJ.js +65 -0
  155. package/dist/policy-ANp36PnI.js +971 -0
  156. package/dist/policy-CInmNGkM.d.ts +1959 -0
  157. package/dist/policy-Cq2fB8Q5.d.ts +20 -0
  158. package/dist/prisma/index.d.ts +135 -0
  159. package/dist/prisma/index.js +430 -0
  160. package/dist/problem-Cgp9-KZA.js +226 -0
  161. package/dist/prompt-CXYhLfsS.js +32 -0
  162. package/dist/protected-B4K3BKoX.js +138 -0
  163. package/dist/protected-BuPAycJT.d.ts +26 -0
  164. package/dist/protected-wY7nXhY9.d.ts +23 -0
  165. package/dist/provider-_wx46HpB.js +48 -0
  166. package/dist/provider-wTAUda2v.d.ts +6 -0
  167. package/dist/react/index.d.ts +6 -0
  168. package/dist/react/index.js +6 -0
  169. package/dist/react/provider-client.d.ts +2 -0
  170. package/dist/react/provider-client.js +3 -0
  171. package/dist/react-native/index.d.ts +42 -0
  172. package/dist/react-native/index.js +174 -0
  173. package/dist/refs-ChLSxbST.js +90 -0
  174. package/dist/relations-r4soIFZB.js +426 -0
  175. package/dist/revocations-Cy06JNx2.d.ts +21 -0
  176. package/dist/rls-BOFU3chG.js +233 -0
  177. package/dist/rls-CJVja9Ww.js +2119 -0
  178. package/dist/rls-graph-DxQ39PJj.js +371 -0
  179. package/dist/rls-helpers-BxRuif0l.js +1346 -0
  180. package/dist/rls-import-CXV6LBlS.js +942 -0
  181. package/dist/rls-migrate-DrFPbf8p.js +366 -0
  182. package/dist/row-check-BajTgQZ-.d.ts +43 -0
  183. package/dist/run-CJ6UvS3a.js +155 -0
  184. package/dist/runtime-DP9ttFh1.js +27 -0
  185. package/dist/saas-BRrZzJkM.js +1193 -0
  186. package/dist/schema-P8yoma__.js +296 -0
  187. package/dist/scim/index.d.ts +2 -0
  188. package/dist/scim/index.js +2 -0
  189. package/dist/scim-9NFa0XVZ.js +1445 -0
  190. package/dist/scopes-cKVp_WTw.js +212 -0
  191. package/dist/server/index.d.ts +123 -0
  192. package/dist/server/index.js +8 -0
  193. package/dist/sha256-BO0swRXL.js +150 -0
  194. package/dist/sink-Bd9PSyfq.js +141 -0
  195. package/dist/sink-Cfr9-7YC.d.ts +104 -0
  196. package/dist/skills-C_lpV4Jx.js +50 -0
  197. package/dist/skills-Dx3QLnMY.js +110 -0
  198. package/dist/snapshot-DE8dE_hz.js +1051 -0
  199. package/dist/solid/index.d.ts +63 -0
  200. package/dist/solid/index.js +197 -0
  201. package/dist/source-DoBBYJi2.js +6 -0
  202. package/dist/sources-DOpW5Ila.d.ts +347 -0
  203. package/dist/sources-DVHM3Mwq.js +284 -0
  204. package/dist/sql-Bll1d7y0.js +22 -0
  205. package/dist/src-CTNT-PBW.js +430 -0
  206. package/dist/ssf/index.d.ts +3 -0
  207. package/dist/ssf/index.js +622 -0
  208. package/dist/store-Bq90395i.d.ts +27 -0
  209. package/dist/store-C6hFW6ta.js +508 -0
  210. package/dist/stores-CkG2HrSg.js +140 -0
  211. package/dist/stores-CvOnu_2T.d.ts +41 -0
  212. package/dist/stream-BhMfZOwr.d.ts +12 -0
  213. package/dist/stream-Q_xlB6Ko.js +52 -0
  214. package/dist/style-CTm1AXDT.js +22 -0
  215. package/dist/subject-C6dLxqHU.js +24 -0
  216. package/dist/subject-settings-xt7V845g.js +82 -0
  217. package/dist/supabase/index.d.ts +67 -0
  218. package/dist/supabase/index.js +4 -0
  219. package/dist/supabase/middleware.d.ts +77 -0
  220. package/dist/supabase/middleware.js +68 -0
  221. package/dist/supabase-D8UlBA7Z.js +83 -0
  222. package/dist/supabase-DTxGtYdi.js +796 -0
  223. package/dist/supabase-hook-DW-xP4hU.js +2 -0
  224. package/dist/supabase-hook-D_OqGb_V.js +1528 -0
  225. package/dist/svelte/Protected.svelte +34 -0
  226. package/dist/svelte/index.d.ts +10 -0
  227. package/dist/svelte/index.js +53 -0
  228. package/dist/svelte/runtime.d.ts +6 -0
  229. package/dist/svelte/runtime.js +3 -0
  230. package/dist/svelte/source.d.ts +10 -0
  231. package/dist/svelte/source.js +8 -0
  232. package/dist/terminal/index.d.ts +171 -0
  233. package/dist/terminal/index.js +821 -0
  234. package/dist/testing/index.d.ts +656 -0
  235. package/dist/testing/index.js +3208 -0
  236. package/dist/testing/saas/index.d.ts +194 -0
  237. package/dist/testing/saas/index.js +3 -0
  238. package/dist/testing/saas/permissions.d.ts +2 -0
  239. package/dist/testing/saas/permissions.js +2 -0
  240. package/dist/text-diff-BdkkYSbi.js +15 -0
  241. package/dist/thenable-okc2xDsj.js +10 -0
  242. package/dist/timeout-D51niuQP.js +8 -0
  243. package/dist/trpc/index.d.ts +80 -0
  244. package/dist/trpc/index.js +205 -0
  245. package/dist/types-B8w5PFtS.d.ts +141 -0
  246. package/dist/types-B9dmiiab.d.ts +66 -0
  247. package/dist/types-BBK-exvJ.d.ts +177 -0
  248. package/dist/types-BvBk1wgn.d.ts +118 -0
  249. package/dist/types-CxquUByI.d.ts +9 -0
  250. package/dist/types-D6Z6-rKJ.d.ts +312 -0
  251. package/dist/types-DbWdEfcm.d.ts +87 -0
  252. package/dist/types-bva3i4Gf.d.ts +98 -0
  253. package/dist/types-i4BAOvdb.d.ts +97 -0
  254. package/dist/unplugin/index.d.ts +7 -0
  255. package/dist/unplugin/index.js +30 -0
  256. package/dist/usage-1ozMubLY.js +42 -0
  257. package/dist/usage-CUnK6Ou9.js +530 -0
  258. package/dist/usage-PyEor6GA.js +2 -0
  259. package/dist/validity-D0uRmJKJ.js +224 -0
  260. package/dist/verifier-B90agnhl.js +461 -0
  261. package/dist/version-CnSh33Fj.js +16 -0
  262. package/dist/vocabulary-Dk0fs-Jk.d.ts +56 -0
  263. package/dist/vue/index.d.ts +57 -0
  264. package/dist/vue/index.js +203 -0
  265. package/dist/webmcp/index.d.ts +75 -0
  266. package/dist/webmcp/index.js +313 -0
  267. package/package.json +423 -4
  268. package/rulesets/permdock.yaml +29 -0
  269. package/schemas/catalog-v1.json +507 -0
  270. package/schemas/openapi/oas-3.1.json +1298 -0
  271. package/schemas/openapi/oas-3.2.json +1516 -0
  272. package/schemas/openapi/overlay-1.1.json +79 -0
  273. package/schemas/supabase-claims-v1.json +139 -0
  274. package/schemas/supabase-manifest-v1.json +217 -0
  275. package/skills/permdock/SKILL.md +66 -0
  276. package/skills/permdock-agents/SKILL.md +77 -0
  277. package/skills/permdock-agents/references/runtimes.md +211 -0
  278. package/skills/permdock-approvals/SKILL.md +80 -0
  279. package/skills/permdock-approvals/references/stores-and-resume.md +83 -0
  280. package/skills/permdock-audit/SKILL.md +67 -0
  281. package/skills/permdock-credentials/SKILL.md +55 -0
  282. package/skills/permdock-credentials/references/tokens-keys-links.md +127 -0
  283. package/skills/permdock-data/SKILL.md +57 -0
  284. package/skills/permdock-data/references/queries-and-rls.md +54 -0
  285. package/skills/permdock-data/references/relationships.md +65 -0
  286. package/skills/permdock-tenancy/SKILL.md +77 -0
  287. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +83 -0
  288. package/skills/permdock-wire/SKILL.md +116 -0
  289. package/skills/permdock-wire/references/adapters.md +480 -0
  290. package/skills/permdock-wire/references/policy-options.md +43 -0
@@ -0,0 +1,1445 @@
1
+ import { n as isReadonlyArray, t as compact } from "./compact-y6vfu2_f.js";
2
+ import { n as freezeDeep } from "./freeze-y1_jQL4v.js";
3
+ import { n as sha256 } from "./sha256-BO0swRXL.js";
4
+ import { a as memorySink, i as membershipEvent } from "./sink-Bd9PSyfq.js";
5
+ //#region src/scim/auth.ts
6
+ function hexToBytes(hex) {
7
+ if (hex.length % 2 !== 0) return;
8
+ const out = new Uint8Array(hex.length / 2);
9
+ for (let index = 0; index < out.length; index += 1) {
10
+ const byte = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
11
+ if (Number.isNaN(byte)) return;
12
+ out[index] = byte;
13
+ }
14
+ return out;
15
+ }
16
+ function timingSafeEqual(left, right) {
17
+ if (left.length !== right.length) return false;
18
+ let diff = 0;
19
+ for (let index = 0; index < left.length; index += 1) diff |= (left[index] ?? 0) ^ (right[index] ?? 0);
20
+ return diff === 0;
21
+ }
22
+ function bearerToken(request) {
23
+ const header = request.headers.get("authorization");
24
+ if (header === null) return;
25
+ return /^Bearer\s+(\S+)$/iu.exec(header)?.[1];
26
+ }
27
+ async function matchStaticToken(tenant, token, options) {
28
+ const expected = hexToBytes(await options.lookup(tenant) ?? "");
29
+ const actual = sha256(token);
30
+ if (expected === void 0) {
31
+ timingSafeEqual(actual, actual);
32
+ return false;
33
+ }
34
+ return timingSafeEqual(actual, expected);
35
+ }
36
+ function tenantClaim(claims) {
37
+ if (typeof claims["tenant"] === "string" && claims["tenant"] !== "") return claims["tenant"];
38
+ if (typeof claims["tid"] === "string" && claims["tid"] !== "") return claims["tid"];
39
+ }
40
+ async function authenticateScim(input) {
41
+ const bearer = bearerToken(input.request);
42
+ if (bearer === void 0) return {
43
+ ok: false,
44
+ status: 401
45
+ };
46
+ if (input.token !== void 0) {
47
+ if (await matchStaticToken(input.tenant, bearer, input.token)) return {
48
+ ok: true,
49
+ credential: { kind: "token" }
50
+ };
51
+ }
52
+ if (input.verifier === void 0) return {
53
+ ok: false,
54
+ status: 401
55
+ };
56
+ const verified = await input.verifier.verify(bearer, compact({ audience: input.audience }));
57
+ if (!verified.ok) return {
58
+ ok: false,
59
+ status: 401
60
+ };
61
+ if (tenantClaim(verified.claims) !== input.tenant) return {
62
+ ok: false,
63
+ status: 403
64
+ };
65
+ return {
66
+ ok: true,
67
+ credential: compact({
68
+ kind: "jwt",
69
+ iss: typeof verified.claims.iss === "string" ? verified.claims.iss : void 0
70
+ })
71
+ };
72
+ }
73
+ function sha256Hex(value) {
74
+ const bytes = sha256(value);
75
+ let hex = "";
76
+ for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
77
+ return hex;
78
+ }
79
+ //#endregion
80
+ //#region src/scim/types.ts
81
+ const SCIM_CONTENT_TYPE = "application/scim+json";
82
+ const USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
83
+ const GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group";
84
+ const LIST_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:ListResponse";
85
+ const ERROR_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:Error";
86
+ const PATCH_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:PatchOp";
87
+ const ROLES_EXTENSION = "urn:permdock:scim:schemas:extension:roles:1.0";
88
+ var DirectoryUniquenessError = class extends Error {
89
+ name = "DirectoryUniquenessError";
90
+ code = "uniqueness";
91
+ constructor(message = "uniqueness") {
92
+ super(message);
93
+ }
94
+ };
95
+ var DirectoryNotFoundError = class extends Error {
96
+ name = "DirectoryNotFoundError";
97
+ code = "not-found";
98
+ constructor(message = "not found") {
99
+ super(message);
100
+ }
101
+ };
102
+ function isDirectoryUniquenessError(error) {
103
+ return error instanceof DirectoryUniquenessError;
104
+ }
105
+ function isDirectoryNotFoundError(error) {
106
+ return error instanceof DirectoryNotFoundError;
107
+ }
108
+ //#endregion
109
+ //#region src/scim/discovery.ts
110
+ const SCHEMA_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Schema";
111
+ function serviceProviderConfig() {
112
+ return {
113
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
114
+ patch: { supported: true },
115
+ bulk: {
116
+ supported: false,
117
+ maxOperations: 0,
118
+ maxPayloadSize: 0
119
+ },
120
+ filter: {
121
+ supported: true,
122
+ maxResults: 200
123
+ },
124
+ changePassword: { supported: false },
125
+ sort: { supported: false },
126
+ etag: { supported: false },
127
+ pagination: {
128
+ cursor: true,
129
+ index: true
130
+ },
131
+ authenticationSchemes: [{
132
+ type: "oauthbearertoken",
133
+ name: "OAuth Bearer Token",
134
+ description: "Static bearer or RFC 7523 JWT bearer bound to the tenant.",
135
+ specUri: "https://www.rfc-editor.org/rfc/rfc6750.html",
136
+ primary: true
137
+ }]
138
+ };
139
+ }
140
+ function resourceTypes() {
141
+ return [{
142
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"],
143
+ id: "User",
144
+ name: "User",
145
+ endpoint: "/Users",
146
+ schema: USER_SCHEMA,
147
+ schemaExtensions: []
148
+ }, {
149
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"],
150
+ id: "Group",
151
+ name: "Group",
152
+ endpoint: "/Groups",
153
+ schema: GROUP_SCHEMA,
154
+ schemaExtensions: [{
155
+ schema: ROLES_EXTENSION,
156
+ required: false
157
+ }]
158
+ }];
159
+ }
160
+ function attribute(name, type, options = {}) {
161
+ return {
162
+ name,
163
+ type,
164
+ multiValued: options.multiValued ?? false,
165
+ required: options.required ?? false,
166
+ ...options.uniqueness === void 0 ? {} : { uniqueness: options.uniqueness },
167
+ ...options.subAttributes === void 0 ? {} : { subAttributes: options.subAttributes }
168
+ };
169
+ }
170
+ function schemas() {
171
+ return [
172
+ {
173
+ schemas: [SCHEMA_SCHEMA],
174
+ id: USER_SCHEMA,
175
+ name: "User",
176
+ attributes: [
177
+ attribute("userName", "string", {
178
+ required: true,
179
+ uniqueness: "server"
180
+ }),
181
+ attribute("externalId", "string", { uniqueness: "server" }),
182
+ attribute("active", "boolean"),
183
+ attribute("emails", "complex", {
184
+ multiValued: true,
185
+ subAttributes: [
186
+ attribute("value", "string"),
187
+ attribute("primary", "boolean"),
188
+ attribute("type", "string")
189
+ ]
190
+ })
191
+ ]
192
+ },
193
+ {
194
+ schemas: [SCHEMA_SCHEMA],
195
+ id: GROUP_SCHEMA,
196
+ name: "Group",
197
+ attributes: [
198
+ attribute("displayName", "string", { required: true }),
199
+ attribute("externalId", "string", { uniqueness: "server" }),
200
+ attribute("members", "complex", {
201
+ multiValued: true,
202
+ subAttributes: [attribute("value", "string")]
203
+ })
204
+ ]
205
+ },
206
+ {
207
+ schemas: [SCHEMA_SCHEMA],
208
+ id: ROLES_EXTENSION,
209
+ name: "PermDockRoles",
210
+ attributes: [attribute("roles", "string", { multiValued: true })]
211
+ }
212
+ ];
213
+ }
214
+ //#endregion
215
+ //#region src/scim/filter.ts
216
+ const COMPARE = /* @__PURE__ */ new Set([
217
+ "eq",
218
+ "ne",
219
+ "co",
220
+ "sw"
221
+ ]);
222
+ function skipSpace(input, index) {
223
+ let cursor = index;
224
+ while (cursor < input.length && input[cursor] === " ") cursor += 1;
225
+ return cursor;
226
+ }
227
+ function readIdent(input, index) {
228
+ const start = skipSpace(input, index);
229
+ if (!/[A-Za-z]/u.test(input[start] ?? "")) return;
230
+ let cursor = start;
231
+ while (cursor < input.length && /[A-Za-z0-9._]/u.test(input[cursor] ?? "")) cursor += 1;
232
+ return {
233
+ value: input.slice(start, cursor),
234
+ next: cursor
235
+ };
236
+ }
237
+ function readString(input, index) {
238
+ const start = skipSpace(input, index);
239
+ if (input[start] !== "\"") return;
240
+ let cursor = start + 1;
241
+ let value = "";
242
+ while (cursor < input.length) {
243
+ const char = input[cursor];
244
+ if (char === "\\") {
245
+ value += input[cursor + 1] ?? "";
246
+ cursor += 2;
247
+ continue;
248
+ }
249
+ if (char === "\"") return {
250
+ value,
251
+ next: cursor + 1
252
+ };
253
+ value += char;
254
+ cursor += 1;
255
+ }
256
+ }
257
+ function readLiteral(input, index) {
258
+ const quoted = readString(input, index);
259
+ if (quoted !== void 0) return quoted;
260
+ const ident = readIdent(input, index);
261
+ if (ident === void 0) return;
262
+ if (ident.value.toLowerCase() === "true") return {
263
+ value: true,
264
+ next: ident.next
265
+ };
266
+ if (ident.value.toLowerCase() === "false") return {
267
+ value: false,
268
+ next: ident.next
269
+ };
270
+ return {
271
+ value: ident.value,
272
+ next: ident.next
273
+ };
274
+ }
275
+ function parsePrimary(input, index) {
276
+ const start = skipSpace(input, index);
277
+ if (input[start] === "(") {
278
+ const inner = parseOr(input, start + 1);
279
+ if (inner === void 0) return;
280
+ const close = skipSpace(input, inner.next);
281
+ if (input[close] !== ")") return;
282
+ return {
283
+ filter: inner.filter,
284
+ next: close + 1
285
+ };
286
+ }
287
+ const attribute = readIdent(input, start);
288
+ if (attribute === void 0) return;
289
+ const opToken = readIdent(input, attribute.next);
290
+ if (opToken === void 0) return;
291
+ const op = opToken.value.toLowerCase();
292
+ if (op === "pr") return {
293
+ filter: {
294
+ op: "pr",
295
+ attribute: attribute.value
296
+ },
297
+ next: opToken.next
298
+ };
299
+ if (!COMPARE.has(op)) return;
300
+ const value = readLiteral(input, opToken.next);
301
+ if (value === void 0) return;
302
+ return {
303
+ filter: {
304
+ op,
305
+ attribute: attribute.value,
306
+ value: value.value
307
+ },
308
+ next: value.next
309
+ };
310
+ }
311
+ function parseAnd(input, index) {
312
+ const first = parsePrimary(input, index);
313
+ if (first === void 0) return;
314
+ const filters = [first.filter];
315
+ let cursor = first.next;
316
+ for (;;) {
317
+ const nextOp = readIdent(input, cursor);
318
+ if (nextOp?.value.toLowerCase() !== "and") break;
319
+ const next = parsePrimary(input, nextOp.next);
320
+ if (next === void 0) return;
321
+ filters.push(next.filter);
322
+ cursor = next.next;
323
+ }
324
+ if (filters.length === 1) return {
325
+ filter: first.filter,
326
+ next: cursor
327
+ };
328
+ return {
329
+ filter: {
330
+ op: "and",
331
+ filters
332
+ },
333
+ next: cursor
334
+ };
335
+ }
336
+ function parseOr(input, index) {
337
+ const first = parseAnd(input, index);
338
+ if (first === void 0) return;
339
+ const filters = [first.filter];
340
+ let cursor = first.next;
341
+ for (;;) {
342
+ const nextOp = readIdent(input, cursor);
343
+ if (nextOp?.value.toLowerCase() !== "or") break;
344
+ const next = parseAnd(input, nextOp.next);
345
+ if (next === void 0) return;
346
+ filters.push(next.filter);
347
+ cursor = next.next;
348
+ }
349
+ if (filters.length === 1) return {
350
+ filter: first.filter,
351
+ next: cursor
352
+ };
353
+ return {
354
+ filter: {
355
+ op: "or",
356
+ filters
357
+ },
358
+ next: cursor
359
+ };
360
+ }
361
+ function parseScimFilter(input) {
362
+ const trimmed = input.trim();
363
+ if (trimmed === "") return;
364
+ const parsed = parseOr(trimmed, 0);
365
+ if (parsed === void 0) return;
366
+ if (skipSpace(trimmed, parsed.next) !== trimmed.length) return;
367
+ return parsed.filter;
368
+ }
369
+ function readAttribute(target, attribute) {
370
+ if (attribute === "members.value") {
371
+ const members = target["members"];
372
+ if (!Array.isArray(members)) return;
373
+ return members.map((member) => {
374
+ if (member !== null && typeof member === "object" && "value" in member) return member.value;
375
+ });
376
+ }
377
+ const parts = attribute.split(".");
378
+ let current = target;
379
+ for (const part of parts) {
380
+ if (current === null || typeof current !== "object") return;
381
+ current = current[part];
382
+ }
383
+ return current;
384
+ }
385
+ function asString(value) {
386
+ if (typeof value === "boolean") return value ? "true" : "false";
387
+ return String(value);
388
+ }
389
+ function matchCompare(actual, op, expected) {
390
+ if (Array.isArray(actual)) return actual.some((item) => matchCompare(item, op, expected));
391
+ if (actual === void 0 || actual === null) return op === "ne";
392
+ if (typeof expected === "boolean" || typeof actual === "boolean") {
393
+ const left = asString(actual).toLowerCase();
394
+ const right = asString(expected).toLowerCase();
395
+ return op === "eq" ? left === right : op === "ne" ? left !== right : false;
396
+ }
397
+ const left = asString(actual);
398
+ const right = asString(expected);
399
+ switch (op) {
400
+ case "eq": return left === right;
401
+ case "ne": return left !== right;
402
+ case "co": return left.includes(right);
403
+ case "sw": return left.startsWith(right);
404
+ default: return op;
405
+ }
406
+ }
407
+ /** RFC 7644 §3.4.2.2: `pr` needs a non-empty value; a multi-valued one needs an item. */
408
+ function present(value) {
409
+ if (Array.isArray(value)) return value.some(present);
410
+ return value !== void 0 && value !== null && value !== "";
411
+ }
412
+ function matchFilter(target, filter) {
413
+ if (filter === void 0) return true;
414
+ const record = target;
415
+ switch (filter.op) {
416
+ case "and": return filter.filters.every((item) => matchFilter(target, item));
417
+ case "or": return filter.filters.some((item) => matchFilter(target, item));
418
+ case "pr": return present(readAttribute(record, filter.attribute));
419
+ case "eq":
420
+ case "ne":
421
+ case "co":
422
+ case "sw": return matchCompare(readAttribute(record, filter.attribute), filter.op, filter.value);
423
+ default: return filter;
424
+ }
425
+ }
426
+ const SUPPORTED_FILTER_ATTRIBUTES = /* @__PURE__ */ new Set([
427
+ "id",
428
+ "userName",
429
+ "externalId",
430
+ "active",
431
+ "displayName",
432
+ "members.value"
433
+ ]);
434
+ function filterSupported(filter) {
435
+ switch (filter.op) {
436
+ case "and":
437
+ case "or": return filter.filters.every(filterSupported);
438
+ case "pr":
439
+ case "eq":
440
+ case "ne":
441
+ case "co":
442
+ case "sw": return SUPPORTED_FILTER_ATTRIBUTES.has(filter.attribute);
443
+ default: return filter;
444
+ }
445
+ }
446
+ //#endregion
447
+ //#region src/scim/body.ts
448
+ function isRecord$1(value) {
449
+ return value !== null && typeof value === "object" && !Array.isArray(value);
450
+ }
451
+ async function readJson(request) {
452
+ const text = await request.text();
453
+ if (text === "") return;
454
+ return JSON.parse(text);
455
+ }
456
+ function schemasOk(body, required) {
457
+ const listed = body["schemas"];
458
+ if (!Array.isArray(listed)) return false;
459
+ return listed.includes(required) || listed.includes("urn:ietf:params:scim:api:messages:2.0:PatchOp");
460
+ }
461
+ function readRoles(body) {
462
+ const extension = body[ROLES_EXTENSION];
463
+ if (!isRecord$1(extension) || !Array.isArray(extension["roles"])) return;
464
+ return extension["roles"].filter((item) => typeof item === "string");
465
+ }
466
+ function userFromBody(body, id) {
467
+ if (typeof body["userName"] !== "string" || body["userName"] === "") return;
468
+ const emails = Array.isArray(body["emails"]) ? body["emails"].flatMap((item) => {
469
+ if (!isRecord$1(item) || typeof item["value"] !== "string") return [];
470
+ return [compact({
471
+ value: item["value"],
472
+ primary: typeof item["primary"] === "boolean" ? item["primary"] : void 0,
473
+ type: typeof item["type"] === "string" ? item["type"] : void 0
474
+ })];
475
+ }) : void 0;
476
+ const active = typeof body["active"] === "boolean" ? body["active"] : typeof body["active"] === "string" ? body["active"].toLowerCase() !== "false" : true;
477
+ return compact({
478
+ id,
479
+ userName: body["userName"],
480
+ externalId: typeof body["externalId"] === "string" ? body["externalId"] : void 0,
481
+ active,
482
+ emails,
483
+ meta: {
484
+ created: "",
485
+ lastModified: ""
486
+ }
487
+ });
488
+ }
489
+ function groupFromBody(body, id, fallbackRoles) {
490
+ if (typeof body["displayName"] !== "string" || body["displayName"] === "") return;
491
+ const members = Array.isArray(body["members"]) ? body["members"].flatMap((item) => {
492
+ if (!isRecord$1(item) || typeof item["value"] !== "string") return [];
493
+ return [{ value: item["value"] }];
494
+ }) : [];
495
+ return compact({
496
+ id,
497
+ displayName: body["displayName"],
498
+ externalId: typeof body["externalId"] === "string" ? body["externalId"] : void 0,
499
+ members,
500
+ roles: readRoles(body) ?? fallbackRoles,
501
+ meta: {
502
+ created: "",
503
+ lastModified: ""
504
+ }
505
+ });
506
+ }
507
+ //#endregion
508
+ //#region src/scim/emit.ts
509
+ function reportUnknownRoles(roles, options) {
510
+ if (roles === void 0 || options.assignable === void 0) return;
511
+ const allowed = new Set(options.assignable);
512
+ for (const name of roles) if (!allowed.has(name)) options.onUnknownRole?.(name);
513
+ }
514
+ function membershipEventsForGroup(input) {
515
+ const previousIds = new Set(input.previous?.members.map((member) => member.value) ?? []);
516
+ const nextIds = new Set(input.next?.members.map((member) => member.value) ?? []);
517
+ const roles = input.roles;
518
+ const events = [];
519
+ for (const id of nextIds) {
520
+ if (previousIds.has(id)) continue;
521
+ events.push(membershipEvent({
522
+ source: "scim",
523
+ operation: "added",
524
+ principal: { id },
525
+ scope: "tenant",
526
+ id: input.tenant,
527
+ via: `group:${input.groupId}`,
528
+ roles: {
529
+ added: roles,
530
+ removed: []
531
+ }
532
+ }));
533
+ }
534
+ for (const id of previousIds) {
535
+ if (nextIds.has(id)) continue;
536
+ events.push(membershipEvent({
537
+ source: "scim",
538
+ operation: "removed",
539
+ principal: { id },
540
+ scope: "tenant",
541
+ id: input.tenant,
542
+ via: `group:${input.groupId}`,
543
+ roles: {
544
+ added: [],
545
+ removed: roles
546
+ }
547
+ }));
548
+ }
549
+ return events;
550
+ }
551
+ async function emitDirectory(options, event, userIds, extra = [], known = []) {
552
+ const kind = event.resource.type === "User" && event.active === false ? "session-revoked" : "changed";
553
+ const sink = options.sink ?? memorySink();
554
+ try {
555
+ await sink.write([event, ...extra]);
556
+ } catch {}
557
+ try {
558
+ await options.onChange?.({
559
+ tenant: event.tenant,
560
+ userIds,
561
+ kind
562
+ });
563
+ } catch {}
564
+ await publish(options, kind, event.tenant, userIds, known);
565
+ }
566
+ /** Every identifier a principal may carry for these users, deduplicated. */
567
+ async function principalIds(options, tenant, userIds, known) {
568
+ const ids = /* @__PURE__ */ new Set();
569
+ const users = await Promise.all(userIds.map(async (id) => {
570
+ const hit = known.find((user) => user.id === id);
571
+ if (hit !== void 0) return hit;
572
+ try {
573
+ return await options.store.getUser(tenant, id);
574
+ } catch {
575
+ return null;
576
+ }
577
+ }));
578
+ for (const [index, user] of users.entries()) {
579
+ ids.add(userIds[index] ?? "");
580
+ if (user !== null) {
581
+ ids.add(user.userName);
582
+ if (user.externalId !== void 0) ids.add(user.externalId);
583
+ }
584
+ }
585
+ ids.delete("");
586
+ return [...ids];
587
+ }
588
+ async function publish(options, kind, tenant, userIds, known) {
589
+ const feed = options.revocations;
590
+ if (feed === void 0 || userIds.length === 0) return;
591
+ try {
592
+ for (const principal of await principalIds(options, tenant, userIds, known)) await feed.revoke({
593
+ principal,
594
+ tenant,
595
+ kind
596
+ });
597
+ } catch {}
598
+ }
599
+ function directoryEvent(input) {
600
+ return compact({
601
+ type: "directory",
602
+ at: (/* @__PURE__ */ new Date()).toISOString(),
603
+ source: "scim",
604
+ operation: input.operation,
605
+ tenant: input.tenant,
606
+ resource: {
607
+ type: input.type,
608
+ id: input.id
609
+ },
610
+ credential: input.credential,
611
+ active: input.active
612
+ });
613
+ }
614
+ //#endregion
615
+ //#region src/scim/group-roles.ts
616
+ function groupRolesFor(groupRoles, id) {
617
+ if (groupRoles === void 0 || typeof id !== "string" || !Object.hasOwn(groupRoles, id)) return;
618
+ return groupRoles[id];
619
+ }
620
+ //#endregion
621
+ //#region src/scim/patch.ts
622
+ const FILTER_PATH = /^(?<attribute>[A-Za-z0-9._]+)\[(?<field>[A-Za-z0-9._]+)\s+eq\s+"(?<value>[^"]+)"\](?<rest>\.[A-Za-z0-9._]+)?$/u;
623
+ function isRecord(value) {
624
+ return value !== null && typeof value === "object" && !Array.isArray(value);
625
+ }
626
+ function coerceActive(value) {
627
+ if (typeof value === "boolean") return value;
628
+ if (typeof value === "string") {
629
+ const lower = value.toLowerCase();
630
+ if (lower === "true") return true;
631
+ if (lower === "false") return false;
632
+ }
633
+ }
634
+ function rolesPath(path) {
635
+ return path === "roles" || path === `urn:permdock:scim:schemas:extension:roles:1.0:roles` || path === `urn:permdock:scim:schemas:extension:roles:1.0.roles`;
636
+ }
637
+ function opName(value) {
638
+ if (typeof value !== "string") return;
639
+ const lower = value.toLowerCase();
640
+ if (lower === "add" || lower === "replace" || lower === "remove") return lower;
641
+ }
642
+ function normalizePatchOps(ops) {
643
+ const next = [];
644
+ for (const raw of ops) {
645
+ if (!isRecord(raw)) return;
646
+ const op = opName(raw["op"]);
647
+ if (op === void 0) return;
648
+ const path = typeof raw["path"] === "string" ? raw["path"] : void 0;
649
+ if (path === void 0) {
650
+ if (!isRecord(raw["value"])) {
651
+ next.push({
652
+ op,
653
+ value: raw["value"]
654
+ });
655
+ continue;
656
+ }
657
+ for (const [key, value] of Object.entries(raw["value"])) {
658
+ if (key === "urn:permdock:scim:schemas:extension:roles:1.0" && isRecord(value)) {
659
+ next.push({
660
+ op,
661
+ path: "roles",
662
+ value: value["roles"]
663
+ });
664
+ continue;
665
+ }
666
+ if (key === "active") {
667
+ next.push({
668
+ op,
669
+ path: "active",
670
+ value: coerceActive(value) ?? value
671
+ });
672
+ continue;
673
+ }
674
+ next.push({
675
+ op,
676
+ path: key,
677
+ value
678
+ });
679
+ }
680
+ continue;
681
+ }
682
+ if (rolesPath(path)) {
683
+ const roles = isRecord(raw["value"]) ? raw["value"]["roles"] : raw["value"];
684
+ next.push({
685
+ op,
686
+ path: "roles",
687
+ value: roles
688
+ });
689
+ continue;
690
+ }
691
+ const filtered = FILTER_PATH.exec(path);
692
+ if (filtered?.groups !== void 0) {
693
+ const attribute = filtered.groups["attribute"];
694
+ const field = filtered.groups["field"];
695
+ const value = filtered.groups["value"];
696
+ if (attribute === void 0 || field === void 0 || value === void 0) return;
697
+ if (op === "remove") {
698
+ next.push({
699
+ op,
700
+ path: attribute,
701
+ value: [{ [field]: value }]
702
+ });
703
+ continue;
704
+ }
705
+ next.push({
706
+ op,
707
+ path: attribute,
708
+ value: raw["value"] ?? [{ [field]: value }]
709
+ });
710
+ continue;
711
+ }
712
+ if (path === "active") {
713
+ next.push({
714
+ op,
715
+ path,
716
+ value: coerceActive(raw["value"]) ?? raw["value"]
717
+ });
718
+ continue;
719
+ }
720
+ next.push({
721
+ op,
722
+ path,
723
+ value: raw["value"]
724
+ });
725
+ }
726
+ return next;
727
+ }
728
+ function readPatchOperations(body) {
729
+ if (!isRecord(body)) return;
730
+ const operations = body["Operations"] ?? body["operations"];
731
+ if (!isReadonlyArray(operations)) return;
732
+ return operations;
733
+ }
734
+ //#endregion
735
+ //#region src/scim/render.ts
736
+ function scimResponse(status, body, extra) {
737
+ return new Response(JSON.stringify(body), {
738
+ status,
739
+ headers: {
740
+ "content-type": SCIM_CONTENT_TYPE,
741
+ ...extra
742
+ }
743
+ });
744
+ }
745
+ function scimError(status, scimType, detail) {
746
+ return scimResponse(status, {
747
+ schemas: [ERROR_SCHEMA],
748
+ status: String(status),
749
+ scimType,
750
+ detail
751
+ });
752
+ }
753
+ function unauthorized() {
754
+ return new Response(null, {
755
+ status: 401,
756
+ headers: { "www-authenticate": "Bearer" }
757
+ });
758
+ }
759
+ function forbidden() {
760
+ return new Response(null, {
761
+ status: 403,
762
+ headers: { "www-authenticate": "Bearer" }
763
+ });
764
+ }
765
+ function mapStoreError(error) {
766
+ if (isDirectoryUniquenessError(error)) return scimError(409, "uniqueness", error.message);
767
+ if (isDirectoryNotFoundError(error)) return scimError(404, "invalidValue", "resource not found");
768
+ return scimError(500, "invalidValue", "store failed");
769
+ }
770
+ function listBody(result, render) {
771
+ return compact({
772
+ schemas: [LIST_SCHEMA],
773
+ totalResults: result.totalResults,
774
+ startIndex: result.startIndex,
775
+ itemsPerPage: result.itemsPerPage,
776
+ nextCursor: result.nextCursor,
777
+ Resources: result.Resources.map(render)
778
+ });
779
+ }
780
+ function renderUser(user, location) {
781
+ return compact({
782
+ schemas: [USER_SCHEMA],
783
+ id: user.id,
784
+ externalId: user.externalId,
785
+ userName: user.userName,
786
+ active: user.active,
787
+ emails: user.emails,
788
+ meta: {
789
+ resourceType: "User",
790
+ created: user.meta.created,
791
+ lastModified: user.meta.lastModified,
792
+ location
793
+ }
794
+ });
795
+ }
796
+ function renderGroup(group, location) {
797
+ const extension = group.roles === void 0 ? void 0 : { [ROLES_EXTENSION]: { roles: group.roles } };
798
+ return compact({
799
+ schemas: group.roles === void 0 ? [GROUP_SCHEMA] : [GROUP_SCHEMA, ROLES_EXTENSION],
800
+ id: group.id,
801
+ externalId: group.externalId,
802
+ displayName: group.displayName,
803
+ members: group.members,
804
+ ...extension,
805
+ meta: {
806
+ resourceType: "Group",
807
+ created: group.meta.created,
808
+ lastModified: group.meta.lastModified,
809
+ location
810
+ }
811
+ });
812
+ }
813
+ //#endregion
814
+ //#region src/scim/route.ts
815
+ const RESOURCES = /* @__PURE__ */ new Set([
816
+ "Users",
817
+ "Groups",
818
+ "ServiceProviderConfig",
819
+ "ResourceTypes",
820
+ "Schemas"
821
+ ]);
822
+ function tenantFromPath(request) {
823
+ const parts = new URL(request.url).pathname.split("/").filter(Boolean);
824
+ const index = parts.findIndex((part) => RESOURCES.has(part));
825
+ if (index <= 0) return "";
826
+ const prev = parts[index - 1] ?? "";
827
+ if (prev === "v2" || prev === "scim") return "";
828
+ return prev;
829
+ }
830
+ function isKind(part) {
831
+ return RESOURCES.has(part);
832
+ }
833
+ function parseRoute(url) {
834
+ const parts = url.pathname.split("/").filter(Boolean);
835
+ const kind = parts.find(isKind);
836
+ if (kind === void 0) return;
837
+ const id = parts[parts.indexOf(kind) + 1];
838
+ switch (kind) {
839
+ case "Users": return compact({
840
+ kind: "Users",
841
+ id
842
+ });
843
+ case "Groups": return compact({
844
+ kind: "Groups",
845
+ id
846
+ });
847
+ case "ServiceProviderConfig": return { kind: "ServiceProviderConfig" };
848
+ case "ResourceTypes": return compact({
849
+ kind: "ResourceTypes",
850
+ id
851
+ });
852
+ case "Schemas": return compact({
853
+ kind: "Schemas",
854
+ id
855
+ });
856
+ default: return kind;
857
+ }
858
+ }
859
+ function prefixOf(request) {
860
+ const url = new URL(request.url);
861
+ const parts = url.pathname.split("/").filter(Boolean);
862
+ const index = parts.findIndex((part) => RESOURCES.has(part));
863
+ return {
864
+ origin: url.origin,
865
+ prefix: index === -1 ? parts : parts.slice(0, index)
866
+ };
867
+ }
868
+ function locationOf(request, kind, id) {
869
+ const { origin, prefix } = prefixOf(request);
870
+ return `${origin}/${(id === void 0 ? [...prefix, kind] : [
871
+ ...prefix,
872
+ kind,
873
+ id
874
+ ]).join("/")}`;
875
+ }
876
+ function pageFrom(url) {
877
+ const startIndex = url.searchParams.get("startIndex");
878
+ const count = url.searchParams.get("count");
879
+ const cursor = url.searchParams.get("cursor");
880
+ return compact({
881
+ startIndex: startIndex === null ? void 0 : Math.trunc(Number(startIndex)),
882
+ count: count === null ? void 0 : Math.trunc(Number(count)),
883
+ cursor: cursor ?? void 0
884
+ });
885
+ }
886
+ //#endregion
887
+ //#region src/scim/handler.ts
888
+ function scimHandler(options) {
889
+ if (options.token === void 0 && options.verifier === void 0) throw new Error("scimHandler requires token or verifier");
890
+ if (options.verifier !== void 0 && options.audience === void 0) throw new Error("scimHandler requires audience with a verifier");
891
+ return async (request) => {
892
+ const url = new URL(request.url);
893
+ const route = parseRoute(url);
894
+ if (route === void 0) return scimError(404, "invalidValue", "unknown route");
895
+ let tenant;
896
+ try {
897
+ tenant = typeof options.tenant === "string" ? options.tenant : await options.tenant(request);
898
+ } catch {
899
+ return forbidden();
900
+ }
901
+ if (tenant === "") return forbidden();
902
+ const auth = await authenticateScim(compact({
903
+ request,
904
+ tenant,
905
+ audience: options.audience,
906
+ token: options.token,
907
+ verifier: options.verifier
908
+ }));
909
+ if (!auth.ok) return auth.status === 403 ? forbidden() : unauthorized();
910
+ try {
911
+ if (route.kind === "ServiceProviderConfig" || route.kind === "ResourceTypes" || route.kind === "Schemas") {
912
+ if (url.searchParams.has("filter")) return scimError(403, "invalidFilter", "discovery endpoints do not filter");
913
+ return discoveryResponse(request, route);
914
+ }
915
+ if (request.method === "GET" && route.id === void 0) {
916
+ const rawFilter = url.searchParams.get("filter");
917
+ const filter = rawFilter === null || rawFilter === "" ? void 0 : parseScimFilter(rawFilter);
918
+ if (rawFilter !== null && rawFilter !== "" && filter === void 0) return scimError(400, "invalidFilter", "unsupported filter");
919
+ if (filter !== void 0 && !filterSupported(filter)) return scimError(400, "invalidFilter", "unsupported filter");
920
+ const page = pageFrom(url);
921
+ if (route.kind === "Users") return scimResponse(200, listBody(await options.store.findUsers(tenant, filter, page), (user) => renderUser(user, locationOf(request, "Users", user.id))));
922
+ return scimResponse(200, listBody(await options.store.findGroups(tenant, filter, page), (group) => renderGroup(group, locationOf(request, "Groups", group.id))));
923
+ }
924
+ if (request.method === "GET" && route.id !== void 0) {
925
+ if (route.kind === "Users") {
926
+ const user = await options.store.getUser(tenant, route.id);
927
+ if (user === null) return scimError(404, "invalidValue", "resource not found");
928
+ return scimResponse(200, renderUser(user, locationOf(request, "Users", user.id)));
929
+ }
930
+ const group = await options.store.getGroup(tenant, route.id);
931
+ if (group === null) return scimError(404, "invalidValue", "resource not found");
932
+ return scimResponse(200, renderGroup(group, locationOf(request, "Groups", group.id)));
933
+ }
934
+ if (request.method === "DELETE" && route.id !== void 0) {
935
+ if (route.kind === "Users") {
936
+ const existing = await options.store.getUser(tenant, route.id);
937
+ await options.store.deleteUser(tenant, route.id);
938
+ await emitDirectory(options, directoryEvent({
939
+ operation: "delete",
940
+ tenant,
941
+ type: "User",
942
+ id: route.id,
943
+ credential: auth.credential,
944
+ active: false
945
+ }), existing === null ? [] : [existing.id], [], existing === null ? [] : [existing]);
946
+ return new Response(null, { status: 204 });
947
+ }
948
+ const existing = await options.store.getGroup(tenant, route.id);
949
+ await options.store.deleteGroup(tenant, route.id);
950
+ await emitDirectory(options, directoryEvent({
951
+ operation: "delete",
952
+ tenant,
953
+ type: "Group",
954
+ id: route.id,
955
+ credential: auth.credential
956
+ }), existing?.members.map((member) => member.value) ?? [], membershipEventsForGroup({
957
+ tenant,
958
+ groupId: route.id,
959
+ roles: existing?.roles ?? groupRolesFor(options.groupRoles, route.id) ?? [],
960
+ previous: existing,
961
+ next: null
962
+ }));
963
+ return new Response(null, { status: 204 });
964
+ }
965
+ const body = await readJson(request);
966
+ if (!isRecord$1(body)) return scimError(400, "invalidSyntax", "malformed body");
967
+ if (request.method === "POST" && route.id === void 0) {
968
+ if (route.kind === "Users") {
969
+ if (!schemasOk(body, "urn:ietf:params:scim:schemas:core:2.0:User")) return scimError(400, "invalidSyntax", "unsupported schema");
970
+ const parsed = userFromBody(body, "");
971
+ if (parsed === void 0) return scimError(400, "invalidValue", "userName is required");
972
+ const stored = await options.store.putUser(tenant, parsed);
973
+ const location = locationOf(request, "Users", stored.id);
974
+ await emitDirectory(options, directoryEvent({
975
+ operation: "create",
976
+ tenant,
977
+ type: "User",
978
+ id: stored.id,
979
+ credential: auth.credential,
980
+ active: stored.active
981
+ }), [stored.id]);
982
+ return scimResponse(201, renderUser(stored, location), { location });
983
+ }
984
+ if (!schemasOk(body, "urn:ietf:params:scim:schemas:core:2.0:Group")) return scimError(400, "invalidSyntax", "unsupported schema");
985
+ const parsed = groupFromBody(body, "", groupRolesFor(options.groupRoles, body["id"]));
986
+ if (parsed === void 0) return scimError(400, "invalidValue", "displayName is required");
987
+ reportUnknownRoles(parsed.roles, options);
988
+ const stored = await options.store.putGroup(tenant, parsed);
989
+ const location = locationOf(request, "Groups", stored.id);
990
+ await emitDirectory(options, directoryEvent({
991
+ operation: "create",
992
+ tenant,
993
+ type: "Group",
994
+ id: stored.id,
995
+ credential: auth.credential
996
+ }), stored.members.map((member) => member.value), membershipEventsForGroup({
997
+ tenant,
998
+ groupId: stored.id,
999
+ roles: stored.roles ?? groupRolesFor(options.groupRoles, stored.id) ?? [],
1000
+ previous: null,
1001
+ next: stored
1002
+ }));
1003
+ return scimResponse(201, renderGroup(stored, location), { location });
1004
+ }
1005
+ if (request.method === "PUT" && route.id !== void 0) {
1006
+ if (route.kind === "Users") {
1007
+ if (!schemasOk(body, "urn:ietf:params:scim:schemas:core:2.0:User")) return scimError(400, "invalidSyntax", "unsupported schema");
1008
+ if (await options.store.getUser(tenant, route.id) === null) return scimError(404, "invalidValue", "resource not found");
1009
+ const parsed = userFromBody(body, route.id);
1010
+ if (parsed === void 0) return scimError(400, "invalidValue", "userName is required");
1011
+ const stored = await options.store.putUser(tenant, parsed);
1012
+ const location = locationOf(request, "Users", stored.id);
1013
+ await emitDirectory(options, directoryEvent({
1014
+ operation: "replace",
1015
+ tenant,
1016
+ type: "User",
1017
+ id: stored.id,
1018
+ credential: auth.credential,
1019
+ active: stored.active
1020
+ }), [stored.id]);
1021
+ return scimResponse(200, renderUser(stored, location), { location });
1022
+ }
1023
+ if (!schemasOk(body, "urn:ietf:params:scim:schemas:core:2.0:Group")) return scimError(400, "invalidSyntax", "unsupported schema");
1024
+ const existing = await options.store.getGroup(tenant, route.id);
1025
+ if (existing === null) return scimError(404, "invalidValue", "resource not found");
1026
+ const parsed = groupFromBody(body, route.id, groupRolesFor(options.groupRoles, route.id));
1027
+ if (parsed === void 0) return scimError(400, "invalidValue", "displayName is required");
1028
+ reportUnknownRoles(parsed.roles, options);
1029
+ const stored = await options.store.putGroup(tenant, parsed);
1030
+ const location = locationOf(request, "Groups", stored.id);
1031
+ await emitDirectory(options, directoryEvent({
1032
+ operation: "replace",
1033
+ tenant,
1034
+ type: "Group",
1035
+ id: stored.id,
1036
+ credential: auth.credential
1037
+ }), stored.members.map((member) => member.value), membershipEventsForGroup({
1038
+ tenant,
1039
+ groupId: stored.id,
1040
+ roles: stored.roles ?? existing.roles ?? [],
1041
+ previous: existing,
1042
+ next: stored
1043
+ }));
1044
+ return scimResponse(200, renderGroup(stored, location), { location });
1045
+ }
1046
+ if (request.method === "PATCH" && route.id !== void 0) {
1047
+ const operations = readPatchOperations(body);
1048
+ if (operations === void 0) return scimError(400, "invalidSyntax", "Operations required");
1049
+ const normalized = normalizePatchOps(operations);
1050
+ if (normalized === void 0) return scimError(400, "invalidSyntax", "invalid patch");
1051
+ if (route.kind === "Users") {
1052
+ const stored = await options.store.patchUser(tenant, route.id, normalized);
1053
+ const location = locationOf(request, "Users", stored.id);
1054
+ await emitDirectory(options, directoryEvent({
1055
+ operation: "patch",
1056
+ tenant,
1057
+ type: "User",
1058
+ id: stored.id,
1059
+ credential: auth.credential,
1060
+ active: stored.active
1061
+ }), [stored.id]);
1062
+ return scimResponse(200, renderUser(stored, location), { location });
1063
+ }
1064
+ const existing = await options.store.getGroup(tenant, route.id);
1065
+ const stored = await options.store.patchGroup(tenant, route.id, normalized);
1066
+ reportUnknownRoles(stored.roles, options);
1067
+ const location = locationOf(request, "Groups", stored.id);
1068
+ await emitDirectory(options, directoryEvent({
1069
+ operation: "patch",
1070
+ tenant,
1071
+ type: "Group",
1072
+ id: stored.id,
1073
+ credential: auth.credential
1074
+ }), stored.members.map((member) => member.value), membershipEventsForGroup({
1075
+ tenant,
1076
+ groupId: stored.id,
1077
+ roles: stored.roles ?? existing?.roles ?? [],
1078
+ previous: existing,
1079
+ next: stored
1080
+ }));
1081
+ return scimResponse(200, renderGroup(stored, location), { location });
1082
+ }
1083
+ return scimError(405, "invalidValue", "method not allowed");
1084
+ } catch (error) {
1085
+ if (error instanceof SyntaxError) return scimError(400, "invalidSyntax", "malformed body");
1086
+ return mapStoreError(error);
1087
+ }
1088
+ };
1089
+ }
1090
+ function discoveryResponse(request, route) {
1091
+ if (route.kind === "ServiceProviderConfig") return scimResponse(200, {
1092
+ ...serviceProviderConfig(),
1093
+ meta: {
1094
+ resourceType: "ServiceProviderConfig",
1095
+ location: locationOf(request, route.kind)
1096
+ }
1097
+ });
1098
+ const resourceType = route.kind === "Schemas" ? "Schema" : "ResourceType";
1099
+ const all = (route.kind === "Schemas" ? schemas() : resourceTypes()).map((item) => Object.assign({}, item, { meta: {
1100
+ resourceType,
1101
+ location: locationOf(request, route.kind, String(item["id"]))
1102
+ } }));
1103
+ if (route.id !== void 0) {
1104
+ const one = all.find((item) => item["id"] === route.id);
1105
+ return one === void 0 ? scimError(404, "invalidValue", `${resourceType} not found`) : scimResponse(200, one);
1106
+ }
1107
+ return scimResponse(200, listBody({
1108
+ Resources: all,
1109
+ totalResults: all.length,
1110
+ startIndex: 1,
1111
+ itemsPerPage: all.length
1112
+ }, (item) => item));
1113
+ }
1114
+ //#endregion
1115
+ //#region src/scim/source.ts
1116
+ function allowedRoles(roles, assignable, onUnknownRole) {
1117
+ if (roles === void 0) return [];
1118
+ if (assignable === void 0) return roles;
1119
+ const allowed = new Set(assignable);
1120
+ const kept = [];
1121
+ for (const name of roles) {
1122
+ if (allowed.has(name)) {
1123
+ kept.push(name);
1124
+ continue;
1125
+ }
1126
+ onUnknownRole?.(name);
1127
+ }
1128
+ return kept;
1129
+ }
1130
+ async function lookupUser(store, tenant, attribute, principalId) {
1131
+ return (await store.findUsers(tenant, {
1132
+ op: "eq",
1133
+ attribute,
1134
+ value: principalId
1135
+ }, {
1136
+ startIndex: 1,
1137
+ count: 1
1138
+ })).Resources[0] ?? null;
1139
+ }
1140
+ async function findUser(store, tenant, principalId, match) {
1141
+ const mode = match ?? "either";
1142
+ if (mode === "externalId" || mode === "either") {
1143
+ const user = await lookupUser(store, tenant, "externalId", principalId);
1144
+ if (user !== null) return user;
1145
+ }
1146
+ if (mode === "userName" || mode === "either") return lookupUser(store, tenant, "userName", principalId);
1147
+ return null;
1148
+ }
1149
+ function directoryMembershipSource(store, options = {}) {
1150
+ return { async membershipsFor(principal, query) {
1151
+ const tenant = query.tenant;
1152
+ if (tenant === void 0 || tenant === "") return [];
1153
+ let user;
1154
+ try {
1155
+ user = await findUser(store, tenant, principal.id, options.match);
1156
+ } catch {
1157
+ return [];
1158
+ }
1159
+ if (user === null || !user.active) return [];
1160
+ let groups;
1161
+ try {
1162
+ groups = [...await store.groupsFor(tenant, user.id)];
1163
+ } catch {
1164
+ return [];
1165
+ }
1166
+ const memberships = [];
1167
+ for (const group of groups) {
1168
+ const mapped = group.roles ?? groupRolesFor(options.groupRoles, group.id);
1169
+ memberships.push(compact({
1170
+ tenant,
1171
+ roles: allowedRoles(mapped, options.assignable, options.onUnknownRole),
1172
+ via: `group:${group.id}`,
1173
+ managedBy: "idp"
1174
+ }));
1175
+ }
1176
+ return memberships;
1177
+ } };
1178
+ }
1179
+ //#endregion
1180
+ //#region src/scim/store.ts
1181
+ const DEFAULT_COUNT = 100;
1182
+ function now() {
1183
+ return (/* @__PURE__ */ new Date()).toISOString();
1184
+ }
1185
+ function createdAt(at, ...candidates) {
1186
+ return candidates.find((value) => value !== void 0 && value !== "") ?? at;
1187
+ }
1188
+ function randomId(prefix) {
1189
+ const bytes = /* @__PURE__ */ new Uint8Array(8);
1190
+ crypto.getRandomValues(bytes);
1191
+ let hex = "";
1192
+ for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
1193
+ return `${prefix}${hex}`;
1194
+ }
1195
+ function directoryKey(tenant, id) {
1196
+ return `${tenant}\0${id}`;
1197
+ }
1198
+ function asError(error) {
1199
+ return error instanceof Error ? error : new Error(String(error));
1200
+ }
1201
+ function pageOf(items, page) {
1202
+ const count = page.count ?? DEFAULT_COUNT;
1203
+ let start = 0;
1204
+ if (page.cursor !== void 0 && page.cursor !== "") {
1205
+ const decoded = Math.trunc(Number(page.cursor));
1206
+ start = Number.isFinite(decoded) && decoded > 0 ? decoded : 0;
1207
+ } else if (page.startIndex !== void 0 && page.startIndex > 0) start = page.startIndex - 1;
1208
+ const slice = items.slice(start, start + count);
1209
+ const nextIndex = start + slice.length;
1210
+ return compact({
1211
+ Resources: slice,
1212
+ totalResults: items.length,
1213
+ startIndex: start + 1,
1214
+ itemsPerPage: slice.length,
1215
+ nextCursor: nextIndex < items.length ? String(nextIndex) : void 0
1216
+ });
1217
+ }
1218
+ function emailsOf(value) {
1219
+ if (!Array.isArray(value)) return;
1220
+ return value.flatMap((item) => {
1221
+ if (item === null || typeof item !== "object") return [];
1222
+ const record = item;
1223
+ if (typeof record.value !== "string") return [];
1224
+ return [compact({
1225
+ value: record.value,
1226
+ primary: typeof record.primary === "boolean" ? record.primary : void 0,
1227
+ type: typeof record.type === "string" ? record.type : void 0
1228
+ })];
1229
+ });
1230
+ }
1231
+ function membersOf(value) {
1232
+ if (!Array.isArray(value)) return [];
1233
+ return value.flatMap((item) => {
1234
+ if (item === null || typeof item !== "object") return [];
1235
+ const record = item;
1236
+ return typeof record.value === "string" ? [{ value: record.value }] : [];
1237
+ });
1238
+ }
1239
+ function rolesOf(value) {
1240
+ if (!Array.isArray(value)) return;
1241
+ return value.filter((item) => typeof item === "string");
1242
+ }
1243
+ function memberValues(value) {
1244
+ return membersOf(value).map((member) => member.value);
1245
+ }
1246
+ function applyUserOp(user, op) {
1247
+ const path = op.path ?? "";
1248
+ if (path === "active" && typeof op.value === "boolean") return {
1249
+ ...user,
1250
+ active: op.value
1251
+ };
1252
+ if (path === "userName" && typeof op.value === "string") return {
1253
+ ...user,
1254
+ userName: op.value
1255
+ };
1256
+ if (path === "externalId") return compact({
1257
+ ...user,
1258
+ externalId: typeof op.value === "string" ? op.value : void 0
1259
+ });
1260
+ if (path === "emails") return compact({
1261
+ ...user,
1262
+ emails: emailsOf(op.value)
1263
+ });
1264
+ return user;
1265
+ }
1266
+ function applyGroupOp(group, op) {
1267
+ const path = op.path ?? "";
1268
+ if (path === "displayName" && typeof op.value === "string") return {
1269
+ ...group,
1270
+ displayName: op.value
1271
+ };
1272
+ if (path === "externalId") return compact({
1273
+ ...group,
1274
+ externalId: typeof op.value === "string" ? op.value : void 0
1275
+ });
1276
+ if (path === "roles") return compact({
1277
+ ...group,
1278
+ roles: rolesOf(op.value)
1279
+ });
1280
+ if (path !== "members") return group;
1281
+ const incoming = memberValues(op.value);
1282
+ const current = group.members.map((member) => member.value);
1283
+ if (op.op === "replace") return {
1284
+ ...group,
1285
+ members: incoming.map((value) => ({ value }))
1286
+ };
1287
+ if (op.op === "add") {
1288
+ const next = [...current];
1289
+ for (const value of incoming) if (!next.includes(value)) next.push(value);
1290
+ return {
1291
+ ...group,
1292
+ members: next.map((value) => ({ value }))
1293
+ };
1294
+ }
1295
+ return {
1296
+ ...group,
1297
+ members: current.filter((value) => !incoming.includes(value)).map((value) => ({ value }))
1298
+ };
1299
+ }
1300
+ function stampUser(user, created) {
1301
+ const at = now();
1302
+ return freezeDeep({
1303
+ ...user,
1304
+ meta: compact({
1305
+ created: createdAt(at, created, user.meta.created),
1306
+ lastModified: at,
1307
+ resourceType: "User",
1308
+ location: user.meta.location
1309
+ })
1310
+ });
1311
+ }
1312
+ function stampGroup(group, created) {
1313
+ const at = now();
1314
+ return freezeDeep({
1315
+ ...group,
1316
+ meta: compact({
1317
+ created: createdAt(at, created, group.meta.created),
1318
+ lastModified: at,
1319
+ resourceType: "Group",
1320
+ location: group.meta.location
1321
+ })
1322
+ });
1323
+ }
1324
+ function memoryDirectoryStore() {
1325
+ const users = /* @__PURE__ */ new Map();
1326
+ const groups = /* @__PURE__ */ new Map();
1327
+ function usersIn(tenant) {
1328
+ const found = [];
1329
+ for (const [key, user] of users) if (key.startsWith(`${tenant}\0`)) found.push(user);
1330
+ return found;
1331
+ }
1332
+ function groupsIn(tenant) {
1333
+ const found = [];
1334
+ for (const [key, group] of groups) if (key.startsWith(`${tenant}\0`)) found.push(group);
1335
+ return found;
1336
+ }
1337
+ function assertUserUnique(tenant, user, ignoreId) {
1338
+ for (const existing of usersIn(tenant)) {
1339
+ if (existing.id === ignoreId) continue;
1340
+ if (existing.userName === user.userName) throw new DirectoryUniquenessError("userName");
1341
+ if (user.externalId !== void 0 && existing.externalId === user.externalId) throw new DirectoryUniquenessError("externalId");
1342
+ }
1343
+ }
1344
+ function assertGroupUnique(tenant, group, ignoreId) {
1345
+ for (const existing of groupsIn(tenant)) {
1346
+ if (existing.id === ignoreId) continue;
1347
+ if (group.externalId !== void 0 && existing.externalId === group.externalId) throw new DirectoryUniquenessError("externalId");
1348
+ }
1349
+ }
1350
+ return {
1351
+ getUser(tenant, id) {
1352
+ return Promise.resolve(users.get(directoryKey(tenant, id)) ?? null);
1353
+ },
1354
+ findUsers(tenant, filter, page) {
1355
+ const matched = usersIn(tenant).filter((user) => matchFilter(user, filter));
1356
+ return Promise.resolve(pageOf(matched, page));
1357
+ },
1358
+ putUser(tenant, user) {
1359
+ try {
1360
+ const id = user.id === "" ? randomId("u_") : user.id;
1361
+ const existing = users.get(directoryKey(tenant, id));
1362
+ const next = stampUser({
1363
+ ...user,
1364
+ id
1365
+ }, existing?.meta.created);
1366
+ assertUserUnique(tenant, next, id);
1367
+ users.set(directoryKey(tenant, id), next);
1368
+ return Promise.resolve(next);
1369
+ } catch (error) {
1370
+ return Promise.reject(asError(error));
1371
+ }
1372
+ },
1373
+ patchUser(tenant, id, ops) {
1374
+ try {
1375
+ const existing = users.get(directoryKey(tenant, id));
1376
+ if (existing === void 0) throw new DirectoryNotFoundError();
1377
+ let next = existing;
1378
+ for (const op of ops) next = applyUserOp(next, op);
1379
+ next = stampUser(next, existing.meta.created);
1380
+ assertUserUnique(tenant, next, id);
1381
+ users.set(directoryKey(tenant, id), next);
1382
+ return Promise.resolve(next);
1383
+ } catch (error) {
1384
+ return Promise.reject(asError(error));
1385
+ }
1386
+ },
1387
+ deleteUser(tenant, id) {
1388
+ if (!users.delete(directoryKey(tenant, id))) return Promise.reject(new DirectoryNotFoundError());
1389
+ for (const group of groupsIn(tenant)) {
1390
+ if (!group.members.some((member) => member.value === id)) continue;
1391
+ groups.set(directoryKey(tenant, group.id), stampGroup({
1392
+ ...group,
1393
+ members: group.members.filter((member) => member.value !== id)
1394
+ }, group.meta.created));
1395
+ }
1396
+ return Promise.resolve();
1397
+ },
1398
+ getGroup(tenant, id) {
1399
+ return Promise.resolve(groups.get(directoryKey(tenant, id)) ?? null);
1400
+ },
1401
+ findGroups(tenant, filter, page) {
1402
+ const matched = groupsIn(tenant).filter((group) => matchFilter(group, filter));
1403
+ return Promise.resolve(pageOf(matched, page));
1404
+ },
1405
+ putGroup(tenant, group) {
1406
+ try {
1407
+ const id = group.id === "" ? randomId("g_") : group.id;
1408
+ const existing = groups.get(directoryKey(tenant, id));
1409
+ const next = stampGroup({
1410
+ ...group,
1411
+ id,
1412
+ members: group.members
1413
+ }, existing?.meta.created);
1414
+ assertGroupUnique(tenant, next, id);
1415
+ groups.set(directoryKey(tenant, id), next);
1416
+ return Promise.resolve(next);
1417
+ } catch (error) {
1418
+ return Promise.reject(asError(error));
1419
+ }
1420
+ },
1421
+ patchGroup(tenant, id, ops) {
1422
+ try {
1423
+ const existing = groups.get(directoryKey(tenant, id));
1424
+ if (existing === void 0) throw new DirectoryNotFoundError();
1425
+ let next = existing;
1426
+ for (const op of ops) next = applyGroupOp(next, op);
1427
+ next = stampGroup(next, existing.meta.created);
1428
+ assertGroupUnique(tenant, next, id);
1429
+ groups.set(directoryKey(tenant, id), next);
1430
+ return Promise.resolve(next);
1431
+ } catch (error) {
1432
+ return Promise.reject(asError(error));
1433
+ }
1434
+ },
1435
+ deleteGroup(tenant, id) {
1436
+ if (!groups.delete(directoryKey(tenant, id))) return Promise.reject(new DirectoryNotFoundError());
1437
+ return Promise.resolve();
1438
+ },
1439
+ groupsFor(tenant, userId) {
1440
+ return Promise.resolve(groupsIn(tenant).filter((group) => group.members.some((member) => member.value === userId)));
1441
+ }
1442
+ };
1443
+ }
1444
+ //#endregion
1445
+ export { sha256Hex as S, PATCH_SCHEMA as _, normalizePatchOps as a, USER_SCHEMA as b, parseScimFilter as c, serviceProviderConfig as d, DirectoryNotFoundError as f, LIST_SCHEMA as g, GROUP_SCHEMA as h, tenantFromPath as i, resourceTypes as l, ERROR_SCHEMA as m, directoryMembershipSource as n, filterSupported as o, DirectoryUniquenessError as p, scimHandler as r, matchFilter as s, memoryDirectoryStore as t, schemas as u, ROLES_EXTENSION as v, authenticateScim as x, SCIM_CONTENT_TYPE as y };