@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,106 @@
1
+ //#region nodefony/src/orm-core/DrizzleTransaction.ts
2
+ /**
3
+ * Nom de savepoint sûr (S1) : un `SAVEPOINT` est un **identifiant** SQL, pas une
4
+ * valeur — il ne peut donc pas être *bindé* en paramètre. Pour éliminer toute
5
+ * injection via un nom non contrôlé (`a"; DROP …`), on impose un identifiant
6
+ * strictement alphanumérique. Un nom invalide échoue **tôt et clairement**.
7
+ */
8
+ const SAVEPOINT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
9
+ function assertSavepointName(name) {
10
+ if (!SAVEPOINT_NAME.test(name)) throw new Error(`DrizzleTransaction: invalid savepoint name ${JSON.stringify(name)} — expected /^[A-Za-z_][A-Za-z0-9_]*$/ (savepoint is an identifier, not a bindable value).`);
11
+ }
12
+ /**
13
+ * Adapte une transaction SQL au contrat portable {@link ITransaction}, sur les
14
+ * trois dialectes (sqlite, postgres, mysql).
15
+ *
16
+ * **Pourquoi une transaction manuelle (`BEGIN`/`COMMIT`/`ROLLBACK`) et pas le
17
+ * helper `db.transaction()` de Drizzle** : `better-sqlite3` est **synchrone** ;
18
+ * son helper attend un callback synchrone et committe au `return` — donc *avant*
19
+ * que les `await` du contrat async (`IRepository`) ne s'exécutent. Encadrer le
20
+ * travail par `BEGIN`…`COMMIT` garantit que toutes les opérations (ordonnées par
21
+ * les `await`) tombent dans la même transaction.
22
+ *
23
+ * **Une transaction vit sur UNE connexion** : {@link DrizzleTransaction.getNative}
24
+ * rend le db Drizzle lié à cette connexion précise (la connexion unique en
25
+ * sqlite ; une connexion empruntée au pool en postgres/mysql). Un repository
26
+ * n'entre dans la transaction que lié par `withTransaction(tx)` — celui rendu par
27
+ * `orm.getRepository()` passe, lui, par le pool, donc HORS transaction.
28
+ *
29
+ * Mode managé (cf `DrizzleOrm.transaction`) : commit auto si la closure résout,
30
+ * rollback auto si elle rejette. `commit`/`rollback` exposés (idempotents via le
31
+ * drapeau interne) pour un pilotage manuel éventuel.
32
+ */
33
+ var DrizzleTransaction = class {
34
+ #db;
35
+ #driver;
36
+ #done = false;
37
+ /**
38
+ * @param db - handle Drizzle lié à la connexion **déjà en `BEGIN`**.
39
+ * @param driver - pilotage transactionnel de cette connexion.
40
+ */
41
+ constructor(db, driver) {
42
+ this.#db = db;
43
+ this.#driver = driver;
44
+ }
45
+ /** Indique si la transaction est déjà terminée (commit ou rollback). */
46
+ isDone() {
47
+ return this.#done;
48
+ }
49
+ /** Valide la transaction (no-op si déjà terminée). */
50
+ async commit() {
51
+ await this.#finish("COMMIT");
52
+ }
53
+ /** Annule la transaction (no-op si déjà terminée). */
54
+ async rollback() {
55
+ await this.#finish("ROLLBACK");
56
+ }
57
+ /**
58
+ * Termine la transaction et rend la connexion — **toujours**, y compris quand
59
+ * l'instruction échoue (sinon le pool fuit une connexion par échec). Le
60
+ * drapeau tombe AVANT l'exécution : un `COMMIT` en échec termine quand même la
61
+ * transaction, et le `rollback()` automatique qui suit doit être un no-op
62
+ * plutôt qu'une seconde instruction sur une connexion déjà rendue.
63
+ *
64
+ * @throws l'erreur du driver, la connexion ayant alors été détruite.
65
+ */
66
+ async #finish(statement) {
67
+ if (this.#done) return;
68
+ this.#done = true;
69
+ try {
70
+ await this.#driver.exec(statement);
71
+ this.#driver.release();
72
+ } catch (error) {
73
+ this.#driver.release(error);
74
+ throw error;
75
+ }
76
+ }
77
+ /** Crée un savepoint nommé (nom validé — anti-injection, cf {@link assertSavepointName}). */
78
+ async savepoint(name) {
79
+ this.#assertLive("savepoint");
80
+ assertSavepointName(name);
81
+ await this.#driver.exec(`SAVEPOINT ${this.#driver.quoteIdent(name)}`);
82
+ }
83
+ /** Annule jusqu'au savepoint sans terminer la transaction (nom validé). */
84
+ async rollbackTo(name) {
85
+ this.#assertLive("rollbackTo");
86
+ assertSavepointName(name);
87
+ await this.#driver.exec(`ROLLBACK TO SAVEPOINT ${this.#driver.quoteIdent(name)}`);
88
+ }
89
+ /**
90
+ * Garde fail-loud : écrire après `commit`/`rollback` viserait une connexion
91
+ * déjà rendue au pool — donc la transaction d'un TIERS, ou aucune. Le dire ici
92
+ * plutôt que laisser le driver rendre une erreur cryptique (ou pire, réussir
93
+ * sur la connexion d'un autre emprunteur).
94
+ *
95
+ * @throws si la transaction est terminée.
96
+ */
97
+ #assertLive(verb) {
98
+ if (this.#done) throw new Error(`DrizzleTransaction: ${verb}() called after the transaction ended (its connection was already released).`);
99
+ }
100
+ /** Expose le handle Drizzle lié à la connexion encadrée par la transaction. */
101
+ getNative() {
102
+ return this.#db;
103
+ }
104
+ };
105
+ //#endregion
106
+ export { DrizzleTransaction };
@@ -0,0 +1,4 @@
1
+ import { DrizzleRepository } from "./DrizzleRepository.js";
2
+ import { DrizzleTransaction } from "./DrizzleTransaction.js";
3
+ import { DrizzleOrm } from "./DrizzleOrm.js";
4
+ export { DrizzleOrm, DrizzleRepository, DrizzleTransaction };
@@ -0,0 +1,318 @@
1
+ import { likeCond } from "./likeSql.js";
2
+ import { pickOrder } from "nodefony";
3
+ import { escapeLikeTerm } from "@nodefony/orm-core";
4
+ import { USER_DEFAULT_ORDER, USER_SORTABLE_FIELDS, USER_TABLE_NAME } from "@nodefony/user";
5
+ import { sql } from "drizzle-orm";
6
+ //#region nodefony/src/queryKit.ts
7
+ /**
8
+ * Cherche l'`id` du user dont le tableau JSON `socialProviders` contient un
9
+ * lien `{provider, providerId}` — le finder du pattern **Shadow User** OAuth
10
+ * (`DrizzleUserRepository.findBySocialProvider`).
11
+ *
12
+ * Sémantique IDENTIQUE sur tous les dialectes : « il EXISTE un élément du
13
+ * tableau dont `provider` ET `providerId` valent exactement ces valeurs »
14
+ * (les autres clés de l'élément, ex. `createdAt`, sont ignorées) :
15
+ * - **sqlite** : `EXISTS (SELECT 1 FROM json_each(col) WHERE json_extract…)`
16
+ * — scan du tableau, valeurs bindées ;
17
+ * - **postgres** : `col @> '[{"provider":…,"providerId":…}]'::jsonb` —
18
+ * containment jsonb natif (indexable GIN), le motif est UN paramètre bindé
19
+ * sérialisé JSON ;
20
+ * - **mysql** : `JSON_CONTAINS(col, <motif>)` — containment JSON natif (même
21
+ * sémantique partielle que `@>` : un objet candidat est contenu dans un
22
+ * élément du tableau si toutes SES clés y matchent), motif = UN paramètre
23
+ * bindé sérialisé JSON, casté par MySQL.
24
+ *
25
+ * @param db - handle Drizzle natif (racine ou transaction) du dialecte.
26
+ * @param dialect - dialecte SQL du connecteur.
27
+ * @param provider - fournisseur d'identité (`"github"`, `"google"`…).
28
+ * @param providerId - identifiant du compte CHEZ le fournisseur.
29
+ * @returns l'`id` du user lié, ou `null` si aucun lien.
30
+ */
31
+ async function findUserIdBySocialProvider(db, dialect, provider, providerId) {
32
+ switch (dialect) {
33
+ case "sqlite": return (await db.all(sql`SELECT "id" AS id FROM ${ident("sqlite", USER_TABLE_NAME)}
34
+ WHERE EXISTS (
35
+ SELECT 1 FROM json_each(${ident("sqlite", USER_TABLE_NAME)}."socialProviders")
36
+ WHERE json_extract(value, '$.provider') = ${provider}
37
+ AND json_extract(value, '$.providerId') = ${providerId}
38
+ ) LIMIT 1`))[0]?.id ?? null;
39
+ case "postgres": {
40
+ const pattern = JSON.stringify([{
41
+ provider,
42
+ providerId
43
+ }]);
44
+ const id = (await db.execute(sql`SELECT "id" AS id FROM ${ident("postgres", USER_TABLE_NAME)}
45
+ WHERE "socialProviders" @> ${pattern}::jsonb LIMIT 1`)).rows[0]?.id;
46
+ return typeof id === "string" ? id : null;
47
+ }
48
+ case "mysql": {
49
+ const pattern = JSON.stringify([{
50
+ provider,
51
+ providerId
52
+ }]);
53
+ const [rows] = await db.execute(sql`SELECT ${sql.raw("`id`")} AS id FROM ${ident("mysql", USER_TABLE_NAME)}
54
+ WHERE JSON_CONTAINS(${sql.raw("`socialProviders`")}, ${pattern}) LIMIT 1`);
55
+ const id = rows[0]?.id;
56
+ return typeof id === "string" ? id : null;
57
+ }
58
+ }
59
+ }
60
+ /**
61
+ * Réservation **atomique** d'une clé d'idempotence en **MySQL** — l'équivalent
62
+ * du `INSERT … ON CONFLICT DO UPDATE … WHERE expiré RETURNING` des dialectes
63
+ * sqlite/pg (cf `DrizzleIdempotencyStore.begin`), reconstruit avec les moyens
64
+ * du dialecte : MySQL n'a **ni `RETURNING` ni `WHERE` sur le `ON DUPLICATE KEY
65
+ * UPDATE`** — et le verdict `affectedRows` d'un ODKU est AMBIGU sous mysql2
66
+ * (flag `CLIENT_FOUND_ROWS` par défaut : ligne « matched inchangée » = 1,
67
+ * indistinguable d'un INSERT — prouvé sur serveur réel). D'où DEUX
68
+ * instructions, chacune atomique et au verdict non-ambigu :
69
+ *
70
+ * 1. **`INSERT IGNORE`** — clé neuve : `affectedRows = 1` ⇒ réservation
71
+ * acquise ; clé existante : TOUJOURS `0` (`FOUND_ROWS` ne concerne pas les
72
+ * doublons d'INSERT). L'unicité de la PK garantit UN seul gagnant sous
73
+ * concurrence. (`IGNORE` downgrade aussi le mode strict en warnings — sans
74
+ * enjeu ici : la clé est bornée en amont, `IDEMPOTENCY_KEY_MAX` 255 ≪
75
+ * varchar(512).)
76
+ * 2. **Vol d'une entrée MORTE** : `UPDATE … WHERE key = ? AND expiresAt < now`
77
+ * — le WHERE porte la condition « morte » ; l'UPDATE change TOUJOURS
78
+ * `expiresAt` (nouveau > now > ancien) donc `affectedRows = 1` ⇔ vol acquis,
79
+ * quel que soit le flag. Deux voleurs concurrents se sérialisent sur le
80
+ * verrou de ligne InnoDB : le second ré-évalue le WHERE sur la version
81
+ * committée (current read) → 0 ligne. Jamais de double-vol.
82
+ *
83
+ * Course résiduelle INSERT(0) → UPDATE(0) alors que la clé vient d'être
84
+ * supprimée (gc/abort) : verdict « contention » → le SELECT de `begin` ne la
85
+ * trouve plus → `in-flight` prudent (jamais `fresh` hors réservation atomique,
86
+ * l'invariant anti double-effet du contrat).
87
+ *
88
+ * @param db - handle Drizzle natif mysql.
89
+ * @param args - clé scopée, empreinte payload, horloge et échéance du bail.
90
+ * @returns `true` si la réservation est acquise (= le `fresh` du contrat).
91
+ */
92
+ async function reserveIdempotencyKeyMysql(db, args) {
93
+ const { key, fingerprint, now, leaseExpiresAt } = args;
94
+ const exec = db;
95
+ const [inserted] = await exec.execute(sql`INSERT IGNORE INTO ${sql.raw("`idempotency_key`")}
96
+ (${sql.raw("`key`, `fingerprint`, `state`, `response`, `expiresAt`")})
97
+ VALUES (${key}, ${fingerprint}, 'if', NULL, ${leaseExpiresAt})`);
98
+ if ((inserted?.affectedRows ?? 0) === 1) return true;
99
+ const [stolen] = await exec.execute(sql`UPDATE ${sql.raw("`idempotency_key`")}
100
+ SET ${sql.raw("`fingerprint`")} = ${fingerprint},
101
+ ${sql.raw("`state`")} = 'if',
102
+ ${sql.raw("`response`")} = NULL,
103
+ ${sql.raw("`expiresAt`")} = ${leaseExpiresAt}
104
+ WHERE ${sql.raw("`key`")} = ${key}
105
+ AND ${sql.raw("`expiresAt`")} < ${now}`);
106
+ return (stolen?.affectedRows ?? 0) > 0;
107
+ }
108
+ /** Identifiant quoté selon le dialecte (`"x"` SQL standard, `` `x` `` MySQL). */
109
+ function ident(dialect, name) {
110
+ return dialect === "mysql" ? sql.raw("`" + name + "`") : sql.raw("\"" + name + "\"");
111
+ }
112
+ /** Condition `enabled = ?` — booléen natif en PG, `0/1` ailleurs (better-sqlite3/mysql2). */
113
+ function enabledCond(dialect, flag) {
114
+ const col = ident(dialect, "enabled");
115
+ if (dialect === "postgres") return sql`${col} = ${flag}`;
116
+ return sql`${col} = ${flag ? 1 : 0}`;
117
+ }
118
+ /** Condition `locked = ?` — même routage booléen qu'`enabled`. */
119
+ function lockedCond(dialect, flag) {
120
+ const col = ident(dialect, "locked");
121
+ if (dialect === "postgres") return sql`${col} = ${flag}`;
122
+ return sql`${col} = ${flag ? 1 : 0}`;
123
+ }
124
+ /**
125
+ * Condition « le compte a (ou n'a pas) au moins un fournisseur externe ».
126
+ *
127
+ * `socialProviders` est un tableau JSON, dont la VACUITÉ s'exprime différemment
128
+ * selon le dialecte — d'où le routage, comme pour le containment de `roles` :
129
+ * SQLite compte les éléments, PostgreSQL compare au tableau vide `jsonb`, MySQL
130
+ * lit la longueur native. La colonne est `NOT NULL` avec `[]` par défaut, donc
131
+ * aucun `NULL` à considérer.
132
+ */
133
+ function hasSocialCond(dialect, flag) {
134
+ const col = ident(dialect, "socialProviders");
135
+ switch (dialect) {
136
+ case "sqlite": return flag ? sql`json_array_length(${col}) > 0` : sql`json_array_length(${col}) = 0`;
137
+ case "postgres": return flag ? sql`jsonb_array_length(${col}) > 0` : sql`jsonb_array_length(${col}) = 0`;
138
+ case "mysql": return flag ? sql`JSON_LENGTH(${col}) > 0` : sql`JSON_LENGTH(${col}) = 0`;
139
+ }
140
+ }
141
+ /** Condition « le tableau JSON `roles` contient `role` » — forme native du dialecte. */
142
+ function roleCond(dialect, role) {
143
+ const roles = ident(dialect, "roles");
144
+ switch (dialect) {
145
+ case "sqlite": return sql`EXISTS (SELECT 1 FROM json_each(${roles}) WHERE value = ${role})`;
146
+ case "postgres": return sql`${roles} @> ${JSON.stringify([role])}::jsonb`;
147
+ case "mysql": return sql`JSON_CONTAINS(${roles}, ${JSON.stringify(role)})`;
148
+ }
149
+ }
150
+ /** Condition `LOWER(identifier) LIKE %q%` (sous-chaîne insensible à la casse, `%`/`_` échappés). */
151
+ function likeIdentifierCond(dialect, q) {
152
+ const idCol = ident(dialect, "identifier");
153
+ const pattern = `%${escapeLikeTerm(q.toLowerCase())}%`;
154
+ return likeCond(dialect, sql`LOWER(${idCol})`, pattern);
155
+ }
156
+ /** Compose la clause WHERE des filtres actifs (undefined si aucun filtre). */
157
+ function userWhere(dialect, f) {
158
+ const conds = [];
159
+ if (f.enabled !== void 0) conds.push(enabledCond(dialect, f.enabled));
160
+ if (f.locked !== void 0) conds.push(lockedCond(dialect, f.locked));
161
+ if (f.hasSocial !== void 0) conds.push(hasSocialCond(dialect, f.hasSocial));
162
+ if (f.role !== void 0) conds.push(roleCond(dialect, f.role));
163
+ if (f.q !== void 0 && f.q.length > 0) conds.push(likeIdentifierCond(dialect, f.q));
164
+ if (conds.length === 0) return void 0;
165
+ return sql.join(conds, sql` AND `);
166
+ }
167
+ /**
168
+ * Compose la clause `ORDER BY` d'un listing natif, quelle que soit l'entité.
169
+ *
170
+ * 🔒 Un nom de colonne ne se lie pas en paramètre : il est **concaténé** dans la
171
+ * requête (`ident()` → `sql.raw`). Le tri est donc borné par `pickOrder` (core),
172
+ * exactement comme dans les stores mémoire et Mongo — une seconde
173
+ * implémentation de ce filtre divergerait sans que rien ne le signale, et ici
174
+ * elle ouvrirait une injection. Le data plane a déjà refusé l'inconnu en 400 ;
175
+ * cet étage existe pour l'appelant interne qui l'oublierait.
176
+ *
177
+ * @param allowed - les seuls champs acceptés (vocabulaire public de l'entité).
178
+ * @param fallback - ordre appliqué si rien de recevable n'a été demandé.
179
+ * @param tiebreaker - colonne ajoutée en dernier ressort quand l'ordre retenu ne
180
+ * la contient pas : sans elle, deux lignes ex æquo peuvent changer de page
181
+ * entre deux appels, et l'une des deux ne jamais apparaître.
182
+ */
183
+ function orderBySql(dialect, order, allowed, fallback, tiebreaker) {
184
+ const effective = pickOrder(order, allowed, fallback);
185
+ const chunks = effective.map(([field, dir]) => sql`${ident(dialect, field)} ${sql.raw(dir === "DESC" ? "DESC" : "ASC")}`);
186
+ if (tiebreaker !== void 0 && !effective.some(([f]) => f === tiebreaker)) chunks.push(sql`${ident(dialect, tiebreaker)} ASC`);
187
+ return sql.join(chunks, sql`, `);
188
+ }
189
+ /** Exécute un SELECT et normalise le retour en tableau de lignes (API native divergente). */
190
+ async function runSelect(db, dialect, query) {
191
+ switch (dialect) {
192
+ case "sqlite": return await db.all(query);
193
+ case "postgres": return (await db.execute(query)).rows;
194
+ case "mysql": {
195
+ const [rows] = await db.execute(query);
196
+ return rows;
197
+ }
198
+ }
199
+ }
200
+ /**
201
+ * Sélectionne les `id` d'une **page** d'utilisateurs (filtres + tri + `LIMIT/
202
+ * OFFSET`), sans matérialiser les lignes. `limit + 1` → `hasNext` sans `COUNT`.
203
+ *
204
+ * @returns les `id` de la page (au plus `window.limit`, dans l'ordre du tri) et
205
+ * `hasNext`.
206
+ */
207
+ async function listUserIdsPage(db, dialect, filters, window) {
208
+ const limit = Math.max(1, Math.floor(window.limit));
209
+ const offset = Math.max(0, Math.floor(window.offset));
210
+ const where = userWhere(dialect, filters);
211
+ const whereSql = where ? sql` WHERE ${where}` : sql``;
212
+ const ids = (await runSelect(db, dialect, sql`SELECT ${ident(dialect, "id")} AS id FROM ${ident(dialect, USER_TABLE_NAME)}${whereSql}
213
+ ORDER BY ${orderBySql(dialect, window.order, USER_SORTABLE_FIELDS, USER_DEFAULT_ORDER, "id")} LIMIT ${limit + 1} OFFSET ${offset}`)).map((r) => r.id).filter((id) => typeof id === "string");
214
+ const hasNext = ids.length > limit;
215
+ return {
216
+ ids: hasNext ? ids.slice(0, limit) : ids,
217
+ hasNext
218
+ };
219
+ }
220
+ /**
221
+ * Condition « endpoint en échec » — `failureCount > 0` (ou `= 0` pour les sains).
222
+ *
223
+ * Même forme sur les trois dialectes : la colonne est un entier, la comparaison
224
+ * est indexable, et aucun `NULL` n'est possible (`failureCount` est `NOT NULL`,
225
+ * initialisé à 0 à la création).
226
+ */
227
+ function failingCond(dialect, flag) {
228
+ const col = ident(dialect, "failureCount");
229
+ return flag ? sql`${col} > 0` : sql`${col} = 0`;
230
+ }
231
+ /** Condition « le tableau JSON `events` contient `event` » — forme native du dialecte. */
232
+ function eventCond(dialect, event) {
233
+ const events = ident(dialect, "events");
234
+ switch (dialect) {
235
+ case "sqlite": return sql`EXISTS (SELECT 1 FROM json_each(${events}) WHERE value = ${event})`;
236
+ case "postgres": return sql`${events} @> ${JSON.stringify([event])}::jsonb`;
237
+ case "mysql": return sql`JSON_CONTAINS(${events}, ${JSON.stringify(event)})`;
238
+ }
239
+ }
240
+ /**
241
+ * Condition `LOWER(url) LIKE %q% OR LOWER(description) LIKE %q%` — la recherche
242
+ * d'un humain porte sur l'adresse OU le libellé. `description` est nullable :
243
+ * `COALESCE` évite qu'un `NULL` fasse retomber tout le `OR` à `NULL` (donc faux).
244
+ */
245
+ function likeWebhookCond(dialect, q) {
246
+ const url = ident(dialect, "url");
247
+ const description = ident(dialect, "description");
248
+ const pattern = `%${escapeLikeTerm(q.toLowerCase())}%`;
249
+ const onUrl = likeCond(dialect, sql`LOWER(${url})`, pattern);
250
+ const onDescription = likeCond(dialect, sql`LOWER(COALESCE(${description}, ''))`, pattern);
251
+ return sql`(${onUrl} OR ${onDescription})`;
252
+ }
253
+ /** Compose la clause WHERE des filtres actifs (undefined si aucun filtre). */
254
+ function webhookWhere(dialect, f) {
255
+ const conds = [];
256
+ if (f.enabled !== void 0) conds.push(enabledCond(dialect, f.enabled));
257
+ if (f.failing !== void 0) conds.push(failingCond(dialect, f.failing));
258
+ if (f.event !== void 0) conds.push(eventCond(dialect, f.event));
259
+ if (f.q !== void 0 && f.q.length > 0) conds.push(likeWebhookCond(dialect, f.q));
260
+ if (conds.length === 0) return void 0;
261
+ return sql.join(conds, sql` AND `);
262
+ }
263
+ /**
264
+ * Sélectionne les `id` d'une **page** d'endpoints webhook (filtres + tri +
265
+ * `LIMIT/OFFSET`), sans matérialiser les lignes. `limit + 1` → `hasNext` sans
266
+ * `COUNT`.
267
+ *
268
+ * Le tri descend dans le `ORDER BY` — jamais appliqué après découpe, sinon la
269
+ * 2ᵉ page recommencerait au lieu de continuer la 1ʳᵉ. À défaut d'`order`
270
+ * recevable, l'ordre par défaut `createdAt DESC, id ASC` rend l'offset
271
+ * déterministe quand deux endpoints partagent la même milliseconde de création.
272
+ *
273
+ * @param window - fenêtre de page **et** tri demandé (déjà en noms publics ; il
274
+ * est re-filtré ici par l'allowlist, cf {@link orderBySql}).
275
+ * @returns les `id` de la page (au plus `limit`, dans l'ordre) et `hasNext`.
276
+ */
277
+ async function listWebhookIdsPage(db, dialect, filters, window) {
278
+ const limit = Math.max(1, Math.floor(window.limit));
279
+ const offset = Math.max(0, Math.floor(window.offset));
280
+ const where = webhookWhere(dialect, filters);
281
+ const whereSql = where ? sql` WHERE ${where}` : sql``;
282
+ const orderSql = orderBySql(dialect, window.order, window.sortable ?? [], [["createdAt", "DESC"], ["id", "ASC"]], "id");
283
+ const ids = (await runSelect(db, dialect, sql`SELECT ${ident(dialect, "id")} AS id FROM ${ident(dialect, "webhook_endpoint")}${whereSql}
284
+ ORDER BY ${orderSql}
285
+ LIMIT ${limit + 1} OFFSET ${offset}`)).map((r) => r.id).filter((id) => typeof id === "string");
286
+ const hasNext = ids.length > limit;
287
+ return {
288
+ ids: hasNext ? ids.slice(0, limit) : ids,
289
+ hasNext
290
+ };
291
+ }
292
+ /**
293
+ * Compte les endpoints qui matchent les filtres (`COUNT(*)` natif filtré) —
294
+ * base du `total` d'une page d'endpoints.
295
+ *
296
+ * @returns le nombre de lignes correspondantes.
297
+ */
298
+ async function countWebhookEndpoints(db, dialect, filters) {
299
+ const where = webhookWhere(dialect, filters);
300
+ const whereSql = where ? sql` WHERE ${where}` : sql``;
301
+ const rows = await runSelect(db, dialect, sql`SELECT COUNT(*) AS cnt FROM ${ident(dialect, "webhook_endpoint")}${whereSql}`);
302
+ return Number(rows[0]?.cnt ?? 0);
303
+ }
304
+ /**
305
+ * Compte les utilisateurs qui matchent les filtres (`COUNT(*)` natif filtré) —
306
+ * base commune du `total` de la page ET du garde-fou `countActiveAdmins`
307
+ * (`{ enabled: true, role: adminRole }`).
308
+ *
309
+ * @returns le nombre de lignes correspondantes.
310
+ */
311
+ async function countUsers(db, dialect, filters) {
312
+ const where = userWhere(dialect, filters);
313
+ const whereSql = where ? sql` WHERE ${where}` : sql``;
314
+ const rows = await runSelect(db, dialect, sql`SELECT COUNT(*) AS cnt FROM ${ident(dialect, USER_TABLE_NAME)}${whereSql}`);
315
+ return Number(rows[0]?.cnt ?? 0);
316
+ }
317
+ //#endregion
318
+ export { countUsers, countWebhookEndpoints, findUserIdBySocialProvider, listUserIdsPage, listWebhookIdsPage, reserveIdempotencyKeyMysql };
@@ -0,0 +1,55 @@
1
+ import path from "node:path";
2
+ //#region nodefony/src/safeTarget.ts
3
+ /**
4
+ * Comment DÉSIGNER la base d'un connecteur sans rien faire fuiter.
5
+ *
6
+ * **Pourquoi une règle à part, et pourquoi elle est PURE.** Trois endroits
7
+ * doivent nommer la même base — l'adaptateur, qui la publie au plan
8
+ * d'administration ; les commandes de migration, qui doivent dire sur QUOI
9
+ * elles ont travaillé ; et le rapport d'état, que lisent un humain, un script
10
+ * et une console. Trois rendus recopiés divergeraient, et la divergence serait
11
+ * silencieuse : chacun passerait ses propres tests. Écrite ici, la règle
12
+ * s'éprouve sans base, sans kernel et sans variable d'environnement.
13
+ *
14
+ * Deux familles, deux dangers différents :
15
+ *
16
+ * - **une base FICHIER** (sqlite) : le danger est l'arborescence du serveur —
17
+ * le nom de l'utilisateur, la structure du disque. Le chemin est donc rendu
18
+ * RELATIF au répertoire de travail, et se réduit au nom de fichier dès qu'il
19
+ * sort du projet ;
20
+ * - **une base RÉSEAU** (PostgreSQL, MySQL) : le danger est le compte. Une URL
21
+ * de migration porte l'identifiant et le mot de passe du seul compte autorisé
22
+ * à modifier un schéma de production. Seuls l'hôte, le port et le nom de la
23
+ * base sont rendus — jamais la partie qui précède l'arobase.
24
+ *
25
+ * ⚠️ `redactSecrets` ne couvre PAS ce cas : aucune de ses familles ne masque un
26
+ * mot de passe d'URL. C'est pourquoi cette fonction ne masque pas, elle
27
+ * RECONSTRUIT — on ne publie que les morceaux dont on a décidé qu'ils sont
28
+ * publiables, ce qui reste vrai pour une URL dont la forme nous surprendrait.
29
+ *
30
+ * @param target - dialecte et coordonnées, telles que la résolution les rend.
31
+ * @param cwd - répertoire de référence pour relativiser un chemin de fichier.
32
+ * @param grammar - grammaire de chemins à employer. Injectée pour une seule
33
+ * raison : une fonction qui lit `path` global ne s'éprouve que sur la
34
+ * plateforme qu'elle décrit, c'est-à-dire jamais ici. Avec `path.win32`, le
35
+ * cas Windows se joue sur n'importe quel système.
36
+ * @returns une désignation affichable, sans identifiant ni mot de passe.
37
+ */
38
+ function describeTargetSafely(target, cwd = process.cwd(), grammar = path) {
39
+ if (target.dialect === "sqlite") {
40
+ const file = target.filename ?? ":memory:";
41
+ if (file === ":memory:" || !grammar.isAbsolute(file)) return file;
42
+ const relatif = grammar.relative(cwd, file);
43
+ return relatif && !relatif.startsWith("..") ? relatif.split(grammar.sep).join("/") : grammar.basename(file);
44
+ }
45
+ const defaultPort = target.dialect === "postgres" ? "5432" : "3306";
46
+ if (target.url === void 0 || target.url === "") return target.dialect;
47
+ try {
48
+ const u = new URL(target.url);
49
+ return `${u.hostname}:${u.port || defaultPort}${u.pathname}`;
50
+ } catch {
51
+ return target.dialect;
52
+ }
53
+ }
54
+ //#endregion
55
+ export { describeTargetSafely };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `@nodefony/drizzle` — module Drizzle ORM (driver `better-sqlite3`) sur
3
+ * `@nodefony/orm-core`.
4
+ *
5
+ * **Module bootable** : enregistré dans `@modules()`, son {@link DrizzleService}
6
+ * connecte au boot un {@link DrizzleOrm} par connecteur configuré (ORM par défaut
7
+ * SQL recommandé). Expose aussi les **classes adapter** (orm-core) pour un usage
8
+ * direct/banc-test. driver concret du socle multi-ORM (avec Mongoose) ; type-safe-first (a figé la forme des opérateurs riches, ADR-0003 #3).
9
+ */
10
+ import { Kernel, Module } from "nodefony";
11
+ import { defineDrizzleConfig, drizzleConfigJsonSchema } from "./nodefony/config/defineModuleConfig.js";
12
+ import DrizzleService from "./nodefony/service/DrizzleService.js";
13
+ import type { IDrizzleConfig, IDrizzleConfigInput } from "./nodefony/interfaces/IDrizzleConfig.js";
14
+ declare module "nodefony" {
15
+ interface NodefonyModuleConfig {
16
+ "@nodefony/drizzle": IDrizzleConfigInput;
17
+ }
18
+ }
19
+ declare class Drizzle extends Module<IDrizzleConfig> {
20
+ constructor(kernel: Kernel);
21
+ /** JSON Schema de la config drizzle → data plane admin (config riche Studio). */
22
+ configSchema(): unknown;
23
+ /**
24
+ * Valide la config (défauts + `module.options` + surcharge env) au boot via
25
+ * `defineDrizzleConfig`, et l'expose au container sous `drizzleConfig` pour que
26
+ * le `DrizzleService` la consomme. Plante propre si la config est invalide
27
+ * (convention Zod, alignée sur `@nodefony/mongoose` — audit config ORM 2026-06).
28
+ */
29
+ onKernelRegister(): Promise<this>;
30
+ /**
31
+ * Monte le data plane ORM (`/nodefony/orm/api/*` + providers santé/flux pour la
32
+ * sonde cluster et le drill Studio) via {@link wireOrmAdminPlane}. Branchement
33
+ * GLOBAL (couvre tous les ORM) et idempotent — factorisé en orm-core (C5), chaque
34
+ * driver l'invoque à l'identique. orm-core étant une lib pure, c'est un module
35
+ * driver qui le déclenche (avant le `mountAll` de framework à `onKernelReady`).
36
+ */
37
+ onKernelBoot(): Promise<this>;
38
+ }
39
+ export default Drizzle;
40
+ export { DrizzleService };
41
+ export { defineDrizzleConfig, drizzleConfigJsonSchema };
42
+ export { drizzleConfigSchema, type DrizzleConfig, } from "./nodefony/config/config.js";
43
+ export type { IDrizzleConfig, IDrizzleConfigInput, IDrizzleConnectorConfig, } from "./nodefony/interfaces/IDrizzleConfig.js";
44
+ export { default as SessionStorage } from "./nodefony/src/SessionStorage.js";
45
+ export { sessionTable, createSessionTable, createSessionEntity, registerSessionEntity, SESSION_CONNECTOR, SESSION_ENTITY_NAME, } from "./nodefony/entity/sessionEntity.js";
46
+ export type { SessionRow } from "./nodefony/entity/sessionEntity.js";
47
+ export { DrizzleMigrator, DEFAULT_LOCK_TIMEOUT_MS, openMigrationDriver, frameworkMigrationsDir, defaultMigrationSources, migrationHash, loadSources, orderSources, HISTORY_TABLE, FORMAT_MARKER, FRAMEWORK_SOURCE, APP_SOURCE, FRAMEWORK_RANK, APP_RANK, PG_LOCK_KEY, MYSQL_LOCK_NAME_SQL, MigrationVerdictError, type IDrizzleMigratorOptions, type IMigrateOptions, type IMigrationTarget, type IMigrationSource, type IMigrationFile, type IMigrationPlan, type IMigrationRun, type IMigrationApplied, type IMigrationDrift, type IMigrationDriver, type IAppliedMigration, type IMigrationVerdict, type IMigrationAction, type MigrationVerdictCode, type ISchemaComparison, type ISchemaGap, } from "./nodefony/src/migrator/index.js";
48
+ export { MIGRATION_FORMAT_VERSION, EXIT, buildReport, verdictOf, exitCodeOf, meaningOf, renderStatus, renderRefusal, styleFor, action, type IMigrationReport, type IMigrationSourceReport, type IReportContext, type MigrationVerdictName, type IStyle, } from "./nodefony/src/migrator/explain.js";
49
+ export { MIGRATE_URL_ENV, readMigrationEnv, resolveDdlMode, resolveCheckMode, resetAllowed, resolveConnector, resolveDivergenceMode, knownConnectors, appMigrationsDir, buildMigrator, type IMigrationEnv, type IConnectorResolution, } from "./nodefony/src/migrator/resolve.js";
50
+ export { DDL_MODES, MIGRATION_CHECK_MODES, DIVERGENCE_MODES, type DdlMode, type MigrationCheckMode, type DivergenceMode, } from "./nodefony/config/config.js";
51
+ export { DrizzleOrm, DrizzleRepository, DrizzleTransaction, } from "./nodefony/src/orm-core/index.js";
52
+ export type { DrizzleOrmOptions, DrizzleDb, DrizzleResolvedRelation, } from "./nodefony/src/orm-core/index.js";
53
+ export { userTable, createUserTable, createUserEntity, registerUserEntity, } from "./nodefony/entity/userTable.js";
54
+ export type { UserRow } from "./nodefony/entity/userTable.js";
55
+ export { DrizzleUserRepository } from "./nodefony/src/DrizzleUserRepository.js";
56
+ export { accessTokenTable, deniedJtiTable, subjectRevocationTable, createAccessTokenTable, createDeniedJtiTable, createSubjectRevocationTable, createTokenEntities, registerTokenEntities, TOKEN_ENTITY_NAMES, } from "./nodefony/entity/tokenEntity.js";
57
+ export type { DeniedJtiRow, SubjectRevocationRow, } from "./nodefony/entity/tokenEntity.js";
58
+ export { DrizzleTokenStore } from "./nodefony/src/DrizzleTokenStore.js";
59
+ export { auditEventTable, createAuditEventTable, createAuditEntities, registerAuditEntities, AUDIT_ENTITY_NAMES, } from "./nodefony/entity/auditEventEntity.js";
60
+ export type { AuditEventRow } from "./nodefony/entity/auditEventEntity.js";
61
+ export { DrizzleAuditStore } from "./nodefony/src/DrizzleAuditStore.js";
62
+ export { webAuthnCredentialTable, createWebAuthnCredentialTable, createWebAuthnCredentialEntity, registerWebAuthnCredentialEntity, WEBAUTHN_CREDENTIAL_ENTITY, } from "./nodefony/entity/webAuthnCredentialEntity.js";
63
+ export type { WebAuthnCredentialRow } from "./nodefony/entity/webAuthnCredentialEntity.js";
64
+ export { DrizzleWebAuthnCredentialStore } from "./nodefony/src/DrizzleWebAuthnCredentialStore.js";
65
+ export { totpSecretTable, createTotpSecretTable, createTotpSecretEntity, registerTotpSecretEntity, TOTP_SECRET_ENTITY, } from "./nodefony/entity/totpSecretEntity.js";
66
+ export type { TotpSecretRow } from "./nodefony/entity/totpSecretEntity.js";
67
+ export { DrizzleTotpSecretStore } from "./nodefony/src/DrizzleTotpSecretStore.js";
68
+ export { idempotencyKeyTable, createIdempotencyTable, createIdempotencyEntities, registerIdempotencyEntities, IDEMPOTENCY_ENTITY_NAME, } from "./nodefony/entity/idempotencyEntity.js";
69
+ export type { IdempotencyKeyRow } from "./nodefony/entity/idempotencyEntity.js";
70
+ export { DrizzleIdempotencyStore } from "./nodefony/src/DrizzleIdempotencyStore.js";
71
+ export type { SqlDialect } from "./nodefony/config/config.js";
72
+ export { webhookEndpointTable, createWebhookEndpointTable, createWebhookEndpointEntity, registerWebhookEndpointEntity, WEBHOOK_ENDPOINT_ENTITY, } from "./nodefony/entity/webhookEndpointEntity.js";
73
+ export type { WebhookEndpointRow } from "./nodefony/entity/webhookEndpointEntity.js";
74
+ export { DrizzleWebhookStore } from "./nodefony/src/DrizzleWebhookStore.js";
75
+ export { registerDrizzleFrameworkStores, isFrameworkFallbackEntity, FRAMEWORK_CONNECTOR, } from "./nodefony/registerStores.js";
76
+ export type { IFrameworkStoresReport } from "./nodefony/registerStores.js";