@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,93 @@
|
|
|
1
|
+
import { createFrameworkTableFactory } from "./colKit.js";
|
|
2
|
+
import { entityRegistry } from "@nodefony/orm-core";
|
|
3
|
+
/** Factory de la table du journal (mémoïsée — une instance par dialecte). */
|
|
4
|
+
const createAuditEventTable = createFrameworkTableFactory({
|
|
5
|
+
name: "audit_event",
|
|
6
|
+
columns: {
|
|
7
|
+
id: {
|
|
8
|
+
kind: "text",
|
|
9
|
+
primaryKey: true
|
|
10
|
+
},
|
|
11
|
+
ts: {
|
|
12
|
+
kind: "epochMs",
|
|
13
|
+
notNull: true
|
|
14
|
+
},
|
|
15
|
+
category: {
|
|
16
|
+
kind: "text",
|
|
17
|
+
notNull: true
|
|
18
|
+
},
|
|
19
|
+
action: {
|
|
20
|
+
kind: "text",
|
|
21
|
+
notNull: true
|
|
22
|
+
},
|
|
23
|
+
outcome: {
|
|
24
|
+
kind: "text",
|
|
25
|
+
notNull: true
|
|
26
|
+
},
|
|
27
|
+
actor: { kind: "text" },
|
|
28
|
+
resource: { kind: "text" },
|
|
29
|
+
reason: { kind: "text" },
|
|
30
|
+
ip: { kind: "text" },
|
|
31
|
+
userAgent: { kind: "text" },
|
|
32
|
+
requestId: { kind: "text" },
|
|
33
|
+
flags: { kind: "json" },
|
|
34
|
+
metadata: { kind: "json" }
|
|
35
|
+
},
|
|
36
|
+
indexes: [
|
|
37
|
+
{
|
|
38
|
+
name: "audit_event_ts_idx",
|
|
39
|
+
on: ["ts"]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "audit_event_category_idx",
|
|
43
|
+
on: ["category"]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "audit_event_actor_idx",
|
|
47
|
+
on: ["actor"]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "audit_event_requestId_idx",
|
|
51
|
+
on: ["requestId"]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Variante SQLite de la table (dialecte par défaut) — export conservé pour
|
|
57
|
+
* l'usage direct/banc-test.
|
|
58
|
+
*/
|
|
59
|
+
const auditEventTable = createAuditEventTable("sqlite");
|
|
60
|
+
/** Noms logiques des entités du store (clés de lookup `getRepository`). */
|
|
61
|
+
const AUDIT_ENTITY_NAMES = { events: "audit_event" };
|
|
62
|
+
/**
|
|
63
|
+
* Construit les descripteurs d'entités du journal d'audit pour un ORM nommé.
|
|
64
|
+
*
|
|
65
|
+
* Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"default"`) et la
|
|
66
|
+
* variante de table suit le dialecte du connecteur (auto-register
|
|
67
|
+
* `registerStores.ts` à `onKernelRegister`). À enregistrer dans `entityRegistry`
|
|
68
|
+
* **avant** `orm.connect()` (cf {@link registerAuditEntities}).
|
|
69
|
+
*
|
|
70
|
+
* @param orm - clé de l'ORM cible dans le `ormRegistry`.
|
|
71
|
+
* @param dialect - dialecte SQL du connecteur (sélectionne la variante de table).
|
|
72
|
+
* @returns le descripteur {@link IEntity} du journal d'audit.
|
|
73
|
+
*/
|
|
74
|
+
function createAuditEntities(connector, dialect = "sqlite") {
|
|
75
|
+
return [{
|
|
76
|
+
connector,
|
|
77
|
+
name: AUDIT_ENTITY_NAMES.events,
|
|
78
|
+
module: "security",
|
|
79
|
+
schema: createAuditEventTable(dialect)
|
|
80
|
+
}];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Enregistre les entités du journal d'audit dans le `entityRegistry` pour un ORM
|
|
84
|
+
* donné. À appeler **avant** `orm.connect()` (l'adapter crée les tables au connect).
|
|
85
|
+
*
|
|
86
|
+
* @param connector - nom de la connexion cible (clé du registre).
|
|
87
|
+
* @param dialect - dialecte SQL du connecteur (variante de table — défaut `sqlite`).
|
|
88
|
+
*/
|
|
89
|
+
function registerAuditEntities(connector, dialect = "sqlite") {
|
|
90
|
+
for (const entity of createAuditEntities(connector, dialect)) entityRegistry.register(entity);
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { AUDIT_ENTITY_NAMES, auditEventTable, createAuditEntities, createAuditEventTable, registerAuditEntities };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { check, index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
3
|
+
import { bigint, boolean, check as check$1, index as index$1, integer as integer$1, jsonb, pgTable, text as text$1, timestamp, uniqueIndex as uniqueIndex$1 } from "drizzle-orm/pg-core";
|
|
4
|
+
import { bigint as bigint$1, boolean as boolean$1, check as check$2, customType, datetime, index as index$2, int, mysqlTable, text as text$2, uniqueIndex as uniqueIndex$2, varchar } from "drizzle-orm/mysql-core";
|
|
5
|
+
//#region nodefony/entity/colKit.ts
|
|
6
|
+
/** Applique les modificateurs d'une spec sur un builder (dialecte déjà choisi). */
|
|
7
|
+
function applyMods(builder, spec) {
|
|
8
|
+
let out = builder;
|
|
9
|
+
if (spec.primaryKey) out = out.primaryKey();
|
|
10
|
+
if (spec.notNull) out = out.notNull();
|
|
11
|
+
if (spec.unique) out = out.unique();
|
|
12
|
+
if (spec.defaultFn) out = out.$defaultFn(spec.defaultFn);
|
|
13
|
+
if (spec.onUpdateFn) out = out.$onUpdateFn(spec.onUpdateFn);
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
/** Colonne SQLite d'une spec logique. */
|
|
17
|
+
function sqliteColumn(name, spec) {
|
|
18
|
+
let base;
|
|
19
|
+
switch (spec.kind) {
|
|
20
|
+
case "text":
|
|
21
|
+
case "enum":
|
|
22
|
+
base = text(name);
|
|
23
|
+
break;
|
|
24
|
+
case "json":
|
|
25
|
+
base = text(name, { mode: "json" });
|
|
26
|
+
break;
|
|
27
|
+
case "bool":
|
|
28
|
+
base = integer(name, { mode: "boolean" });
|
|
29
|
+
break;
|
|
30
|
+
case "epochMs":
|
|
31
|
+
case "int64":
|
|
32
|
+
case "int":
|
|
33
|
+
base = integer(name);
|
|
34
|
+
break;
|
|
35
|
+
case "dateMs": base = integer(name, { mode: "timestamp_ms" });
|
|
36
|
+
}
|
|
37
|
+
return applyMods(base, spec);
|
|
38
|
+
}
|
|
39
|
+
/** Colonne Postgres d'une spec logique. */
|
|
40
|
+
function pgColumn(name, spec) {
|
|
41
|
+
let base;
|
|
42
|
+
switch (spec.kind) {
|
|
43
|
+
case "text":
|
|
44
|
+
case "enum":
|
|
45
|
+
base = text$1(name);
|
|
46
|
+
break;
|
|
47
|
+
case "json":
|
|
48
|
+
base = jsonb(name);
|
|
49
|
+
break;
|
|
50
|
+
case "bool":
|
|
51
|
+
base = boolean(name);
|
|
52
|
+
break;
|
|
53
|
+
case "epochMs":
|
|
54
|
+
case "int64":
|
|
55
|
+
base = bigint(name, { mode: "number" });
|
|
56
|
+
break;
|
|
57
|
+
case "int":
|
|
58
|
+
base = integer$1(name);
|
|
59
|
+
break;
|
|
60
|
+
case "dateMs": base = timestamp(name, {
|
|
61
|
+
withTimezone: true,
|
|
62
|
+
mode: "date",
|
|
63
|
+
precision: 3
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return applyMods(base, spec);
|
|
67
|
+
}
|
|
68
|
+
/** Colonnes d'index résolues + validées (fail-loud sur un nom inconnu). */
|
|
69
|
+
function indexColumns(spec, ix, table) {
|
|
70
|
+
const cols = ix.on.map((name) => {
|
|
71
|
+
const col = table[name];
|
|
72
|
+
if (!col) throw new Error(`[@nodefony/drizzle] colKit "${spec.name}": index "${ix.name}" references unknown column "${name}" (declared: ${Object.keys(spec.columns).join(", ")}).`);
|
|
73
|
+
return col;
|
|
74
|
+
});
|
|
75
|
+
if (cols.length === 0) throw new Error(`[@nodefony/drizzle] colKit "${spec.name}": index "${ix.name}" has no column.`);
|
|
76
|
+
return cols;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Dérive les contraintes `CHECK` des colonnes `enum` d'une spec.
|
|
80
|
+
*
|
|
81
|
+
* Le prédicat est composé en **`sql.raw` intégral** — donc sans le moindre
|
|
82
|
+
* paramètre lié : un placeholder n'a aucun sens dans une définition de table,
|
|
83
|
+
* et `drizzle-kit` comme le DDL dérivé rendent la clause telle quelle. Les
|
|
84
|
+
* valeurs viennent du code du framework, jamais d'une entrée ; l'apostrophe est
|
|
85
|
+
* malgré tout doublée — un littéral SQL se compose toujours de la même façon.
|
|
86
|
+
*
|
|
87
|
+
* Le nom de la contrainte est `<table>_<colonne>_check` : il apparaîtra dans le
|
|
88
|
+
* message d'erreur du serveur, et il est gravé dans la migration `0000`.
|
|
89
|
+
*
|
|
90
|
+
* @param spec - spec logique de la table.
|
|
91
|
+
* @param quote - caractère de citation des identifiants du dialecte.
|
|
92
|
+
* @returns une contrainte par colonne `enum` (vide s'il n'y en a aucune).
|
|
93
|
+
*/
|
|
94
|
+
function enumChecks(spec, quote) {
|
|
95
|
+
const checks = [];
|
|
96
|
+
for (const [name, col] of Object.entries(spec.columns)) {
|
|
97
|
+
if (col.kind !== "enum") continue;
|
|
98
|
+
if (col.values.length === 0) throw new Error(`[@nodefony/drizzle] colKit "${spec.name}": enum column "${name}" has no value.`);
|
|
99
|
+
const list = col.values.map((value) => `'${value.replace(/'/g, "''")}'`).join(", ");
|
|
100
|
+
checks.push({
|
|
101
|
+
name: `${spec.name}_${name}_check`,
|
|
102
|
+
value: sql.raw(`${quote}${name}${quote} IN (${list})`)
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return checks;
|
|
106
|
+
}
|
|
107
|
+
/** Construit la variante SQLite d'une spec. */
|
|
108
|
+
function buildSqliteTable(spec) {
|
|
109
|
+
const columns = {};
|
|
110
|
+
for (const [name, col] of Object.entries(spec.columns)) columns[name] = sqliteColumn(name, col);
|
|
111
|
+
const indexes = spec.indexes;
|
|
112
|
+
const checks = enumChecks(spec, "\"");
|
|
113
|
+
if (!indexes?.length && !checks.length) return sqliteTable(spec.name, columns);
|
|
114
|
+
return sqliteTable(spec.name, columns, (t) => {
|
|
115
|
+
const out = {};
|
|
116
|
+
for (const ix of indexes ?? []) {
|
|
117
|
+
const cols = indexColumns(spec, ix, t);
|
|
118
|
+
out[ix.name] = (ix.unique ? uniqueIndex(ix.name) : index(ix.name)).on(...cols);
|
|
119
|
+
}
|
|
120
|
+
for (const ck of checks) out[ck.name] = check(ck.name, ck.value);
|
|
121
|
+
return out;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/** Construit la variante Postgres d'une spec. */
|
|
125
|
+
function buildPgTable(spec) {
|
|
126
|
+
const columns = {};
|
|
127
|
+
for (const [name, col] of Object.entries(spec.columns)) columns[name] = pgColumn(name, col);
|
|
128
|
+
const indexes = spec.indexes;
|
|
129
|
+
const checks = enumChecks(spec, "\"");
|
|
130
|
+
if (!indexes?.length && !checks.length) return pgTable(spec.name, columns);
|
|
131
|
+
return pgTable(spec.name, columns, (t) => {
|
|
132
|
+
const out = {};
|
|
133
|
+
for (const ix of indexes ?? []) {
|
|
134
|
+
const cols = indexColumns(spec, ix, t);
|
|
135
|
+
out[ix.name] = (ix.unique ? uniqueIndex$1(ix.name) : index$1(ix.name)).on(...cols);
|
|
136
|
+
}
|
|
137
|
+
for (const ck of checks) out[ck.name] = check$1(ck.name, ck.value);
|
|
138
|
+
return out;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Longueur des colonnes `text` **indexables** en MySQL (`varchar(512)`) :
|
|
143
|
+
* InnoDB ne peut pas porter de PK/UNIQUE/index sur un `TEXT` sans longueur de
|
|
144
|
+
* préfixe → toute colonne `text` PK/UNIQUE/référencée par un index devient un
|
|
145
|
+
* `varchar`. 512 chars × 4 bytes (utf8mb4) = 2048 bytes < 3072 (limite d'index
|
|
146
|
+
* InnoDB en row format DYNAMIC, défaut MySQL ≥ 8.0) et couvre la plus longue
|
|
147
|
+
* valeur framework (clé d'idempotence : `JSON.stringify([identity, clientKey])`
|
|
148
|
+
* ≤ ~330 chars — `IDEMPOTENCY_KEY_MAX` 255 + identité sha256 + ponctuation).
|
|
149
|
+
*/
|
|
150
|
+
const MYSQL_INDEXED_TEXT_LENGTH = 512;
|
|
151
|
+
/**
|
|
152
|
+
* Largeur des colonnes `enum` en MySQL (`varchar(64)`) — fixe et généreuse : la
|
|
153
|
+
* valeur admise est bornée par la contrainte `CHECK`, pas par le type, donc
|
|
154
|
+
* ajouter une valeur à l'énumération ne doit pas devenir un changement de type
|
|
155
|
+
* de colonne. 64 chars couvre tout identifiant d'état, et reste indexable.
|
|
156
|
+
*/
|
|
157
|
+
const MYSQL_ENUM_LENGTH = 64;
|
|
158
|
+
/**
|
|
159
|
+
* Type `json` du dialecte mysql, TOLÉRANT au serveur : MySQL possède un type
|
|
160
|
+
* JSON binaire que mysql2 désérialise en objet, mais **MariaDB** (la cible
|
|
161
|
+
* libre du projet, même dialecte) le stocke en alias LONGTEXT → le driver rend
|
|
162
|
+
* une **string** brute. Le `json()` natif drizzle ne parse pas côté lecture →
|
|
163
|
+
* un objet stocké reviendrait en string sur MariaDB (prouvé e2e). Ce customType
|
|
164
|
+
* parse si string, passe tel quel sinon — un seul kind `json` pour les deux
|
|
165
|
+
* serveurs.
|
|
166
|
+
*/
|
|
167
|
+
const mysqlJsonCompat = customType({
|
|
168
|
+
dataType() {
|
|
169
|
+
return "json";
|
|
170
|
+
},
|
|
171
|
+
toDriver(value) {
|
|
172
|
+
return JSON.stringify(value);
|
|
173
|
+
},
|
|
174
|
+
fromDriver(value) {
|
|
175
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
/** Colonne MySQL d'une spec logique (`indexed` = référencée par un index de table). */
|
|
179
|
+
function mysqlColumn(name, spec, indexed) {
|
|
180
|
+
let base;
|
|
181
|
+
switch (spec.kind) {
|
|
182
|
+
case "text":
|
|
183
|
+
base = spec.primaryKey || spec.unique || indexed ? varchar(name, { length: MYSQL_INDEXED_TEXT_LENGTH }) : text$2(name);
|
|
184
|
+
break;
|
|
185
|
+
case "enum":
|
|
186
|
+
base = varchar(name, { length: MYSQL_ENUM_LENGTH });
|
|
187
|
+
break;
|
|
188
|
+
case "json":
|
|
189
|
+
base = mysqlJsonCompat(name);
|
|
190
|
+
break;
|
|
191
|
+
case "bool":
|
|
192
|
+
base = boolean$1(name);
|
|
193
|
+
break;
|
|
194
|
+
case "epochMs":
|
|
195
|
+
case "int64":
|
|
196
|
+
base = bigint$1(name, { mode: "number" });
|
|
197
|
+
break;
|
|
198
|
+
case "int":
|
|
199
|
+
base = int(name);
|
|
200
|
+
break;
|
|
201
|
+
case "dateMs": base = datetime(name, {
|
|
202
|
+
mode: "date",
|
|
203
|
+
fsp: 3
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return applyMods(base, spec);
|
|
207
|
+
}
|
|
208
|
+
/** Construit la variante MySQL d'une spec. */
|
|
209
|
+
function buildMysqlTable(spec) {
|
|
210
|
+
const indexedCols = /* @__PURE__ */ new Set();
|
|
211
|
+
for (const ix of spec.indexes ?? []) for (const name of ix.on) indexedCols.add(name);
|
|
212
|
+
const columns = {};
|
|
213
|
+
for (const [name, col] of Object.entries(spec.columns)) columns[name] = mysqlColumn(name, col, indexedCols.has(name));
|
|
214
|
+
const indexes = spec.indexes;
|
|
215
|
+
const checks = enumChecks(spec, "`");
|
|
216
|
+
if (!indexes?.length && !checks.length) return mysqlTable(spec.name, columns);
|
|
217
|
+
return mysqlTable(spec.name, columns, (t) => {
|
|
218
|
+
const out = {};
|
|
219
|
+
for (const ix of indexes ?? []) {
|
|
220
|
+
const cols = indexColumns(spec, ix, t);
|
|
221
|
+
out[ix.name] = (ix.unique ? uniqueIndex$2(ix.name) : index$2(ix.name)).on(...cols);
|
|
222
|
+
}
|
|
223
|
+
for (const ck of checks) out[ck.name] = check$2(ck.name, ck.value);
|
|
224
|
+
return out;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function buildFrameworkTable(dialect, spec) {
|
|
228
|
+
for (const ix of spec.indexes ?? []) {
|
|
229
|
+
if (ix.on.length === 0) throw new Error(`[@nodefony/drizzle] colKit "${spec.name}": index "${ix.name}" has no column.`);
|
|
230
|
+
for (const name of ix.on) if (!(name in spec.columns)) throw new Error(`[@nodefony/drizzle] colKit "${spec.name}": index "${ix.name}" references unknown column "${name}" (declared: ${Object.keys(spec.columns).join(", ")}).`);
|
|
231
|
+
}
|
|
232
|
+
switch (dialect) {
|
|
233
|
+
case "sqlite": return buildSqliteTable(spec);
|
|
234
|
+
case "postgres": return buildPgTable(spec);
|
|
235
|
+
case "mysql": return buildMysqlTable(spec);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Fabrique la factory `createXTable(dialect)` d'une entité framework : une
|
|
240
|
+
* table par dialecte, construite au premier usage puis **mémoïsée** (une seule
|
|
241
|
+
* instance par dialecte — le DDL, le registre d'entités et les stores voient
|
|
242
|
+
* le même objet table).
|
|
243
|
+
*
|
|
244
|
+
* @param spec - spec logique de la table.
|
|
245
|
+
* @returns factory mémoïsée (défaut `sqlite`).
|
|
246
|
+
*/
|
|
247
|
+
function createFrameworkTableFactory(spec) {
|
|
248
|
+
const cache = /* @__PURE__ */ new Map();
|
|
249
|
+
const factory = (dialect = "sqlite") => {
|
|
250
|
+
let table = cache.get(dialect);
|
|
251
|
+
if (!table) {
|
|
252
|
+
table = buildFrameworkTable(dialect, spec);
|
|
253
|
+
cache.set(dialect, table);
|
|
254
|
+
}
|
|
255
|
+
return table;
|
|
256
|
+
};
|
|
257
|
+
return factory;
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
export { buildFrameworkTable, createFrameworkTableFactory };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createFrameworkTableFactory } from "./colKit.js";
|
|
2
|
+
import { entityRegistry } from "@nodefony/orm-core";
|
|
3
|
+
/**
|
|
4
|
+
* Factory de la table d'idempotence pour un dialecte donné — **premier maillon du
|
|
5
|
+
* chantier portabilité multi-dialecte** (un schéma logique → la table Drizzle du
|
|
6
|
+
* bon dialecte), migrée du switch manuel 2 tables vers le colKit (S4 : la
|
|
7
|
+
* variante mysql sort du kit). Le `DrizzleOrm` l'appelle selon `connector.dialect`.
|
|
8
|
+
*/
|
|
9
|
+
const createIdempotencyTable = createFrameworkTableFactory({
|
|
10
|
+
name: "idempotency_key",
|
|
11
|
+
columns: {
|
|
12
|
+
key: {
|
|
13
|
+
kind: "text",
|
|
14
|
+
primaryKey: true
|
|
15
|
+
},
|
|
16
|
+
fingerprint: {
|
|
17
|
+
kind: "text",
|
|
18
|
+
notNull: true
|
|
19
|
+
},
|
|
20
|
+
state: {
|
|
21
|
+
kind: "enum",
|
|
22
|
+
values: ["if", "done"],
|
|
23
|
+
notNull: true
|
|
24
|
+
},
|
|
25
|
+
response: { kind: "json" },
|
|
26
|
+
expiresAt: {
|
|
27
|
+
kind: "epochMs",
|
|
28
|
+
notNull: true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
indexes: [{
|
|
32
|
+
name: "idempotency_key_expiresAt_idx",
|
|
33
|
+
on: ["expiresAt"]
|
|
34
|
+
}]
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Variante SQLite mémoïsée (dialecte par défaut) — export statique de compat
|
|
38
|
+
* (bancs, typage du store {@link DrizzleIdempotencyStore}).
|
|
39
|
+
*/
|
|
40
|
+
const idempotencyKeyTable = createIdempotencyTable("sqlite");
|
|
41
|
+
/** Nom logique de l'entité (clé de lookup `getRepository` / `entityRegistry`). */
|
|
42
|
+
const IDEMPOTENCY_ENTITY_NAME = "idempotency_key";
|
|
43
|
+
/**
|
|
44
|
+
* Construit le descripteur d'entité du store d'idempotence pour un ORM nommé.
|
|
45
|
+
*
|
|
46
|
+
* Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"default"`) : la
|
|
47
|
+
* table est statique mais sa liaison à un ORM dépend de la config → on ne peut
|
|
48
|
+
* pas la figer via `@entity`. À enregistrer dans `entityRegistry` **avant**
|
|
49
|
+
* `orm.connect()` (cf {@link registerIdempotencyEntities}).
|
|
50
|
+
*
|
|
51
|
+
* @param orm - clé de l'ORM cible dans le `ormRegistry`.
|
|
52
|
+
* @param dialect - dialecte SQL du connecteur (sélectionne la variante de table).
|
|
53
|
+
* @returns le descripteur {@link IEntity} de la table d'idempotence.
|
|
54
|
+
*/
|
|
55
|
+
function createIdempotencyEntities(connector, dialect = "sqlite") {
|
|
56
|
+
return [{
|
|
57
|
+
connector,
|
|
58
|
+
name: IDEMPOTENCY_ENTITY_NAME,
|
|
59
|
+
module: "framework",
|
|
60
|
+
schema: createIdempotencyTable(dialect)
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Enregistre l'entité du store d'idempotence dans le `entityRegistry` pour un ORM
|
|
65
|
+
* donné. À appeler **avant** `orm.connect()` (l'adapter crée la table au connect).
|
|
66
|
+
*
|
|
67
|
+
* @param connector - nom de la connexion cible (clé du registre).
|
|
68
|
+
* @param dialect - dialecte SQL du connecteur (variante de table — défaut `sqlite`).
|
|
69
|
+
*/
|
|
70
|
+
function registerIdempotencyEntities(connector, dialect = "sqlite") {
|
|
71
|
+
for (const entity of createIdempotencyEntities(connector, dialect)) entityRegistry.register(entity);
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { IDEMPOTENCY_ENTITY_NAME, createIdempotencyEntities, createIdempotencyTable, idempotencyKeyTable, registerIdempotencyEntities };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createFrameworkTableFactory } from "./colKit.js";
|
|
2
|
+
import { entityRegistry } from "@nodefony/orm-core";
|
|
3
|
+
//#region nodefony/entity/sessionEntity.ts
|
|
4
|
+
/** ORM cible du stockage de session (connecteur par défaut du module). */
|
|
5
|
+
const SESSION_CONNECTOR = "default";
|
|
6
|
+
/** Nom logique de l'entité (clé de lookup `getRepository` / `entityRegistry`). */
|
|
7
|
+
const SESSION_ENTITY_NAME = "session";
|
|
8
|
+
/**
|
|
9
|
+
* Factory de la table de session pour un dialecte donné (mémoïsée — une
|
|
10
|
+
* instance par dialecte). `sqlite` (défaut) et `postgres` sont portés ;
|
|
11
|
+
* `mysql` jette (S4).
|
|
12
|
+
*/
|
|
13
|
+
const createSessionTable = createFrameworkTableFactory({
|
|
14
|
+
name: "session",
|
|
15
|
+
columns: {
|
|
16
|
+
session_id: {
|
|
17
|
+
kind: "text",
|
|
18
|
+
primaryKey: true
|
|
19
|
+
},
|
|
20
|
+
Attributes: { kind: "json" },
|
|
21
|
+
flashBag: { kind: "json" },
|
|
22
|
+
metaBag: { kind: "json" },
|
|
23
|
+
user: { kind: "text" },
|
|
24
|
+
createdAt: {
|
|
25
|
+
kind: "epochMs",
|
|
26
|
+
notNull: true
|
|
27
|
+
},
|
|
28
|
+
updatedAt: {
|
|
29
|
+
kind: "epochMs",
|
|
30
|
+
notNull: true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
indexes: [{
|
|
34
|
+
name: "session_user_idx",
|
|
35
|
+
on: ["user"]
|
|
36
|
+
}]
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Variante SQLite de la table de session (dialecte par défaut) — export
|
|
40
|
+
* conservé pour l'usage direct/banc-test.
|
|
41
|
+
*/
|
|
42
|
+
const sessionTable = createSessionTable("sqlite");
|
|
43
|
+
/**
|
|
44
|
+
* Construit le descripteur d'entité session pour un ORM nommé.
|
|
45
|
+
*
|
|
46
|
+
* Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"default"`) et la
|
|
47
|
+
* variante de table suit le dialecte du connecteur → l'enregistrement est fait
|
|
48
|
+
* par `registerStores.ts` à `onKernelRegister` (plus de décorateur `@entity`
|
|
49
|
+
* figé au chargement du fichier, qui imposait la variante sqlite quel que soit
|
|
50
|
+
* le dialecte configuré). À enregistrer **avant** `orm.connect()`.
|
|
51
|
+
*
|
|
52
|
+
* @param orm - clé de l'ORM cible dans le `ormRegistry`.
|
|
53
|
+
* @param dialect - dialecte SQL du connecteur (sélectionne la variante de table).
|
|
54
|
+
* @returns le descripteur {@link IEntity} de la table de session.
|
|
55
|
+
*/
|
|
56
|
+
function createSessionEntity(connector, dialect = "sqlite") {
|
|
57
|
+
return [{
|
|
58
|
+
connector,
|
|
59
|
+
name: SESSION_ENTITY_NAME,
|
|
60
|
+
module: "http",
|
|
61
|
+
schema: createSessionTable(dialect)
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Enregistre l'entité session dans le `entityRegistry` pour un ORM donné.
|
|
66
|
+
* À appeler **avant** `orm.connect()` (l'adapter crée la table au connect).
|
|
67
|
+
*
|
|
68
|
+
* @param connector - nom de la connexion cible (clé du registre).
|
|
69
|
+
* @param dialect - dialecte SQL du connecteur (variante de table — défaut `sqlite`).
|
|
70
|
+
*/
|
|
71
|
+
function registerSessionEntity(connector, dialect = "sqlite") {
|
|
72
|
+
for (const entity of createSessionEntity(connector, dialect)) entityRegistry.register(entity);
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { SESSION_CONNECTOR, SESSION_ENTITY_NAME, createSessionEntity, createSessionTable, registerSessionEntity, sessionTable };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { createFrameworkTableFactory } from "./colKit.js";
|
|
2
|
+
import { entityRegistry } from "@nodefony/orm-core";
|
|
3
|
+
//#region nodefony/entity/tokenEntity.ts
|
|
4
|
+
/**
|
|
5
|
+
* Entités du **store de jetons** `@nodefony/security` (schema-as-code) —
|
|
6
|
+
* implémentation SQL d'`ITokenStore` (PAT, refresh, denylist `jti`, seuil de
|
|
7
|
+
* révocation en masse), déclinées via le `colKit` (S2 multi-dialecte) : une
|
|
8
|
+
* spec logique par table, la variante du dialecte du connecteur.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ **Horodatages en epoch ms (kind `epochMs`, exposés `number`)** :
|
|
11
|
+
* `IAccessTokenRecord` porte des `number` (`Date.now()`), pas des `Date` →
|
|
12
|
+
* `AccessTokenRow` (forme repository) est **identique** à `IAccessTokenRecord`
|
|
13
|
+
* → zéro mapping store ↔ entité.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ **Pas de `.default()` SQL** (règle colKit) : le DDL dérivé n'émet pas de
|
|
16
|
+
* `DEFAULT`, seulement les contraintes colonne (PK / NOT NULL / UNIQUE). D'où
|
|
17
|
+
* `secretHash` en `unique` **colonne** (unicité réelle en dev). Les index
|
|
18
|
+
* déclarés, eux, sont bien créés des DEUX côtés — par `drizzle-kit` dans la
|
|
19
|
+
* migration, et par le DDL dérivé en développement — et le banc de parité le
|
|
20
|
+
* vérifie table par table (`tests/integration/migrations-parity.ts`).
|
|
21
|
+
*
|
|
22
|
+
* Les unions du contrat (`kind`, `subjectType`, `revokedReason`,
|
|
23
|
+
* `IResourcePermission[]`) vivent sur les types `Row`/`IAccessTokenRecord`
|
|
24
|
+
* (frontière typée), plus sur les colonnes — décision S1 : les types publics
|
|
25
|
+
* des entités framework sont leurs interfaces `Row`, pas l'inférence Drizzle.
|
|
26
|
+
*/
|
|
27
|
+
const ACCESS_TOKEN_TABLE_SPEC = {
|
|
28
|
+
name: "access_token",
|
|
29
|
+
columns: {
|
|
30
|
+
id: {
|
|
31
|
+
kind: "text",
|
|
32
|
+
primaryKey: true
|
|
33
|
+
},
|
|
34
|
+
kind: {
|
|
35
|
+
kind: "text",
|
|
36
|
+
notNull: true
|
|
37
|
+
},
|
|
38
|
+
name: {
|
|
39
|
+
kind: "text",
|
|
40
|
+
notNull: true
|
|
41
|
+
},
|
|
42
|
+
prefix: { kind: "text" },
|
|
43
|
+
subjectId: {
|
|
44
|
+
kind: "text",
|
|
45
|
+
notNull: true
|
|
46
|
+
},
|
|
47
|
+
subjectType: {
|
|
48
|
+
kind: "text",
|
|
49
|
+
notNull: true
|
|
50
|
+
},
|
|
51
|
+
tenantId: { kind: "text" },
|
|
52
|
+
scopes: {
|
|
53
|
+
kind: "json",
|
|
54
|
+
notNull: true,
|
|
55
|
+
defaultFn: () => []
|
|
56
|
+
},
|
|
57
|
+
audience: {
|
|
58
|
+
kind: "json",
|
|
59
|
+
notNull: true,
|
|
60
|
+
defaultFn: () => []
|
|
61
|
+
},
|
|
62
|
+
resources: { kind: "json" },
|
|
63
|
+
secretHash: {
|
|
64
|
+
kind: "text",
|
|
65
|
+
notNull: true,
|
|
66
|
+
unique: true
|
|
67
|
+
},
|
|
68
|
+
hashAlg: {
|
|
69
|
+
kind: "text",
|
|
70
|
+
notNull: true
|
|
71
|
+
},
|
|
72
|
+
clientId: { kind: "text" },
|
|
73
|
+
cnf: { kind: "text" },
|
|
74
|
+
family: { kind: "text" },
|
|
75
|
+
replacedBy: { kind: "text" },
|
|
76
|
+
createdAt: {
|
|
77
|
+
kind: "epochMs",
|
|
78
|
+
notNull: true
|
|
79
|
+
},
|
|
80
|
+
expiresAt: { kind: "epochMs" },
|
|
81
|
+
lastUsedAt: { kind: "epochMs" },
|
|
82
|
+
lastUsedIp: { kind: "text" },
|
|
83
|
+
lastUsedUserAgent: { kind: "text" },
|
|
84
|
+
revokedAt: { kind: "epochMs" },
|
|
85
|
+
revokedReason: { kind: "text" },
|
|
86
|
+
metadata: {
|
|
87
|
+
kind: "json",
|
|
88
|
+
notNull: true,
|
|
89
|
+
defaultFn: () => ({})
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
indexes: [{
|
|
93
|
+
name: "access_token_subjectId_idx",
|
|
94
|
+
on: ["subjectId"]
|
|
95
|
+
}, {
|
|
96
|
+
name: "access_token_family_idx",
|
|
97
|
+
on: ["family"]
|
|
98
|
+
}]
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Denylist des access tokens (`jti`) révoqués avant leur `exp` — révocation
|
|
102
|
+
* immédiate ciblée. Purgée par `gc` une fois `expiresAt` (epoch ms) dépassé.
|
|
103
|
+
*/
|
|
104
|
+
const DENIED_JTI_TABLE_SPEC = {
|
|
105
|
+
name: "denied_jti",
|
|
106
|
+
columns: {
|
|
107
|
+
jti: {
|
|
108
|
+
kind: "text",
|
|
109
|
+
primaryKey: true
|
|
110
|
+
},
|
|
111
|
+
expiresAt: {
|
|
112
|
+
kind: "epochMs",
|
|
113
|
+
notNull: true
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Seuil de révocation **en masse** par porteur : tout access auto-porté émis
|
|
119
|
+
* avant `invalidBefore` (epoch ms) est rejeté (logout global / ban).
|
|
120
|
+
*/
|
|
121
|
+
const SUBJECT_REVOCATION_TABLE_SPEC = {
|
|
122
|
+
name: "subject_revocation",
|
|
123
|
+
columns: {
|
|
124
|
+
subjectId: {
|
|
125
|
+
kind: "text",
|
|
126
|
+
primaryKey: true
|
|
127
|
+
},
|
|
128
|
+
invalidBefore: {
|
|
129
|
+
kind: "epochMs",
|
|
130
|
+
notNull: true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
/** Factory de la table des records (mémoïsée — une instance par dialecte). */
|
|
135
|
+
const createAccessTokenTable = createFrameworkTableFactory(ACCESS_TOKEN_TABLE_SPEC);
|
|
136
|
+
/** Factory de la denylist `jti` (mémoïsée — une instance par dialecte). */
|
|
137
|
+
const createDeniedJtiTable = createFrameworkTableFactory(DENIED_JTI_TABLE_SPEC);
|
|
138
|
+
/** Factory des seuils de révocation (mémoïsée — une instance par dialecte). */
|
|
139
|
+
const createSubjectRevocationTable = createFrameworkTableFactory(SUBJECT_REVOCATION_TABLE_SPEC);
|
|
140
|
+
/**
|
|
141
|
+
* Variantes SQLite des trois tables (dialecte par défaut) — exports conservés
|
|
142
|
+
* pour l'usage direct/banc-test.
|
|
143
|
+
*/
|
|
144
|
+
const accessTokenTable = createAccessTokenTable("sqlite");
|
|
145
|
+
const deniedJtiTable = createDeniedJtiTable("sqlite");
|
|
146
|
+
const subjectRevocationTable = createSubjectRevocationTable("sqlite");
|
|
147
|
+
/** Noms logiques des entités du store (clés de lookup `getRepository`). */
|
|
148
|
+
const TOKEN_ENTITY_NAMES = {
|
|
149
|
+
records: "access_token",
|
|
150
|
+
denied: "denied_jti",
|
|
151
|
+
revocations: "subject_revocation"
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Construit les descripteurs d'entités du store de jetons pour un ORM nommé.
|
|
155
|
+
*
|
|
156
|
+
* Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"default"`) et la
|
|
157
|
+
* variante de table suit le dialecte du connecteur (auto-register
|
|
158
|
+
* `registerStores.ts` à `onKernelRegister`). À enregistrer dans `entityRegistry`
|
|
159
|
+
* **avant** `orm.connect()` (cf {@link registerTokenEntities}).
|
|
160
|
+
*
|
|
161
|
+
* @param orm - clé de l'ORM cible dans le `ormRegistry`.
|
|
162
|
+
* @param dialect - dialecte SQL du connecteur (sélectionne la variante des tables).
|
|
163
|
+
* @returns les trois descripteurs {@link IEntity} (records / denylist / seuils).
|
|
164
|
+
*/
|
|
165
|
+
function createTokenEntities(connector, dialect = "sqlite") {
|
|
166
|
+
return [
|
|
167
|
+
{
|
|
168
|
+
connector,
|
|
169
|
+
name: TOKEN_ENTITY_NAMES.records,
|
|
170
|
+
module: "security",
|
|
171
|
+
schema: createAccessTokenTable(dialect)
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
connector,
|
|
175
|
+
name: TOKEN_ENTITY_NAMES.denied,
|
|
176
|
+
module: "security",
|
|
177
|
+
schema: createDeniedJtiTable(dialect)
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
connector,
|
|
181
|
+
name: TOKEN_ENTITY_NAMES.revocations,
|
|
182
|
+
module: "security",
|
|
183
|
+
schema: createSubjectRevocationTable(dialect)
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Enregistre les entités du store de jetons dans le `entityRegistry` pour un ORM
|
|
189
|
+
* donné. À appeler **avant** `orm.connect()` (l'adapter crée les tables au connect).
|
|
190
|
+
*
|
|
191
|
+
* @param connector - nom de la connexion cible (clé du registre).
|
|
192
|
+
* @param dialect - dialecte SQL du connecteur (variante des tables — défaut `sqlite`).
|
|
193
|
+
*/
|
|
194
|
+
function registerTokenEntities(connector, dialect = "sqlite") {
|
|
195
|
+
for (const entity of createTokenEntities(connector, dialect)) entityRegistry.register(entity);
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { TOKEN_ENTITY_NAMES, accessTokenTable, createAccessTokenTable, createDeniedJtiTable, createSubjectRevocationTable, createTokenEntities, deniedJtiTable, registerTokenEntities, subjectRevocationTable };
|