lopata 0.0.1

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 (147) hide show
  1. package/README.md +15 -0
  2. package/package.json +51 -0
  3. package/runtime/bindings/ai.ts +132 -0
  4. package/runtime/bindings/analytics-engine.ts +96 -0
  5. package/runtime/bindings/browser.ts +64 -0
  6. package/runtime/bindings/cache.ts +179 -0
  7. package/runtime/bindings/cf-streams.ts +56 -0
  8. package/runtime/bindings/container-docker.ts +225 -0
  9. package/runtime/bindings/container.ts +662 -0
  10. package/runtime/bindings/crypto-extras.ts +89 -0
  11. package/runtime/bindings/d1.ts +315 -0
  12. package/runtime/bindings/do-executor-inprocess.ts +140 -0
  13. package/runtime/bindings/do-executor-worker.ts +368 -0
  14. package/runtime/bindings/do-executor.ts +45 -0
  15. package/runtime/bindings/do-websocket-bridge.ts +70 -0
  16. package/runtime/bindings/do-worker-entry.ts +220 -0
  17. package/runtime/bindings/do-worker-env.ts +74 -0
  18. package/runtime/bindings/durable-object.ts +992 -0
  19. package/runtime/bindings/email.ts +180 -0
  20. package/runtime/bindings/html-rewriter.ts +84 -0
  21. package/runtime/bindings/hyperdrive.ts +130 -0
  22. package/runtime/bindings/images.ts +381 -0
  23. package/runtime/bindings/kv.ts +359 -0
  24. package/runtime/bindings/queue.ts +507 -0
  25. package/runtime/bindings/r2.ts +759 -0
  26. package/runtime/bindings/rpc-stub.ts +267 -0
  27. package/runtime/bindings/scheduled.ts +172 -0
  28. package/runtime/bindings/service-binding.ts +217 -0
  29. package/runtime/bindings/static-assets.ts +481 -0
  30. package/runtime/bindings/websocket-pair.ts +182 -0
  31. package/runtime/bindings/workflow.ts +858 -0
  32. package/runtime/bunflare-config.ts +56 -0
  33. package/runtime/cli/cache.ts +39 -0
  34. package/runtime/cli/context.ts +105 -0
  35. package/runtime/cli/d1.ts +163 -0
  36. package/runtime/cli/dev.ts +392 -0
  37. package/runtime/cli/kv.ts +84 -0
  38. package/runtime/cli/queues.ts +109 -0
  39. package/runtime/cli/r2.ts +140 -0
  40. package/runtime/cli/traces.ts +251 -0
  41. package/runtime/cli.ts +102 -0
  42. package/runtime/config.ts +148 -0
  43. package/runtime/d1-migrate.ts +37 -0
  44. package/runtime/dashboard/api.ts +174 -0
  45. package/runtime/dashboard/app.tsx +220 -0
  46. package/runtime/dashboard/components/breadcrumb.tsx +16 -0
  47. package/runtime/dashboard/components/buttons.tsx +13 -0
  48. package/runtime/dashboard/components/code-block.tsx +5 -0
  49. package/runtime/dashboard/components/detail-field.tsx +8 -0
  50. package/runtime/dashboard/components/empty-state.tsx +8 -0
  51. package/runtime/dashboard/components/filter-input.tsx +11 -0
  52. package/runtime/dashboard/components/index.ts +16 -0
  53. package/runtime/dashboard/components/key-value-table.tsx +23 -0
  54. package/runtime/dashboard/components/modal.tsx +23 -0
  55. package/runtime/dashboard/components/page-header.tsx +11 -0
  56. package/runtime/dashboard/components/pill-button.tsx +14 -0
  57. package/runtime/dashboard/components/refresh-button.tsx +7 -0
  58. package/runtime/dashboard/components/service-info.tsx +45 -0
  59. package/runtime/dashboard/components/status-badge.tsx +7 -0
  60. package/runtime/dashboard/components/table-link.tsx +5 -0
  61. package/runtime/dashboard/components/table.tsx +26 -0
  62. package/runtime/dashboard/components.tsx +19 -0
  63. package/runtime/dashboard/index.html +23 -0
  64. package/runtime/dashboard/lib.ts +45 -0
  65. package/runtime/dashboard/rpc/client.ts +20 -0
  66. package/runtime/dashboard/rpc/handlers/ai.ts +71 -0
  67. package/runtime/dashboard/rpc/handlers/analytics-engine.ts +53 -0
  68. package/runtime/dashboard/rpc/handlers/cache.ts +24 -0
  69. package/runtime/dashboard/rpc/handlers/config.ts +137 -0
  70. package/runtime/dashboard/rpc/handlers/containers.ts +194 -0
  71. package/runtime/dashboard/rpc/handlers/d1.ts +84 -0
  72. package/runtime/dashboard/rpc/handlers/do.ts +117 -0
  73. package/runtime/dashboard/rpc/handlers/email.ts +82 -0
  74. package/runtime/dashboard/rpc/handlers/errors.ts +32 -0
  75. package/runtime/dashboard/rpc/handlers/generations.ts +60 -0
  76. package/runtime/dashboard/rpc/handlers/kv.ts +76 -0
  77. package/runtime/dashboard/rpc/handlers/overview.ts +94 -0
  78. package/runtime/dashboard/rpc/handlers/queue.ts +79 -0
  79. package/runtime/dashboard/rpc/handlers/r2.ts +72 -0
  80. package/runtime/dashboard/rpc/handlers/scheduled.ts +91 -0
  81. package/runtime/dashboard/rpc/handlers/traces.ts +64 -0
  82. package/runtime/dashboard/rpc/handlers/workers.ts +65 -0
  83. package/runtime/dashboard/rpc/handlers/workflows.ts +171 -0
  84. package/runtime/dashboard/rpc/hooks.ts +132 -0
  85. package/runtime/dashboard/rpc/server.ts +70 -0
  86. package/runtime/dashboard/rpc/types.ts +396 -0
  87. package/runtime/dashboard/sql-browser/data-browser-tab.tsx +122 -0
  88. package/runtime/dashboard/sql-browser/editable-cell.tsx +117 -0
  89. package/runtime/dashboard/sql-browser/filter-row.tsx +99 -0
  90. package/runtime/dashboard/sql-browser/history-panels.tsx +110 -0
  91. package/runtime/dashboard/sql-browser/hooks.ts +137 -0
  92. package/runtime/dashboard/sql-browser/index.ts +4 -0
  93. package/runtime/dashboard/sql-browser/insert-row-form.tsx +85 -0
  94. package/runtime/dashboard/sql-browser/modals.tsx +116 -0
  95. package/runtime/dashboard/sql-browser/schema-browser-tab.tsx +67 -0
  96. package/runtime/dashboard/sql-browser/sql-browser.tsx +52 -0
  97. package/runtime/dashboard/sql-browser/sql-console-tab.tsx +124 -0
  98. package/runtime/dashboard/sql-browser/table-data-view.tsx +566 -0
  99. package/runtime/dashboard/sql-browser/table-sidebar.tsx +38 -0
  100. package/runtime/dashboard/sql-browser/types.ts +61 -0
  101. package/runtime/dashboard/sql-browser/utils.ts +167 -0
  102. package/runtime/dashboard/style.css +177 -0
  103. package/runtime/dashboard/views/ai.tsx +152 -0
  104. package/runtime/dashboard/views/analytics-engine.tsx +169 -0
  105. package/runtime/dashboard/views/cache.tsx +93 -0
  106. package/runtime/dashboard/views/containers.tsx +197 -0
  107. package/runtime/dashboard/views/d1.tsx +81 -0
  108. package/runtime/dashboard/views/do.tsx +168 -0
  109. package/runtime/dashboard/views/email.tsx +235 -0
  110. package/runtime/dashboard/views/errors.tsx +558 -0
  111. package/runtime/dashboard/views/home.tsx +287 -0
  112. package/runtime/dashboard/views/kv.tsx +273 -0
  113. package/runtime/dashboard/views/queue.tsx +193 -0
  114. package/runtime/dashboard/views/r2.tsx +202 -0
  115. package/runtime/dashboard/views/scheduled.tsx +89 -0
  116. package/runtime/dashboard/views/trace-waterfall.tsx +410 -0
  117. package/runtime/dashboard/views/traces.tsx +768 -0
  118. package/runtime/dashboard/views/workers.tsx +55 -0
  119. package/runtime/dashboard/views/workflows.tsx +473 -0
  120. package/runtime/db.ts +258 -0
  121. package/runtime/env.ts +362 -0
  122. package/runtime/error-page/app.tsx +394 -0
  123. package/runtime/error-page/build.ts +269 -0
  124. package/runtime/error-page/index.html +16 -0
  125. package/runtime/error-page/style.css +31 -0
  126. package/runtime/execution-context.ts +18 -0
  127. package/runtime/file-watcher.ts +57 -0
  128. package/runtime/generation-manager.ts +230 -0
  129. package/runtime/generation.ts +411 -0
  130. package/runtime/plugin.ts +292 -0
  131. package/runtime/request-cf.ts +28 -0
  132. package/runtime/rpc-validate.ts +154 -0
  133. package/runtime/tracing/context.ts +40 -0
  134. package/runtime/tracing/db.ts +73 -0
  135. package/runtime/tracing/frames.ts +75 -0
  136. package/runtime/tracing/instrument.ts +186 -0
  137. package/runtime/tracing/span.ts +138 -0
  138. package/runtime/tracing/store.ts +499 -0
  139. package/runtime/tracing/types.ts +47 -0
  140. package/runtime/vite-plugin/config-plugin.ts +68 -0
  141. package/runtime/vite-plugin/dev-server-plugin.ts +493 -0
  142. package/runtime/vite-plugin/dist/index.mjs +52333 -0
  143. package/runtime/vite-plugin/globals-plugin.ts +94 -0
  144. package/runtime/vite-plugin/index.ts +43 -0
  145. package/runtime/vite-plugin/modules-plugin.ts +88 -0
  146. package/runtime/vite-plugin/react-router-plugin.ts +95 -0
  147. package/runtime/worker-registry.ts +52 -0
@@ -0,0 +1,359 @@
1
+ import type { Database } from "bun:sqlite";
2
+
3
+ export interface KVLimits {
4
+ maxKeySize?: number; // default 512 (bytes)
5
+ maxValueSize?: number; // default 25 * 1024 * 1024 (25 MiB)
6
+ maxMetadataSize?: number; // default 1024 (bytes)
7
+ minTtlSeconds?: number; // default 60
8
+ maxBulkGetKeys?: number; // default 100
9
+ }
10
+
11
+ const KV_DEFAULTS: Required<KVLimits> = {
12
+ maxKeySize: 512,
13
+ maxValueSize: 25 * 1024 * 1024,
14
+ maxMetadataSize: 1024,
15
+ minTtlSeconds: 60,
16
+ maxBulkGetKeys: 100,
17
+ };
18
+
19
+ type KVGetOptions = { type?: string; cacheTtl?: number };
20
+
21
+ export class SqliteKVNamespace {
22
+ private db: Database;
23
+ private namespace: string;
24
+ private limits: Required<KVLimits>;
25
+
26
+ constructor(db: Database, namespace: string, limits?: KVLimits) {
27
+ this.db = db;
28
+ this.namespace = namespace;
29
+ this.limits = { ...KV_DEFAULTS, ...limits };
30
+ }
31
+
32
+ private validateKey(key: string): void {
33
+ if (key === "" || key === "." || key === "..") {
34
+ throw new Error(`KV key "${key}" is not allowed`);
35
+ }
36
+ if (new TextEncoder().encode(key).byteLength > this.limits.maxKeySize) {
37
+ throw new Error(`KV key exceeds max size of ${this.limits.maxKeySize} bytes`);
38
+ }
39
+ }
40
+
41
+ private validateValue(blob: Buffer): void {
42
+ if (blob.byteLength > this.limits.maxValueSize) {
43
+ throw new Error(`KV value exceeds max size of ${this.limits.maxValueSize} bytes`);
44
+ }
45
+ }
46
+
47
+ private validateMetadata(metadata: unknown): string {
48
+ const serialized = JSON.stringify(metadata);
49
+ if (new TextEncoder().encode(serialized).byteLength > this.limits.maxMetadataSize) {
50
+ throw new Error(`KV metadata exceeds max size of ${this.limits.maxMetadataSize} bytes`);
51
+ }
52
+ return serialized;
53
+ }
54
+
55
+ private validateTtl(ttl: number): void {
56
+ if (ttl < this.limits.minTtlSeconds) {
57
+ throw new Error(`KV expirationTtl must be at least ${this.limits.minTtlSeconds} seconds`);
58
+ }
59
+ }
60
+
61
+ async get(key: string, options?: string | KVGetOptions): Promise<string | ArrayBuffer | object | ReadableStream | null>;
62
+ async get(keys: string[], options?: string | KVGetOptions): Promise<Map<string, string | ArrayBuffer | object | ReadableStream | null>>;
63
+ async get(
64
+ keyOrKeys: string | string[],
65
+ options?: string | KVGetOptions,
66
+ ): Promise<string | ArrayBuffer | object | ReadableStream | null | Map<string, string | ArrayBuffer | object | ReadableStream | null>> {
67
+ if (Array.isArray(keyOrKeys)) {
68
+ return this.bulkGet(keyOrKeys, options);
69
+ }
70
+
71
+ this.validateKey(keyOrKeys);
72
+
73
+ const row = this.db.query<{ value: Buffer; metadata: string | null; expiration: number | null }, [string, string]>(
74
+ "SELECT value, metadata, expiration FROM kv WHERE namespace = ? AND key = ?"
75
+ ).get(this.namespace, keyOrKeys);
76
+
77
+ if (!row) return null;
78
+
79
+ if (row.expiration && row.expiration < Date.now() / 1000) {
80
+ this.db.run("DELETE FROM kv WHERE namespace = ? AND key = ?", [this.namespace, keyOrKeys]);
81
+ return null;
82
+ }
83
+
84
+ const type = typeof options === "string" ? options : options?.type ?? "text";
85
+ return this.decodeValue(row.value, type);
86
+ }
87
+
88
+ private async bulkGet(
89
+ keys: string[],
90
+ options?: string | KVGetOptions,
91
+ ): Promise<Map<string, string | ArrayBuffer | object | ReadableStream | null>> {
92
+ if (keys.length > this.limits.maxBulkGetKeys) {
93
+ throw new Error(`KV bulk get exceeds max of ${this.limits.maxBulkGetKeys} keys`);
94
+ }
95
+ for (const key of keys) {
96
+ this.validateKey(key);
97
+ }
98
+
99
+ const result = new Map<string, string | ArrayBuffer | object | ReadableStream | null>();
100
+ const type = typeof options === "string" ? options : options?.type ?? "text";
101
+ if (type === "arrayBuffer" || type === "stream") {
102
+ throw new Error(`KV bulk get does not support type "${type}"`);
103
+ }
104
+ const now = Date.now() / 1000;
105
+
106
+ if (keys.length === 0) return result;
107
+
108
+ const placeholders = keys.map(() => "?").join(", ");
109
+ const rows = this.db.query<
110
+ { key: string; value: Buffer; expiration: number | null },
111
+ [string, ...string[]]
112
+ >(
113
+ `SELECT key, value, expiration FROM kv WHERE namespace = ? AND key IN (${placeholders})`
114
+ ).all(this.namespace, ...keys);
115
+
116
+ const rowMap = new Map<string, { value: Buffer; expiration: number | null }>();
117
+ for (const row of rows) {
118
+ if (row.expiration && row.expiration < now) {
119
+ this.db.run("DELETE FROM kv WHERE namespace = ? AND key = ?", [this.namespace, row.key]);
120
+ } else {
121
+ rowMap.set(row.key, row);
122
+ }
123
+ }
124
+
125
+ for (const key of keys) {
126
+ const row = rowMap.get(key);
127
+ result.set(key, row ? this.decodeValue(row.value, type) : null);
128
+ }
129
+
130
+ return result;
131
+ }
132
+
133
+ async getWithMetadata(key: string, options?: string | KVGetOptions): Promise<{ value: string | ArrayBuffer | object | ReadableStream | null; metadata: unknown; cacheStatus: null }>;
134
+ async getWithMetadata(keys: string[], options?: string | KVGetOptions): Promise<Map<string, { value: string | ArrayBuffer | object | ReadableStream | null; metadata: unknown }>>;
135
+ async getWithMetadata(
136
+ keyOrKeys: string | string[],
137
+ options?: string | KVGetOptions,
138
+ ): Promise<{ value: string | ArrayBuffer | object | ReadableStream | null; metadata: unknown; cacheStatus: null } | Map<string, { value: string | ArrayBuffer | object | ReadableStream | null; metadata: unknown }>> {
139
+ if (Array.isArray(keyOrKeys)) {
140
+ return this.bulkGetWithMetadata(keyOrKeys, options);
141
+ }
142
+
143
+ this.validateKey(keyOrKeys);
144
+
145
+ const row = this.db.query<{ value: Buffer; metadata: string | null; expiration: number | null }, [string, string]>(
146
+ "SELECT value, metadata, expiration FROM kv WHERE namespace = ? AND key = ?"
147
+ ).get(this.namespace, keyOrKeys);
148
+
149
+ if (!row) return { value: null, metadata: null, cacheStatus: null };
150
+
151
+ if (row.expiration && row.expiration < Date.now() / 1000) {
152
+ this.db.run("DELETE FROM kv WHERE namespace = ? AND key = ?", [this.namespace, keyOrKeys]);
153
+ return { value: null, metadata: null, cacheStatus: null };
154
+ }
155
+
156
+ const type = typeof options === "string" ? options : options?.type ?? "text";
157
+ const value = this.decodeValue(row.value, type);
158
+ const metadata = row.metadata ? JSON.parse(row.metadata) : null;
159
+ return { value, metadata, cacheStatus: null };
160
+ }
161
+
162
+ private async bulkGetWithMetadata(
163
+ keys: string[],
164
+ options?: string | KVGetOptions,
165
+ ): Promise<Map<string, { value: string | ArrayBuffer | object | ReadableStream | null; metadata: unknown }>> {
166
+ if (keys.length > this.limits.maxBulkGetKeys) {
167
+ throw new Error(`KV bulk get exceeds max of ${this.limits.maxBulkGetKeys} keys`);
168
+ }
169
+ for (const key of keys) {
170
+ this.validateKey(key);
171
+ }
172
+
173
+ const result = new Map<string, { value: string | ArrayBuffer | object | ReadableStream | null; metadata: unknown }>();
174
+ const type = typeof options === "string" ? options : options?.type ?? "text";
175
+ if (type === "arrayBuffer" || type === "stream") {
176
+ throw new Error(`KV bulk get does not support type "${type}"`);
177
+ }
178
+ const now = Date.now() / 1000;
179
+
180
+ if (keys.length === 0) return result;
181
+
182
+ const placeholders = keys.map(() => "?").join(", ");
183
+ const rows = this.db.query<
184
+ { key: string; value: Buffer; metadata: string | null; expiration: number | null },
185
+ [string, ...string[]]
186
+ >(
187
+ `SELECT key, value, metadata, expiration FROM kv WHERE namespace = ? AND key IN (${placeholders})`
188
+ ).all(this.namespace, ...keys);
189
+
190
+ const rowMap = new Map<string, { value: Buffer; metadata: string | null; expiration: number | null }>();
191
+ for (const row of rows) {
192
+ if (row.expiration && row.expiration < now) {
193
+ this.db.run("DELETE FROM kv WHERE namespace = ? AND key = ?", [this.namespace, row.key]);
194
+ } else {
195
+ rowMap.set(row.key, row);
196
+ }
197
+ }
198
+
199
+ for (const key of keys) {
200
+ const row = rowMap.get(key);
201
+ if (row) {
202
+ result.set(key, {
203
+ value: this.decodeValue(row.value, type),
204
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
205
+ });
206
+ } else {
207
+ result.set(key, { value: null, metadata: null });
208
+ }
209
+ }
210
+
211
+ return result;
212
+ }
213
+
214
+ async put(
215
+ key: string,
216
+ value: string | ArrayBuffer | ReadableStream,
217
+ options?: { metadata?: unknown; expirationTtl?: number; expiration?: number },
218
+ ) {
219
+ this.validateKey(key);
220
+ const blob = await this.encodeValue(value);
221
+ this.validateValue(blob);
222
+
223
+ if (options?.expirationTtl !== undefined) {
224
+ this.validateTtl(options.expirationTtl);
225
+ }
226
+ if (options?.expiration !== undefined) {
227
+ const minExpiration = Date.now() / 1000 + this.limits.minTtlSeconds;
228
+ if (options.expiration < minExpiration) {
229
+ throw new Error(`KV expiration must be at least ${this.limits.minTtlSeconds} seconds in the future`);
230
+ }
231
+ }
232
+
233
+ let expiration: number | null = null;
234
+ if (options?.expiration) expiration = options.expiration;
235
+ else if (options?.expirationTtl) expiration = Date.now() / 1000 + options.expirationTtl;
236
+
237
+ let metadata: string | null = null;
238
+ if (options?.metadata !== undefined) {
239
+ metadata = this.validateMetadata(options.metadata);
240
+ }
241
+
242
+ this.db.run(
243
+ "INSERT OR REPLACE INTO kv (namespace, key, value, metadata, expiration) VALUES (?, ?, ?, ?, ?)",
244
+ [this.namespace, key, blob, metadata, expiration],
245
+ );
246
+ }
247
+
248
+ async delete(key: string) {
249
+ this.db.run("DELETE FROM kv WHERE namespace = ? AND key = ?", [this.namespace, key]);
250
+ }
251
+
252
+ async list(options?: { prefix?: string; limit?: number; cursor?: string }) {
253
+ const prefix = options?.prefix ?? "";
254
+ const limit = Math.min(options?.limit ?? 1000, 1000);
255
+ const cursor = options?.cursor ?? "";
256
+
257
+ const now = Date.now() / 1000;
258
+
259
+ // Lazily delete expired entries for this namespace
260
+ this.db.run(
261
+ "DELETE FROM kv WHERE namespace = ? AND expiration IS NOT NULL AND expiration < ?",
262
+ [this.namespace, now],
263
+ );
264
+
265
+ let rows: { key: string; expiration: number | null; metadata: string | null }[];
266
+
267
+ // Use range query instead of LIKE to avoid SQL wildcard injection (% and _ in prefix)
268
+ const prefixEnd = prefix.length > 0
269
+ ? prefix.slice(0, -1) + String.fromCharCode(prefix.charCodeAt(prefix.length - 1) + 1)
270
+ : "";
271
+
272
+ if (prefix) {
273
+ if (cursor) {
274
+ rows = this.db.query<
275
+ { key: string; expiration: number | null; metadata: string | null },
276
+ [string, string, string, string, number]
277
+ >(
278
+ "SELECT key, expiration, metadata FROM kv WHERE namespace = ? AND key >= ? AND key < ? AND key > ? ORDER BY key LIMIT ?",
279
+ ).all(this.namespace, prefix, prefixEnd, cursor, limit + 1);
280
+ } else {
281
+ rows = this.db.query<
282
+ { key: string; expiration: number | null; metadata: string | null },
283
+ [string, string, string, number]
284
+ >(
285
+ "SELECT key, expiration, metadata FROM kv WHERE namespace = ? AND key >= ? AND key < ? ORDER BY key LIMIT ?",
286
+ ).all(this.namespace, prefix, prefixEnd, limit + 1);
287
+ }
288
+ } else {
289
+ if (cursor) {
290
+ rows = this.db.query<
291
+ { key: string; expiration: number | null; metadata: string | null },
292
+ [string, string, number]
293
+ >(
294
+ "SELECT key, expiration, metadata FROM kv WHERE namespace = ? AND key > ? ORDER BY key LIMIT ?",
295
+ ).all(this.namespace, cursor, limit + 1);
296
+ } else {
297
+ rows = this.db.query<
298
+ { key: string; expiration: number | null; metadata: string | null },
299
+ [string, number]
300
+ >(
301
+ "SELECT key, expiration, metadata FROM kv WHERE namespace = ? ORDER BY key LIMIT ?",
302
+ ).all(this.namespace, limit + 1);
303
+ }
304
+ }
305
+
306
+ const listComplete = rows.length <= limit;
307
+ const resultRows = rows.slice(0, limit);
308
+
309
+ const keys = resultRows.map((row) => ({
310
+ name: row.key,
311
+ expiration: row.expiration ?? undefined,
312
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
313
+ }));
314
+
315
+ const lastRow = resultRows[resultRows.length - 1];
316
+ const newCursor = listComplete || !lastRow ? "" : lastRow.key;
317
+
318
+ return { keys, list_complete: listComplete, cursor: newCursor };
319
+ }
320
+
321
+ private decodeValue(blob: Buffer, type: string): string | ArrayBuffer | object | ReadableStream {
322
+ if (type === "json") {
323
+ return JSON.parse(Buffer.from(blob).toString());
324
+ }
325
+ if (type === "arrayBuffer") {
326
+ const buf = Buffer.from(blob);
327
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
328
+ }
329
+ if (type === "stream") {
330
+ const buf = Buffer.from(blob);
331
+ return new ReadableStream({
332
+ start(controller) {
333
+ controller.enqueue(new Uint8Array(buf));
334
+ controller.close();
335
+ },
336
+ });
337
+ }
338
+ // text
339
+ return Buffer.from(blob).toString();
340
+ }
341
+
342
+ private async encodeValue(value: string | ArrayBuffer | ReadableStream): Promise<Buffer> {
343
+ if (typeof value === "string") {
344
+ return Buffer.from(value);
345
+ }
346
+ if (value instanceof ArrayBuffer) {
347
+ return Buffer.from(value);
348
+ }
349
+ // ReadableStream
350
+ const chunks: Uint8Array[] = [];
351
+ const reader = value.getReader();
352
+ while (true) {
353
+ const { done, value: chunk } = await reader.read();
354
+ if (done) break;
355
+ chunks.push(chunk);
356
+ }
357
+ return Buffer.concat(chunks);
358
+ }
359
+ }