@nodefony/framework 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 (96) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +50 -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 +211 -0
  6. package/dist/nodefony/config/config.js +61 -0
  7. package/dist/nodefony/config/defineModuleConfig.js +36 -0
  8. package/dist/nodefony/controller/AdminApiController.js +163 -0
  9. package/dist/nodefony/controller/ApiKeyController.js +151 -0
  10. package/dist/nodefony/controller/BenchController.js +132 -0
  11. package/dist/nodefony/controller/IssuerMetadataController.js +148 -0
  12. package/dist/nodefony/controller/OAuth2Controller.js +133 -0
  13. package/dist/nodefony/controller/ProtectedResourceMetadataController.js +221 -0
  14. package/dist/nodefony/controller/SessionAuthController.js +141 -0
  15. package/dist/nodefony/controller/TokenAuthController.js +113 -0
  16. package/dist/nodefony/controller/TotpController.js +129 -0
  17. package/dist/nodefony/controller/WebAuthnController.js +242 -0
  18. package/dist/nodefony/controller/oauthAuthority.js +74 -0
  19. package/dist/nodefony/decorators/routerDecorators.js +967 -0
  20. package/dist/nodefony/interfaces/IAdminBroker.js +1 -0
  21. package/dist/nodefony/interfaces/IController.js +1 -0
  22. package/dist/nodefony/interfaces/IIdempotencyStore.js +1 -0
  23. package/dist/nodefony/interfaces/IResolver.js +1 -0
  24. package/dist/nodefony/interfaces/IRoute.js +1 -0
  25. package/dist/nodefony/interfaces/index.js +1 -0
  26. package/dist/nodefony/service/AdminBroker.js +106 -0
  27. package/dist/nodefony/service/Eta.js +68 -0
  28. package/dist/nodefony/service/IdempotencyStore.js +136 -0
  29. package/dist/nodefony/service/router.js +243 -0
  30. package/dist/nodefony/src/Controller.js +515 -0
  31. package/dist/nodefony/src/FrameworkAdminApi.js +268 -0
  32. package/dist/nodefony/src/KernelAdminApi.js +1243 -0
  33. package/dist/nodefony/src/PlaygroundAdminApi.js +97 -0
  34. package/dist/nodefony/src/RedisIdempotencyStore.js +254 -0
  35. package/dist/nodefony/src/Resolver.js +416 -0
  36. package/dist/nodefony/src/ResourceController.js +148 -0
  37. package/dist/nodefony/src/Route.js +476 -0
  38. package/dist/nodefony/src/SyslogAdminApi.js +466 -0
  39. package/dist/nodefony/src/Template.js +15 -0
  40. package/dist/nodefony/src/configMutation.js +186 -0
  41. package/dist/nodefony/src/docsReader.js +929 -0
  42. package/dist/nodefony/src/idempotency.js +137 -0
  43. package/dist/nodefony/src/idempotencyGc.js +32 -0
  44. package/dist/nodefony/src/idempotencyStoreRegistry.js +36 -0
  45. package/dist/nodefony/src/scopeCatalog.js +40 -0
  46. package/dist/nodefony/src/syslogFilters.js +51 -0
  47. package/dist/types/index.d.ts +96 -0
  48. package/dist/types/nodefony/config/config.d.ts +41 -0
  49. package/dist/types/nodefony/config/defineModuleConfig.d.ts +27 -0
  50. package/dist/types/nodefony/controller/AdminApiController.d.ts +70 -0
  51. package/dist/types/nodefony/controller/ApiKeyController.d.ts +49 -0
  52. package/dist/types/nodefony/controller/BenchController.d.ts +45 -0
  53. package/dist/types/nodefony/controller/IssuerMetadataController.d.ts +86 -0
  54. package/dist/types/nodefony/controller/OAuth2Controller.d.ts +81 -0
  55. package/dist/types/nodefony/controller/ProtectedResourceMetadataController.d.ts +147 -0
  56. package/dist/types/nodefony/controller/SessionAuthController.d.ts +74 -0
  57. package/dist/types/nodefony/controller/TokenAuthController.d.ts +56 -0
  58. package/dist/types/nodefony/controller/TotpController.d.ts +42 -0
  59. package/dist/types/nodefony/controller/WebAuthnController.d.ts +123 -0
  60. package/dist/types/nodefony/controller/oauthAuthority.d.ts +54 -0
  61. package/dist/types/nodefony/decorators/routerDecorators.d.ts +632 -0
  62. package/dist/types/nodefony/interfaces/IAdminBroker.d.ts +79 -0
  63. package/dist/types/nodefony/interfaces/IController.d.ts +38 -0
  64. package/dist/types/nodefony/interfaces/IIdempotencyStore.d.ts +1 -0
  65. package/dist/types/nodefony/interfaces/IResolver.d.ts +25 -0
  66. package/dist/types/nodefony/interfaces/IRoute.d.ts +31 -0
  67. package/dist/types/nodefony/interfaces/index.d.ts +5 -0
  68. package/dist/types/nodefony/service/AdminBroker.d.ts +37 -0
  69. package/dist/types/nodefony/service/Eta.d.ts +25 -0
  70. package/dist/types/nodefony/service/IdempotencyStore.d.ts +45 -0
  71. package/dist/types/nodefony/service/router.d.ts +53 -0
  72. package/dist/types/nodefony/src/Controller.d.ts +193 -0
  73. package/dist/types/nodefony/src/FrameworkAdminApi.d.ts +35 -0
  74. package/dist/types/nodefony/src/KernelAdminApi.d.ts +71 -0
  75. package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +98 -0
  76. package/dist/types/nodefony/src/RedisIdempotencyStore.d.ts +104 -0
  77. package/dist/types/nodefony/src/Resolver.d.ts +165 -0
  78. package/dist/types/nodefony/src/ResourceController.d.ts +171 -0
  79. package/dist/types/nodefony/src/Route.d.ts +192 -0
  80. package/dist/types/nodefony/src/SyslogAdminApi.d.ts +38 -0
  81. package/dist/types/nodefony/src/Template.d.ts +8 -0
  82. package/dist/types/nodefony/src/configMutation.d.ts +109 -0
  83. package/dist/types/nodefony/src/docsReader.d.ts +369 -0
  84. package/dist/types/nodefony/src/idempotency.d.ts +96 -0
  85. package/dist/types/nodefony/src/idempotencyGc.d.ts +30 -0
  86. package/dist/types/nodefony/src/idempotencyStoreRegistry.d.ts +59 -0
  87. package/dist/types/nodefony/src/scopeCatalog.d.ts +26 -0
  88. package/dist/types/nodefony/src/syslogFilters.d.ts +52 -0
  89. package/docs/admin.md +451 -0
  90. package/docs/controller.md +645 -0
  91. package/docs/decorateurs.md +845 -0
  92. package/docs/idempotence.md +741 -0
  93. package/docs/index.md +151 -0
  94. package/docs/routing.md +648 -0
  95. package/docs/templates.md +380 -0
  96. package/package.json +83 -0
@@ -0,0 +1,268 @@
1
+ import router_default from "../service/router.js";
2
+ import { createPlaygroundEndpoints } from "./PlaygroundAdminApi.js";
3
+ import { parseFilters, parsePageQuery } from "nodefony";
4
+ //#region nodefony/src/FrameworkAdminApi.ts
5
+ /**
6
+ * Capacités de page d'un endpoint, ÉVALUÉES sous filet.
7
+ *
8
+ * `sortable` et `search` sont des fonctions parce que la réponse dépend du
9
+ * store effectivement branché au démarrage — donc elles peuvent lever. Ce
10
+ * catalogue est lu au chargement de la console : une exception ici privait
11
+ * l'application entière de sa navigation à cause d'un seul module en panne. Ne
12
+ * rien publier est le repli sûr — le plan REFUSE en 400 ce qui n'est pas
13
+ * déclaré, une capacité tue ne promet donc rien de faux.
14
+ *
15
+ * @param caps - la déclaration du producteur, ou `undefined`.
16
+ * @returns le fragment `{ page }` à fusionner, ou rien.
17
+ */
18
+ function readPageCapabilities(caps) {
19
+ if (!caps) return {};
20
+ try {
21
+ return { page: {
22
+ sortable: caps.sortable?.() ?? [],
23
+ filters: caps.filters ?? {},
24
+ search: caps.search?.() ?? false,
25
+ facets: caps.facets ?? {}
26
+ } };
27
+ } catch {
28
+ return {};
29
+ }
30
+ }
31
+ /**
32
+ * Producteur `IAdminApi` du module **framework** — exposé sous
33
+ * `/nodefony/framework/api/*`.
34
+ *
35
+ * 3ᵉ producteur du data plane admin (kernel, http, puis framework). Introspecte
36
+ * le **Router** : c'est l'équivalent web de `nodefony router:dump` et la source
37
+ * de la future vue « Routes » de Studio (P10.8).
38
+ *
39
+ * Le framework héberge le broker → il s'enregistre directement (pas besoin de
40
+ * passer par `IAdminRegistry` du container comme un module externe).
41
+ *
42
+ * Endpoints :
43
+ * - `GET /nodefony/framework/api/routes` → toutes les routes enregistrées
44
+ * - `GET /nodefony/framework/api/info` → résumé (nb routes, méthodes, modules)
45
+ * - `GET /nodefony/framework/api/admin` → **catalogue** du data plane admin
46
+ * (tous les producteurs + descriptors + endpoints) — pièce « discovery »
47
+ * de P10.2 que Studio lit pour générer sa navigation admin.
48
+ *
49
+ * @param broker - le broker admin (pour le catalogue). Optionnel : sans lui,
50
+ * `admin` renvoie une liste vide.
51
+ * @param opts - composition (`playground: true` → endpoints Playground, dev-only).
52
+ * @returns le contrat admin de framework, prêt à `broker.register()`.
53
+ */
54
+ function createFrameworkAdminApi(broker, opts) {
55
+ /** Normalise `requirements.methods` (string | string[]) → tableau majuscule. */
56
+ const methodsOf = (route) => {
57
+ const m = route.requirements?.methods ?? route.method;
58
+ if (Array.isArray(m)) return m.map((x) => String(x).toUpperCase());
59
+ if (typeof m === "string") return m.split(",").map((s) => s.trim().toUpperCase()).filter(Boolean);
60
+ return ["ANY"];
61
+ };
62
+ const serializeRoute = (route) => ({
63
+ name: route.name,
64
+ path: route.path ?? null,
65
+ methods: methodsOf(route),
66
+ controller: route.controller?.name ?? null,
67
+ action: route.classMethod ?? null,
68
+ module: route.module?.name ?? null,
69
+ host: route.host ?? null,
70
+ bypassFirewall: route.bypassFirewall
71
+ });
72
+ const descriptor = {
73
+ label: "Routes",
74
+ icon: "route",
75
+ order: 2
76
+ };
77
+ /** Premier param d'une query (string|string[]). */
78
+ const one = (v) => Array.isArray(v) ? v[0] : v;
79
+ /**
80
+ * Colonnes sur lesquelles `routes/page` sait trier — l'allowlist que
81
+ * `parsePageQuery` fait respecter. Elle a exactement les clés que {@link cell}
82
+ * sait rendre : un `order` sur autre chose est refusé (400) au lieu de trier
83
+ * sur une chaîne vide, ce qui rendait l'ordre arbitraire sans le dire.
84
+ */
85
+ const SORTABLE_COLUMNS = [
86
+ "methods",
87
+ "path",
88
+ "name",
89
+ "controller",
90
+ "module",
91
+ "firewall"
92
+ ];
93
+ /**
94
+ * `routes/page` sait TOUJOURS chercher : sa collection est le dump du Router,
95
+ * en mémoire, donc `q` est un simple balayage — ce qui n'est vrai d'aucune
96
+ * ressource persistée, d'où la déclaration explicite plutôt qu'un défaut.
97
+ *
98
+ * Constante partagée entre la publication (`page.search`) et le handler
99
+ * (`parsePageQuery(..., { searchable })`) : les écrire séparément recréerait
100
+ * la divergence que la publication vient supprimer — une console qui affiche
101
+ * une barre de recherche que le serveur refuse en 400.
102
+ */
103
+ const SEARCHABLE = true;
104
+ /** Valeur d'une colonne pour le tri/filtre serveur (clés = colonnes du front). */
105
+ const cell = (r, key) => {
106
+ switch (key) {
107
+ case "methods": return r.methods.join(",");
108
+ case "path": return r.path ?? "";
109
+ case "name": return r.name ?? "";
110
+ case "controller": return [r.controller, r.action].filter(Boolean).join(".");
111
+ case "module": return r.module ?? "";
112
+ case "firewall": return r.bypassFirewall ? "bypass" : "protected";
113
+ default: return "";
114
+ }
115
+ };
116
+ /**
117
+ * Applique un opérateur de filtre (miroir serveur du DataGrid).
118
+ *
119
+ * ⚠️ **Ce langage d'opérateurs appartient à CET endpoint, pas au framework.**
120
+ * Le contrat de filtre (`parseFilters`, cœur) est `nom=valeur` sans opérateur ;
121
+ * `routes/page` peut se permettre davantage parce que sa collection est en
122
+ * mémoire (le dump du Router) et qu'aucun store n'a à l'indexer. Un data plane
123
+ * adossé à une ressource persistée déclare un `IFilterSpec` et laisse le cœur
124
+ * valider — il ne recopie pas ce `matchOp`.
125
+ */
126
+ const matchOp = (raw, op, value) => {
127
+ const s = String(raw ?? "");
128
+ const v = String(value ?? "");
129
+ if (op !== "isEmpty" && op !== "notEmpty" && v === "") return true;
130
+ switch (op) {
131
+ case "contains": return s.toLowerCase().includes(v.toLowerCase());
132
+ case "equals": return s === v;
133
+ case "in": {
134
+ const split = (list) => list.split(",").map((t) => t.trim()).filter((t) => t !== "");
135
+ const wanted = split(v);
136
+ if (wanted.length === 0) return true;
137
+ return split(s).some((c) => wanted.includes(c));
138
+ }
139
+ case "startsWith": return s.toLowerCase().startsWith(v.toLowerCase());
140
+ case "endsWith": return s.toLowerCase().endsWith(v.toLowerCase());
141
+ case "isEmpty": return s === "";
142
+ case "notEmpty": return s !== "";
143
+ default: return true;
144
+ }
145
+ };
146
+ const endpoints = [
147
+ {
148
+ path: "routes",
149
+ summary: "All registered routes (Router dump) — name, path, methods, controller",
150
+ handler: () => router_default.routes.map(serializeRoute)
151
+ },
152
+ {
153
+ path: "routes/page",
154
+ summary: "Routes paginées côté SERVEUR — contrat IPageQuery : ?limit&offset&order=champ:ASC&q, plus filters(JSON) — langage d'opérateurs PROPRE à cet endpoint (collection en mémoire), sérialisé par la vue Routes seule. Rend un IPage.",
155
+ page: {
156
+ sortable: () => SORTABLE_COLUMNS,
157
+ search: () => SEARCHABLE
158
+ },
159
+ handler: (request) => {
160
+ const query = parsePageQuery(request.query, {
161
+ defaultLimit: 25,
162
+ sortable: SORTABLE_COLUMNS,
163
+ searchable: SEARCHABLE
164
+ });
165
+ parseFilters(request.query, {}, { accepts: ["filters"] });
166
+ const search = query.q?.toLowerCase() ?? "";
167
+ let filters = [];
168
+ try {
169
+ const raw = one(request.query.filters);
170
+ if (raw) filters = JSON.parse(raw);
171
+ } catch {
172
+ filters = [];
173
+ }
174
+ let rows = router_default.routes.map(serializeRoute);
175
+ if (search) rows = rows.filter((r) => [
176
+ r.methods.join(","),
177
+ r.path,
178
+ r.name,
179
+ r.controller,
180
+ r.action,
181
+ r.module,
182
+ r.bypassFirewall ? "bypass" : "protected"
183
+ ].filter(Boolean).join(" ").toLowerCase().includes(search));
184
+ for (const f of filters) rows = rows.filter((r) => matchOp(cell(r, f.key), f.op, f.value));
185
+ const total = rows.length;
186
+ if (query.order?.length) {
187
+ const order = query.order;
188
+ rows = [...rows].sort((a, b) => {
189
+ for (const [key, dir] of order) {
190
+ const cmp = cell(a, key).localeCompare(cell(b, key));
191
+ if (cmp !== 0) return dir === "DESC" ? -cmp : cmp;
192
+ }
193
+ return 0;
194
+ });
195
+ }
196
+ const offset = query.offset ?? 0;
197
+ return {
198
+ items: rows.slice(offset, offset + query.limit),
199
+ total,
200
+ limit: query.limit,
201
+ offset,
202
+ hasNext: offset + query.limit < total
203
+ };
204
+ }
205
+ },
206
+ {
207
+ path: "info",
208
+ summary: "Routing summary — total routes, methods, owning modules",
209
+ handler: () => {
210
+ const routes = router_default.routes;
211
+ const methods = /* @__PURE__ */ new Set();
212
+ const modules = /* @__PURE__ */ new Set();
213
+ for (const r of routes) {
214
+ methodsOf(r).forEach((m) => methods.add(m));
215
+ if (r.module?.name) modules.add(r.module.name);
216
+ }
217
+ return {
218
+ routesTotal: routes.length,
219
+ methods: [...methods].sort(),
220
+ modules: [...modules].sort()
221
+ };
222
+ }
223
+ },
224
+ {
225
+ path: "admin",
226
+ summary: "Admin data plane catalog — producers, descriptors, endpoints",
227
+ handler: () => {
228
+ if (!broker) return { producers: [] };
229
+ const descByNs = new Map(broker.list().map((p) => [p.adminNamespace, p.adminDescriptor()]));
230
+ const byNs = /* @__PURE__ */ new Map();
231
+ for (const r of broker.routes()) {
232
+ let arr = byNs.get(r.namespace);
233
+ if (!arr) {
234
+ arr = [];
235
+ byNs.set(r.namespace, arr);
236
+ }
237
+ const caps = r.endpoint.page;
238
+ arr.push({
239
+ method: r.method,
240
+ path: r.path,
241
+ role: r.role,
242
+ summary: r.endpoint.summary ?? null,
243
+ ...readPageCapabilities(caps)
244
+ });
245
+ }
246
+ return { producers: [...byNs.keys()].map((ns) => {
247
+ const d = descByNs.get(ns);
248
+ return {
249
+ namespace: ns,
250
+ label: d?.label ?? ns,
251
+ icon: d?.icon ?? null,
252
+ order: d?.order ?? 99,
253
+ role: d?.role ?? null,
254
+ endpoints: byNs.get(ns)
255
+ };
256
+ }).sort((a, b) => a.order - b.order) };
257
+ }
258
+ }
259
+ ];
260
+ if (opts?.playground) endpoints.push(...createPlaygroundEndpoints());
261
+ return {
262
+ adminNamespace: "framework",
263
+ adminDescriptor: () => descriptor,
264
+ adminEndpoints: () => endpoints
265
+ };
266
+ }
267
+ //#endregion
268
+ export { createFrameworkAdminApi };