@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,275 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ AUDIENCE_BREAKDOWN_MAX,
4
+ AUDIENCE_FUNNEL_MAX_STEPS,
5
+ AUDIENCE_FUNNEL_NAME_MAX_LENGTH,
6
+ AUDIENCE_MAX_ORIGINS,
7
+ AUDIENCE_ORIGIN_MAX_LENGTH,
8
+ AUDIENCE_RETENTION_MAX_DAYS,
9
+ AUDIENCE_RETENTION_MIN_DAYS,
10
+ AUDIENCE_SITE_DESCRIPTION_MAX_LENGTH,
11
+ AUDIENCE_SITE_NAME_MAX_LENGTH,
12
+ audienceActivitySchema,
13
+ audienceBreakdownItemSchema,
14
+ audienceDimensionSchema,
15
+ audienceFunnelSchema,
16
+ audienceFunnelStepDraftSchema,
17
+ audienceLiveSchema,
18
+ audienceOverviewSchema,
19
+ audiencePlatformSchema,
20
+ audienceRangeSchema,
21
+ audienceVisitorModeSchema,
22
+ audienceSiteSchema,
23
+ audienceUsageSchema
24
+ } from '../domain/audience';
25
+
26
+ /**
27
+ * Commandes de l'audience d'un espace.
28
+ *
29
+ * Préfixe unique `audience.`, comme `git.`, `database.` et `project.` — d'où le
30
+ * camelCase derrière le point.
31
+ *
32
+ * ⚠️ Conséquence à connaître : le filet de démarrage (`MUTATION_VERB` dans
33
+ * `src/features/_topics.ts`) cherche un verbe **juste après le point**. Il ne
34
+ * verra donc **aucune** de ces commandes, et un `mutates` oublié ne produira
35
+ * aucun avertissement. Il se relit à la main.
36
+ *
37
+ * ## Ce qui n'est pas ici
38
+ *
39
+ * L'**ingestion** n'est pas une commande. Elle entre par HTTP, sans session,
40
+ * depuis des machines qui ne connaissent pas DevEye — voir `POST /api/t/b`. Ces
41
+ * commandes-ci ne font que *lire* ce qu'elle a écrit, et *déclarer* les sites.
42
+ *
43
+ * L'espace visé n'apparaît dans aucune entrée : il voyage sur l'enveloppe WS et
44
+ * le dispatcheur le résout, appartenance vérifiée, avant le handler.
45
+ */
46
+
47
+ const siteId = z.number().int().positive();
48
+
49
+ /** Ce qu'on peut régler sur un site — identique à l'ajout et à la modification. */
50
+ const siteBody = {
51
+ name: z.string().min(1).max(AUDIENCE_SITE_NAME_MAX_LENGTH),
52
+ description: z.string().max(AUDIENCE_SITE_DESCRIPTION_MAX_LENGTH),
53
+ platform: audiencePlatformSchema,
54
+ visitorMode: audienceVisitorModeSchema,
55
+ /**
56
+ * Les hôtes autorisés à écrire. Le client envoie des hôtes nus
57
+ * (`exemple.fr`) ou des origines complètes (`https://exemple.fr`) : le
58
+ * serveur normalise, parce que c'est lui qui compare.
59
+ */
60
+ origins: z.array(z.string().max(AUDIENCE_ORIGIN_MAX_LENGTH)).max(AUDIENCE_MAX_ORIGINS),
61
+ active: z.boolean(),
62
+ retentionDays: z
63
+ .number()
64
+ .int()
65
+ .min(AUDIENCE_RETENTION_MIN_DAYS)
66
+ .max(AUDIENCE_RETENTION_MAX_DAYS)
67
+ };
68
+
69
+ // ------------------------------------------------------------------ sites
70
+
71
+ /** Le nombre de sites de l'espace, pour la tuile de l'accueil. */
72
+ export const audienceCount = {
73
+ command: 'audience.count' as const,
74
+ input: z.object({}),
75
+ output: z.object({ count: z.number().int().nonnegative() })
76
+ };
77
+
78
+ /** Les sites de l'espace, dans l'ordre de l'utilisateur. */
79
+ export const audienceList = {
80
+ command: 'audience.list' as const,
81
+ input: z.object({}),
82
+ output: z.object({ sites: z.array(audienceSiteSchema) })
83
+ };
84
+
85
+ /**
86
+ * Un site, avec les projets qui le suivent et l'adresse de sa balise.
87
+ *
88
+ * `ingestOrigin` vient du serveur (`AUDIENCE_ORIGIN`, à défaut `PUBLIC_ORIGIN`)
89
+ * et non de l'origine du navigateur : l'application est derrière le VPN, alors
90
+ * que l'ingestion doit être joignable sans lui — les deux adresses diffèrent
91
+ * donc par construction. La déduire côté client aurait donné une balise juste en
92
+ * développement et fausse en production, ce qui est le pire des deux mondes.
93
+ */
94
+ export const audienceGet = {
95
+ command: 'audience.get' as const,
96
+ input: z.object({ siteId }),
97
+ output: z.object({
98
+ site: audienceSiteSchema,
99
+ usage: z.array(audienceUsageSchema),
100
+ ingestOrigin: z.string()
101
+ })
102
+ };
103
+
104
+ /**
105
+ * Déclare un site. La clé publique est **engendrée par le serveur** : la laisser
106
+ * choisir permettrait de viser celle d'un site existant d'un autre espace.
107
+ */
108
+ export const audienceSiteAdd = {
109
+ command: 'audience.siteAdd' as const,
110
+ input: z.object(siteBody),
111
+ output: z.object({ site: audienceSiteSchema })
112
+ };
113
+
114
+ export const audienceSiteUpdate = {
115
+ command: 'audience.siteUpdate' as const,
116
+ input: z.object({ siteId, ...siteBody }),
117
+ output: z.object({ site: audienceSiteSchema })
118
+ };
119
+
120
+ /**
121
+ * Supprime un site **et tout son historique**.
122
+ *
123
+ * Contrairement aux projets, rien ne s'archive ici : un site retiré n'a pas de
124
+ * seconde vie, et garder des millions d'événements orphelins pour un objet que
125
+ * plus rien ne nomme ne rendrait service à personne. Les liaisons de projet
126
+ * tombent avec (`CASCADE`), les projets eux-mêmes ne bougent pas.
127
+ */
128
+ export const audienceSiteRemove = {
129
+ command: 'audience.siteRemove' as const,
130
+ input: z.object({ siteId }),
131
+ output: z.object({ ok: z.literal(true) })
132
+ };
133
+
134
+ /**
135
+ * Renouvelle la clé publique.
136
+ *
137
+ * Le geste utile quand une clé s'est retrouvée là où elle n'aurait pas dû —
138
+ * dépôt public, capture d'écran. L'ancienne cesse d'entrer **immédiatement** ;
139
+ * l'historique déjà collecté, lui, reste : il a été mesuré, il est vrai.
140
+ */
141
+ export const audienceSiteRotateKey = {
142
+ command: 'audience.siteRotateKey' as const,
143
+ input: z.object({ siteId }),
144
+ output: z.object({ site: audienceSiteSchema })
145
+ };
146
+
147
+ export const audienceReorder = {
148
+ command: 'audience.reorder' as const,
149
+ input: z.object({ siteIds: z.array(siteId).min(1) }),
150
+ output: z.object({ ok: z.literal(true) })
151
+ };
152
+
153
+ // ------------------------------------------------------------ statistiques
154
+
155
+ /** Le bandeau, la comparaison à la période précédente, et la courbe. */
156
+ export const audienceOverview = {
157
+ command: 'audience.overview' as const,
158
+ input: z.object({ siteId, range: audienceRangeSchema }),
159
+ output: audienceOverviewSchema
160
+ };
161
+
162
+ /**
163
+ * Un classement sur l'axe demandé.
164
+ *
165
+ * Une commande pour les neuf axes plutôt que neuf commandes : ils rendent tous
166
+ * la même forme, se lisent par la même requête, et l'écran en affiche plusieurs
167
+ * côte à côte. Neuf entrées auraient divergé au premier ajustement.
168
+ */
169
+ export const audienceBreakdown = {
170
+ command: 'audience.breakdown' as const,
171
+ input: z.object({
172
+ siteId,
173
+ range: audienceRangeSchema,
174
+ dimension: audienceDimensionSchema,
175
+ limit: z.number().int().min(1).max(AUDIENCE_BREAKDOWN_MAX).optional()
176
+ }),
177
+ output: z.object({ items: z.array(audienceBreakdownItemSchema) })
178
+ };
179
+
180
+ /** La carte jour × heure, en heure **locale du visiteur**, et les fuseaux. */
181
+ export const audienceActivity = {
182
+ command: 'audience.activity' as const,
183
+ input: z.object({ siteId, range: audienceRangeSchema }),
184
+ output: audienceActivitySchema
185
+ };
186
+
187
+ /**
188
+ * Qui est là en ce moment (cinq dernières minutes).
189
+ *
190
+ * Volontairement séparée de `overview` : elle est minuscule, elle se relit
191
+ * souvent, et la faire voyager avec le bandeau obligerait à recalculer tout
192
+ * l'agrégat pour rafraîchir un compteur.
193
+ */
194
+ export const audienceLive = {
195
+ command: 'audience.live' as const,
196
+ input: z.object({ siteId }),
197
+ output: audienceLiveSchema
198
+ };
199
+
200
+ // ------------------------------------------------------------ entonnoirs
201
+
202
+ const funnelId = z.number().int().positive();
203
+
204
+ /** Les marches, dans l'ordre. Un entonnoir d'une seule marche n'en est pas un. */
205
+ const steps = z.array(audienceFunnelStepDraftSchema).min(2).max(AUDIENCE_FUNNEL_MAX_STEPS);
206
+
207
+ /**
208
+ * Les entonnoirs d'un site, **avec leurs chiffres** sur la fenêtre demandée.
209
+ *
210
+ * Définitions et mesures dans la même réponse : l'écran n'affiche jamais l'une
211
+ * sans l'autre, et les séparer aurait fait deux allers-retours pour dessiner un
212
+ * seul graphique. Les entonnoirs se comptent en unités, pas en milliers.
213
+ */
214
+ export const audienceFunnelList = {
215
+ command: 'audience.funnelList' as const,
216
+ input: z.object({ siteId, range: audienceRangeSchema }),
217
+ output: z.object({ funnels: z.array(audienceFunnelSchema) })
218
+ };
219
+
220
+ /**
221
+ * Définit un entonnoir à partir de ce que le site a **déjà** émis.
222
+ *
223
+ * C'est tout le découpage : le site pose des signaux nommés, l'entonnoir se
224
+ * compose ici. Mesurer autre chose ne demande donc aucun redéploiement — ni
225
+ * même de prévenir qui que ce soit.
226
+ */
227
+ export const audienceFunnelAdd = {
228
+ command: 'audience.funnelAdd' as const,
229
+ input: z.object({
230
+ siteId,
231
+ name: z.string().min(1).max(AUDIENCE_FUNNEL_NAME_MAX_LENGTH),
232
+ steps
233
+ }),
234
+ output: z.object({ funnelId })
235
+ };
236
+
237
+ export const audienceFunnelUpdate = {
238
+ command: 'audience.funnelUpdate' as const,
239
+ input: z.object({
240
+ funnelId,
241
+ name: z.string().min(1).max(AUDIENCE_FUNNEL_NAME_MAX_LENGTH),
242
+ steps
243
+ }),
244
+ output: z.object({ ok: z.literal(true) })
245
+ };
246
+
247
+ /**
248
+ * Supprime un entonnoir. **Aucune mesure n'est perdue** : un entonnoir n'est
249
+ * qu'une lecture des événements déjà là, jamais une collecte à part. Le
250
+ * recréer à l'identique rendrait exactement les mêmes chiffres.
251
+ */
252
+ export const audienceFunnelRemove = {
253
+ command: 'audience.funnelRemove' as const,
254
+ input: z.object({ funnelId }),
255
+ output: z.object({ ok: z.literal(true) })
256
+ };
257
+
258
+ export const audienceCommands = [
259
+ audienceCount,
260
+ audienceList,
261
+ audienceGet,
262
+ audienceSiteAdd,
263
+ audienceSiteUpdate,
264
+ audienceSiteRemove,
265
+ audienceSiteRotateKey,
266
+ audienceReorder,
267
+ audienceOverview,
268
+ audienceBreakdown,
269
+ audienceActivity,
270
+ audienceLive,
271
+ audienceFunnelList,
272
+ audienceFunnelAdd,
273
+ audienceFunnelUpdate,
274
+ audienceFunnelRemove
275
+ ] as const;
@@ -0,0 +1,230 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ BACKUP_ACCESS_KEY_MAX,
4
+ BACKUP_BUCKET_MAX,
5
+ BACKUP_DESTINATION_NAME_MAX,
6
+ BACKUP_ENDPOINT_MAX,
7
+ BACKUP_JOB_NAME_MAX,
8
+ BACKUP_PATH_MAX,
9
+ BACKUP_SECRET_MAX,
10
+ backupDestinationKindSchema,
11
+ backupDestinationProbeSchema,
12
+ backupDestinationSchema,
13
+ backupEncryptionSchema,
14
+ backupJobSchema,
15
+ backupRunSchema,
16
+ backupScheduleKindSchema,
17
+ backupSourceCandidateSchema,
18
+ backupSourceKindSchema
19
+ } from '../domain/backup';
20
+
21
+ /**
22
+ * Commandes des sauvegardes.
23
+ *
24
+ * Deux moitiés qui ne se recouvrent pas: les **destinations** (où) et les
25
+ * **travaux** (quoi, quand, combien de copies). Tout est à l'étage ouvert, donc
26
+ * aucune de ces commandes ne demande de session déverrouillée — c'est la
27
+ * condition pour qu'une sauvegarde parte à 3 h du matin.
28
+ *
29
+ * `backup.deveyeDump` n'existe pas comme commande: la base de DevEye se
30
+ * sauvegarde par un travail de source `deveye` comme les autres, et il n'y a
31
+ * aucune raison de lui inventer un chemin à part.
32
+ */
33
+
34
+ const destinationId = z.number().int().positive();
35
+ const jobId = z.number().int().positive();
36
+
37
+ // ---------------------------------------------------------- destinations
38
+
39
+ /** Les destinations de l'espace, dans l'ordre où elles ont été déclarées. */
40
+ export const backupDestinationList = {
41
+ command: 'backup.destinationList' as const,
42
+ input: z.object({}),
43
+ output: z.object({ destinations: z.array(backupDestinationSchema) })
44
+ };
45
+
46
+ /**
47
+ * Déclare une destination.
48
+ *
49
+ * Un seul schéma pour les trois genres, avec les champs des deux autres à
50
+ * `null`: la forme alternative (trois commandes, ou une union discriminée)
51
+ * obligerait l'écran à trois formulaires là où il n'en a qu'un, dont les champs
52
+ * apparaissent selon le genre choisi.
53
+ */
54
+ export const backupDestinationAdd = {
55
+ command: 'backup.destinationAdd' as const,
56
+ input: z.object({
57
+ kind: backupDestinationKindSchema,
58
+ name: z.string().min(1).max(BACKUP_DESTINATION_NAME_MAX),
59
+ deviceId: z.uuid().nullable(),
60
+ path: z.string().max(BACKUP_PATH_MAX),
61
+ endpoint: z.string().max(BACKUP_ENDPOINT_MAX).nullable(),
62
+ region: z.string().max(64).nullable(),
63
+ bucket: z.string().max(BACKUP_BUCKET_MAX).nullable(),
64
+ accessKeyId: z.string().max(BACKUP_ACCESS_KEY_MAX).nullable(),
65
+ secret: z.string().max(BACKUP_SECRET_MAX).nullable(),
66
+ pathStyle: z.boolean()
67
+ }),
68
+ output: z.object({ destination: backupDestinationSchema })
69
+ };
70
+
71
+ /** `secret` omis = inchangé: le serveur ne l'a jamais rendu, on ne le réécrit pas. */
72
+ export const backupDestinationUpdate = {
73
+ command: 'backup.destinationUpdate' as const,
74
+ input: z.object({
75
+ destinationId,
76
+ name: z.string().min(1).max(BACKUP_DESTINATION_NAME_MAX),
77
+ deviceId: z.uuid().nullable(),
78
+ path: z.string().max(BACKUP_PATH_MAX),
79
+ endpoint: z.string().max(BACKUP_ENDPOINT_MAX).nullable(),
80
+ region: z.string().max(64).nullable(),
81
+ bucket: z.string().max(BACKUP_BUCKET_MAX).nullable(),
82
+ accessKeyId: z.string().max(BACKUP_ACCESS_KEY_MAX).nullable(),
83
+ secret: z.string().max(BACKUP_SECRET_MAX).optional(),
84
+ pathStyle: z.boolean()
85
+ }),
86
+ output: z.object({ destination: backupDestinationSchema })
87
+ };
88
+
89
+ /**
90
+ * Retire une destination. **Refusée** tant qu'un travail la vise.
91
+ *
92
+ * Contrairement aux jetons de déploiement, qui laissent la cible orpheline mais
93
+ * vivante: un travail sans destination n'a aucun comportement raisonnable — il
94
+ * ne peut ni s'exécuter ni le dire à l'avance. Mieux vaut obliger à trancher.
95
+ */
96
+ export const backupDestinationRemove = {
97
+ command: 'backup.destinationRemove' as const,
98
+ input: z.object({ destinationId }),
99
+ output: z.object({ destinationId })
100
+ };
101
+
102
+ /**
103
+ * Contrôle qu'une destination est joignable et inscriptible, **maintenant**.
104
+ *
105
+ * Écrit puis relit puis efface un petit objet témoin: lister un bucket ne prouve
106
+ * pas qu'on peut y écrire, et découvrir le contraire à 3 h du matin est
107
+ * exactement ce que cette commande existe pour éviter.
108
+ */
109
+ export const backupDestinationTest = {
110
+ command: 'backup.destinationTest' as const,
111
+ input: z.object({ destinationId }),
112
+ output: backupDestinationProbeSchema
113
+ };
114
+
115
+ // ---------------------------------------------------------------- travaux
116
+
117
+ /** Les travaux de l'espace, avec l'état de leur dernier passage. */
118
+ export const backupJobList = {
119
+ command: 'backup.jobList' as const,
120
+ input: z.object({}),
121
+ output: z.object({ jobs: z.array(backupJobSchema) })
122
+ };
123
+
124
+ /** Compte les travaux actifs. Métadonnée en clair pure, pour la tuile d'accueil. */
125
+ export const backupCount = {
126
+ command: 'backup.count' as const,
127
+ input: z.object({}),
128
+ output: z.object({
129
+ count: z.number().int().nonnegative(),
130
+ /** Combien ont échoué à leur dernier passage — ce que la tuile signale. */
131
+ failing: z.number().int().nonnegative()
132
+ })
133
+ };
134
+
135
+ /** Un travail et son historique, du plus récent au plus ancien. */
136
+ export const backupJobGet = {
137
+ command: 'backup.jobGet' as const,
138
+ input: z.object({ jobId, limit: z.number().int().positive().max(100).optional() }),
139
+ output: z.object({ job: backupJobSchema, runs: z.array(backupRunSchema) })
140
+ };
141
+
142
+ const jobBody = {
143
+ name: z.string().min(1).max(BACKUP_JOB_NAME_MAX),
144
+ destinationId,
145
+ encryption: backupEncryptionSchema,
146
+ source: backupSourceKindSchema,
147
+ sourceId: z.number().int().positive().nullable(),
148
+ enabled: z.boolean(),
149
+ schedule: backupScheduleKindSchema,
150
+ scheduleHour: z.number().int().min(0).max(23),
151
+ scheduleWeekday: z.number().int().min(0).max(6),
152
+ scheduleDay: z.number().int().min(1).max(28),
153
+ keepLast: z.number().int().min(1).max(365)
154
+ };
155
+
156
+ export const backupJobAdd = {
157
+ command: 'backup.jobAdd' as const,
158
+ input: z.object(jobBody),
159
+ output: z.object({ job: backupJobSchema })
160
+ };
161
+
162
+ export const backupJobUpdate = {
163
+ command: 'backup.jobUpdate' as const,
164
+ input: z.object({ jobId, ...jobBody }),
165
+ output: z.object({ job: backupJobSchema })
166
+ };
167
+
168
+ /**
169
+ * Supprime un travail et son historique.
170
+ *
171
+ * Les archives déjà écrites ne sont **pas** touchées: DevEye a produit des
172
+ * fichiers chez quelqu'un d'autre, et les effacer parce qu'on range sa
173
+ * configuration serait le contraire d'une sauvegarde. L'écran le dit.
174
+ */
175
+ export const backupJobRemove = {
176
+ command: 'backup.jobRemove' as const,
177
+ input: z.object({ jobId }),
178
+ output: z.object({ jobId })
179
+ };
180
+
181
+ /**
182
+ * Lance un travail tout de suite.
183
+ *
184
+ * Ne rend pas l'archive: une sauvegarde dure des minutes, et attendre la réponse
185
+ * d'une commande WS pendant ce temps-là ne marcherait pas. Elle inscrit
186
+ * l'exécution en `running` et rend sa ligne; l'avancement se lit en
187
+ * re-sollicitant, comme partout ailleurs (le sujet `backup` est diffusé à
188
+ * chaque transition).
189
+ */
190
+ export const backupJobRun = {
191
+ command: 'backup.jobRun' as const,
192
+ input: z.object({ jobId }),
193
+ output: z.object({ run: backupRunSchema })
194
+ };
195
+
196
+ /** Les sources sauvegardables de l'espace, pour remplir le sélecteur. */
197
+ export const backupSources = {
198
+ command: 'backup.sources' as const,
199
+ input: z.object({}),
200
+ output: z.object({ candidates: z.array(backupSourceCandidateSchema) })
201
+ };
202
+
203
+ /**
204
+ * Les dernières exécutions de l'espace, tous travaux confondus.
205
+ *
206
+ * Ce que la vue d'ensemble montre: on veut savoir « est-ce que mes sauvegardes
207
+ * tournent », pas ouvrir sept fiches pour le découvrir.
208
+ */
209
+ export const backupRuns = {
210
+ command: 'backup.runs' as const,
211
+ input: z.object({ limit: z.number().int().positive().max(200).optional() }),
212
+ output: z.object({ runs: z.array(backupRunSchema) })
213
+ };
214
+
215
+ export const backupCommands = [
216
+ backupDestinationList,
217
+ backupDestinationAdd,
218
+ backupDestinationUpdate,
219
+ backupDestinationRemove,
220
+ backupDestinationTest,
221
+ backupJobList,
222
+ backupCount,
223
+ backupJobGet,
224
+ backupJobAdd,
225
+ backupJobUpdate,
226
+ backupJobRemove,
227
+ backupJobRun,
228
+ backupSources,
229
+ backupRuns
230
+ ] as const;