@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,79 @@
1
+ import type { IRepository } from "@nodefony/orm-core";
2
+ import type { IPage } from "nodefony";
3
+ import type { IWebhookEndpoint, IWebhookListQuery, IWebhookStore, WebhookEndpointUpdate } from "@nodefony/security";
4
+ import type { SqlDialect } from "../interfaces/IDrizzleConfig.js";
5
+ import type { DrizzleOrm } from "./orm-core/DrizzleOrm.js";
6
+ import type { DrizzleDb } from "./orm-core/DrizzleRepository.js";
7
+ import { type WebhookEndpointRow } from "../entity/webhookEndpointEntity.js";
8
+ /**
9
+ * Store d'endpoints webhook **Drizzle** (driver `better-sqlite3`) —
10
+ * implémentation SQL d'{@link IWebhookStore} au-dessus d'un unique repository
11
+ * `@nodefony/orm-core` (`webhook_endpoint`). Persistance DURABLE du registre
12
+ * (les endpoints survivent au redémarrage, contrairement à `MemoryWebhookStore`).
13
+ *
14
+ * **Approche B** : l'ORM ne connaît `@nodefony/security` qu'en `import type` → 0
15
+ * dépendance runtime. C'est l'application qui enregistre la fabrique
16
+ * (`registerWebhookStore("drizzle", …)`) et l'entité
17
+ * (`registerWebhookEndpointEntity(orm)` avant `orm.connect()`).
18
+ *
19
+ * **Portable sauf le listing paginé** : le CRUD passe par le contrat
20
+ * `IRepository` (transposable tel quel aux autres drivers) ; seuls `listPage` /
21
+ * `countEndpoints` descendent au SQL natif via le `queryKit`, parce que le
22
+ * filtre `event` cherche dans un tableau JSON — inexprimable en `Criteria`.
23
+ *
24
+ * **Mapping Row ↔ contrat** minimal : `IWebhookEndpoint` est déjà « plat tout
25
+ * `| null` » ; seuls les champs JSON `events` (`readonly` → mutable) et
26
+ * `metadata` sont copiés défensivement.
27
+ */
28
+ export declare class DrizzleWebhookStore implements IWebhookStore {
29
+ #private;
30
+ /**
31
+ * {@inheritDoc IWebhookStore.sortableFields}
32
+ *
33
+ * Le moteur SQL trie sur n'importe laquelle de ces colonnes. Cette liste sert
34
+ * DEUX fois : elle annonce la capacité au data plane, et elle borne le
35
+ * `ORDER BY` construit par le queryKit (identifiant concaténé, non liable).
36
+ */
37
+ readonly sortableFields: readonly ["createdAt", "updatedAt", "url", "enabled", "failureCount", "id"];
38
+ /**
39
+ * @param repo - repository de la table `webhook_endpoint`.
40
+ * @param location - emplacement physique de la base (fichier SQLite) pour Studio
41
+ * ({@link DrizzleOrm.location}) ; `undefined` pour un backend réseau/`:memory:`.
42
+ * @param db - handle Drizzle natif, requis par le seul listing paginé (filtre
43
+ * `event` = containment dans un tableau JSON, hors `Criteria` portable).
44
+ * `null` = store construit sans handle : `listPage` refuse plutôt que de
45
+ * charger toute la table en silence.
46
+ * @param dialect - dialecte SQL du connecteur (route les requêtes du queryKit).
47
+ */
48
+ constructor(repo: IRepository<WebhookEndpointRow>, location?: string, db?: DrizzleDb | null, dialect?: SqlDialect);
49
+ /**
50
+ * Emplacement physique de la base (fichier SQLite) pour l'écran Studio « Stores »
51
+ * — lu par `readStoreLocation`. `undefined` = backend réseau ou `:memory:`.
52
+ */
53
+ get location(): string | undefined;
54
+ /**
55
+ * Construit le store depuis un {@link DrizzleOrm} connecté. L'entité
56
+ * (`registerWebhookEndpointEntity`) doit avoir été enregistrée **avant**
57
+ * `orm.connect()`.
58
+ *
59
+ * @param orm - ORM Drizzle connecté hébergeant la table du store.
60
+ */
61
+ static from(orm: DrizzleOrm): DrizzleWebhookStore;
62
+ save(endpoint: IWebhookEndpoint): Promise<void>;
63
+ findById(id: string): Promise<IWebhookEndpoint | null>;
64
+ update(id: string, patch: WebhookEndpointUpdate): Promise<void>;
65
+ delete(id: string): Promise<void>;
66
+ listAll(): Promise<IWebhookEndpoint[]>;
67
+ /**
68
+ * {@inheritDoc IWebhookStore.listPage}
69
+ *
70
+ * Chemin NATIF (queryKit) : le filtre `event` cherche dans un tableau JSON —
71
+ * `json_each` (sqlite) / `@>` jsonb (postgres) / `JSON_CONTAINS` (mysql), non
72
+ * exprimable en `Criteria` portable. On sélectionne les `id` de la page (SQL
73
+ * pur, aucune ligne matérialisée), puis on recharge la page typée en 1
74
+ * requête `IN (...)` — coût O(page), jamais O(table).
75
+ */
76
+ listPage(query: IWebhookListQuery): Promise<IPage<IWebhookEndpoint>>;
77
+ /** {@inheritDoc IWebhookStore.countEndpoints} */
78
+ countEndpoints(query: IWebhookListQuery): Promise<number>;
79
+ }
@@ -0,0 +1,72 @@
1
+ import { SessionsService } from "@nodefony/http";
2
+ import type { ISessionStorage, ISerializedSession, ISessionRecord, ISessionListFilter, ISessionListQuery } from "@nodefony/http";
3
+ import type { IPage } from "nodefony";
4
+ /**
5
+ * Stockage de session **Drizzle** (driver `better-sqlite3`), branché sur
6
+ * `@nodefony/orm-core`.
7
+ *
8
+ * Implémente le contrat {@link ISessionStorage} consommé par le `SessionsService`
9
+ * de `@nodefony/http` — store de session portable. Persiste via
10
+ * le repository orm-core de l'entité `session` (connecteur `default`, table créée
11
+ * au boot par `DrizzleOrm`). Le GC supprime les sessions expirées avec un
12
+ * opérateur riche portable (`updatedAt < cutoff`).
13
+ */
14
+ declare class SessionStorage implements ISessionStorage {
15
+ #private;
16
+ manager: SessionsService;
17
+ idleTimeoutS: number;
18
+ absoluteTimeoutS: number;
19
+ /**
20
+ * Même vocabulaire public que les autres backends — le tri part dans le
21
+ * `ORDER BY`, donc il ne coûte rien de plus qu'un index bien posé.
22
+ */
23
+ readonly sortableFields: readonly ["updatedAt", "createdAt", "user", "id"];
24
+ constructor(manager: SessionsService);
25
+ /**
26
+ * Emplacement physique de la base (fichier SQLite) pour l'écran Studio « Stores »
27
+ * — lu par `readStoreLocation` du core au boot de `SessionsService`. Résolu
28
+ * **lazy** depuis l'ORM du connecteur session (comme {@link SessionStorage.#repo}) :
29
+ * `undefined` si l'ORM n'est pas encore enregistré, en `:memory:`, ou réseau
30
+ * (postgres/mysql → l'emplacement EST l'infra déclarée, surfacée à part). Lecture
31
+ * DÉFENSIVE (getter `location` optionnel) — l'ORM base `IOrm` ne l'expose pas.
32
+ */
33
+ get location(): string | undefined;
34
+ read(id: string): Promise<ISerializedSession>;
35
+ start(id: string): Promise<ISerializedSession>;
36
+ write(id: string, data: ISerializedSession): Promise<ISerializedSession>;
37
+ open(): Promise<number>;
38
+ close(): boolean;
39
+ destroy(id: string): Promise<boolean>;
40
+ gc(idleSeconds?: number, absoluteSeconds?: number): Promise<void>;
41
+ /**
42
+ * Prolonge l'idle d'une session (timeout glissant) : `UPDATE updatedAt = now`
43
+ * sur la PK `session_id` — SANS réécrire le blob (touch NIST/OWASP). N'affecte
44
+ * pas `createdAt` (= borne absolute). ORM déconnecté → no-op ; une ligne absente
45
+ * (session expirée) → 0 row affectée, silencieux.
46
+ */
47
+ touch(id: string): Promise<void>;
48
+ /**
49
+ * Énumération admin (capacité optionnelle d'`ISessionStorage`) : un `SELECT`
50
+ * filtrable par `user` (WHERE indexable côté SQL). **Redaction par construction**
51
+ * — seuls `user`/`metaBag`/timestamps sortent de la base ; `Attributes`/`flashBag`
52
+ * (potentiellement sensibles) restent en base. ORM déconnecté → `[]`.
53
+ */
54
+ listAll(filter?: ISessionListFilter): Promise<ISessionRecord[]>;
55
+ /**
56
+ * Pagination **native** `LIMIT/OFFSET` + `COUNT` (helper `paginate` orm-core) :
57
+ * une page = une requête bornée, quel que soit le nombre de sessions en base.
58
+ * Ordre `updatedAt` DESC départagé par `session_id` — déterministe même quand
59
+ * deux sessions partagent la milliseconde. ORM déconnecté → page vide.
60
+ */
61
+ listPage(query: ISessionListQuery): Promise<IPage<ISessionRecord>>;
62
+ /** `COUNT(*)` natif filtré — aucune ligne matérialisée. ORM déconnecté → 0. */
63
+ countSessions(query?: Partial<ISessionListQuery>): Promise<number>;
64
+ /**
65
+ * `COUNT(DISTINCT user)` natif filtré. `write` normalise l'anonyme en `NULL`,
66
+ * et `COUNT(DISTINCT …)` ignore les `NULL` : les sessions anonymes ne forment
67
+ * donc pas un « utilisateur » de plus, sans filtre supplémentaire.
68
+ * ORM déconnecté → 0 (même dégradation que {@link countSessions}).
69
+ */
70
+ countDistinctUsers(query?: Partial<ISessionListQuery>): Promise<number>;
71
+ }
72
+ export default SessionStorage;
@@ -0,0 +1,48 @@
1
+ import type { Kernel } from "nodefony";
2
+ import type { SqlDialect } from "../config/config.js";
3
+ import type { IDrizzleConnectorConfig } from "../interfaces/IDrizzleConfig.js";
4
+ /**
5
+ * OÙ vit la base d'un connecteur — **une seule implémentation, deux lecteurs**.
6
+ *
7
+ * Le service qui connecte l'application au démarrage et les commandes de
8
+ * migration doivent désigner exactement la même base. Ce n'est pas une
9
+ * commodité : quand les deux divergent, rien ne le signale. La commande décrit
10
+ * alors une base que l'application n'utilise pas, annonce « à jour » ou
11
+ * « appliqué », et rend le code de sortie du succès.
12
+ *
13
+ * C'est arrivé, et voici comment : `filename` est **optionnel sans défaut**
14
+ * dans le schéma de configuration, parce que sa valeur dépend du kernel
15
+ * (`<app>/var/databases/…`) et que le schéma reste pur. Une lecture naïve de la
16
+ * configuration rend donc `undefined` — et le pilote SQLite retombe alors sur
17
+ * une base **en mémoire**, vide, jetée à la fin du processus. Toutes les
18
+ * migrations s'y « appliquent » parfaitement.
19
+ *
20
+ * La règle vit donc ici, et les deux appelants l'appellent.
21
+ */
22
+ /**
23
+ * Chemin SQLite par défaut d'un connecteur, résolu depuis le kernel.
24
+ *
25
+ * Sous `kernel.varDir` (`<app>/var`) — la base commune des données persistées :
26
+ * un seul répertoire à sauvegarder et à ignorer du dépôt, et « où sont mes
27
+ * données » a une réponse unique.
28
+ *
29
+ * @param kernel - kernel courant (`null` accepté : repli sur le répertoire courant).
30
+ * @param name - nom du connecteur.
31
+ * @returns le chemin absolu du fichier de base.
32
+ */
33
+ export declare function defaultConnectorFilename(kernel: Kernel | null, name: string): string;
34
+ /** Coordonnées complètes d'un connecteur, prêtes à ouvrir une connexion. */
35
+ export interface IConnectorTarget {
36
+ dialect: SqlDialect;
37
+ filename?: string;
38
+ url?: string;
39
+ }
40
+ /**
41
+ * Résout les coordonnées d'un connecteur : dialecte, et fichier ou URL.
42
+ *
43
+ * @param kernel - kernel courant, pour le chemin par défaut.
44
+ * @param name - nom du connecteur.
45
+ * @param cfg - configuration déclarée du connecteur.
46
+ * @returns les coordonnées, avec le fichier SQLite TOUJOURS résolu.
47
+ */
48
+ export declare function resolveConnectorTarget(kernel: Kernel | null, name: string, cfg: IDrizzleConnectorConfig): IConnectorTarget;
@@ -0,0 +1,29 @@
1
+ import type { SQL } from "drizzle-orm";
2
+ import type { SqlDialect } from "../interfaces/IDrizzleConfig.js";
3
+ /**
4
+ * Compose une condition `LIKE` **avec sa clause `ESCAPE`** — le seul endroit de
5
+ * l'adapter Drizzle qui écrive un `LIKE`.
6
+ *
7
+ * La clause n'est pas un raffinement : sans elle, le contrat `$like` n'a pas une
8
+ * sémantique mais trois. PostgreSQL et MySQL appliquent déjà l'antislash comme
9
+ * échappement par défaut, SQLite n'en a aucun et cherche l'antislash littéral —
10
+ * si bien qu'un motif échappé rendait la bonne ligne en production et rien du
11
+ * tout en développement (mesuré sur les trois moteurs). L'émettre aligne les
12
+ * trois sur le comportement déjà majoritaire, et rend enfin exprimable un `%`
13
+ * littéral.
14
+ *
15
+ * Le motif est **bindé** (paramètre), jamais concaténé : seul le littéral
16
+ * d'échappement est brut, et il ne dépend que du dialecte.
17
+ *
18
+ * @param dialect - dialecte du connecteur branché.
19
+ * @param expr - l'expression de gauche, déjà composée (colonne, `LOWER(col)`…).
20
+ * @param pattern - le motif, dont les fragments littéraux ont été neutralisés
21
+ * par `escapeLikeTerm` (`@nodefony/orm-core`).
22
+ * @returns la condition complète.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * likeCond(dialect, sql`LOWER(${col})`, `${escapeLikeTerm(q.toLowerCase())}%`)
27
+ * ```
28
+ */
29
+ export declare function likeCond(dialect: SqlDialect, expr: SQL, pattern: string): SQL;
@@ -0,0 +1,94 @@
1
+ import type { SqlDialect } from "../../config/config.js";
2
+ import { type IMigrationTarget } from "./drivers/index.js";
3
+ import { type IMigrationApplied, type IMigrationPlan, type IMigrationRun, type IMigrationSource } from "./types.js";
4
+ /** Délai d'attente du verrou, par défaut. */
5
+ export declare const DEFAULT_LOCK_TIMEOUT_MS = 30000;
6
+ /** Options de construction de l'applicateur. */
7
+ export interface IDrizzleMigratorOptions extends IMigrationTarget {
8
+ /** Nom du connecteur — apparaît dans chaque verdict. */
9
+ connector: string;
10
+ /** Registre de sources, espace de noms OUVERT. */
11
+ sources: readonly IMigrationSource[];
12
+ /** Délai d'attente du verrou (ms). */
13
+ lockTimeoutMs?: number;
14
+ /** Qui applique — hôte du job par défaut. */
15
+ appliedBy?: string;
16
+ /** Horloge, injectable pour les tests. */
17
+ now?: () => number;
18
+ }
19
+ /** Assouplissements explicites d'une passe d'application. */
20
+ export interface IMigrateOptions {
21
+ /** Accepte qu'une migration appliquée n'ait plus de fichier. */
22
+ ignoreMissing?: boolean;
23
+ /** Accepte d'appliquer une migration antérieure à la dernière appliquée. */
24
+ outOfOrder?: boolean;
25
+ /** N'applique rien : rend le plan tel qu'il serait exécuté. */
26
+ dryRun?: boolean;
27
+ }
28
+ export declare class DrizzleMigrator {
29
+ #private;
30
+ /**
31
+ * @param options - connecteur, cible de connexion et registre de sources.
32
+ */
33
+ constructor(options: IDrizzleMigratorOptions);
34
+ /** Connecteur servi par cet applicateur. */
35
+ get connector(): string;
36
+ /** Dialecte servi par cet applicateur. */
37
+ get dialect(): SqlDialect;
38
+ /**
39
+ * État complet de la migration — **lecture seule, sans verrou ni écriture**.
40
+ *
41
+ * Sert la ligne de commande, le plan d'administration, la porte d'agent et la
42
+ * sonde de disponibilité : un seul producteur pour quatre consommateurs. Elle
43
+ * ne crée pas la table d'historique : une sonde qui écrit dans la base n'est
44
+ * plus une sonde.
45
+ *
46
+ * @returns le plan : appliquées, restantes, dérives, échecs, adoption requise.
47
+ */
48
+ status(): Promise<IMigrationPlan>;
49
+ /**
50
+ * Applique les migrations restantes, sous verrou.
51
+ *
52
+ * L'ordre : verrou, amorçage de la table d'historique, chargement des
53
+ * sources, VALIDATION complète, puis application une par une. La validation
54
+ * précède toute écriture — un refus laisse la base intacte.
55
+ *
56
+ * @param options - assouplissements explicites, tous à `false` par défaut.
57
+ * @returns les migrations effectivement appliquées.
58
+ * @throws MigrationVerdictError si la validation refuse d'aller plus loin.
59
+ */
60
+ migrate(options?: IMigrateOptions): Promise<IMigrationRun>;
61
+ baseline(upTo?: string): Promise<IMigrationApplied[]>;
62
+ /**
63
+ * Lève les marqueurs d'échec, **après inspection humaine**.
64
+ *
65
+ * Ce n'est pas une reprise : MySQL n'a pas de DDL transactionnel, donc une
66
+ * migration interrompue y laisse un état partiel que seul un humain peut
67
+ * qualifier. Réparer dit « j'ai regardé, la base est dans l'état que je
68
+ * crois » — ensuite seulement la migration se rejoue.
69
+ *
70
+ * @param options - source à réparer, et ré-alignement d'empreintes assumé.
71
+ * @returns ce qui a été levé et ré-aligné.
72
+ */
73
+ repair(options?: {
74
+ source?: string;
75
+ updateHashes?: boolean;
76
+ forget?: readonly {
77
+ source: string;
78
+ tag: string;
79
+ }[];
80
+ }): Promise<{
81
+ cleared: {
82
+ source: string;
83
+ tag: string;
84
+ }[];
85
+ rehashed: {
86
+ source: string;
87
+ tag: string;
88
+ }[];
89
+ forgotten: {
90
+ source: string;
91
+ tag: string;
92
+ }[];
93
+ }>;
94
+ }
@@ -0,0 +1,280 @@
1
+ import type { SqlDialect } from "../../config/config.js";
2
+ import { type IMigrationTarget } from "./drivers/index.js";
3
+ import type { ISchemaReader } from "./catalog.js";
4
+ /**
5
+ * L'adoption d'une base qui EXISTAIT avant les migrations.
6
+ *
7
+ * ## Le trou que cette brique ferme
8
+ *
9
+ * Le générateur compare le code au **journal des fichiers**, jamais à la base.
10
+ * Quand ce journal est vide — une application passée du mode dérivé, où le
11
+ * démarrage fabrique le schéma, au mode de production, où il ne le fabrique
12
+ * plus — il croit partir de rien et émet le schéma INITIAL : un `CREATE TABLE`
13
+ * de tables qui existent déjà, avec leurs données. Le fichier est inapplicable,
14
+ * et il empoisonne la suite : l'adoption l'inscrit comme appliqué, l'historique
15
+ * affirme alors un schéma que la base n'a pas, et plus aucune commande n'offre
16
+ * de geste. Mesuré au banc de découvrabilité : le seul chemin restant était de
17
+ * détruire la base.
18
+ *
19
+ * La cause n'est pas le générateur — il fait ce qu'on lui donne à lire. C'est
20
+ * l'**instantané** de référence qui manque. Le fabriquer depuis la base, et non
21
+ * depuis le code, remet les trois sources d'accord : les fichiers décrivent
22
+ * l'état réel, l'historique le déclare appliqué, et la génération suivante
23
+ * produit l'`ALTER` qu'on attendait.
24
+ *
25
+ * ## Pourquoi la migration de référence est DÉCOMMENTÉE
26
+ *
27
+ * L'outil d'introspection rend son `CREATE TABLE` entre `/* … *\/`, avec une
28
+ * ligne qui invite à le décommenter avant de l'exécuter. Le laisser commenté
29
+ * donnerait une baseline qui ne recrée RIEN : l'historique serait complet et
30
+ * une base neuve, montée depuis ces mêmes fichiers, sortirait vide. Une
31
+ * migration doit rester rejouable depuis zéro — c'est ce qui permet de créer un
32
+ * environnement de plus. Sur la base adoptée, elle n'est jamais exécutée :
33
+ * l'adoption l'inscrit comme appliquée sans lancer une instruction.
34
+ */
35
+ /** Ce que l'introspection a produit, une fois remise en forme. */
36
+ export interface IAdoptedBaseline {
37
+ /** Tag final de la migration de référence, `0000_<nom>`. */
38
+ tag: string;
39
+ /** Chemin absolu du fichier SQL. */
40
+ file: string;
41
+ /**
42
+ * Tables entrées dans la référence sans être déclarées par l'application.
43
+ *
44
+ * L'outil ne sait pas restreindre sa lecture à une liste de tables — il
45
+ * n'accepte qu'une exclusion. Ce que la base porte en plus est donc LU, et
46
+ * doit être NOMMÉ : à la génération suivante, une table de la référence
47
+ * qu'aucune entité ne déclare est une table que le diff propose de
48
+ * SUPPRIMER.
49
+ */
50
+ extraTables: string[];
51
+ /**
52
+ * Le corps a-t-il pu être DÉCOMMENTÉ ?
53
+ *
54
+ * Publié plutôt que supposé : si l'outil change sa mise en forme, une
55
+ * baseline muette passerait pour une baseline rejouable, et le défaut ne se
56
+ * verrait que le jour où quelqu'un monte un environnement neuf.
57
+ */
58
+ runnable: boolean;
59
+ }
60
+ /**
61
+ * Les coordonnées de connexion, dans la forme que l'outil d'introspection lit.
62
+ *
63
+ * SQLite désigne un FICHIER, les autres une URL — et c'est le seul endroit où
64
+ * cette distinction se fait, pour qu'elle ne se redécide pas à chaque appelant.
65
+ *
66
+ * @param target - cible résolue du connecteur.
67
+ * @returns l'URL de connexion, ou `null` si la cible n'en porte aucune.
68
+ */
69
+ export declare function introspectionUrl(target: IMigrationTarget): string | null;
70
+ /**
71
+ * Rend exécutable le corps qu'une introspection a mis en commentaire.
72
+ *
73
+ * Fonction PURE — c'est ce qui la rend éprouvable sans base et sans outil
74
+ * tiers : une règle qui exige un sous-processus pour être vue rouge n'est
75
+ * jamais vue rouge.
76
+ *
77
+ * Ne touche à RIEN si la forme attendue n'est pas là : rendre `null` laisse
78
+ * l'appelant dire ce qu'il a constaté, au lieu de publier un fichier tronqué
79
+ * par une expression écrite pour une autre version de l'outil.
80
+ *
81
+ * @param sql - le fichier tel que l'outil l'a écrit.
82
+ * @returns le SQL exécutable, ou `null` si aucun bloc commenté n'a été trouvé.
83
+ */
84
+ export declare function uncommentIntrospection(sql: string): string | null;
85
+ /** Une entrée du journal des fichiers, telle que l'outil l'écrit. */
86
+ interface IJournalEntry {
87
+ idx: number;
88
+ version: string;
89
+ when: number;
90
+ tag: string;
91
+ breakpoints: boolean;
92
+ }
93
+ /** Le journal des fichiers d'un dossier de migrations. */
94
+ interface IJournal {
95
+ version: string;
96
+ dialect: string;
97
+ entries: IJournalEntry[];
98
+ }
99
+ /**
100
+ * Lit le journal des FICHIERS d'un dossier de migrations.
101
+ *
102
+ * ⚠️ Ce journal n'est pas l'historique. Il vit dans le dépôt, il est versionné,
103
+ * et il dit ce que le code CONNAÎT. L'historique, lui, vit dans la base et dit
104
+ * ce qu'ELLE a reçu. Les confondre fait conclure « tout est appliqué » sur une
105
+ * base qui n'a rien vu.
106
+ *
107
+ * @param outDir - dossier `<migrations>/<dialecte>`.
108
+ * @returns le journal, ou `null` s'il n'y en a pas encore.
109
+ */
110
+ export declare function readJournal(outDir: string): Promise<IJournal | null>;
111
+ /**
112
+ * Donne à la migration de référence le nom que l'utilisateur a choisi.
113
+ *
114
+ * L'outil d'introspection tire un nom au hasard (`0000_futuristic_spectrum`) et
115
+ * n'accepte pas qu'on le lui impose. Or ce tag est une IDENTITÉ : il est
116
+ * enregistré dans chaque base qui reçoit la migration, et il ne se renomme plus
117
+ * ensuite. Le fixer ici, une fois, avant que quiconque l'ait vu.
118
+ *
119
+ * @param outDir - dossier `<migrations>/<dialecte>`.
120
+ * @param oldTag - tag tiré par l'outil.
121
+ * @param newTag - tag voulu.
122
+ * @returns le chemin du fichier SQL, sous son nom final.
123
+ */
124
+ export declare function renameTag(outDir: string, oldTag: string, newTag: string): Promise<string>;
125
+ /**
126
+ * Retire les modules que l'introspection dépose à côté de sa migration.
127
+ *
128
+ * `schema.ts` et `relations.ts` décrivent la base en TypeScript — utile à qui
129
+ * repart de zéro, hors sujet ici : les entités de l'application sont déjà la
130
+ * source du schéma. Les laisser dans le dossier des migrations y mettrait deux
131
+ * descriptions du même schéma, dont une que personne ne met à jour.
132
+ *
133
+ * @param outDir - dossier `<migrations>/<dialecte>`.
134
+ */
135
+ export declare function dropIntrospectionModules(outDir: string): Promise<void>;
136
+ /**
137
+ * Les tables que l'instantané fraîchement écrit décrit.
138
+ *
139
+ * Lues sur l'instantané et non sur le SQL : c'est lui qui sert de référence au
140
+ * prochain diff, donc c'est lui qui dit ce qui a réellement été adopté. Le
141
+ * fichier `.sql`, lui, n'est qu'un rendu.
142
+ *
143
+ * @param outDir - dossier `<migrations>/<dialecte>`.
144
+ * @returns les noms de tables, ou `[]` si l'instantané est illisible.
145
+ */
146
+ export declare function snapshotTables(outDir: string): Promise<string[]>;
147
+ /**
148
+ * Rend utilisable une référence que l'introspection vient d'écrire.
149
+ *
150
+ * L'outil rend ce qu'il a LU, fidèlement. Deux fidélités rendent pourtant le
151
+ * fichier inutilisable, et il faut les défaire — c'est la même famille de
152
+ * geste que le décommentage du corps : l'artefact est exact, personne ne peut
153
+ * s'en servir.
154
+ *
155
+ * ## 1. Les objets d'une table EXCLUE
156
+ *
157
+ * L'exclusion porte sur les tables, jamais sur ce qui gravite autour. La table
158
+ * d'historique est écartée — c'est le framework qui la crée —, mais sa
159
+ * SÉQUENCE, elle, entre dans la référence. Deux conséquences, toutes deux
160
+ * constatées sur un serveur : la référence rejouée sur un environnement neuf
161
+ * échoue (la séquence existe déjà, posée par les migrations du framework), et
162
+ * la génération suivante propose de la SUPPRIMER — ce que la base refuse,
163
+ * puisque la table d'historique en dépend. L'adoption se retrouve alors dans
164
+ * l'état qu'elle existe pour éviter : un historique en place, et plus aucune
165
+ * commande qui passe.
166
+ *
167
+ * ## 2. La qualification de schéma
168
+ *
169
+ * Une application peut vivre dans un schéma autre que `public` — le montage
170
+ * habituel d'une base mutualisée, obtenu par le chemin de recherche de la
171
+ * connexion. Les entités, elles, ne portent aucun schéma : `pgTable("article")`
172
+ * désigne `public.article` pour l'outil de comparaison, quand l'introspection
173
+ * rend `nf_app.article`. Au premier champ ajouté, l'outil voit une table qui
174
+ * disparaît et une autre qui apparaît, et demande s'il s'agit d'un RENOMMAGE :
175
+ * sans terminal la commande s'arrête, avec un terminal répondre « oui » écrit
176
+ * un `ALTER TABLE … RENAME` qui déplacerait les données.
177
+ *
178
+ * On retire donc la qualification, ce qui rétablit la symétrie avec les
179
+ * entités. À l'exécution, le chemin de recherche de la connexion place les
180
+ * tables là où elles doivent être : c'est déjà lui qui décide, l'adoption
181
+ * cesse simplement de le contredire.
182
+ *
183
+ * ## 3. Le booléen de MySQL
184
+ *
185
+ * `BOOLEAN` est un SYNONYME de `TINYINT(1)` en MySQL : le serveur ne garde que
186
+ * la forme physique, et l'introspection ne peut donc rendre que `tinyint(1)`.
187
+ * L'outil de comparaison, lui, compare des noms de type : il voit une
188
+ * différence là où la base n'en a aucune, et propose un `MODIFY COLUMN` sur
189
+ * chaque booléen — refusé ensuite comme destructif. Une application MySQL qui
190
+ * adopte sa base se retrouve donc bloquée au premier champ ajouté, pour une
191
+ * table qu'elle n'a jamais touchée.
192
+ *
193
+ * La forme déclarée est rétablie. Elle ne peut écraser aucune intention : dans
194
+ * une entité, `tinyint()` rend `tinyint` sans largeur — vérifié au source
195
+ * (`drizzle-orm/mysql-core/columns/tinyint.js`) —, et seul `boolean()` produit
196
+ * `tinyint(1)`. Sur un DDL écrit à la main hors de l'ORM, la réécriture reste
197
+ * exacte : les deux formes désignent la même colonne.
198
+ *
199
+ * @param outDir - dossier `<migrations>/<dialecte>`.
200
+ * @param options.dialect - dialecte lu ; seul MySQL a le synonyme booléen.
201
+ * @param file - fichier SQL de la référence.
202
+ * @param options.schema - schéma effectivement lu, ou `null` (hors PostgreSQL,
203
+ * ou lecture dans `public` : il n'y a alors rien à déqualifier).
204
+ * @param options.excludedTables - tables écartées de la lecture, dont les
205
+ * objets satellites doivent l'être aussi.
206
+ * @param options.stripTables - tables que l'outil a LUES faute de pouvoir les
207
+ * exclure, et qu'il faut retirer du résultat (cf `lectureSansExclusion`).
208
+ */
209
+ export declare function normalizeIntrospection(outDir: string, file: string, options: {
210
+ schema: string | null;
211
+ excludedTables: readonly string[];
212
+ stripTables?: readonly string[];
213
+ dialect?: SqlDialect;
214
+ }): Promise<void>;
215
+ /**
216
+ * Parmi les tables qu'on s'apprête à CRÉER, celles que la base porte déjà.
217
+ *
218
+ * 🔴 **La base est la seule source, et elle est INTERROGÉE.** Il serait tentant
219
+ * de déduire la présence d'une table de son absence dans une comparaison au
220
+ * code : c'est ce que faisait la première version, et le raccourci a produit un
221
+ * refus mensonger. La comparaison ne connaît que le REGISTRE — les entités
222
+ * qu'une application enregistre à son démarrage — alors que la génération part
223
+ * des FICHIERS, qui en contiennent d'autres : celles d'un module désactivé,
224
+ * d'un connecteur différent, ou d'un module pas encore câblé. Une table hors
225
+ * registre n'est ni manquante ni présente : elle est INCONNUE, et déduire sa
226
+ * présence faisait refuser la première migration d'une application en nommant
227
+ * sept tables qui n'existaient nulle part — puis renvoyait vers une adoption
228
+ * qui échouait pour la raison inverse. Deux commandes qui se prescrivent l'une
229
+ * l'autre en se refusant : une impasse, exactement celle que ce chantier
230
+ * existe pour fermer.
231
+ *
232
+ * Le lecteur est INJECTÉ plutôt que construit ici : c'est ce qui rend la règle
233
+ * éprouvable sans kernel ni serveur, et une règle qu'on ne peut pas voir rouge
234
+ * n'est pas une règle.
235
+ *
236
+ * Le coût est d'une requête de catalogue par table, et l'appelant ne s'en sert
237
+ * que lorsque le journal est vide — une fois dans la vie d'une application.
238
+ *
239
+ * @param reader - lecteur de catalogue ouvert sur la base visée.
240
+ * @param tables - tables que la migration créerait.
241
+ * @returns les tables déjà en base, dans l'ordre reçu.
242
+ */
243
+ export declare function tablesPresentIn(reader: ISchemaReader, tables: readonly string[]): Promise<string[]>;
244
+ /**
245
+ * Fabrique la migration de RÉFÉRENCE d'une base déjà en place.
246
+ *
247
+ * Orchestration complète du côté fichiers — la connexion est lue, rien n'est
248
+ * écrit dans la base : c'est l'appelant qui décide ensuite d'inscrire cette
249
+ * migration dans l'historique.
250
+ *
251
+ * Le dossier de travail ne survit à rien, ni au succès ni à l'échec : un module
252
+ * temporaire laissé derrière serait relu par le prochain outil qui balaie les
253
+ * sources, et personne ne saurait d'où il sort.
254
+ *
255
+ * @param options - racine du projet, dossier de sortie, cible, tables exclues,
256
+ * tables déclarées, nom voulu pour la migration, dossier de travail.
257
+ * @returns le tag final, le fichier, et si son corps est exécutable.
258
+ * @throws Error si la base ne porte aucune table à adopter, ou si l'outil échoue.
259
+ */
260
+ export declare function adoptFromDatabase({ projectRoot, outDir, dialect, target, excludedTables, declaredTables, name, workDir, }: {
261
+ projectRoot: string;
262
+ outDir: string;
263
+ dialect: SqlDialect;
264
+ target: IMigrationTarget;
265
+ /** Tables à ne PAS lire — celles du framework, et l'historique. */
266
+ excludedTables: readonly string[];
267
+ /**
268
+ * Tables que l'application DÉCLARE.
269
+ *
270
+ * Sert à CONSTATER ce que la lecture a ramassé, jamais à le restreindre :
271
+ * l'outil n'accepte qu'une exclusion (une liste positive tue son
272
+ * introspection sur MySQL). Une base partagée avec un autre logiciel voit
273
+ * donc ses tables entrer dans la référence — le taire les ferait proposer à
274
+ * la suppression à la génération suivante, sans que personne l'ait vu venir.
275
+ */
276
+ declaredTables: readonly string[];
277
+ name: string;
278
+ workDir: string;
279
+ }): Promise<IAdoptedBaseline>;
280
+ export {};