@nodefony/framework 10.0.0-beta.6 → 10.0.0-beta.7
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/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +3 -3
- package/dist/nodefony/controller/CspReportController.js +2 -2
- package/dist/nodefony/controller/IssuerMetadataController.js +2 -2
- package/dist/nodefony/controller/ProtectedResourceMetadataController.js +74 -9
- package/dist/nodefony/controller/SessionAuthController.js +1 -1
- package/dist/nodefony/decorators/routerDecorators.js +101 -13
- package/dist/nodefony/grpc/GrpcController.js +138 -0
- package/dist/nodefony/grpc/GrpcService.js +75 -0
- package/dist/nodefony/grpc/declarations.js +33 -0
- package/dist/nodefony/grpc/index.js +3 -0
- package/dist/nodefony/service/AdminBroker.js +1 -1
- package/dist/nodefony/service/IdempotencyStore.js +1 -1
- package/dist/nodefony/service/router.js +11 -9
- package/dist/nodefony/src/Controller.js +21 -3
- package/dist/nodefony/src/Resolver.js +83 -43
- package/dist/nodefony/src/Route.js +45 -45
- package/dist/nodefony/src/loginPage.js +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/nodefony/controller/ProtectedResourceMetadataController.d.ts +21 -1
- package/dist/types/nodefony/decorators/routerDecorators.d.ts +2 -1
- package/dist/types/nodefony/grpc/GrpcController.d.ts +75 -0
- package/dist/types/nodefony/grpc/GrpcService.d.ts +51 -0
- package/dist/types/nodefony/grpc/declarations.d.ts +12 -0
- package/dist/types/nodefony/grpc/index.d.ts +13 -0
- package/dist/types/nodefony/interfaces/IController.d.ts +2 -2
- package/dist/types/nodefony/service/router.d.ts +5 -4
- package/dist/types/nodefony/src/Controller.d.ts +15 -3
- package/dist/types/nodefony/src/Resolver.d.ts +34 -7
- package/dist/types/nodefony/src/Route.d.ts +35 -0
- package/dist/types/nodefony/src/loginPage.d.ts +1 -1
- package/docs/controller.md +89 -9
- package/docs/grpc.md +690 -0
- package/docs/idempotence.md +11 -6
- package/docs/index.md +12 -8
- package/docs/routing.md +30 -2
- package/package.json +24 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// \0@oxc-project+runtime@0.
|
|
1
|
+
// \0@oxc-project+runtime@0.158.0/helpers/esm/decorate.js
|
|
2
2
|
function __decorate(decorators, target, key, desc) {
|
|
3
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { RedisIdempotencyStore } from "./nodefony/src/RedisIdempotencyStore.js";
|
|
|
5
5
|
import { scheduleIdempotencyGc } from "./nodefony/src/idempotencyGc.js";
|
|
6
6
|
import router_default, { FRAMEWORK_ROUTER, isFrameworkRouter } from "./nodefony/service/router.js";
|
|
7
7
|
import Route from "./nodefony/src/Route.js";
|
|
8
|
-
import Controller, { configureFileServing } from "./nodefony/src/Controller.js";
|
|
8
|
+
import Controller, { configureFileServing, contentDisposition } from "./nodefony/src/Controller.js";
|
|
9
9
|
import ResourceController from "./nodefony/src/ResourceController.js";
|
|
10
10
|
import Resolver from "./nodefony/src/Resolver.js";
|
|
11
11
|
import AdminBroker_default from "./nodefony/service/AdminBroker.js";
|
|
@@ -28,7 +28,7 @@ import { buildPlaygroundSnapshot } from "./nodefony/src/PlaygroundAdminApi.js";
|
|
|
28
28
|
import { createSyslogAdminApi } from "./nodefony/src/SyslogAdminApi.js";
|
|
29
29
|
import Eta from "./nodefony/service/Eta.js";
|
|
30
30
|
import { All, Anonymous, Body, BypassFirewall, Cookie, Csp, CsrfExempt, CsrfProtect, CurrentUser, Delete, Domain, Get, Head, Header, Headers, HttpCode, Idempotent, IsGranted, Options, Param, Patch, Post, Put, Query, Redirect, Req, RequireAuthentication, RequireScope, Res, Scope, Session, UploadedFile, UploadedFiles, UseSession, buildContextParamArgs, controller, controllers, getParamArgsMeta, getRouteSchema, route, routeExpectsBodyStream } from "./nodefony/decorators/routerDecorators.js";
|
|
31
|
-
import __decorate from "./_virtual/_@oxc-project_runtime@0.
|
|
31
|
+
import __decorate from "./_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
32
32
|
import { validateRouteInput } from "./nodefony/src/routeSchema.js";
|
|
33
33
|
import path from "node:path";
|
|
34
34
|
import { AUTO_STORE, EMPTY_INFRA, Module, durableStoreRemedy, readStoreConnector, readStoreLocation, resolveAutoStore, services, storeConnectivity } from "nodefony";
|
|
@@ -208,4 +208,4 @@ Framework = __decorate([services([
|
|
|
208
208
|
IdempotencyStore_default
|
|
209
209
|
])], Framework);
|
|
210
210
|
var framework_default = Framework;
|
|
211
|
-
export { AdminApiController, AdminBroker_default as AdminBroker, All, Anonymous, ApiKeyController, BenchController, Body, BypassFirewall, Controller, Cookie, Csp, CspReportController, CsrfExempt, CsrfProtect, CurrentUser, Delete, Domain, Eta, FRAMEWORK_ROUTER, Get, Head, Header, Headers, HttpCode, Idempotent, IsGranted, IssuerMetadataController, LoginPageController, IdempotencyStore_default as MemoryIdempotencyStore, OAuth2Controller, Options, Param, Patch, Post, ProtectedResourceMetadataController, Put, Query, Redirect, Req, RequireAuthentication, RequireScope, Res, Resolver, ResourceController, Route, router_default as Router, Scope, Session, SessionAuthController, TokenAuthController, TotpController, UploadedFile, UploadedFiles, UseSession, WebAuthnController, buildContextParamArgs, buildPlaygroundSnapshot, collectProtectedResources, controller, controllers, createFrameworkAdminApi, createKernelAdminApi, createSyslogAdminApi, framework_default as default, defineFrameworkConfig, frameworkConfigJsonSchema, frameworkConfigSchema, getIdempotencyStoreFactory, getParamArgsMeta, getRouteSchema, isFrameworkRouter, listIdempotencyStores, mountApiKeyRoutes, mountBenchRoutes, mountCspReportRoute, mountIssuerMetadataRoutes, mountLoginPage, mountOAuth2Routes, mountProtectedResourceRoutes, mountSessionAuthRoutes, mountTokenAuthRoutes, mountTotpRoutes, mountWebAuthnRoutes, protectedResourceRoutePaths, registerIdempotencyStore, route, routeExpectsBodyStream, validateRouteInput };
|
|
211
|
+
export { AdminApiController, AdminBroker_default as AdminBroker, All, Anonymous, ApiKeyController, BenchController, Body, BypassFirewall, Controller, Cookie, Csp, CspReportController, CsrfExempt, CsrfProtect, CurrentUser, Delete, Domain, Eta, FRAMEWORK_ROUTER, Get, Head, Header, Headers, HttpCode, Idempotent, IsGranted, IssuerMetadataController, LoginPageController, IdempotencyStore_default as MemoryIdempotencyStore, OAuth2Controller, Options, Param, Patch, Post, ProtectedResourceMetadataController, Put, Query, Redirect, Req, RequireAuthentication, RequireScope, Res, Resolver, ResourceController, Route, router_default as Router, Scope, Session, SessionAuthController, TokenAuthController, TotpController, UploadedFile, UploadedFiles, UseSession, WebAuthnController, buildContextParamArgs, buildPlaygroundSnapshot, collectProtectedResources, contentDisposition, controller, controllers, createFrameworkAdminApi, createKernelAdminApi, createSyslogAdminApi, framework_default as default, defineFrameworkConfig, frameworkConfigJsonSchema, frameworkConfigSchema, getIdempotencyStoreFactory, getParamArgsMeta, getRouteSchema, isFrameworkRouter, listIdempotencyStores, mountApiKeyRoutes, mountBenchRoutes, mountCspReportRoute, mountIssuerMetadataRoutes, mountLoginPage, mountOAuth2Routes, mountProtectedResourceRoutes, mountSessionAuthRoutes, mountTokenAuthRoutes, mountTotpRoutes, mountWebAuthnRoutes, protectedResourceRoutePaths, registerIdempotencyStore, route, routeExpectsBodyStream, validateRouteInput };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import router_default from "../service/router.js";
|
|
2
2
|
import Controller from "../src/Controller.js";
|
|
3
3
|
import { Body, CsrfExempt } from "../decorators/routerDecorators.js";
|
|
4
|
-
import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.
|
|
5
|
-
import __decorate from "../../_virtual/_@oxc-project_runtime@0.
|
|
4
|
+
import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorateParam.js";
|
|
5
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
6
6
|
import { CSP_REPORT_MAX_BYTES, CSP_REPORT_PATH } from "nodefony";
|
|
7
7
|
import { HttpError } from "@nodefony/http";
|
|
8
8
|
// nodefony/controller/CspReportController.ts
|
|
@@ -92,7 +92,7 @@ var IssuerMetadataController = class extends Controller {
|
|
|
92
92
|
try {
|
|
93
93
|
document = buildAuthorizationServerMetadata({ issuer });
|
|
94
94
|
} catch (error) {
|
|
95
|
-
this.log(`métadonnées d'émetteur impubliables : ${error.message}`, "CRITIC");
|
|
95
|
+
this.log(`métadonnées d'émetteur impubliables : ${error instanceof Error ? error.message : String(error)}`, "CRITIC");
|
|
96
96
|
return this.renderJson({ error: "server_error" }, 500);
|
|
97
97
|
}
|
|
98
98
|
return this.renderJson(document, 200, { "Cache-Control": "public, max-age=3600" });
|
|
@@ -110,7 +110,7 @@ var IssuerMetadataController = class extends Controller {
|
|
|
110
110
|
try {
|
|
111
111
|
keys = await publisher.getPublicJWKS();
|
|
112
112
|
} catch (error) {
|
|
113
|
-
this.log(`JWKS illisible : ${error.message}`, "CRITIC");
|
|
113
|
+
this.log(`JWKS illisible : ${error instanceof Error ? error.message : String(error)}`, "CRITIC");
|
|
114
114
|
return this.renderJson({ error: "server_error" }, 500);
|
|
115
115
|
}
|
|
116
116
|
return this.renderJson(keys, 200, { "Cache-Control": "public, max-age=300" });
|
|
@@ -66,9 +66,10 @@ var ProtectedResourceMetadataController = class extends Controller {
|
|
|
66
66
|
/** Chemin bien connu demandé, tel que le Router l'a matché. */
|
|
67
67
|
#askedPath() {
|
|
68
68
|
const req = this.request;
|
|
69
|
-
|
|
69
|
+
if (typeof req !== "object" || req === null) return null;
|
|
70
|
+
const p = "pathname" in req ? req.pathname : void 0;
|
|
70
71
|
if (typeof p === "string") return p;
|
|
71
|
-
const url = req
|
|
72
|
+
const url = "url" in req ? req.url : void 0;
|
|
72
73
|
if (url instanceof URL) return url.pathname;
|
|
73
74
|
if (typeof url === "string") try {
|
|
74
75
|
return new URL(url, "http://localhost").pathname;
|
|
@@ -112,7 +113,7 @@ var ProtectedResourceMetadataController = class extends Controller {
|
|
|
112
113
|
try {
|
|
113
114
|
document = buildProtectedResourceMetadata(match);
|
|
114
115
|
} catch (error) {
|
|
115
|
-
this.log(`métadonnées de ressource protégée impubliables : ${error.message}`, "CRITIC");
|
|
116
|
+
this.log(`métadonnées de ressource protégée impubliables : ${error instanceof Error ? error.message : String(error)}`, "CRITIC");
|
|
116
117
|
return this.renderJson({ error: "server_error" }, 500);
|
|
117
118
|
}
|
|
118
119
|
return this.renderJson(document, 200, { "Cache-Control": "public, max-age=3600" });
|
|
@@ -140,23 +141,87 @@ var ProtectedResourceMetadataController = class extends Controller {
|
|
|
140
141
|
* interface à implémenter formellement — le même couplage que `tokenService` ou
|
|
141
142
|
* `adminBroker`.
|
|
142
143
|
*
|
|
144
|
+
* Deux sources peuvent déclarer la MÊME ressource — une zone du pare-feu et la
|
|
145
|
+
* porte MCP qu'elle couvre, typiquement — : elles sont fusionnées
|
|
146
|
+
* ({@link mergeProtectedResources}), jamais départagées par leur ordre.
|
|
147
|
+
*
|
|
143
148
|
* @param container - conteneur de services de l'application
|
|
144
|
-
* @returns les ressources déclarées, dans l'ordre des
|
|
149
|
+
* @returns les ressources déclarées, une entrée par ressource, dans l'ordre des
|
|
150
|
+
* services
|
|
145
151
|
*/
|
|
146
152
|
function collectProtectedResources(container) {
|
|
147
153
|
const collected = [];
|
|
148
154
|
for (const name of container.keys()) {
|
|
149
155
|
const service = container.get(name);
|
|
150
|
-
if (typeof service
|
|
156
|
+
if (typeof service !== "object" || service === null || !("publishedProtectedResources" in service) || typeof service.publishedProtectedResources !== "function") continue;
|
|
151
157
|
let declared;
|
|
152
158
|
try {
|
|
153
|
-
declared = service.publishedProtectedResources
|
|
159
|
+
declared = Reflect.apply(service.publishedProtectedResources, service, []);
|
|
160
|
+
} catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (!Array.isArray(declared)) continue;
|
|
164
|
+
for (const entry of declared) if (isProtectedResourceInput(entry)) collected.push(entry);
|
|
165
|
+
}
|
|
166
|
+
return mergeProtectedResources(collected);
|
|
167
|
+
}
|
|
168
|
+
/** Une liste de chaînes, ou rien. */
|
|
169
|
+
function isStringList(value) {
|
|
170
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Une déclaration a-t-elle la forme d'une ressource publiable ? Le contrat est
|
|
174
|
+
* structurel ({@link IProtectedResourcePublisher}) : ce qu'un service rend se
|
|
175
|
+
* VÉRIFIE, il ne se suppose pas.
|
|
176
|
+
*/
|
|
177
|
+
function isProtectedResourceInput(value) {
|
|
178
|
+
if (typeof value !== "object" || value === null) return false;
|
|
179
|
+
if (!("resource" in value) || typeof value.resource !== "string") return false;
|
|
180
|
+
if (!("authorizationServers" in value) || !isStringList(value.authorizationServers)) return false;
|
|
181
|
+
return !("scopesSupported" in value) || value.scopesSupported === void 0 || isStringList(value.scopesSupported);
|
|
182
|
+
}
|
|
183
|
+
/** L'union de deux listes, dans l'ordre d'apparition. */
|
|
184
|
+
function union(first, second) {
|
|
185
|
+
return [.../* @__PURE__ */ new Set([...first, ...second])];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Une ressource, UN document : fusionne les déclarations d'une même ressource
|
|
189
|
+
* (même URI canonique) — l'union de leurs serveurs d'autorisation et de leurs
|
|
190
|
+
* scopes ; le premier nom et la première documentation déclarés.
|
|
191
|
+
*
|
|
192
|
+
* Pourquoi fusionner plutôt que garder la première : une zone du pare-feu
|
|
193
|
+
* publie sa ressource avec les émetteurs de confiance, et la porte MCP qu'elle
|
|
194
|
+
* couvre publie la MÊME ressource avec les scopes de ses outils. Garder la
|
|
195
|
+
* première jetait les scopes sans un mot — et un agent qui suit le document
|
|
196
|
+
* demandait alors un jeton sans eux, qui n'ouvrait pas l'outil.
|
|
197
|
+
*
|
|
198
|
+
* @param entries - déclarations, dans l'ordre des services
|
|
199
|
+
* @returns une entrée par ressource, à la place de la première déclaration
|
|
200
|
+
*/
|
|
201
|
+
function mergeProtectedResources(entries) {
|
|
202
|
+
const merged = [];
|
|
203
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
let key;
|
|
206
|
+
try {
|
|
207
|
+
key = canonicalResourceUri(entry.resource);
|
|
154
208
|
} catch {
|
|
209
|
+
merged.push(entry);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const seen = byResource.get(key);
|
|
213
|
+
if (seen === void 0) {
|
|
214
|
+
const first = { ...entry };
|
|
215
|
+
byResource.set(key, first);
|
|
216
|
+
merged.push(first);
|
|
155
217
|
continue;
|
|
156
218
|
}
|
|
157
|
-
|
|
219
|
+
seen.authorizationServers = union(seen.authorizationServers, entry.authorizationServers);
|
|
220
|
+
if (entry.scopesSupported !== void 0) seen.scopesSupported = union(seen.scopesSupported ?? [], entry.scopesSupported);
|
|
221
|
+
if (seen.resourceName === void 0 && entry.resourceName !== void 0) seen.resourceName = entry.resourceName;
|
|
222
|
+
if (seen.resourceDocumentation === void 0 && entry.resourceDocumentation !== void 0) seen.resourceDocumentation = entry.resourceDocumentation;
|
|
158
223
|
}
|
|
159
|
-
return
|
|
224
|
+
return merged;
|
|
160
225
|
}
|
|
161
226
|
/**
|
|
162
227
|
* Chemins bien connus à monter pour un jeu de ressources déclarées.
|
|
@@ -217,4 +282,4 @@ function mountProtectedResourceRoutes(frameworkModule, resources) {
|
|
|
217
282
|
mounted = true;
|
|
218
283
|
return paths.length;
|
|
219
284
|
}
|
|
220
|
-
export { collectProtectedResources, ProtectedResourceMetadataController as default, mountProtectedResourceRoutes, protectedResourceRoutePaths };
|
|
285
|
+
export { collectProtectedResources, ProtectedResourceMetadataController as default, mergeProtectedResources, mountProtectedResourceRoutes, protectedResourceRoutePaths };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CsrfProtect } from "../decorators/routerDecorators.js";
|
|
2
2
|
import router_default from "../service/router.js";
|
|
3
3
|
import SecurityApiController from "../src/SecurityApiController.js";
|
|
4
|
-
import __decorate from "../../_virtual/_@oxc-project_runtime@0.
|
|
4
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
5
5
|
import { AUTH_API_BASE } from "nodefony";
|
|
6
6
|
// nodefony/controller/SessionAuthController.ts
|
|
7
7
|
let mounted = false;
|
|
@@ -97,6 +97,36 @@ function controllers(targets) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
100
|
+
* Applique à une route en cours de montage ce que la classe et la méthode
|
|
101
|
+
* déclarent par décorateur : `@Domain` (hôte) et `@BypassFirewall`. Seule
|
|
102
|
+
* implémentation de ces précédences — `@controller` et `@GrpcService` montent
|
|
103
|
+
* leurs routes par elle.
|
|
104
|
+
*
|
|
105
|
+
* Précédences : `@route({ host })` > `@Domain` méthode > `@Domain` classe ;
|
|
106
|
+
* option `bypassFirewall: true` > `@BypassFirewall` méthode > classe. Lecture
|
|
107
|
+
* au montage, fail-closed : un oubli laisse la route gardée.
|
|
108
|
+
*
|
|
109
|
+
* @param ctor - la classe contrôleur.
|
|
110
|
+
* @param options - les options de la route, complétées EN PLACE.
|
|
111
|
+
* @param method - la méthode dont les décorateurs s'appliquent (celle du
|
|
112
|
+
* contrat pour une route gRPC, pas l'action de base).
|
|
113
|
+
*
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
116
|
+
function applyDeclaredRouteOptions(ctor, options, method) {
|
|
117
|
+
if (options.host === void 0) {
|
|
118
|
+
const methodDomain = method === void 0 ? void 0 : Reflect.getMetadata(DOMAIN_METHOD_METADATA, ctor, method);
|
|
119
|
+
const classDomain = Reflect.getMetadata(DOMAIN_CLASS_METADATA, ctor);
|
|
120
|
+
const domain = methodDomain ?? classDomain;
|
|
121
|
+
if (domain) options.host = domain;
|
|
122
|
+
}
|
|
123
|
+
if (options.bypassFirewall !== true) {
|
|
124
|
+
const methodBypass = method === void 0 ? void 0 : Reflect.getMetadata(BYPASS_FIREWALL_METHOD_METADATA, ctor, method);
|
|
125
|
+
const classBypass = Reflect.getMetadata(BYPASS_FIREWALL_CLASS_METADATA, ctor);
|
|
126
|
+
if (methodBypass === true || classBypass === true) options.bypassFirewall = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
100
130
|
* Declaration Controller
|
|
101
131
|
*
|
|
102
132
|
* @param prefix - prefixage du router du controller.
|
|
@@ -124,17 +154,7 @@ function controller(prefix) {
|
|
|
124
154
|
let hasMagic = false;
|
|
125
155
|
for (const [name, options] of Object.entries(metadata)) {
|
|
126
156
|
options.prefix = prefix;
|
|
127
|
-
|
|
128
|
-
const methodDomain = options.classMethod === void 0 ? void 0 : Reflect.getMetadata(DOMAIN_METHOD_METADATA, mycontroller, options.classMethod);
|
|
129
|
-
const classDomain = Reflect.getMetadata(DOMAIN_CLASS_METADATA, mycontroller);
|
|
130
|
-
const domain = methodDomain ?? classDomain;
|
|
131
|
-
if (domain) options.host = domain;
|
|
132
|
-
}
|
|
133
|
-
if (options.bypassFirewall !== true) {
|
|
134
|
-
const methodBypass = options.classMethod === void 0 ? void 0 : Reflect.getMetadata(BYPASS_FIREWALL_METHOD_METADATA, mycontroller, options.classMethod);
|
|
135
|
-
const classBypass = Reflect.getMetadata(BYPASS_FIREWALL_CLASS_METADATA, mycontroller);
|
|
136
|
-
if (methodBypass === true || classBypass === true) options.bypassFirewall = true;
|
|
137
|
-
}
|
|
157
|
+
applyDeclaredRouteOptions(mycontroller, options, options.classMethod);
|
|
138
158
|
if (options.path === "*") {
|
|
139
159
|
hasMagic = {
|
|
140
160
|
options,
|
|
@@ -153,6 +173,72 @@ function controller(prefix) {
|
|
|
153
173
|
function route(name, options) {
|
|
154
174
|
return defineRoute(name, options);
|
|
155
175
|
}
|
|
176
|
+
/** Plus long délai qu'un minuteur Node honore : au-delà, il tombe à 1 ms. */
|
|
177
|
+
const MAX_TIMER_MS = 2147483647;
|
|
178
|
+
/**
|
|
179
|
+
* Valide l'option de route `responseTimeout` À LA DÉCLARATION : un entier de
|
|
180
|
+
* 0 à 2³¹−1. Hors bornes, Node ramènerait le minuteur à 1 ms — chaque requête
|
|
181
|
+
* de la route expirerait aussitôt, sans que rien ne dise pourquoi.
|
|
182
|
+
*
|
|
183
|
+
* @param value - l'option telle que déclarée (absente = celle du serveur).
|
|
184
|
+
* @param className - classe du contrôleur, nommée dans l'erreur.
|
|
185
|
+
* @param method - action, nommée dans l'erreur.
|
|
186
|
+
* @returns la valeur validée, ou `undefined` si l'option est absente.
|
|
187
|
+
* @throws BootConfigurationError qui nomme l'action et la borne.
|
|
188
|
+
*/
|
|
189
|
+
function checkedResponseTimeout(value, className, method) {
|
|
190
|
+
if (value === void 0) return void 0;
|
|
191
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_TIMER_MS) throw new BootConfigurationError(`${className}.${method} : « responseTimeout » entier entre 0 et ${MAX_TIMER_MS} ms attendu (reçu ${String(value)}).`);
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
/** Les bornes d'envoi chiffrées qu'une route peut régler pour elle-même. */
|
|
195
|
+
const UPLOAD_LIMIT_KEYS = /* @__PURE__ */ new Set([
|
|
196
|
+
"maxFileSize",
|
|
197
|
+
"maxTotalFileSize",
|
|
198
|
+
"maxFiles",
|
|
199
|
+
"maxFields",
|
|
200
|
+
"maxFieldsSize"
|
|
201
|
+
]);
|
|
202
|
+
function isUploadLimitKey(key) {
|
|
203
|
+
return UPLOAD_LIMIT_KEYS.has(key);
|
|
204
|
+
}
|
|
205
|
+
/** Les empreintes que le flux d'envoi sait calculer. */
|
|
206
|
+
function isUploadHash(value) {
|
|
207
|
+
return value === false || value === "sha1" || value === "md5" || value === "sha256";
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Valide l'option de route `upload` À LA DÉCLARATION : des entiers ≥ 1 pour
|
|
211
|
+
* les bornes, une empreinte connue, aucune clé inconnue. Une faute de frappe
|
|
212
|
+
* (`maxFilesize`) laisserait sinon la route sur la borne de l'application —
|
|
213
|
+
* 500 Mo par défaut — sans que rien ne le dise.
|
|
214
|
+
*
|
|
215
|
+
* @param value - l'option telle que déclarée (absente = celles de la config).
|
|
216
|
+
* @param bodyStream - la route laisse son corps en flux : ses bornes ne
|
|
217
|
+
* s'appliqueraient jamais, la déclaration est contradictoire.
|
|
218
|
+
* @param className - classe du contrôleur, nommée dans l'erreur.
|
|
219
|
+
* @param method - action, nommée dans l'erreur.
|
|
220
|
+
* @returns les bornes validées, figées, sans clé `undefined` (la fusion sur
|
|
221
|
+
* la configuration ne doit rien écraser par du vide), ou `undefined`.
|
|
222
|
+
* @throws BootConfigurationError qui nomme l'action et la clé fautive.
|
|
223
|
+
*/
|
|
224
|
+
function checkedUpload(value, bodyStream, className, method) {
|
|
225
|
+
if (value === void 0) return void 0;
|
|
226
|
+
const where = `${className}.${method} : « upload »`;
|
|
227
|
+
if (bodyStream) throw new BootConfigurationError(`${where} et « bodyStream: true » s'excluent — un corps laissé en flux n'est jamais lu par le pipeline, ses bornes ne s'appliqueraient pas.`);
|
|
228
|
+
const checked = {};
|
|
229
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
230
|
+
if (entry === void 0) continue;
|
|
231
|
+
if (key === "hashAlgorithm") {
|
|
232
|
+
if (!isUploadHash(entry)) throw new BootConfigurationError(`${where} : « hashAlgorithm » vaut false, "sha1", "md5" ou "sha256" (reçu ${String(entry)}).`);
|
|
233
|
+
checked.hashAlgorithm = entry;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (!isUploadLimitKey(key)) throw new BootConfigurationError(`${where} : clé inconnue « ${key} » — une route règle ${[...UPLOAD_LIMIT_KEYS].join(", ")} et hashAlgorithm ; le dossier et l'encodage restent ceux de la configuration.`);
|
|
237
|
+
if (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 1) throw new BootConfigurationError(`${where} : « ${key} » entier ≥ 1 attendu (reçu ${String(entry)}).`);
|
|
238
|
+
checked[key] = entry;
|
|
239
|
+
}
|
|
240
|
+
return Object.freeze(checked);
|
|
241
|
+
}
|
|
156
242
|
/** L'implémentation de {@link route}, sans les surcharges de typage. */
|
|
157
243
|
function defineRoute(name, options) {
|
|
158
244
|
return function(target, propertyKey, descriptor) {
|
|
@@ -190,7 +276,9 @@ function defineRoute(name, options) {
|
|
|
190
276
|
defaults: options.defaults,
|
|
191
277
|
requirements: options.requirements,
|
|
192
278
|
bypassFirewall: options.bypassFirewall,
|
|
193
|
-
fallback: options.fallback
|
|
279
|
+
fallback: options.fallback,
|
|
280
|
+
responseTimeout: checkedResponseTimeout(options.responseTimeout, className, propertyKey),
|
|
281
|
+
upload: checkedUpload(options.upload, options.bodyStream === true, className, propertyKey)
|
|
194
282
|
};
|
|
195
283
|
Reflect.defineMetadata(metadataKey, metadata, target.constructor);
|
|
196
284
|
return descriptor;
|
|
@@ -1176,4 +1264,4 @@ function computeAuthenticationRequirement(ctor, method) {
|
|
|
1176
1264
|
amr: Object.freeze([.../* @__PURE__ */ new Set([...onClass.amr, ...onMethod.amr])])
|
|
1177
1265
|
});
|
|
1178
1266
|
}
|
|
1179
|
-
export { All, Anonymous, BYPASS_FIREWALL_CLASS_METADATA, BYPASS_FIREWALL_METHOD_METADATA, Body, BypassFirewall, Cookie, Csp, CsrfExempt, CsrfProtect, CurrentUser, DOMAIN_CLASS_METADATA, DOMAIN_METHOD_METADATA, Delete, Domain, Get, HEADERS_METADATA, HTTP_CODE_METADATA, Head, Header, Headers, HttpCode, Idempotent, IsGranted, Options, PARAM_ARGS_METADATA, Param, Patch, Post, Put, Query, REDIRECT_METADATA, Redirect, Req, RequireAuthentication, RequireScope, Res, Scope, Session, USE_SESSION_CLASS_METADATA, USE_SESSION_METHOD_METADATA, UploadedFile, UploadedFiles, UseSession, assertRouteSchemaDeclaration, buildContextParamArgs, buildParamArgs, computeActionMeta, controller, controllers, extractActionScopes, getParamArgsMeta, getRouteSchema, readInvocationBody, readRouteInput, resolveActionMeta, resolveParamArg, resolveSessionIntent, route, routeExpectsBodyStream };
|
|
1267
|
+
export { All, Anonymous, BYPASS_FIREWALL_CLASS_METADATA, BYPASS_FIREWALL_METHOD_METADATA, Body, BypassFirewall, Cookie, Csp, CsrfExempt, CsrfProtect, CurrentUser, DOMAIN_CLASS_METADATA, DOMAIN_METHOD_METADATA, Delete, Domain, Get, HEADERS_METADATA, HTTP_CODE_METADATA, Head, Header, Headers, HttpCode, Idempotent, IsGranted, Options, PARAM_ARGS_METADATA, Param, Patch, Post, Put, Query, REDIRECT_METADATA, Redirect, Req, RequireAuthentication, RequireScope, Res, Scope, Session, USE_SESSION_CLASS_METADATA, USE_SESSION_METHOD_METADATA, UploadedFile, UploadedFiles, UseSession, applyDeclaredRouteOptions, assertRouteSchemaDeclaration, buildContextParamArgs, buildParamArgs, computeActionMeta, controller, controllers, extractActionScopes, getParamArgsMeta, getReservedActionNames, getRouteSchema, readInvocationBody, readRouteInput, resolveActionMeta, resolveParamArg, resolveSessionIntent, route, routeExpectsBodyStream };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import Controller from "../src/Controller.js";
|
|
2
|
+
import { computeActionMeta, getReservedActionNames } from "../decorators/routerDecorators.js";
|
|
3
|
+
import { grpcDeclarationOf } from "./declarations.js";
|
|
4
|
+
import { BootConfigurationError } from "nodefony";
|
|
5
|
+
import { HttpContext } from "@nodefony/http";
|
|
6
|
+
import { GrpcEndpoint, markGrpcRoute } from "@nodefony/http/grpc";
|
|
7
|
+
// nodefony/grpc/GrpcController.ts
|
|
8
|
+
/**
|
|
9
|
+
* Nom de l'action de base, celle que toute route gRPC exécute : elle remet la
|
|
10
|
+
* requête à l'adaptateur. Les gardes, elles, se lisent sur la méthode du
|
|
11
|
+
* contrat.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
const GRPC_ACTION = "serveGrpc";
|
|
16
|
+
/**
|
|
17
|
+
* Ce que chaque décorateur sans effet en gRPC laisse dans la méta d'action —
|
|
18
|
+
* un refus au démarrage le nomme (ADR-0026 D1, point 5 ; S-grpc-18).
|
|
19
|
+
*/
|
|
20
|
+
function inertDecorators(meta) {
|
|
21
|
+
const found = [];
|
|
22
|
+
if (meta.paramsMeta !== null) found.push("@Body/@Param/@Query… (paramètres)");
|
|
23
|
+
if (meta.schema !== null) found.push("schéma de route");
|
|
24
|
+
if (meta.sessionIntent !== null) found.push("@UseSession");
|
|
25
|
+
if (meta.cspDirectives !== null) found.push("@Csp");
|
|
26
|
+
if (meta.idempotent !== null) found.push("@Idempotent");
|
|
27
|
+
if (meta.httpCode !== null) found.push("@HttpCode");
|
|
28
|
+
if (meta.headerEntries !== null) found.push("@Header");
|
|
29
|
+
if (meta.redirectMeta !== null) found.push("@Redirect");
|
|
30
|
+
return found;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Classe de base d'un point d'entrée gRPC (ADR-0026 D1), jumelle de
|
|
34
|
+
* `RealtimeController` : marquée par `@GrpcService(Contrat)`, déclarée par
|
|
35
|
+
* `@controllers([...])`. Chaque méthode du contrat est une méthode de la
|
|
36
|
+
* classe, appelée par l'adaptateur Connect avec `(request, HandlerContext)` ;
|
|
37
|
+
* la logique métier vit dans un service injecté.
|
|
38
|
+
*
|
|
39
|
+
* Singleton par défaut : aucune instance par appel, l'adaptateur lie les
|
|
40
|
+
* méthodes une fois. `@Scope("request")` donne une instance par appel.
|
|
41
|
+
*
|
|
42
|
+
* Ce que le framework décide pour chaque méthode, sans code de ta part :
|
|
43
|
+
* - les gardes (`@IsGranted`, `@RequireAuthentication`…) posées sur la
|
|
44
|
+
* méthode ou la classe s'appliquent ; un refus part en 16
|
|
45
|
+
* `UNAUTHENTICATED` sans identité, en 7 `PERMISSION_DENIED` sinon ;
|
|
46
|
+
* - `this.user`, l'ALS et la portée `request` valent pendant tout l'appel,
|
|
47
|
+
* flux compris ;
|
|
48
|
+
* - une `ConnectError` part telle quelle ; une `HttpError` est traduite par
|
|
49
|
+
* la table de `google/rpc/code.proto` ; toute autre exception devient 13
|
|
50
|
+
* `INTERNAL`. Dans un flux, la traduction ne vaut qu'avant le premier
|
|
51
|
+
* message ;
|
|
52
|
+
* - un flux est interrompu en 14 `UNAVAILABLE` à l'arrêt du serveur et en 16
|
|
53
|
+
* à la révocation de son identité : passer `ctx.signal` à ses sources ;
|
|
54
|
+
* - messages bornés à 4 Mio (section `grpc` de la configuration http).
|
|
55
|
+
*
|
|
56
|
+
* Un décorateur sans effet en gRPC (`@Body`, `@UseSession`, `@Csp`,
|
|
57
|
+
* `@Idempotent`…) sur une méthode du contrat interrompt le démarrage.
|
|
58
|
+
* Guide complet : `docs/grpc.md` du paquet.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* \@GrpcService(GreetService)
|
|
62
|
+
* export class GreetController extends GrpcController {
|
|
63
|
+
* constructor(context: ContextType) {
|
|
64
|
+
* super("GreetController", context);
|
|
65
|
+
* }
|
|
66
|
+
* greet(request: GreetRequest) {
|
|
67
|
+
* return { greeting: `Bonjour, ${request.name} !` };
|
|
68
|
+
* }
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
72
|
+
*/
|
|
73
|
+
var GrpcController = class extends Controller {
|
|
74
|
+
/**
|
|
75
|
+
* Vérifie la déclaration gRPC de la classe et fige ses routes — au
|
|
76
|
+
* démarrage (`Router.setController`), jamais à la première requête.
|
|
77
|
+
*
|
|
78
|
+
* Refuse : une classe sans `@GrpcService` ; une méthode du contrat absente
|
|
79
|
+
* de la classe ou dont le nom est réservé par le framework ; un décorateur
|
|
80
|
+
* sans effet en gRPC sur une méthode du contrat (ou sur la classe) — le
|
|
81
|
+
* refus nomme la méthode et le décorateur.
|
|
82
|
+
*
|
|
83
|
+
* Fige ensuite, sur chaque route, la méta d'action lue sur la MÉTHODE DU
|
|
84
|
+
* CONTRAT (l'action exécutée reste celle de la base) et le corps laissé en
|
|
85
|
+
* flux : les gardes valent quel que soit l'ordre des décorateurs de classe.
|
|
86
|
+
*
|
|
87
|
+
* @throws BootConfigurationError sur une déclaration refusée.
|
|
88
|
+
*/
|
|
89
|
+
static assertScope() {
|
|
90
|
+
const declaration = grpcDeclarationOf(this);
|
|
91
|
+
if (declaration === void 0) throw new BootConfigurationError(`Contrôleur gRPC « ${this.name} » sans @GrpcService(Contrat) : rien ne dit quel service il sert. Poser @GrpcService sur la classe.`);
|
|
92
|
+
const typeName = declaration.service.typeName;
|
|
93
|
+
const reserved = getReservedActionNames();
|
|
94
|
+
const metas = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const method of declaration.service.methods) {
|
|
96
|
+
const name = method.localName;
|
|
97
|
+
const where = `Service gRPC ${typeName} (« ${this.name} »), méthode « ${name} »`;
|
|
98
|
+
const owner = name === "serveGrpc" || name === "grpcEndpoint" ? "GrpcController" : reserved.get(name);
|
|
99
|
+
if (owner !== void 0) throw new BootConfigurationError(`${where} : ce nom est réservé par ${owner}, dont tout contrôleur hérite. Renommer la méthode dans le contrat.`);
|
|
100
|
+
if (typeof Reflect.get(this.prototype, name) !== "function") throw new BootConfigurationError(`${where} : absente de la classe. Chaque méthode du contrat s'implémente sur son contrôleur.`);
|
|
101
|
+
const meta = computeActionMeta(this, name);
|
|
102
|
+
const inert = inertDecorators(meta);
|
|
103
|
+
if (inert.length !== 0) throw new BootConfigurationError(`${where} : ${inert.join(", ")} sans effet sur un appel gRPC (posé sur la méthode ou sur la classe). Les retirer.`);
|
|
104
|
+
metas.set(name, meta);
|
|
105
|
+
}
|
|
106
|
+
for (const { route, method } of declaration.routes) {
|
|
107
|
+
const meta = metas.get(method.localName);
|
|
108
|
+
if (meta === void 0) continue;
|
|
109
|
+
route.actionMeta = {
|
|
110
|
+
...meta,
|
|
111
|
+
bodyStream: true
|
|
112
|
+
};
|
|
113
|
+
route.bodyStream = true;
|
|
114
|
+
markGrpcRoute(route);
|
|
115
|
+
}
|
|
116
|
+
Object.defineProperty(this.prototype, "grpcEndpoint", {
|
|
117
|
+
value: new GrpcEndpoint(declaration.service, { perCall: this.scope !== "singleton" }),
|
|
118
|
+
configurable: true
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* L'action de base de toute route gRPC : remet la requête et la réponse
|
|
123
|
+
* brutes à l'adaptateur, qui appelle la méthode du contrat et écrit la
|
|
124
|
+
* réponse. Synchrone, sauf pour un flux dont la session est à sauver : la
|
|
125
|
+
* promesse de la remise est alors rendue au résolveur, qui en lit l'échec.
|
|
126
|
+
*
|
|
127
|
+
* @throws Error quand la classe n'a pas été enregistrée par le routeur, ou
|
|
128
|
+
* hors d'une requête HTTP.
|
|
129
|
+
*/
|
|
130
|
+
serveGrpc() {
|
|
131
|
+
const endpoint = this.grpcEndpoint;
|
|
132
|
+
if (endpoint === void 0) throw new Error(`Contrôleur gRPC « ${this.constructor.name} » non enregistré : le déclarer par @controllers([...]).`);
|
|
133
|
+
const context = this.context;
|
|
134
|
+
if (!(context instanceof HttpContext)) throw new Error("Un appel gRPC n'est servi que sur une requête HTTP.");
|
|
135
|
+
return endpoint.serve(context, this);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
export { GRPC_ACTION, GrpcController };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import router_default from "../service/router.js";
|
|
2
|
+
import { applyDeclaredRouteOptions } from "../decorators/routerDecorators.js";
|
|
3
|
+
import { GRPC_ACTION } from "./GrpcController.js";
|
|
4
|
+
import { claimGrpcService, declareGrpcService } from "./declarations.js";
|
|
5
|
+
import { BootConfigurationError } from "nodefony";
|
|
6
|
+
import { MethodOptions_IdempotencyLevel } from "@bufbuild/protobuf/wkt";
|
|
7
|
+
// nodefony/grpc/GrpcService.ts
|
|
8
|
+
/**
|
|
9
|
+
* Déclare un contrôleur comme le point d'entrée du service gRPC `service`
|
|
10
|
+
* (le descripteur généré par `buf`, ex. `EchoService` de `echo_pb.ts`).
|
|
11
|
+
*
|
|
12
|
+
* Monte une route littérale du Router par méthode du contrat,
|
|
13
|
+
* `POST /<paquet.Service>/<Méthode>`, plus `GET` pour une méthode déclarée
|
|
14
|
+
* `option idempotency_level = NO_SIDE_EFFECTS` (le GET de Connect). Ces
|
|
15
|
+
* routes passent par tout le pipeline — pare-feu, `@IsGranted`, hôtes
|
|
16
|
+
* virtuels, profileur, liste des routes —, et leurs gardes se lisent sur la
|
|
17
|
+
* méthode du contrat. La classe se déclare ensuite par `@controllers([...])`,
|
|
18
|
+
* comme tout point d'entrée d'un module.
|
|
19
|
+
*
|
|
20
|
+
* Les décorateurs de classe `@Domain` et `@BypassFirewall` se placent SOUS
|
|
21
|
+
* `@GrpcService` (lus au montage des routes, comme sous `@controller`).
|
|
22
|
+
*
|
|
23
|
+
* Le descripteur vient du code généré, jamais écrit à la main : contrat dans
|
|
24
|
+
* `proto/<paquet>/v1/*.proto`, `npx buf generate` vers `nodefony/gen/`, code
|
|
25
|
+
* commité. Les routes répondent sur 5152 (gRPC, gRPC-Web, Connect), 5151
|
|
26
|
+
* (gRPC-Web, Connect : HTTP/1.1, jamais le gRPC natif) et 5153 si h2c est
|
|
27
|
+
* activé. Guide complet : `docs/grpc.md` du paquet.
|
|
28
|
+
*
|
|
29
|
+
* @param service - le descripteur du service.
|
|
30
|
+
* @returns le décorateur de classe.
|
|
31
|
+
* @throws BootConfigurationError quand une autre classe sert déjà ce contrat.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* \@GrpcService(EchoService)
|
|
35
|
+
* export class EchoController extends GrpcController {
|
|
36
|
+
* constructor(context: ContextType) {
|
|
37
|
+
* super("EchoController", context);
|
|
38
|
+
* }
|
|
39
|
+
* \@IsGranted("ROLE_ECHO")
|
|
40
|
+
* say(request: SayRequest) {
|
|
41
|
+
* return { sentence: request.sentence };
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
46
|
+
*/
|
|
47
|
+
function GrpcService(service) {
|
|
48
|
+
return function(ctor) {
|
|
49
|
+
const owner = claimGrpcService(service.typeName, ctor.name);
|
|
50
|
+
if (owner !== null) throw new BootConfigurationError(`@GrpcService(${service.typeName}) sur « ${ctor.name} » : ce service est déjà servi par « ${owner} ». Un contrat n'a qu'un point d'entrée.`);
|
|
51
|
+
const routes = [];
|
|
52
|
+
for (const method of service.methods) {
|
|
53
|
+
const options = {
|
|
54
|
+
prefix: `/${service.typeName}`,
|
|
55
|
+
path: `/${method.name}`,
|
|
56
|
+
constructor: ctor,
|
|
57
|
+
className: ctor.name,
|
|
58
|
+
classMethod: GRPC_ACTION,
|
|
59
|
+
method: method.idempotency === MethodOptions_IdempotencyLevel.NO_SIDE_EFFECTS ? ["POST", "GET"] : "POST"
|
|
60
|
+
};
|
|
61
|
+
applyDeclaredRouteOptions(ctor, options, method.localName);
|
|
62
|
+
const route = router_default.createRoute(`${ctor.name}::${method.localName}`, options);
|
|
63
|
+
routes.push({
|
|
64
|
+
route,
|
|
65
|
+
method
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
declareGrpcService(ctor, {
|
|
69
|
+
service,
|
|
70
|
+
routes
|
|
71
|
+
});
|
|
72
|
+
return ctor;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export { GrpcService };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// nodefony/grpc/declarations.ts
|
|
2
|
+
/**
|
|
3
|
+
* Déclarations par classe contrôleur. Écrite à l'import (`@GrpcService`), lue
|
|
4
|
+
* une fois au démarrage (`GrpcController.assertScope`) : jamais sur le chemin
|
|
5
|
+
* d'un appel.
|
|
6
|
+
*/
|
|
7
|
+
const declarations = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
/**
|
|
9
|
+
* Contrat déjà servi, par nom complet : deux classes qui serviraient le même
|
|
10
|
+
* service monteraient deux fois les mêmes chemins.
|
|
11
|
+
*/
|
|
12
|
+
const servedBy = /* @__PURE__ */ new Map();
|
|
13
|
+
/** @internal */
|
|
14
|
+
function declareGrpcService(ctor, declaration) {
|
|
15
|
+
declarations.set(ctor, declaration);
|
|
16
|
+
}
|
|
17
|
+
/** @internal */
|
|
18
|
+
function grpcDeclarationOf(ctor) {
|
|
19
|
+
return declarations.get(ctor);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Retient quelle classe sert un contrat.
|
|
23
|
+
*
|
|
24
|
+
* @returns le nom de la classe qui le servait déjà, ou `null`.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
function claimGrpcService(typeName, className) {
|
|
28
|
+
const owner = servedBy.get(typeName);
|
|
29
|
+
if (owner !== void 0) return owner;
|
|
30
|
+
servedBy.set(typeName, className);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export { claimGrpcService, declareGrpcService, grpcDeclarationOf };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import router_default from "./router.js";
|
|
2
2
|
import AdminApiController from "../controller/AdminApiController.js";
|
|
3
|
-
import __decorate from "../../_virtual/_@oxc-project_runtime@0.
|
|
3
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
4
4
|
import { ADMIN_DEFAULT_ROLE, Service, injectable, resolveAdminRole } from "nodefony";
|
|
5
5
|
// nodefony/service/AdminBroker.ts
|
|
6
6
|
const serviceName = "adminBroker";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import __decorate from "../../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
2
2
|
import { Service, assertPageQuery, injectable } from "nodefony";
|
|
3
3
|
// nodefony/service/IdempotencyStore.ts
|
|
4
4
|
const serviceName = "idempotencyStore";
|