@cleverbrush/server 4.4.0 → 4.4.2

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 CHANGED
@@ -97,6 +97,12 @@ server.handle(GetUser, ({ params }) => {
97
97
  });
98
98
  ```
99
99
 
100
+ When several route schemas validate the same URL, the server selects the most
101
+ specific route independently of registration order. Exact static routes win,
102
+ followed by routes with more literal path segments and then fewer dynamic
103
+ segments. Registration order only breaks ties between equally specific routes.
104
+ The same precedence applies to WebSocket subscriptions.
105
+
100
106
  ### Authorization
101
107
 
102
108
  ```ts
package/dist/Router.d.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import type { EndpointRegistration, RouteMatch, SubscriptionRegistration } from './types.js';
2
2
  /**
3
- * Radix-style HTTP router that maps method + path to endpoint registrations.
3
+ * HTTP and WebSocket router that maps paths to endpoint registrations.
4
4
  *
5
5
  * Both static string paths (exact-match only) and `ParseStringSchemaBuilder`
6
6
  * typed path templates are supported. For dynamic path parameters use
7
7
  * `route()` / `parseString()` templates rather than colon-param strings.
8
+ * When multiple templates validate the same URL, static routes win, followed
9
+ * by routes with more literal segments and then fewer dynamic segments.
10
+ * Registration order breaks equal-specificity ties.
8
11
  */
9
12
  export declare class Router {
10
13
  #private;
@@ -12,6 +15,14 @@ export declare class Router {
12
15
  * Register an endpoint with the router.
13
16
  */
14
17
  addRoute(registration: EndpointRegistration): void;
18
+ /**
19
+ * Prepare all dynamic route collections for request-time matching.
20
+ *
21
+ * Sorting is stable, so registration order remains the tie-breaker for
22
+ * routes with equal specificity. The method is idempotent and is also
23
+ * called lazily by match operations if routes have changed.
24
+ */
25
+ finalize(): void;
15
26
  /**
16
27
  * Match an incoming HTTP method and URL to a registered endpoint.
17
28
  *
@@ -22,6 +33,9 @@ export declare class Router {
22
33
  * - `{ match: null, methodNotAllowed: false }` — no match at all (404).
23
34
  * - `{ match: null, methodNotAllowed: false, badRequest: true }` — the URL
24
35
  * contains malformed percent-encoding (caller should respond with 400).
36
+ *
37
+ * Exact paths are indexed directly. Dynamic candidates are ordered by
38
+ * specificity during finalization and evaluated until the first match.
25
39
  */
26
40
  match(method: string, url: string): {
27
41
  match: RouteMatch | null;
@@ -36,7 +50,8 @@ export declare class Router {
36
50
  /**
37
51
  * Match an incoming WebSocket upgrade URL to a registered subscription.
38
52
  *
39
- * Returns the matched registration and parsed path params, or `null`.
53
+ * Returns the most specific matched registration and parsed path params,
54
+ * or `null`. Registration order breaks equal-specificity ties.
40
55
  */
41
56
  matchSubscription(url: string): {
42
57
  registration: SubscriptionRegistration;
@@ -1,2 +1,2 @@
1
1
  import{object as P,SYMBOL_HAS_PROPERTIES as B,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as R}from"@cleverbrush/schema";function b(n){let e={};if(n.paramsSchema){let a=n.paramsSchema;typeof a.introspect=="function"&&a.introspect().objectSchema?e.params=a.introspect().objectSchema:a[B]===!0&&(e.params=a)}if(n.bodySchema){let a=n.bodySchema;(a[B]===!0||typeof a.introspect=="function")&&(e.body=a)}n.querySchema&&(e.query=n.querySchema),n.headerSchema&&(e.headers=n.headerSchema);let t=P(e);return P.getPropertiesFor(t)}function F(n){return n==null||typeof n!="object"?!1:typeof n[R]=="object"&&n[R]!==null}function x(n,e){let t={};for(let[a,s]of Object.entries(e)){if(!F(s))throw new Error(`Cache tag "${n}": property "${a}" is not a valid PropertyDescriptor. Make sure you select a leaf property from the tree (e.g. p.query.id, not p.query).`);let r=s[R];t[a]={getValue:i=>r.getValue(i)}}return{name:n,properties:t}}function J(n,e){let t=Object.entries(n.properties);if(t.length===0)return n.name;let a=[];for(let[s,r]of t.sort(([i],[y])=>i.localeCompare(y))){let i=r.getValue(e);i.success&&i.value!==void 0&&a.push(`${s}=${String(i.value)}`)}return a.length===0?n.name:`${n.name}:${a.join(",")}`}var f=Symbol.for("cleverbrush.tracked");function q(n,e){return{[f]:!0,id:n,data:e}}function X(n){return n!==null&&typeof n=="object"&&f in n&&n[f]===!0}var u=class n{#s;#a;#n;#i;#r;#o;#l;#t;#d;#h;#c;#u;#T;#y;constructor(e,t="/",a=null,s=null,r=null,i=null,y=null,d=null,l=null,h=null,c=[],m=null,S=!1,g=null){this.#s=e,this.#a=t,this.#n=a,this.#i=s,this.#r=r,this.#o=i,this.#l=y,this.#t=d,this.#d=l,this.#h=h,this.#c=c,this.#u=m,this.#T=S,this.#y=g}#e(e){return new n(e.basePath??this.#s,e.pathTemplate??this.#a,e.incomingSchema!==void 0?e.incomingSchema:this.#n,e.outgoingSchema!==void 0?e.outgoingSchema:this.#i,e.querySchema!==void 0?e.querySchema:this.#r,e.headerSchema!==void 0?e.headerSchema:this.#o,e.serviceSchemas!==void 0?e.serviceSchemas:this.#l,e.authRoles!==void 0?e.authRoles:this.#t,e.summary!==void 0?e.summary:this.#d,e.description!==void 0?e.description:this.#h,e.tags??this.#c,e.operationId!==void 0?e.operationId:this.#u,e.deprecated??this.#T,e.externalDocs!==void 0?e.externalDocs:this.#y)}incoming(e){return this.#e({incomingSchema:e})}outgoing(e){return this.#e({outgoingSchema:e})}query(e){return this.#e({querySchema:e})}headers(e){return this.#e({headerSchema:e})}inject(e){return this.#e({serviceSchemas:e})}authorize(...e){let t;e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?t=e.slice(1):t=e;let a=this.#t?[...this.#t,...t]:t;return this.#e({authRoles:a})}public(){return this.#e({authRoles:null})}summary(e){return this.#e({summary:e})}description(e){return this.#e({description:e})}tags(...e){return this.#e({tags:e})}operationId(e){return this.#e({operationId:e})}deprecated(){return this.#e({deprecated:!0})}externalDocs(e,t){return this.#e({externalDocs:{url:e,description:t}})}introspect(){return{protocol:"subscription",basePath:this.#s,pathTemplate:this.#a,incomingSchema:this.#n,outgoingSchema:this.#i,querySchema:this.#r,headerSchema:this.#o,serviceSchemas:this.#l,authRoles:this.#t,summary:this.#d,description:this.#h,tags:this.#c,operationId:this.#u,deprecated:this.#T,externalDocs:this.#y}}};function E(n,e){return new u(n,e??"/")}function k(n){return n instanceof u}function ne(n,e){let t=[],a=[];for(let s of Object.keys(n)){let r=n[s],i=e[s];for(let y of Object.keys(r)){let d=r[y],l=i[y],h=typeof l=="function"?l:l.handler,c=typeof l=="function"?void 0:l.middlewares;k(d)?a.push({endpoint:d,handler:h,middlewares:c}):t.push({endpoint:d,handler:h,middlewares:c})}}return{_entries:t,_subscriptions:a}}var p=class n{#s;#a;#n;#i;#r;#o;#l;#t;#d;#h;#c;#u;#T;#y;#e;#m;#S;#g;#R;#f;#P;#B;#b;#x;#p;constructor(e,t,a,s,r,i,y=null,d=null,l=null,h=null,c=[],m=null,S=!1,g=null,w=null,j=null,C=null,v=null,A=null,I=null,Q=null,D=null,K=null,M=null,U=[]){this.#s=e,this.#a=t,this.#n=a,this.#i=s,this.#r=r,this.#o=i,this.#l=y,this.#t=d,this.#d=l,this.#h=h,this.#c=c,this.#u=m,this.#T=S,this.#y=g,this.#e=w,this.#m=j,this.#S=C,this.#g=v,this.#R=A,this.#f=I,this.#P=Q,this.#B=D,this.#b=K,this.#x=M,this.#p=U}body(e){return new n(this.#s,this.#a,this.#n,e,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}query(e){return new n(this.#s,this.#a,this.#n,this.#i,e,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}headers(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,e,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}inject(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,e,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}authorize(...e){let t;e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?t=e.slice(1):t=e;let a=this.#t?[...this.#t,...t]:t;return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,a,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}public(){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,null,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}returns(e){let t=e!=null&&typeof e=="object"&&"introspect"in e?e:null;return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,t??this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}responses(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}summary(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,e,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}description(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,e,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}tags(...e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,e,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}operationId(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,e,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}deprecated(){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,!0,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}example(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,e,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}examples(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,e,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}producesFile(e,t){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,{contentType:e,description:t},this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}upload(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,{maxFileSize:e?.maxFileSize??10*1024*1024,allowedMimeTypes:e?.allowedMimeTypes,maxFileCount:e?.maxFileCount??10},this.#p)}get path(){let e=this.#a,t=this.#n,a;if(typeof t=="string")a=t;else{let{literals:s,segments:r}=t.introspect().templateDefinition,i="";for(let y=0;y<r.length;y++)i+=s[y]+`:${r[y].path}`;i+=s[r.length]??"",a=i}return a==="/"?e||"/":e+a}introspect(){return{method:this.#s,basePath:this.#a,pathTemplate:this.#n,bodySchema:this.#i,querySchema:this.#r,headerSchema:this.#o,serviceSchemas:this.#l,authRoles:this.#t,summary:this.#d,description:this.#h,tags:this.#c,operationId:this.#u,deprecated:this.#T,responseSchema:this.#y,responsesSchemas:this.#e,example:this.#m,examples:this.#S,producesFile:this.#g,produces:this.#R,responseHeaderSchema:this.#f,externalDocs:this.#P,links:this.#B,callbacks:this.#b,fileUpload:this.#x,cacheTags:this.#p}}produces(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,e,this.#f,this.#P,this.#B,this.#b,this.#x,this.#p)}responseHeaders(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,e,this.#P,this.#B,this.#b,this.#x,this.#p)}externalDocs(e,t){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,{url:e,description:t},this.#B,this.#b,this.#x,this.#p)}links(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,e,this.#b,this.#x,this.#p)}callbacks(e){return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,e,this.#x,this.#p)}cacheTag(e,t){return this.clearsCacheTag(e,t)}clearsCacheTag(e,t){if(!t)return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,[...this.#p,{name:e,properties:{}}]);let a=G(this.#n),s=b({paramsSchema:a,bodySchema:this.#i,querySchema:this.#r,headerSchema:this.#o}),r=t(s);if(typeof r!="object"||r===null)throw new Error(`Cache tag "${e}": selector must return an object with property descriptors (e.g. { id: p.query.id }).`);let i=x(e,r);return new n(this.#s,this.#a,this.#n,this.#i,this.#r,this.#o,this.#l,this.#t,this.#d,this.#h,this.#c,this.#u,this.#T,this.#y,this.#e,this.#m,this.#S,this.#g,this.#R,this.#f,this.#P,this.#B,this.#b,this.#x,[...this.#p,i])}};function o(n,e,t,a,s){return new p(n,e,t??"/",null,null,null,null,a??null,s?.summary??null,s?.description??null,s?.tags??[],s?.operationId??null,s?.deprecated??!1,null,null,null,null,null,null,null)}function T(n,e){return{get:t=>o("GET",n,t,e),post:t=>o("POST",n,t,e),put:t=>o("PUT",n,t,e),patch:t=>o("PATCH",n,t,e),delete:t=>o("DELETE",n,t,e),head:t=>o("HEAD",n,t,e),options:t=>o("OPTIONS",n,t,e),public:()=>T(n,null)}}function z(n){return{...T(n,null),authorize(...e){let t;return e.length>0&&typeof e[0]=="object"&&e[0]!==null&&"introspect"in e[0]?t=e.slice(1):t=e,T(n,t)},public(){return T(n,null)}}}function G(n){if(n&&typeof n!="string"&&typeof n.introspect=="function"){let e=n.introspect();if(e.objectSchema)return e.objectSchema}return null}function L(n){return H}var H={get:(n,e)=>o("GET",n,e),post:(n,e)=>o("POST",n,e),put:(n,e)=>o("PUT",n,e),patch:(n,e)=>o("PATCH",n,e),delete:(n,e)=>o("DELETE",n,e),head:(n,e)=>o("HEAD",n,e),options:(n,e)=>o("OPTIONS",n,e),resource:z,subscription:(n,e)=>E(n,e)};import{object as Y,parseString as $}from"@cleverbrush/schema";function O(n){let e=Y(n);return((t,...a)=>$(e,s=>s(t,...a)))}function V(n,...e){return n!=null&&Array.isArray(n.raw)?O({})(n):O(n??{})}function ie(n){for(let e of Object.values(n))Object.freeze(e);return Object.freeze(n)}function re(n,e){let t={};for(let a of Object.keys(n))t[a]={...n[a]};for(let a of Object.keys(e))Object.hasOwn(t,a)?t[a]={...t[a],...e[a]}:t[a]={...e[a]};for(let a of Object.values(t))Object.freeze(a);return Object.freeze(t)}function oe(n,...e){let t={};for(let a of e)t[a]=n[a],Object.freeze(t[a]);return Object.freeze(t)}function ye(n,...e){let t=new Set(e),a={};for(let s of Object.keys(n))t.has(s)||(a[s]=n[s],Object.freeze(a[s]));return Object.freeze(a)}export{b as a,x as b,J as c,q as d,X as e,u as f,ne as g,p as h,L as i,H as j,V as k,ie as l,re as m,oe as n,ye as o};
2
- //# sourceMappingURL=chunk-LWRLB46M.js.map
2
+ //# sourceMappingURL=chunk-PLSVKHPB.js.map