@takosjp/yurucommu-core 3.4.4 → 4.0.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/package.json +1 -1
- package/packages/api/package.json +1 -1
- package/src/backend/lib/blocklist-purge.ts +6 -6
- package/src/backend/public.ts +9 -4
- package/src/backend/routes/account-teardown.ts +2 -2
- package/src/backend/routes/apps.ts +4 -5
- package/src/backend/routes/media.ts +5 -6
- package/src/backend/routes/posts/delete-cascade.ts +7 -7
- package/src/backend/routes/stories/query-helpers.ts +2 -2
- package/src/backend/routes/stories/routes.ts +2 -5
- package/src/backend/runtime/bun.ts +2000 -179
- package/src/backend/runtime/cloudflare.ts +20 -58
- package/src/backend/runtime/managed-runtime.ts +117 -139
- package/src/backend/runtime/s3-fetch.ts +380 -0
- package/src/backend/runtime/types.ts +29 -61
- package/src/backend/types.ts +4 -4
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ObjectStore,
|
|
3
|
+
ObjectStoreBody,
|
|
4
|
+
ObjectStoreObject,
|
|
5
|
+
ObjectStorePutOptions,
|
|
6
|
+
} from "./types.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The host supplies a Fetch-compatible capability that is already scoped to
|
|
10
|
+
* one object bucket. The adapter never receives (or needs) an endpoint,
|
|
11
|
+
* bucket name, region, or credential.
|
|
12
|
+
*/
|
|
13
|
+
export interface S3ObjectFetcher {
|
|
14
|
+
fetch(request: Request): Promise<Response>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface S3FetchObjectStoreOptions {
|
|
18
|
+
/** Maximum number of bytes a GET may expose to the application. */
|
|
19
|
+
readonly maxObjectBytes?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type S3FetchObjectOperation = "put" | "get" | "delete";
|
|
23
|
+
|
|
24
|
+
/** An S3 protocol failure without response-body or endpoint details. */
|
|
25
|
+
export class S3FetchObjectStoreError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
readonly operation: S3FetchObjectOperation,
|
|
28
|
+
readonly code: string,
|
|
29
|
+
readonly status?: number,
|
|
30
|
+
) {
|
|
31
|
+
super(code);
|
|
32
|
+
this.name = "S3FetchObjectStoreError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SYNTHETIC_ORIGIN = "https://s3.invalid";
|
|
37
|
+
const DEFAULT_MAX_OBJECT_BYTES = 64 * 1024 * 1024;
|
|
38
|
+
const MAX_HEADER_VALUE_LENGTH = 8 * 1024;
|
|
39
|
+
const FETCH_REJECTION_CODES: Record<S3FetchObjectOperation, string> = {
|
|
40
|
+
put: "s3_fetcher_put_rejected",
|
|
41
|
+
get: "s3_fetcher_get_rejected",
|
|
42
|
+
delete: "s3_fetcher_delete_rejected",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Adapt a bucket-scoped S3 HTTP Fetcher to the core's provider-neutral object
|
|
47
|
+
* store contract.
|
|
48
|
+
*
|
|
49
|
+
* The URL is intentionally synthetic. A host-owned Fetcher receives the
|
|
50
|
+
* request and supplies the actual endpoint and credentials internally; no
|
|
51
|
+
* provider materialization can escape through this public adapter.
|
|
52
|
+
*/
|
|
53
|
+
export function createS3FetchObjectStore(
|
|
54
|
+
fetcher: S3ObjectFetcher,
|
|
55
|
+
options: S3FetchObjectStoreOptions = {},
|
|
56
|
+
): ObjectStore {
|
|
57
|
+
if (!fetcher || typeof fetcher.fetch !== "function") {
|
|
58
|
+
throw new TypeError("s3_fetcher_invalid");
|
|
59
|
+
}
|
|
60
|
+
const maxObjectBytes = options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES;
|
|
61
|
+
assertMaxObjectBytes(maxObjectBytes);
|
|
62
|
+
|
|
63
|
+
return new S3FetchObjectStore(fetcher, maxObjectBytes);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class S3FetchObjectStore implements ObjectStore {
|
|
67
|
+
constructor(
|
|
68
|
+
private readonly fetcher: S3ObjectFetcher,
|
|
69
|
+
private readonly maxObjectBytes: number,
|
|
70
|
+
) {}
|
|
71
|
+
|
|
72
|
+
async put(
|
|
73
|
+
key: string,
|
|
74
|
+
value: ObjectStoreBody,
|
|
75
|
+
options?: ObjectStorePutOptions,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const headers = new Headers();
|
|
78
|
+
if (options?.contentType !== undefined) {
|
|
79
|
+
setBoundedHeader(headers, "content-type", options.contentType, "put");
|
|
80
|
+
}
|
|
81
|
+
const byteLength = knownBodyLength(value);
|
|
82
|
+
if (byteLength !== undefined) {
|
|
83
|
+
headers.set("content-length", String(byteLength));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const response = await fetchFromS3(
|
|
87
|
+
this.fetcher,
|
|
88
|
+
new Request(objectUrl(key), {
|
|
89
|
+
method: "PUT",
|
|
90
|
+
headers,
|
|
91
|
+
body: value as BodyInit,
|
|
92
|
+
}),
|
|
93
|
+
"put",
|
|
94
|
+
);
|
|
95
|
+
await expectSuccess(response, "put");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async get(key: string): Promise<ObjectStoreObject | null> {
|
|
99
|
+
const response = await fetchFromS3(
|
|
100
|
+
this.fetcher,
|
|
101
|
+
new Request(objectUrl(key), { method: "GET" }),
|
|
102
|
+
"get",
|
|
103
|
+
);
|
|
104
|
+
if (response.status === 404) {
|
|
105
|
+
await cancelBody(response);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
await expectSuccess(response, "get", false);
|
|
109
|
+
|
|
110
|
+
let byteLength: number | undefined;
|
|
111
|
+
let contentType: string | undefined;
|
|
112
|
+
let etag: string | undefined;
|
|
113
|
+
try {
|
|
114
|
+
byteLength = parseContentLength(
|
|
115
|
+
response.headers.get("content-length"),
|
|
116
|
+
this.maxObjectBytes,
|
|
117
|
+
"get",
|
|
118
|
+
response.status,
|
|
119
|
+
);
|
|
120
|
+
contentType = boundedHeader(
|
|
121
|
+
response.headers,
|
|
122
|
+
"content-type",
|
|
123
|
+
"get",
|
|
124
|
+
response.status,
|
|
125
|
+
);
|
|
126
|
+
etag = boundedHeader(response.headers, "etag", "get", response.status);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
await cancelBody(response);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
key,
|
|
133
|
+
body:
|
|
134
|
+
response.body === null
|
|
135
|
+
? null
|
|
136
|
+
: boundedBody(response.body, this.maxObjectBytes, "get"),
|
|
137
|
+
...(contentType === undefined ? {} : { contentType }),
|
|
138
|
+
...(etag === undefined ? {} : { etag }),
|
|
139
|
+
...(byteLength === undefined ? {} : { byteLength }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async delete(key: string | readonly string[]): Promise<void> {
|
|
144
|
+
const isBatch = typeof key !== "string";
|
|
145
|
+
const keys = [...new Set(isBatch ? key : [key])];
|
|
146
|
+
const failures: S3FetchObjectStoreError[] = [];
|
|
147
|
+
for (const entry of keys) {
|
|
148
|
+
try {
|
|
149
|
+
const response = await fetchFromS3(
|
|
150
|
+
this.fetcher,
|
|
151
|
+
new Request(objectUrl(entry), { method: "DELETE" }),
|
|
152
|
+
"delete",
|
|
153
|
+
);
|
|
154
|
+
await expectSuccess(response, "delete");
|
|
155
|
+
} catch (error) {
|
|
156
|
+
failures.push(
|
|
157
|
+
error instanceof S3FetchObjectStoreError
|
|
158
|
+
? error
|
|
159
|
+
: new S3FetchObjectStoreError(
|
|
160
|
+
"delete",
|
|
161
|
+
"s3_delete_operation_failed",
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (failures.length === 0) return;
|
|
167
|
+
if (!isBatch) {
|
|
168
|
+
throw failures[0];
|
|
169
|
+
}
|
|
170
|
+
throw new AggregateError(failures, "s3_batch_delete_failed");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function fetchFromS3(
|
|
175
|
+
fetcher: S3ObjectFetcher,
|
|
176
|
+
request: Request,
|
|
177
|
+
operation: S3FetchObjectOperation,
|
|
178
|
+
): Promise<Response> {
|
|
179
|
+
try {
|
|
180
|
+
return await fetcher.fetch(request);
|
|
181
|
+
} catch {
|
|
182
|
+
// Never retain the supplied rejection as `cause` or copy any of its text:
|
|
183
|
+
// the host Fetcher may contain endpoint and credential diagnostics.
|
|
184
|
+
throw new S3FetchObjectStoreError(
|
|
185
|
+
operation,
|
|
186
|
+
FETCH_REJECTION_CODES[operation],
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function objectUrl(key: string): string {
|
|
192
|
+
return `${SYNTHETIC_ORIGIN}/${key
|
|
193
|
+
.split("/")
|
|
194
|
+
.map(encodePathSegment)
|
|
195
|
+
.join("/")}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Encode every path segment, including dot segments, so URL normalization
|
|
199
|
+
* cannot reinterpret a user key as traversal. */
|
|
200
|
+
function encodePathSegment(value: string): string {
|
|
201
|
+
const encoded = encodeURIComponent(value).replace(
|
|
202
|
+
/[.!'()*]/gu,
|
|
203
|
+
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
204
|
+
);
|
|
205
|
+
// URL parsers normalize `%2E`/`%2E%2E` path segments as traversal before a
|
|
206
|
+
// Fetcher sees the request. Double-escape only those complete segments so
|
|
207
|
+
// the host can decode the wire path without losing the key boundary.
|
|
208
|
+
return value === "." || value === ".."
|
|
209
|
+
? encoded.replace(/%2E/gu, "%252E")
|
|
210
|
+
: encoded;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function knownBodyLength(value: ObjectStoreBody): number | undefined {
|
|
214
|
+
if (value instanceof Blob) return value.size;
|
|
215
|
+
if (value instanceof ArrayBuffer) return value.byteLength;
|
|
216
|
+
if (typeof value === "string") {
|
|
217
|
+
return new TextEncoder().encode(value).byteLength;
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function expectSuccess(
|
|
223
|
+
response: Response,
|
|
224
|
+
operation: S3FetchObjectOperation,
|
|
225
|
+
cancelSuccessfulBody = true,
|
|
226
|
+
): Promise<void> {
|
|
227
|
+
if (response.status >= 200 && response.status < 300) {
|
|
228
|
+
if (cancelSuccessfulBody) await cancelBody(response);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
await cancelBody(response);
|
|
232
|
+
throw new S3FetchObjectStoreError(
|
|
233
|
+
operation,
|
|
234
|
+
"s3_response_unexpected_status",
|
|
235
|
+
response.status,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function cancelBody(response: Response): Promise<void> {
|
|
240
|
+
await response.body?.cancel().catch(() => undefined);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function boundedHeader(
|
|
244
|
+
headers: Headers,
|
|
245
|
+
name: string,
|
|
246
|
+
operation: S3FetchObjectOperation,
|
|
247
|
+
status: number,
|
|
248
|
+
): string | undefined {
|
|
249
|
+
const value = headers.get(name);
|
|
250
|
+
if (value === null || value.length === 0) return undefined;
|
|
251
|
+
if (value.length > MAX_HEADER_VALUE_LENGTH) {
|
|
252
|
+
throw new S3FetchObjectStoreError(
|
|
253
|
+
operation,
|
|
254
|
+
"s3_response_header_too_large",
|
|
255
|
+
status,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function setBoundedHeader(
|
|
262
|
+
headers: Headers,
|
|
263
|
+
name: string,
|
|
264
|
+
value: string,
|
|
265
|
+
operation: S3FetchObjectOperation,
|
|
266
|
+
): void {
|
|
267
|
+
if (value.length > MAX_HEADER_VALUE_LENGTH) {
|
|
268
|
+
throw new S3FetchObjectStoreError(operation, "s3_request_header_too_large");
|
|
269
|
+
}
|
|
270
|
+
headers.set(name, value);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function parseContentLength(
|
|
274
|
+
value: string | null,
|
|
275
|
+
maxObjectBytes: number,
|
|
276
|
+
operation: S3FetchObjectOperation,
|
|
277
|
+
status: number,
|
|
278
|
+
): number | undefined {
|
|
279
|
+
if (value === null) return undefined;
|
|
280
|
+
if (
|
|
281
|
+
value.length > MAX_HEADER_VALUE_LENGTH ||
|
|
282
|
+
!/^\d+$/u.test(value) ||
|
|
283
|
+
!Number.isSafeInteger(Number(value))
|
|
284
|
+
) {
|
|
285
|
+
throw new S3FetchObjectStoreError(
|
|
286
|
+
operation,
|
|
287
|
+
"s3_response_content_length_invalid",
|
|
288
|
+
status,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const parsed = Number(value);
|
|
292
|
+
if (parsed > maxObjectBytes) {
|
|
293
|
+
throw new S3FetchObjectStoreError(
|
|
294
|
+
operation,
|
|
295
|
+
"s3_response_object_too_large",
|
|
296
|
+
status,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return parsed;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function boundedBody(
|
|
303
|
+
stream: ReadableStream<Uint8Array>,
|
|
304
|
+
maxObjectBytes: number,
|
|
305
|
+
operation: S3FetchObjectOperation,
|
|
306
|
+
): ReadableStream<Uint8Array> {
|
|
307
|
+
const reader = stream.getReader();
|
|
308
|
+
let size = 0;
|
|
309
|
+
let finished = false;
|
|
310
|
+
const release = (): void => {
|
|
311
|
+
if (finished) return;
|
|
312
|
+
finished = true;
|
|
313
|
+
try {
|
|
314
|
+
reader.releaseLock();
|
|
315
|
+
} catch {
|
|
316
|
+
// The bounded adapter owns no useful diagnostic at this boundary. A
|
|
317
|
+
// lock-release failure after read/cancel completion must not surface a
|
|
318
|
+
// provider error or retain its cause.
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return new ReadableStream<Uint8Array>(
|
|
323
|
+
{
|
|
324
|
+
async pull(controller) {
|
|
325
|
+
try {
|
|
326
|
+
const result = await reader.read();
|
|
327
|
+
if (result.done) {
|
|
328
|
+
controller.close();
|
|
329
|
+
release();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
size += result.value.byteLength;
|
|
333
|
+
if (size > maxObjectBytes) {
|
|
334
|
+
await reader
|
|
335
|
+
.cancel("s3_response_object_too_large")
|
|
336
|
+
.catch(() => undefined);
|
|
337
|
+
controller.error(
|
|
338
|
+
new S3FetchObjectStoreError(
|
|
339
|
+
operation,
|
|
340
|
+
"s3_response_object_too_large",
|
|
341
|
+
),
|
|
342
|
+
);
|
|
343
|
+
release();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
controller.enqueue(result.value);
|
|
347
|
+
} catch {
|
|
348
|
+
controller.error(
|
|
349
|
+
new S3FetchObjectStoreError(
|
|
350
|
+
operation,
|
|
351
|
+
"s3_response_body_read_failed",
|
|
352
|
+
),
|
|
353
|
+
);
|
|
354
|
+
release();
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
async cancel() {
|
|
358
|
+
try {
|
|
359
|
+
// Do not forward an arbitrary caller reason into a provider-owned
|
|
360
|
+
// stream, and never retain a provider rejection as cause/text.
|
|
361
|
+
await reader.cancel("s3_response_body_cancelled");
|
|
362
|
+
} catch {
|
|
363
|
+
throw new S3FetchObjectStoreError(
|
|
364
|
+
operation,
|
|
365
|
+
"s3_response_body_cancel_failed",
|
|
366
|
+
);
|
|
367
|
+
} finally {
|
|
368
|
+
release();
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{ highWaterMark: 0 },
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function assertMaxObjectBytes(value: number): void {
|
|
377
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
378
|
+
throw new TypeError("s3_object_response_limit_invalid");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -58,82 +58,50 @@ export interface IDatabase {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Bodies accepted by the provider-neutral object store seam.
|
|
62
|
+
*
|
|
63
|
+
* A body is handed to an adapter exactly once. Adapters must not eagerly
|
|
64
|
+
* consume or clone streams; callers that need replayability own that concern.
|
|
62
65
|
*/
|
|
63
|
-
export
|
|
66
|
+
export type ObjectStoreBody =
|
|
67
|
+
Blob | ReadableStream<Uint8Array> | ArrayBuffer | string;
|
|
68
|
+
|
|
69
|
+
/** Options for writing one object. */
|
|
70
|
+
export interface ObjectStorePutOptions {
|
|
64
71
|
contentType?: string;
|
|
65
|
-
contentLength?: number;
|
|
66
|
-
etag?: string;
|
|
67
|
-
httpMetadata?: {
|
|
68
|
-
contentType?: string;
|
|
69
|
-
cacheControl?: string;
|
|
70
|
-
contentDisposition?: string;
|
|
71
|
-
contentEncoding?: string;
|
|
72
|
-
contentLanguage?: string;
|
|
73
|
-
};
|
|
74
|
-
customMetadata?: Record<string, string>;
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
/**
|
|
78
|
-
*
|
|
75
|
+
* One object returned by an ObjectStore read.
|
|
76
|
+
*
|
|
77
|
+
* The body remains lazy and is consumed through the returned stream. Metadata
|
|
78
|
+
* is deliberately flat so callers do not depend on a vendor SDK's shape.
|
|
79
79
|
*/
|
|
80
|
-
export interface
|
|
80
|
+
export interface ObjectStoreObject {
|
|
81
81
|
key: string;
|
|
82
|
-
body: ReadableStream | null;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* backend (e.g. the in-memory test stub) tracks an etag.
|
|
87
|
-
*/
|
|
88
|
-
httpEtag?: string;
|
|
89
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
90
|
-
text(): Promise<string>;
|
|
91
|
-
json<T = unknown>(): Promise<T>;
|
|
92
|
-
httpMetadata?: ObjectMetadata["httpMetadata"];
|
|
93
|
-
customMetadata?: Record<string, string>;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* List objects result
|
|
98
|
-
*/
|
|
99
|
-
export interface ListObjectsResult {
|
|
100
|
-
objects: Array<{
|
|
101
|
-
key: string;
|
|
102
|
-
size: number;
|
|
103
|
-
uploaded: Date;
|
|
104
|
-
etag?: string;
|
|
105
|
-
httpMetadata?: ObjectMetadata["httpMetadata"];
|
|
106
|
-
}>;
|
|
107
|
-
truncated: boolean;
|
|
108
|
-
cursor?: string;
|
|
109
|
-
delimitedPrefixes?: string[];
|
|
82
|
+
body: ReadableStream<Uint8Array> | null;
|
|
83
|
+
contentType?: string;
|
|
84
|
+
etag?: string;
|
|
85
|
+
byteLength?: number;
|
|
110
86
|
}
|
|
111
87
|
|
|
112
88
|
/**
|
|
113
|
-
*
|
|
89
|
+
* Provider-neutral object storage seam.
|
|
90
|
+
*
|
|
91
|
+
* Implementations expose only the operations used by production code. Object
|
|
92
|
+
* enumeration and separate metadata probes are intentionally not part of the
|
|
93
|
+
* contract; batch deletion is represented by passing an array of keys.
|
|
114
94
|
*/
|
|
115
|
-
export interface
|
|
95
|
+
export interface ObjectStore {
|
|
116
96
|
put(
|
|
117
97
|
key: string,
|
|
118
|
-
value:
|
|
119
|
-
options?:
|
|
120
|
-
httpMetadata?: ObjectMetadata["httpMetadata"];
|
|
121
|
-
customMetadata?: Record<string, string>;
|
|
122
|
-
},
|
|
98
|
+
value: ObjectStoreBody,
|
|
99
|
+
options?: ObjectStorePutOptions,
|
|
123
100
|
): Promise<void>;
|
|
124
101
|
|
|
125
|
-
get(key: string): Promise<
|
|
126
|
-
|
|
127
|
-
delete(key: string | string[]): Promise<void>;
|
|
128
|
-
|
|
129
|
-
list(options?: {
|
|
130
|
-
prefix?: string;
|
|
131
|
-
limit?: number;
|
|
132
|
-
cursor?: string;
|
|
133
|
-
delimiter?: string;
|
|
134
|
-
}): Promise<ListObjectsResult>;
|
|
102
|
+
get(key: string): Promise<ObjectStoreObject | null>;
|
|
135
103
|
|
|
136
|
-
|
|
104
|
+
delete(key: string | readonly string[]): Promise<void>;
|
|
137
105
|
}
|
|
138
106
|
|
|
139
107
|
/**
|
|
@@ -182,7 +150,7 @@ export interface IStaticAssets {
|
|
|
182
150
|
*/
|
|
183
151
|
export interface RuntimeEnv {
|
|
184
152
|
db: IDatabase;
|
|
185
|
-
storage?:
|
|
153
|
+
storage?: ObjectStore;
|
|
186
154
|
kv?: IKeyValueStore;
|
|
187
155
|
assets?: IStaticAssets;
|
|
188
156
|
|
package/src/backend/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Database } from "../db/index.ts";
|
|
2
2
|
import type {
|
|
3
3
|
IKeyValueStore,
|
|
4
|
-
|
|
4
|
+
ObjectStore,
|
|
5
5
|
IStaticAssets,
|
|
6
6
|
} from "./runtime/types.ts";
|
|
7
7
|
import type { IQueueProducer } from "./runtime/queue.ts";
|
|
@@ -133,8 +133,8 @@ export interface EnvVars {
|
|
|
133
133
|
/**
|
|
134
134
|
* Application Environment
|
|
135
135
|
*
|
|
136
|
-
* Uses the runtime-neutral
|
|
137
|
-
* wraps the native `D1Database` /
|
|
136
|
+
* Uses the runtime-neutral contracts. The Cloudflare worker entry
|
|
137
|
+
* wraps the native `D1Database` / object bucket / `KVNamespace` / `Fetcher`
|
|
138
138
|
* bindings with the adapters in `runtime/cloudflare.ts` before handing
|
|
139
139
|
* the Env to Hono. The local runtime compatibility classes already
|
|
140
140
|
* implement these contracts directly.
|
|
@@ -145,7 +145,7 @@ export interface EnvVars {
|
|
|
145
145
|
*/
|
|
146
146
|
export type Env = {
|
|
147
147
|
DB_INSTANCE: Database;
|
|
148
|
-
MEDIA?:
|
|
148
|
+
MEDIA?: ObjectStore;
|
|
149
149
|
KV: IKeyValueStore;
|
|
150
150
|
ASSETS?: IStaticAssets;
|
|
151
151
|
DELIVERY_QUEUE?: IQueueProducer<DeliveryQueueMessageV1>;
|