toiljs 0.0.60 → 0.0.62
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/.github/workflows/ci.yml +31 -0
- package/CHANGELOG.md +17 -0
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2 -2
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/index.d.ts +1 -1
- package/build/client/index.js +1 -1
- package/build/client/routing/mount.js +11 -26
- package/build/client/ssr/markers.d.ts +1 -0
- package/build/client/ssr/markers.js +9 -2
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +21 -0
- package/build/compiler/config.js +35 -0
- package/build/compiler/docs.d.ts +2 -1
- package/build/compiler/docs.js +33 -304
- package/build/compiler/index.d.ts +13 -0
- package/build/compiler/index.js +113 -21
- package/build/compiler/template-build.d.ts +23 -3
- package/build/compiler/template-build.js +120 -30
- package/build/compiler/toil-docs.generated.d.ts +1 -0
- package/build/compiler/toil-docs.generated.js +20 -0
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/daemon/catalog.d.ts +26 -0
- package/build/devserver/daemon/catalog.js +48 -0
- package/build/devserver/daemon/cron.d.ts +4 -0
- package/build/devserver/daemon/cron.js +50 -0
- package/build/devserver/daemon/host.d.ts +37 -0
- package/build/devserver/daemon/host.js +94 -0
- package/build/devserver/daemon/index.d.ts +34 -0
- package/build/devserver/daemon/index.js +241 -0
- package/build/devserver/db/catalog.d.ts +2 -1
- package/build/devserver/db/catalog.js +44 -44
- package/build/devserver/db/database.d.ts +27 -11
- package/build/devserver/db/database.js +539 -169
- package/build/devserver/db/index.d.ts +1 -1
- package/build/devserver/db/index.js +1 -1
- package/build/devserver/db/routeKinds.d.ts +8 -0
- package/build/devserver/db/routeKinds.js +139 -0
- package/build/devserver/db/types.d.ts +64 -1
- package/build/devserver/db/types.js +33 -1
- package/build/devserver/index.d.ts +10 -0
- package/build/devserver/index.js +7 -0
- package/build/devserver/mstore/store.d.ts +18 -0
- package/build/devserver/mstore/store.js +82 -0
- package/build/devserver/runtime/host.d.ts +6 -0
- package/build/devserver/runtime/host.js +45 -1
- package/build/devserver/runtime/module.d.ts +1 -0
- package/build/devserver/runtime/module.js +27 -1
- package/build/devserver/server.d.ts +6 -0
- package/build/devserver/server.js +59 -0
- package/build/devserver/ssr.d.ts +25 -0
- package/build/devserver/ssr.js +114 -0
- package/build/devserver/wasm/sections.d.ts +2 -0
- package/build/devserver/wasm/sections.js +42 -0
- package/build/devserver/wasm/surface.d.ts +18 -0
- package/build/devserver/wasm/surface.js +41 -0
- package/docs/README.md +4 -4
- package/docs/auth-todo.md +6 -6
- package/docs/caching.md +5 -5
- package/docs/cli.md +15 -0
- package/docs/client.md +40 -0
- package/docs/crypto.md +4 -4
- package/docs/data.md +6 -6
- package/docs/email.md +28 -28
- package/docs/environment.md +10 -10
- package/docs/index.md +26 -0
- package/docs/ratelimit.md +10 -10
- package/docs/routing.md +2 -2
- package/docs/server.md +61 -0
- package/docs/ssr.md +561 -113
- package/docs/styling.md +22 -0
- package/docs/time.md +1 -1
- package/eslint.config.js +10 -1
- package/examples/basic/client/components/Header.tsx +3 -0
- package/examples/basic/client/routes/features/actions.tsx +0 -2
- package/examples/basic/client/routes/hello.tsx +89 -19
- package/examples/basic/client/styles/main.css +48 -0
- package/examples/basic/server/SsrHelloRender.ts +97 -0
- package/examples/basic/server/main.ts +5 -0
- package/examples/basic/server/streams/Echo.ts +49 -0
- package/package.json +12 -10
- package/scripts/gen-toil-docs.mjs +96 -0
- package/src/cli/create.ts +2 -2
- package/src/client/index.ts +1 -1
- package/src/client/routing/mount.tsx +19 -31
- package/src/client/ssr/markers.tsx +33 -4
- package/src/compiler/config.ts +88 -2
- package/src/compiler/docs.ts +47 -308
- package/src/compiler/index.ts +236 -32
- package/src/compiler/ssr-codegen.ts +1 -1
- package/src/compiler/template-build.ts +271 -53
- package/src/compiler/toil-docs.generated.ts +26 -0
- package/src/devserver/daemon/catalog.ts +120 -0
- package/src/devserver/daemon/cron.ts +87 -0
- package/src/devserver/daemon/host.ts +224 -0
- package/src/devserver/daemon/index.ts +349 -0
- package/src/devserver/db/catalog.ts +61 -53
- package/src/devserver/db/database.ts +613 -149
- package/src/devserver/db/index.ts +1 -1
- package/src/devserver/db/routeKinds.ts +147 -0
- package/src/devserver/db/types.ts +65 -2
- package/src/devserver/index.ts +12 -0
- package/src/devserver/mstore/store.ts +121 -0
- package/src/devserver/runtime/host.ts +92 -1
- package/src/devserver/runtime/module.ts +35 -1
- package/src/devserver/server.ts +101 -0
- package/src/devserver/ssr.ts +166 -0
- package/src/devserver/wasm/sections.ts +59 -0
- package/src/devserver/wasm/surface.ts +88 -0
- package/test/daemon-build.test.ts +198 -0
- package/test/daemon-catalog.test.ts +265 -0
- package/test/daemon-emulation.test.ts +216 -0
- package/test/devserver-database.test.ts +396 -5
- package/test/email-preview.test.ts +6 -1
- package/test/fixtures/daemon-app.ts +56 -0
- package/test/global-setup.ts +17 -0
- package/test/ssr-hydration.test.tsx +107 -0
- package/test/ssr-render.test.ts +96 -27
- package/test/ssr-template.test.tsx +47 -2
- package/vitest.config.ts +3 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { __resetDbForTests, __setDbCatalogForTests, buildDatabaseImports, configureDbPersistence, DevDatabase, devDb, persistDb, setDbCatalog, } from './database.js';
|
|
2
2
|
export { parseCatalog } from './catalog.js';
|
|
3
|
-
export { type DbDevState, freshDbState } from './types.js';
|
|
3
|
+
export { CollectionFamily, DbFunctionKind, type DbDevState, freshDbState } from './types.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { __resetDbForTests, __setDbCatalogForTests, buildDatabaseImports, configureDbPersistence, DevDatabase, devDb, persistDb, setDbCatalog, } from './database.js';
|
|
2
2
|
export { parseCatalog } from './catalog.js';
|
|
3
|
-
export { freshDbState } from './types.js';
|
|
3
|
+
export { CollectionFamily, DbFunctionKind, freshDbState } from './types.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DbFunctionKind } from './types.js';
|
|
2
|
+
export interface RouteKindEntry {
|
|
3
|
+
readonly method: number;
|
|
4
|
+
readonly kind: DbFunctionKind;
|
|
5
|
+
readonly pattern: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseRouteKinds(wasm: Buffer): readonly RouteKindEntry[];
|
|
8
|
+
export declare function routeKindForRequest(routes: readonly RouteKindEntry[], method: string, path: string): DbFunctionKind | null;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { customSection } from '../wasm/sections.js';
|
|
2
|
+
import { DbFunctionKind } from './types.js';
|
|
3
|
+
const SECTION = 'toildb.route_kinds';
|
|
4
|
+
const VERSION = 1;
|
|
5
|
+
const MAX_SECTION_BYTES = 128 * 1024;
|
|
6
|
+
const MAX_ROUTES = 2048;
|
|
7
|
+
const MAX_PATTERN_BYTES = 2048;
|
|
8
|
+
const UTF8_DECODER = new TextDecoder('utf-8', { fatal: true });
|
|
9
|
+
const METHOD_CODES = {
|
|
10
|
+
GET: 0,
|
|
11
|
+
POST: 1,
|
|
12
|
+
PUT: 2,
|
|
13
|
+
DELETE: 3,
|
|
14
|
+
PATCH: 4,
|
|
15
|
+
HEAD: 5,
|
|
16
|
+
OPTIONS: 6,
|
|
17
|
+
};
|
|
18
|
+
export function parseRouteKinds(wasm) {
|
|
19
|
+
let section;
|
|
20
|
+
try {
|
|
21
|
+
section = customSection(wasm, SECTION);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
if (section === null)
|
|
27
|
+
return [];
|
|
28
|
+
if (section.length > MAX_SECTION_BYTES)
|
|
29
|
+
return [];
|
|
30
|
+
const r = new Reader(section);
|
|
31
|
+
const version = r.u16();
|
|
32
|
+
if (!r.ok || version !== VERSION)
|
|
33
|
+
return [];
|
|
34
|
+
const count = r.u16();
|
|
35
|
+
if (!r.ok || count > MAX_ROUTES)
|
|
36
|
+
return [];
|
|
37
|
+
const routes = [];
|
|
38
|
+
for (let i = 0; i < count && r.ok; i++) {
|
|
39
|
+
const method = r.u8();
|
|
40
|
+
const kindByte = r.u8();
|
|
41
|
+
const pattern = r.string();
|
|
42
|
+
const kind = kindByte === 0 ? DbFunctionKind.Query : kindByte === 1 ? DbFunctionKind.Action : null;
|
|
43
|
+
if (!r.ok || method < 0 || method > 6 || kind === null || !validPattern(pattern))
|
|
44
|
+
return [];
|
|
45
|
+
routes.push({ method, kind, pattern });
|
|
46
|
+
}
|
|
47
|
+
if (!r.ok || r.remaining() !== 0)
|
|
48
|
+
return [];
|
|
49
|
+
return routes;
|
|
50
|
+
}
|
|
51
|
+
export function routeKindForRequest(routes, method, path) {
|
|
52
|
+
const methodCode = METHOD_CODES[method.toUpperCase()];
|
|
53
|
+
if (methodCode === undefined)
|
|
54
|
+
return null;
|
|
55
|
+
for (const route of routes) {
|
|
56
|
+
if (route.method === methodCode && routeMatches(route.pattern, path))
|
|
57
|
+
return route.kind;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function validPattern(pattern) {
|
|
62
|
+
if (pattern.length === 0 || !pattern.startsWith('/'))
|
|
63
|
+
return false;
|
|
64
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
65
|
+
const c = pattern.charCodeAt(i);
|
|
66
|
+
if (c < 0x20 || c > 0x7e)
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
function routeMatches(pattern, pathWithQuery) {
|
|
72
|
+
const q = pathWithQuery.indexOf('?');
|
|
73
|
+
const path = q >= 0 ? pathWithQuery.slice(0, q) : pathWithQuery;
|
|
74
|
+
const patternSegs = pattern.split('/').filter(Boolean);
|
|
75
|
+
const pathSegs = path.split('/').filter(Boolean);
|
|
76
|
+
if (patternSegs.length !== pathSegs.length)
|
|
77
|
+
return false;
|
|
78
|
+
for (let i = 0; i < patternSegs.length; i++) {
|
|
79
|
+
const p = patternSegs[i] ?? '';
|
|
80
|
+
const a = pathSegs[i] ?? '';
|
|
81
|
+
if (p.startsWith(':') && p.length > 1 && a.length > 0)
|
|
82
|
+
continue;
|
|
83
|
+
if (p !== a)
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
class Reader {
|
|
89
|
+
bytes;
|
|
90
|
+
pos = 0;
|
|
91
|
+
ok = true;
|
|
92
|
+
constructor(bytes) {
|
|
93
|
+
this.bytes = bytes;
|
|
94
|
+
}
|
|
95
|
+
remaining() {
|
|
96
|
+
return this.bytes.length - this.pos;
|
|
97
|
+
}
|
|
98
|
+
u8() {
|
|
99
|
+
if (!this.ok || this.pos + 1 > this.bytes.length) {
|
|
100
|
+
this.ok = false;
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
return this.bytes[this.pos++] ?? 0;
|
|
104
|
+
}
|
|
105
|
+
u16() {
|
|
106
|
+
if (!this.ok || this.pos + 2 > this.bytes.length) {
|
|
107
|
+
this.ok = false;
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
const out = this.bytes.readUInt16LE(this.pos);
|
|
111
|
+
this.pos += 2;
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
u32() {
|
|
115
|
+
if (!this.ok || this.pos + 4 > this.bytes.length) {
|
|
116
|
+
this.ok = false;
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
const out = this.bytes.readUInt32LE(this.pos);
|
|
120
|
+
this.pos += 4;
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
string() {
|
|
124
|
+
const len = this.u32();
|
|
125
|
+
if (!this.ok || len > MAX_PATTERN_BYTES || this.pos + len > this.bytes.length) {
|
|
126
|
+
this.ok = false;
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const out = UTF8_DECODER.decode(this.bytes.subarray(this.pos, this.pos + len));
|
|
131
|
+
this.pos += len;
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
this.ok = false;
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -1,7 +1,42 @@
|
|
|
1
|
+
export declare enum CollectionFamily {
|
|
2
|
+
Record = 0,
|
|
3
|
+
View = 1,
|
|
4
|
+
Events = 2,
|
|
5
|
+
Counter = 3,
|
|
6
|
+
Membership = 4,
|
|
7
|
+
Unique = 5,
|
|
8
|
+
Capacity = 6
|
|
9
|
+
}
|
|
10
|
+
export declare enum DbFunctionKind {
|
|
11
|
+
Query = "query",
|
|
12
|
+
Action = "action",
|
|
13
|
+
Derive = "derive",
|
|
14
|
+
Job = "job",
|
|
15
|
+
Admin = "admin"
|
|
16
|
+
}
|
|
17
|
+
export declare function isCollectionFamily(value: number): value is CollectionFamily;
|
|
18
|
+
export interface DevCollectionHandle {
|
|
19
|
+
name: string;
|
|
20
|
+
family: CollectionFamily;
|
|
21
|
+
schemaVersion: number;
|
|
22
|
+
replication: number;
|
|
23
|
+
placement: number;
|
|
24
|
+
fillMaxWaitMs: number;
|
|
25
|
+
fillAllowStale: boolean;
|
|
26
|
+
}
|
|
27
|
+
export type DbCatalogState = {
|
|
28
|
+
kind: 'no-section';
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'malformed';
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'present';
|
|
33
|
+
collections: Map<string, DevCollectionHandle>;
|
|
34
|
+
};
|
|
1
35
|
export interface DbDevState {
|
|
2
|
-
handles:
|
|
36
|
+
handles: DevCollectionHandle[];
|
|
3
37
|
lastResult: Buffer | null;
|
|
4
38
|
lastResultVersion: number;
|
|
39
|
+
functionKind: DbFunctionKind;
|
|
5
40
|
}
|
|
6
41
|
export declare function freshDbState(): DbDevState;
|
|
7
42
|
export interface Reservation {
|
|
@@ -19,6 +54,12 @@ export interface DbSnapshot {
|
|
|
19
54
|
v: string;
|
|
20
55
|
sv: number;
|
|
21
56
|
}>;
|
|
57
|
+
recordIdem?: Record<string, {
|
|
58
|
+
requestHash: string;
|
|
59
|
+
state: 'pending' | 'done';
|
|
60
|
+
outcome?: RecordOutcomeSnapshot;
|
|
61
|
+
}>;
|
|
62
|
+
uniqueIdem?: Record<string, string>;
|
|
22
63
|
views: Record<string, {
|
|
23
64
|
v: string;
|
|
24
65
|
sv: number;
|
|
@@ -28,6 +69,7 @@ export interface DbSnapshot {
|
|
|
28
69
|
sv: number;
|
|
29
70
|
}>>;
|
|
30
71
|
counters: Record<string, string>;
|
|
72
|
+
counterIdem?: Record<string, string>;
|
|
31
73
|
events: Record<string, {
|
|
32
74
|
v: string;
|
|
33
75
|
sv: number;
|
|
@@ -43,16 +85,37 @@ export interface DbSnapshot {
|
|
|
43
85
|
}][];
|
|
44
86
|
}>;
|
|
45
87
|
}
|
|
88
|
+
export type RecordOutcomeSnapshot = {
|
|
89
|
+
kind: 'unit';
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'value';
|
|
92
|
+
v: string;
|
|
93
|
+
sv: number;
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'absent';
|
|
96
|
+
} | {
|
|
97
|
+
kind: 'already_exists';
|
|
98
|
+
} | {
|
|
99
|
+
kind: 'not_found';
|
|
100
|
+
} | {
|
|
101
|
+
kind: 'conflict';
|
|
102
|
+
};
|
|
46
103
|
export declare const MAX_RESERVATIONS = 4096;
|
|
47
104
|
export declare const MAX_RESERVATION_TTL_MS = 86400000;
|
|
48
105
|
export declare const MAX_NAME = 512;
|
|
49
106
|
export declare const MAX_KEY = 4096;
|
|
50
107
|
export declare const MAX_VALUE: number;
|
|
108
|
+
export declare const DEFAULT_FILL_WAIT_MS = 50;
|
|
109
|
+
export declare const MAX_FILL_WAIT_MS = 60000;
|
|
51
110
|
export declare function satI64(v: bigint): bigint;
|
|
52
111
|
export declare const ABSENT = -2;
|
|
53
112
|
export declare const TOO_SMALL = -1;
|
|
54
113
|
export declare const INVALID_HANDLE = -1001;
|
|
55
114
|
export declare const ALREADY_EXISTS = -1003;
|
|
56
115
|
export declare const CONFLICT = -1004;
|
|
116
|
+
export declare const UNAVAILABLE = -1031;
|
|
57
117
|
export declare const CODEC_ERR = -1006;
|
|
118
|
+
export declare const OP_NOT_ALLOWED_FOR_FAMILY = -1010;
|
|
119
|
+
export declare const OP_NOT_ALLOWED_IN_KIND = -1011;
|
|
58
120
|
export declare const TOO_MANY_KEYS = -1020;
|
|
121
|
+
export declare const SCHEMA_UNAVAILABLE = -1070;
|
|
@@ -1,11 +1,39 @@
|
|
|
1
|
+
export var CollectionFamily;
|
|
2
|
+
(function (CollectionFamily) {
|
|
3
|
+
CollectionFamily[CollectionFamily["Record"] = 0] = "Record";
|
|
4
|
+
CollectionFamily[CollectionFamily["View"] = 1] = "View";
|
|
5
|
+
CollectionFamily[CollectionFamily["Events"] = 2] = "Events";
|
|
6
|
+
CollectionFamily[CollectionFamily["Counter"] = 3] = "Counter";
|
|
7
|
+
CollectionFamily[CollectionFamily["Membership"] = 4] = "Membership";
|
|
8
|
+
CollectionFamily[CollectionFamily["Unique"] = 5] = "Unique";
|
|
9
|
+
CollectionFamily[CollectionFamily["Capacity"] = 6] = "Capacity";
|
|
10
|
+
})(CollectionFamily || (CollectionFamily = {}));
|
|
11
|
+
export var DbFunctionKind;
|
|
12
|
+
(function (DbFunctionKind) {
|
|
13
|
+
DbFunctionKind["Query"] = "query";
|
|
14
|
+
DbFunctionKind["Action"] = "action";
|
|
15
|
+
DbFunctionKind["Derive"] = "derive";
|
|
16
|
+
DbFunctionKind["Job"] = "job";
|
|
17
|
+
DbFunctionKind["Admin"] = "admin";
|
|
18
|
+
})(DbFunctionKind || (DbFunctionKind = {}));
|
|
19
|
+
export function isCollectionFamily(value) {
|
|
20
|
+
return value >= CollectionFamily.Record && value <= CollectionFamily.Capacity;
|
|
21
|
+
}
|
|
1
22
|
export function freshDbState() {
|
|
2
|
-
return {
|
|
23
|
+
return {
|
|
24
|
+
handles: [],
|
|
25
|
+
lastResult: null,
|
|
26
|
+
lastResultVersion: -1,
|
|
27
|
+
functionKind: DbFunctionKind.Job,
|
|
28
|
+
};
|
|
3
29
|
}
|
|
4
30
|
export const MAX_RESERVATIONS = 4096;
|
|
5
31
|
export const MAX_RESERVATION_TTL_MS = 86_400_000;
|
|
6
32
|
export const MAX_NAME = 512;
|
|
7
33
|
export const MAX_KEY = 4096;
|
|
8
34
|
export const MAX_VALUE = 256 * 1024;
|
|
35
|
+
export const DEFAULT_FILL_WAIT_MS = 50;
|
|
36
|
+
export const MAX_FILL_WAIT_MS = 60_000;
|
|
9
37
|
const I64_MIN = -(2n ** 63n);
|
|
10
38
|
const I64_MAX = 2n ** 63n - 1n;
|
|
11
39
|
export function satI64(v) {
|
|
@@ -16,5 +44,9 @@ export const TOO_SMALL = -1;
|
|
|
16
44
|
export const INVALID_HANDLE = -1001;
|
|
17
45
|
export const ALREADY_EXISTS = -1003;
|
|
18
46
|
export const CONFLICT = -1004;
|
|
47
|
+
export const UNAVAILABLE = -1031;
|
|
19
48
|
export const CODEC_ERR = -1006;
|
|
49
|
+
export const OP_NOT_ALLOWED_FOR_FAMILY = -1010;
|
|
50
|
+
export const OP_NOT_ALLOWED_IN_KIND = -1011;
|
|
20
51
|
export const TOO_MANY_KEYS = -1020;
|
|
52
|
+
export const SCHEMA_UNAVAILABLE = -1070;
|
|
@@ -7,3 +7,13 @@ export type { WasmDispatchResult } from './runtime/module.js';
|
|
|
7
7
|
export { buildHostImports, freshDispatchState } from './runtime/host.js';
|
|
8
8
|
export type { DispatchState, MemoryRef } from './runtime/host.js';
|
|
9
9
|
export type { ViteTarget } from './http/proxy.js';
|
|
10
|
+
export { DaemonHost, daemonEmulationEnabled } from './daemon/index.js';
|
|
11
|
+
export { parseDaemonCatalog } from './daemon/catalog.js';
|
|
12
|
+
export type { DaemonCatalog, ScheduledTask, CronMasks } from './daemon/catalog.js';
|
|
13
|
+
export { buildDaemonImports, freshDaemonState } from './daemon/host.js';
|
|
14
|
+
export type { DaemonState, DaemonRuntime, ResolvedDaemonConfig } from './daemon/host.js';
|
|
15
|
+
export { cronMatches, cronNeverFires, nextCronFireMs } from './daemon/cron.js';
|
|
16
|
+
export { parseSurface } from './wasm/surface.js';
|
|
17
|
+
export type { Surface, SurfaceFlags } from './wasm/surface.js';
|
|
18
|
+
export { customSection } from './wasm/sections.js';
|
|
19
|
+
export { DevMemoryStore, devMemoryStore } from './mstore/store.js';
|
package/build/devserver/index.js
CHANGED
|
@@ -2,3 +2,10 @@ export { startDevServer } from './server.js';
|
|
|
2
2
|
export { METHOD_CODES, encodeRequestEnvelope, decodeResponseEnvelope, unpackHandleResult, } from './http/envelope.js';
|
|
3
3
|
export { WasmServerModule, WasmAbortError, UNHANDLED_HEADER } from './runtime/module.js';
|
|
4
4
|
export { buildHostImports, freshDispatchState } from './runtime/host.js';
|
|
5
|
+
export { DaemonHost, daemonEmulationEnabled } from './daemon/index.js';
|
|
6
|
+
export { parseDaemonCatalog } from './daemon/catalog.js';
|
|
7
|
+
export { buildDaemonImports, freshDaemonState } from './daemon/host.js';
|
|
8
|
+
export { cronMatches, cronNeverFires, nextCronFireMs } from './daemon/cron.js';
|
|
9
|
+
export { parseSurface } from './wasm/surface.js';
|
|
10
|
+
export { customSection } from './wasm/sections.js';
|
|
11
|
+
export { DevMemoryStore, devMemoryStore } from './mstore/store.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class DevMemoryStore {
|
|
2
|
+
private readonly map;
|
|
3
|
+
private now;
|
|
4
|
+
private live;
|
|
5
|
+
private exp;
|
|
6
|
+
get(key: string): Buffer | null;
|
|
7
|
+
set(key: string, value: Buffer, ttlSecs: number): void;
|
|
8
|
+
delete(key: string): boolean;
|
|
9
|
+
incr(key: string, delta: bigint, ttlSecs: number): bigint | null;
|
|
10
|
+
cas(key: string, expect: Buffer | null, next: Buffer, ttlSecs: number): boolean;
|
|
11
|
+
expire(key: string, ttlSecs: number): boolean;
|
|
12
|
+
scan(prefix: string, cursor: bigint): {
|
|
13
|
+
next: bigint;
|
|
14
|
+
keys: string[];
|
|
15
|
+
} | null;
|
|
16
|
+
__reset(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare const devMemoryStore: DevMemoryStore;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export class DevMemoryStore {
|
|
2
|
+
map = new Map();
|
|
3
|
+
now() {
|
|
4
|
+
return Date.now();
|
|
5
|
+
}
|
|
6
|
+
live(key) {
|
|
7
|
+
const e = this.map.get(key);
|
|
8
|
+
if (e === undefined)
|
|
9
|
+
return null;
|
|
10
|
+
if (e.expiresAtMs !== 0 && e.expiresAtMs <= this.now()) {
|
|
11
|
+
this.map.delete(key);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return e;
|
|
15
|
+
}
|
|
16
|
+
exp(ttlSecs) {
|
|
17
|
+
return ttlSecs > 0 ? this.now() + ttlSecs * 1000 : 0;
|
|
18
|
+
}
|
|
19
|
+
get(key) {
|
|
20
|
+
const e = this.live(key);
|
|
21
|
+
return e ? e.value : null;
|
|
22
|
+
}
|
|
23
|
+
set(key, value, ttlSecs) {
|
|
24
|
+
this.map.set(key, { value: Buffer.from(value), expiresAtMs: this.exp(ttlSecs) });
|
|
25
|
+
}
|
|
26
|
+
delete(key) {
|
|
27
|
+
return this.map.delete(key);
|
|
28
|
+
}
|
|
29
|
+
incr(key, delta, ttlSecs) {
|
|
30
|
+
const e = this.live(key);
|
|
31
|
+
let cur = 0n;
|
|
32
|
+
if (e !== null) {
|
|
33
|
+
const s = e.value.toString('utf8').trim();
|
|
34
|
+
if (!/^-?\d+$/.test(s))
|
|
35
|
+
return null;
|
|
36
|
+
try {
|
|
37
|
+
cur = BigInt(s);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const next = BigInt.asIntN(64, cur + delta);
|
|
44
|
+
this.map.set(key, {
|
|
45
|
+
value: Buffer.from(next.toString(), 'utf8'),
|
|
46
|
+
expiresAtMs: ttlSecs > 0 ? this.exp(ttlSecs) : (e?.expiresAtMs ?? 0),
|
|
47
|
+
});
|
|
48
|
+
return next;
|
|
49
|
+
}
|
|
50
|
+
cas(key, expect, next, ttlSecs) {
|
|
51
|
+
const e = this.live(key);
|
|
52
|
+
if (expect === null) {
|
|
53
|
+
if (e !== null)
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
else if (e === null || !e.value.equals(expect)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
this.map.set(key, { value: Buffer.from(next), expiresAtMs: this.exp(ttlSecs) });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
expire(key, ttlSecs) {
|
|
63
|
+
const e = this.live(key);
|
|
64
|
+
if (!e)
|
|
65
|
+
return false;
|
|
66
|
+
e.expiresAtMs = this.exp(ttlSecs);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
scan(prefix, cursor) {
|
|
70
|
+
const live = [...this.map.keys()].filter((k) => this.live(k) !== null && k.startsWith(prefix));
|
|
71
|
+
live.sort();
|
|
72
|
+
const start = Number(cursor);
|
|
73
|
+
if (start < 0 || start > live.length)
|
|
74
|
+
return null;
|
|
75
|
+
const batch = live.slice(start);
|
|
76
|
+
return { next: BigInt(live.length), keys: batch };
|
|
77
|
+
}
|
|
78
|
+
__reset() {
|
|
79
|
+
this.map.clear();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export const devMemoryStore = new DevMemoryStore();
|
|
@@ -16,4 +16,10 @@ export declare function freshDispatchState(): DispatchState;
|
|
|
16
16
|
export interface MemoryRef {
|
|
17
17
|
memory: WebAssembly.Memory | null;
|
|
18
18
|
}
|
|
19
|
+
export declare function readBytes(ref: MemoryRef, ptr: number, len: number): Buffer;
|
|
20
|
+
export declare function writeBytesOut(ref: MemoryRef, bytes: Buffer, outPtr: number, outCap: number): number;
|
|
21
|
+
export declare function buildEnvImports(ref: MemoryRef, _state: {
|
|
22
|
+
crypto: CryptoState;
|
|
23
|
+
db: DbDevState;
|
|
24
|
+
}): Record<string, (...a: never[]) => unknown>;
|
|
19
25
|
export declare function buildHostImports(ref: MemoryRef, state: DispatchState): WebAssembly.Imports;
|
|
@@ -32,12 +32,21 @@ function mem(ref) {
|
|
|
32
32
|
throw new Error('host import called before memory was bound');
|
|
33
33
|
return Buffer.from(ref.memory.buffer);
|
|
34
34
|
}
|
|
35
|
-
function readBytes(ref, ptr, len) {
|
|
35
|
+
export function readBytes(ref, ptr, len) {
|
|
36
36
|
const m = mem(ref);
|
|
37
37
|
if (ptr < 0 || len < 0 || ptr + len > m.length)
|
|
38
38
|
throw new Error(`host import read out of bounds: ptr=${String(ptr)} len=${String(len)}`);
|
|
39
39
|
return m.subarray(ptr, ptr + len);
|
|
40
40
|
}
|
|
41
|
+
export function writeBytesOut(ref, bytes, outPtr, outCap) {
|
|
42
|
+
if (bytes.length > outCap)
|
|
43
|
+
return -1;
|
|
44
|
+
const m = mem(ref);
|
|
45
|
+
if (outPtr < 0 || outPtr + bytes.length > m.length)
|
|
46
|
+
throw new Error('host import write out of bounds');
|
|
47
|
+
bytes.copy(m, outPtr);
|
|
48
|
+
return bytes.length;
|
|
49
|
+
}
|
|
41
50
|
function readGuestString(ref, ptr) {
|
|
42
51
|
if (ptr === 0)
|
|
43
52
|
return '';
|
|
@@ -80,6 +89,41 @@ function envLookup(ref, keyPtr, keyLen, outPtr, outCap, secure) {
|
|
|
80
89
|
bytes.copy(m, outPtr);
|
|
81
90
|
return bytes.length;
|
|
82
91
|
}
|
|
92
|
+
export function buildEnvImports(ref, _state) {
|
|
93
|
+
return {
|
|
94
|
+
abort: (msgPtr, filePtr, line, col) => {
|
|
95
|
+
throw new WasmAbortError(readGuestString(ref, msgPtr), readGuestString(ref, filePtr), line, col);
|
|
96
|
+
},
|
|
97
|
+
env_get: (keyPtr, keyLen, outPtr, outCap) => envLookup(ref, keyPtr, keyLen, outPtr, outCap, false),
|
|
98
|
+
env_get_secure: (keyPtr, keyLen, outPtr, outCap) => envLookup(ref, keyPtr, keyLen, outPtr, outCap, true),
|
|
99
|
+
thread_spawn: (_startArg) => -1,
|
|
100
|
+
'Date.now': () => BigInt(Date.now()),
|
|
101
|
+
email_send: (reqPtr, reqLen) => {
|
|
102
|
+
const raw = readBytes(ref, reqPtr, reqLen);
|
|
103
|
+
const svc = getEmailService();
|
|
104
|
+
if (svc === null) {
|
|
105
|
+
const to = parseEmailBlob(raw)?.to ?? '<unparsed>';
|
|
106
|
+
process.stdout.write(` ✉ dev email_send -> ${to} (no email config; not sent)\n`);
|
|
107
|
+
return EmailStatus.Sent;
|
|
108
|
+
}
|
|
109
|
+
const { status, parsed } = svc.prepare(raw);
|
|
110
|
+
if (parsed === null) {
|
|
111
|
+
process.stdout.write(` ✉ dev email_send -> ${EmailStatus[status]}\n`);
|
|
112
|
+
return status;
|
|
113
|
+
}
|
|
114
|
+
void svc
|
|
115
|
+
.deliver(parsed)
|
|
116
|
+
.then((s) => {
|
|
117
|
+
const label = s === EmailStatus.Sent ? 'sent' : EmailStatus[s];
|
|
118
|
+
process.stdout.write(` ✉ dev email_send -> ${parsed.to} (${label})\n`);
|
|
119
|
+
})
|
|
120
|
+
.catch((e) => {
|
|
121
|
+
process.stdout.write(` ✉ dev email_send -> ${parsed.to} (error: ${String(e)})\n`);
|
|
122
|
+
});
|
|
123
|
+
return EmailStatus.Sent;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
83
127
|
export function buildHostImports(ref, state) {
|
|
84
128
|
return {
|
|
85
129
|
env: {
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { persistDb, setDbCatalog } from '../db/index.js';
|
|
2
|
+
import { DbFunctionKind, persistDb, setDbCatalog } from '../db/index.js';
|
|
3
|
+
import { parseRouteKinds, routeKindForRequest } from '../db/routeKinds.js';
|
|
3
4
|
import { decodeResponseEnvelope, encodeRequestEnvelope, unpackHandleResult, } from '../http/envelope.js';
|
|
4
5
|
import { buildHostImports, freshDispatchState } from './host.js';
|
|
5
6
|
export { WasmAbortError } from './host.js';
|
|
6
7
|
export const UNHANDLED_HEADER = 'x-toil-unhandled';
|
|
7
8
|
const WASM_PAGE = 65536;
|
|
9
|
+
function dbKindForHttpMethod(method) {
|
|
10
|
+
switch (method.toUpperCase()) {
|
|
11
|
+
case 'GET':
|
|
12
|
+
case 'HEAD':
|
|
13
|
+
case 'OPTIONS':
|
|
14
|
+
return DbFunctionKind.Query;
|
|
15
|
+
case 'POST':
|
|
16
|
+
case 'PUT':
|
|
17
|
+
case 'PATCH':
|
|
18
|
+
case 'DELETE':
|
|
19
|
+
default:
|
|
20
|
+
return DbFunctionKind.Action;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
8
23
|
const PROVIDED_IMPORTS = new Set([
|
|
9
24
|
'abort',
|
|
10
25
|
'set_status',
|
|
@@ -67,6 +82,7 @@ export class WasmServerModule {
|
|
|
67
82
|
wasmPath;
|
|
68
83
|
module = null;
|
|
69
84
|
loadedMtimeMs = -1;
|
|
85
|
+
routeKinds = [];
|
|
70
86
|
constructor(wasmPath) {
|
|
71
87
|
this.wasmPath = wasmPath;
|
|
72
88
|
}
|
|
@@ -80,6 +96,7 @@ export class WasmServerModule {
|
|
|
80
96
|
}
|
|
81
97
|
catch {
|
|
82
98
|
this.module = null;
|
|
99
|
+
this.routeKinds = [];
|
|
83
100
|
this.loadedMtimeMs = -1;
|
|
84
101
|
return false;
|
|
85
102
|
}
|
|
@@ -90,6 +107,7 @@ export class WasmServerModule {
|
|
|
90
107
|
this.assertImportSurface(module);
|
|
91
108
|
this.assertExportSurface(module);
|
|
92
109
|
setDbCatalog(bytes);
|
|
110
|
+
this.routeKinds = parseRouteKinds(bytes);
|
|
93
111
|
this.module = module;
|
|
94
112
|
this.loadedMtimeMs = mtimeMs;
|
|
95
113
|
return true;
|
|
@@ -101,6 +119,13 @@ export class WasmServerModule {
|
|
|
101
119
|
const ref = { memory: null };
|
|
102
120
|
const state = freshDispatchState();
|
|
103
121
|
state.clientIp = req.clientIp ?? '';
|
|
122
|
+
const routeKind = routeKindForRequest(this.routeKinds, req.method, req.path);
|
|
123
|
+
state.db.functionKind =
|
|
124
|
+
this.routeKinds.length === 0
|
|
125
|
+
? DbFunctionKind.Job
|
|
126
|
+
: routeKind === DbFunctionKind.Query
|
|
127
|
+
? DbFunctionKind.Query
|
|
128
|
+
: dbKindForHttpMethod(req.method);
|
|
104
129
|
const instance = new WebAssembly.Instance(this.module, buildHostImports(ref, state));
|
|
105
130
|
const exports = instance.exports;
|
|
106
131
|
ref.memory = exports.memory;
|
|
@@ -132,6 +157,7 @@ export class WasmServerModule {
|
|
|
132
157
|
const ref = { memory: null };
|
|
133
158
|
const state = freshDispatchState();
|
|
134
159
|
state.clientIp = req.clientIp ?? '';
|
|
160
|
+
state.db.functionKind = DbFunctionKind.Query;
|
|
135
161
|
const instance = new WebAssembly.Instance(this.module, buildHostImports(ref, state));
|
|
136
162
|
const exports = instance.exports;
|
|
137
163
|
if (typeof exports.render !== 'function')
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { EmailBackendConfig } from 'toiljs/shared';
|
|
2
|
+
import type { ResolvedDaemonConfig } from './daemon/host.js';
|
|
2
3
|
import { type ViteTarget } from './http/proxy.js';
|
|
4
|
+
import { type DevSsrTemplate } from './ssr.js';
|
|
3
5
|
export interface DevServerOptions {
|
|
4
6
|
readonly root: string;
|
|
5
7
|
readonly port: number;
|
|
6
8
|
readonly host?: string;
|
|
7
9
|
readonly wasmFile: string;
|
|
10
|
+
readonly coldWasmFile?: string;
|
|
11
|
+
readonly nodeMode?: string;
|
|
12
|
+
readonly daemon?: ResolvedDaemonConfig;
|
|
8
13
|
readonly vite: ViteTarget;
|
|
9
14
|
readonly maxBodyLength?: number;
|
|
10
15
|
readonly email?: EmailBackendConfig;
|
|
16
|
+
readonly ssrTemplates?: readonly DevSsrTemplate[];
|
|
11
17
|
}
|
|
12
18
|
export interface RunningDevServer {
|
|
13
19
|
readonly port: number;
|