@nodefony/drizzle 10.0.0-alpha.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.
Files changed (143) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +162 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/index.js +105 -0
  6. package/dist/nodefony/command/migrateShared.js +247 -0
  7. package/dist/nodefony/command/orm-generate.js +356 -0
  8. package/dist/nodefony/command/orm-migrate-baseline.js +208 -0
  9. package/dist/nodefony/command/orm-migrate-repair.js +114 -0
  10. package/dist/nodefony/command/orm-migrate-status.js +67 -0
  11. package/dist/nodefony/command/orm-migrate.js +141 -0
  12. package/dist/nodefony/command/orm-reset.js +166 -0
  13. package/dist/nodefony/config/config.js +107 -0
  14. package/dist/nodefony/config/defineModuleConfig.js +63 -0
  15. package/dist/nodefony/entity/auditEventEntity.js +93 -0
  16. package/dist/nodefony/entity/colKit.js +260 -0
  17. package/dist/nodefony/entity/idempotencyEntity.js +74 -0
  18. package/dist/nodefony/entity/sessionEntity.js +75 -0
  19. package/dist/nodefony/entity/tokenEntity.js +198 -0
  20. package/dist/nodefony/entity/totpSecretEntity.js +98 -0
  21. package/dist/nodefony/entity/userTable.js +141 -0
  22. package/dist/nodefony/entity/webAuthnCredentialEntity.js +114 -0
  23. package/dist/nodefony/entity/webhookEndpointEntity.js +106 -0
  24. package/dist/nodefony/interfaces/IDrizzleConfig.js +1 -0
  25. package/dist/nodefony/interfaces/index.js +1 -0
  26. package/dist/nodefony/migrations-schema/mysql.js +48 -0
  27. package/dist/nodefony/migrations-schema/postgres.js +48 -0
  28. package/dist/nodefony/migrations-schema/sqlite.js +48 -0
  29. package/dist/nodefony/registerStores.js +218 -0
  30. package/dist/nodefony/service/DrizzleService.js +282 -0
  31. package/dist/nodefony/src/DrizzleAuditStore.js +203 -0
  32. package/dist/nodefony/src/DrizzleIdempotencyStore.js +278 -0
  33. package/dist/nodefony/src/DrizzleTokenStore.js +244 -0
  34. package/dist/nodefony/src/DrizzleTotpSecretStore.js +151 -0
  35. package/dist/nodefony/src/DrizzleUserRepository.js +217 -0
  36. package/dist/nodefony/src/DrizzleWebAuthnCredentialStore.js +159 -0
  37. package/dist/nodefony/src/DrizzleWebhookStore.js +169 -0
  38. package/dist/nodefony/src/SessionStorage.js +259 -0
  39. package/dist/nodefony/src/connectorTarget.js +59 -0
  40. package/dist/nodefony/src/likeSql.js +50 -0
  41. package/dist/nodefony/src/migrator/DrizzleMigrator.js +775 -0
  42. package/dist/nodefony/src/migrator/adopt.js +553 -0
  43. package/dist/nodefony/src/migrator/appSchema.js +414 -0
  44. package/dist/nodefony/src/migrator/catalog.js +76 -0
  45. package/dist/nodefony/src/migrator/destructive.js +213 -0
  46. package/dist/nodefony/src/migrator/divergence.js +84 -0
  47. package/dist/nodefony/src/migrator/drivers/index.js +39 -0
  48. package/dist/nodefony/src/migrator/drivers/mysqlDriver.js +147 -0
  49. package/dist/nodefony/src/migrator/drivers/postgresDriver.js +151 -0
  50. package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +121 -0
  51. package/dist/nodefony/src/migrator/explain.js +565 -0
  52. package/dist/nodefony/src/migrator/hash.js +47 -0
  53. package/dist/nodefony/src/migrator/history.js +219 -0
  54. package/dist/nodefony/src/migrator/index.js +16 -0
  55. package/dist/nodefony/src/migrator/kit.js +296 -0
  56. package/dist/nodefony/src/migrator/name.js +68 -0
  57. package/dist/nodefony/src/migrator/paths.js +88 -0
  58. package/dist/nodefony/src/migrator/refusals.js +143 -0
  59. package/dist/nodefony/src/migrator/resolve.js +281 -0
  60. package/dist/nodefony/src/migrator/schemaDiff.js +86 -0
  61. package/dist/nodefony/src/migrator/sources.js +419 -0
  62. package/dist/nodefony/src/migrator/status.js +231 -0
  63. package/dist/nodefony/src/migrator/types.js +91 -0
  64. package/dist/nodefony/src/orm-core/DrizzleOrm.js +1154 -0
  65. package/dist/nodefony/src/orm-core/DrizzleRepository.js +610 -0
  66. package/dist/nodefony/src/orm-core/DrizzleTransaction.js +106 -0
  67. package/dist/nodefony/src/orm-core/index.js +4 -0
  68. package/dist/nodefony/src/queryKit.js +318 -0
  69. package/dist/nodefony/src/safeTarget.js +55 -0
  70. package/dist/types/index.d.ts +76 -0
  71. package/dist/types/nodefony/command/migrateShared.d.ts +137 -0
  72. package/dist/types/nodefony/command/orm-generate.d.ts +53 -0
  73. package/dist/types/nodefony/command/orm-migrate-baseline.d.ts +87 -0
  74. package/dist/types/nodefony/command/orm-migrate-repair.d.ts +66 -0
  75. package/dist/types/nodefony/command/orm-migrate-status.d.ts +38 -0
  76. package/dist/types/nodefony/command/orm-migrate.d.ts +65 -0
  77. package/dist/types/nodefony/command/orm-reset.d.ts +55 -0
  78. package/dist/types/nodefony/config/config.d.ts +110 -0
  79. package/dist/types/nodefony/config/defineModuleConfig.d.ts +24 -0
  80. package/dist/types/nodefony/entity/auditEventEntity.d.ts +56 -0
  81. package/dist/types/nodefony/entity/colKit.d.ts +130 -0
  82. package/dist/types/nodefony/entity/idempotencyEntity.d.ts +52 -0
  83. package/dist/types/nodefony/entity/sessionEntity.d.ts +50 -0
  84. package/dist/types/nodefony/entity/tokenEntity.d.ts +53 -0
  85. package/dist/types/nodefony/entity/totpSecretEntity.d.ts +61 -0
  86. package/dist/types/nodefony/entity/userTable.d.ts +65 -0
  87. package/dist/types/nodefony/entity/webAuthnCredentialEntity.d.ts +57 -0
  88. package/dist/types/nodefony/entity/webhookEndpointEntity.d.ts +58 -0
  89. package/dist/types/nodefony/interfaces/IDrizzleConfig.d.ts +17 -0
  90. package/dist/types/nodefony/interfaces/index.d.ts +1 -0
  91. package/dist/types/nodefony/migrations-schema/mysql.d.ts +9 -0
  92. package/dist/types/nodefony/migrations-schema/postgres.d.ts +9 -0
  93. package/dist/types/nodefony/migrations-schema/sqlite.d.ts +9 -0
  94. package/dist/types/nodefony/registerStores.d.ts +52 -0
  95. package/dist/types/nodefony/service/DrizzleService.d.ts +27 -0
  96. package/dist/types/nodefony/src/DrizzleAuditStore.d.ts +65 -0
  97. package/dist/types/nodefony/src/DrizzleIdempotencyStore.d.ts +129 -0
  98. package/dist/types/nodefony/src/DrizzleTokenStore.d.ts +146 -0
  99. package/dist/types/nodefony/src/DrizzleTotpSecretStore.d.ts +60 -0
  100. package/dist/types/nodefony/src/DrizzleUserRepository.d.ts +67 -0
  101. package/dist/types/nodefony/src/DrizzleWebAuthnCredentialStore.d.ts +62 -0
  102. package/dist/types/nodefony/src/DrizzleWebhookStore.d.ts +79 -0
  103. package/dist/types/nodefony/src/SessionStorage.d.ts +72 -0
  104. package/dist/types/nodefony/src/connectorTarget.d.ts +48 -0
  105. package/dist/types/nodefony/src/likeSql.d.ts +29 -0
  106. package/dist/types/nodefony/src/migrator/DrizzleMigrator.d.ts +94 -0
  107. package/dist/types/nodefony/src/migrator/adopt.d.ts +280 -0
  108. package/dist/types/nodefony/src/migrator/appSchema.d.ts +223 -0
  109. package/dist/types/nodefony/src/migrator/catalog.d.ts +100 -0
  110. package/dist/types/nodefony/src/migrator/destructive.d.ts +123 -0
  111. package/dist/types/nodefony/src/migrator/divergence.d.ts +61 -0
  112. package/dist/types/nodefony/src/migrator/drivers/index.d.ts +26 -0
  113. package/dist/types/nodefony/src/migrator/drivers/mysqlDriver.d.ts +83 -0
  114. package/dist/types/nodefony/src/migrator/drivers/postgresDriver.d.ts +81 -0
  115. package/dist/types/nodefony/src/migrator/drivers/sqliteDriver.d.ts +53 -0
  116. package/dist/types/nodefony/src/migrator/explain.d.ts +424 -0
  117. package/dist/types/nodefony/src/migrator/hash.d.ts +39 -0
  118. package/dist/types/nodefony/src/migrator/history.d.ts +139 -0
  119. package/dist/types/nodefony/src/migrator/index.d.ts +20 -0
  120. package/dist/types/nodefony/src/migrator/kit.d.ts +141 -0
  121. package/dist/types/nodefony/src/migrator/name.d.ts +52 -0
  122. package/dist/types/nodefony/src/migrator/paths.d.ts +54 -0
  123. package/dist/types/nodefony/src/migrator/refusals.d.ts +170 -0
  124. package/dist/types/nodefony/src/migrator/resolve.d.ts +201 -0
  125. package/dist/types/nodefony/src/migrator/schemaDiff.d.ts +112 -0
  126. package/dist/types/nodefony/src/migrator/sources.d.ts +119 -0
  127. package/dist/types/nodefony/src/migrator/status.d.ts +132 -0
  128. package/dist/types/nodefony/src/migrator/types.d.ts +273 -0
  129. package/dist/types/nodefony/src/orm-core/DrizzleOrm.d.ts +241 -0
  130. package/dist/types/nodefony/src/orm-core/DrizzleRepository.d.ts +98 -0
  131. package/dist/types/nodefony/src/orm-core/DrizzleTransaction.d.ts +71 -0
  132. package/dist/types/nodefony/src/orm-core/index.d.ts +11 -0
  133. package/dist/types/nodefony/src/queryKit.d.ts +136 -0
  134. package/dist/types/nodefony/src/safeTarget.d.ts +42 -0
  135. package/docs/index.md +954 -0
  136. package/docs/migrations.md +691 -0
  137. package/migrations/mysql/0000_framework_init.sql +137 -0
  138. package/migrations/mysql/meta/_journal.json +13 -0
  139. package/migrations/postgres/0000_framework_init.sql +128 -0
  140. package/migrations/postgres/meta/_journal.json +13 -0
  141. package/migrations/sqlite/0000_framework_init.sql +127 -0
  142. package/migrations/sqlite/meta/_journal.json +13 -0
  143. package/package.json +126 -0
@@ -0,0 +1,273 @@
1
+ import type { SqlDialect } from "../../config/config.js";
2
+ import type { ISchemaReader } from "./catalog.js";
3
+ /**
4
+ * URL de connexion réservée au **travail de migration**.
5
+ *
6
+ * Elle porte le compte qui a le droit de modifier le schéma — un compte que les
7
+ * exemplaires qui servent le trafic n'ont pas, et ne doivent pas avoir. C'est
8
+ * tout son intérêt : le secret est monté dans le travail de migration
9
+ * seulement.
10
+ *
11
+ * **Qui la lit, exactement** : les commandes `orm:migrate`,
12
+ * `orm:migrate:status`, `orm:migrate:baseline` et `orm:migrate:repair`.
13
+ * Personne d'autre — ni le démarrage de l'application, ni `orm:reset`, ni un
14
+ * store. Une variable de privilège élevé qui fuiterait dans le chemin ordinaire
15
+ * annulerait le moindre privilège qu'elle sert à installer.
16
+ *
17
+ * ⚠️ Elle doit désigner une connexion **directe** au serveur. Un répartiteur de
18
+ * connexions en mode transaction (PgBouncer `transaction`) casse les verrous
19
+ * consultatifs de session — et le verrou de l'applicateur en est un.
20
+ */
21
+ export declare const MIGRATE_URL_ENV = "NF_MIGRATE_DATABASE_URL";
22
+ /**
23
+ * Contrats de l'applicateur de migrations — verdicts, entrées de journal,
24
+ * historique, plan, et le pilote à connexion unique dont il a besoin.
25
+ *
26
+ * **Pourquoi un fichier de types séparé** : ces formes traversent la frontière
27
+ * npm (CLI, data plane d'administration, sonde de disponibilité, porte MCP) —
28
+ * quatre consommateurs pour un seul producteur. Les tenir à part évite qu'un
29
+ * import de l'applicateur tire les pilotes de base de données avec lui.
30
+ */
31
+ /**
32
+ * Nom de la table d'historique — **jamais qualifié d'un schéma**.
33
+ *
34
+ * Elle suit donc le `search_path` de la connexion, exactement comme les tables
35
+ * d'entités. Écrire `public.nodefony_migrations` exclurait à vie le patron
36
+ * PostgreSQL d'isolation par schéma sur une base mutualisée : déplacer ensuite
37
+ * la table d'historique chez un utilisateur serait une migration de données,
38
+ * pas un correctif.
39
+ */
40
+ export declare const HISTORY_TABLE = "nodefony_migrations";
41
+ /**
42
+ * Marqueur de format que porte la première ligne de chaque fichier `.sql`.
43
+ *
44
+ * Le format de découpe est celui de drizzle-kit, adopté à vie ; un défaut
45
+ * découvert après publication ne pourrait plus être corrigé sans changer le
46
+ * SENS de fichiers déjà livrés. Le marqueur donne la porte de sortie : un
47
+ * fichier d'un autre format est **refusé en le nommant**, jamais lu au mieux.
48
+ */
49
+ export declare const FORMAT_MARKER = "-- nodefony:migration format=1";
50
+ /** Séparateur de statements produit par drizzle-kit. */
51
+ export declare const STATEMENT_BREAKPOINT = "--> statement-breakpoint";
52
+ /**
53
+ * Codes de refus stables de l'applicateur.
54
+ *
55
+ * Ils sont le contrat lu par une machine — un agent lit `code`, jamais une
56
+ * phrase française. Ajouter un code est additif ; en changer un est une
57
+ * rupture, au même titre qu'un code de sortie.
58
+ */
59
+ export type MigrationVerdictCode =
60
+ /** Historique vide alors que les tables du schéma existent déjà. */
61
+ "NF_MIGRATE_BASELINE_REQUIRED"
62
+ /** Une migration a échoué (ou n'a jamais fini) : réparer avant de reprendre. */
63
+ | "NF_MIGRATE_FAILED_MARKER"
64
+ /** Le fichier d'une migration appliquée a changé depuis son application. */
65
+ | "NF_MIGRATE_HASH_MISMATCH"
66
+ /** Une migration en attente se range AVANT la dernière appliquée de sa source. */
67
+ | "NF_MIGRATE_OUT_OF_ORDER"
68
+ /** Une migration appliquée n'a plus de fichier dans une source pourtant présente. */
69
+ | "NF_MIGRATE_MISSING_FILE"
70
+ /** Un fichier ne porte pas le format que cet applicateur sait lire. */
71
+ | "NF_MIGRATE_UNKNOWN_FORMAT"
72
+ /** Le verrou n'a pas pu être obtenu dans le délai imparti. */
73
+ | "NF_MIGRATE_LOCK_TIMEOUT"
74
+ /** Le tag demandé (`--up-to`) ne désigne aucune migration connue. */
75
+ | "NF_MIGRATE_UNKNOWN_TAG"
76
+ /** La source demandée (`--source`) n'est pas déclarée par cette application. */
77
+ | "NF_MIGRATE_UNKNOWN_SOURCE"
78
+ /** Le journal d'une source annonce un fichier que le dossier ne contient pas. */
79
+ | "NF_MIGRATE_JOURNAL_MISMATCH";
80
+ /** Commande à exécuter pour lever un refus — l'agent lit `nextActions[0]`. */
81
+ export interface IMigrationAction {
82
+ /** Commande complète, prête à copier. */
83
+ command: string;
84
+ /** Arguments, séparés pour un appel programmatique. */
85
+ args: string[];
86
+ }
87
+ /**
88
+ * Verdict structuré : **la source**, dont la prose n'est qu'un rendu.
89
+ *
90
+ * Un producteur, quatre rendus (phrase de la CLI, `--json`, data plane,
91
+ * indication dans un corps d'erreur). Écrire le message pour l'humain ET un
92
+ * JSON pour la machine ferait deux implémentations d'une même règle, qui
93
+ * divergeraient.
94
+ */
95
+ export interface IMigrationVerdict {
96
+ /** Code stable du refus. */
97
+ code: MigrationVerdictCode;
98
+ /** Connecteur concerné. */
99
+ connector: string;
100
+ /** Source de migrations concernée, quand le refus en désigne une. */
101
+ source?: string;
102
+ /** Tag concerné, quand le refus en désigne un. */
103
+ tag?: string;
104
+ /** Faits constatés, sans mise en forme — de quoi rendre la phrase. */
105
+ facts: Record<string, string | number | boolean | readonly string[]>;
106
+ /** Ce qu'il faut faire ensuite, du plus direct au plus assumé. */
107
+ nextActions: IMigrationAction[];
108
+ }
109
+ /**
110
+ * Une source de migrations : un espace de noms OUVERT.
111
+ *
112
+ * `framework` et `app` sont deux valeurs **réservées**, pas une énumération :
113
+ * le registre d'entités est déjà ouvert à N modules, et un modèle à deux
114
+ * valeurs casserait APRÈS publication (un module tiers n'aurait d'autre choix
115
+ * que de se déverser dans `app`, et désinstaller un module bloquerait toute
116
+ * migration ultérieure, pour toujours).
117
+ */
118
+ export interface IMigrationSource {
119
+ /** Nom logique, découplé du paquet qui livre le dossier. */
120
+ name: string;
121
+ /** Dossier RACINE des migrations ; le sous-dossier de dialecte est dérivé. */
122
+ dir: string;
123
+ /** Rang d'application : `framework` = 0, modules ensuite, `app` en dernier. */
124
+ rank: number;
125
+ }
126
+ /** Un fichier de migration chargé depuis une source. */
127
+ export interface IMigrationFile {
128
+ /** Source qui le livre. */
129
+ source: string;
130
+ /** Identité immuable du fichier (`0000_framework_init`). */
131
+ tag: string;
132
+ /** Rang dans le journal de SA source. */
133
+ idx: number;
134
+ /** Empreinte auto-descriptive `sha256:<hex>` du contenu normalisé. */
135
+ hash: string;
136
+ /** Statements découpés, dans l'ordre, sans les vides. */
137
+ statements: readonly string[];
138
+ /** Chemin du fichier — pour nommer un refus. */
139
+ path: string;
140
+ }
141
+ /** Une ligne de la table d'historique, colonnes lues NOMMÉMENT. */
142
+ export interface IAppliedMigration {
143
+ source: string;
144
+ tag: string;
145
+ hash: string;
146
+ runId: string;
147
+ startedAt: number;
148
+ finishedAt: number | null;
149
+ executionMs: number | null;
150
+ success: boolean;
151
+ error: string | null;
152
+ appliedBy: string | null;
153
+ }
154
+ /** Une dérive constatée : le fichier a changé après avoir été appliqué. */
155
+ export interface IMigrationDrift {
156
+ source: string;
157
+ tag: string;
158
+ /** Empreinte enregistrée au moment de l'application. */
159
+ expected: string;
160
+ /** Empreinte du fichier tel qu'il est aujourd'hui. */
161
+ actual: string;
162
+ }
163
+ /**
164
+ * État complet, calculé en LECTURE SEULE.
165
+ *
166
+ * Même producteur pour la CLI, le data plane, la porte d'agent et la sonde de
167
+ * disponibilité — quatre consommateurs, une seule vérité.
168
+ */
169
+ export interface IMigrationPlan {
170
+ connector: string;
171
+ dialect: SqlDialect;
172
+ /** Migrations appliquées avec succès, ordre d'application. */
173
+ applied: readonly IAppliedMigration[];
174
+ /** Migrations à appliquer, dans l'ordre (rang de source, puis journal). */
175
+ pending: readonly IMigrationFile[];
176
+ /** Fichiers modifiés après application. */
177
+ drifted: readonly IMigrationDrift[];
178
+ /** Marqueurs d'échec, ou migrations jamais terminées. */
179
+ failed: readonly IAppliedMigration[];
180
+ /** Appliquées en base mais sans fichier, dans une source PRÉSENTE. */
181
+ missing: readonly {
182
+ source: string;
183
+ tag: string;
184
+ }[];
185
+ /** Sources vues en base mais absentes du registre — ignorées, jamais bloquantes. */
186
+ ignoredSources: readonly string[];
187
+ /** Historique vide alors que les tables du schéma existent déjà. */
188
+ baselineRequired: boolean;
189
+ }
190
+ /** Résultat de l'application d'une migration. */
191
+ export interface IMigrationApplied {
192
+ source: string;
193
+ tag: string;
194
+ executionMs: number;
195
+ }
196
+ /** Résultat d'un `migrate()`. */
197
+ export interface IMigrationRun {
198
+ /** Identifiant du run — groupe les migrations d'un même déploiement. */
199
+ runId: string;
200
+ /** Migrations effectivement appliquées, dans l'ordre. */
201
+ applied: readonly IMigrationApplied[];
202
+ }
203
+ /**
204
+ * Pilote de base de données de l'applicateur — **une connexion, pas un pool**.
205
+ *
206
+ * `pg_advisory_lock` et `GET_LOCK` sont des verrous de SESSION : un pool les
207
+ * rend inopérants (verrou pris sur une connexion, DDL exécuté sur une autre,
208
+ * libération sur une troisième). L'applicateur tient donc sa propre connexion,
209
+ * du verrou jusqu'à sa libération, avec les mêmes pilotes chargés en lazy que
210
+ * l'adapter — aucune dépendance nouvelle.
211
+ */
212
+ export interface IMigrationDriver extends ISchemaReader {
213
+ /** Dialecte servi. */
214
+ readonly dialect: SqlDialect;
215
+ /**
216
+ * Le DDL de ce dialecte est-il transactionnel ?
217
+ *
218
+ * MySQL répond `false` : un `CREATE TABLE` y valide implicitement. C'est ce
219
+ * qui interdit toute reprise aveugle après un échec — la réparation tranche.
220
+ */
221
+ readonly transactionalDdl: boolean;
222
+ /** Exécute un statement sans résultat. */
223
+ exec(sql: string): Promise<void>;
224
+ /** Exécute une requête paramétrée ; les paramètres s'écrivent `?`. */
225
+ query<T extends Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<T[]>;
226
+ /** Ouvre une transaction (`BEGIN IMMEDIATE` en sqlite). */
227
+ begin(): Promise<void>;
228
+ commit(): Promise<void>;
229
+ rollback(): Promise<void>;
230
+ /**
231
+ * Prend le verrou d'applicateur, ou lève au bout de `timeoutMs`.
232
+ *
233
+ * L'identité du verrou est un **contrat inter-versions** : deux versions du
234
+ * framework qui ne s'excluent plus, c'est pendant un déploiement que ça se
235
+ * paie — le seul moment qui compte.
236
+ */
237
+ lock(timeoutMs: number): Promise<void>;
238
+ /** Libère le verrou. Sans effet s'il n'était pas tenu. */
239
+ unlock(): Promise<void>;
240
+ /** Ferme la connexion. */
241
+ close(): Promise<void>;
242
+ }
243
+ /**
244
+ * Le verrou d'applicateur n'a pas été obtenu dans le délai imparti.
245
+ *
246
+ * Une classe, et pas une `Error` nue, pour une raison précise : c'est ce qui
247
+ * permet à l'applicateur de la reconnaître et de la rendre sous le code
248
+ * `NF_MIGRATE_LOCK_TIMEOUT`, publié dans le contrat et lu par les
249
+ * orchestrateurs pour décider d'ATTENDRE puis de reprendre. Levée nue, elle
250
+ * tombait dans le fourre-tout des pannes : le code promis n'était jamais émis,
251
+ * et les branches qui l'attendaient — jusqu'au code de sortie — étaient
252
+ * inatteignables.
253
+ *
254
+ * Un verrou tenu n'est pas une panne : c'est le déploiement d'à côté qui
255
+ * travaille, et la réponse est d'attendre.
256
+ */
257
+ export declare class MigrationLockTimeoutError extends Error {
258
+ readonly timeoutMs: number;
259
+ /**
260
+ * @param timeoutMs - délai d'attente écoulé.
261
+ * @param message - phrase française destinée à un humain.
262
+ */
263
+ constructor(timeoutMs: number, message: string);
264
+ }
265
+ /** Erreur portant un {@link IMigrationVerdict} — le message n'est qu'un rendu. */
266
+ export declare class MigrationVerdictError extends Error {
267
+ readonly verdict: IMigrationVerdict;
268
+ /**
269
+ * @param verdict - verdict structuré, seule source de la décision.
270
+ * @param message - phrase française destinée à un humain.
271
+ */
272
+ constructor(verdict: IMigrationVerdict, message: string);
273
+ }
@@ -0,0 +1,241 @@
1
+ import { Orm } from "@nodefony/orm-core";
2
+ import type { IOrmMigrationApplyReply, IOrmMigrationPlanReply, IOrmMigrationReply } from "@nodefony/orm-core";
3
+ import { type ISchemaComparison } from "../migrator/schemaDiff.js";
4
+ import type { IColumnInfo, IConnectionInfo, IOrmProbe, IRepository, ITransaction } from "@nodefony/orm-core";
5
+ import type { SqlDialect } from "../../interfaces/IDrizzleConfig.js";
6
+ /** Options de connexion de l'adapter Drizzle (driver selon `dialect`). */
7
+ export interface DrizzleOrmOptions {
8
+ /**
9
+ * Dialecte SQL : `sqlite` (défaut, driver better-sqlite3) · `postgres` (driver
10
+ * `pg`, lazy) · `mysql` (driver `mysql2`, lazy). Sélectionne le client ET la
11
+ * variante de table que les entités enregistrent.
12
+ */
13
+ dialect?: SqlDialect;
14
+ /** Fichier SQLite (`":memory:"` par défaut) — dialecte `sqlite` uniquement. */
15
+ filename?: string;
16
+ /**
17
+ * Chaîne de connexion (`postgres://…`, `mysql://…`) — dialectes `postgres`/
18
+ * `mysql`. Requise pour ces dialectes.
19
+ */
20
+ url?: string;
21
+ /**
22
+ * Le schéma doit-il être DÉRIVÉ du code à la connexion ?
23
+ *
24
+ * `true` (défaut) : `CREATE TABLE IF NOT EXISTS` et les index déclarés sont
25
+ * émis à chaque connexion — c'est le confort du développement et l'usage
26
+ * direct en banc de test.
27
+ *
28
+ * `false` : la connexion ne touche PAS au schéma. C'est ce que veulent les
29
+ * modes `migrate` et `none` : le schéma y appartient aux migrations, et une
30
+ * création dérivée qui passerait par-dessus créerait précisément la
31
+ * divergence que les migrations existent pour empêcher — une table posée par
32
+ * le démarrage n'a aucune trace dans l'historique, donc plus personne ne sait
33
+ * d'où elle vient.
34
+ */
35
+ deriveSchema?: boolean;
36
+ /**
37
+ * Qui sait lire l'état des migrations de CE connecteur.
38
+ *
39
+ * Injecté par le service du module, seul à détenir la configuration (fichiers
40
+ * de migration, mode `ddl`, coordonnées) — l'ORM, lui, ne connaît que sa
41
+ * connexion. Sans lui, {@link DrizzleOrm.migrationStatus} répond que le
42
+ * connecteur n'est pas déclaré dans la configuration, ce qui est le cas d'un
43
+ * ORM construit à la main dans un banc.
44
+ */
45
+ migrationStatus?: () => Promise<IOrmMigrationReply>;
46
+ /** Qui sait dire ce qui S'APPLIQUERAIT — même origine que `migrationStatus`. */
47
+ migrationPlan?: () => Promise<IOrmMigrationPlanReply>;
48
+ /** Qui sait APPLIQUER — refuse hors développement, en le disant. */
49
+ applyMigrations?: () => Promise<IOrmMigrationApplyReply>;
50
+ }
51
+ /**
52
+ * Adapter Drizzle (driver `better-sqlite3`) **branché sur `@nodefony/orm-core`**
53
+ * — 3ᵉ adapter du banc multi-ORM (P7.4), choix SQL #1 moderne.
54
+ *
55
+ * Particularité vs les autres ORM : Drizzle est **schema-as-code** — il n'y
56
+ * a pas de « compilation » de modèle. `entity.schema` *est* déjà une table
57
+ * Drizzle (`sqliteTable(...)`). L'adapter :
58
+ * - dérive le DDL de chaque table via `getTableConfig()` et le crée (dev/test ;
59
+ * la prod passera par `drizzle-kit`) — pas de `sync()` natif côté Drizzle ;
60
+ * - résout les relations déclaratives ({@link IEntityRelation}) en métadonnées
61
+ * d'**eager-load manuel** (cf {@link DrizzleRepository}), pour rester générique
62
+ * sans imposer la couche `relations()` de Drizzle ;
63
+ * - pilote les transactions à la main (`BEGIN`/`COMMIT`/`ROLLBACK`) car
64
+ * `better-sqlite3` est synchrone (cf {@link DrizzleTransaction}).
65
+ *
66
+ * Trappe SQL brut : {@link DrizzleOrm.getNativeConnection} expose le db Drizzle
67
+ * (tag `sql`) pour les jointures arbitraires (ADR-0003 risque #1).
68
+ */
69
+ export declare class DrizzleOrm extends Orm {
70
+ #private;
71
+ /**
72
+ * Ce que cet adapter sait de l'état de sa connexion, **par dialecte**.
73
+ *
74
+ * `postgres`/`mysql` traduisent les signaux de leur pool → `"events"`.
75
+ * `sqlite` est une base EMBARQUÉE : il n'y a ni serveur à perdre ni socket
76
+ * à surveiller, donc rien à constater — dire `"assumed"` n'est pas un aveu
77
+ * de faiblesse, c'est la description exacte de la situation.
78
+ */
79
+ get liveness(): "events" | "assumed";
80
+ /**
81
+ * @param name - clé unique de l'ORM dans le `ormRegistry` (ex. `"db_test"`).
82
+ * @param options - options de connexion (`dialect`, `filename` sqlite, `url` pg).
83
+ */
84
+ constructor(name: string, options?: DrizzleOrmOptions);
85
+ /**
86
+ * Le schéma est-il dérivé du code à la connexion, ou appartient-il aux
87
+ * migrations ?
88
+ *
89
+ * Se lire de l'extérieur a un usage précis : une brique qui s'apprête à
90
+ * interroger une table doit pouvoir dire si quelqu'un la crée. Quand la
91
+ * réponse est « non » et que la table ne figure dans aucune migration, un refus
92
+ * au démarrage vaut infiniment mieux qu'une erreur de colonne inconnue à la
93
+ * première requête d'un utilisateur.
94
+ *
95
+ * @returns `true` en mode `auto` (développement, test), `false` sinon.
96
+ */
97
+ get derivesSchema(): boolean;
98
+ /** Dialecte SQL de ce connecteur. */
99
+ get dialect(): SqlDialect;
100
+ /**
101
+ * Écart constaté entre le schéma déclaré par le code et la base — `null`
102
+ * quand la base est conforme.
103
+ *
104
+ * Publié pour que trois surfaces disent la MÊME chose : le corps d'erreur
105
+ * rendu au client, la sonde de disponibilité, et l'écran d'administration.
106
+ * En mode dérivé il est renseigné au démarrage, après rattrapage de ce qui se
107
+ * rattrapait ; ailleurs il ne l'est que sur demande explicite
108
+ * ({@link DrizzleOrm.compareToDeclared}).
109
+ */
110
+ get schemaDrift(): ISchemaComparison | null;
111
+ /**
112
+ * Compare la base à ce que le code déclare — sans jamais rien modifier.
113
+ *
114
+ * C'est la **troisième source** : les outils de migration croisent les
115
+ * fichiers et l'historique, et concluent « tout est appliqué » sans avoir
116
+ * regardé la base. Croiser le schéma réel rend visible l'incident qu'aucun
117
+ * d'eux ne voit — historique complet, rien en attente, et pourtant la base ne
118
+ * correspond pas au code (un `ALTER` passé à la main, un correctif jamais
119
+ * reporté, deux environnements qui ont divergé).
120
+ *
121
+ * La lecture passe par la connexion DÉJÀ ouverte de ce connecteur : en
122
+ * ouvrir une seconde sur `:memory:` désignerait une base vide et rendrait un
123
+ * verdict faux.
124
+ *
125
+ * @returns les écarts, séparés selon qu'ils se rattrapent ou non.
126
+ */
127
+ compareToDeclared(): Promise<ISchemaComparison>;
128
+ /**
129
+ * Emplacement PHYSIQUE lisible de la base, pour l'écran Studio « Stores »
130
+ * (« où sont écrites mes données ? »). Fichier SQLite **relativisé** au cwd
131
+ * (anti info-leak, cf {@link DrizzleOrm.#safeTarget}) — `undefined` pour
132
+ * `:memory:` (volatil) et pour un backend RÉSEAU (postgres/mysql), dont
133
+ * l'emplacement EST l'infra déclarée, déjà surfacée à part (le Studio dérive
134
+ * alors « backend réseau — voir l'infra »). Stable dès la construction
135
+ * (`#filename` fixé au ctor, indépendant du connect).
136
+ */
137
+ get location(): string | undefined;
138
+ protected onConnect(): Promise<void>;
139
+ disconnect(): Promise<void>;
140
+ getRepository<T = unknown>(name: string): IRepository<T>;
141
+ /**
142
+ * Exécute `work` dans une transaction, sur les TROIS dialectes : commit si la
143
+ * closure résout, rollback si elle rejette (cf {@link DrizzleTransaction}).
144
+ *
145
+ * Un repository n'entre dans la transaction que lié par `withTransaction(tx)` :
146
+ * en postgres/mysql, la transaction tient une connexion dédiée du pool, tandis
147
+ * que `getRepository()` écrit via le pool — donc hors transaction.
148
+ *
149
+ * @param work - travail transactionnel ; reçoit la transaction à lier aux repositories.
150
+ * @returns la valeur rendue par `work`.
151
+ * @throws `not connected` hors connexion ; sinon l'erreur de `work`, après rollback.
152
+ */
153
+ transaction<R>(work: (tx: ITransaction) => Promise<R>): Promise<R>;
154
+ getNativeConnection<C = unknown>(): C;
155
+ /**
156
+ * État des migrations de ce connecteur — la MÊME charge utile que
157
+ * `orm:migrate:status --json`, jamais un second calcul.
158
+ *
159
+ * Le travail est fait par le lecteur injecté à la construction : lui seul
160
+ * détient la configuration (fichiers, mode `ddl`, coordonnées), quand cet
161
+ * objet ne connaît que sa connexion. Sans lecteur — un ORM construit à la
162
+ * main dans un banc —, la réponse dit exactement cela, et surtout PAS « ne
163
+ * porte pas de migrations » : ce serait faux d'un connecteur SQL, et un
164
+ * message faux publié est appris par les scripts qui le lisent.
165
+ *
166
+ * @returns l'état, ou l'empêchement qui explique pourquoi il n'y en a pas.
167
+ */
168
+ migrationStatus(): Promise<IOrmMigrationReply>;
169
+ /**
170
+ * Ce qui S'APPLIQUERAIT, avec son SQL — lecture seule.
171
+ *
172
+ * @returns le plan, ou l'empêchement.
173
+ */
174
+ migrationPlan(): Promise<IOrmMigrationPlanReply>;
175
+ /**
176
+ * Applique les migrations en attente — **développement seulement**, le
177
+ * lecteur injecté refuse ailleurs en le disant.
178
+ *
179
+ * @returns ce qui a été appliqué, ou l'empêchement.
180
+ */
181
+ applyMigrations(): Promise<IOrmMigrationApplyReply>;
182
+ /**
183
+ * Ping bas-coût : `SELECT 1` — round-trip RÉEL vers la base, routé par
184
+ * dialecte (pool pg/mysql, client better-sqlite3 synchrone).
185
+ *
186
+ * @throws si le connecteur n'est pas connecté.
187
+ */
188
+ ping(): Promise<void>;
189
+ /**
190
+ * Sonde driver-spécifique, **routée par dialecte** — alimente le data plane
191
+ * admin (panneau Studio ORM) :
192
+ * - **sqlite** : stockage via PRAGMA (synchrone, bon marché) — taille
193
+ * (`page_count × page_size`), mode de journal (WAL ?), pages libres ;
194
+ * - **postgres / mysql** : état du **pool**, la métrique qui compte sur une
195
+ * base serveur (sa saturation est une falaise de débit) — lu sur des
196
+ * compteurs EN MÉMOIRE, donc sans requête réseau.
197
+ *
198
+ * Le stockage d'une base serveur n'est PAS sondé : il coûterait une requête
199
+ * (`pg_database_size`…) à chaque appel, pour une donnée que l'admin du SGBD
200
+ * expose déjà. Mieux vaut ne rien promettre que promettre en silence.
201
+ *
202
+ * Best-effort — `{}` seulement si le connecteur n'est pas connecté.
203
+ *
204
+ * @returns sonde `storage` (sqlite) ou `pool` (serveur), `{}` hors connexion.
205
+ */
206
+ probe(): Promise<IOrmProbe>;
207
+ /**
208
+ * Colonnes normalisées d'une entité, dérivées du DDL Drizzle
209
+ * (`getTableConfig`) — alimente le graphe canonique / ERD / contexte IA.
210
+ *
211
+ * @param name - nom logique de l'entité.
212
+ * @returns colonnes (`[]` si l'entité n'est pas connue de cet ORM).
213
+ */
214
+ describeEntity(name: string): IColumnInfo[];
215
+ /**
216
+ * Décrit le schéma ENTIER attendu par le code : une entrée par table.
217
+ *
218
+ * Même calcul que {@link DrizzleOrm.describeEntity}, à l'échelle du
219
+ * connecteur — et c'est le point : le rattrapage de colonnes au démarrage, le
220
+ * constat de divergence et l'écran d'administration comparent tous « ce que
221
+ * le code déclare » à « ce que la base contient ». Trois lecteurs, un seul
222
+ * producteur ; recopier ce parcours ailleurs le ferait diverger du jour où
223
+ * un dialecte s'ajoute.
224
+ *
225
+ * @returns une entrée par table, avec son NOM EN BASE (≠ nom d'entité).
226
+ */
227
+ describeTables(): {
228
+ entity: string;
229
+ table: string;
230
+ columns: IColumnInfo[];
231
+ }[];
232
+ /**
233
+ * Décrit la connexion : driver `sqlite` (better-sqlite3) + cible (chemin du
234
+ * fichier, `:memory:` pour les tests). Aucun credential (SQLite = fichier local).
235
+ * Le chemin est **relativisé** à la racine du process : on ne fuite jamais
236
+ * l'arborescence absolue du serveur (home, structure FS) dans le data plane.
237
+ *
238
+ * @returns driver + cible (chemin relatif, basename si hors projet, ou `:memory:`).
239
+ */
240
+ describeConnection(): IConnectionInfo;
241
+ }
@@ -0,0 +1,98 @@
1
+ import type { Column } from "drizzle-orm";
2
+ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
3
+ import type { SQLiteTable } from "drizzle-orm/sqlite-core";
4
+ import type { PgTable } from "drizzle-orm/pg-core";
5
+ import type { MySqlTable } from "drizzle-orm/mysql-core";
6
+ import type { SqlDialect } from "../../interfaces/IDrizzleConfig.js";
7
+ import type { Criteria, IRepository, ITransaction, RepositoryReadOptions, UpdateData } from "@nodefony/orm-core";
8
+ /**
9
+ * Handle Drizzle (instance racine ou transaction) — schéma résolu côté adapter.
10
+ *
11
+ * Typage = **vue d'exécution CANONIQUE** (surface better-sqlite3) quel que soit
12
+ * le dialecte : `NodePgDatabase` expose la même surface builder pour les verbes
13
+ * du repository (`select`/`insert`/`update`/`delete`), et l'exactitude runtime
14
+ * est prouvée par les e2e PG (session/token/user/webauthn/totp/audit/webhook).
15
+ * La surface d'exécution native qui DIVERGE (`db.all` vs `db.execute().rows`)
16
+ * est routée par le `queryKit` — jamais ici.
17
+ */
18
+ export type DrizzleDb = BetterSQLite3Database<Record<string, never>>;
19
+ /** Table Drizzle multi-dialecte (l'union honnête des variantes colKit). */
20
+ export type DrizzleTable = SQLiteTable | PgTable | MySqlTable;
21
+ /** Colonne Drizzle dialecte-agnostique (classe de base — acceptée par les
22
+ * opérateurs `eq`/`lt`/`asc`… et les fragments `sql`). */
23
+ export type DrizzleColumn = Column;
24
+ /**
25
+ * Vue d'exécution canonique (typage sqlite) d'une table multi-dialecte —
26
+ * **LE point unique** de conversion vers les builders (cf {@link DrizzleDb} :
27
+ * la surface builder est structurellement identique sqlite/pg, prouvée e2e).
28
+ * Consommé aussi par les stores à requêtes builder (`DrizzleAuditStore`).
29
+ */
30
+ export declare function execTable(table: DrizzleTable): SQLiteTable;
31
+ /**
32
+ * Relation résolue au boot de l'ORM, prête pour l'eager-load manuel (sans la
33
+ * couche `relations()` de Drizzle, pour rester générique cross-entités).
34
+ */
35
+ export interface DrizzleResolvedRelation {
36
+ /** Cardinalité. */
37
+ type: "one-to-many" | "many-to-one" | "one-to-one";
38
+ /** Table cible Drizzle (variante du dialecte du connecteur). */
39
+ targetTable: DrizzleTable;
40
+ /** Colonne clé étrangère (sur la cible pour 1-N, sur la source pour N-1). */
41
+ foreignKey: string;
42
+ /** Clé primaire de l'entité courante (côté parent du 1-N). */
43
+ localKey: string;
44
+ /** Clé primaire de la cible (côté lookup du N-1). */
45
+ targetKey: string;
46
+ }
47
+ /**
48
+ * Repository portable (contrat {@link IRepository}) au-dessus d'une table Drizzle
49
+ * + driver `better-sqlite3`.
50
+ *
51
+ * 3ᵉ adapter du banc orm-core (ADR-0003) : valide le contrat sur un ORM
52
+ * **type-safe-first** dont le `WHERE` est un *builder* d'expressions (pas un objet
53
+ * plat). Spécificités traduites ici :
54
+ * - critère portable → expressions Drizzle (`eq`/`and`/`gt`/`inArray`/`like`...),
55
+ * opérateurs riches inclus (résolution ADR-0003 risque #3) ;
56
+ * - **eager-load manuel** (`options.relations`) : une requête `IN (...)` par
57
+ * relation déclarée, puis regroupement en mémoire — portable sans déclarer la
58
+ * couche `relations()` de Drizzle ;
59
+ * - liaison transactionnelle via {@link DrizzleRepository.withTransaction} (le
60
+ * handle de transaction *est* un db Drizzle → réutilisé tel quel).
61
+ *
62
+ * @typeParam T - forme plate de l'entité gérée.
63
+ */
64
+ export declare class DrizzleRepository<T = unknown> implements IRepository<T> {
65
+ #private;
66
+ /**
67
+ * @param db - handle Drizzle (instance racine ou transaction).
68
+ * @param table - table Drizzle de l'entité (variante du dialecte).
69
+ * @param relations - relations résolues (eager-load), indexées par champ.
70
+ * @param connector - nom de la connexion (clé du registre) — défaut `"default"`.
71
+ * @param dialect - dialecte SQL du connecteur — défaut `"sqlite"`.
72
+ * @param transactional - `true` quand `db` est un handle de transaction :
73
+ * l'instance est jetable (une par `withTransaction`) et, en pg, sa connexion
74
+ * dédiée est rendue au commit — préparer dessus coûterait sans jamais
75
+ * s'amortir. Les transactions gardent le chemin non préparé.
76
+ */
77
+ constructor(db: DrizzleDb, table: DrizzleTable, relations: Record<string, DrizzleResolvedRelation>, connector?: string, dialect?: SqlDialect, transactional?: boolean);
78
+ find(criteria?: Criteria<T>, options?: RepositoryReadOptions): Promise<T[]>;
79
+ findOne(criteria: Criteria<T>, options?: RepositoryReadOptions): Promise<T | null>;
80
+ create(data: Partial<T>): Promise<T>;
81
+ createMany(data: Partial<T>[]): Promise<T[]>;
82
+ updateOne(criteria: Criteria<T>, data: Partial<T>): Promise<T | null>;
83
+ upsert(criteria: Criteria<T>, update: UpdateData<T>, insertOnly?: Partial<T>): Promise<T>;
84
+ updateMany(criteria: Criteria<T>, data: Partial<T>): Promise<number>;
85
+ increment(criteria: Criteria<T>, changes: Partial<Record<keyof T, number>>): Promise<T | null>;
86
+ delete(criteria: Criteria<T>): Promise<number>;
87
+ deleteOne(criteria: Criteria<T>): Promise<boolean>;
88
+ findOneAndDelete(criteria: Criteria<T>): Promise<T | null>;
89
+ count(criteria?: Criteria<T>): Promise<number>;
90
+ /**
91
+ * `COUNT(DISTINCT col)` natif — la déduplication reste dans le moteur, aucune
92
+ * ligne n'est rapatriée. `COUNT(DISTINCT …)` ignore les `NULL` sur les trois
93
+ * dialectes, ce qui donne au contrat sa sémantique sans clause supplémentaire.
94
+ */
95
+ countDistinct(field: keyof T & string, criteria?: Criteria<T>): Promise<number>;
96
+ exists(criteria: Criteria<T>): Promise<boolean>;
97
+ withTransaction(tx: ITransaction): IRepository<T>;
98
+ }