Skip to content

Commit 3df171e

Browse files
committed
wip: in-progress compiler/dbcatalog/bindings changes (checkpoint push)
1 parent ce78d12 commit 3df171e

6 files changed

Lines changed: 187 additions & 6 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"prepublishOnly": "node scripts/build",
8181
"watch": "node scripts/build --watch",
8282
"coverage": "npx c8 -- npm test",
83-
"test": "npm run test:parser && npm run test:compiler -- --parallel && npm run test:browser && npm run test:toilconfig && npm run test:transform && npm run test:cli && npm run test:json && npm run test:data && npm run test:dbresolve && npm run test:dbstatic && npm run test:streams",
83+
"test": "npm run test:parser && npm run test:compiler -- --parallel && npm run test:browser && npm run test:toilconfig && npm run test:transform && npm run test:cli && npm run test:json && npm run test:data && npm run test:dbresolve && npm run test:dbstatic && npm run test:routekinds && npm run test:streams",
8484
"test:parser": "node --enable-source-maps tests/parser",
8585
"test:compiler": "node --enable-source-maps --no-warnings tests/compiler",
8686
"test:browser": "node --enable-source-maps tests/browser",
@@ -93,6 +93,7 @@
9393
"test:data": "node tests/data/run.mjs",
9494
"test:dbresolve": "node tests/dbresolve/run.mjs",
9595
"test:dbstatic": "node tests/dbstatic/run.mjs",
96+
"test:routekinds": "node tests/routekinds/run.mjs",
9697
"test:streams": "node tests/streams/run.mjs && node tests/streams/catalog.mjs && node tests/streams/codegen.mjs",
9798
"asbuild": "npm run asbuild:debug && npm run asbuild:release",
9899
"asbuild:debug": "node bin/toilscript --config src/toilconfig.json --target debug",

src/compiler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import {
2323
buildToilDbCatalog,
2424
buildToilDbTypes,
25+
buildToilDbRouteKinds,
2526
buildToilSurface,
2627
buildToilStreamCatalog,
2728
buildToilDaemonCatalog
@@ -833,6 +834,10 @@ export class Compiler extends DiagnosticEmitter {
833834
let surface = buildToilSurface(program, targetMode);
834835
if (surface != null) module.addCustomSection("toil.surface", surface);
835836
if (targetMode != "cold") {
837+
// Request-path DB policy metadata. The edge uses this only to tighten
838+
// mutating HTTP methods that the source declared read-only with `@query`.
839+
let routeKinds = buildToilDbRouteKinds(program);
840+
if (routeKinds != null) module.addCustomSection("toildb.route_kinds", routeKinds);
836841
// hot or legacy: any @stream class -> toilstream.catalog.
837842
let streamCat = buildToilStreamCatalog(program);
838843
if (streamCat != null) module.addCustomSection("toilstream.catalog", streamCat);

src/dbcatalog.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
//

src/program.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,6 +2291,9 @@ export class Program extends DiagnosticEmitter {
22912291
let isStatic = declaration.is(CommonFlags.Static);
22922292
let methodTargetMode = this.options.targetMode;
22932293
let acceptedFlags = DecoratorFlags.Inline | DecoratorFlags.Unsafe;
2294+
// ToilDB function kinds also apply to methods, including @rest route
2295+
// handlers. The parser's DB checker already walks MethodDeclaration bodies.
2296+
acceptedFlags |= DecoratorFlags.DbFunction;
22942297
if (!declaration.is(CommonFlags.Generic)) {
22952298
acceptedFlags |= DecoratorFlags.OperatorBinary
22962299
| DecoratorFlags.OperatorPrefix

std/assembly/bindings/toildb.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ export namespace toildbHost {
3535
export declare function get(handle: u32, keyPtr: usize, keyLen: i32): i32;
3636

3737
// record bounded multi-get. Input at keysPtr: u32 count + per key (u32 len +
38-
// bytes). Result (stashed): u32 count + per item u8 present (+ u32 len + bytes
39-
// when present), in request order. Returns the stashed length | negative error.
38+
// bytes). Result (stashed): u32 count + per item u8 present
39+
// (+ u32 schema_version + u32 len + bytes when present), in request order.
4040
// @ts-ignore: decorator
4141
@external("env", "data.get_many")
4242
export declare function getMany(handle: u32, keysPtr: usize, keysLen: i32): i32;
@@ -148,7 +148,7 @@ export namespace toildbHost {
148148
): i32;
149149

150150
// membership.list(limit) -> framed-list length (stashed) | negative error.
151-
// The blob is `u32 count` then per member `u32 len + bytes`.
151+
// The blob is `u32 count` then per member `u32 schema_version + u32 len + bytes`.
152152
// @ts-ignore: decorator
153153
@external("env", "data.membership_list")
154154
export declare function membershipList(handle: u32, setPtr: usize, setLen: i32, limit: i32): i32;
@@ -216,7 +216,7 @@ export namespace toildbHost {
216216
): i32;
217217

218218
// events.latest(limit) -> framed-list length (stashed) | negative error.
219-
// The blob is `u32 count` then per event `u32 len + bytes`, newest first.
219+
// The blob is `u32 count` then per event `u32 schema_version + u32 len + bytes`, newest first.
220220
// @ts-ignore: decorator
221221
@external("env", "data.latest")
222222
export declare function latest(handle: u32, keyPtr: usize, keyLen: i32, limit: i32): i32;

std/assembly/toildb.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ export class Events<K, V> {
471471
}
472472

473473
/// The newest `limit` events, newest first. Decodes each framed event into a
474-
/// `V`. The host frames them as `u32 count` then per event `u32 len + bytes`.
474+
/// `V`. The host frames them as `u32 count` then per event `u32 schema_version + u32 len + bytes`.
475475
latest(key: K, limit: i32): V[] {
476476
const kb = key.encode();
477477
const status = toildbHost.latest(this.__handle, kb.dataStart, kb.byteLength, limit);

0 commit comments

Comments
 (0)