@deveye/types 0.15.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/package.json +68 -0
  4. package/src/domain/audience.ts +549 -0
  5. package/src/domain/backup.ts +355 -0
  6. package/src/domain/credential.ts +55 -0
  7. package/src/domain/database.ts +467 -0
  8. package/src/domain/deploy.ts +231 -0
  9. package/src/domain/device.ts +172 -0
  10. package/src/domain/deviceFiles.ts +84 -0
  11. package/src/domain/deviceLogs.ts +82 -0
  12. package/src/domain/featureRegistry.ts +392 -0
  13. package/src/domain/finance.ts +477 -0
  14. package/src/domain/git.ts +419 -0
  15. package/src/domain/home.ts +314 -0
  16. package/src/domain/live.ts +272 -0
  17. package/src/domain/logs.ts +117 -0
  18. package/src/domain/mail.ts +394 -0
  19. package/src/domain/metrics.ts +127 -0
  20. package/src/domain/note.ts +202 -0
  21. package/src/domain/notifications.ts +268 -0
  22. package/src/domain/packages.ts +35 -0
  23. package/src/domain/password.ts +36 -0
  24. package/src/domain/presence.ts +21 -0
  25. package/src/domain/project.ts +168 -0
  26. package/src/domain/projectBoard.ts +130 -0
  27. package/src/domain/projectChat.ts +46 -0
  28. package/src/domain/projectHistory.ts +82 -0
  29. package/src/domain/projectLink.ts +87 -0
  30. package/src/domain/projectPlan.ts +68 -0
  31. package/src/domain/report.ts +492 -0
  32. package/src/domain/role.ts +8 -0
  33. package/src/domain/secrecy.ts +66 -0
  34. package/src/domain/sentinel.ts +623 -0
  35. package/src/domain/sharing.ts +186 -0
  36. package/src/domain/syncProtocol.ts +116 -0
  37. package/src/domain/twoFactor.ts +40 -0
  38. package/src/domain/uptime.ts +216 -0
  39. package/src/domain/user.ts +141 -0
  40. package/src/domain/workspace.ts +56 -0
  41. package/src/domain/workspaceRole.ts +251 -0
  42. package/src/features/admin.ts +112 -0
  43. package/src/features/audience.ts +275 -0
  44. package/src/features/backup.ts +230 -0
  45. package/src/features/database.ts +461 -0
  46. package/src/features/deploy.ts +245 -0
  47. package/src/features/device.ts +292 -0
  48. package/src/features/deviceFiles.ts +83 -0
  49. package/src/features/deviceLogs.ts +36 -0
  50. package/src/features/deviceTerminal.ts +57 -0
  51. package/src/features/finance.ts +360 -0
  52. package/src/features/git.ts +368 -0
  53. package/src/features/home.ts +32 -0
  54. package/src/features/live.ts +113 -0
  55. package/src/features/logs.ts +86 -0
  56. package/src/features/mail.ts +374 -0
  57. package/src/features/metrics.ts +185 -0
  58. package/src/features/note.ts +189 -0
  59. package/src/features/notify.ts +164 -0
  60. package/src/features/password.ts +67 -0
  61. package/src/features/project.ts +709 -0
  62. package/src/features/registry.ts +103 -0
  63. package/src/features/secrecy.ts +120 -0
  64. package/src/features/sentinel.ts +233 -0
  65. package/src/features/sharing.ts +79 -0
  66. package/src/features/twoFactor.ts +47 -0
  67. package/src/features/uptime.ts +186 -0
  68. package/src/features/user.ts +91 -0
  69. package/src/features/workspace.ts +200 -0
  70. package/src/http/auth.ts +94 -0
  71. package/src/http/device.ts +222 -0
  72. package/src/http/status.ts +45 -0
  73. package/src/index.ts +1700 -0
  74. package/src/protocol/agent.ts +1171 -0
  75. package/src/protocol/envelope.ts +46 -0
  76. package/src/protocol/error.ts +27 -0
  77. package/src/protocol/result.ts +17 -0
  78. package/src/protocol/version.ts +6 -0
  79. package/src/sdk/client-ambient.d.ts +238 -0
  80. package/src/sdk/client.ts +66 -0
  81. package/src/sdk/ids.ts +25 -0
  82. package/src/sdk/index.ts +11 -0
  83. package/src/sdk/manifest.ts +326 -0
  84. package/src/sdk/providers.ts +48 -0
  85. package/src/sdk/server.ts +378 -0
  86. package/src/sdk/testing.ts +179 -0
  87. package/src/utils/version.ts +28 -0
@@ -0,0 +1,186 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ UPTIME_INTERVAL_MAX,
4
+ UPTIME_INTERVAL_MIN,
5
+ UPTIME_KEYWORD_MAX_LENGTH,
6
+ UPTIME_NAME_MAX_LENGTH,
7
+ UPTIME_THRESHOLD_MAX,
8
+ UPTIME_TIMEOUT_MAX,
9
+ UPTIME_TIMEOUT_MIN,
10
+ UPTIME_URL_MAX_LENGTH,
11
+ uptimeCheckSchema,
12
+ uptimeCheckStatsSchema,
13
+ uptimeIncidentSchema,
14
+ uptimeMethodSchema,
15
+ uptimePointSchema,
16
+ uptimeRangeSchema,
17
+ uptimeResolutionSchema,
18
+ uptimeRetentionSchema,
19
+ uptimeServiceSchema
20
+ } from '../domain/uptime';
21
+
22
+ const serviceId = z.number().int().positive();
23
+
24
+ /** Everything the user may set on a service. */
25
+ const uptimeDraftSchema = z.object({
26
+ name: z.string().min(1).max(UPTIME_NAME_MAX_LENGTH),
27
+ url: z.string().url().max(UPTIME_URL_MAX_LENGTH),
28
+ method: uptimeMethodSchema,
29
+ expectedStatus: z.number().int().min(100).max(599).nullable(),
30
+ keyword: z.string().max(UPTIME_KEYWORD_MAX_LENGTH).nullable(),
31
+ intervalSeconds: z.number().int().min(UPTIME_INTERVAL_MIN).max(UPTIME_INTERVAL_MAX),
32
+ timeoutSeconds: z.number().int().min(UPTIME_TIMEOUT_MIN).max(UPTIME_TIMEOUT_MAX),
33
+ failureThreshold: z.number().int().min(1).max(UPTIME_THRESHOLD_MAX),
34
+ retentionDays: uptimeRetentionSchema,
35
+ enabled: z.boolean()
36
+ });
37
+
38
+ /**
39
+ * List the workspace's services in the user's own order, each carrying its live
40
+ * state and its 24 h / 7 d / 30 d ratios. Never gated: uptime data lives in the
41
+ * open tier, so the feature opens with no password prompt.
42
+ */
43
+ export const uptimeList = {
44
+ command: 'uptime.list' as const,
45
+ input: z.object({}),
46
+ output: z.object({ services: z.array(uptimeServiceSchema) })
47
+ };
48
+
49
+ /**
50
+ * "N services up out of M" — the only thing the home card and the navbar widget
51
+ * need. Pure clear metadata, so it costs one indexed count. Paused services are
52
+ * excluded entirely, and `up + down` may be **below** `total`: one awaiting its
53
+ * first probe is neither, and must not be reported as a failure.
54
+ */
55
+ export const uptimeCount = {
56
+ command: 'uptime.count' as const,
57
+ input: z.object({}),
58
+ output: z.object({
59
+ total: z.number().int().nonnegative(),
60
+ up: z.number().int().nonnegative(),
61
+ down: z.number().int().nonnegative()
62
+ })
63
+ };
64
+
65
+ export const uptimeAdd = {
66
+ command: 'uptime.add' as const,
67
+ input: z.object({ service: uptimeDraftSchema }),
68
+ output: z.object({ service: uptimeServiceSchema })
69
+ };
70
+
71
+ /** Replace a service's whole configuration. History and incidents are kept. */
72
+ export const uptimeUpdate = {
73
+ command: 'uptime.update' as const,
74
+ input: z.object({ id: serviceId, service: uptimeDraftSchema }),
75
+ output: z.object({ service: uptimeServiceSchema })
76
+ };
77
+
78
+ /**
79
+ * Pause or resume probing without touching the rest of the configuration — a
80
+ * paused service keeps its history and simply stops being scheduled.
81
+ */
82
+ export const uptimeSetEnabled = {
83
+ command: 'uptime.setEnabled' as const,
84
+ input: z.object({ id: serviceId, enabled: z.boolean() }),
85
+ output: z.object({ service: uptimeServiceSchema })
86
+ };
87
+
88
+ /** Destroy a service **and its whole history** — there is no archive here. */
89
+ export const uptimeRemove = {
90
+ command: 'uptime.remove' as const,
91
+ input: z.object({ id: serviceId }),
92
+ output: z.object({ id: serviceId })
93
+ };
94
+
95
+ /**
96
+ * Lay out the workspace's services: `ids` is the **complete** list in its final
97
+ * order (lower index first). Nothing else positions a service — new ones are
98
+ * appended — so the order is entirely the user's, as it is for notes. Touches no
99
+ * probe state, so reordering never disturbs monitoring.
100
+ */
101
+ export const uptimeReorder = {
102
+ command: 'uptime.reorder' as const,
103
+ input: z.object({ ids: z.array(serviceId).min(1) }),
104
+ output: z.object({ ids: z.array(serviceId) })
105
+ };
106
+
107
+ /** Probe a service right now instead of waiting for its next tick. */
108
+ export const uptimeCheckNow = {
109
+ command: 'uptime.checkNow' as const,
110
+ input: z.object({ id: serviceId }),
111
+ output: z.object({ service: uptimeServiceSchema })
112
+ };
113
+
114
+ /**
115
+ * Chart series over a window. The server picks the bucket size from the range
116
+ * (`raw` up to 24 h, hourly up to 30 d, daily beyond) and reads the long ranges
117
+ * from the daily rollup, which is never pruned.
118
+ */
119
+ export const uptimeHistory = {
120
+ command: 'uptime.history' as const,
121
+ input: z.object({ id: serviceId, range: uptimeRangeSchema }),
122
+ output: z.object({
123
+ resolution: uptimeResolutionSchema,
124
+ points: z.array(uptimePointSchema)
125
+ })
126
+ };
127
+
128
+ /**
129
+ * Which pings a journal query is about. Shared by {@link uptimeChecks} and
130
+ * {@link uptimeCheckStats} so a page and its aggregates always describe the
131
+ * exact same selection.
132
+ */
133
+ const checkFilterSchema = z.object({
134
+ /** Keep only probes at or after this epoch second; `null` = the whole history. */
135
+ since: z.number().int().nonnegative().nullable(),
136
+ /** Restrict to failed probes. */
137
+ failuresOnly: z.boolean()
138
+ });
139
+
140
+ /**
141
+ * Raw ping journal, most recent first. `before` pages backwards through time
142
+ * (exclusive upper bound, epoch seconds).
143
+ */
144
+ export const uptimeChecks = {
145
+ command: 'uptime.checks' as const,
146
+ input: z.object({
147
+ id: serviceId,
148
+ limit: z.number().int().min(1).max(200),
149
+ before: z.number().int().nonnegative().optional(),
150
+ filter: checkFilterSchema
151
+ }),
152
+ output: z.object({ checks: z.array(uptimeCheckSchema) })
153
+ };
154
+
155
+ /**
156
+ * Aggregates over the same selection {@link uptimeChecks} pages through. Kept a
157
+ * separate command on purpose: it scans the whole filtered range, so the short
158
+ * "last few measures" list on the detail view never pays for it.
159
+ */
160
+ export const uptimeCheckStats = {
161
+ command: 'uptime.checkStats' as const,
162
+ input: z.object({ id: serviceId, filter: checkFilterSchema }),
163
+ output: z.object({ stats: uptimeCheckStatsSchema })
164
+ };
165
+
166
+ /** Outage history, most recent first; the ongoing one (if any) comes back too. */
167
+ export const uptimeIncidents = {
168
+ command: 'uptime.incidents' as const,
169
+ input: z.object({ id: serviceId, limit: z.number().int().min(1).max(100) }),
170
+ output: z.object({ incidents: z.array(uptimeIncidentSchema) })
171
+ };
172
+
173
+ export const uptimeCommands = [
174
+ uptimeList,
175
+ uptimeCount,
176
+ uptimeAdd,
177
+ uptimeUpdate,
178
+ uptimeSetEnabled,
179
+ uptimeRemove,
180
+ uptimeReorder,
181
+ uptimeCheckNow,
182
+ uptimeHistory,
183
+ uptimeChecks,
184
+ uptimeCheckStats,
185
+ uptimeIncidents
186
+ ] as const;
@@ -0,0 +1,91 @@
1
+ import { z } from 'zod';
2
+ import { userColorSchema, userSettingFlagSchema } from '../domain/user';
3
+
4
+ /**
5
+ * Upper bound on the avatar data URL length (characters ≈ bytes for base64
6
+ * ASCII). ~1.5 MB comfortably fits a client-resized JPEG/PNG/WebP thumbnail
7
+ * while bounding the WS payload and the `users.avatar` column.
8
+ */
9
+ export const AVATAR_MAX_LENGTH = 1_500_000;
10
+
11
+ /** Accepted image data URL prefixes for an uploaded avatar. */
12
+ const avatarDataUrl = z
13
+ .string()
14
+ .min(1)
15
+ .max(AVATAR_MAX_LENGTH)
16
+ .regex(
17
+ /^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/]+=*$/,
18
+ 'Avatar must be a base64 image data URL'
19
+ );
20
+
21
+ export const userSetAvatar = {
22
+ command: 'user.setAvatar' as const,
23
+ input: z.object({ avatar: avatarDataUrl }),
24
+ output: z.object({ avatar: z.string() })
25
+ };
26
+
27
+ /** Max length of a wallpaper data URL stored in the theme (~4 MB in base64). */
28
+ export const THEME_IMAGE_MAX_LENGTH = 4_200_000;
29
+
30
+ /** Number of saved background slots kept in the appearance gallery. */
31
+ export const THEME_SLOT_COUNT = 5;
32
+
33
+ /**
34
+ * Upper bound on one saved slot value. Slots hold *compressed* data URLs (the
35
+ * client downscales before saving) or a short raw URL, so this is a safety cap,
36
+ * not the typical size — it keeps the whole theme JSON (5 slots + active image)
37
+ * within the browser's localStorage budget and the `users.theme` column.
38
+ */
39
+ export const THEME_SLOT_IMAGE_MAX_LENGTH = 1_500_000;
40
+
41
+ export const themeStateSchema = z.object({
42
+ accent: z.string().nullable(),
43
+ bgPreset: z.string().nullable(),
44
+ bgImage: z.string().max(THEME_IMAGE_MAX_LENGTH).nullable(),
45
+ /**
46
+ * Saved background gallery: up to THEME_SLOT_COUNT slots, each a compressed
47
+ * data URL / raw URL, or null for an empty slot. Defaults to empty so themes
48
+ * saved before this field parse cleanly.
49
+ */
50
+ bgImages: z
51
+ .array(z.string().max(THEME_SLOT_IMAGE_MAX_LENGTH).nullable())
52
+ .max(THEME_SLOT_COUNT)
53
+ .default([]),
54
+ bgDim: z.number().int().min(0).max(100),
55
+ bgBlur: z.number().int().min(0).max(100)
56
+ });
57
+
58
+ export type ThemeStateDTO = z.infer<typeof themeStateSchema>;
59
+
60
+ export const userSetTheme = {
61
+ command: 'user.setTheme' as const,
62
+ input: themeStateSchema,
63
+ output: z.object({ ok: z.literal(true) })
64
+ };
65
+
66
+ /**
67
+ * Couleur d'identité du compte. En `scope: 'account'` côté serveur : elle suit
68
+ * le compte, pas l'espace depuis lequel on la change.
69
+ */
70
+ export const userSetColor = {
71
+ command: 'user.setColor' as const,
72
+ input: z.object({ color: userColorSchema }),
73
+ output: z.object({ color: userColorSchema })
74
+ };
75
+
76
+ /**
77
+ * Pose ou retire un drapeau de compte. Une commande unique pour tous les
78
+ * drapeaux plutôt qu'une par réglage : le serveur n'a alors qu'un seul chemin de
79
+ * lecture-modification-écriture sur `users.settings`, et un nouveau drapeau ne
80
+ * coûte qu'une entrée dans `userSettingFlagSchema`.
81
+ *
82
+ * La sortie renvoie le sac **complet** tel qu'il vient d'être écrit : le client
83
+ * peut s'aligner dessus au lieu de rejouer sa propre arithmétique.
84
+ */
85
+ export const userSetSetting = {
86
+ command: 'user.setSetting' as const,
87
+ input: z.object({ flag: userSettingFlagSchema, enabled: z.boolean() }),
88
+ output: z.object({ settings: z.array(z.string()) })
89
+ };
90
+
91
+ export const userCommands = [userSetAvatar, userSetTheme, userSetColor, userSetSetting] as const;
@@ -0,0 +1,200 @@
1
+ import { z } from 'zod';
2
+ import { homeLayoutSchema } from '../domain/home';
3
+ import { workspaceSchema } from '../domain/workspace';
4
+ import {
5
+ WORKSPACE_ROLE_NAME_MAX,
6
+ workspaceCapabilitySchema,
7
+ workspaceFeatureGrantSchema,
8
+ workspacePermissionsSchema,
9
+ workspaceRoleSchema
10
+ } from '../domain/workspaceRole';
11
+ import { themeStateSchema } from './user';
12
+
13
+ export const workspaceAdd = {
14
+ command: 'workspace.add' as const,
15
+ input: z.object({ name: z.string().min(1).max(120) }),
16
+ output: z.object({ workspace: workspaceSchema })
17
+ };
18
+
19
+ export const workspaceDelete = {
20
+ command: 'workspace.delete' as const,
21
+ input: z.object({ workspaceId: z.number().int().positive() }),
22
+ output: z.object({ workspaceId: z.number().int().positive() })
23
+ };
24
+
25
+ /**
26
+ * Bascule vers un espace : renvoie son apparence et sa disposition d'accueil.
27
+ *
28
+ * L'espace visé n'apparaît pas dans l'entrée — il voyage sur l'enveloppe, et le
29
+ * dispatcheur a déjà vérifié l'appartenance avant d'appeler le handler. Passer
30
+ * par `/api/auth/me` ne conviendrait pas : cette route recalcule l'espace actif à
31
+ * partir du favori et renverrait l'apparence de l'espace qu'*elle* choisit, pas
32
+ * de celui vers lequel on bascule.
33
+ */
34
+ export const workspaceActivate = {
35
+ command: 'workspace.activate' as const,
36
+ input: z.object({}),
37
+ output: z.object({
38
+ theme: themeStateSchema.nullable(),
39
+ homeLayout: homeLayoutSchema.nullable(),
40
+ /** Droits dans l'espace rejoint : l'UI s'y conforme dès la bascule. */
41
+ permissions: workspacePermissionsSchema
42
+ })
43
+ };
44
+
45
+ /**
46
+ * Définit (ou efface) l'espace favori du compte : celui chargé en premier à la
47
+ * connexion. `null` → l'espace personnel.
48
+ */
49
+ export const workspaceSetFavorite = {
50
+ command: 'workspace.setFavorite' as const,
51
+ input: z.object({ workspaceId: z.number().int().positive().nullable() }),
52
+ output: z.object({ workspaceId: z.number().int().positive().nullable() })
53
+ };
54
+
55
+ /** Renomme l'espace actif. Réservé à son propriétaire. */
56
+ export const workspaceRename = {
57
+ command: 'workspace.rename' as const,
58
+ input: z.object({ name: z.string().min(1).max(120) }),
59
+ output: z.object({ workspace: workspaceSchema })
60
+ };
61
+
62
+ /**
63
+ * Quitte l'espace actif. Le propriétaire ne le peut pas : il supprime son espace
64
+ * ou le transmet, il ne l'abandonne pas avec ses membres dedans.
65
+ */
66
+ export const workspaceLeave = {
67
+ command: 'workspace.leave' as const,
68
+ input: z.object({}),
69
+ output: z.object({ workspaceId: z.number().int().positive() })
70
+ };
71
+
72
+ /** Exclut un membre de l'espace actif. Réservé au propriétaire. */
73
+ export const workspaceRemoveMember = {
74
+ command: 'workspace.removeMember' as const,
75
+ input: z.object({ userId: z.number().int().positive() }),
76
+ output: z.object({ userId: z.number().int().positive() })
77
+ };
78
+
79
+ /**
80
+ * Ajoute un membre à l'espace actif, désigné par son adresse.
81
+ *
82
+ * Pas de lien, pas d'acceptation : le compte existe déjà (l'inscription est sur
83
+ * invitation d'un administrateur), donc le désigner suffit. Un lien serait un
84
+ * détour — et un secret transmissible de plus à surveiller.
85
+ */
86
+ export const workspaceAddMember = {
87
+ command: 'workspace.addMember' as const,
88
+ input: z.object({ email: z.string().email() }),
89
+ output: z.object({ workspace: workspaceSchema })
90
+ };
91
+
92
+ /**
93
+ * État de la clé d'espace : déjà active, applicable, et ce qui l'empêcherait.
94
+ */
95
+ export const workspaceSharedKeyStatus = {
96
+ command: 'workspace.sharedKeyStatus' as const,
97
+ input: z.object({}),
98
+ output: z.object({
99
+ /** L'espace utilise déjà sa propre clé. */
100
+ enabled: z.boolean(),
101
+ /** Faux pour un espace personnel, qui garde la clé de son propriétaire. */
102
+ applicable: z.boolean(),
103
+ /** Obstacles à lever avant conversion, rédigés pour l'utilisateur. */
104
+ blockers: z.array(z.string())
105
+ })
106
+ };
107
+
108
+ /**
109
+ * Convertit l'espace vers sa propre clé. Exige le propriétaire, session
110
+ * déverrouillée : le contenu existant doit être relu avec sa clé avant d'être
111
+ * réécrit sous celle de l'espace.
112
+ */
113
+ export const workspaceEnableSharedKey = {
114
+ command: 'workspace.enableSharedKey' as const,
115
+ input: z.object({}),
116
+ output: z.object({ converted: z.number().int().nonnegative() })
117
+ };
118
+
119
+ /** Le contenu modifiable d'un rôle. */
120
+ const roleDraftSchema = z.object({
121
+ name: z.string().min(1).max(WORKSPACE_ROLE_NAME_MAX),
122
+ color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
123
+ capabilities: z.array(workspaceCapabilitySchema),
124
+ features: z.array(workspaceFeatureGrantSchema)
125
+ });
126
+
127
+ export const workspaceRoleList = {
128
+ command: 'workspace.roleList' as const,
129
+ input: z.object({}),
130
+ output: z.object({
131
+ roles: z.array(workspaceRoleSchema),
132
+ /** Quel membre porte quel rôle. `null` = aucun, donc aucun droit. */
133
+ memberRoles: z.array(
134
+ z.object({
135
+ userId: z.number().int().positive(),
136
+ roleId: z.number().int().positive().nullable()
137
+ })
138
+ ),
139
+ /** Droits effectifs de l'appelant, pour que l'UI n'affiche que l'accessible. */
140
+ permissions: workspacePermissionsSchema
141
+ })
142
+ };
143
+
144
+ export const workspaceRoleCreate = {
145
+ command: 'workspace.roleCreate' as const,
146
+ input: roleDraftSchema,
147
+ output: z.object({ role: workspaceRoleSchema })
148
+ };
149
+
150
+ export const workspaceRoleUpdate = {
151
+ command: 'workspace.roleUpdate' as const,
152
+ input: roleDraftSchema.extend({ roleId: z.number().int().positive() }),
153
+ output: z.object({ role: workspaceRoleSchema })
154
+ };
155
+
156
+ /** Refusé tant que des membres le portent : on ne révoque personne par surprise. */
157
+ export const workspaceRoleDelete = {
158
+ command: 'workspace.roleDelete' as const,
159
+ input: z.object({ roleId: z.number().int().positive() }),
160
+ output: z.object({ roleId: z.number().int().positive() })
161
+ };
162
+
163
+ /** Désigne le rôle attribué d'office à qui rejoint l'espace. */
164
+ export const workspaceRoleSetDefault = {
165
+ command: 'workspace.roleSetDefault' as const,
166
+ input: z.object({ roleId: z.number().int().positive() }),
167
+ output: z.object({ roleId: z.number().int().positive() })
168
+ };
169
+
170
+ /** Attribue un rôle à un membre. `null` le laisse sans aucun droit. */
171
+ export const workspaceAssignRole = {
172
+ command: 'workspace.assignRole' as const,
173
+ input: z.object({
174
+ userId: z.number().int().positive(),
175
+ roleId: z.number().int().positive().nullable()
176
+ }),
177
+ output: z.object({
178
+ userId: z.number().int().positive(),
179
+ roleId: z.number().int().positive().nullable()
180
+ })
181
+ };
182
+
183
+ export const workspaceCommands = [
184
+ workspaceRoleList,
185
+ workspaceRoleCreate,
186
+ workspaceRoleUpdate,
187
+ workspaceRoleDelete,
188
+ workspaceRoleSetDefault,
189
+ workspaceAssignRole,
190
+ workspaceSharedKeyStatus,
191
+ workspaceEnableSharedKey,
192
+ workspaceRename,
193
+ workspaceLeave,
194
+ workspaceRemoveMember,
195
+ workspaceAddMember,
196
+ workspaceAdd,
197
+ workspaceDelete,
198
+ workspaceActivate,
199
+ workspaceSetFavorite
200
+ ] as const;
@@ -0,0 +1,94 @@
1
+ import { z } from 'zod';
2
+ import { homeLayoutSchema } from '../domain/home';
3
+ import { userSchema } from '../domain/user';
4
+ import { workspaceSchema } from '../domain/workspace';
5
+ import { workspacePermissionsSchema } from '../domain/workspaceRole';
6
+ import { themeStateSchema } from '../features/user';
7
+
8
+ /**
9
+ * Contenu commun à `/login`, `/refresh` et `/me` : le compte, les espaces
10
+ * auxquels il a accès, et **l'espace actif seul** — son thème et sa disposition
11
+ * d'accueil.
12
+ *
13
+ * Le thème des autres espaces n'est délibérément pas embarqué : `bgImages` peut
14
+ * contenir plusieurs data URLs, et les livrer tous multiplierait la charge utile
15
+ * par le nombre d'espaces. Basculer d'espace va chercher les siens.
16
+ */
17
+ export const sessionBundleSchema = z.object({
18
+ user: userSchema,
19
+ workspaces: z.array(workspaceSchema),
20
+ /** Espace chargé à l'ouverture : le favori s'il est encore accessible, sinon le personnel. */
21
+ activeWorkspaceId: z.number().int().positive(),
22
+ theme: themeStateSchema.nullable(),
23
+ homeLayout: homeLayoutSchema.nullable(),
24
+ /** Droits de l'appelant dans l'espace actif, pour que l'UI masque le reste. */
25
+ permissions: workspacePermissionsSchema
26
+ });
27
+
28
+ export type SessionBundle = z.infer<typeof sessionBundleSchema>;
29
+
30
+ export const loginRequestSchema = z.object({
31
+ username: z.string().min(1).max(120),
32
+ password: z.string().min(1).max(512)
33
+ });
34
+
35
+ export type LoginRequest = z.infer<typeof loginRequestSchema>;
36
+
37
+ /**
38
+ * L'inscription n'est plus libre : elle exige un jeton d'invitation émis par un
39
+ * administrateur. La route existait déjà, ouverte à tous et connectant
40
+ * automatiquement — simplement inatteignable depuis l'interface, ce qui n'est
41
+ * pas une protection.
42
+ */
43
+ export const registerRequestSchema = z.object({
44
+ inviteToken: z.string().min(1),
45
+ username: z
46
+ .string()
47
+ .min(3)
48
+ .max(64)
49
+ .regex(/^[a-zA-Z0-9_.-]+$/, 'username may only contain letters, digits, _ . -'),
50
+ email: z.string().email().max(320),
51
+ password: z.string().min(8).max(512)
52
+ });
53
+
54
+ export type RegisterRequest = z.infer<typeof registerRequestSchema>;
55
+
56
+ export const changePasswordRequestSchema = z.object({
57
+ currentPassword: z.string().min(1).max(512),
58
+ newPassword: z.string().min(8).max(512)
59
+ });
60
+
61
+ export type ChangePasswordRequest = z.infer<typeof changePasswordRequestSchema>;
62
+
63
+ export const changePasswordResponseSchema = z.object({ changed: z.literal(true) });
64
+
65
+ export type ChangePasswordResponse = z.infer<typeof changePasswordResponseSchema>;
66
+
67
+ /**
68
+ * The auth flow returns the authenticated user and its workspaces in one shot.
69
+ * Tokens are delivered as HttpOnly cookies, never in the JSON body.
70
+ *
71
+ * When 2FA is enabled, login first replies with `twoFactorRequired` and an
72
+ * interim challenge token (cookie); the client then posts a TOTP/backup code.
73
+ */
74
+ export const loginResponseSchema = z.discriminatedUnion('twoFactorRequired', [
75
+ sessionBundleSchema.extend({ twoFactorRequired: z.literal(false) }),
76
+ z.object({ twoFactorRequired: z.literal(true) })
77
+ ]);
78
+
79
+ export type LoginResponse = z.infer<typeof loginResponseSchema>;
80
+
81
+ export const twoFactorChallengeRequestSchema = z.object({
82
+ /** A 6-digit TOTP code or a recovery backup code. */
83
+ code: z.string().min(6).max(16)
84
+ });
85
+
86
+ export type TwoFactorChallengeRequest = z.infer<typeof twoFactorChallengeRequestSchema>;
87
+
88
+ export const refreshResponseSchema = sessionBundleSchema;
89
+
90
+ export type RefreshResponse = z.infer<typeof refreshResponseSchema>;
91
+
92
+ export const meResponseSchema = sessionBundleSchema;
93
+
94
+ export type MeResponse = z.infer<typeof meResponseSchema>;