toiljs 0.0.59 → 0.0.61

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.
Files changed (158) hide show
  1. package/.github/workflows/ci.yml +31 -0
  2. package/CHANGELOG.md +15 -0
  3. package/build/cli/.tsbuildinfo +1 -1
  4. package/build/cli/index.js +311 -118
  5. package/build/client/.tsbuildinfo +1 -1
  6. package/build/client/index.d.ts +1 -1
  7. package/build/client/index.js +1 -1
  8. package/build/client/routing/mount.js +12 -1
  9. package/build/client/ssr/markers.d.ts +1 -0
  10. package/build/client/ssr/markers.js +3 -0
  11. package/build/compiler/.tsbuildinfo +1 -1
  12. package/build/compiler/config.d.ts +21 -0
  13. package/build/compiler/config.js +35 -0
  14. package/build/compiler/docs.d.ts +2 -1
  15. package/build/compiler/docs.js +33 -304
  16. package/build/compiler/index.d.ts +13 -0
  17. package/build/compiler/index.js +113 -21
  18. package/build/compiler/template-build.d.ts +21 -1
  19. package/build/compiler/template-build.js +110 -26
  20. package/build/compiler/toil-docs.generated.d.ts +1 -0
  21. package/build/compiler/toil-docs.generated.js +20 -0
  22. package/build/devserver/.tsbuildinfo +1 -1
  23. package/build/devserver/daemon/catalog.d.ts +26 -0
  24. package/build/devserver/daemon/catalog.js +48 -0
  25. package/build/devserver/daemon/cron.d.ts +4 -0
  26. package/build/devserver/daemon/cron.js +50 -0
  27. package/build/devserver/daemon/host.d.ts +37 -0
  28. package/build/devserver/daemon/host.js +94 -0
  29. package/build/devserver/daemon/index.d.ts +34 -0
  30. package/build/devserver/daemon/index.js +241 -0
  31. package/build/devserver/db/catalog.d.ts +2 -0
  32. package/build/devserver/db/catalog.js +80 -0
  33. package/build/devserver/db/database.d.ts +80 -0
  34. package/build/devserver/db/database.js +1032 -0
  35. package/build/devserver/db/index.d.ts +3 -0
  36. package/build/devserver/db/index.js +3 -0
  37. package/build/devserver/db/routeKinds.d.ts +8 -0
  38. package/build/devserver/db/routeKinds.js +139 -0
  39. package/build/devserver/db/types.d.ts +121 -0
  40. package/build/devserver/db/types.js +52 -0
  41. package/build/devserver/email/index.js +1 -1
  42. package/build/devserver/index.d.ts +19 -24
  43. package/build/devserver/index.js +11 -165
  44. package/build/devserver/mstore/store.d.ts +18 -0
  45. package/build/devserver/mstore/store.js +82 -0
  46. package/build/devserver/{host.d.ts → runtime/host.d.ts} +7 -1
  47. package/build/devserver/{host.js → runtime/host.js} +51 -7
  48. package/build/devserver/{module.d.ts → runtime/module.d.ts} +2 -1
  49. package/build/devserver/{module.js → runtime/module.js} +34 -1
  50. package/build/devserver/server.d.ts +23 -0
  51. package/build/devserver/server.js +223 -0
  52. package/build/devserver/ssr.d.ts +25 -0
  53. package/build/devserver/ssr.js +114 -0
  54. package/build/devserver/wasm/sections.d.ts +2 -0
  55. package/build/devserver/wasm/sections.js +42 -0
  56. package/build/devserver/wasm/surface.d.ts +18 -0
  57. package/build/devserver/wasm/surface.js +41 -0
  58. package/docs/README.md +4 -4
  59. package/docs/auth-todo.md +6 -6
  60. package/docs/caching.md +5 -5
  61. package/docs/cli.md +15 -0
  62. package/docs/client.md +40 -0
  63. package/docs/crypto.md +4 -4
  64. package/docs/data.md +6 -6
  65. package/docs/email.md +28 -28
  66. package/docs/environment.md +10 -10
  67. package/docs/index.md +26 -0
  68. package/docs/ratelimit.md +10 -10
  69. package/docs/routing.md +2 -2
  70. package/docs/server.md +61 -0
  71. package/docs/ssr.md +561 -113
  72. package/docs/styling.md +22 -0
  73. package/docs/time.md +3 -3
  74. package/eslint.config.js +10 -1
  75. package/examples/basic/client/components/Header.tsx +3 -0
  76. package/examples/basic/client/routes/features/actions.tsx +0 -2
  77. package/examples/basic/client/routes/hello.tsx +89 -19
  78. package/examples/basic/client/styles/main.css +48 -0
  79. package/examples/basic/server/SsrHelloRender.ts +97 -0
  80. package/examples/basic/server/main.ts +5 -0
  81. package/examples/basic/server/migrations/GuestEntry.migration.ts +39 -0
  82. package/examples/basic/server/streams/Echo.ts +49 -0
  83. package/package.json +12 -10
  84. package/scripts/gen-toil-docs.mjs +96 -0
  85. package/server/runtime/time.ts +3 -3
  86. package/src/cli/create.ts +40 -3
  87. package/src/cli/db.ts +158 -0
  88. package/src/cli/diagnostics.ts +19 -0
  89. package/src/cli/doctor.ts +20 -0
  90. package/src/cli/index.ts +10 -0
  91. package/src/cli/update.ts +58 -0
  92. package/src/client/index.ts +1 -1
  93. package/src/client/routing/mount.tsx +18 -2
  94. package/src/client/ssr/markers.tsx +22 -0
  95. package/src/compiler/config.ts +88 -2
  96. package/src/compiler/docs.ts +47 -308
  97. package/src/compiler/index.ts +236 -32
  98. package/src/compiler/ssr-codegen.ts +1 -1
  99. package/src/compiler/template-build.ts +247 -46
  100. package/src/compiler/toil-docs.generated.ts +26 -0
  101. package/src/devserver/daemon/catalog.ts +120 -0
  102. package/src/devserver/daemon/cron.ts +87 -0
  103. package/src/devserver/daemon/host.ts +224 -0
  104. package/src/devserver/daemon/index.ts +349 -0
  105. package/src/devserver/db/catalog.ts +108 -0
  106. package/src/devserver/db/database.ts +1633 -0
  107. package/src/devserver/db/index.ts +18 -0
  108. package/src/devserver/db/routeKinds.ts +147 -0
  109. package/src/devserver/db/types.ts +139 -0
  110. package/src/devserver/email/index.ts +1 -1
  111. package/src/devserver/index.ts +31 -287
  112. package/src/devserver/mstore/store.ts +121 -0
  113. package/src/devserver/{host.ts → runtime/host.ts} +98 -7
  114. package/src/devserver/{module.ts → runtime/module.ts} +47 -1
  115. package/src/devserver/server.ts +393 -0
  116. package/src/devserver/ssr.ts +166 -0
  117. package/src/devserver/wasm/sections.ts +59 -0
  118. package/src/devserver/wasm/surface.ts +88 -0
  119. package/test/daemon-build.test.ts +198 -0
  120. package/test/daemon-catalog.test.ts +265 -0
  121. package/test/daemon-emulation.test.ts +216 -0
  122. package/test/db.test.ts +0 -0
  123. package/test/devserver-database.test.ts +510 -14
  124. package/test/devserver-pqauth.test.ts +1 -1
  125. package/test/devserver-secrets.test.ts +5 -1
  126. package/test/doctor.test.ts +13 -0
  127. package/test/email-preview.test.ts +6 -1
  128. package/test/example-guestbook.test.ts +43 -1
  129. package/test/fixtures/daemon-app.ts +56 -0
  130. package/test/global-setup.ts +17 -0
  131. package/test/pqauth-e2e.test.ts +1 -1
  132. package/test/ssr-render.test.ts +94 -27
  133. package/test/ssr-template.test.tsx +44 -1
  134. package/vitest.config.ts +3 -0
  135. package/build/devserver/database.d.ts +0 -8
  136. package/build/devserver/database.js +0 -418
  137. package/src/devserver/database.ts +0 -618
  138. /package/build/devserver/{dotenv.d.ts → config/dotenv.d.ts} +0 -0
  139. /package/build/devserver/{dotenv.js → config/dotenv.js} +0 -0
  140. /package/build/devserver/{env.d.ts → config/env.d.ts} +0 -0
  141. /package/build/devserver/{env.js → config/env.js} +0 -0
  142. /package/build/devserver/{ratelimit.d.ts → config/ratelimit.d.ts} +0 -0
  143. /package/build/devserver/{ratelimit.js → config/ratelimit.js} +0 -0
  144. /package/build/devserver/{cache.d.ts → http/cache.d.ts} +0 -0
  145. /package/build/devserver/{cache.js → http/cache.js} +0 -0
  146. /package/build/devserver/{envelope.d.ts → http/envelope.d.ts} +0 -0
  147. /package/build/devserver/{envelope.js → http/envelope.js} +0 -0
  148. /package/build/devserver/{proxy.d.ts → http/proxy.d.ts} +0 -0
  149. /package/build/devserver/{proxy.js → http/proxy.js} +0 -0
  150. /package/build/devserver/{crypto.d.ts → runtime/crypto.d.ts} +0 -0
  151. /package/build/devserver/{crypto.js → runtime/crypto.js} +0 -0
  152. /package/src/devserver/{dotenv.ts → config/dotenv.ts} +0 -0
  153. /package/src/devserver/{env.ts → config/env.ts} +0 -0
  154. /package/src/devserver/{ratelimit.ts → config/ratelimit.ts} +0 -0
  155. /package/src/devserver/{cache.ts → http/cache.ts} +0 -0
  156. /package/src/devserver/{envelope.ts → http/envelope.ts} +0 -0
  157. /package/src/devserver/{proxy.ts → http/proxy.ts} +0 -0
  158. /package/src/devserver/{crypto.ts → runtime/crypto.ts} +0 -0
@@ -0,0 +1,3 @@
1
+ export { __resetDbForTests, __setDbCatalogForTests, buildDatabaseImports, configureDbPersistence, DevDatabase, devDb, persistDb, setDbCatalog, } from './database.js';
2
+ export { parseCatalog } from './catalog.js';
3
+ export { CollectionFamily, DbFunctionKind, type DbDevState, freshDbState } from './types.js';
@@ -0,0 +1,3 @@
1
+ export { __resetDbForTests, __setDbCatalogForTests, buildDatabaseImports, configureDbPersistence, DevDatabase, devDb, persistDb, setDbCatalog, } from './database.js';
2
+ export { parseCatalog } from './catalog.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
+ }
@@ -0,0 +1,121 @@
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
+ };
35
+ export interface DbDevState {
36
+ handles: DevCollectionHandle[];
37
+ lastResult: Buffer | null;
38
+ lastResultVersion: number;
39
+ functionKind: DbFunctionKind;
40
+ }
41
+ export declare function freshDbState(): DbDevState;
42
+ export interface Reservation {
43
+ amount: bigint;
44
+ expiresMs: number;
45
+ confirmed: boolean;
46
+ }
47
+ export interface CapLedger {
48
+ total: bigint;
49
+ reservations: Map<bigint, Reservation>;
50
+ nextId: bigint;
51
+ }
52
+ export interface DbSnapshot {
53
+ store: Record<string, {
54
+ v: string;
55
+ sv: number;
56
+ }>;
57
+ recordIdem?: Record<string, {
58
+ requestHash: string;
59
+ state: 'pending' | 'done';
60
+ outcome?: RecordOutcomeSnapshot;
61
+ }>;
62
+ uniqueIdem?: Record<string, string>;
63
+ views: Record<string, {
64
+ v: string;
65
+ sv: number;
66
+ }>;
67
+ members: Record<string, Record<string, {
68
+ v: string;
69
+ sv: number;
70
+ }>>;
71
+ counters: Record<string, string>;
72
+ counterIdem?: Record<string, string>;
73
+ events: Record<string, {
74
+ v: string;
75
+ sv: number;
76
+ }[]>;
77
+ eventDedup: Record<string, string[]>;
78
+ capacity: Record<string, {
79
+ total: string;
80
+ nextId: string;
81
+ reservations: [string, {
82
+ amount: string;
83
+ expiresMs: number;
84
+ confirmed: boolean;
85
+ }][];
86
+ }>;
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
+ };
103
+ export declare const MAX_RESERVATIONS = 4096;
104
+ export declare const MAX_RESERVATION_TTL_MS = 86400000;
105
+ export declare const MAX_NAME = 512;
106
+ export declare const MAX_KEY = 4096;
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;
110
+ export declare function satI64(v: bigint): bigint;
111
+ export declare const ABSENT = -2;
112
+ export declare const TOO_SMALL = -1;
113
+ export declare const INVALID_HANDLE = -1001;
114
+ export declare const ALREADY_EXISTS = -1003;
115
+ export declare const CONFLICT = -1004;
116
+ export declare const UNAVAILABLE = -1031;
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;
120
+ export declare const TOO_MANY_KEYS = -1020;
121
+ export declare const SCHEMA_UNAVAILABLE = -1070;
@@ -0,0 +1,52 @@
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
+ }
22
+ export function freshDbState() {
23
+ return {
24
+ handles: [],
25
+ lastResult: null,
26
+ lastResultVersion: -1,
27
+ functionKind: DbFunctionKind.Job,
28
+ };
29
+ }
30
+ export const MAX_RESERVATIONS = 4096;
31
+ export const MAX_RESERVATION_TTL_MS = 86_400_000;
32
+ export const MAX_NAME = 512;
33
+ export const MAX_KEY = 4096;
34
+ export const MAX_VALUE = 256 * 1024;
35
+ export const DEFAULT_FILL_WAIT_MS = 50;
36
+ export const MAX_FILL_WAIT_MS = 60_000;
37
+ const I64_MIN = -(2n ** 63n);
38
+ const I64_MAX = 2n ** 63n - 1n;
39
+ export function satI64(v) {
40
+ return v < I64_MIN ? I64_MIN : v > I64_MAX ? I64_MAX : v;
41
+ }
42
+ export const ABSENT = -2;
43
+ export const TOO_SMALL = -1;
44
+ export const INVALID_HANDLE = -1001;
45
+ export const ALREADY_EXISTS = -1003;
46
+ export const CONFLICT = -1004;
47
+ export const UNAVAILABLE = -1031;
48
+ export const CODEC_ERR = -1006;
49
+ export const OP_NOT_ALLOWED_FOR_FAMILY = -1010;
50
+ export const OP_NOT_ALLOWED_IN_KIND = -1011;
51
+ export const TOO_MANY_KEYS = -1020;
52
+ export const SCHEMA_UNAVAILABLE = -1070;
@@ -1,4 +1,4 @@
1
- import { loadEnvFiles } from '../dotenv.js';
1
+ import { loadEnvFiles } from '../config/dotenv.js';
2
2
  import { EmailCaps } from './caps.js';
3
3
  import { resolveEmailConfig } from './config.js';
4
4
  import { sendVia } from './providers.js';
@@ -1,24 +1,19 @@
1
- import type { EmailBackendConfig } from 'toiljs/shared';
2
- import { type ViteTarget } from './proxy.js';
3
- export { METHOD_CODES, encodeRequestEnvelope, decodeResponseEnvelope, unpackHandleResult, } from './envelope.js';
4
- export type { EnvelopeRequest, EnvelopeResponse } from './envelope.js';
5
- export { WasmServerModule, WasmAbortError, UNHANDLED_HEADER } from './module.js';
6
- export type { WasmDispatchResult } from './module.js';
7
- export { buildHostImports, freshDispatchState } from './host.js';
8
- export type { DispatchState, MemoryRef } from './host.js';
9
- export type { ViteTarget } from './proxy.js';
10
- export interface DevServerOptions {
11
- readonly root: string;
12
- readonly port: number;
13
- readonly host?: string;
14
- readonly wasmFile: string;
15
- readonly vite: ViteTarget;
16
- readonly maxBodyLength?: number;
17
- readonly email?: EmailBackendConfig;
18
- }
19
- export interface RunningDevServer {
20
- readonly port: number;
21
- readonly host: string;
22
- close(): Promise<void>;
23
- }
24
- export declare function startDevServer(options: DevServerOptions): Promise<RunningDevServer>;
1
+ export { startDevServer } from './server.js';
2
+ export type { DevServerOptions, RunningDevServer } from './server.js';
3
+ export { METHOD_CODES, encodeRequestEnvelope, decodeResponseEnvelope, unpackHandleResult, } from './http/envelope.js';
4
+ export type { EnvelopeRequest, EnvelopeResponse } from './http/envelope.js';
5
+ export { WasmServerModule, WasmAbortError, UNHANDLED_HEADER } from './runtime/module.js';
6
+ export type { WasmDispatchResult } from './runtime/module.js';
7
+ export { buildHostImports, freshDispatchState } from './runtime/host.js';
8
+ export type { DispatchState, MemoryRef } from './runtime/host.js';
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';
@@ -1,165 +1,11 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { Server } from '@dacely/hyper-express';
4
- import pc from 'picocolors';
5
- import { applyCacheRule, lookupCache } from './cache.js';
6
- import { initEmailService } from './email/index.js';
7
- import { METHOD_CODES } from './envelope.js';
8
- import { WasmServerModule } from './module.js';
9
- import { proxyToVite, wireWebsocketProxy } from './proxy.js';
10
- export { METHOD_CODES, encodeRequestEnvelope, decodeResponseEnvelope, unpackHandleResult, } from './envelope.js';
11
- export { WasmServerModule, WasmAbortError, UNHANDLED_HEADER } from './module.js';
12
- export { buildHostImports, freshDispatchState } from './host.js';
13
- const DEFAULT_MAX_BODY_LENGTH = 1024 * 1024 * 8;
14
- const VITE_PREFIXES = ['/@', '/node_modules/', '/__toil/'];
15
- const MIME = {
16
- '.html': 'text/html; charset=utf-8',
17
- '.js': 'text/javascript; charset=utf-8',
18
- '.mjs': 'text/javascript; charset=utf-8',
19
- '.css': 'text/css; charset=utf-8',
20
- '.json': 'application/json; charset=utf-8',
21
- '.txt': 'text/plain; charset=utf-8',
22
- '.svg': 'image/svg+xml',
23
- '.png': 'image/png',
24
- '.jpg': 'image/jpeg',
25
- '.jpeg': 'image/jpeg',
26
- '.webp': 'image/webp',
27
- '.avif': 'image/avif',
28
- '.gif': 'image/gif',
29
- '.ico': 'image/x-icon',
30
- '.wasm': 'application/wasm',
31
- '.woff2': 'font/woff2',
32
- };
33
- function isViteInternal(url) {
34
- return VITE_PREFIXES.some((p) => url.startsWith(p));
35
- }
36
- function resolveSendfile(root, file) {
37
- const resolved = path.resolve(root, file);
38
- if (resolved !== root && !resolved.startsWith(root + path.sep))
39
- return null;
40
- if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile())
41
- return null;
42
- return resolved;
43
- }
44
- async function toEnvelopeRequest(request) {
45
- const hasBody = request.method !== 'GET' && request.method !== 'HEAD';
46
- const body = hasBody ? new Uint8Array(await request.buffer()) : new Uint8Array(0);
47
- const xff = request.headers['x-forwarded-for'];
48
- const clientIp = typeof xff === 'string' && xff.length > 0 ? xff.split(',')[0].trim() : '127.0.0.1';
49
- return {
50
- method: request.method,
51
- path: request.url,
52
- headers: Object.entries(request.headers),
53
- body,
54
- clientIp,
55
- };
56
- }
57
- function sendWasmResponse(response, root, result) {
58
- response.status(result.status);
59
- let hasContentType = false;
60
- for (const [name, value] of result.headers) {
61
- if (name.toLowerCase() === 'content-type')
62
- hasContentType = true;
63
- response.header(name, value);
64
- }
65
- response.header('server', 'toil-dev');
66
- if (result.sendfile !== null) {
67
- const file = resolveSendfile(root, result.sendfile);
68
- if (file === null) {
69
- response.status(404).send('not found\n');
70
- return;
71
- }
72
- if (!hasContentType) {
73
- response.header('content-type', MIME[path.extname(file).toLowerCase()] ?? 'application/octet-stream');
74
- }
75
- response.sendFile(file);
76
- return;
77
- }
78
- if (!hasContentType)
79
- response.header('content-type', 'text/plain; charset=utf-8');
80
- response.send(Buffer.from(result.body.buffer, result.body.byteOffset, result.body.length));
81
- }
82
- export async function startDevServer(options) {
83
- const host = options.host ?? '127.0.0.1';
84
- const root = path.resolve(options.root);
85
- const emailInit = initEmailService(root, options.email);
86
- if (emailInit.service !== null) {
87
- process.stdout.write(pc.dim(` ✉ email enabled: ${emailInit.note}`) + '\n');
88
- }
89
- else if (emailInit.note !== null) {
90
- process.stdout.write(pc.yellow(' ! ') + pc.dim(`email off: ${emailInit.note}`) + '\n');
91
- }
92
- const module = new WasmServerModule(options.wasmFile);
93
- let warnedMissing = false;
94
- let loadedOnce = false;
95
- const refresh = () => {
96
- try {
97
- if (module.refresh() && loadedOnce) {
98
- process.stdout.write(pc.green(' ✓ ') + pc.dim('server module reloaded') + '\n');
99
- }
100
- loadedOnce ||= module.available;
101
- }
102
- catch (e) {
103
- process.stdout.write(pc.red(` ✗ server wasm failed to load: ${String(e)}`) + '\n');
104
- }
105
- if (!module.available && !warnedMissing) {
106
- warnedMissing = true;
107
- process.stdout.write(pc.yellow(' ! ') +
108
- pc.dim(`server wasm not found at ${options.wasmFile}; serving client only`) +
109
- '\n');
110
- }
111
- };
112
- refresh();
113
- const app = new Server({
114
- max_body_length: options.maxBodyLength ?? DEFAULT_MAX_BODY_LENGTH,
115
- max_body_buffer: 1024 * 32,
116
- fast_abort: true,
117
- });
118
- app.set_error_handler((_request, response, error) => {
119
- if (response.completed)
120
- return;
121
- response.atomic(() => {
122
- response.status(500).send(`internal error: ${error.message}\n`);
123
- });
124
- });
125
- wireWebsocketProxy(app, options.vite);
126
- app.any('/*', async (request, response) => {
127
- response.removeHeader('uWebSockets');
128
- const dispatchable = !isViteInternal(request.url) && METHOD_CODES[request.method] !== undefined;
129
- if (dispatchable)
130
- refresh();
131
- if (dispatchable && module.available) {
132
- const envelopeReq = await toEnvelopeRequest(request);
133
- const cacheHost = request.headers.host ?? 'dev';
134
- const hasAuth = request.headers.cookie !== undefined || request.headers.authorization !== undefined;
135
- const cached = lookupCache(cacheHost, request.method, request.url, envelopeReq.body);
136
- if (cached !== null) {
137
- sendWasmResponse(response, root, cached);
138
- return;
139
- }
140
- try {
141
- const result = module.dispatch(envelopeReq);
142
- if (!result.unhandled) {
143
- const finalized = applyCacheRule(cacheHost, request.method, request.url, envelopeReq.body, hasAuth, result);
144
- sendWasmResponse(response, root, finalized);
145
- return;
146
- }
147
- }
148
- catch (e) {
149
- process.stdout.write(pc.red(` ✗ ${request.method} ${request.path} server error: ${String(e)}`) +
150
- '\n');
151
- response.status(500).send('internal error\n');
152
- return;
153
- }
154
- }
155
- await proxyToVite(request, response, options.vite);
156
- });
157
- await app.listen(options.port, host);
158
- return {
159
- port: options.port,
160
- host,
161
- close: async () => {
162
- await app.shutdown();
163
- },
164
- };
165
- }
1
+ export { startDevServer } from './server.js';
2
+ export { METHOD_CODES, encodeRequestEnvelope, decodeResponseEnvelope, unpackHandleResult, } from './http/envelope.js';
3
+ export { WasmServerModule, WasmAbortError, UNHANDLED_HEADER } from './runtime/module.js';
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();
@@ -1,5 +1,5 @@
1
+ import { type DbDevState } from '../db/index.js';
1
2
  import { type CryptoState } from './crypto.js';
2
- import { type DbDevState } from './database.js';
3
3
  export declare class WasmAbortError extends Error {
4
4
  constructor(message: string, fileName: string, line: number, column: number);
5
5
  }
@@ -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;