sproutboat 0.7.0 → 0.9.0
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/CHANGELOG.md +304 -0
- package/README.md +33 -1
- package/SURFACE.md +15 -5
- package/package.json +12 -6
- package/src/bearssl.ts +180 -0
- package/src/broker.ts +420 -19
- package/src/build.ts +61 -1
- package/src/compile.ts +94 -14
- package/src/config.ts +25 -0
- package/src/dev.ts +2 -0
- package/src/main.ts +260 -6
- package/src/native-fetch-prelude.js +130 -171
- package/src/patch-porffor.ts +104 -6
- package/src/sqlite.ts +99 -0
- package/src/standalone-build.ts +64 -0
- package/src/surface.ts +54 -5
- package/src/toolchain.ts +71 -0
- package/src/transport-broker.js +299 -0
- package/src/transport-embedded.js +1571 -0
- package/src/types-gen.ts +90 -0
- package/src/wrap.ts +46 -2
- package/types/sproutboat.d.ts +217 -0
- package/vendor/sqlite-amalgamation-3500400/shell.c +34019 -0
- package/vendor/sqlite-amalgamation-3500400/sqlite3.c +262899 -0
- package/vendor/sqlite-amalgamation-3500400/sqlite3.h +13775 -0
- package/vendor/sqlite-amalgamation-3500400/sqlite3ext.h +723 -0
- package/vendor/sqlite-amalgamation.zip +0 -0
package/src/types-gen.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sproutboat types` — the project's `env`, as TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* In Workers `env` is a parameter, so an editor can infer it from a signature.
|
|
5
|
+
* Here it is a global, which means nothing is inferable at all: `env.SESSIONS`
|
|
6
|
+
* is an undeclared identifier until this file exists. That makes generated
|
|
7
|
+
* types worth more here than they are there, not less.
|
|
8
|
+
*
|
|
9
|
+
* The output declares `env` from `sproutboat.jsonc` and references the ambient
|
|
10
|
+
* types shipped in `types/sproutboat.d.ts`, so binding *shapes* version with
|
|
11
|
+
* the CLI and binding *names* version with the project.
|
|
12
|
+
*
|
|
13
|
+
* Names need no quoting: `parseConfig` already holds every binding and var to
|
|
14
|
+
* /^[A-Z][A-Z0-9_]*$/, so all of them are plain identifiers.
|
|
15
|
+
*/
|
|
16
|
+
import { resourceRefs, type SproutboatConfig } from "./config";
|
|
17
|
+
|
|
18
|
+
/** The file the generator writes, relative to the project directory. */
|
|
19
|
+
export const TYPES_FILE = "sproutboat-env.d.ts";
|
|
20
|
+
|
|
21
|
+
const HEADER = `// Generated by \`sproutboat types\`. Do not edit.
|
|
22
|
+
//
|
|
23
|
+
// Regenerated whenever sproutboat.jsonc is newer than this file, so a binding
|
|
24
|
+
// added to the config shows up in the editor without asking. Commit it: it is
|
|
25
|
+
// part of the project's type surface, like a schema.
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
type Entry = { name: string; type: string; note?: string };
|
|
29
|
+
|
|
30
|
+
function entries(config: SproutboatConfig): Entry[] {
|
|
31
|
+
const out: Entry[] = [];
|
|
32
|
+
|
|
33
|
+
for (const [name, value] of Object.entries(config.vars ?? {})) {
|
|
34
|
+
out.push({ name, type: "string", note: `vars: ${JSON.stringify(value)}` });
|
|
35
|
+
}
|
|
36
|
+
for (const name of config.secrets ?? []) {
|
|
37
|
+
out.push({ name, type: "string", note: "secret, set with `sproutboat secrets set`" });
|
|
38
|
+
}
|
|
39
|
+
for (const { binding } of resourceRefs(config.kv_namespaces)) out.push({ name: binding, type: "KVNamespace" });
|
|
40
|
+
for (const { binding } of resourceRefs(config.d1_databases)) out.push({ name: binding, type: "D1Database" });
|
|
41
|
+
for (const { binding } of resourceRefs(config.r2_buckets)) out.push({ name: binding, type: "R2Bucket" });
|
|
42
|
+
for (const { binding } of resourceRefs(config.queues)) out.push({ name: binding, type: "Queue" });
|
|
43
|
+
for (const name of config.analytics_engine_datasets ?? []) {
|
|
44
|
+
out.push({ name, type: "AnalyticsEngineDataset" });
|
|
45
|
+
}
|
|
46
|
+
for (const [binding, className] of Object.entries(config.durable_objects ?? {})) {
|
|
47
|
+
out.push({ name: binding, type: "DurableObjectNamespace", note: `class ${className}` });
|
|
48
|
+
}
|
|
49
|
+
for (const service of config.services ?? []) {
|
|
50
|
+
out.push({ name: service.binding, type: "Fetcher", note: `service binding: ${service.service}` });
|
|
51
|
+
}
|
|
52
|
+
if (config.assets?.binding) {
|
|
53
|
+
out.push({ name: config.assets.binding, type: "Fetcher", note: `static assets: ${config.assets.directory}` });
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The contents of `sproutboat-env.d.ts` for `config`.
|
|
60
|
+
*
|
|
61
|
+
* Pure, so the test can assert on the text and `dev` can compare it against
|
|
62
|
+
* what is already on disk without writing.
|
|
63
|
+
*/
|
|
64
|
+
export function generateTypes(config: SproutboatConfig): string {
|
|
65
|
+
const rows = entries(config);
|
|
66
|
+
const body = rows.length
|
|
67
|
+
? rows
|
|
68
|
+
.map((entry) => ` ${entry.note ? `/** ${entry.note} */\n ` : ""}${entry.name}: ${entry.type};`)
|
|
69
|
+
.join("\n")
|
|
70
|
+
: " // No bindings declared in sproutboat.jsonc yet.";
|
|
71
|
+
|
|
72
|
+
return `${HEADER}
|
|
73
|
+
/// <reference types="sproutboat/types" />
|
|
74
|
+
|
|
75
|
+
declare global {
|
|
76
|
+
/**
|
|
77
|
+
* Bindings from sproutboat.jsonc, for project "${config.name}".
|
|
78
|
+
*
|
|
79
|
+
* A global, not a parameter: the handler signature is \`fetch(request)\`.
|
|
80
|
+
* Every call on these is synchronous, so \`await\` is allowed and does
|
|
81
|
+
* nothing.
|
|
82
|
+
*/
|
|
83
|
+
const env: {
|
|
84
|
+
${body}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {};
|
|
89
|
+
`;
|
|
90
|
+
}
|
package/src/wrap.ts
CHANGED
|
@@ -13,6 +13,23 @@
|
|
|
13
13
|
* imported — callers do `readFile(preludePath, "utf8")`. */
|
|
14
14
|
export const preludePath = new URL("./native-fetch-prelude.js", import.meta.url);
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* #15 — the two transports the prelude can be built with.
|
|
18
|
+
*
|
|
19
|
+
* Both define `__sbCall(reqJson) -> replyJson` and nothing else; every binding
|
|
20
|
+
* shim above that line is identical, which is what lets one conformance suite
|
|
21
|
+
* hold both honest. `broker` talks to the per-deployment broker over loopback
|
|
22
|
+
* (deployed, dev, phase-0 standalone); `embedded` compiles SQLite into the
|
|
23
|
+
* sprout and needs no second process at all.
|
|
24
|
+
*/
|
|
25
|
+
export type Transport = "broker" | "embedded";
|
|
26
|
+
export const transportPath = (transport: Transport): URL =>
|
|
27
|
+
new URL(transport === "embedded" ? "./transport-embedded.js" : "./transport-broker.js", import.meta.url);
|
|
28
|
+
|
|
29
|
+
/** Where the prelude expects its transport spliced in. */
|
|
30
|
+
export const TRANSPORT_MARKER =
|
|
31
|
+
"// TRANSPORT: wrap.ts splices one of transport-broker.js / transport-embedded.js here.";
|
|
32
|
+
|
|
16
33
|
// The server honours $PORT at runtime (patches/porffor-render.patch); this baked
|
|
17
34
|
// value is only a fallback for a directly-run binary.
|
|
18
35
|
const DEFAULT_PORT = 8080;
|
|
@@ -44,6 +61,9 @@ export type Bindings = {
|
|
|
44
61
|
queues: string[];
|
|
45
62
|
analytics: string[];
|
|
46
63
|
do: Array<{ binding: string; className: string }>;
|
|
64
|
+
/** #48 — worker-to-worker: binding name -> the project it calls. The hostname
|
|
65
|
+
* it resolves to is a runtime input, not part of the artifact. */
|
|
66
|
+
services: Array<{ binding: string; service: string }>;
|
|
47
67
|
crons: string[];
|
|
48
68
|
/** Static-asset binding name for `env.<NAME>.fetch(request)`; `""` when assets are edge-only. */
|
|
49
69
|
assets: string;
|
|
@@ -58,6 +78,7 @@ export const EMPTY_BINDINGS: Bindings = {
|
|
|
58
78
|
queues: [],
|
|
59
79
|
analytics: [],
|
|
60
80
|
do: [],
|
|
81
|
+
services: [],
|
|
61
82
|
crons: [],
|
|
62
83
|
assets: "",
|
|
63
84
|
};
|
|
@@ -72,6 +93,7 @@ function hasBindings(b: Bindings): boolean {
|
|
|
72
93
|
b.queues.length > 0 ||
|
|
73
94
|
b.analytics.length > 0 ||
|
|
74
95
|
b.do.length > 0 ||
|
|
96
|
+
b.services.length > 0 ||
|
|
75
97
|
b.assets !== ""
|
|
76
98
|
);
|
|
77
99
|
}
|
|
@@ -142,6 +164,9 @@ export function wrapNativeFetchHandler(
|
|
|
142
164
|
bindings: Bindings = EMPTY_BINDINGS,
|
|
143
165
|
port: number = DEFAULT_PORT,
|
|
144
166
|
compatibilityDate: string = BASELINE_COMPATIBILITY_DATE,
|
|
167
|
+
appName: string = "app",
|
|
168
|
+
/** #15 — assets baked into the module for a binary that has no files beside it. */
|
|
169
|
+
assets?: { manifest: unknown; files: Record<string, string> },
|
|
145
170
|
): string {
|
|
146
171
|
const neutralised = neutraliseExports(source);
|
|
147
172
|
if (neutralised === null || !/\bfetch\s*\(/.test(source)) {
|
|
@@ -151,16 +176,26 @@ export function wrapNativeFetchHandler(
|
|
|
151
176
|
const env = `const env = ${JSON.stringify(vars)};\nglobalThis.env = env;\n`;
|
|
152
177
|
// Baked, not a binding: the date belongs to the artifact, and a handler must
|
|
153
178
|
// not be able to change the semantics it was compiled against at runtime.
|
|
154
|
-
const compat =
|
|
179
|
+
const compat =
|
|
180
|
+
`globalThis.__sbCompat = ${JSON.stringify(compatibilityDate)};\n` +
|
|
181
|
+
// #15 — the embedded transport derives its default data directory from this.
|
|
182
|
+
`globalThis.__sbAppName = ${JSON.stringify(appName)};\n` +
|
|
183
|
+
// #15 — and enforces the outbound allowlist itself, with no broker to do it.
|
|
184
|
+
`globalThis.__sbOutbound = ${JSON.stringify(bindings.outbound)};\n` +
|
|
185
|
+
(assets ? `globalThis.__sbAssets = ${JSON.stringify(assets)};\n` : "");
|
|
155
186
|
const wire = hasBindings(bindings) ? `__sbInstallBindings(env, ${JSON.stringify(bindings)});\n` : "";
|
|
156
187
|
const registerDO = bindings.do.length
|
|
157
188
|
? `__sbRegisterDO({ ${bindings.do.map((d) => `${d.className}: ${d.className}`).join(", ")} });\n`
|
|
158
189
|
: "";
|
|
190
|
+
// Cron / queue / alarm timers, for a transport that has no broker to deliver
|
|
191
|
+
// them. The broker transport defines this as a no-op, so the emitted module
|
|
192
|
+
// is the same either way.
|
|
193
|
+
const triggers = hasBindings(bindings) ? `__sbStartLocalTriggers(__sbHandlers, ${JSON.stringify(bindings)});\n` : "";
|
|
159
194
|
|
|
160
195
|
return (
|
|
161
196
|
`${prelude}\n${compat}${env}${wire}` +
|
|
162
197
|
`${neutralised}\n` +
|
|
163
|
-
`${registerDO}` +
|
|
198
|
+
`${registerDO}${triggers}` +
|
|
164
199
|
`export default {\n port: ${port},\n fetch(request) { return __sbEntry(__sbHandlers, request); }\n};\n`
|
|
165
200
|
);
|
|
166
201
|
}
|
|
@@ -200,6 +235,14 @@ export function readBindingsFromEnv(): Bindings {
|
|
|
200
235
|
const parsed: VarsJson = JSON.parse(raw);
|
|
201
236
|
if (!isVarsObject(parsed)) throw new Error("SPROUTBOAT_BINDINGS_JSON must be a JSON object");
|
|
202
237
|
const strings = (v: VarsJson): string[] => (Array.isArray(v) ? v.filter(isVarsString) : []);
|
|
238
|
+
const services: Array<{ binding: string; service: string }> = [];
|
|
239
|
+
if (Array.isArray(parsed.services)) {
|
|
240
|
+
for (const entry of parsed.services) {
|
|
241
|
+
if (isVarsObject(entry) && isVarsString(entry.binding) && isVarsString(entry.service)) {
|
|
242
|
+
services.push({ binding: entry.binding, service: entry.service });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
203
246
|
const dos: Array<{ binding: string; className: string }> = [];
|
|
204
247
|
if (Array.isArray(parsed.do)) {
|
|
205
248
|
for (const entry of parsed.do) {
|
|
@@ -217,6 +260,7 @@ export function readBindingsFromEnv(): Bindings {
|
|
|
217
260
|
queues: strings(parsed.queues),
|
|
218
261
|
analytics: strings(parsed.analytics),
|
|
219
262
|
do: dos,
|
|
263
|
+
services,
|
|
220
264
|
crons: strings(parsed.crons),
|
|
221
265
|
assets: isVarsString(parsed.assets) ? parsed.assets : "",
|
|
222
266
|
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient types for a Sproutboat handler.
|
|
3
|
+
*
|
|
4
|
+
* Shipped with the CLI rather than as a separate package, so the types always
|
|
5
|
+
* describe the runtime that the installed CLI compiles. `sproutboat types`
|
|
6
|
+
* generates a project's `sproutboat-env.d.ts`, which references this file and
|
|
7
|
+
* declares `env` with that project's bindings.
|
|
8
|
+
*
|
|
9
|
+
* Two things differ from Cloudflare Workers and are the reason these exist:
|
|
10
|
+
* `env` is a global rather than a parameter, so nothing is inferable from a
|
|
11
|
+
* signature, and every binding call is synchronous.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
// ---------------------------------------------------------------- KV
|
|
16
|
+
|
|
17
|
+
interface KVNamespace {
|
|
18
|
+
/** The stored string, or `null`. Synchronous: no `await`. */
|
|
19
|
+
get(key: string): string | null;
|
|
20
|
+
put(key: string, value: string): void;
|
|
21
|
+
delete(key: string): void;
|
|
22
|
+
/** Keys under `prefix` (all keys when omitted). */
|
|
23
|
+
list(prefix?: string): string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------- D1
|
|
27
|
+
|
|
28
|
+
interface D1Meta {
|
|
29
|
+
/** `INSERT` only. The autoincrement id of the row just written. */
|
|
30
|
+
last_row_id: number;
|
|
31
|
+
changes: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface D1Result<T = Record<string, unknown>> {
|
|
35
|
+
results: T[];
|
|
36
|
+
success: boolean;
|
|
37
|
+
meta: D1Meta;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface D1PreparedStatement {
|
|
41
|
+
/** Positional `?` parameters, in order. */
|
|
42
|
+
bind(...values: Array<string | number | boolean | null>): D1PreparedStatement;
|
|
43
|
+
all<T = Record<string, unknown>>(): D1Result<T>;
|
|
44
|
+
run(): D1Result;
|
|
45
|
+
/** The first row, or one column of it when named. `null` if there are none. */
|
|
46
|
+
first<T = Record<string, unknown>>(): T | null;
|
|
47
|
+
first<T = unknown>(column: string): T | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface D1Database {
|
|
51
|
+
prepare(sql: string): D1PreparedStatement;
|
|
52
|
+
batch(statements: D1PreparedStatement[]): D1Result[];
|
|
53
|
+
/** Runs a whole script. Use this for multi-statement DDL. */
|
|
54
|
+
exec(sql: string): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------- R2
|
|
58
|
+
|
|
59
|
+
interface R2Object {
|
|
60
|
+
key: string;
|
|
61
|
+
size: number;
|
|
62
|
+
etag: string;
|
|
63
|
+
uploaded: string;
|
|
64
|
+
customMetadata?: Record<string, string>;
|
|
65
|
+
/**
|
|
66
|
+
* The object as a string. An object is held whole in memory on the way in
|
|
67
|
+
* and on the way out, so keep them small.
|
|
68
|
+
*/
|
|
69
|
+
body: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type R2ObjectHead = Omit<R2Object, "body">;
|
|
73
|
+
|
|
74
|
+
interface R2Objects {
|
|
75
|
+
objects: R2ObjectHead[];
|
|
76
|
+
truncated: boolean;
|
|
77
|
+
cursor?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface R2PutOptions {
|
|
81
|
+
customMetadata?: Record<string, string>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface R2ListOptions {
|
|
85
|
+
prefix?: string;
|
|
86
|
+
limit?: number;
|
|
87
|
+
cursor?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface R2Bucket {
|
|
91
|
+
put(key: string, value: string, options?: R2PutOptions): void;
|
|
92
|
+
get(key: string): R2Object | null;
|
|
93
|
+
/** Metadata without the body. */
|
|
94
|
+
head(key: string): R2ObjectHead | null;
|
|
95
|
+
delete(key: string): void;
|
|
96
|
+
list(options?: R2ListOptions): R2Objects;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ------------------------------------------------------------ Queues
|
|
100
|
+
|
|
101
|
+
interface QueueSendOptions {
|
|
102
|
+
delaySeconds?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface Queue<Body = unknown> {
|
|
106
|
+
/** Returns immediately. The batch reaches `queue()` out of band. */
|
|
107
|
+
send(body: Body, options?: QueueSendOptions): void;
|
|
108
|
+
sendBatch(messages: Array<{ body: Body }>): void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface QueueMessage<Body = unknown> {
|
|
112
|
+
id: string;
|
|
113
|
+
timestamp: number;
|
|
114
|
+
body: Body;
|
|
115
|
+
/** Mark as handled. Without this the message is redelivered. */
|
|
116
|
+
ack(): void;
|
|
117
|
+
/** Hand it back for another attempt. */
|
|
118
|
+
retry(): void;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface MessageBatch<Body = unknown> {
|
|
122
|
+
queue: string;
|
|
123
|
+
messages: Array<QueueMessage<Body>>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --------------------------------------------------- Durable Objects
|
|
127
|
+
|
|
128
|
+
interface DurableObjectId {
|
|
129
|
+
toString(): string;
|
|
130
|
+
name?: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface DurableObjectStub {
|
|
134
|
+
fetch(request: Request): Response;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface DurableObjectNamespace {
|
|
138
|
+
idFromName(name: string): DurableObjectId;
|
|
139
|
+
idFromString(hex: string): DurableObjectId;
|
|
140
|
+
newUniqueId(): DurableObjectId;
|
|
141
|
+
get(id: DurableObjectId): DurableObjectStub;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface DurableObjectStorage {
|
|
145
|
+
get<T = unknown>(key: string): T | undefined;
|
|
146
|
+
put<T>(key: string, value: T): void;
|
|
147
|
+
delete(key: string): void;
|
|
148
|
+
deleteAll(): void;
|
|
149
|
+
/** Stored values under `prefix`, keyed by storage key. */
|
|
150
|
+
list<T = unknown>(options?: { prefix?: string; limit?: number }): Map<string, T>;
|
|
151
|
+
/**
|
|
152
|
+
* At most one alarm is pending per object; a later `setAlarm` replaces it.
|
|
153
|
+
* The handler is the class's `alarm()` method.
|
|
154
|
+
*/
|
|
155
|
+
setAlarm(scheduledTime: number): void;
|
|
156
|
+
getAlarm(): number | null;
|
|
157
|
+
deleteAlarm(): void;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface DurableObjectState {
|
|
161
|
+
id: DurableObjectId;
|
|
162
|
+
/** Synchronous, like every other binding. */
|
|
163
|
+
storage: DurableObjectStorage;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// -------------------------------------------------- Analytics Engine
|
|
167
|
+
|
|
168
|
+
interface AnalyticsEngineDataPoint {
|
|
169
|
+
blobs?: string[];
|
|
170
|
+
doubles?: number[];
|
|
171
|
+
indexes?: string[];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface AnalyticsEngineDataset {
|
|
175
|
+
writeDataPoint(event: AnalyticsEngineDataPoint): void;
|
|
176
|
+
query<T = unknown>(options?: { limit?: number }): { count: number; rows: T[] };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ------------------------------------ Fetchers: services and assets
|
|
180
|
+
|
|
181
|
+
/** A service binding, and the shape of the static-asset binding. */
|
|
182
|
+
interface Fetcher {
|
|
183
|
+
fetch(request: Request): Response;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --------------------------------------------------------- Handlers
|
|
187
|
+
|
|
188
|
+
interface ScheduledEvent {
|
|
189
|
+
/** The expression that fired, as written in `triggers.crons`. */
|
|
190
|
+
cron: string;
|
|
191
|
+
scheduledTime: number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The default export.
|
|
196
|
+
*
|
|
197
|
+
* `fetch` takes only a request: `env` is a global, and there is no `ctx`, so
|
|
198
|
+
* there is no `ctx.waitUntil`. Use a queue for work that outlives a response.
|
|
199
|
+
*/
|
|
200
|
+
interface SproutboatHandler<QueueBody = unknown> {
|
|
201
|
+
fetch(request: Request): Response | Promise<Response>;
|
|
202
|
+
scheduled?(event: ScheduledEvent): void | Promise<void>;
|
|
203
|
+
queue?(batch: MessageBatch<QueueBody>): void | Promise<void>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* What a Durable Object class implements. Declare it above the default
|
|
208
|
+
* export and name it in `durable_objects`.
|
|
209
|
+
*/
|
|
210
|
+
interface DurableObject {
|
|
211
|
+
fetch(request: Request): Response | Promise<Response>;
|
|
212
|
+
/** Runs after `storage.setAlarm`, with no request in flight. */
|
|
213
|
+
alarm?(): void | Promise<void>;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export {};
|