sproutboat 0.36.0 → 0.37.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 CHANGED
@@ -10,6 +10,27 @@ maintained going forward by the `release` skill.
10
10
 
11
11
  ## Unreleased
12
12
 
13
+ ## [0.37.0] - 2026-10-27
14
+
15
+ Built on `@sproutboat/runtime` 0.34.1, `@sproutboat/toolchain` 0.19.1 and
16
+ `@sproutboat/wire` 0.20.2.
17
+
18
+ ### Fixed
19
+
20
+ - **A failed binding call you do not await no longer ends the process.** An un-awaited
21
+ `db.prepare("INSERT ...").run()` that broke a foreign key used to take the whole binary down, even
22
+ with a `try` / `catch` around the call. As on Workers, an unhandled rejection is now logged
23
+ (`Uncaught (in promise) ...`), fires `unhandledrejection` when the app has a listener
24
+ (`addEventListener("unhandledrejection", ...)`), and the process and other requests keep going. An
25
+ un-awaited `async function` that throws is reported the same way. At every compatibility date.
26
+
27
+ ### Changed
28
+
29
+ - **Types for the Promise bindings.** `sproutboat types` gives D1 (from compatibility date 2026-10-24),
30
+ KV (2026-10-25) and R2 (2026-10-26) bindings the Promise types (`D1DatabaseAsync`,
31
+ `KVNamespaceAsync`, `R2BucketAsync`); earlier dates keep the synchronous ones.
32
+ - Docs, examples and type comments no longer say binding calls are synchronous at every date.
33
+
13
34
  ## [0.36.0] - 2026-10-26
14
35
 
15
36
  Built on `@sproutboat/runtime` 0.34.0, `@sproutboat/toolchain` 0.19.0 and
package/README.md CHANGED
@@ -146,7 +146,9 @@ store instead.
146
146
  The handler is `export default { fetch(request) }`. It may import from other
147
147
  files in the project and from its own `node_modules`, and may also export
148
148
  `scheduled(event)` / `queue(batch)` handlers and Durable Object classes. `env`
149
- is a global, not a parameter, and every binding call is synchronous.
149
+ is a global, not a parameter. Binding calls are synchronous until the compatibility
150
+ date that makes each one return a Promise, as on Workers (D1 from 2026-10-24, KV from
151
+ 2026-10-25, R2 from 2026-10-26); `await` works at every date, so always await them.
150
152
  [`examples/kitchen-sink/`](examples/kitchen-sink) uses every binding.
151
153
 
152
154
  For objects larger than one request, use resumable R2 multipart uploads. The
package/SURFACE.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Generated by `src/surface.test.ts` from `src/surface.ts` + the pinned
4
4
  > toolchain constants. Do not edit by hand — run `UPDATE_SURFACE=1 bun test`.
5
5
 
6
- **Package:** `sproutboat` 0.36.0
6
+ **Package:** `sproutboat` 0.37.0
7
7
 
8
8
  ## Commands
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutboat",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "Wrangler-shaped CLI for Sproutboat. Deploys workers to any control plane via --api-url / SPROUTBOAT_API_URL.",
5
5
  "keywords": [
6
6
  "cli",
@@ -64,8 +64,8 @@
64
64
  "@sproutboat/artifact": "^0.3.0",
65
65
  "@sproutboat/assets": "^0.3.0",
66
66
  "@sproutboat/config": "^0.6.0",
67
- "@sproutboat/runtime": "^0.34.0",
68
- "@sproutboat/toolchain": "^0.19.0",
67
+ "@sproutboat/runtime": "^0.34.1",
68
+ "@sproutboat/toolchain": "^0.19.1",
69
69
  "@sproutboat/wire": "^0.20.2"
70
70
  },
71
71
  "devDependencies": {
@@ -78,10 +78,10 @@
78
78
  "typescript": "7.0.2"
79
79
  },
80
80
  "optionalDependencies": {
81
- "@sproutboat/cli-darwin-arm64": "0.36.0",
82
- "@sproutboat/cli-darwin-x64": "0.36.0",
83
- "@sproutboat/cli-linux-arm64": "0.36.0",
84
- "@sproutboat/cli-linux-x64": "0.36.0"
81
+ "@sproutboat/cli-darwin-arm64": "0.37.0",
82
+ "@sproutboat/cli-darwin-x64": "0.37.0",
83
+ "@sproutboat/cli-linux-arm64": "0.37.0",
84
+ "@sproutboat/cli-linux-x64": "0.37.0"
85
85
  },
86
86
  "engines": {
87
87
  "node": ">=18"
package/src/types-gen.ts CHANGED
@@ -26,6 +26,11 @@ const HEADER = `// Generated by \`sproutboat types\`. Do not edit.
26
26
 
27
27
  type Entry = { name: string; type: string; note?: string };
28
28
 
29
+ /** Whether the binding's calls return Promises at the project's compatibility date. */
30
+ function asyncAt(config: SproutboatConfig, date: string): boolean {
31
+ return config.compatibility_date >= date;
32
+ }
33
+
29
34
  function entries(config: SproutboatConfig): Entry[] {
30
35
  const out: Entry[] = [];
31
36
 
@@ -35,9 +40,12 @@ function entries(config: SproutboatConfig): Entry[] {
35
40
  for (const name of config.secrets ?? []) {
36
41
  out.push({ name, type: "string", note: "secret, set with `sproutboat secrets put`" });
37
42
  }
38
- for (const { binding } of resourceRefs(config.kv_namespaces)) out.push({ name: binding, type: "KVNamespace" });
39
- for (const { binding } of resourceRefs(config.d1_databases)) out.push({ name: binding, type: "D1Database" });
40
- for (const { binding } of resourceRefs(config.r2_buckets)) out.push({ name: binding, type: "R2Bucket" });
43
+ for (const { binding } of resourceRefs(config.kv_namespaces))
44
+ out.push({ name: binding, type: asyncAt(config, "2026-10-25") ? "KVNamespaceAsync" : "KVNamespace" });
45
+ for (const { binding } of resourceRefs(config.d1_databases))
46
+ out.push({ name: binding, type: asyncAt(config, "2026-10-24") ? "D1DatabaseAsync" : "D1Database" });
47
+ for (const { binding } of resourceRefs(config.r2_buckets))
48
+ out.push({ name: binding, type: asyncAt(config, "2026-10-26") ? "R2BucketAsync" : "R2Bucket" });
41
49
  for (const { binding } of resourceRefs(config.queues)) out.push({ name: binding, type: "Queue" });
42
50
  for (const name of config.analytics_engine_datasets ?? []) {
43
51
  out.push({ name, type: "AnalyticsEngineDataset" });
@@ -87,8 +95,9 @@ declare global {
87
95
  * Bindings from sproutboat.jsonc, for project "${config.name}".
88
96
  *
89
97
  * ${handlerNote}
90
- * Every call on these is synchronous, so \`await\` is allowed and does
91
- * nothing.
98
+ * D1 (from 2026-10-24), KV (2026-10-25) and R2 (2026-10-26) return Promises, as on
99
+ * Workers, so a project at or after those dates gets the *Async types; before them
100
+ * the calls are synchronous. \`await\` works at every date.
92
101
  */
93
102
  const env: {
94
103
  ${body}
@@ -6,7 +6,11 @@
6
6
  * generates a project's `sproutboat-env.d.ts`, which references this file and
7
7
  * declares `env` with that project's bindings.
8
8
  *
9
- * Bindings are synchronous. Before compatibility date 2026-09-28, `env` is a
9
+ * KV, D1 and R2 are synchronous before the compatibility date that makes each one return a
10
+ * Promise (D1 2026-10-24, KV 2026-10-25, R2 2026-10-26): `KVNamespace`, `D1Database` and
11
+ * `R2Bucket` are the synchronous types, `KVNamespaceAsync`, `D1DatabaseAsync` and
12
+ * `R2BucketAsync` the Promise types, and `sproutboat types` gives each binding the one for the
13
+ * project's date. `await` works at every date. Before compatibility date 2026-09-28, `env` is a
10
14
  * global only and `ctx` is the second handler argument. From that date, `env`
11
15
  * is also passed as the second argument and `ctx` as the third.
12
16
  */
@@ -22,7 +26,7 @@ declare global {
22
26
  }
23
27
 
24
28
  interface KVNamespace {
25
- /** The stored string, or `null`. Synchronous: no `await`. An expired key reads as absent. */
29
+ /** The stored string, or `null`. Synchronous before 2026-10-25 (a Promise from then). An expired key reads as absent. */
26
30
  get(key: string): string | null;
27
31
  put(key: string, value: string, options?: KVPutOptions): void;
28
32
  delete(key: string): void;
@@ -30,6 +34,33 @@ declare global {
30
34
  list(prefix?: string): string[];
31
35
  }
32
36
 
37
+ /**
38
+ * KV from compatibility date 2026-10-25: every method returns a Promise, as on Workers.
39
+ * `sproutboat types` picks this type for a project at or after that date.
40
+ */
41
+ interface KVNamespaceAsync {
42
+ get(key: string, type?: "text"): Promise<string | null>;
43
+ get<T = unknown>(key: string, type: "json"): Promise<T | null>;
44
+ get(key: string, type: "arrayBuffer"): Promise<ArrayBuffer | null>;
45
+ get(key: string, type: "stream"): Promise<ReadableStream | null>;
46
+ getWithMetadata<M = unknown>(
47
+ key: string,
48
+ type?: "text" | "json" | "arrayBuffer" | "stream",
49
+ ): Promise<{ value: unknown; metadata: M | null; cacheStatus: string | null }>;
50
+ put(
51
+ key: string,
52
+ value: string | ArrayBuffer | ArrayBufferView | ReadableStream,
53
+ options?: KVPutOptions & { metadata?: unknown },
54
+ ): Promise<void>;
55
+ delete(key: string): Promise<void>;
56
+ list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<{
57
+ keys: Array<{ name: string; expiration?: number; metadata?: unknown }>;
58
+ list_complete: boolean;
59
+ cursor?: string;
60
+ cacheStatus: string | null;
61
+ }>;
62
+ }
63
+
33
64
  // ---------------------------------------------------------------- D1
34
65
 
35
66
  interface D1Meta {
@@ -63,6 +94,48 @@ declare global {
63
94
  exec(sql: string): void;
64
95
  }
65
96
 
97
+ /** D1 from compatibility date 2026-10-24: workerd's meta, with D1's keys. */
98
+ interface D1MetaAsync {
99
+ served_by: string;
100
+ duration: number;
101
+ changes: number;
102
+ last_row_id: number;
103
+ changed_db: boolean;
104
+ size_after: number;
105
+ rows_read: number;
106
+ rows_written: number;
107
+ }
108
+
109
+ interface D1ResultAsync<T = Record<string, unknown>> {
110
+ results: T[];
111
+ success: boolean;
112
+ meta: D1MetaAsync;
113
+ }
114
+
115
+ /**
116
+ * From compatibility date 2026-10-24 `all()`, `run()`, `first()` and `raw()` return Promises, as
117
+ * on Workers. `sproutboat types` picks this type for a project at or after that date.
118
+ */
119
+ interface D1PreparedStatementAsync {
120
+ /** Positional `?` parameters, in order. Bytes bind as a BLOB. */
121
+ bind(...values: Array<string | number | boolean | null | ArrayBuffer | ArrayBufferView>): D1PreparedStatementAsync;
122
+ all<T = Record<string, unknown>>(): Promise<D1ResultAsync<T>>;
123
+ run<T = Record<string, unknown>>(): Promise<D1ResultAsync<T>>;
124
+ first<T = Record<string, unknown>>(): Promise<T | null>;
125
+ first<T = unknown>(column: string): Promise<T | null>;
126
+ raw<T = unknown[]>(options?: { columnNames?: false }): Promise<T[]>;
127
+ raw<T = unknown[]>(options: { columnNames: true }): Promise<[string[], ...T[]]>;
128
+ }
129
+
130
+ interface D1DatabaseAsync {
131
+ prepare(sql: string): D1PreparedStatementAsync;
132
+ batch<T = Record<string, unknown>>(statements: D1PreparedStatementAsync[]): Promise<D1ResultAsync<T>[]>;
133
+ /** Runs a script line by line and counts the lines. */
134
+ exec(sql: string): Promise<{ count: number; duration: number }>;
135
+ /** A Sproutboat addition: an online, integrity-checked copy of the database. */
136
+ backup(name?: string): Promise<{ path: string; bytes: number }>;
137
+ }
138
+
66
139
  // ---------------------------------------------------------------- R2
67
140
 
68
141
  interface R2HttpMetadata {
@@ -172,6 +245,65 @@ declare global {
172
245
  list(options?: R2ListOptions): R2Objects;
173
246
  }
174
247
 
248
+ /**
249
+ * R2 from compatibility date 2026-10-26: every method returns a Promise, as on Workers, and
250
+ * `uploaded` is a Date. `sproutboat types` picks these types for a project at or after that date.
251
+ */
252
+ interface R2ObjectAsync {
253
+ key: string;
254
+ size: number;
255
+ etag: string;
256
+ httpEtag: string;
257
+ checksums: { md5?: ArrayBuffer };
258
+ uploaded: Date;
259
+ httpMetadata?: R2HttpMetadata;
260
+ customMetadata?: Record<string, string>;
261
+ range?: { offset: number; length: number };
262
+ }
263
+
264
+ interface R2ObjectBodyAsync extends R2ObjectAsync {
265
+ body: ReadableStream;
266
+ bodyUsed: boolean;
267
+ arrayBuffer(): Promise<ArrayBuffer>;
268
+ text(): Promise<string>;
269
+ json<T = unknown>(): Promise<T>;
270
+ blob(): Promise<Blob>;
271
+ }
272
+
273
+ interface R2MultipartUploadAsync {
274
+ key: string;
275
+ uploadId: string;
276
+ uploadPart(
277
+ partNumber: number,
278
+ value: string | ArrayBuffer | ArrayBufferView | ReadableStream | Blob,
279
+ ): Promise<R2UploadedPart>;
280
+ complete(parts: R2UploadedPart[]): Promise<R2ObjectAsync>;
281
+ abort(): Promise<void>;
282
+ }
283
+
284
+ interface R2BucketAsync extends Pick<R2Bucket, "createUploadUrl" | "createDownloadUrl"> {
285
+ head(key: string): Promise<R2ObjectAsync | null>;
286
+ get(
287
+ key: string,
288
+ options?: { range?: { offset?: number; length?: number; suffix?: number }; onlyIf?: unknown },
289
+ ): Promise<R2ObjectBodyAsync | null>;
290
+ put(
291
+ key: string,
292
+ value: string | ArrayBuffer | ArrayBufferView | ReadableStream | Blob | null,
293
+ options?: { httpMetadata?: R2HttpMetadata | Headers; customMetadata?: Record<string, string>; onlyIf?: unknown },
294
+ ): Promise<R2ObjectAsync | null>;
295
+ delete(keys: string | string[]): Promise<void>;
296
+ list(options?: {
297
+ prefix?: string;
298
+ limit?: number;
299
+ cursor?: string;
300
+ delimiter?: string;
301
+ include?: Array<"httpMetadata" | "customMetadata">;
302
+ }): Promise<{ objects: R2ObjectAsync[]; truncated: boolean; cursor?: string; delimitedPrefixes: string[] }>;
303
+ createMultipartUpload(key: string, options?: R2PutOptions): Promise<R2MultipartUploadAsync>;
304
+ resumeMultipartUpload(key: string, uploadId: string): R2MultipartUploadAsync;
305
+ }
306
+
175
307
  // ------------------------------------------------------------ Queues
176
308
 
177
309
  type QueuesContentType = "json" | "text" | "bytes" | "v8";