@@ -575,6 +575,178 @@ export function buildToilDbTypes(program: Program): Uint8Array | null {
575575 return w . toBytes ( ) ;
576576}
577577
578+ class RouteKindEntry {
579+ method : i32 = 0 ;
580+ kind : i32 = 0 ;
581+ path : string = "" ;
582+ }
583+
584+ class RouteInfo {
585+ method : i32 = - 1 ;
586+ path : string = "" ;
587+ }
588+
589+ /** Build the `toildb.route_kinds` section bytes, or `null` when no route needs
590+ * an extra runtime DB-policy clamp. The edge already derives the default kind
591+ * from the trusted HTTP method. This section only carries the stricter source
592+ * signal that the method clamp cannot infer: mutating-method routes explicitly
593+ * declared `@query`.
594+ *
595+ * Wire format (LE):
596+ * u16 format_version = 1
597+ * u16 n_routes
598+ * per route:
599+ * u8 method (same values as runtime Methods / request envelope)
600+ * u8 function_kind (0 = Query)
601+ * str route_pattern (same normalized pattern emitted into __toilMatch)
602+ */
603+ export function buildToilDbRouteKinds ( program : Program ) : Uint8Array | null {
604+ let routes = new Array < RouteKindEntry > ( ) ;
605+ let sources = program . sources ;
606+ for ( let i = 0 , k = sources . length ; i < k ; ++ i ) {
607+ let source = sources [ i ] ;
608+ if ( source . isLibrary ) continue ;
609+ let statements = source . statements ;
610+ for ( let j = 0 , l = statements . length ; j < l ; ++ j ) {
611+ let statement = statements [ j ] ;
612+ if ( statement . kind != NodeKind . ClassDeclaration ) continue ;
613+ let cls = < ClassDeclaration > statement ;
614+ let rest = decoOf ( cls . decorators , DecoratorKind . Rest ) ;
615+ if ( rest == null ) continue ;
616+ let prefix = restPrefixOf ( rest ) ;
617+ let members = cls . members ;
618+ for ( let m = 0 , mn = members . length ; m < mn ; ++ m ) {
619+ let member = members [ m ] ;
620+ if ( member . kind != NodeKind . MethodDeclaration ) continue ;
621+ let method = < MethodDeclaration > member ;
622+ let routeDeco = routeDecoratorOf ( method ) ;
623+ if ( routeDeco == null ) continue ;
624+ let info = routeInfoOf ( routeDeco ) ;
625+ if ( info == null || isSafeMethod ( info . method ) ) continue ;
626+ if ( explicitRequestKind ( method . decorators ) != 0 ) continue ;
627+ let entry = new RouteKindEntry ( ) ;
628+ entry . method = info . method ;
629+ entry . kind = 0 ; // FunctionKind::Query
630+ entry . path = joinRoutePath ( prefix , info . path ) ;
631+ routes . push ( entry ) ;
632+ }
633+ }
634+ }
635+ if ( routes . length == 0 ) return null ;
636+ let w = new CatWriter ( ) ;
637+ w . u16 ( 1 ) ;
638+ w . u16 ( routes . length ) ;
639+ for ( let i = 0 , k = routes . length ; i < k ; ++ i ) {
640+ w . u8 ( routes [ i ] . method ) ;
641+ w . u8 ( routes [ i ] . kind ) ;
642+ w . str ( routes [ i ] . path ) ;
643+ }
644+ return w . toBytes ( ) ;
645+ }
646+
647+ function explicitRequestKind ( decorators : DecoratorNode [ ] | null ) : i32 {
648+ if ( decorators == null ) return - 1 ;
649+ for ( let i = 0 , k = decorators . length ; i < k ; ++ i ) {
650+ let dk = decorators [ i ] . decoratorKind ;
651+ if ( dk == DecoratorKind . Query ) return 0 ;
652+ if ( dk == DecoratorKind . Action ) return 1 ;
653+ }
654+ return - 1 ;
655+ }
656+
657+ function isSafeMethod ( method : i32 ) : bool {
658+ return method == 0 || method == 5 || method == 6 ;
659+ }
660+
661+ function restPrefixOf ( deco : DecoratorNode ) : string {
662+ let args = deco . args ;
663+ if ( args == null || args . length == 0 ) return "" ;
664+ let a = args [ 0 ] ;
665+ if ( a instanceof StringLiteralExpression ) {
666+ let s = ( < StringLiteralExpression > a ) . value . trim ( ) ;
667+ if ( s . length == 0 || s == "/" ) return "" ;
668+ if ( ! s . startsWith ( "/" ) ) s = "/" + s ;
669+ return s . replace ( / \/ + $ / , "" ) ;
670+ }
671+ return "" ;
672+ }
673+
674+ function routeDecoratorOf ( method : MethodDeclaration ) : DecoratorNode | null {
675+ let decos = method . decorators ;
676+ if ( decos == null ) return null ;
677+ for ( let i = 0 , k = decos . length ; i < k ; ++ i ) {
678+ switch ( decos [ i ] . decoratorKind ) {
679+ case DecoratorKind . Route :
680+ case DecoratorKind . Get :
681+ case DecoratorKind . Post :
682+ case DecoratorKind . Put :
683+ case DecoratorKind . Delete :
684+ case DecoratorKind . Patch :
685+ case DecoratorKind . Head :
686+ case DecoratorKind . Options : return decos [ i ] ;
687+ }
688+ }
689+ return null ;
690+ }
691+
692+ function routeInfoOf ( deco : DecoratorNode ) : RouteInfo | null {
693+ let out = new RouteInfo ( ) ;
694+ let dk = deco . decoratorKind ;
695+ if ( dk == DecoratorKind . Route ) {
696+ let args = deco . args ;
697+ if ( args == null || args . length == 0 || ! ( args [ 0 ] instanceof ObjectLiteralExpression ) ) return null ;
698+ let obj = < ObjectLiteralExpression > args [ 0 ] ;
699+ let mv = objectField ( obj , "method" ) ;
700+ let pv = objectField ( obj , "path" ) ;
701+ if ( mv == null || pv == null || ! ( pv instanceof StringLiteralExpression ) ) return null ;
702+ let mName = enumMember ( mv ) ;
703+ if ( mName == null ) return null ;
704+ out . method = methodCode ( mName ) ;
705+ out . path = ( < StringLiteralExpression > pv ) . value ;
706+ } else {
707+ out . method = verbMethodCode ( dk ) ;
708+ let args = deco . args ;
709+ if ( args == null || args . length == 0 || ! ( args [ 0 ] instanceof StringLiteralExpression ) ) return null ;
710+ out . path = ( < StringLiteralExpression > args [ 0 ] ) . value ;
711+ }
712+ return out . method >= 0 ? out : null ;
713+ }
714+
715+ function verbMethodCode ( dk : DecoratorKind ) : i32 {
716+ switch ( dk ) {
717+ case DecoratorKind . Post : return 1 ;
718+ case DecoratorKind . Put : return 2 ;
719+ case DecoratorKind . Delete : return 3 ;
720+ case DecoratorKind . Patch : return 4 ;
721+ case DecoratorKind . Head : return 5 ;
722+ case DecoratorKind . Options : return 6 ;
723+ default : return 0 ;
724+ }
725+ }
726+
727+ function methodCode ( name : string ) : i32 {
728+ switch ( name ) {
729+ case "GET" : return 0 ;
730+ case "POST" : return 1 ;
731+ case "PUT" : return 2 ;
732+ case "DELETE" : return 3 ;
733+ case "PATCH" : return 4 ;
734+ case "HEAD" : return 5 ;
735+ case "OPTIONS" : return 6 ;
736+ default : return - 1 ;
737+ }
738+ }
739+
740+ function joinRoutePath ( prefix : string , routePath : string ) : string {
741+ let r = routePath . trim ( ) ;
742+ if ( r . length == 0 ) r = "/" ;
743+ if ( ! r . startsWith ( "/" ) ) r = "/" + r ;
744+ if ( r == "/" ) return prefix . length > 0 ? prefix : "/" ;
745+ if ( r . length > 1 ) r = r . replace ( / \/ + $ / , "" ) ;
746+ let full = prefix + r ;
747+ return full . length == 0 ? "/" : full ;
748+ }
749+
578750// ===========================================================================
579751// Streams + daemon metadata sections (spec 03 sections 6/7/8, Part 5 layouts).
580752//
0 commit comments