@devxcrew/platform 0.1.2 → 0.1.6

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 (280) hide show
  1. package/README.md +7 -1
  2. package/dist/index.d.ts +6 -4
  3. package/dist/index.js +4 -2
  4. package/dist/modules/identity/audit/audit.controller.d.ts +17 -0
  5. package/dist/modules/identity/audit/audit.controller.js +12 -0
  6. package/dist/modules/identity/audit/audit.migration.d.ts +1 -0
  7. package/dist/modules/identity/audit/audit.migration.js +1 -0
  8. package/dist/modules/identity/audit/audit.provider.d.ts +24 -0
  9. package/dist/modules/identity/audit/audit.provider.js +14 -0
  10. package/dist/modules/identity/audit/audit.repository.d.ts +26 -0
  11. package/dist/modules/identity/audit/audit.repository.js +46 -0
  12. package/dist/modules/identity/audit/audit.routes.d.ts +5 -0
  13. package/dist/modules/identity/audit/audit.routes.js +2 -0
  14. package/dist/modules/identity/audit/audit.schema.d.ts +1 -0
  15. package/dist/modules/identity/audit/audit.schema.js +1 -0
  16. package/dist/modules/identity/audit/audit.seed.d.ts +1 -0
  17. package/dist/modules/identity/audit/audit.seed.js +1 -0
  18. package/dist/modules/identity/audit/audit.service.d.ts +23 -0
  19. package/dist/modules/identity/audit/audit.service.js +46 -0
  20. package/dist/modules/identity/audit/audit.types.d.ts +12 -0
  21. package/dist/modules/identity/audit/audit.types.js +1 -0
  22. package/dist/modules/identity/audit/index.d.ts +4 -0
  23. package/dist/modules/identity/audit/index.js +3 -0
  24. package/dist/modules/identity/{identity.administration-service.d.ts → composition/identity.composition.d.ts} +59 -27
  25. package/dist/modules/identity/composition/identity.composition.js +171 -0
  26. package/dist/modules/identity/composition/identity.seed.d.ts +3 -0
  27. package/dist/modules/identity/composition/identity.seed.js +16 -0
  28. package/dist/modules/identity/identity.controller.d.ts +8 -6
  29. package/dist/modules/identity/identity.controller.js +90 -70
  30. package/dist/modules/identity/identity.migration.d.ts +1 -2
  31. package/dist/modules/identity/identity.migration.js +1 -79
  32. package/dist/modules/identity/identity.provider.d.ts +39 -13
  33. package/dist/modules/identity/identity.provider.js +19 -73
  34. package/dist/modules/identity/identity.public-schemas.d.ts +9 -3
  35. package/dist/modules/identity/identity.public-schemas.js +9 -3
  36. package/dist/modules/identity/identity.repository.d.ts +1 -40
  37. package/dist/modules/identity/identity.repository.js +1 -196
  38. package/dist/modules/identity/identity.routes.d.ts +2 -5
  39. package/dist/modules/identity/identity.routes.js +3 -11
  40. package/dist/modules/identity/identity.schema.d.ts +1 -27
  41. package/dist/modules/identity/identity.schema.js +1 -34
  42. package/dist/modules/identity/identity.seed.d.ts +1 -3
  43. package/dist/modules/identity/identity.seed.js +1 -81
  44. package/dist/modules/identity/identity.service.d.ts +1 -32
  45. package/dist/modules/identity/identity.service.js +1 -83
  46. package/dist/modules/identity/identity.types.d.ts +13 -106
  47. package/dist/modules/identity/index.d.ts +18 -11
  48. package/dist/modules/identity/index.js +14 -8
  49. package/dist/modules/identity/{identity.administration-migration.js → legacy/identity.administration-migration.js} +3 -11
  50. package/dist/modules/identity/legacy/identity.migration.d.ts +2 -0
  51. package/dist/modules/identity/legacy/identity.migration.js +72 -0
  52. package/dist/modules/identity/organization/index.d.ts +6 -0
  53. package/dist/modules/identity/organization/index.js +5 -0
  54. package/dist/modules/identity/organization/organization.controller.d.ts +35 -0
  55. package/dist/modules/identity/organization/organization.controller.js +18 -0
  56. package/dist/modules/identity/organization/organization.migration.d.ts +1 -0
  57. package/dist/modules/identity/organization/organization.migration.js +1 -0
  58. package/dist/modules/identity/organization/organization.provider.d.ts +46 -0
  59. package/dist/modules/identity/organization/organization.provider.js +33 -0
  60. package/dist/modules/identity/organization/organization.repository.d.ts +58 -0
  61. package/dist/modules/identity/organization/organization.repository.js +101 -0
  62. package/dist/modules/identity/organization/organization.routes.d.ts +5 -0
  63. package/dist/modules/identity/organization/organization.routes.js +2 -0
  64. package/dist/modules/identity/organization/organization.schema.d.ts +11 -0
  65. package/dist/modules/identity/organization/organization.schema.js +18 -0
  66. package/dist/modules/identity/organization/organization.seed.d.ts +3 -0
  67. package/dist/modules/identity/organization/organization.seed.js +11 -0
  68. package/dist/modules/identity/organization/organization.service.d.ts +39 -0
  69. package/dist/modules/identity/organization/organization.service.js +55 -0
  70. package/dist/modules/identity/organization/organization.types.d.ts +19 -0
  71. package/dist/modules/identity/organization/organization.types.js +1 -0
  72. package/dist/modules/identity/permission/index.d.ts +9 -0
  73. package/dist/modules/identity/permission/index.js +6 -0
  74. package/dist/modules/identity/permission/permission.controller.d.ts +21 -0
  75. package/dist/modules/identity/permission/permission.controller.js +12 -0
  76. package/dist/modules/identity/{identity.permission-declarations-migration.js → permission/permission.declarations.migration.js} +9 -4
  77. package/dist/modules/identity/{identity.permission-labels-migration.js → permission/permission.labels.migration.js} +5 -2
  78. package/dist/modules/identity/permission/permission.migration.d.ts +2 -0
  79. package/dist/modules/identity/permission/permission.migration.js +2 -0
  80. package/dist/modules/identity/permission/permission.provider.d.ts +19 -0
  81. package/dist/modules/identity/permission/permission.provider.js +14 -0
  82. package/dist/modules/identity/permission/permission.repository.d.ts +51 -0
  83. package/dist/modules/identity/permission/permission.repository.js +84 -0
  84. package/dist/modules/identity/permission/permission.routes.d.ts +5 -0
  85. package/dist/modules/identity/permission/permission.routes.js +2 -0
  86. package/dist/modules/identity/permission/permission.schema.d.ts +14 -0
  87. package/dist/modules/identity/permission/permission.schema.js +29 -0
  88. package/dist/modules/identity/permission/permission.seed.d.ts +4 -0
  89. package/dist/modules/identity/permission/permission.seed.js +17 -0
  90. package/dist/modules/identity/permission/permission.service.d.ts +31 -0
  91. package/dist/modules/identity/permission/permission.service.js +77 -0
  92. package/dist/modules/identity/permission/permission.types.d.ts +15 -0
  93. package/dist/modules/identity/permission/permission.types.js +1 -0
  94. package/dist/modules/identity/role/index.d.ts +10 -0
  95. package/dist/modules/identity/role/index.js +6 -0
  96. package/dist/modules/identity/role/role.controller.d.ts +42 -0
  97. package/dist/modules/identity/role/role.controller.js +25 -0
  98. package/dist/modules/identity/{identity.roles-migration.js → role/role.migration.js} +5 -18
  99. package/dist/modules/identity/role/role.provider.d.ts +14 -0
  100. package/dist/modules/identity/role/role.provider.js +30 -0
  101. package/dist/modules/identity/role/role.repository.d.ts +38 -0
  102. package/dist/modules/identity/role/role.repository.js +46 -0
  103. package/dist/modules/identity/role/role.routes.d.ts +5 -0
  104. package/dist/modules/identity/role/role.routes.js +2 -0
  105. package/dist/modules/identity/role/role.schema.d.ts +16 -0
  106. package/dist/modules/identity/role/role.schema.js +27 -0
  107. package/dist/modules/identity/role/role.seed.d.ts +3 -0
  108. package/dist/modules/identity/role/role.seed.js +8 -0
  109. package/dist/modules/identity/role/role.service.d.ts +69 -0
  110. package/dist/modules/identity/role/role.service.js +192 -0
  111. package/dist/modules/identity/role/role.types.d.ts +17 -0
  112. package/dist/modules/identity/role/role.types.js +1 -0
  113. package/dist/modules/identity/role-permission/index.d.ts +6 -0
  114. package/dist/modules/identity/role-permission/index.js +4 -0
  115. package/dist/modules/identity/role-permission/role-permission.controller.d.ts +1 -0
  116. package/dist/modules/identity/role-permission/role-permission.controller.js +1 -0
  117. package/dist/modules/identity/role-permission/role-permission.migration.d.ts +1 -0
  118. package/dist/modules/identity/role-permission/role-permission.migration.js +1 -0
  119. package/dist/modules/identity/role-permission/role-permission.provider.d.ts +14 -0
  120. package/dist/modules/identity/role-permission/role-permission.provider.js +15 -0
  121. package/dist/modules/identity/role-permission/role-permission.repository.d.ts +12 -0
  122. package/dist/modules/identity/role-permission/role-permission.repository.js +51 -0
  123. package/dist/modules/identity/role-permission/role-permission.routes.d.ts +1 -0
  124. package/dist/modules/identity/role-permission/role-permission.routes.js +2 -0
  125. package/dist/modules/identity/role-permission/role-permission.schema.d.ts +2 -0
  126. package/dist/modules/identity/role-permission/role-permission.schema.js +3 -0
  127. package/dist/modules/identity/role-permission/role-permission.seed.d.ts +3 -0
  128. package/dist/modules/identity/role-permission/role-permission.seed.js +10 -0
  129. package/dist/modules/identity/role-permission/role-permission.service.d.ts +13 -0
  130. package/dist/modules/identity/role-permission/role-permission.service.js +35 -0
  131. package/dist/modules/identity/role-permission/role-permission.types.d.ts +12 -0
  132. package/dist/modules/identity/role-permission/role-permission.types.js +1 -0
  133. package/dist/modules/identity/session/index.d.ts +6 -0
  134. package/dist/modules/identity/session/index.js +5 -0
  135. package/dist/modules/identity/session/session.authentication.controller.d.ts +30 -0
  136. package/dist/modules/identity/session/session.authentication.controller.js +37 -0
  137. package/dist/modules/identity/session/session.controller.d.ts +18 -0
  138. package/dist/modules/identity/session/session.controller.js +15 -0
  139. package/dist/modules/identity/session/session.migration.d.ts +1 -0
  140. package/dist/modules/identity/session/session.migration.js +1 -0
  141. package/dist/modules/identity/session/session.provider.d.ts +34 -0
  142. package/dist/modules/identity/session/session.provider.js +23 -0
  143. package/dist/modules/identity/session/session.repository.d.ts +36 -0
  144. package/dist/modules/identity/session/session.repository.js +128 -0
  145. package/dist/modules/identity/session/session.routes.d.ts +27 -0
  146. package/dist/modules/identity/session/session.routes.js +13 -0
  147. package/dist/modules/identity/session/session.schema.d.ts +6 -0
  148. package/dist/modules/identity/session/session.schema.js +12 -0
  149. package/dist/modules/identity/session/session.seed.d.ts +1 -0
  150. package/dist/modules/identity/session/session.seed.js +1 -0
  151. package/dist/modules/identity/session/session.service.d.ts +20 -0
  152. package/dist/modules/identity/session/session.service.js +27 -0
  153. package/dist/modules/identity/session/session.types.d.ts +17 -0
  154. package/dist/modules/identity/session/session.types.js +1 -0
  155. package/dist/modules/identity/settings/index.d.ts +5 -0
  156. package/dist/modules/identity/settings/index.js +4 -0
  157. package/dist/modules/identity/settings/settings.controller.d.ts +35 -0
  158. package/dist/modules/identity/settings/settings.controller.js +18 -0
  159. package/dist/modules/identity/settings/settings.migration.d.ts +1 -0
  160. package/dist/modules/identity/settings/settings.migration.js +1 -0
  161. package/dist/modules/identity/settings/settings.provider.d.ts +35 -0
  162. package/dist/modules/identity/settings/settings.provider.js +14 -0
  163. package/dist/modules/identity/settings/settings.repository.d.ts +49 -0
  164. package/dist/modules/identity/settings/settings.repository.js +75 -0
  165. package/dist/modules/identity/settings/settings.routes.d.ts +1 -0
  166. package/dist/modules/identity/settings/settings.routes.js +7 -0
  167. package/dist/modules/identity/settings/settings.schema.d.ts +15 -0
  168. package/dist/modules/identity/settings/settings.schema.js +27 -0
  169. package/dist/modules/identity/settings/settings.seed.d.ts +1 -0
  170. package/dist/modules/identity/settings/settings.seed.js +1 -0
  171. package/dist/modules/identity/settings/settings.service.d.ts +40 -0
  172. package/dist/modules/identity/settings/settings.service.js +101 -0
  173. package/dist/modules/identity/settings/settings.types.d.ts +20 -0
  174. package/dist/modules/identity/settings/settings.types.js +1 -0
  175. package/dist/modules/identity/support/identity.schema.d.ts +8 -0
  176. package/dist/modules/identity/support/identity.schema.js +10 -0
  177. package/dist/modules/identity/support/pagination.d.ts +13 -0
  178. package/dist/modules/identity/support/pagination.js +27 -0
  179. package/dist/modules/identity/support/pagination.schema.d.ts +43 -0
  180. package/dist/modules/identity/support/pagination.schema.js +15 -0
  181. package/dist/modules/identity/support/resource-controller.d.ts +14 -0
  182. package/dist/modules/identity/support/resource-controller.js +1 -0
  183. package/dist/modules/identity/user/index.d.ts +13 -0
  184. package/dist/modules/identity/user/index.js +11 -0
  185. package/dist/modules/identity/user/user.administration.service.d.ts +59 -0
  186. package/dist/modules/identity/user/user.administration.service.js +159 -0
  187. package/dist/modules/identity/user/user.controller.d.ts +44 -0
  188. package/dist/modules/identity/user/user.controller.js +21 -0
  189. package/dist/modules/identity/user/user.lifecycle.controller.d.ts +58 -0
  190. package/dist/modules/identity/user/user.lifecycle.controller.js +27 -0
  191. package/dist/modules/identity/{identity.lifecycle-schema.js → user/user.lifecycle.schema.js} +5 -5
  192. package/dist/modules/identity/{identity.lifecycle-service.d.ts → user/user.lifecycle.service.d.ts} +8 -4
  193. package/dist/modules/identity/{identity.lifecycle-service.js → user/user.lifecycle.service.js} +66 -118
  194. package/dist/modules/identity/user/user.migration.d.ts +1 -0
  195. package/dist/modules/identity/user/user.migration.js +1 -0
  196. package/dist/modules/identity/{identity.password.js → user/user.password.js} +1 -1
  197. package/dist/modules/identity/user/user.provider.d.ts +80 -0
  198. package/dist/modules/identity/user/user.provider.js +22 -0
  199. package/dist/modules/identity/user/user.repository.d.ts +64 -0
  200. package/dist/modules/identity/user/user.repository.js +129 -0
  201. package/dist/modules/identity/user/user.routes.d.ts +12 -0
  202. package/dist/modules/identity/user/user.routes.js +9 -0
  203. package/dist/modules/identity/user/user.schema.d.ts +34 -0
  204. package/dist/modules/identity/user/user.schema.js +46 -0
  205. package/dist/modules/identity/user/user.seed.d.ts +14 -0
  206. package/dist/modules/identity/user/user.seed.js +47 -0
  207. package/dist/modules/identity/user/user.service.d.ts +33 -0
  208. package/dist/modules/identity/user/user.service.js +76 -0
  209. package/dist/modules/identity/user/user.types.d.ts +27 -0
  210. package/dist/modules/identity/user/user.types.js +1 -0
  211. package/dist/modules/identity/user-role/index.d.ts +8 -0
  212. package/dist/modules/identity/user-role/index.js +6 -0
  213. package/dist/modules/identity/user-role/user-role.controller.d.ts +42 -0
  214. package/dist/modules/identity/user-role/user-role.controller.js +21 -0
  215. package/dist/modules/identity/user-role/user-role.migration.d.ts +1 -0
  216. package/dist/modules/identity/user-role/user-role.migration.js +1 -0
  217. package/dist/modules/identity/user-role/user-role.policy.d.ts +3 -0
  218. package/dist/modules/identity/user-role/user-role.policy.js +25 -0
  219. package/dist/modules/identity/user-role/user-role.provider.d.ts +18 -0
  220. package/dist/modules/identity/user-role/user-role.provider.js +23 -0
  221. package/dist/modules/identity/user-role/user-role.repository.d.ts +34 -0
  222. package/dist/modules/identity/user-role/user-role.repository.js +87 -0
  223. package/dist/modules/identity/user-role/user-role.routes.d.ts +5 -0
  224. package/dist/modules/identity/user-role/user-role.routes.js +2 -0
  225. package/dist/modules/identity/user-role/user-role.schema.d.ts +14 -0
  226. package/dist/modules/identity/user-role/user-role.schema.js +25 -0
  227. package/dist/modules/identity/user-role/user-role.seed.d.ts +7 -0
  228. package/dist/modules/identity/user-role/user-role.seed.js +11 -0
  229. package/dist/modules/identity/user-role/user-role.service.d.ts +60 -0
  230. package/dist/modules/identity/user-role/user-role.service.js +216 -0
  231. package/dist/modules/identity/user-role/user-role.types.d.ts +12 -0
  232. package/dist/modules/identity/user-role/user-role.types.js +1 -0
  233. package/dist/modules/tenant/index.d.ts +6 -0
  234. package/dist/modules/tenant/index.js +5 -0
  235. package/dist/modules/tenant/tenant.command.d.ts +3 -0
  236. package/dist/modules/tenant/tenant.command.js +43 -0
  237. package/dist/modules/tenant/tenant.controller.d.ts +4 -0
  238. package/dist/modules/tenant/tenant.controller.js +12 -0
  239. package/dist/modules/tenant/tenant.migration.d.ts +5 -0
  240. package/dist/modules/tenant/tenant.migration.js +46 -0
  241. package/dist/modules/tenant/tenant.provider.d.ts +3 -0
  242. package/dist/modules/tenant/tenant.provider.js +44 -0
  243. package/dist/modules/tenant/tenant.readiness.d.ts +6 -0
  244. package/dist/modules/tenant/tenant.readiness.js +45 -0
  245. package/dist/modules/tenant/tenant.repository.d.ts +15 -0
  246. package/dist/modules/tenant/tenant.repository.js +23 -0
  247. package/dist/modules/tenant/tenant.routes.d.ts +4 -0
  248. package/dist/modules/tenant/tenant.routes.js +7 -0
  249. package/dist/modules/tenant/tenant.schema.d.ts +20 -0
  250. package/dist/modules/tenant/tenant.schema.js +31 -0
  251. package/dist/modules/tenant/tenant.seed.d.ts +8 -0
  252. package/dist/modules/tenant/tenant.seed.js +40 -0
  253. package/dist/modules/tenant/tenant.service.d.ts +16 -0
  254. package/dist/modules/tenant/tenant.service.js +72 -0
  255. package/dist/modules/tenant/tenant.types.d.ts +39 -0
  256. package/dist/modules/tenant/tenant.types.js +1 -0
  257. package/package.json +9 -5
  258. package/dist/modules/identity/identity.administration-repository.d.ts +0 -19
  259. package/dist/modules/identity/identity.administration-repository.js +0 -102
  260. package/dist/modules/identity/identity.administration-schema.d.ts +0 -113
  261. package/dist/modules/identity/identity.administration-schema.js +0 -121
  262. package/dist/modules/identity/identity.administration-service.js +0 -517
  263. package/dist/modules/identity/identity.membership-policy.d.ts +0 -3
  264. package/dist/modules/identity/identity.membership-policy.js +0 -27
  265. package/dist/modules/identity/identity.permission-declarations.d.ts +0 -23
  266. package/dist/modules/identity/identity.permission-declarations.js +0 -57
  267. package/dist/modules/identity/identity.presentation-service.d.ts +0 -23
  268. package/dist/modules/identity/identity.presentation-service.js +0 -51
  269. package/dist/modules/identity/identity.roles-service.d.ts +0 -49
  270. package/dist/modules/identity/identity.roles-service.js +0 -327
  271. /package/dist/modules/identity/{identity.administration-migration.d.ts → legacy/identity.administration-migration.d.ts} +0 -0
  272. /package/dist/modules/identity/{identity.permission-declarations-migration.d.ts → permission/permission.declarations.migration.d.ts} +0 -0
  273. /package/dist/modules/identity/{identity.permission-labels-migration.d.ts → permission/permission.labels.migration.d.ts} +0 -0
  274. /package/dist/modules/identity/{identity.roles-migration.d.ts → role/role.migration.d.ts} +0 -0
  275. /package/dist/modules/identity/{identity.error.d.ts → support/identity.error.d.ts} +0 -0
  276. /package/dist/modules/identity/{identity.error.js → support/identity.error.js} +0 -0
  277. /package/dist/modules/identity/{identity.request-context.d.ts → support/identity.request-context.d.ts} +0 -0
  278. /package/dist/modules/identity/{identity.request-context.js → support/identity.request-context.js} +0 -0
  279. /package/dist/modules/identity/{identity.lifecycle-schema.d.ts → user/user.lifecycle.schema.d.ts} +0 -0
  280. /package/dist/modules/identity/{identity.password.d.ts → user/user.password.d.ts} +0 -0
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ export const tenantRequestSchema = z.strictObject({
3
+ portal: z.enum(["user", "admin", "super-admin"]).default("user"),
4
+ tenantId: z.string().trim().min(1).max(255).optional()
5
+ });
6
+ export const tenantMappingSchema = z
7
+ .strictObject({
8
+ tenantId: z.string().min(1),
9
+ driver: z.enum(["sqlite", "mariadb"]),
10
+ databaseName: z
11
+ .string()
12
+ .regex(/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/)
13
+ .nullable(),
14
+ sqlitePath: z.string().trim().min(1).nullable(),
15
+ active: z.number().int().min(0).max(1),
16
+ version: z.number().int().min(1)
17
+ })
18
+ .superRefine((value, context) => {
19
+ if (value.driver === "mariadb" && !value.databaseName)
20
+ context.addIssue({
21
+ code: "custom",
22
+ path: ["databaseName"],
23
+ message: "Database name is required."
24
+ });
25
+ if (value.driver === "sqlite" && !value.sqlitePath)
26
+ context.addIssue({
27
+ code: "custom",
28
+ path: ["sqlitePath"],
29
+ message: "Database path is required."
30
+ });
31
+ });
@@ -0,0 +1,8 @@
1
+ import type { TenantDatabase } from "./tenant.types.js";
2
+ import type { ConnectionTarget } from "@devxcrew/framework";
3
+ export declare function provisionTenant(database: TenantDatabase & {
4
+ provisionConnection(target: ConnectionTarget): Promise<unknown>;
5
+ }, environment: NodeJS.ProcessEnv): Promise<{
6
+ tenantId: string;
7
+ status: string;
8
+ }>;
@@ -0,0 +1,40 @@
1
+ import { createHash } from "node:crypto";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { HttpError, readDatabaseConfiguration } from "@devxcrew/framework";
4
+ import { createIdentityTenantDirectory } from "../identity/index.js";
5
+ export async function provisionTenant(database, environment) {
6
+ const id = environment.IDENTITY_TENANT_ID ?? "default";
7
+ const tenant = await createIdentityTenantDirectory(database.database).active(id);
8
+ if (!tenant)
9
+ throw new HttpError(422, "tenant_missing", "Seed an active tenant before provisioning.");
10
+ const existing = await database.database
11
+ .selectFrom("tenant_connections")
12
+ .selectAll()
13
+ .where("tenant_id", "=", id)
14
+ .executeTakeFirst();
15
+ if (existing)
16
+ return { tenantId: id, status: "existing" };
17
+ const fingerprint = createHash("sha256").update(id).digest("hex").slice(0, 16);
18
+ const { driver } = readDatabaseConfiguration(environment);
19
+ const databaseName = driver === "mariadb"
20
+ ? `${(environment.DB_MASTER_NAME ?? "master").slice(0, 30)}_tenant_${fingerprint}`
21
+ : null;
22
+ const sqlitePath = driver === "sqlite"
23
+ ? join(dirname(resolve(environment.DB_SQLITE_PATH ?? "storage/private/data/identity.sqlite")), `tenant-${fingerprint}.sqlite`)
24
+ : null;
25
+ const target = { key: id, driver, databaseName, sqlitePath, version: 1 };
26
+ await database.provisionConnection(target);
27
+ // Register only after storage migrations succeed. Reruns preserve the existing mapping.
28
+ await database.database
29
+ .insertInto("tenant_connections")
30
+ .values({
31
+ tenant_id: id,
32
+ driver,
33
+ database_name: databaseName,
34
+ sqlite_path: sqlitePath,
35
+ active: 1
36
+ })
37
+ .onConflict((conflict) => conflict.column("tenant_id").doNothing())
38
+ .execute();
39
+ return { tenantId: id, status: "provisioned" };
40
+ }
@@ -0,0 +1,16 @@
1
+ import type { IdentityTenantDirectory } from "../identity/index.js";
2
+ import { type DatabaseInfrastructureSchema } from "@devxcrew/framework";
3
+ import { TenantRepository } from "./tenant.repository.js";
4
+ import type { TenantDatabase, TenantPrincipal, TenantScope } from "./tenant.types.js";
5
+ export declare class TenantService<Schema extends DatabaseInfrastructureSchema = DatabaseInfrastructureSchema> {
6
+ private readonly repository;
7
+ private readonly directory;
8
+ private readonly connections;
9
+ private readonly mode;
10
+ private readonly clientId;
11
+ private readonly context;
12
+ constructor(repository: TenantRepository, directory: IdentityTenantDirectory, connections: TenantDatabase<Schema>, mode: "single-client" | "multi-tenant", clientId: string);
13
+ current(): TenantScope<Schema>;
14
+ verify(): Promise<void>;
15
+ withPrincipal<T>(principal: TenantPrincipal, requested: string | undefined, work: () => Promise<T>, signal?: AbortSignal): Promise<T>;
16
+ }
@@ -0,0 +1,72 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { HttpError } from "@devxcrew/framework";
3
+ import { DatabaseCapacityError, ResumableTransfer, DatabaseExecution } from "@devxcrew/framework";
4
+ import { tenantMappingSchema } from "./tenant.schema.js";
5
+ export class TenantService {
6
+ repository;
7
+ directory;
8
+ connections;
9
+ mode;
10
+ clientId;
11
+ context = new AsyncLocalStorage();
12
+ constructor(repository, directory, connections, mode, clientId) {
13
+ this.repository = repository;
14
+ this.directory = directory;
15
+ this.connections = connections;
16
+ this.mode = mode;
17
+ this.clientId = clientId;
18
+ }
19
+ current() {
20
+ const scope = this.context.getStore();
21
+ if (!scope)
22
+ throw new HttpError(401, "tenant_context_required", "An authenticated tenant context is required.");
23
+ return scope;
24
+ }
25
+ verify() {
26
+ return this.repository.verify();
27
+ }
28
+ async withPrincipal(principal, requested, work, signal) {
29
+ signal?.throwIfAborted();
30
+ const id = principal.tenant.id;
31
+ if (!id ||
32
+ (requested && requested !== id) ||
33
+ (this.mode === "single-client" && id !== this.clientId))
34
+ throw new HttpError(403, "tenant_denied", "The session does not authorize this tenant.");
35
+ const activeTenant = await this.directory.active(id);
36
+ const mapping = await this.repository.find(id);
37
+ if (!activeTenant || !mapping || Number(mapping.active) !== 1)
38
+ throw new HttpError(403, "tenant_unavailable", "No active tenant database is available.");
39
+ const target = tenantMappingSchema.safeParse({
40
+ tenantId: id,
41
+ driver: mapping.driver,
42
+ databaseName: mapping.databaseName,
43
+ sqlitePath: mapping.sqlitePath,
44
+ active: Number(mapping.active),
45
+ version: Number(mapping.version)
46
+ });
47
+ if (!target.success)
48
+ throw new HttpError(503, "tenant_mapping_invalid", "Tenant database configuration is invalid.");
49
+ return this.connections
50
+ .withConnection({
51
+ key: id,
52
+ driver: target.data.driver,
53
+ databaseName: target.data.databaseName,
54
+ sqlitePath: target.data.sqlitePath,
55
+ version: target.data.version
56
+ }, async (database) => {
57
+ signal?.throwIfAborted();
58
+ return this.context.run({
59
+ tenantId: id,
60
+ principal,
61
+ database,
62
+ data: new DatabaseExecution(database),
63
+ transfers: new ResumableTransfer(database)
64
+ }, work);
65
+ })
66
+ .catch((error) => {
67
+ if (error instanceof DatabaseCapacityError)
68
+ throw new HttpError(503, "tenant_busy", "Tenant connection capacity is currently busy.");
69
+ throw error;
70
+ });
71
+ }
72
+ }
@@ -0,0 +1,39 @@
1
+ import type { ResumableTransfer } from "@devxcrew/framework";
2
+ import type { Principal, Portal, IdentitySchema } from "../identity/index.js";
3
+ import type { Generated, Kysely } from "kysely";
4
+ import type { IncomingMessage } from "node:http";
5
+ import type { DatabaseInfrastructureSchema, ConnectionTarget, DatabaseExecution } from "@devxcrew/framework";
6
+ export type TenantPrincipal = Principal;
7
+ export interface TenantIdentity {
8
+ authenticateRequest(request: IncomingMessage, portal: Portal, signal?: AbortSignal): Promise<Principal>;
9
+ }
10
+ export interface TenantRegistrySchema {
11
+ tenant_connections: {
12
+ tenant_id: string;
13
+ driver: "sqlite" | "mariadb";
14
+ database_name: string | null;
15
+ sqlite_path: string | null;
16
+ active: number;
17
+ version: Generated<number>;
18
+ };
19
+ }
20
+ export interface TenantMasterSchema extends IdentitySchema, TenantRegistrySchema, DatabaseInfrastructureSchema {
21
+ }
22
+ export interface TenantScope<Schema extends DatabaseInfrastructureSchema = DatabaseInfrastructureSchema> {
23
+ tenantId: string;
24
+ principal: Principal;
25
+ database: Kysely<Schema>;
26
+ data: DatabaseExecution<Schema>;
27
+ transfers: ResumableTransfer<Schema>;
28
+ }
29
+ export interface TenantProvider<Schema extends DatabaseInfrastructureSchema = DatabaseInfrastructureSchema> {
30
+ handle(request: IncomingMessage, response: import("node:http").ServerResponse, signal?: AbortSignal): boolean | Promise<boolean>;
31
+ verify(): Promise<void>;
32
+ runRequest<T>(request: IncomingMessage, work: () => Promise<T>, signal?: AbortSignal): Promise<T>;
33
+ withPrincipal<T>(principal: Principal, requestedTenant: string | undefined, work: () => Promise<T>, signal?: AbortSignal): Promise<T>;
34
+ current(): TenantScope<Schema>;
35
+ }
36
+ export interface TenantDatabase<Schema extends DatabaseInfrastructureSchema = DatabaseInfrastructureSchema> {
37
+ database: Kysely<TenantMasterSchema>;
38
+ withConnection<T>(target: ConnectionTarget, work: (database: Kysely<Schema>) => Promise<T>): Promise<T>;
39
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devxcrew/platform",
3
- "version": "0.1.2",
3
+ "version": "0.1.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/devxcrew/platform.git"
@@ -24,9 +24,9 @@
24
24
  "node": ">=26.10.0"
25
25
  },
26
26
  "scripts": {
27
- "build": "tsc -p tsconfig.json",
27
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
28
28
  "prepack": "npm run build",
29
- "test": "tsx --test src/modules/identity/*.test.ts",
29
+ "test": "tsx --test src/modules/identity/composition/tests/*.test.ts src/modules/tenant/tests/*.test.ts",
30
30
  "mcp:connect": "node agent/connect.mjs",
31
31
  "mcp:verify": "node agent/connect.mjs --strict",
32
32
  "version-bump": "devxcrew-tools version:bump",
@@ -49,7 +49,8 @@
49
49
  "zod": "^4.0.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@devxcrew/tools": "0.1.7",
52
+ "@devxcrew/framework": "0.1.12",
53
+ "@devxcrew/tools": "0.1.8",
53
54
  "@types/node": "^26.0.0",
54
55
  "tsx": "^4.23.12",
55
56
  "typescript": "7.0.2"
@@ -58,5 +59,8 @@
58
59
  "access": "public",
59
60
  "registry": "https://registry.npmjs.org/"
60
61
  },
61
- "license": "MIT"
62
+ "license": "MIT",
63
+ "peerDependencies": {
64
+ "@devxcrew/framework": ">=0.1.12 <1"
65
+ }
62
66
  }
@@ -1,19 +0,0 @@
1
- import { type Kysely } from "kysely";
2
- import type { IdentitySchema, Principal } from "./identity.types.js";
3
- import type { IdentityListQuery, IdentityResource } from "./identity.administration-schema.js";
4
- export declare class IdentityAdministrationRepository {
5
- private readonly db;
6
- constructor(db: Kysely<IdentitySchema>);
7
- list(actor: Principal, resource: IdentityResource, query: IdentityListQuery): Promise<{
8
- data: Record<string, unknown>[];
9
- meta: {
10
- page: number;
11
- perPage: number;
12
- total: number;
13
- lastPage: number;
14
- };
15
- }>;
16
- show(actor: Principal, resource: IdentityResource, id: string): Promise<Record<string, unknown> | undefined>;
17
- private source;
18
- private present;
19
- }
@@ -1,102 +0,0 @@
1
- import { sql } from "kysely";
2
- import { IdentityError } from "./identity.error.js";
3
- export class IdentityAdministrationRepository {
4
- db;
5
- constructor(db) {
6
- this.db = db;
7
- }
8
- async list(actor, resource, query) {
9
- const source = this.source(actor, resource);
10
- const fields = resource === "users"
11
- ? ["id", "name", "email"]
12
- : resource === "organizations" || resource === "roles"
13
- ? ["id", "name"]
14
- : resource === "memberships"
15
- ? ["id", "userName", "organizationName", "roleName"]
16
- : ["id"];
17
- if (!fields.includes(query.sort))
18
- throw new IdentityError(422, "This sort field is not supported for this resource.");
19
- const search = query.search
20
- ? sql `where ${sql.join(fields.map((field) => sql `lower(cast(${sql.ref(field)} as text)) like ${`%${query.search.toLowerCase()}%`}`), sql ` or `)}`
21
- : sql ``;
22
- const count = await sql `select count(*) as total from (${source}) as resources ${search}`.execute(this.db);
23
- const sort = query.sort;
24
- const rows = await sql `select * from (${source}) as resources ${search}
25
- order by ${sql.ref(sort)} ${query.direction === "asc" ? sql `asc` : sql `desc`}, id asc
26
- limit ${query.perPage} offset ${(query.page - 1) * query.perPage}`.execute(this.db);
27
- const total = Number(count.rows[0].total);
28
- return {
29
- data: rows.rows.map((row) => this.present(resource, row)),
30
- meta: {
31
- page: query.page,
32
- perPage: query.perPage,
33
- total,
34
- lastPage: Math.max(1, Math.ceil(total / query.perPage)),
35
- },
36
- };
37
- }
38
- async show(actor, resource, id) {
39
- const result = await sql `select * from (${this.source(actor, resource)}) as resources where id = ${id} limit 1`.execute(this.db);
40
- return result.rows[0] ? this.present(resource, result.rows[0]) : undefined;
41
- }
42
- source(actor, resource) {
43
- const global = actor.portal === "super-admin";
44
- if (resource === "users")
45
- return sql `select u.id,u.name,u.email,u.active,u.version from identity_users u
46
- where exists (select 1 from identity_memberships m left join identity_custom_roles c on c.id=m.custom_role_id
47
- where m.user_id=u.id and (m.custom_role_id is null or c.app_id=${actor.appId})
48
- ${global ? sql `` : sql `and m.tenant_id=${actor.tenant.id}`})
49
- ${global ? sql `or not exists(select 1 from identity_memberships m where m.user_id=u.id)` : sql ``}`;
50
- if (resource === "organizations")
51
- return sql `select id,name,active,version from identity_tenants ${global ? sql `` : sql `where id=${actor.tenant.id}`}`;
52
- if (resource === "memberships")
53
- return sql `select m.user_id||'~'||m.tenant_id||'~'||m.role_id as id,m.user_id as userId,m.tenant_id as tenantId,
54
- coalesce(m.custom_role_id,m.role_id) as roleId,m.role_id as portal,m.active,m.version,u.name as userName,t.name as organizationName,
55
- coalesce(c.name,m.role_id) as roleName from identity_memberships m
56
- join identity_users u on u.id=m.user_id join identity_tenants t on t.id=m.tenant_id
57
- left join identity_custom_roles c on c.id=m.custom_role_id
58
- where (m.custom_role_id is null or c.app_id=${actor.appId}) ${global ? sql `` : sql `and m.tenant_id=${actor.tenant.id}`}`;
59
- if (resource === "roles")
60
- return sql `select r.id,r.id as name,r.id as portal,1 as system,null as tenantId,1 as active,r.version,(select json_group_array(permission_id) from identity_role_permissions p where p.role_id=r.id) as permissionIds from identity_roles r
61
- union all select c.id,c.name,'user' as portal,0 as system,c.tenant_id as tenantId,c.active,c.version,
62
- (select json_group_array(permission_id) from identity_custom_role_permissions p where p.role_id=c.id) as permissionIds
63
- from identity_custom_roles c where c.app_id=${actor.appId} ${global ? sql `` : sql `and c.tenant_id=${actor.tenant.id}`}`;
64
- if (resource === "permissions")
65
- return sql `select p.id,coalesce(d.label,p.id) as label,
66
- coalesce(d.owner,case when p.id like 'desk.%' then 'desk' else 'identity' end) as owner,
67
- d.app_id as appId,
68
- coalesce(d.portals,case when p.id='desk.user' then '["user"]' when p.id='desk.admin' then '["admin"]'
69
- when p.id='desk.super-admin' then '["super-admin"]' when p.id='identity.manage' then '["admin","super-admin"]'
70
- else '["user","admin","super-admin"]' end) as portals
71
- from identity_permissions p left join identity_permission_declarations d on d.permission_id=p.id
72
- where d.permission_id is null or d.app_id=${actor.appId}`;
73
- if (resource === "sessions")
74
- return sql `select token_hash as id,user_id as userId,tenant_id as tenantId,portal,expires_at as expiresAt from identity_sessions
75
- where app_id=${actor.appId} and expires_at>${new Date().toISOString()}
76
- ${global ? sql `` : sql `and tenant_id=${actor.tenant.id}`}
77
- ${actor.portal === "user" ? sql `and user_id=${actor.user.id}` : sql ``}`;
78
- return sql `select id,actor_id as actorId,tenant_id as tenantId,action,resource_id as resourceId,created_at as createdAt
79
- from identity_audit_events where app_id=${actor.appId} ${global ? sql `` : sql `and tenant_id=${actor.tenant.id}`}`;
80
- }
81
- present(resource, row) {
82
- if (resource === "permissions")
83
- return { ...row, portals: JSON.parse(String(row.portals)) };
84
- if (resource === "roles")
85
- return {
86
- ...row,
87
- version: Number(row.version),
88
- active: Boolean(row.active),
89
- system: Boolean(row.system),
90
- permissionIds: JSON.parse(String(row.permissionIds)),
91
- };
92
- if (resource === "users" ||
93
- resource === "organizations" ||
94
- resource === "memberships")
95
- return {
96
- ...row,
97
- active: Boolean(row.active),
98
- version: Number(row.version),
99
- };
100
- return row;
101
- }
102
- }
@@ -1,113 +0,0 @@
1
- import { z } from "zod";
2
- export declare const resourceIdSchema: z.ZodString;
3
- export declare const listSchema: z.ZodObject<{
4
- page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
5
- perPage: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
6
- search: z.ZodDefault<z.ZodString>;
7
- sort: z.ZodDefault<z.ZodEnum<{
8
- email: "email";
9
- id: "id";
10
- name: "name";
11
- }>>;
12
- direction: z.ZodDefault<z.ZodEnum<{
13
- asc: "asc";
14
- desc: "desc";
15
- }>>;
16
- }, z.core.$strict>;
17
- export declare const resourceListQuerySchema: z.ZodPipe<z.ZodObject<{
18
- page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
19
- search: z.ZodDefault<z.ZodString>;
20
- sort: z.ZodDefault<z.ZodEnum<{
21
- email: "email";
22
- id: "id";
23
- name: "name";
24
- }>>;
25
- direction: z.ZodDefault<z.ZodEnum<{
26
- asc: "asc";
27
- desc: "desc";
28
- }>>;
29
- per_page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
30
- }, z.core.$strict>, z.ZodTransform<{
31
- page: number;
32
- search: string;
33
- sort: "email" | "id" | "name";
34
- direction: "asc" | "desc";
35
- perPage: number;
36
- }, {
37
- page: number;
38
- search: string;
39
- sort: "email" | "id" | "name";
40
- direction: "asc" | "desc";
41
- per_page: number;
42
- }>>;
43
- export declare const membershipDeleteQuerySchema: z.ZodObject<{
44
- expectedVersion: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
45
- }, z.core.$strict>;
46
- export declare const userCreateSchema: z.ZodObject<{
47
- name: z.ZodString;
48
- email: z.ZodPipe<z.ZodEmail, z.ZodTransform<string, string>>;
49
- password: z.ZodString;
50
- roleId: z.ZodDefault<z.ZodString>;
51
- tenantId: z.ZodOptional<z.ZodString>;
52
- }, z.core.$strict>;
53
- export declare const userUpdateSchema: z.ZodObject<{
54
- name: z.ZodOptional<z.ZodString>;
55
- email: z.ZodOptional<z.ZodPipe<z.ZodEmail, z.ZodTransform<string, string>>>;
56
- active: z.ZodOptional<z.ZodBoolean>;
57
- expectedVersion: z.ZodNumber;
58
- }, z.core.$strict>;
59
- export declare const organizationCreateSchema: z.ZodObject<{
60
- id: z.ZodString;
61
- name: z.ZodString;
62
- active: z.ZodDefault<z.ZodBoolean>;
63
- }, z.core.$strict>;
64
- export declare const organizationUpdateSchema: z.ZodObject<{
65
- name: z.ZodOptional<z.ZodString>;
66
- active: z.ZodOptional<z.ZodBoolean>;
67
- expectedVersion: z.ZodNumber;
68
- }, z.core.$strict>;
69
- export declare const membershipSchema: z.ZodObject<{
70
- userId: z.ZodString;
71
- tenantId: z.ZodOptional<z.ZodString>;
72
- roleId: z.ZodString;
73
- }, z.core.$strict>;
74
- export declare const roleUpdateSchema: z.ZodObject<{
75
- permissionIds: z.ZodArray<z.ZodString>;
76
- expectedVersion: z.ZodNumber;
77
- }, z.core.$strict>;
78
- export declare const profileSchema: z.ZodObject<{
79
- name: z.ZodString;
80
- expectedVersion: z.ZodNumber;
81
- }, z.core.$strict>;
82
- export declare const customRoleCreateSchema: z.ZodObject<{
83
- name: z.ZodString;
84
- permissionIds: z.ZodArray<z.ZodString>;
85
- tenantId: z.ZodOptional<z.ZodString>;
86
- }, z.core.$strict>;
87
- export declare const customRoleUpdateSchema: z.ZodObject<{
88
- name: z.ZodOptional<z.ZodString>;
89
- permissionIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
90
- active: z.ZodOptional<z.ZodBoolean>;
91
- expectedVersion: z.ZodNumber;
92
- }, z.core.$strict>;
93
- export declare const membershipUpdateSchema: z.ZodObject<{
94
- roleId: z.ZodString;
95
- active: z.ZodBoolean;
96
- expectedVersion: z.ZodNumber;
97
- }, z.core.$strict>;
98
- export declare const settingsSchema: z.ZodObject<{
99
- displayName: z.ZodString;
100
- expectedVersion: z.ZodNumber;
101
- locale: z.ZodEnum<{
102
- en: "en";
103
- "en-GB": "en-GB";
104
- "en-US": "en-US";
105
- }>;
106
- timeZone: z.ZodString;
107
- }, z.core.$strict>;
108
- export declare const securitySettingsSchema: z.ZodObject<{
109
- sessionSeconds: z.ZodNumber;
110
- expectedVersion: z.ZodNumber;
111
- }, z.core.$strict>;
112
- export type IdentityListQuery = z.infer<typeof listSchema>;
113
- export type IdentityResource = "users" | "organizations" | "memberships" | "roles" | "permissions" | "sessions" | "audit-events";
@@ -1,121 +0,0 @@
1
- import { z } from "zod";
2
- import { identityKeySchema } from "./identity.schema.js";
3
- export const resourceIdSchema = z
4
- .string()
5
- .regex(/^[a-zA-Z0-9._~-]{1,300}$/)
6
- .refine((value) => !/^\.+$/.test(value), "Use an identifier with a letter or number.");
7
- export const listSchema = z
8
- .object({
9
- page: z.coerce.number().int().min(1).max(100000).default(1),
10
- perPage: z.coerce.number().int().min(1).max(100).default(20),
11
- search: z.string().trim().max(100).default(""),
12
- sort: z.enum(["id", "name", "email"]).default("id"),
13
- direction: z.enum(["asc", "desc"]).default("asc"),
14
- })
15
- .strict();
16
- export const resourceListQuerySchema = listSchema
17
- .omit({ perPage: true })
18
- .extend({ per_page: listSchema.shape.perPage })
19
- .strict()
20
- .transform(({ per_page, ...input }) => ({ ...input, perPage: per_page }));
21
- export const membershipDeleteQuerySchema = z
22
- .object({
23
- expectedVersion: z
24
- .string()
25
- .regex(/^(0|[1-9][0-9]{0,9})$/)
26
- .transform(Number),
27
- })
28
- .strict();
29
- const name = z.string().trim().min(1).max(100);
30
- const email = z
31
- .email()
32
- .max(254)
33
- .transform((v) => v.trim().toLowerCase());
34
- export const userCreateSchema = z
35
- .object({
36
- name,
37
- email,
38
- password: z.string().min(12).max(256),
39
- roleId: resourceIdSchema.default("user"),
40
- tenantId: identityKeySchema.optional(),
41
- })
42
- .strict();
43
- const expectedVersion = z.number().int().min(0);
44
- export const userUpdateSchema = z
45
- .object({
46
- name: name.optional(),
47
- email: email.optional(),
48
- active: z.boolean().optional(),
49
- expectedVersion,
50
- })
51
- .strict()
52
- .refine((v) => Object.keys(v).length > 1, "Provide a changed field.");
53
- export const organizationCreateSchema = z
54
- .object({ id: identityKeySchema, name, active: z.boolean().default(true) })
55
- .strict();
56
- export const organizationUpdateSchema = z
57
- .object({
58
- name: name.optional(),
59
- active: z.boolean().optional(),
60
- expectedVersion,
61
- })
62
- .strict()
63
- .refine((v) => Object.keys(v).length > 1, "Provide a changed field.");
64
- export const membershipSchema = z
65
- .object({
66
- userId: identityKeySchema,
67
- tenantId: identityKeySchema.optional(),
68
- roleId: resourceIdSchema,
69
- })
70
- .strict();
71
- export const roleUpdateSchema = z
72
- .object({
73
- permissionIds: z.array(resourceIdSchema).max(100),
74
- expectedVersion,
75
- })
76
- .strict();
77
- export const profileSchema = z.object({ name, expectedVersion }).strict();
78
- export const customRoleCreateSchema = z
79
- .object({
80
- name,
81
- permissionIds: z.array(resourceIdSchema).max(100),
82
- tenantId: identityKeySchema.optional(),
83
- })
84
- .strict();
85
- export const customRoleUpdateSchema = z
86
- .object({
87
- name: name.optional(),
88
- permissionIds: z.array(resourceIdSchema).max(100).optional(),
89
- active: z.boolean().optional(),
90
- expectedVersion,
91
- })
92
- .strict()
93
- .refine((input) => Object.keys(input).length > 1, "Provide a changed field.");
94
- export const membershipUpdateSchema = z
95
- .object({ roleId: resourceIdSchema, active: z.boolean(), expectedVersion })
96
- .strict();
97
- export const settingsSchema = z
98
- .object({
99
- displayName: name,
100
- expectedVersion,
101
- locale: z.enum(["en", "en-US", "en-GB"]),
102
- timeZone: z
103
- .string()
104
- .max(100)
105
- .refine((v) => {
106
- try {
107
- new Intl.DateTimeFormat("en", { timeZone: v });
108
- return true;
109
- }
110
- catch {
111
- return false;
112
- }
113
- }, "Use a supported IANA time zone."),
114
- })
115
- .strict();
116
- export const securitySettingsSchema = z
117
- .object({
118
- sessionSeconds: z.number().int().min(300).max(86400),
119
- expectedVersion,
120
- })
121
- .strict();