@deveye/types 0.15.2 → 0.16.1
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.
- package/package.json +6 -6
- package/src/domain/device.ts +4 -29
- package/src/domain/featureRegistry.ts +40 -108
- package/src/domain/home.ts +40 -101
- package/src/domain/live.ts +36 -87
- package/src/domain/metrics.ts +10 -14
- package/src/domain/notifications.ts +39 -110
- package/src/domain/project.ts +3 -162
- package/src/domain/report.ts +54 -95
- package/src/domain/secrecy.ts +3 -5
- package/src/domain/sharing.ts +33 -70
- package/src/domain/syncProtocol.ts +5 -12
- package/src/domain/user.ts +8 -14
- package/src/domain/workspace.ts +0 -3
- package/src/domain/workspaceRole.ts +34 -82
- package/src/features/agent.ts +306 -0
- package/src/features/live.ts +17 -37
- package/src/features/notify.ts +19 -57
- package/src/features/registry.ts +7 -44
- package/src/features/secrecy.ts +4 -9
- package/src/features/sharing.ts +12 -26
- package/src/features/user.ts +6 -11
- package/src/features/workspace.ts +6 -11
- package/src/http/auth.ts +6 -13
- package/src/http/device.ts +13 -17
- package/src/http/status.ts +6 -10
- package/src/index.ts +29 -932
- package/src/protocol/agent.ts +40 -70
- package/src/protocol/envelope.ts +3 -10
- package/src/sdk/client-ambient.d.ts +228 -21
- package/src/sdk/client.ts +277 -17
- package/src/sdk/devb.ts +120 -0
- package/src/sdk/manifest.test.ts +0 -1
- package/src/sdk/manifest.ts +94 -25
- package/src/sdk/providers.ts +310 -5
- package/src/sdk/server.ts +483 -19
- package/src/sdk/testing.test.ts +2 -2
- package/src/sdk/testing.ts +302 -26
- package/src/utils/version.ts +5 -8
- package/src/domain/audience.ts +0 -549
- package/src/domain/backup.ts +0 -355
- package/src/domain/credential.ts +0 -55
- package/src/domain/database.ts +0 -467
- package/src/domain/deploy.ts +0 -231
- package/src/domain/finance.ts +0 -477
- package/src/domain/git.ts +0 -419
- package/src/domain/mail.ts +0 -394
- package/src/domain/note.ts +0 -202
- package/src/domain/password.ts +0 -36
- package/src/domain/projectBoard.ts +0 -130
- package/src/domain/projectChat.ts +0 -46
- package/src/domain/projectHistory.ts +0 -82
- package/src/domain/projectLink.ts +0 -87
- package/src/domain/projectPlan.ts +0 -68
- package/src/domain/sentinel.ts +0 -623
- package/src/domain/uptime.ts +0 -216
- package/src/features/audience.ts +0 -275
- package/src/features/backup.ts +0 -230
- package/src/features/database.ts +0 -461
- package/src/features/deploy.ts +0 -245
- package/src/features/device.ts +0 -292
- package/src/features/deviceFiles.ts +0 -83
- package/src/features/deviceLogs.ts +0 -36
- package/src/features/deviceTerminal.ts +0 -57
- package/src/features/finance.ts +0 -360
- package/src/features/git.ts +0 -368
- package/src/features/mail.ts +0 -374
- package/src/features/metrics.ts +0 -185
- package/src/features/note.ts +0 -189
- package/src/features/password.ts +0 -67
- package/src/features/project.ts +0 -709
- package/src/features/sentinel.ts +0 -233
- package/src/features/uptime.ts +0 -186
package/src/domain/live.ts
CHANGED
|
@@ -53,17 +53,10 @@ export function segmentValue(segment: string): string {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* L'
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* lit ou il sélectionne », une main fermée dit « il déplace quelque chose ».
|
|
61
|
-
* Sans lui, tous les pairs seraient perpétuellement en flèche neutre, et le
|
|
62
|
-
* geste d'en face resterait illisible.
|
|
63
|
-
*
|
|
64
|
-
* Volontairement **court** : l'ensemble des curseurs CSS compte une trentaine de
|
|
65
|
-
* valeurs, dont la plupart ne se distinguent pas à seize pixels. Sept familles
|
|
66
|
-
* suffisent, et c'est autant de dessins à tenir.
|
|
56
|
+
* L'état du curseur, tel que le navigateur le dessine à celui qui le tient.
|
|
57
|
+
* Transmis avec la position parce qu'il porte l'intention (cliquer, lire,
|
|
58
|
+
* déplacer). Sept familles seulement : la plupart des curseurs CSS ne se
|
|
59
|
+
* distinguent pas à seize pixels.
|
|
67
60
|
*/
|
|
68
61
|
export const liveCursorKindSchema = z.enum([
|
|
69
62
|
'default',
|
|
@@ -83,26 +76,14 @@ export const liveCursorKindSchema = z.enum([
|
|
|
83
76
|
export type LiveCursorKind = z.infer<typeof liveCursorKindSchema>;
|
|
84
77
|
|
|
85
78
|
/**
|
|
86
|
-
* Position du curseur dans la
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* - `y` est en **pixels absolus du contenu**, défilement compris. Le contenu
|
|
95
|
-
* est le même des deux côtés (même liste, mêmes lignes) : « le pair est sur
|
|
96
|
-
* le 14ᵉ message » est le sens qu'on veut, alors qu'une fraction de la
|
|
97
|
-
* hauteur totale se décalerait dès qu'une liste est chargée plus loin d'un
|
|
98
|
-
* côté que de l'autre.
|
|
99
|
-
*
|
|
100
|
-
* Les bornes de `x` dépassent [0, 1] très largement, et à dessein : le pointeur
|
|
101
|
-
* vit aussi **à côté** de la boîte de contenu — ses marges, les bords de l'écran
|
|
102
|
-
* — et l'y écrêter ferait disparaître le curseur d'un pair alors qu'on est
|
|
103
|
-
* toujours sur la même page. Sur un écran très large, ces marges représentent
|
|
104
|
-
* plusieurs fois la largeur du contenu ; les bornes ne sont donc qu'un garde-fou
|
|
105
|
-
* contre l'absurde, et seul le cadre de la fenêtre décide de ce qui s'affiche.
|
|
79
|
+
* Position du curseur dans la surface de la vue. Unités mixtes à dessein :
|
|
80
|
+
* - `x` est relatif (0..1) à la largeur de la surface, bornée à 1240 px : seule
|
|
81
|
+
* une fraction reste juste quand les deux fenêtres divergent ;
|
|
82
|
+
* - `y` est en pixels absolus du contenu, défilement compris : le contenu est
|
|
83
|
+
* le même des deux côtés, alors qu'une fraction de la hauteur se décalerait
|
|
84
|
+
* dès qu'une liste est chargée plus loin d'un côté.
|
|
85
|
+
* Les bornes de `x` dépassent [0, 1] : le pointeur vit aussi dans les marges,
|
|
86
|
+
* et l'écrêter ferait disparaître le curseur d'un pair sur la même page.
|
|
106
87
|
*/
|
|
107
88
|
export const liveCursorSchema = z.object({
|
|
108
89
|
x: z.number().min(-10).max(10),
|
|
@@ -112,13 +93,11 @@ export const liveCursorSchema = z.object({
|
|
|
112
93
|
export type LiveCursor = z.infer<typeof liveCursorSchema>;
|
|
113
94
|
|
|
114
95
|
/**
|
|
115
|
-
* Un pair tel qu'il est diffusé.
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* que la session lui a déjà donnés. Seule la **couleur** voyage, parce qu'elle
|
|
121
|
-
* doit changer à l'instant où son propriétaire la change.
|
|
96
|
+
* Un pair tel qu'il est diffusé. Ni pseudo ni avatar : `users.avatar` est une
|
|
97
|
+
* URL de données pouvant atteindre 1,5 Mo, et le roster repart à chaque
|
|
98
|
+
* changement de chemin ; le client résout les deux par `userId` contre les
|
|
99
|
+
* membres de l'espace. Seule la couleur voyage, parce qu'elle doit changer à
|
|
100
|
+
* l'instant où son propriétaire la change.
|
|
122
101
|
*/
|
|
123
102
|
export const livePeerSchema = z.object({
|
|
124
103
|
/** Identité de la *connexion*, pas du compte : deux onglets = deux pairs. */
|
|
@@ -141,50 +120,32 @@ export type LivePeer = z.infer<typeof livePeerSchema>;
|
|
|
141
120
|
*/
|
|
142
121
|
export const nativeLiveTopicSchema = z.enum([
|
|
143
122
|
...workspaceFeatureIdSchema.options,
|
|
144
|
-
/**
|
|
145
|
-
* Les messages des projets, séparés de `projects` exprès.
|
|
146
|
-
*
|
|
147
|
-
* Une feature vaut normalement un sujet, mais un fil de discussion bat à une
|
|
148
|
-
* toute autre cadence que la structure qui le porte : sans cette coupure,
|
|
149
|
-
* chaque message ferait re-solliciter le tableau, la frise et le portefeuille
|
|
150
|
-
* entiers. `TOPIC_FEATURE` le rattache au même droit — c'est bien la même
|
|
151
|
-
* feature, vue à deux vitesses.
|
|
152
|
-
*/
|
|
153
|
-
'projectsChat',
|
|
154
123
|
/** Membres, rôles, nom, logo de l'espace. */
|
|
155
124
|
'workspace',
|
|
156
125
|
/**
|
|
157
|
-
* L'accueil de l'espace :
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* commande (`workspace.activate`) les rend tous les deux, donc les séparer
|
|
161
|
-
* en deux sujets ne ferait que doubler les allers-retours pour un même
|
|
162
|
-
* rafraîchissement. Ce sont aussi des réglages **de l'espace** : `account`
|
|
163
|
-
* ne conviendrait pas au thème, il ne sort jamais de l'espace personnel.
|
|
126
|
+
* L'accueil de l'espace : disposition et apparence. Ensemble parce qu'ils
|
|
127
|
+
* se relisent ensemble (`workspace.activate` rend les deux), et sous
|
|
128
|
+
* l'espace et non `account` : le thème est un réglage de l'espace.
|
|
164
129
|
*/
|
|
165
130
|
'home',
|
|
166
131
|
/** Réglages de compte (avatar, couleur, thème, chiffrement). */
|
|
167
132
|
'account',
|
|
168
133
|
/**
|
|
169
|
-
* Les canaux d'alerte de l'espace
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* au sujet de l'espace ferait re-solliciter la liste des membres, les rôles
|
|
174
|
-
* et le nom à chaque fois qu'on coche une case. Ni `uptime` ni ses voisins
|
|
175
|
-
* ne conviennent non plus : une route change pour **une** fonctionnalité,
|
|
176
|
-
* mais un canal change pour toutes à la fois, et `mutates` est déclaré par
|
|
177
|
-
* commande, pas par argument.
|
|
134
|
+
* Les canaux d'alerte de l'espace et les routes qui pointent dessus. Un
|
|
135
|
+
* sujet à part : les canaux se relisent depuis les réglages de n'importe
|
|
136
|
+
* quelle fonctionnalité, et un canal change pour toutes à la fois alors que
|
|
137
|
+
* `mutates` est déclaré par commande.
|
|
178
138
|
*/
|
|
179
139
|
'notify'
|
|
180
140
|
]);
|
|
181
141
|
export type NativeLiveTopic = z.infer<typeof nativeLiveTopicSchema>;
|
|
182
142
|
|
|
183
143
|
/**
|
|
184
|
-
* Un module
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
144
|
+
* Un module vaut **un** sujet, qui est son id, plus les sujets secondaires que
|
|
145
|
+
* son manifest déclare (`topics`, validés au boot : `projectsChat` bat les
|
|
146
|
+
* messages sans faire re-solliciter le tableau). Ces sujets-là ne figurent pas
|
|
147
|
+
* ici : l'app les lit dans les manifests installés. Le préfixe `x-` garantit
|
|
148
|
+
* qu'un sujet externe ne percute ni une feature native ni un sujet réservé.
|
|
188
149
|
*/
|
|
189
150
|
export const liveTopicSchema = z.union([nativeLiveTopicSchema, externalFeatureIdSchema]);
|
|
190
151
|
export type LiveTopic = z.infer<typeof liveTopicSchema>;
|
|
@@ -218,7 +179,6 @@ export const TOPIC_FEATURE: Record<NativeLiveTopic, WorkspaceFeatureId | null> =
|
|
|
218
179
|
uptime: 'uptime',
|
|
219
180
|
mail: 'mail',
|
|
220
181
|
projects: 'projects',
|
|
221
|
-
projectsChat: 'projects',
|
|
222
182
|
git: 'git',
|
|
223
183
|
deploy: 'deploy',
|
|
224
184
|
database: 'database',
|
|
@@ -229,28 +189,17 @@ export const TOPIC_FEATURE: Record<NativeLiveTopic, WorkspaceFeatureId | null> =
|
|
|
229
189
|
workspace: null,
|
|
230
190
|
home: null,
|
|
231
191
|
account: null,
|
|
232
|
-
// Aucun droit de feature à vérifier : la
|
|
233
|
-
//
|
|
234
|
-
// commande (droits de la fonctionnalité et gestion de ses canaux). Même
|
|
235
|
-
// nature que `workspace`.
|
|
192
|
+
// Aucun droit de feature à vérifier : la relecture déclenchée est gardée
|
|
193
|
+
// côté commande. Même nature que `workspace`.
|
|
236
194
|
notify: null
|
|
237
195
|
};
|
|
238
196
|
|
|
239
197
|
/**
|
|
240
|
-
* Le droit qu'exige la
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* - `'public'` → montré à tout membre (aujourd'hui : personne, gardé pour un
|
|
246
|
-
* éventuel lieu commun) ;
|
|
247
|
-
* - `'private'` → **jamais montré**, à personne.
|
|
248
|
-
*
|
|
249
|
-
* Les vues de compte et d'administration (Profil, Sécurité, Journaux,
|
|
250
|
-
* Utilisateurs, Gestion de l'espace) tombent dans `'private'`. `featureBehind`
|
|
251
|
-
* côté client leur rend `null` parce qu'elles ont leurs propres gardes ; ici
|
|
252
|
-
* `null` voudrait dire « visible par tous », ce qui ferait fuiter « untel est
|
|
253
|
-
* dans Sécurité ». D'où le troisième cas, plutôt qu'une réutilisation directe.
|
|
198
|
+
* Le droit qu'exige la racine d'un chemin, pour décider si un pair est montré
|
|
199
|
+
* là où il est ou renvoyé à « ailleurs » : une feature (montré à qui a `read`
|
|
200
|
+
* dessus), `'public'` (tout membre), `'private'` (jamais montré). Les vues de
|
|
201
|
+
* compte et d'administration tombent dans `'private'` : `null` voudrait dire
|
|
202
|
+
* « visible par tous » et ferait fuiter « untel est dans Sécurité ».
|
|
254
203
|
*/
|
|
255
204
|
export type LivePathGate = FeatureId | 'public' | 'private';
|
|
256
205
|
|
package/src/domain/metrics.ts
CHANGED
|
@@ -49,11 +49,9 @@ export const metricSnapshotSchema = z.object({
|
|
|
49
49
|
batteryCharging: z.boolean().nullable().default(null),
|
|
50
50
|
/**
|
|
51
51
|
* Programs running at this instant, heaviest first, aggregated by name.
|
|
52
|
-
* `null`
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* bounded). Persisted separately (`device_process_samples`) under this row's
|
|
56
|
-
* `timestamp`, so it is *not* echoed back by `metrics.query`.
|
|
52
|
+
* `null` = not carried by this row (capture mode `off`, or trimmed from a
|
|
53
|
+
* long offline queue). Persisted separately (`device_process_samples`)
|
|
54
|
+
* under this row's `timestamp`, so `metrics.query` does not echo it back.
|
|
57
55
|
*/
|
|
58
56
|
processes: z.array(reportProcessSchema).max(2000).nullable().default(null),
|
|
59
57
|
/**
|
|
@@ -67,10 +65,10 @@ export const metricSnapshotSchema = z.object({
|
|
|
67
65
|
export type MetricSnapshot = z.infer<typeof metricSnapshotSchema>;
|
|
68
66
|
|
|
69
67
|
/**
|
|
70
|
-
* Batch of snapshots pushed by an agent over the agent WebSocket. Bounded
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
68
|
+
* Batch of snapshots pushed by an agent over the agent WebSocket. Bounded so an
|
|
69
|
+
* offline queue drains in chunks; the agent additionally caps a batch by
|
|
70
|
+
* serialized size, since 100 snapshots with process lists would make a
|
|
71
|
+
* multi-megabyte frame.
|
|
74
72
|
*/
|
|
75
73
|
export const metricsBatchSchema = z.object({
|
|
76
74
|
deviceId: z.uuid(),
|
|
@@ -88,11 +86,9 @@ export const metricsResolutionSchema = z.enum(['raw', 'minute', 'hour']);
|
|
|
88
86
|
export type MetricsResolution = z.infer<typeof metricsResolutionSchema>;
|
|
89
87
|
|
|
90
88
|
/**
|
|
91
|
-
* A point read back from `device_metrics
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* points, and carrying every process list along would cost megabytes for data
|
|
95
|
-
* the graphs never read.
|
|
89
|
+
* A point read back from `device_metrics`: a snapshot minus its process list,
|
|
90
|
+
* which lives in its own table (`metrics.processesAt`). A graph window holds
|
|
91
|
+
* hundreds of points; carrying every process list along would cost megabytes.
|
|
96
92
|
*/
|
|
97
93
|
export const metricSeriesPointSchema = metricSnapshotSchema.omit({
|
|
98
94
|
processes: true,
|
|
@@ -4,64 +4,27 @@ import { NOTIFYING_FEATURES } from './featureRegistry';
|
|
|
4
4
|
import { externalFeatureIdSchema } from './workspaceRole';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Les canaux d'alerte d'un espace
|
|
7
|
+
* Les canaux d'alerte d'un espace : une liste, et des liaisons vers elle.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Un canal est une destination nommée (type, libellé, cible) qui appartient à
|
|
10
|
+
* une fonctionnalité : c'est une source de cette fonctionnalité, gérée dans
|
|
11
|
+
* ses réglages. Deux features qui préviennent le même salon le déclarent deux
|
|
12
|
+
* fois, et chacune se corrige à un seul endroit.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* d'ouvrir cinq écrans, et en oublier un ne se voyait qu'à la première alerte
|
|
16
|
-
* qui n'arrivait plus.
|
|
17
|
-
* 2. **Deux destinataires étaient impossibles.** Une équipe pour la production,
|
|
18
|
-
* une autre pour la recette : il fallait choisir.
|
|
19
|
-
* 3. **Aucun routage par élément.** Toutes les bases d'un espace prévenaient les
|
|
20
|
-
* mêmes gens, quel que soit le projet derrière.
|
|
21
|
-
*
|
|
22
|
-
* ## La forme retenue
|
|
23
|
-
*
|
|
24
|
-
* Un **canal** est une destination nommée : un type, un libellé, une cible. Il
|
|
25
|
-
* appartient à **une fonctionnalité** (091) : c'est une source de cette
|
|
26
|
-
* fonctionnalité, au même titre qu'un jeton Dokploy pour le Déploiement, et il
|
|
27
|
-
* se gère dans ses réglages à elle. La 087 l'avait fait vivre à l'échelle de
|
|
28
|
-
* l'espace, partagé par les cinq émetteurs ; on retrouvait alors une liste
|
|
29
|
-
* commune gérée depuis cinq endroits, l'inverse du patron des sources. Le prix
|
|
30
|
-
* assumé du retour : deux features qui préviennent le même salon le déclarent
|
|
31
|
-
* deux fois. On en déclare autant qu'on veut, et on les corrige **à un seul
|
|
32
|
-
* endroit** : les réglages de leur fonctionnalité.
|
|
33
|
-
*
|
|
34
|
-
* Une **route** dit qui écrit vers quels canaux, et la sélection vit **sur
|
|
35
|
-
* l'élément** (092) : chaque cible coche un ou plusieurs canaux de sa feature
|
|
36
|
-
* dans ses propres réglages, et sans sélection rien ne part. L'héritage
|
|
37
|
-
* d'une « route de la fonctionnalité » a été essayé (087) puis retiré : cocher
|
|
38
|
-
* un canal à l'échelle de la feature ne visait aucun élément nommable, et les
|
|
39
|
-
* cases des éléments, grisées tant qu'ils « suivaient » la feature, semblaient
|
|
40
|
-
* ne jamais pouvoir se cocher. Une route de fonctionnalité (`itemId` absent)
|
|
41
|
-
* ne subsiste que pour les émetteurs **sans éléments** (Sentinelle), dont les
|
|
42
|
-
* alertes ne visent rien de plus fin.
|
|
43
|
-
*
|
|
44
|
-
* Ce qui n'a pas changé, et qui compte : **tout est éteint par défaut.** Sans
|
|
45
|
-
* canal ni route, rien ne part. Une fonctionnalité qui se met à écrire à des
|
|
46
|
-
* gens sans qu'ils l'aient demandé reste le travers que ce modèle refuse.
|
|
14
|
+
* Une route dit qui écrit vers quels canaux ; la sélection vit sur l'élément.
|
|
15
|
+
* Une route de fonctionnalité (`itemId` absent) ne subsiste que pour les
|
|
16
|
+
* émetteurs sans éléments (Sentinelle). Tout est éteint par défaut : sans
|
|
17
|
+
* canal ni route, rien ne part.
|
|
47
18
|
*/
|
|
48
19
|
|
|
49
20
|
/**
|
|
50
|
-
* Le type d'un canal, et ce qu'il change à l'envoi.
|
|
51
|
-
*
|
|
52
|
-
* `
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* - `email` — un compte Mail « open » de l'espace expédie vers une adresse.
|
|
59
|
-
* - `webhook` — un POST JSON générique : le message lisible est répété dans
|
|
60
|
-
* `content` (Discord) et `text` (Slack), les champs structurés suivent pour
|
|
61
|
-
* un point d'entrée maison. Aucune des trois têtes ne gêne les autres.
|
|
62
|
-
* - `discord` — la mise en page riche de Discord (embeds, couleurs, champs),
|
|
63
|
-
* et pour le déploiement le **suivi vivant** : un seul message qui se met à
|
|
64
|
-
* jour du début à la fin.
|
|
21
|
+
* Le type d'un canal, et ce qu'il change à l'envoi. `webhook` et `discord`
|
|
22
|
+
* sont une déclaration, jamais devinés d'après l'URL.
|
|
23
|
+
* - `email` : un compte Mail « open » de l'espace expédie vers une adresse.
|
|
24
|
+
* - `webhook` : un POST JSON générique, le message lisible répété dans
|
|
25
|
+
* `content` (Discord) et `text` (Slack), les champs structurés à côté.
|
|
26
|
+
* - `discord` : embeds, couleurs, champs, et le suivi vivant d'un déploiement
|
|
27
|
+
* (un seul message mis à jour du début à la fin).
|
|
65
28
|
*/
|
|
66
29
|
export const notificationChannelKindSchema = z.enum(['email', 'webhook', 'discord']);
|
|
67
30
|
export type NotificationChannelKind = z.infer<typeof notificationChannelKindSchema>;
|
|
@@ -69,12 +32,9 @@ export type NotificationChannelKind = z.infer<typeof notificationChannelKindSche
|
|
|
69
32
|
export const NOTIFICATION_CHANNEL_KINDS = notificationChannelKindSchema.options;
|
|
70
33
|
|
|
71
34
|
/**
|
|
72
|
-
* Les fonctionnalités
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* posée sur une fonctionnalité qui n'écrira jamais. Il double le drapeau
|
|
76
|
-
* `notifies` du registre, et le contrôle en bas de fichier interdit qu'ils
|
|
77
|
-
* divergent.
|
|
35
|
+
* Les fonctionnalités natives qui savent prévenir. Enum fermé : c'est lui qui
|
|
36
|
+
* garde une route d'être posée sur une fonctionnalité qui n'écrira jamais. Il
|
|
37
|
+
* double `notifies` du registre ; le contrôle en bas de fichier les tient égaux.
|
|
78
38
|
*/
|
|
79
39
|
export const nativeNotificationFeatureSchema = z.enum([
|
|
80
40
|
'uptime',
|
|
@@ -102,11 +62,9 @@ export const NOTIFICATION_TARGET_MAX = 2048;
|
|
|
102
62
|
export const NOTIFICATION_EMAIL_MAX = 320;
|
|
103
63
|
|
|
104
64
|
/**
|
|
105
|
-
* Un canal tel que le client le reçoit.
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* pouvoir relire pour la corriger. Elle est chiffrée au repos (étage ouvert),
|
|
109
|
-
* comme l'étaient déjà les réglages qu'elle remplace.
|
|
65
|
+
* Un canal tel que le client le reçoit. `target` sort en clair : c'est une
|
|
66
|
+
* adresse que son auteur a saisie et doit pouvoir relire. Chiffrée au repos
|
|
67
|
+
* (étage ouvert).
|
|
110
68
|
*/
|
|
111
69
|
export const notificationChannelSchema = z.object({
|
|
112
70
|
id: z.number().int().positive(),
|
|
@@ -115,35 +73,25 @@ export const notificationChannelSchema = z.object({
|
|
|
115
73
|
label: z.string().min(1).max(NOTIFICATION_LABEL_MAX),
|
|
116
74
|
/**
|
|
117
75
|
* Adresse destinataire (`email`) ou URL appelée en POST (`webhook`,
|
|
118
|
-
* `discord`)
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* destinations pour router vers l'une d'elles. Leur *contenu* ne l'est
|
|
123
|
-
* pas : confier le réglage d'Uptime ne confie pas l'adresse de l'astreinte
|
|
124
|
-
* ni l'URL du salon de production. On voit donc « Astreinte · e-mail », on
|
|
125
|
-
* peut y router, et on ne peut ni la lire ni la modifier.
|
|
76
|
+
* `discord`). Vide pour qui n'a pas la gestion des canaux de la
|
|
77
|
+
* fonctionnalité (`channels` du grant) : la liste est lisible avec la
|
|
78
|
+
* fonctionnalité, pour router, mais confier le réglage d'Uptime ne confie
|
|
79
|
+
* pas l'adresse de l'astreinte.
|
|
126
80
|
*/
|
|
127
81
|
target: z.string().max(NOTIFICATION_TARGET_MAX),
|
|
128
82
|
/** Le compte Mail expéditeur ; `null` hors des canaux `email`. */
|
|
129
83
|
mailAccountId: z.number().int().positive().nullable(),
|
|
130
84
|
/**
|
|
131
|
-
* Ce canal partirait-il
|
|
132
|
-
*
|
|
133
|
-
* Faux quand le compte expéditeur manque, a disparu, est désactivé ou n'est
|
|
134
|
-
* pas au palier « open ». L'interface le dit au lieu de laisser croire à un
|
|
135
|
-
* canal actif — l'avertissement n'existait à l'origine que dans Uptime, et
|
|
136
|
-
* son absence ailleurs faisait passer un canal muet pour un canal réglé.
|
|
85
|
+
* Ce canal partirait-il maintenant ? Faux quand le compte expéditeur
|
|
86
|
+
* manque, a disparu, est désactivé ou n'est pas au palier « open ».
|
|
137
87
|
*/
|
|
138
88
|
ready: z.boolean(),
|
|
139
89
|
/** Éteint sans être supprimé : ses routes restent, rien ne part. */
|
|
140
90
|
enabled: z.boolean(),
|
|
141
91
|
position: z.number().int().nonnegative(),
|
|
142
92
|
/**
|
|
143
|
-
* Combien de routes le désignent
|
|
144
|
-
*
|
|
145
|
-
* la main, et les demander toutes pour afficher un nombre serait une
|
|
146
|
-
* requête par ligne.
|
|
93
|
+
* Combien de routes le désignent (« utilisé par N »). Compté côté serveur :
|
|
94
|
+
* le client n'a pas les routes des éléments sous la main.
|
|
147
95
|
*/
|
|
148
96
|
usageCount: z.number().int().nonnegative()
|
|
149
97
|
});
|
|
@@ -172,13 +120,7 @@ export const notificationRouteTargetSchema = z.object({
|
|
|
172
120
|
});
|
|
173
121
|
export type NotificationRouteTarget = z.infer<typeof notificationRouteTargetSchema>;
|
|
174
122
|
|
|
175
|
-
/**
|
|
176
|
-
* Où écrit une cible : sa sélection de canaux, rien de plus.
|
|
177
|
-
*
|
|
178
|
-
* Vide, elle ne prévient personne — il n'y a plus d'héritage à distinguer
|
|
179
|
-
* (092), donc plus de drapeau `inherits` : une sélection vide et une sélection
|
|
180
|
-
* jamais faite disent la même chose, le silence.
|
|
181
|
-
*/
|
|
123
|
+
/** Où écrit une cible : sa sélection de canaux. Vide, elle ne prévient personne. */
|
|
182
124
|
export const notificationRouteSchema = z.object({
|
|
183
125
|
channelIds: z.array(z.number().int().positive())
|
|
184
126
|
});
|
|
@@ -191,21 +133,15 @@ export const notificationRouteInputSchema = notificationRouteTargetSchema.extend
|
|
|
191
133
|
export type NotificationRouteInput = z.infer<typeof notificationRouteInputSchema>;
|
|
192
134
|
|
|
193
135
|
/**
|
|
194
|
-
* Ce que rend un envoi d'essai : parti, ou pourquoi non.
|
|
195
|
-
*
|
|
196
|
-
* `sent: false` avec un `error` n'est pas une exception — « aucun canal activé »
|
|
197
|
-
* est une réponse, pas une panne, et la remonter comme telle laisserait l'écran
|
|
198
|
-
* afficher « échec » là où il n'y a rien à échouer.
|
|
136
|
+
* Ce que rend un envoi d'essai : parti, ou pourquoi non. `sent: false` avec un
|
|
137
|
+
* `error` n'est pas une exception : « aucun canal activé » est une réponse.
|
|
199
138
|
*/
|
|
200
139
|
export const notificationTestSchema = z.object({ sent: z.boolean(), error: z.string().nullable() });
|
|
201
140
|
export type NotificationTest = z.infer<typeof notificationTestSchema>;
|
|
202
141
|
|
|
203
142
|
/**
|
|
204
|
-
* Ce qu'une suppression de canal emporte
|
|
205
|
-
*
|
|
206
|
-
* Rendu **avant** la suppression pour que la confirmation nomme ce qui va
|
|
207
|
-
* cesser de prévenir, plutôt que de demander « êtes-vous sûr ? » sans dire de
|
|
208
|
-
* quoi. Une liste vide veut dire qu'aucune route ne le désigne.
|
|
143
|
+
* Ce qu'une suppression de canal emporte, rendu avant la suppression pour que
|
|
144
|
+
* la confirmation nomme ce qui va cesser de prévenir. Liste vide = aucune route.
|
|
209
145
|
*/
|
|
210
146
|
export const notificationChannelUsageSchema = z.object({
|
|
211
147
|
channelId: z.number().int().positive(),
|
|
@@ -224,7 +160,7 @@ export type NotificationChannelUsage = z.infer<typeof notificationChannelUsageSc
|
|
|
224
160
|
export interface NotificationChannelRow {
|
|
225
161
|
id: number;
|
|
226
162
|
workspace_id: number;
|
|
227
|
-
/** La fonctionnalité propriétaire : un canal est une source de
|
|
163
|
+
/** La fonctionnalité propriétaire : un canal est une source de sa feature. */
|
|
228
164
|
feature: NotificationFeature;
|
|
229
165
|
kind: NotificationChannelKind;
|
|
230
166
|
label_enc: string;
|
|
@@ -245,16 +181,9 @@ export interface NotificationRouteRow {
|
|
|
245
181
|
}
|
|
246
182
|
|
|
247
183
|
/**
|
|
248
|
-
* Contrôle de cohérence
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* tenir séparés est un choix (l'un décrit, l'autre valide), les laisser diverger
|
|
252
|
-
* n'en est pas un. Une fonctionnalité marquée `notifies` mais absente de l'enum
|
|
253
|
-
* afficherait un onglet Notifications dont toutes les commandes seraient
|
|
254
|
-
* refusées — un écran qui ment, découvert à la première alerte attendue.
|
|
255
|
-
*
|
|
256
|
-
* Même esprit que le contrôle des sujets `mutates` côté serveur : attraper
|
|
257
|
-
* l'oubli au démarrage plutôt qu'en production.
|
|
184
|
+
* Contrôle de cohérence au chargement : une fonctionnalité marquée `notifies`
|
|
185
|
+
* mais absente de l'enum afficherait un onglet Notifications dont toutes les
|
|
186
|
+
* commandes seraient refusées.
|
|
258
187
|
*/
|
|
259
188
|
{
|
|
260
189
|
const registry = [...NOTIFYING_FEATURES].sort().join(', ');
|
package/src/domain/project.ts
CHANGED
|
@@ -1,168 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* dont le serveur a besoin pour lister, trier, compter et router sans rien
|
|
8
|
-
* déchiffrer — `workspace_id`, `status`, `security_tier`, `sort_order`, les
|
|
9
|
-
* dates, `archived_at`. **Chiffré** tout ce qui identifie : titre, description,
|
|
10
|
-
* étiquettes, version.
|
|
11
|
-
*
|
|
12
|
-
* Comme le mail, et contrairement à Uptime ou au coffre, l'étage de chiffrement
|
|
13
|
-
* n'est pas fixé par la feature mais **choisi par projet** (`securityTier`) :
|
|
14
|
-
* un projet `open` peut être synchronisé en tâche de fond (dépôt git,
|
|
15
|
-
* déploiement) ; un projet `guarded` ne se déchiffre que pendant une session
|
|
16
|
-
* vivante et déverrouillée, et perd donc ses intégrations automatiques.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
export const PROJECT_TITLE_MAX_LENGTH = 120;
|
|
20
|
-
export const PROJECT_DESCRIPTION_MAX_LENGTH = 4000;
|
|
21
|
-
export const PROJECT_VERSION_MAX_LENGTH = 40;
|
|
22
|
-
export const PROJECT_TAG_LABEL_MAX_LENGTH = 32;
|
|
23
|
-
export const PROJECT_MAX_TAGS = 24;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Borne de l'icône d'un projet, en caractères de son URL de données.
|
|
27
|
-
*
|
|
28
|
-
* ~400 ko : de quoi loger confortablement une vignette carrée redimensionnée
|
|
29
|
-
* par le client, sans laisser une charge utile WS grossir au gré de ce qu'on
|
|
30
|
-
* dépose. L'icône vit **dans le payload chiffré** comme le titre : elle
|
|
31
|
-
* identifie le projet autant qu'un nom, et un projet confidentiel ne doit pas
|
|
32
|
-
* la laisser lire.
|
|
33
|
-
*/
|
|
34
|
-
export const PROJECT_ICON_MAX_LENGTH = 400_000;
|
|
35
|
-
|
|
36
|
-
/** Vide = icône par défaut. Sinon, une URL de données d'image. */
|
|
37
|
-
export const projectIconSchema = z
|
|
38
|
-
.string()
|
|
39
|
-
.max(PROJECT_ICON_MAX_LENGTH)
|
|
40
|
-
.refine((v) => v === '' || /^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/]+=*$/.test(v), {
|
|
41
|
-
message: 'L’icône doit être une image encodée en base64.'
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
/** Quel coffre chiffre l'arbre du projet. Voir l'en-tête de ce fichier. */
|
|
45
|
-
export const projectSecurityTierSchema = z.enum(['open', 'guarded']);
|
|
46
|
-
export type ProjectSecurityTier = z.infer<typeof projectSecurityTierSchema>;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Où en est le projet. Volontairement court : c'est un état de pilotage, pas un
|
|
50
|
-
* workflow — le détail de l'avancement vit dans les colonnes du kanban.
|
|
4
|
+
* Où en est un projet : un état de pilotage, pas un workflow (l'avancement vit
|
|
5
|
+
* dans les colonnes du kanban). Seul vocabulaire de Projets gardé ici, parce que
|
|
6
|
+
* d'autres features le parlent (`ProjectUsage.status`, `sdk/providers.ts`).
|
|
51
7
|
*/
|
|
52
8
|
export const projectStatusSchema = z.enum(['draft', 'active', 'paused', 'done']);
|
|
53
9
|
export type ProjectStatus = z.infer<typeof projectStatusSchema>;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* D'où vient le numéro de version affiché.
|
|
57
|
-
* - `manual` — saisi à la main dans le profil du projet ;
|
|
58
|
-
* - `github_release` — la dernière release publiée du dépôt lié. Le champ
|
|
59
|
-
* devient alors en lecture seule dans l'interface, et un projet `guarded` ne
|
|
60
|
-
* peut pas le choisir (sa synchronisation de fond est impossible).
|
|
61
|
-
*/
|
|
62
|
-
export const projectVersionSourceSchema = z.enum(['manual', 'github_release']);
|
|
63
|
-
export type ProjectVersionSource = z.infer<typeof projectVersionSourceSchema>;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Les deux familles d'étiquettes, cumulables sur un même projet :
|
|
67
|
-
* - `type` — ce que le projet *est* (app mobile, site web, service…) ;
|
|
68
|
-
* - `tech` — ce avec quoi il est fait (react, react native, express, vite…).
|
|
69
|
-
*
|
|
70
|
-
* Le libellé est libre plutôt qu'énuméré : une pile technique se renouvelle plus
|
|
71
|
-
* vite qu'un schéma, et une valeur inconnue ne doit jamais faire disparaître un
|
|
72
|
-
* projet de la liste. Les étiquettes voyagent **dans le payload chiffré** — le
|
|
73
|
-
* filtrage se fait donc côté client, ce qui suffit largement à cette échelle.
|
|
74
|
-
*/
|
|
75
|
-
export const projectTagKindSchema = z.enum(['type', 'tech']);
|
|
76
|
-
export type ProjectTagKind = z.infer<typeof projectTagKindSchema>;
|
|
77
|
-
|
|
78
|
-
export const projectTagSchema = z.object({
|
|
79
|
-
kind: projectTagKindSchema,
|
|
80
|
-
label: z.string().min(1).max(PROJECT_TAG_LABEL_MAX_LENGTH)
|
|
81
|
-
});
|
|
82
|
-
export type ProjectTag = z.infer<typeof projectTagSchema>;
|
|
83
|
-
|
|
84
|
-
export const projectSchema = z.object({
|
|
85
|
-
id: z.number().int().positive(),
|
|
86
|
-
title: z.string().max(PROJECT_TITLE_MAX_LENGTH),
|
|
87
|
-
/** Vignette du projet ; vide = l'icône par défaut de la feature. */
|
|
88
|
-
icon: projectIconSchema,
|
|
89
|
-
description: z.string().max(PROJECT_DESCRIPTION_MAX_LENGTH),
|
|
90
|
-
tags: z.array(projectTagSchema).max(PROJECT_MAX_TAGS),
|
|
91
|
-
version: z.string().max(PROJECT_VERSION_MAX_LENGTH),
|
|
92
|
-
versionSource: projectVersionSourceSchema,
|
|
93
|
-
status: projectStatusSchema,
|
|
94
|
-
securityTier: projectSecurityTierSchema,
|
|
95
|
-
/** Bornes de la fenêtre du projet, en secondes unix. Facultatives. */
|
|
96
|
-
startDate: z.number().int().nullable(),
|
|
97
|
-
dueDate: z.number().int().nullable(),
|
|
98
|
-
sortOrder: z.number().int().nonnegative(),
|
|
99
|
-
/**
|
|
100
|
-
* Qui l'a créé : attribution, jamais une frontière d'accès. `null` quand le
|
|
101
|
-
* compte a été supprimé depuis — un départ n'emporte pas le travail d'un
|
|
102
|
-
* espace partagé (cf. `ON DELETE SET NULL` dans la migration).
|
|
103
|
-
*/
|
|
104
|
-
authorUserId: z.number().int().positive().nullable(),
|
|
105
|
-
archived: z.boolean(),
|
|
106
|
-
created: z.number().int(),
|
|
107
|
-
updated: z.number().int()
|
|
108
|
-
});
|
|
109
|
-
export type Project = z.infer<typeof projectSchema>;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* La ligne du portefeuille : le projet, plus ce que le serveur sait compter
|
|
113
|
-
* **sans déchiffrer** (colonnes en clair uniquement).
|
|
114
|
-
*
|
|
115
|
-
* `masked: true` désigne un projet `guarded` dont le corps n'a pas pu être
|
|
116
|
-
* déchiffré parce que la session est verrouillée. La ligne reste listée, avec
|
|
117
|
-
* ses compteurs — on doit pouvoir voir qu'un projet existe, et le déverrouiller
|
|
118
|
-
* en connaissance de cause, sans que la liste entière disparaisse. Même parti
|
|
119
|
-
* pris que les notes privées.
|
|
120
|
-
*/
|
|
121
|
-
export const projectSummarySchema = z.object({
|
|
122
|
-
project: projectSchema,
|
|
123
|
-
masked: z.boolean(),
|
|
124
|
-
/** Cartes actives (non archivées) et celles assises dans une colonne de fin. */
|
|
125
|
-
cardTotal: z.number().int().nonnegative(),
|
|
126
|
-
cardDone: z.number().int().nonnegative(),
|
|
127
|
-
/** Cartes actives dont l'échéance est dépassée. */
|
|
128
|
-
cardOverdue: z.number().int().nonnegative(),
|
|
129
|
-
/** Prochaine échéance à venir, toutes cartes confondues. */
|
|
130
|
-
nextDueDate: z.number().int().nullable(),
|
|
131
|
-
/** Messages non lus par l'appelant sur tout le projet. */
|
|
132
|
-
unread: z.number().int().nonnegative()
|
|
133
|
-
});
|
|
134
|
-
export type ProjectSummary = z.infer<typeof projectSummarySchema>;
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Ce que le client peut poser sur un projet. La version n'y est pas quand elle
|
|
138
|
-
* est pilotée par les releases : `project.setVersion` la traite à part, pour que
|
|
139
|
-
* l'édition du profil ne puisse pas écraser en silence une valeur synchronisée.
|
|
140
|
-
*/
|
|
141
|
-
export const projectDraftSchema = z.object({
|
|
142
|
-
title: z.string().min(1).max(PROJECT_TITLE_MAX_LENGTH),
|
|
143
|
-
icon: projectIconSchema,
|
|
144
|
-
description: z.string().max(PROJECT_DESCRIPTION_MAX_LENGTH),
|
|
145
|
-
tags: z.array(projectTagSchema).max(PROJECT_MAX_TAGS),
|
|
146
|
-
status: projectStatusSchema,
|
|
147
|
-
startDate: z.number().int().nullable(),
|
|
148
|
-
dueDate: z.number().int().nullable()
|
|
149
|
-
});
|
|
150
|
-
export type ProjectDraft = z.infer<typeof projectDraftSchema>;
|
|
151
|
-
|
|
152
|
-
/** Ligne SQL (serveur uniquement). `content` porte le payload chiffré. */
|
|
153
|
-
export interface ProjectRow {
|
|
154
|
-
id: number;
|
|
155
|
-
workspace_id: number;
|
|
156
|
-
/** Auteur. En espace partagé il dit qui a créé la ligne, rien de plus. */
|
|
157
|
-
user_id: number | null;
|
|
158
|
-
status: ProjectStatus;
|
|
159
|
-
security_tier: ProjectSecurityTier;
|
|
160
|
-
version_source: ProjectVersionSource;
|
|
161
|
-
sort_order: number;
|
|
162
|
-
start_date: number | null;
|
|
163
|
-
due_date: number | null;
|
|
164
|
-
archived_at: number | null;
|
|
165
|
-
content: string;
|
|
166
|
-
created: number;
|
|
167
|
-
updated: number;
|
|
168
|
-
}
|