@nodefony/framework 10.0.0-alpha.8 → 10.0.0-beta.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/README.md +4 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/index.js +7 -6
- package/dist/nodefony/controller/AdminApiController.js +4 -3
- package/dist/nodefony/controller/ApiKeyController.js +1 -1
- package/dist/nodefony/controller/OAuth2Controller.js +11 -6
- package/dist/nodefony/controller/SessionAuthController.js +3 -3
- package/dist/nodefony/controller/TokenAuthController.js +2 -2
- package/dist/nodefony/controller/TotpController.js +5 -4
- package/dist/nodefony/controller/WebAuthnController.js +4 -4
- package/dist/nodefony/decorators/routerDecorators.js +41 -35
- package/dist/nodefony/service/AdminBroker.js +2 -2
- package/dist/nodefony/service/IdempotencyStore.js +4 -5
- package/dist/nodefony/service/router.js +75 -25
- package/dist/nodefony/src/Controller.js +184 -78
- package/dist/nodefony/src/FrameworkAdminApi.js +19 -6
- package/dist/nodefony/src/KernelAdminApi.js +60 -55
- package/dist/nodefony/src/PlaygroundAdminApi.js +3 -3
- package/dist/nodefony/src/RedisIdempotencyStore.js +2 -2
- package/dist/nodefony/src/Resolver.js +219 -63
- package/dist/nodefony/src/Route.js +38 -38
- package/dist/nodefony/src/SyslogAdminApi.js +4 -4
- package/dist/nodefony/src/docsReader.js +22 -18
- package/dist/nodefony/src/scopeCatalog.js +1 -1
- package/dist/nodefony/src/singletonGuard.js +76 -0
- package/dist/types/nodefony/controller/BenchController.d.ts +2 -2
- package/dist/types/nodefony/controller/OAuth2Controller.d.ts +3 -3
- package/dist/types/nodefony/controller/ProtectedResourceMetadataController.d.ts +1 -1
- package/dist/types/nodefony/controller/SessionAuthController.d.ts +2 -1
- package/dist/types/nodefony/decorators/routerDecorators.d.ts +49 -43
- package/dist/types/nodefony/interfaces/IController.d.ts +9 -7
- package/dist/types/nodefony/interfaces/IResolver.d.ts +10 -10
- package/dist/types/nodefony/interfaces/IRoute.d.ts +13 -13
- package/dist/types/nodefony/service/AdminBroker.d.ts +3 -3
- package/dist/types/nodefony/service/router.d.ts +33 -7
- package/dist/types/nodefony/src/Controller.d.ts +97 -24
- package/dist/types/nodefony/src/Resolver.d.ts +154 -15
- package/dist/types/nodefony/src/ResourceController.d.ts +6 -6
- package/dist/types/nodefony/src/Route.d.ts +24 -22
- package/dist/types/nodefony/src/docsReader.d.ts +3 -3
- package/dist/types/nodefony/src/singletonGuard.d.ts +53 -0
- package/docs/admin.md +26 -26
- package/docs/controller.md +139 -121
- package/docs/decorateurs.md +108 -105
- package/docs/idempotence.md +78 -71
- package/docs/routing.md +57 -51
- package/docs/templates.md +17 -17
- package/package.json +12 -12
|
@@ -2,7 +2,7 @@ import { CORE_PACKAGE, checkOutdated, countModuleDocs, listModuleDocs, listModul
|
|
|
2
2
|
import { getResolvedPath, navigateSchemaNode, nodeFlags, notEditableReason, recipeFor, validateLeafValue } from "./configMutation.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { GitService, Syslog, aggregateProvenance,
|
|
5
|
+
import { GitService, Syslog, aggregateProvenance, collectDevStatus, computeConfigProvenance, defaultAppConfig, extractJsonSchemaDefaults, extractMarkdownSection, flattenConfigSchema, getActiveLogDriver, listLogDrivers, outlineMarkdown, parseNfEnvOverrides, parseStoreManifest, readResolvedPath, withResolvedPath } from "nodefony";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
8
|
//#region nodefony/src/KernelAdminApi.ts
|
|
@@ -50,11 +50,11 @@ function readAdapterManifest(pkg) {
|
|
|
50
50
|
const pkgJson = join(REPO_ROOT, "node_modules", ...pkg.split("/"), "package.json");
|
|
51
51
|
try {
|
|
52
52
|
if (!existsSync(pkgJson)) return null;
|
|
53
|
-
const
|
|
54
|
-
if (!
|
|
53
|
+
const manifest = parseStoreManifest(JSON.parse(readFileSync(pkgJson, "utf8")));
|
|
54
|
+
if (!manifest?.provides) return null;
|
|
55
55
|
return {
|
|
56
|
-
storeKind:
|
|
57
|
-
stores:
|
|
56
|
+
storeKind: manifest.storeKind,
|
|
57
|
+
stores: manifest.stores
|
|
58
58
|
};
|
|
59
59
|
} catch {
|
|
60
60
|
return null;
|
|
@@ -153,9 +153,8 @@ function flattenPaths(value, prefix, out) {
|
|
|
153
153
|
function attributeOverrideSources(entries) {
|
|
154
154
|
const byTarget = /* @__PURE__ */ new Map();
|
|
155
155
|
for (const e of entries) for (const key of Object.keys(e.config)) {
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
158
|
-
const targetSeg = m[1].toLowerCase();
|
|
156
|
+
const targetSeg = /^module-(.+)$/i.exec(key)?.[1]?.toLowerCase();
|
|
157
|
+
if (targetSeg === void 0) continue;
|
|
159
158
|
const paths = /* @__PURE__ */ new Set();
|
|
160
159
|
flattenPaths(e.config[key], "", paths);
|
|
161
160
|
const arr = byTarget.get(targetSeg);
|
|
@@ -214,7 +213,7 @@ function computeConfigEntry(key, mod, runtimePaths) {
|
|
|
214
213
|
const envPaths = new Set(parseNfEnvOverrides(process.env).filter((o) => o.moduleSeg === seg).map((o) => o.path.join(".")));
|
|
215
214
|
provenance = computeConfigProvenance(opts, extractJsonSchemaDefaults(schema), envPaths);
|
|
216
215
|
}
|
|
217
|
-
if (provenance && runtimePaths
|
|
216
|
+
if (provenance && runtimePaths?.size) {
|
|
218
217
|
for (const k of Object.keys(provenance)) if (runtimePaths.has(k.toLowerCase())) provenance[k] = "runtime";
|
|
219
218
|
}
|
|
220
219
|
return {
|
|
@@ -259,12 +258,13 @@ function auditConfigChange(kernel, request, moduleKey, path, before, after) {
|
|
|
259
258
|
});
|
|
260
259
|
}
|
|
261
260
|
/**
|
|
262
|
-
* Journalise un changement de debug runtime (catégorie `
|
|
261
|
+
* Journalise un changement de debug runtime (catégorie `config` : une verbosité
|
|
262
|
+
* est une configuration runtime). Même découplage
|
|
263
263
|
* que {@link auditConfigChange} (résolution par nom, no-op si pas d'audit).
|
|
264
264
|
*/
|
|
265
265
|
function auditLogLevelChange(kernel, request, module, action, level, ttlMs) {
|
|
266
266
|
(kernel.container?.get("auditService"))?.record?.({
|
|
267
|
-
category: "
|
|
267
|
+
category: "config",
|
|
268
268
|
action: `log.debug.${action}`,
|
|
269
269
|
outcome: "success",
|
|
270
270
|
actor: actorLabel(request.user),
|
|
@@ -352,7 +352,7 @@ function createKernelAdminApi(kernel) {
|
|
|
352
352
|
if (!mod) return null;
|
|
353
353
|
return {
|
|
354
354
|
path: mod.path,
|
|
355
|
-
pkg: mod.getModuleName
|
|
355
|
+
pkg: mod.getModuleName() ?? key
|
|
356
356
|
};
|
|
357
357
|
};
|
|
358
358
|
const resolvePackageDir = (name) => {
|
|
@@ -401,7 +401,7 @@ function createKernelAdminApi(kernel) {
|
|
|
401
401
|
const runtimeEdited = /* @__PURE__ */ new Map();
|
|
402
402
|
const devGuard = () => kernel.environment === "development" || Boolean(kernel.debug);
|
|
403
403
|
const repoRoot = process.cwd();
|
|
404
|
-
const relPath = (p) => p
|
|
404
|
+
const relPath = (p) => p?.startsWith(repoRoot) ? p.slice(repoRoot.length).replace(/^[/\\]+/, "") || "." : p ?? null;
|
|
405
405
|
const redactInfraUrl = (url) => url.replace(/\/\/[^/@]*@/, "//***@");
|
|
406
406
|
const buildConfigEntries = () => {
|
|
407
407
|
const modules = kernel.getModules();
|
|
@@ -581,16 +581,13 @@ function createKernelAdminApi(kernel) {
|
|
|
581
581
|
isApp: false,
|
|
582
582
|
path: relPath(core.path)
|
|
583
583
|
}];
|
|
584
|
-
for (const name of Object.
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
path: relPath(mod.path)
|
|
592
|
-
});
|
|
593
|
-
}
|
|
584
|
+
for (const [name, mod] of Object.entries(modules)) list.push({
|
|
585
|
+
key: name,
|
|
586
|
+
name: mod.getModuleName() ?? name,
|
|
587
|
+
version: mod.getModuleVersion() ?? null,
|
|
588
|
+
isApp: mod.isApp,
|
|
589
|
+
path: relPath(mod.path)
|
|
590
|
+
});
|
|
594
591
|
return list;
|
|
595
592
|
}
|
|
596
593
|
},
|
|
@@ -600,14 +597,11 @@ function createKernelAdminApi(kernel) {
|
|
|
600
597
|
handler: () => {
|
|
601
598
|
const modules = kernel.getModules();
|
|
602
599
|
const services = [];
|
|
603
|
-
for (const name of Object.
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
class: mod.get(service)?.constructor?.name ?? null
|
|
609
|
-
});
|
|
610
|
-
}
|
|
600
|
+
for (const [name, mod] of Object.entries(modules)) for (const service of mod.getServiceNames()) services.push({
|
|
601
|
+
name: service,
|
|
602
|
+
module: name,
|
|
603
|
+
class: mod.get(service)?.constructor?.name ?? null
|
|
604
|
+
});
|
|
611
605
|
services.sort((a, b) => `${a.module}.${a.name}`.localeCompare(`${b.module}.${b.name}`));
|
|
612
606
|
return services;
|
|
613
607
|
}
|
|
@@ -627,8 +621,7 @@ function createKernelAdminApi(kernel) {
|
|
|
627
621
|
const entries = buildConfigEntries();
|
|
628
622
|
const matches = (name, key) => name.toLowerCase() === filter || key.toLowerCase() === filter;
|
|
629
623
|
if (filter !== null) {
|
|
630
|
-
|
|
631
|
-
if (!entry || entry.configSchema == null) {
|
|
624
|
+
if (entries.find((e) => matches(e.name, e.key))?.configSchema == null) {
|
|
632
625
|
const modules = kernel.getModules();
|
|
633
626
|
const nameOf = (k) => modules[k].getModuleName?.() ?? k;
|
|
634
627
|
const loadedKey = Object.keys(modules).find((k) => matches(nameOf(k), k));
|
|
@@ -658,7 +651,7 @@ function createKernelAdminApi(kernel) {
|
|
|
658
651
|
if (filter !== null && entry.name.toLowerCase() !== filter && entry.key.toLowerCase() !== filter) continue;
|
|
659
652
|
for (const leaf of flattenConfigSchema(entry.configSchema)) {
|
|
660
653
|
const path = leaf.key.split(".");
|
|
661
|
-
const note = SECRET_KEY.test(path
|
|
654
|
+
const note = SECRET_KEY.test(path.at(-1) ?? "") && !leaf.note.includes("secret") ? [leaf.note, "secret"].filter(Boolean).join(", ") : leaf.note;
|
|
662
655
|
rows.push({
|
|
663
656
|
key: leaf.key,
|
|
664
657
|
module: entry.name,
|
|
@@ -692,7 +685,12 @@ function createKernelAdminApi(kernel) {
|
|
|
692
685
|
if ((res.resolved === "drizzle" || res.resolved === "mongoose") && infra.database) return "NF_DATABASE_URL";
|
|
693
686
|
return null;
|
|
694
687
|
};
|
|
695
|
-
const
|
|
688
|
+
const loadedPackages = /* @__PURE__ */ new Set();
|
|
689
|
+
for (const mod of Object.values(kernel.modules)) {
|
|
690
|
+
const npmName = mod.package?.name;
|
|
691
|
+
if (typeof npmName === "string") loadedPackages.add(npmName);
|
|
692
|
+
loadedPackages.add(mod.name);
|
|
693
|
+
}
|
|
696
694
|
return {
|
|
697
695
|
engines: OFFICIAL_STORE_ADAPTERS.map((a) => {
|
|
698
696
|
const manifest = readAdapterManifest(a.package);
|
|
@@ -703,7 +701,7 @@ function createKernelAdminApi(kernel) {
|
|
|
703
701
|
kind: manifest?.storeKind ?? "durable",
|
|
704
702
|
provides: manifest?.stores ?? [],
|
|
705
703
|
installed: manifest !== null || isPackageInstalled(a.package),
|
|
706
|
-
loaded:
|
|
704
|
+
loaded: loadedPackages.has(a.package) || loadedPackages.has(a.engine)
|
|
707
705
|
};
|
|
708
706
|
}),
|
|
709
707
|
infra: {
|
|
@@ -745,7 +743,7 @@ function createKernelAdminApi(kernel) {
|
|
|
745
743
|
reason: "prod_immutable"
|
|
746
744
|
}
|
|
747
745
|
};
|
|
748
|
-
const key = request.params.module;
|
|
746
|
+
const key = request.params.module ?? "";
|
|
749
747
|
const mod = kernel.getModules()[key];
|
|
750
748
|
if (!mod) return {
|
|
751
749
|
status: 404,
|
|
@@ -806,17 +804,24 @@ function createKernelAdminApi(kernel) {
|
|
|
806
804
|
};
|
|
807
805
|
const opts = mod.options ?? {};
|
|
808
806
|
const before = getResolvedPath(opts, segments);
|
|
809
|
-
|
|
807
|
+
const nextOpts = withResolvedPath(opts, segments, value);
|
|
808
|
+
if (nextOpts === null) return {
|
|
810
809
|
status: 422,
|
|
811
810
|
body: {
|
|
812
811
|
error: "Path not present in module config",
|
|
813
812
|
path: body.path
|
|
814
813
|
}
|
|
815
814
|
};
|
|
815
|
+
const moduleInstance = kernel.getModules()[key];
|
|
816
|
+
if (moduleInstance) kernel.replaceModuleOptions(moduleInstance, nextOpts);
|
|
816
817
|
const svcMod = mod;
|
|
817
818
|
for (const sname of svcMod.getServiceNames?.() ?? []) {
|
|
818
819
|
const svc = svcMod.get?.(sname);
|
|
819
|
-
if (svc?.options
|
|
820
|
+
if (svc?.options === opts) svc.options = nextOpts;
|
|
821
|
+
else if (svc?.options) {
|
|
822
|
+
const nextSvc = withResolvedPath(svc.options, segments, value);
|
|
823
|
+
if (nextSvc !== null) svc.options = nextSvc;
|
|
824
|
+
}
|
|
820
825
|
svc?.onConfigChanged?.(segments);
|
|
821
826
|
}
|
|
822
827
|
let edited = runtimeEdited.get(key);
|
|
@@ -909,7 +914,7 @@ function createKernelAdminApi(kernel) {
|
|
|
909
914
|
path: "module/{name}",
|
|
910
915
|
summary: "Detail of one module by key (http, framework, … or core)",
|
|
911
916
|
handler: async (request) => {
|
|
912
|
-
const key = request.params.name;
|
|
917
|
+
const key = request.params.name ?? "";
|
|
913
918
|
if (key === CORE_KEY) {
|
|
914
919
|
const core = await readCoreInfo();
|
|
915
920
|
return {
|
|
@@ -944,10 +949,10 @@ function createKernelAdminApi(kernel) {
|
|
|
944
949
|
return {
|
|
945
950
|
key,
|
|
946
951
|
name: cfg.name,
|
|
947
|
-
version: mod.getModuleVersion
|
|
952
|
+
version: mod.getModuleVersion() ?? null,
|
|
948
953
|
isApp: cfg.isApp,
|
|
949
954
|
path: relPath(mod.path),
|
|
950
|
-
dependencies: mod.getDependencies
|
|
955
|
+
dependencies: mod.getDependencies(),
|
|
951
956
|
services,
|
|
952
957
|
config: cfg.config,
|
|
953
958
|
configSchema: cfg.configSchema,
|
|
@@ -964,7 +969,7 @@ function createKernelAdminApi(kernel) {
|
|
|
964
969
|
path: "module/{name}/dependencies",
|
|
965
970
|
summary: "Module dependencies with installed versions",
|
|
966
971
|
handler: async (request) => {
|
|
967
|
-
const target = resolveTarget(request.params.name);
|
|
972
|
+
const target = resolveTarget(request.params.name ?? "");
|
|
968
973
|
if (!target) return {
|
|
969
974
|
status: 404,
|
|
970
975
|
body: {
|
|
@@ -983,7 +988,7 @@ function createKernelAdminApi(kernel) {
|
|
|
983
988
|
path: "module/{name}/dependencies/outdated",
|
|
984
989
|
summary: "Check external dependencies for updates (npm registry)",
|
|
985
990
|
handler: async (request) => {
|
|
986
|
-
const target = resolveTarget(request.params.name);
|
|
991
|
+
const target = resolveTarget(request.params.name ?? "");
|
|
987
992
|
if (!target) return {
|
|
988
993
|
status: 404,
|
|
989
994
|
body: {
|
|
@@ -1003,7 +1008,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1003
1008
|
path: "module/{name}/docs",
|
|
1004
1009
|
summary: "Documentation index of one module (markdown in <module>/docs)",
|
|
1005
1010
|
handler: async (request) => {
|
|
1006
|
-
const key = request.params.name;
|
|
1011
|
+
const key = request.params.name ?? "";
|
|
1007
1012
|
const dir = resolveDocDir(key);
|
|
1008
1013
|
if (!dir) return {
|
|
1009
1014
|
status: 404,
|
|
@@ -1023,7 +1028,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1023
1028
|
path: "module/{name}/docs/{slug}",
|
|
1024
1029
|
summary: "Raw markdown of one module doc by slug",
|
|
1025
1030
|
handler: async (request) => {
|
|
1026
|
-
const key = request.params.name;
|
|
1031
|
+
const key = request.params.name ?? "";
|
|
1027
1032
|
const dir = resolveDocDir(key);
|
|
1028
1033
|
if (!dir) return {
|
|
1029
1034
|
status: 404,
|
|
@@ -1033,7 +1038,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1033
1038
|
available: docKeys()
|
|
1034
1039
|
}
|
|
1035
1040
|
};
|
|
1036
|
-
const doc = await readModuleDoc(dir, request.params.slug);
|
|
1041
|
+
const doc = await readModuleDoc(dir, request.params.slug ?? "");
|
|
1037
1042
|
if (!doc) return {
|
|
1038
1043
|
status: 404,
|
|
1039
1044
|
body: {
|
|
@@ -1062,7 +1067,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1062
1067
|
markdown: section.markdown
|
|
1063
1068
|
};
|
|
1064
1069
|
}
|
|
1065
|
-
if (request.query
|
|
1070
|
+
if (Object.hasOwn(request.query, "outline")) {
|
|
1066
1071
|
const { markdown, ...rest } = doc;
|
|
1067
1072
|
return {
|
|
1068
1073
|
...rest,
|
|
@@ -1113,7 +1118,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1113
1118
|
path: "module/{name}/symbol/{symbol}",
|
|
1114
1119
|
summary: "Declaration (signature + TSDoc) of one exported symbol",
|
|
1115
1120
|
handler: async (request) => {
|
|
1116
|
-
const key = request.params.name;
|
|
1121
|
+
const key = request.params.name ?? "";
|
|
1117
1122
|
const modulePath = resolveTarget(key)?.path ?? resolvePackageDir(key);
|
|
1118
1123
|
if (!modulePath) return {
|
|
1119
1124
|
status: 404,
|
|
@@ -1123,7 +1128,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1123
1128
|
available: docKeys()
|
|
1124
1129
|
}
|
|
1125
1130
|
};
|
|
1126
|
-
const symbol = request.params.symbol;
|
|
1131
|
+
const symbol = request.params.symbol ?? "";
|
|
1127
1132
|
const found = await readSymbolDeclaration(modulePath, symbol);
|
|
1128
1133
|
if (!found) return {
|
|
1129
1134
|
status: 404,
|
|
@@ -1145,7 +1150,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1145
1150
|
path: "module/{name}/symbols",
|
|
1146
1151
|
summary: "Exported TS symbols + TSDoc descriptions (.ai/symbols.json)",
|
|
1147
1152
|
handler: async (request) => {
|
|
1148
|
-
const key = request.params.name;
|
|
1153
|
+
const key = request.params.name ?? "";
|
|
1149
1154
|
const target = resolveTarget(key);
|
|
1150
1155
|
if (!target) return {
|
|
1151
1156
|
status: 404,
|
|
@@ -1166,7 +1171,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1166
1171
|
path: "module/{name}/coverage",
|
|
1167
1172
|
summary: "Latest test coverage report (vitest json-summary)",
|
|
1168
1173
|
handler: async (request) => {
|
|
1169
|
-
const key = request.params.name;
|
|
1174
|
+
const key = request.params.name ?? "";
|
|
1170
1175
|
const target = resolveTarget(key);
|
|
1171
1176
|
if (!target) return {
|
|
1172
1177
|
status: 404,
|
|
@@ -1186,7 +1191,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1186
1191
|
path: "module/{name}/tests",
|
|
1187
1192
|
summary: "List test files of one module",
|
|
1188
1193
|
handler: async (request) => {
|
|
1189
|
-
const key = request.params.name;
|
|
1194
|
+
const key = request.params.name ?? "";
|
|
1190
1195
|
const target = resolveTarget(key);
|
|
1191
1196
|
if (!target) return {
|
|
1192
1197
|
status: 404,
|
|
@@ -1213,7 +1218,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1213
1218
|
status: 403,
|
|
1214
1219
|
body: { error: "Test runner disabled outside development" }
|
|
1215
1220
|
};
|
|
1216
|
-
const key = request.params.name;
|
|
1221
|
+
const key = request.params.name ?? "";
|
|
1217
1222
|
const target = resolveTarget(key);
|
|
1218
1223
|
if (!target) return {
|
|
1219
1224
|
status: 404,
|
|
@@ -1241,7 +1246,7 @@ function createKernelAdminApi(kernel) {
|
|
|
1241
1246
|
startedAt: Date.now()
|
|
1242
1247
|
});
|
|
1243
1248
|
if (testJobs.size > 16) {
|
|
1244
|
-
const oldest = [...testJobs.entries()].sort((a, b) => a[1].startedAt - b[1].startedAt)
|
|
1249
|
+
const oldest = [...testJobs.entries()].sort((a, b) => a[1].startedAt - b[1].startedAt).at(0);
|
|
1245
1250
|
if (oldest) testJobs.delete(oldest[0]);
|
|
1246
1251
|
}
|
|
1247
1252
|
runModuleTests(target.path, file).then((result) => testJobs.set(jobId, {
|
|
@@ -4,8 +4,8 @@ import AdminApiController from "../controller/AdminApiController.js";
|
|
|
4
4
|
//#region nodefony/src/PlaygroundAdminApi.ts
|
|
5
5
|
/** Normalise `requirements.methods` (string | string[]) → tableau majuscule. */
|
|
6
6
|
function methodsOf(route) {
|
|
7
|
-
const m = route.requirements
|
|
8
|
-
if (Array.isArray(m)) return m.map((x) =>
|
|
7
|
+
const m = route.requirements.methods ?? route.method;
|
|
8
|
+
if (Array.isArray(m)) return m.map((x) => x.toUpperCase());
|
|
9
9
|
if (typeof m === "string") return m.split(",").map((s) => s.trim().toUpperCase()).filter(Boolean);
|
|
10
10
|
return ["ANY"];
|
|
11
11
|
}
|
|
@@ -44,7 +44,7 @@ function serializeAction(route) {
|
|
|
44
44
|
csrfProtect: meta.csrfProtect,
|
|
45
45
|
csrfExempt: meta.csrfExempt,
|
|
46
46
|
session: meta.sessionIntent,
|
|
47
|
-
bypassFirewall: route.bypassFirewall
|
|
47
|
+
bypassFirewall: route.bypassFirewall
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
}
|
|
@@ -173,7 +173,7 @@ var RedisIdempotencyStore = class {
|
|
|
173
173
|
}
|
|
174
174
|
/** Préfixe effectif des clés, cloisonné par application (mémoïsé). */
|
|
175
175
|
#prefix() {
|
|
176
|
-
|
|
176
|
+
this.#prefixCache ??= this.#resolvePrefix();
|
|
177
177
|
return this.#prefixCache;
|
|
178
178
|
}
|
|
179
179
|
#key(key) {
|
|
@@ -227,7 +227,7 @@ var RedisIdempotencyStore = class {
|
|
|
227
227
|
if (!client) return;
|
|
228
228
|
const k = this.#key(key);
|
|
229
229
|
const existing = this.#parse(await client.get(k));
|
|
230
|
-
if (existing
|
|
230
|
+
if (existing?.s !== "if") {
|
|
231
231
|
this.#dec();
|
|
232
232
|
return;
|
|
233
233
|
}
|