@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,709 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ PROJECT_VERSION_MAX_LENGTH,
4
+ projectDraftSchema,
5
+ projectSchema,
6
+ projectSecurityTierSchema,
7
+ projectStatusSchema,
8
+ projectSummarySchema,
9
+ projectVersionSourceSchema
10
+ } from '../domain/project';
11
+ import {
12
+ PROJECT_COLUMN_NAME_MAX_LENGTH,
13
+ projectCardDraftSchema,
14
+ projectCardSchema,
15
+ projectColumnSchema
16
+ } from '../domain/projectBoard';
17
+ import {
18
+ PROJECT_MESSAGE_MAX_LENGTH,
19
+ PROJECT_MESSAGE_PAGE_SIZE,
20
+ projectMessageSchema
21
+ } from '../domain/projectChat';
22
+ import {
23
+ projectCardDepSchema,
24
+ projectMilestoneDraftSchema,
25
+ projectMilestoneSchema
26
+ } from '../domain/projectPlan';
27
+ import { PROJECT_EVENT_PAGE_SIZE, projectEventSchema } from '../domain/projectHistory';
28
+ import { myTaskSchema, projectLinkCountsSchema } from '../domain/projectLink';
29
+
30
+ /**
31
+ * Commandes des projets.
32
+ *
33
+ * L'espace visé n'apparaît dans aucune entrée : il voyage sur l'enveloppe WS
34
+ * (voir `protocol/envelope`) et le dispatcheur le résout, appartenance vérifiée,
35
+ * avant que le handler ne s'exécute.
36
+ *
37
+ * ⚠️ Toutes les commandes partagent le préfixe `project.`, qui porte le sujet
38
+ * live `projects` (`src/features/_topics.ts`). Le contrôle de démarrage qui
39
+ * attrape un `mutates` oublié cherche un verbe **juste après le point**
40
+ * (`note.add`) : avec des noms en camelCase il ne voit rien ici. `mutates` est
41
+ * donc à relire à la main sur chaque écriture ajoutée à ce fichier.
42
+ */
43
+
44
+ const projectId = z.number().int().positive();
45
+
46
+ /**
47
+ * Le portefeuille : tous les projets actifs de l'espace, avec leurs compteurs.
48
+ *
49
+ * Jamais verrouillée. Un projet `guarded` dont la session ne peut pas lire le
50
+ * corps revient **masqué** (`masked: true`) plutôt qu'absent : la liste doit
51
+ * dire ce qui existe même verrouillée. `archived: true` bascule sur l'ensemble
52
+ * disjoint des projets archivés.
53
+ */
54
+ export const projectList = {
55
+ command: 'project.list' as const,
56
+ input: z.object({ archived: z.boolean().optional() }),
57
+ output: z.object({ projects: z.array(projectSummarySchema) })
58
+ };
59
+
60
+ /**
61
+ * Compte les projets **actifs**. Métadonnée en clair pure : aucune ligne n'est
62
+ * déchiffrée, la tuile d'accueil affiche donc toujours un nombre, même session
63
+ * verrouillée.
64
+ */
65
+ export const projectCount = {
66
+ command: 'project.count' as const,
67
+ input: z.object({}),
68
+ output: z.object({ count: z.number().int().nonnegative() })
69
+ };
70
+
71
+ /**
72
+ * Un projet en entier. Sur un projet `guarded`, la session doit être
73
+ * déverrouillée : sinon le serveur répond `locked` et le client ouvre l'invite
74
+ * habituelle avant de rejouer l'appel.
75
+ */
76
+ export const projectGet = {
77
+ command: 'project.get' as const,
78
+ input: z.object({ projectId }),
79
+ output: z.object({ project: projectSchema })
80
+ };
81
+
82
+ /**
83
+ * Crée un projet. `securityTier` est figé à la création et ne se change ensuite
84
+ * que par {@link projectSetSecurityTier}, qui re-chiffre tout l'arbre.
85
+ */
86
+ export const projectAdd = {
87
+ command: 'project.add' as const,
88
+ input: z.object({
89
+ project: projectDraftSchema,
90
+ securityTier: projectSecurityTierSchema
91
+ }),
92
+ output: z.object({ project: projectSchema })
93
+ };
94
+
95
+ /**
96
+ * Modifie le profil d'un projet. Ne touche ni au tier ni à la source de version
97
+ * — deux bascules qui ont des effets de bord, et donc leurs propres commandes.
98
+ */
99
+ export const projectUpdate = {
100
+ command: 'project.update' as const,
101
+ input: z.object({ projectId, project: projectDraftSchema }),
102
+ output: z.object({ project: projectSchema })
103
+ };
104
+
105
+ /**
106
+ * Pose la version affichée, et d'où elle vient.
107
+ *
108
+ * `github_release` exige un dépôt lié et un projet `open` (un projet `guarded`
109
+ * ne se synchronise pas) ; `version` est alors ignorée et recalculée par le
110
+ * service de fond. En `manual`, la valeur passée fait foi.
111
+ */
112
+ export const projectSetVersion = {
113
+ command: 'project.setVersion' as const,
114
+ input: z.object({
115
+ projectId,
116
+ source: projectVersionSourceSchema,
117
+ version: z.string().max(PROJECT_VERSION_MAX_LENGTH)
118
+ }),
119
+ output: z.object({ project: projectSchema })
120
+ };
121
+
122
+ export const projectSetStatus = {
123
+ command: 'project.setStatus' as const,
124
+ input: z.object({ projectId, status: projectStatusSchema }),
125
+ output: z.object({ project: projectSchema })
126
+ };
127
+
128
+ /**
129
+ * Bascule l'étage de chiffrement, et **re-chiffre tout l'arbre du projet**
130
+ * (cartes, messages, jalons, événements, cache git) sous la nouvelle clé, dans
131
+ * la foulée. Exige une session déverrouillée dans les deux sens : on ne
132
+ * re-chiffre pas ce qu'on ne peut pas lire.
133
+ *
134
+ * Passer en `guarded` **désactive les intégrations** du projet (synchronisation
135
+ * git, déploiement) : elles ont besoin de lire sans session.
136
+ */
137
+ export const projectSetSecurityTier = {
138
+ command: 'project.setSecurityTier' as const,
139
+ input: z.object({ projectId, securityTier: projectSecurityTierSchema }),
140
+ output: z.object({ project: projectSchema })
141
+ };
142
+
143
+ /**
144
+ * Archive un projet : il quitte le portefeuille sans que rien ne soit détruit.
145
+ * C'est ce que « supprimer » fait dans l'interface — il n'existe volontairement
146
+ * aucune commande de suppression dans ce module.
147
+ */
148
+ export const projectArchive = {
149
+ command: 'project.archive' as const,
150
+ input: z.object({ projectId }),
151
+ output: z.object({ projectId })
152
+ };
153
+
154
+ export const projectRestore = {
155
+ command: 'project.restore' as const,
156
+ input: z.object({ projectId }),
157
+ output: z.object({ projectId })
158
+ };
159
+
160
+ /**
161
+ * Ordonne le portefeuille : `projectIds` en est le contenu **complet**, dans son
162
+ * ordre final (indice le plus bas en premier). Ne touche jamais au corps
163
+ * chiffré, donc fonctionne aussi sur des projets masqués.
164
+ */
165
+ export const projectReorder = {
166
+ command: 'project.reorder' as const,
167
+ input: z.object({ projectIds: z.array(projectId).min(1) }),
168
+ output: z.object({ projectIds: z.array(projectId) })
169
+ };
170
+
171
+ // ------------------------------------------------------------------ tableau
172
+
173
+ const columnId = z.number().int().positive();
174
+ const cardId = z.number().int().positive();
175
+
176
+ /**
177
+ * Le tableau complet d'un projet : ses colonnes et ses cartes vivantes.
178
+ *
179
+ * Un seul aller-retour plutôt qu'un par colonne — le client ne tient aucun cache
180
+ * normalisé, il re-sollicite, et un tableau entier tient largement dans une
181
+ * trame. Sur un projet confidentiel, exige une session déverrouillée : les
182
+ * titres des cartes sont chiffrés au même étage que le projet.
183
+ *
184
+ * `archived: true` renvoie à la place les cartes archivées, sans les colonnes —
185
+ * c'est l'ensemble disjoint, lu par la page d'historique.
186
+ */
187
+ export const projectBoard = {
188
+ command: 'project.board' as const,
189
+ input: z.object({ projectId, archived: z.boolean().optional() }),
190
+ output: z.object({
191
+ columns: z.array(projectColumnSchema),
192
+ cards: z.array(projectCardSchema)
193
+ })
194
+ };
195
+
196
+ export const projectColumnAdd = {
197
+ command: 'project.columnAdd' as const,
198
+ input: z.object({ projectId, name: z.string().min(1).max(PROJECT_COLUMN_NAME_MAX_LENGTH) }),
199
+ output: z.object({ column: projectColumnSchema })
200
+ };
201
+
202
+ export const projectColumnUpdate = {
203
+ command: 'project.columnUpdate' as const,
204
+ input: z.object({
205
+ columnId,
206
+ name: z.string().min(1).max(PROJECT_COLUMN_NAME_MAX_LENGTH),
207
+ countsAsDone: z.boolean(),
208
+ wipLimit: z.number().int().positive().nullable()
209
+ }),
210
+ output: z.object({ column: projectColumnSchema })
211
+ };
212
+
213
+ /**
214
+ * Supprime une colonne — la seule suppression du module, et elle est bornée :
215
+ * refusée (`conflict`) tant que la colonne porte la moindre carte, archivée
216
+ * comprise. La contrainte SQL est en CASCADE ; sans cette garde, retirer une
217
+ * colonne détruirait silencieusement des cartes que rien ne permet de
218
+ * supprimer par ailleurs.
219
+ */
220
+ export const projectColumnRemove = {
221
+ command: 'project.columnRemove' as const,
222
+ input: z.object({ columnId }),
223
+ output: z.object({ columnId })
224
+ };
225
+
226
+ export const projectColumnReorder = {
227
+ command: 'project.columnReorder' as const,
228
+ input: z.object({ projectId, columnIds: z.array(columnId).min(1) }),
229
+ output: z.object({ columnIds: z.array(columnId) })
230
+ };
231
+
232
+ export const projectCardAdd = {
233
+ command: 'project.cardAdd' as const,
234
+ input: z.object({ projectId, columnId, card: projectCardDraftSchema }),
235
+ output: z.object({ card: projectCardSchema })
236
+ };
237
+
238
+ export const projectCardUpdate = {
239
+ command: 'project.cardUpdate' as const,
240
+ input: z.object({ cardId, card: projectCardDraftSchema }),
241
+ output: z.object({ card: projectCardSchema })
242
+ };
243
+
244
+ /**
245
+ * Range une colonne : `cardIds` en est le contenu **complet** dans son ordre
246
+ * final, et chaque carte listée est versée dans `columnId` au passage. Une
247
+ * seule commande couvre donc le tri interne et le passage d'une colonne à
248
+ * l'autre — c'est le motif de `note.reorder`.
249
+ *
250
+ * Ne touche jamais au corps chiffré : un glisser-déposer fonctionne donc sans
251
+ * déverrouiller quoi que ce soit.
252
+ */
253
+ export const projectCardMove = {
254
+ command: 'project.cardMove' as const,
255
+ input: z.object({ columnId, cardIds: z.array(cardId) }),
256
+ output: z.object({ columnId, cardIds: z.array(cardId) })
257
+ };
258
+
259
+ /**
260
+ * Archive une carte : elle quitte le tableau sans que rien ne soit détruit.
261
+ * C'est ce que « supprimer » fait dans l'interface — il n'existe volontairement
262
+ * aucune commande de suppression de carte.
263
+ */
264
+ export const projectCardArchive = {
265
+ command: 'project.cardArchive' as const,
266
+ input: z.object({ cardId }),
267
+ output: z.object({ cardId })
268
+ };
269
+
270
+ export const projectCardRestore = {
271
+ command: 'project.cardRestore' as const,
272
+ input: z.object({ cardId }),
273
+ output: z.object({ cardId })
274
+ };
275
+
276
+ // ------------------------------------------------------------ planification
277
+
278
+ const milestoneId = z.number().int().positive();
279
+
280
+ /**
281
+ * Ce que le tableau ne dit pas : les jalons du projet et le graphe des
282
+ * dépendances entre ses cartes.
283
+ *
284
+ * Volontairement **séparé de `project.board`** : la frise se compose des cartes
285
+ * (que le tableau a déjà chargées) et de ce complément. Les fusionner ferait
286
+ * payer les dépendances à chaque ouverture du kanban, qui n'en a que faire.
287
+ */
288
+ export const projectPlan = {
289
+ command: 'project.plan' as const,
290
+ input: z.object({ projectId }),
291
+ output: z.object({
292
+ milestones: z.array(projectMilestoneSchema),
293
+ deps: z.array(projectCardDepSchema)
294
+ })
295
+ };
296
+
297
+ export const projectMilestoneAdd = {
298
+ command: 'project.milestoneAdd' as const,
299
+ input: z.object({ projectId, milestone: projectMilestoneDraftSchema }),
300
+ output: z.object({ milestone: projectMilestoneSchema })
301
+ };
302
+
303
+ export const projectMilestoneUpdate = {
304
+ command: 'project.milestoneUpdate' as const,
305
+ input: z.object({ milestoneId, milestone: projectMilestoneDraftSchema }),
306
+ output: z.object({ milestone: projectMilestoneSchema })
307
+ };
308
+
309
+ /** Marque un jalon atteint (ou revient dessus). */
310
+ export const projectMilestoneSetReached = {
311
+ command: 'project.milestoneSetReached' as const,
312
+ input: z.object({ milestoneId, reached: z.boolean() }),
313
+ output: z.object({ milestone: projectMilestoneSchema })
314
+ };
315
+
316
+ /**
317
+ * Retire un jalon. Ses cartes ne sont pas touchées — elles se retrouvent
318
+ * simplement sans jalon (`ON DELETE SET NULL`). Un jalon n'est pas un « bloc » :
319
+ * il ne porte aucun travail, seulement une date, donc le retirer ne perd rien.
320
+ */
321
+ export const projectMilestoneRemove = {
322
+ command: 'project.milestoneRemove' as const,
323
+ input: z.object({ milestoneId }),
324
+ output: z.object({ milestoneId })
325
+ };
326
+
327
+ /** Rattache une carte à un jalon, ou l'en détache (`null`). */
328
+ export const projectCardSetMilestone = {
329
+ command: 'project.cardSetMilestone' as const,
330
+ input: z.object({ cardId, milestoneId: milestoneId.nullable() }),
331
+ output: z.object({ cardId, milestoneId: milestoneId.nullable() })
332
+ };
333
+
334
+ /**
335
+ * Déclare que `cardId` est bloquée par `blockedByCardId`.
336
+ *
337
+ * Refusée (`validation`) si elle fermerait un cycle : une carte ne peut pas
338
+ * dépendre d'elle-même, fût-ce par un chemin de dix arêtes. Le contrôle est
339
+ * fait côté serveur, là où le graphe complet est connu.
340
+ */
341
+ export const projectDepAdd = {
342
+ command: 'project.depAdd' as const,
343
+ input: projectCardDepSchema,
344
+ output: z.object({ dep: projectCardDepSchema })
345
+ };
346
+
347
+ export const projectDepRemove = {
348
+ command: 'project.depRemove' as const,
349
+ input: projectCardDepSchema,
350
+ output: projectCardDepSchema
351
+ };
352
+
353
+ // ----------------------------------------------------------------- git
354
+
355
+ /**
356
+ * La liaison du projet vers un dépôt de l'espace.
357
+ *
358
+ * Trois commandes seulement : **le dépôt n'appartient pas au projet.** Il vit
359
+ * dans la feature Git (`features/git.ts`), qui porte son cache, sa
360
+ * synchronisation et ses jetons. Ce qui suit ne fait que poser et retirer un
361
+ * pointeur — d'où le fait que tout y soit un `repoId` et rien d'autre.
362
+ */
363
+
364
+ /**
365
+ * Les dépôts liés au projet, dans l'ordre de la feature Git.
366
+ *
367
+ * **Plusieurs**, et c'est le cas normal : un projet réel se compose souvent d'un
368
+ * client, d'un serveur et de contrats partagés, chacun dans son dépôt.
369
+ */
370
+ export const projectRepoList = {
371
+ command: 'project.repoList' as const,
372
+ input: z.object({ projectId }),
373
+ output: z.object({ repoIds: z.array(z.number().int().positive()) })
374
+ };
375
+
376
+ /**
377
+ * Ajoute un dépôt existant au projet. **Idempotente** : le relier deux fois
378
+ * n'est pas une erreur, c'est le même fait déclaré deux fois. Rend la liste
379
+ * complète, pour que l'appelant n'ait pas à la recomposer.
380
+ *
381
+ * Refusé sur un projet confidentiel : la synchronisation tourne sans session, et
382
+ * rattacher un projet gardé à une entité d'espace en clair révélerait par la
383
+ * bande ce qu'il contient. Le dire ici évite un réglage sans effet.
384
+ */
385
+ export const projectRepoLink = {
386
+ command: 'project.repoLink' as const,
387
+ input: z.object({ projectId, repoId: z.number().int().positive() }),
388
+ output: z.object({ repoIds: z.array(z.number().int().positive()) })
389
+ };
390
+
391
+ /**
392
+ * Retire une liaison.
393
+ *
394
+ * **Le dépôt et son cache survivent** : ils appartiennent à l'espace, et
395
+ * d'autres projets peuvent s'en servir. C'est le pointeur qui part, rien d'autre.
396
+ */
397
+ export const projectRepoUnlink = {
398
+ command: 'project.repoUnlink' as const,
399
+ input: z.object({ projectId, repoId: z.number().int().positive() }),
400
+ output: z.object({ repoIds: z.array(z.number().int().positive()) })
401
+ };
402
+
403
+ // ------------------------------------------------------------- transverse
404
+
405
+ /**
406
+ * Toutes mes tâches, tous projets de l'espace confondus.
407
+ *
408
+ * Une seule requête, rendue possible par le fait qu'`assignee_user_id` est en
409
+ * clair. Les cartes d'un projet confidentiel verrouillé reviennent masquées
410
+ * plutôt qu'absentes : une liste de tâches incomplète serait pire qu'une liste
411
+ * qui dit ce qu'elle ne peut pas lire.
412
+ */
413
+ export const projectMyTasks = {
414
+ command: 'project.myTasks' as const,
415
+ input: z.object({}),
416
+ output: z.object({ tasks: z.array(myTaskSchema) })
417
+ };
418
+
419
+ /**
420
+ * Combien d'éléments chaque intégration du projet a à montrer.
421
+ *
422
+ * Une seule commande pour les quatre, parce qu'elle sert une seule décision :
423
+ * quels onglets la fiche du projet doit ouvrir. Les demander une par une ferait
424
+ * quatre allers-retours pour dessiner une barre d'onglets, et la ferait
425
+ * apparaître par morceaux.
426
+ *
427
+ * Ne déchiffre rien et ne demande aucune session : ce sont des liaisons en
428
+ * clair. Un projet confidentiel n'en a aucune par construction (voir
429
+ * {@link projectRepoLink}), et répond donc quatre zéros.
430
+ */
431
+ export const projectLinkCounts = {
432
+ command: 'project.linkCounts' as const,
433
+ input: z.object({ projectId }),
434
+ output: z.object({ counts: projectLinkCountsSchema })
435
+ };
436
+
437
+ /**
438
+ * Les services surveillés rattachés au projet, dans l'ordre d'Uptime.
439
+ *
440
+ * Ne rend que des identifiants : les nommer supposerait de lire Uptime au nom
441
+ * de l'appelant, alors que ce droit-là s'y vérifie déjà. Le client résout les
442
+ * noms et les états par `uptime.list`, et affiche des identifiants nus si son
443
+ * rôle ne lui ouvre pas cette feature — plutôt que de faire disparaître des
444
+ * liaisons qui existent.
445
+ */
446
+ export const projectUptimeList = {
447
+ command: 'project.uptimeList' as const,
448
+ input: z.object({ projectId }),
449
+ output: z.object({ serviceIds: z.array(z.number().int().positive()) })
450
+ };
451
+
452
+ /**
453
+ * Rattache un service surveillé au projet. **Idempotente** : rattacher deux
454
+ * fois le même service n'est pas une erreur, c'est le même fait déclaré deux
455
+ * fois. Rend la liste complète, pour que l'appelant n'ait pas à la recomposer.
456
+ */
457
+ export const projectUptimeLink = {
458
+ command: 'project.uptimeLink' as const,
459
+ input: z.object({ projectId, serviceId: z.number().int().positive() }),
460
+ output: z.object({ serviceIds: z.array(z.number().int().positive()) })
461
+ };
462
+
463
+ /** Retire la liaison. Le service, lui, n'est pas touché. */
464
+ export const projectUptimeUnlink = {
465
+ command: 'project.uptimeUnlink' as const,
466
+ input: z.object({ projectId, serviceId: z.number().int().positive() }),
467
+ output: z.object({ serviceIds: z.array(z.number().int().positive()) })
468
+ };
469
+
470
+ /**
471
+ * Les bases de données rattachées au projet, dans l'ordre de la feature Bases.
472
+ *
473
+ * Ne rend que des identifiants, pour la même raison que
474
+ * {@link projectUptimeList} : les nommer supposerait de lire la feature Bases au
475
+ * nom de l'appelant, alors que ce droit s'y vérifie déjà.
476
+ */
477
+ export const projectDatabaseList = {
478
+ command: 'project.databaseList' as const,
479
+ input: z.object({ projectId }),
480
+ output: z.object({ databaseIds: z.array(z.number().int().positive()) })
481
+ };
482
+
483
+ /**
484
+ * Rattache une base au projet. **Idempotente**, et refusée sur un projet
485
+ * confidentiel : la liaison est une ligne en clair, et la base vit à l'étage
486
+ * ouvert — exactement comme pour un dépôt git.
487
+ */
488
+ export const projectDatabaseLink = {
489
+ command: 'project.databaseLink' as const,
490
+ input: z.object({ projectId, databaseId: z.number().int().positive() }),
491
+ output: z.object({ databaseIds: z.array(z.number().int().positive()) })
492
+ };
493
+
494
+ /** Retire la liaison. La base, elle, n'est pas touchée. */
495
+ export const projectDatabaseUnlink = {
496
+ command: 'project.databaseUnlink' as const,
497
+ input: z.object({ projectId, databaseId: z.number().int().positive() }),
498
+ output: z.object({ databaseIds: z.array(z.number().int().positive()) })
499
+ };
500
+
501
+ /**
502
+ * Les sites suivis rattachés au projet, dans l'ordre de la feature Audience.
503
+ *
504
+ * Ne rend que des identifiants, pour la même raison que
505
+ * {@link projectDatabaseList} : les nommer supposerait de lire la feature
506
+ * Audience au nom de l'appelant, alors que ce droit s'y vérifie déjà.
507
+ */
508
+ export const projectAudienceList = {
509
+ command: 'project.audienceList' as const,
510
+ input: z.object({ projectId }),
511
+ output: z.object({ siteIds: z.array(z.number().int().positive()) })
512
+ };
513
+
514
+ /**
515
+ * Rattache un site au projet. **Idempotente**, et refusée sur un projet
516
+ * confidentiel : la liaison est une ligne en clair, et le site vit à l'étage
517
+ * ouvert — exactement comme un dépôt git ou une base.
518
+ */
519
+ export const projectAudienceLink = {
520
+ command: 'project.audienceLink' as const,
521
+ input: z.object({ projectId, siteId: z.number().int().positive() }),
522
+ output: z.object({ siteIds: z.array(z.number().int().positive()) })
523
+ };
524
+
525
+ /** Retire la liaison. Le site, lui, n'est pas touché. */
526
+ export const projectAudienceUnlink = {
527
+ command: 'project.audienceUnlink' as const,
528
+ input: z.object({ projectId, siteId: z.number().int().positive() }),
529
+ output: z.object({ siteIds: z.array(z.number().int().positive()) })
530
+ };
531
+
532
+ // ---------------------------------------------------------- déploiement
533
+
534
+ /**
535
+ * La liaison du projet vers les cibles de déploiement de l'espace.
536
+ *
537
+ * Trois commandes, exactement comme pour un dépôt : **la cible n'appartient pas
538
+ * au projet.** Elle vit dans la feature Déploiement, avec son jeton, son
539
+ * historique et son suivi d'état, et plusieurs projets peuvent viser la même —
540
+ * le cas normal quand un client et un serveur partent dans la même pile compose.
541
+ * Ce qui suit ne fait que poser et retirer un pointeur.
542
+ *
543
+ * Déclencher ne se fait pas ici : c'est `deploy.trigger`, qui accepte un
544
+ * `projectId` facultatif pour inscrire le fait dans la frise du projet.
545
+ */
546
+ export const projectDeployList = {
547
+ command: 'project.deployList' as const,
548
+ input: z.object({ projectId }),
549
+ output: z.object({ targetIds: z.array(z.number().int().positive()) })
550
+ };
551
+
552
+ /**
553
+ * Rattache une cible au projet. **Idempotente**, et refusée sur un projet
554
+ * confidentiel : la liaison est une ligne en clair, la cible vit à l'étage
555
+ * ouvert et son suivi tourne sans session — exactement comme pour un dépôt.
556
+ */
557
+ export const projectDeployLink = {
558
+ command: 'project.deployLink' as const,
559
+ input: z.object({ projectId, targetId: z.number().int().positive() }),
560
+ output: z.object({ targetIds: z.array(z.number().int().positive()) })
561
+ };
562
+
563
+ /** Retire la liaison. La cible, son historique et les autres projets survivent. */
564
+ export const projectDeployUnlink = {
565
+ command: 'project.deployUnlink' as const,
566
+ input: z.object({ projectId, targetId: z.number().int().positive() }),
567
+ output: z.object({ targetIds: z.array(z.number().int().positive()) })
568
+ };
569
+
570
+ // -------------------------------------------------------------- historique
571
+
572
+ /**
573
+ * La frise verticale d'un projet, du plus récent au plus ancien.
574
+ *
575
+ * Même pagination par curseur remontant que la discussion : `before` demande la
576
+ * page qui précède un événement donné. Sans lui, on obtient le haut de la frise.
577
+ */
578
+ export const projectEventList = {
579
+ command: 'project.eventList' as const,
580
+ input: z.object({
581
+ projectId,
582
+ before: z.number().int().positive().optional(),
583
+ limit: z.number().int().positive().max(PROJECT_EVENT_PAGE_SIZE).optional()
584
+ }),
585
+ output: z.object({
586
+ events: z.array(projectEventSchema),
587
+ hasMore: z.boolean()
588
+ })
589
+ };
590
+
591
+ // -------------------------------------------------------------- discussion
592
+
593
+ const messageId = z.number().int().positive();
594
+
595
+ /**
596
+ * Le fil d'une carte, du plus ancien au plus récent.
597
+ *
598
+ * Pagination par **curseur remontant** : `before` demande la page qui précède
599
+ * un message donné, ce qui est le seul ordre qui tienne dans un fil où l'on
600
+ * écrit par le bas. Sans `before`, on obtient la fin du fil — ce qu'on veut à
601
+ * l'ouverture.
602
+ */
603
+ export const projectMessageList = {
604
+ command: 'project.messageList' as const,
605
+ input: z.object({
606
+ cardId,
607
+ before: messageId.optional(),
608
+ limit: z.number().int().positive().max(PROJECT_MESSAGE_PAGE_SIZE).optional()
609
+ }),
610
+ output: z.object({
611
+ messages: z.array(projectMessageSchema),
612
+ /** Reste-t-il des messages plus anciens à charger ? */
613
+ hasMore: z.boolean()
614
+ })
615
+ };
616
+
617
+ /**
618
+ * Poste un message. Les `mentions` sont des identifiants de **membres de
619
+ * l'espace** ; le serveur refuse tout autre. Elles voyagent à part du texte
620
+ * plutôt que d'être ré-extraites côté serveur : celui-ci ne lit pas le texte
621
+ * pour le comprendre, seulement pour le stocker chiffré.
622
+ */
623
+ export const projectMessageSend = {
624
+ command: 'project.messageSend' as const,
625
+ input: z.object({
626
+ cardId,
627
+ text: z.string().min(1).max(PROJECT_MESSAGE_MAX_LENGTH),
628
+ mentions: z.array(z.number().int().positive()).max(32)
629
+ }),
630
+ output: z.object({ message: projectMessageSchema })
631
+ };
632
+
633
+ /** Retouche son propre message. Un autre auteur est refusé (`forbidden`). */
634
+ export const projectMessageEdit = {
635
+ command: 'project.messageEdit' as const,
636
+ input: z.object({
637
+ messageId,
638
+ text: z.string().min(1).max(PROJECT_MESSAGE_MAX_LENGTH),
639
+ mentions: z.array(z.number().int().positive()).max(32)
640
+ }),
641
+ output: z.object({ message: projectMessageSchema })
642
+ };
643
+
644
+ /**
645
+ * Pose le point d'eau haute de lecture de l'appelant sur une carte : tout
646
+ * message d'identifiant inférieur ou égal cesse d'être compté comme non lu.
647
+ *
648
+ * N'est **pas** une mutation diffusée : une lecture est personnelle, l'annoncer
649
+ * à l'espace ferait re-solliciter tout le monde pour rien.
650
+ */
651
+ export const projectMarkRead = {
652
+ command: 'project.markRead' as const,
653
+ input: z.object({ cardId, lastMessageId: messageId }),
654
+ output: z.object({ cardId, lastMessageId: messageId })
655
+ };
656
+
657
+ export const projectCommands = [
658
+ projectList,
659
+ projectCount,
660
+ projectGet,
661
+ projectAdd,
662
+ projectUpdate,
663
+ projectSetVersion,
664
+ projectSetStatus,
665
+ projectSetSecurityTier,
666
+ projectArchive,
667
+ projectRestore,
668
+ projectReorder,
669
+ projectBoard,
670
+ projectColumnAdd,
671
+ projectColumnUpdate,
672
+ projectColumnRemove,
673
+ projectColumnReorder,
674
+ projectCardAdd,
675
+ projectCardUpdate,
676
+ projectCardMove,
677
+ projectCardArchive,
678
+ projectCardRestore,
679
+ projectMessageList,
680
+ projectMessageSend,
681
+ projectMessageEdit,
682
+ projectMarkRead,
683
+ projectPlan,
684
+ projectMilestoneAdd,
685
+ projectMilestoneUpdate,
686
+ projectMilestoneSetReached,
687
+ projectMilestoneRemove,
688
+ projectCardSetMilestone,
689
+ projectDepAdd,
690
+ projectDepRemove,
691
+ projectEventList,
692
+ projectRepoList,
693
+ projectRepoLink,
694
+ projectRepoUnlink,
695
+ projectDeployList,
696
+ projectDeployLink,
697
+ projectDeployUnlink,
698
+ projectMyTasks,
699
+ projectLinkCounts,
700
+ projectUptimeList,
701
+ projectUptimeLink,
702
+ projectUptimeUnlink,
703
+ projectDatabaseList,
704
+ projectDatabaseLink,
705
+ projectDatabaseUnlink,
706
+ projectAudienceList,
707
+ projectAudienceLink,
708
+ projectAudienceUnlink
709
+ ] as const;