nucleus-core-ts 0.10.8 → 0.10.10
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/.build-ok +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -1
- package/dist/src/Server/testApp.d.ts +28 -2
- package/package.json +1 -1
package/dist/.build-ok
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.10.
|
|
1
|
+
0.10.10
|
package/dist/server.d.ts
CHANGED
|
@@ -30,5 +30,6 @@ export { serve } from './src/Server/NucleusServer';
|
|
|
30
30
|
export { buildOpenApiDocument } from './src/Server/openapi';
|
|
31
31
|
export { collectRoutes, type RouteCollector } from './src/Server/routeCollector';
|
|
32
32
|
export type { AnyRoute, Guard, RouteGroup } from './src/Server/router';
|
|
33
|
+
export { NucleusTestApp } from './src/Server/testApp';
|
|
33
34
|
export { defineWebSocketRoute, type WebSocketRoute } from './src/Server/websocket';
|
|
34
35
|
export { decodeHeaderList } from './src/Utils/headerList';
|
package/dist/server.js
CHANGED
|
@@ -606,4 +606,4 @@ ${content}
|
|
|
606
606
|
<script id="api-reference" data-url="${forHtml(documentUrl)}"></script>
|
|
607
607
|
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
608
608
|
</body>
|
|
609
|
-
</html>`}function chainGuards(...guards){return async(context)=>{for(let guard5 of guards){let early=await guard5(context);if(early!==void 0)return early}return}}function docsGroup(groups,config){let swagger=config.swagger;if(swagger?.enabled===!1)return null;let base=swagger?.path??"/swagger",info=swagger?.documentation?.info,title=info?.title??"Nucleus API",description=info?.description??"Auto-generated API documentation",document=buildOpenApiDocument(groups,swagger),page=viewerHtml(`${base}/json`,title,description);return{prefix:base,routes:[defineRoute({method:"GET",path:"/",detail:{summary:"API documentation",tags:["Documentation"]},handler:()=>new Response(page,{headers:{"content-type":"text/html; charset=utf8"}})}),defineRoute({method:"GET",path:"/json",detail:{summary:"OpenAPI document",tags:["Documentation"]},handler:()=>document})]}}async function createNucleusServer(options){await buildNucleus(options.config);let groups=getMountedRouteGroups();if(groups.length===0)throw Error("createNucleusServer: the plugin mounted no route groups. That is a wiring "+"fault, not an empty install \u2014 refusing to serve rather than answering 404 to everything.");let resolved=getResolvedOptions2(),rawOptions=options.config.options,securityOf=(candidate)=>typeof candidate==="object"&&candidate!==null?candidate.security?.maxRequestBodyBytes:void 0,maxRequestBodyBytes=securityOf(resolved)??securityOf(rawOptions),storageCeiling=requiredBodyCeiling({storageEnabled:resolved?.storage?.enabled,maxFileSizeBytes:resolved?.storage?.maxFileSizeBytes,maxFiles:resolved?.storage?.formData?.maxFiles}),bunBodyCeiling=bunBodyCeilingFor({storageRequired:storageCeiling,guardCeiling:maxRequestBodyBytes});for(let step of getBootSteps())await step({config:{serve:{maxRequestBodySize:bunBodyCeiling}}});let hostRoutes=options.routes??[],resolvedSwagger=resolved?.swagger,outerSwagger=options.config.swagger,docs=docsGroup([...groups,...hostRoutes],{swagger:outerSwagger??resolvedSwagger});return serve({groups:[...groups,...docs?[docs]:[],...hostRoutes],maxRequestBodyBytes:bunBodyCeiling,onStop:[...getShutdownSteps(),...options.onStop??[]],socket:options.socket??getMountedSocket()??void 0,staticAssets:(()=>{let directory=getMountedStaticAssets();return directory?{prefix:"/nucleus-core",directory}:void 0})(),onRequest:chainGuards(...options.onRequest?[options.onRequest]:[],createInboundGuard({maxRequestBodyBytes}),getAuthMiddleware()),onError:getErrorHandler(),onAfter:getAfterHandler()??void 0,context:(()=>{let runtime=getMountedRuntime2();return runtime?{nucleus:runtime}:void 0})(),port:options.port,hostname:options.hostname})}init_routeCollector();init_websocket();export{serve,defineWebSocketRoute,defineRoute,decodeHeaderList,createNucleusServer,collectRoutes,buildOpenApiDocument,buildNucleus};
|
|
609
|
+
</html>`}function chainGuards(...guards){return async(context)=>{for(let guard5 of guards){let early=await guard5(context);if(early!==void 0)return early}return}}function docsGroup(groups,config){let swagger=config.swagger;if(swagger?.enabled===!1)return null;let base=swagger?.path??"/swagger",info=swagger?.documentation?.info,title=info?.title??"Nucleus API",description=info?.description??"Auto-generated API documentation",document=buildOpenApiDocument(groups,swagger),page=viewerHtml(`${base}/json`,title,description);return{prefix:base,routes:[defineRoute({method:"GET",path:"/",detail:{summary:"API documentation",tags:["Documentation"]},handler:()=>new Response(page,{headers:{"content-type":"text/html; charset=utf8"}})}),defineRoute({method:"GET",path:"/json",detail:{summary:"OpenAPI document",tags:["Documentation"]},handler:()=>document})]}}async function createNucleusServer(options){await buildNucleus(options.config);let groups=getMountedRouteGroups();if(groups.length===0)throw Error("createNucleusServer: the plugin mounted no route groups. That is a wiring "+"fault, not an empty install \u2014 refusing to serve rather than answering 404 to everything.");let resolved=getResolvedOptions2(),rawOptions=options.config.options,securityOf=(candidate)=>typeof candidate==="object"&&candidate!==null?candidate.security?.maxRequestBodyBytes:void 0,maxRequestBodyBytes=securityOf(resolved)??securityOf(rawOptions),storageCeiling=requiredBodyCeiling({storageEnabled:resolved?.storage?.enabled,maxFileSizeBytes:resolved?.storage?.maxFileSizeBytes,maxFiles:resolved?.storage?.formData?.maxFiles}),bunBodyCeiling=bunBodyCeilingFor({storageRequired:storageCeiling,guardCeiling:maxRequestBodyBytes});for(let step of getBootSteps())await step({config:{serve:{maxRequestBodySize:bunBodyCeiling}}});let hostRoutes=options.routes??[],resolvedSwagger=resolved?.swagger,outerSwagger=options.config.swagger,docs=docsGroup([...groups,...hostRoutes],{swagger:outerSwagger??resolvedSwagger});return serve({groups:[...groups,...docs?[docs]:[],...hostRoutes],maxRequestBodyBytes:bunBodyCeiling,onStop:[...getShutdownSteps(),...options.onStop??[]],socket:options.socket??getMountedSocket()??void 0,staticAssets:(()=>{let directory=getMountedStaticAssets();return directory?{prefix:"/nucleus-core",directory}:void 0})(),onRequest:chainGuards(...options.onRequest?[options.onRequest]:[],createInboundGuard({maxRequestBodyBytes}),getAuthMiddleware()),onError:getErrorHandler(),onAfter:getAfterHandler()??void 0,context:(()=>{let runtime=getMountedRuntime2();return runtime?{nucleus:runtime}:void 0})(),port:options.port,hostname:options.hostname})}init_routeCollector();init_response();function compile(pattern2){let names2=[],source=pattern2.split("/").map((segment)=>{if(segment.startsWith(":"))return names2.push(segment.slice(1)),"([^/]+)";if(segment==="*")return names2.push("*"),"(.*)";return segment.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}).join("/");return{regex:new RegExp(`^${source}/?$`),names:names2}}var DEFAULT_ERROR_RESPONDER=({set:set2,error:error3})=>{let carried=error3?.status,status=typeof carried==="number"&&carried>=100&&carried<=599?carried:500;set2.status=status;let message=status<500?String(error3?.message??""):"Internal Server Error";return Response.json(withEnvelopeFlags({isSuccess:!1,message,status,errors:[{message}],data:null}))};class NucleusTestApp{mountConfig;recorded=[];decorations={};sockets=[];globalGuard;beforeHandlers=[];listening=null;errorResponder;afterResponder;table=null;constructor(mountConfig={}){this.mountConfig=mountConfig;for(let method of["get","post","put","patch","delete","head","options"]){let register=(path8,handler,schema3)=>{return this.add(method.toUpperCase(),path8,handler,schema3),this};this[method]=register}}add(method,path8,handler,schema3={}){let prefix=this.mountConfig.prefix??"",joined=path8==="/"?prefix||"/":`${prefix}${path8}`;this.recorded.push({method,path:joined,route:{method,path:joined,handler,body:schema3.body,query:schema3.query,params:schema3.params,detail:schema3.detail,type:schema3.type}}),this.table=null}ws(path8,definition){return this.sockets.push({path:path8,...definition}),this}use(plugin){if(plugin instanceof NucleusTestApp){for(let one of plugin.recorded)this.recorded.push(one);for(let socket of plugin.sockets)this.sockets.push(socket);return this.table=null,this}let group=plugin;if(group&&Array.isArray(group.routes)){let prefix=typeof group.prefix==="string"?group.prefix:"";for(let route of group.routes){let declared=route.path==="/"?"":route.path,path8=`${prefix}${declared}`||"/";this.recorded.push({method:route.method,path:path8,route:{...route,path:path8}})}this.table=null}return this}onRequest(guard5){return this.globalGuard=guard5,this.table=null,this}onError(responder){return this.errorResponder=responder,this.table=null,this}onAfterHandle(responder){return this.afterResponder=responder,this.table=null,this}onBeforeHandle(guard5){return this.beforeHandlers.push(guard5),this.table=null,this}decorate(key2,value2){let entries=typeof key2==="string"?{[key2]:value2}:key2;return Object.assign(this,entries),Object.assign(this.decorations,entries),this.table=null,this}listen(port=0){return this.listening=serve({groups:[{routes:this.recorded.map((one)=>one.route)}],socket:this.sockets[0],port:Number(port)||0,reusePort:!1,onRequest:this.globalGuard,onError:this.errorResponder??DEFAULT_ERROR_RESPONDER,onAfter:this.afterResponder}),this}get server(){return this.listening??void 0}stop(closeActiveConnections){return this.listening?.stop(closeActiveConnections)}get socketRoutes(){return this.sockets}get routes(){return this.recorded.map((one)=>`${one.method} ${one.path}`)}build(){if(this.table)return this.table;let groups=[{guards:this.beforeHandlers,routes:this.recorded.map((one)=>one.route)}];return this.table=buildRoutes(groups,{context:this.decorations,onRequest:this.globalGuard,onError:this.errorResponder??DEFAULT_ERROR_RESPONDER,onAfter:this.afterResponder}),this.table}async handle(request){let table=this.build(),{pathname}=new URL(request.url),method=request.method.toUpperCase(),exact=table[pathname]??table[pathname.replace(/\/$/,"")];if(exact?.[method])return exact[method](request);for(let[pattern2,byMethod]of Object.entries(table)){if(!byMethod[method])continue;if(!pattern2.includes(":")&&!pattern2.includes("*"))continue;let{regex,names:names2}=compile(pattern2),matched=regex.exec(pathname);if(!matched)continue;let params={};return names2.forEach((name2,index)=>{params[name2]=decodeURIComponent(matched[index+1]??"")}),request.params=params,byMethod[method](request)}return new Response("NOT_FOUND",{status:404})}}init_websocket();export{serve,defineWebSocketRoute,defineRoute,decodeHeaderList,createNucleusServer,collectRoutes,buildOpenApiDocument,buildNucleus,NucleusTestApp};
|
|
@@ -25,6 +25,8 @@ type Handler = (context: any) => unknown;
|
|
|
25
25
|
export declare class NucleusTestApp {
|
|
26
26
|
private readonly mountConfig;
|
|
27
27
|
private readonly recorded;
|
|
28
|
+
/** What `.decorate()` put on every handler's context. */
|
|
29
|
+
private readonly decorations;
|
|
28
30
|
private readonly sockets;
|
|
29
31
|
private globalGuard;
|
|
30
32
|
private beforeHandlers;
|
|
@@ -41,7 +43,20 @@ export declare class NucleusTestApp {
|
|
|
41
43
|
private add;
|
|
42
44
|
/** Elysia's `.ws`. Recorded so socket wiring can be asserted, not served. */
|
|
43
45
|
ws(path: string, definition: unknown): this;
|
|
44
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Merges another instance's routes, or mounts a `RouteGroup` directly.
|
|
48
|
+
*
|
|
49
|
+
* The group form is what a converted consumer needs. Their factories used to
|
|
50
|
+
* return an Elysia app and now return `{ prefix, routes }`, so a test that
|
|
51
|
+
* said `new Elysia().use(createTheirRoutes())` has nothing to hand this
|
|
52
|
+
* class — it only ever merged its own kind. Found while moving a real
|
|
53
|
+
* delivery: twelve route files and the entry point converted cleanly, and
|
|
54
|
+
* six test files had no way to mount what the factories now return.
|
|
55
|
+
*
|
|
56
|
+
* Anything else is still accepted and ignored, because the Elysia shape this
|
|
57
|
+
* mimics took plugins of every kind and a test that passes one should not
|
|
58
|
+
* start throwing.
|
|
59
|
+
*/
|
|
45
60
|
use(plugin: unknown): this;
|
|
46
61
|
onRequest(guard: Handler): this;
|
|
47
62
|
onError(responder: Handler): this;
|
|
@@ -56,7 +71,18 @@ export declare class NucleusTestApp {
|
|
|
56
71
|
* fail both refuses here with the guard's answer rather than a 422.
|
|
57
72
|
*/
|
|
58
73
|
onBeforeHandle(guard: Handler): this;
|
|
59
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Elysia's `.decorate`, including the half that matters to a handler.
|
|
76
|
+
*
|
|
77
|
+
* This used to assign onto the INSTANCE only, so a factory could read it
|
|
78
|
+
* back — and `ctx.nucleus` stayed undefined inside the handler, which is
|
|
79
|
+
* where Elysia puts it. A converted consumer test that decorated `nucleus`
|
|
80
|
+
* and called an endpoint got `undefined is not an object (evaluating
|
|
81
|
+
* 'nucleus.logger')`: the decoration was accepted, kept, and never delivered.
|
|
82
|
+
*
|
|
83
|
+
* Both now: on the instance for whoever reads it back, and on every handler's
|
|
84
|
+
* context, which is what `.decorate` is for.
|
|
85
|
+
*/
|
|
60
86
|
decorate(key: string | Record<string, unknown>, value?: unknown): this;
|
|
61
87
|
/** Binds a port for real, because a test that reads `.server.port` needs one. */
|
|
62
88
|
listen(port?: number | string): this;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.10",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|