@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.
- package/LICENSE +544 -0
- package/README.md +162 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +105 -0
- package/dist/nodefony/command/migrateShared.js +247 -0
- package/dist/nodefony/command/orm-generate.js +356 -0
- package/dist/nodefony/command/orm-migrate-baseline.js +208 -0
- package/dist/nodefony/command/orm-migrate-repair.js +114 -0
- package/dist/nodefony/command/orm-migrate-status.js +67 -0
- package/dist/nodefony/command/orm-migrate.js +141 -0
- package/dist/nodefony/command/orm-reset.js +166 -0
- package/dist/nodefony/config/config.js +107 -0
- package/dist/nodefony/config/defineModuleConfig.js +63 -0
- package/dist/nodefony/entity/auditEventEntity.js +93 -0
- package/dist/nodefony/entity/colKit.js +260 -0
- package/dist/nodefony/entity/idempotencyEntity.js +74 -0
- package/dist/nodefony/entity/sessionEntity.js +75 -0
- package/dist/nodefony/entity/tokenEntity.js +198 -0
- package/dist/nodefony/entity/totpSecretEntity.js +98 -0
- package/dist/nodefony/entity/userTable.js +141 -0
- package/dist/nodefony/entity/webAuthnCredentialEntity.js +114 -0
- package/dist/nodefony/entity/webhookEndpointEntity.js +106 -0
- package/dist/nodefony/interfaces/IDrizzleConfig.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/migrations-schema/mysql.js +48 -0
- package/dist/nodefony/migrations-schema/postgres.js +48 -0
- package/dist/nodefony/migrations-schema/sqlite.js +48 -0
- package/dist/nodefony/registerStores.js +218 -0
- package/dist/nodefony/service/DrizzleService.js +282 -0
- package/dist/nodefony/src/DrizzleAuditStore.js +203 -0
- package/dist/nodefony/src/DrizzleIdempotencyStore.js +278 -0
- package/dist/nodefony/src/DrizzleTokenStore.js +244 -0
- package/dist/nodefony/src/DrizzleTotpSecretStore.js +151 -0
- package/dist/nodefony/src/DrizzleUserRepository.js +217 -0
- package/dist/nodefony/src/DrizzleWebAuthnCredentialStore.js +159 -0
- package/dist/nodefony/src/DrizzleWebhookStore.js +169 -0
- package/dist/nodefony/src/SessionStorage.js +259 -0
- package/dist/nodefony/src/connectorTarget.js +59 -0
- package/dist/nodefony/src/likeSql.js +50 -0
- package/dist/nodefony/src/migrator/DrizzleMigrator.js +775 -0
- package/dist/nodefony/src/migrator/adopt.js +553 -0
- package/dist/nodefony/src/migrator/appSchema.js +414 -0
- package/dist/nodefony/src/migrator/catalog.js +76 -0
- package/dist/nodefony/src/migrator/destructive.js +213 -0
- package/dist/nodefony/src/migrator/divergence.js +84 -0
- package/dist/nodefony/src/migrator/drivers/index.js +39 -0
- package/dist/nodefony/src/migrator/drivers/mysqlDriver.js +147 -0
- package/dist/nodefony/src/migrator/drivers/postgresDriver.js +151 -0
- package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +121 -0
- package/dist/nodefony/src/migrator/explain.js +565 -0
- package/dist/nodefony/src/migrator/hash.js +47 -0
- package/dist/nodefony/src/migrator/history.js +219 -0
- package/dist/nodefony/src/migrator/index.js +16 -0
- package/dist/nodefony/src/migrator/kit.js +296 -0
- package/dist/nodefony/src/migrator/name.js +68 -0
- package/dist/nodefony/src/migrator/paths.js +88 -0
- package/dist/nodefony/src/migrator/refusals.js +143 -0
- package/dist/nodefony/src/migrator/resolve.js +281 -0
- package/dist/nodefony/src/migrator/schemaDiff.js +86 -0
- package/dist/nodefony/src/migrator/sources.js +419 -0
- package/dist/nodefony/src/migrator/status.js +231 -0
- package/dist/nodefony/src/migrator/types.js +91 -0
- package/dist/nodefony/src/orm-core/DrizzleOrm.js +1154 -0
- package/dist/nodefony/src/orm-core/DrizzleRepository.js +610 -0
- package/dist/nodefony/src/orm-core/DrizzleTransaction.js +106 -0
- package/dist/nodefony/src/orm-core/index.js +4 -0
- package/dist/nodefony/src/queryKit.js +318 -0
- package/dist/nodefony/src/safeTarget.js +55 -0
- package/dist/types/index.d.ts +76 -0
- package/dist/types/nodefony/command/migrateShared.d.ts +137 -0
- package/dist/types/nodefony/command/orm-generate.d.ts +53 -0
- package/dist/types/nodefony/command/orm-migrate-baseline.d.ts +87 -0
- package/dist/types/nodefony/command/orm-migrate-repair.d.ts +66 -0
- package/dist/types/nodefony/command/orm-migrate-status.d.ts +38 -0
- package/dist/types/nodefony/command/orm-migrate.d.ts +65 -0
- package/dist/types/nodefony/command/orm-reset.d.ts +55 -0
- package/dist/types/nodefony/config/config.d.ts +110 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +24 -0
- package/dist/types/nodefony/entity/auditEventEntity.d.ts +56 -0
- package/dist/types/nodefony/entity/colKit.d.ts +130 -0
- package/dist/types/nodefony/entity/idempotencyEntity.d.ts +52 -0
- package/dist/types/nodefony/entity/sessionEntity.d.ts +50 -0
- package/dist/types/nodefony/entity/tokenEntity.d.ts +53 -0
- package/dist/types/nodefony/entity/totpSecretEntity.d.ts +61 -0
- package/dist/types/nodefony/entity/userTable.d.ts +65 -0
- package/dist/types/nodefony/entity/webAuthnCredentialEntity.d.ts +57 -0
- package/dist/types/nodefony/entity/webhookEndpointEntity.d.ts +58 -0
- package/dist/types/nodefony/interfaces/IDrizzleConfig.d.ts +17 -0
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/migrations-schema/mysql.d.ts +9 -0
- package/dist/types/nodefony/migrations-schema/postgres.d.ts +9 -0
- package/dist/types/nodefony/migrations-schema/sqlite.d.ts +9 -0
- package/dist/types/nodefony/registerStores.d.ts +52 -0
- package/dist/types/nodefony/service/DrizzleService.d.ts +27 -0
- package/dist/types/nodefony/src/DrizzleAuditStore.d.ts +65 -0
- package/dist/types/nodefony/src/DrizzleIdempotencyStore.d.ts +129 -0
- package/dist/types/nodefony/src/DrizzleTokenStore.d.ts +146 -0
- package/dist/types/nodefony/src/DrizzleTotpSecretStore.d.ts +60 -0
- package/dist/types/nodefony/src/DrizzleUserRepository.d.ts +67 -0
- package/dist/types/nodefony/src/DrizzleWebAuthnCredentialStore.d.ts +62 -0
- package/dist/types/nodefony/src/DrizzleWebhookStore.d.ts +79 -0
- package/dist/types/nodefony/src/SessionStorage.d.ts +72 -0
- package/dist/types/nodefony/src/connectorTarget.d.ts +48 -0
- package/dist/types/nodefony/src/likeSql.d.ts +29 -0
- package/dist/types/nodefony/src/migrator/DrizzleMigrator.d.ts +94 -0
- package/dist/types/nodefony/src/migrator/adopt.d.ts +280 -0
- package/dist/types/nodefony/src/migrator/appSchema.d.ts +223 -0
- package/dist/types/nodefony/src/migrator/catalog.d.ts +100 -0
- package/dist/types/nodefony/src/migrator/destructive.d.ts +123 -0
- package/dist/types/nodefony/src/migrator/divergence.d.ts +61 -0
- package/dist/types/nodefony/src/migrator/drivers/index.d.ts +26 -0
- package/dist/types/nodefony/src/migrator/drivers/mysqlDriver.d.ts +83 -0
- package/dist/types/nodefony/src/migrator/drivers/postgresDriver.d.ts +81 -0
- package/dist/types/nodefony/src/migrator/drivers/sqliteDriver.d.ts +53 -0
- package/dist/types/nodefony/src/migrator/explain.d.ts +424 -0
- package/dist/types/nodefony/src/migrator/hash.d.ts +39 -0
- package/dist/types/nodefony/src/migrator/history.d.ts +139 -0
- package/dist/types/nodefony/src/migrator/index.d.ts +20 -0
- package/dist/types/nodefony/src/migrator/kit.d.ts +141 -0
- package/dist/types/nodefony/src/migrator/name.d.ts +52 -0
- package/dist/types/nodefony/src/migrator/paths.d.ts +54 -0
- package/dist/types/nodefony/src/migrator/refusals.d.ts +170 -0
- package/dist/types/nodefony/src/migrator/resolve.d.ts +201 -0
- package/dist/types/nodefony/src/migrator/schemaDiff.d.ts +112 -0
- package/dist/types/nodefony/src/migrator/sources.d.ts +119 -0
- package/dist/types/nodefony/src/migrator/status.d.ts +132 -0
- package/dist/types/nodefony/src/migrator/types.d.ts +273 -0
- package/dist/types/nodefony/src/orm-core/DrizzleOrm.d.ts +241 -0
- package/dist/types/nodefony/src/orm-core/DrizzleRepository.d.ts +98 -0
- package/dist/types/nodefony/src/orm-core/DrizzleTransaction.d.ts +71 -0
- package/dist/types/nodefony/src/orm-core/index.d.ts +11 -0
- package/dist/types/nodefony/src/queryKit.d.ts +136 -0
- package/dist/types/nodefony/src/safeTarget.d.ts +42 -0
- package/docs/index.md +954 -0
- package/docs/migrations.md +691 -0
- package/migrations/mysql/0000_framework_init.sql +137 -0
- package/migrations/mysql/meta/_journal.json +13 -0
- package/migrations/postgres/0000_framework_init.sql +128 -0
- package/migrations/postgres/meta/_journal.json +13 -0
- package/migrations/sqlite/0000_framework_init.sql +127 -0
- package/migrations/sqlite/meta/_journal.json +13 -0
- 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,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";
|