@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,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 };