@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
|
@@ -11,16 +11,15 @@ import type {
|
|
|
11
11
|
MessageBatch,
|
|
12
12
|
Queue,
|
|
13
13
|
R2Bucket,
|
|
14
|
-
R2Object,
|
|
15
14
|
} from "@cloudflare/workers-types";
|
|
16
15
|
import { getDb } from "../../db/index.ts";
|
|
17
16
|
import type {
|
|
18
17
|
IKeyValueStore,
|
|
19
|
-
|
|
18
|
+
ObjectStore,
|
|
19
|
+
ObjectStoreBody,
|
|
20
|
+
ObjectStoreObject,
|
|
21
|
+
ObjectStorePutOptions,
|
|
20
22
|
IStaticAssets,
|
|
21
|
-
ListObjectsResult,
|
|
22
|
-
ObjectMetadata,
|
|
23
|
-
StorageObject,
|
|
24
23
|
} from "./types.ts";
|
|
25
24
|
import type {
|
|
26
25
|
IQueueBatch,
|
|
@@ -33,76 +32,39 @@ import type {
|
|
|
33
32
|
/**
|
|
34
33
|
* Cloudflare R2 Storage Adapter
|
|
35
34
|
*/
|
|
36
|
-
class CloudflareStorage implements
|
|
35
|
+
class CloudflareStorage implements ObjectStore {
|
|
37
36
|
constructor(private bucket: R2Bucket) {}
|
|
38
37
|
|
|
39
38
|
async put(
|
|
40
39
|
key: string,
|
|
41
|
-
value:
|
|
42
|
-
options?:
|
|
43
|
-
httpMetadata?: ObjectMetadata["httpMetadata"];
|
|
44
|
-
customMetadata?: Record<string, string>;
|
|
45
|
-
},
|
|
40
|
+
value: ObjectStoreBody,
|
|
41
|
+
options?: ObjectStorePutOptions,
|
|
46
42
|
): Promise<void> {
|
|
47
43
|
await this.bucket.put(key, value as Parameters<R2Bucket["put"]>[1], {
|
|
48
|
-
httpMetadata:
|
|
49
|
-
|
|
44
|
+
httpMetadata:
|
|
45
|
+
options?.contentType === undefined
|
|
46
|
+
? undefined
|
|
47
|
+
: { contentType: options.contentType },
|
|
50
48
|
});
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
async get(key: string): Promise<
|
|
51
|
+
async get(key: string): Promise<ObjectStoreObject | null> {
|
|
54
52
|
const obj = await this.bucket.get(key);
|
|
55
53
|
if (!obj) return null;
|
|
56
54
|
|
|
57
55
|
return {
|
|
58
56
|
key,
|
|
59
57
|
body: obj.body as unknown as ReadableStream,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
text: () => obj.text(),
|
|
64
|
-
json: <T>() => obj.json<T>(),
|
|
65
|
-
httpMetadata: obj.httpMetadata,
|
|
66
|
-
customMetadata: obj.customMetadata,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async delete(key: string | string[]): Promise<void> {
|
|
71
|
-
await this.bucket.delete(key);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async list(options?: {
|
|
75
|
-
prefix?: string;
|
|
76
|
-
limit?: number;
|
|
77
|
-
cursor?: string;
|
|
78
|
-
delimiter?: string;
|
|
79
|
-
}): Promise<ListObjectsResult> {
|
|
80
|
-
const result = await this.bucket.list(options);
|
|
81
|
-
return {
|
|
82
|
-
objects: result.objects.map((obj: R2Object) => ({
|
|
83
|
-
key: obj.key,
|
|
84
|
-
size: obj.size,
|
|
85
|
-
uploaded: obj.uploaded,
|
|
86
|
-
etag: obj.etag,
|
|
87
|
-
httpMetadata: obj.httpMetadata,
|
|
88
|
-
})),
|
|
89
|
-
truncated: result.truncated,
|
|
90
|
-
cursor: result.truncated ? result.cursor : undefined,
|
|
91
|
-
delimitedPrefixes: result.delimitedPrefixes,
|
|
58
|
+
contentType: obj.httpMetadata?.contentType,
|
|
59
|
+
etag: obj.httpEtag,
|
|
60
|
+
byteLength: obj.size,
|
|
92
61
|
};
|
|
93
62
|
}
|
|
94
63
|
|
|
95
|
-
async
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
contentType: obj.httpMetadata?.contentType,
|
|
101
|
-
contentLength: obj.size,
|
|
102
|
-
etag: obj.etag,
|
|
103
|
-
httpMetadata: obj.httpMetadata,
|
|
104
|
-
customMetadata: obj.customMetadata,
|
|
105
|
-
};
|
|
64
|
+
async delete(key: string | readonly string[]): Promise<void> {
|
|
65
|
+
await this.bucket.delete(
|
|
66
|
+
typeof key === "string" ? key : ([...key] as string[]),
|
|
67
|
+
);
|
|
106
68
|
}
|
|
107
69
|
}
|
|
108
70
|
|
|
@@ -241,7 +203,7 @@ export function wrapCloudflareBindings<
|
|
|
241
203
|
"DB" | "MEDIA" | "KV" | "ASSETS" | "DELIVERY_QUEUE" | "DELIVERY_DLQ"
|
|
242
204
|
> & {
|
|
243
205
|
DB_INSTANCE: ReturnType<typeof getDb>;
|
|
244
|
-
MEDIA?:
|
|
206
|
+
MEDIA?: ObjectStore;
|
|
245
207
|
KV: IKeyValueStore;
|
|
246
208
|
ASSETS?: IStaticAssets;
|
|
247
209
|
DELIVERY_QUEUE?: IQueueProducer<unknown>;
|
|
@@ -4,12 +4,10 @@ import {
|
|
|
4
4
|
managedRuntimeGatewayFailure,
|
|
5
5
|
managedRuntimeKeyValueListRequest,
|
|
6
6
|
managedRuntimeKeyValueRequest,
|
|
7
|
-
managedRuntimeObjectListRequest,
|
|
8
7
|
managedRuntimeObjectRequest,
|
|
9
8
|
managedRuntimeQueueBatchSendGatewayRequest,
|
|
10
9
|
managedRuntimeQueueSendGatewayRequest,
|
|
11
10
|
parseManagedRuntimeKeyValueListResponse,
|
|
12
|
-
parseManagedRuntimeObjectListResponse,
|
|
13
11
|
parseManagedRuntimeConnectionMaterialization,
|
|
14
12
|
parseManagedRuntimeQueueSendResponse,
|
|
15
13
|
type ManagedRuntimeConnectionMaterialization,
|
|
@@ -17,10 +15,10 @@ import {
|
|
|
17
15
|
|
|
18
16
|
import type {
|
|
19
17
|
IKeyValueStore,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
ObjectStore,
|
|
19
|
+
ObjectStoreBody,
|
|
20
|
+
ObjectStoreObject,
|
|
21
|
+
ObjectStorePutOptions,
|
|
24
22
|
} from "./types.ts";
|
|
25
23
|
import type {
|
|
26
24
|
IQueueProducer,
|
|
@@ -88,7 +86,7 @@ export function createManagedRuntimeKeyValueStore(
|
|
|
88
86
|
|
|
89
87
|
export function createManagedRuntimeObjectStorage(
|
|
90
88
|
options: ManagedRuntimeDataAdapterOptions,
|
|
91
|
-
):
|
|
89
|
+
): ObjectStore {
|
|
92
90
|
const materialization = parseManagedRuntimeConnectionMaterialization(
|
|
93
91
|
options.materialization,
|
|
94
92
|
);
|
|
@@ -326,7 +324,7 @@ class ManagedRuntimeKeyValueStore implements IKeyValueStore {
|
|
|
326
324
|
}
|
|
327
325
|
}
|
|
328
326
|
|
|
329
|
-
class ManagedRuntimeObjectStorage implements
|
|
327
|
+
class ManagedRuntimeObjectStorage implements ObjectStore {
|
|
330
328
|
constructor(private readonly options: ManagedRuntimeDataClientOptions) {
|
|
331
329
|
assertResponseLimit(options.maxMetadataResponseBytes);
|
|
332
330
|
assertResponseLimit(options.maxValueResponseBytes);
|
|
@@ -334,23 +332,17 @@ class ManagedRuntimeObjectStorage implements IObjectStorage {
|
|
|
334
332
|
|
|
335
333
|
async put(
|
|
336
334
|
key: string,
|
|
337
|
-
value:
|
|
338
|
-
options?:
|
|
339
|
-
httpMetadata?: ObjectMetadata["httpMetadata"];
|
|
340
|
-
customMetadata?: Record<string, string>;
|
|
341
|
-
},
|
|
335
|
+
value: ObjectStoreBody,
|
|
336
|
+
options?: ObjectStorePutOptions,
|
|
342
337
|
): Promise<void> {
|
|
343
338
|
const request = managedRuntimeObjectRequest(this.options.authority, {
|
|
344
339
|
method: "PUT",
|
|
345
340
|
key,
|
|
346
341
|
idempotencyKey: this.options.idempotencyKey(),
|
|
347
342
|
value: value as BodyInit,
|
|
348
|
-
...(options?.
|
|
349
|
-
? {}
|
|
350
|
-
: { httpMetadata: options.httpMetadata }),
|
|
351
|
-
...(options?.customMetadata === undefined
|
|
343
|
+
...(options?.contentType === undefined
|
|
352
344
|
? {}
|
|
353
|
-
: {
|
|
345
|
+
: { httpMetadata: { contentType: options.contentType } }),
|
|
354
346
|
});
|
|
355
347
|
await expectOkResponse(
|
|
356
348
|
await this.options.gateway.fetch(request),
|
|
@@ -358,7 +350,7 @@ class ManagedRuntimeObjectStorage implements IObjectStorage {
|
|
|
358
350
|
);
|
|
359
351
|
}
|
|
360
352
|
|
|
361
|
-
async get(key: string): Promise<
|
|
353
|
+
async get(key: string): Promise<ObjectStoreObject | null> {
|
|
362
354
|
const request = managedRuntimeObjectRequest(this.options.authority, {
|
|
363
355
|
method: "GET",
|
|
364
356
|
key,
|
|
@@ -371,11 +363,11 @@ class ManagedRuntimeObjectStorage implements IObjectStorage {
|
|
|
371
363
|
}
|
|
372
364
|
return new ManagedRuntimeStorageObject(
|
|
373
365
|
key,
|
|
374
|
-
await
|
|
366
|
+
await checkedStreamingResponse(raw, this.options.maxValueResponseBytes),
|
|
375
367
|
);
|
|
376
368
|
}
|
|
377
369
|
|
|
378
|
-
async delete(key: string | string[]): Promise<void> {
|
|
370
|
+
async delete(key: string | readonly string[]): Promise<void> {
|
|
379
371
|
for (const entry of Array.isArray(key) ? key : [key]) {
|
|
380
372
|
const request = managedRuntimeObjectRequest(this.options.authority, {
|
|
381
373
|
method: "DELETE",
|
|
@@ -388,149 +380,135 @@ class ManagedRuntimeObjectStorage implements IObjectStorage {
|
|
|
388
380
|
);
|
|
389
381
|
}
|
|
390
382
|
}
|
|
391
|
-
|
|
392
|
-
async list(options?: {
|
|
393
|
-
prefix?: string;
|
|
394
|
-
limit?: number;
|
|
395
|
-
cursor?: string;
|
|
396
|
-
delimiter?: string;
|
|
397
|
-
}): Promise<ListObjectsResult> {
|
|
398
|
-
const request = managedRuntimeObjectListRequest(this.options.authority, {
|
|
399
|
-
idempotencyKey: this.options.idempotencyKey(),
|
|
400
|
-
...options,
|
|
401
|
-
});
|
|
402
|
-
const response = await checkedResponse(
|
|
403
|
-
await this.options.gateway.fetch(request),
|
|
404
|
-
this.options.maxMetadataResponseBytes,
|
|
405
|
-
);
|
|
406
|
-
const parsed = parseManagedRuntimeObjectListResponse(await response.json());
|
|
407
|
-
return {
|
|
408
|
-
objects: parsed.objects.map((entry) => ({
|
|
409
|
-
key: entry.key,
|
|
410
|
-
size: entry.size,
|
|
411
|
-
uploaded: new Date(entry.uploaded),
|
|
412
|
-
...(entry.etag === undefined ? {} : { etag: entry.etag }),
|
|
413
|
-
})),
|
|
414
|
-
truncated: parsed.truncated,
|
|
415
|
-
...(parsed.cursor === undefined ? {} : { cursor: parsed.cursor }),
|
|
416
|
-
...(parsed.delimitedPrefixes === undefined
|
|
417
|
-
? {}
|
|
418
|
-
: { delimitedPrefixes: [...parsed.delimitedPrefixes] }),
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async head(key: string): Promise<ObjectMetadata | null> {
|
|
423
|
-
const request = managedRuntimeObjectRequest(this.options.authority, {
|
|
424
|
-
method: "HEAD",
|
|
425
|
-
key,
|
|
426
|
-
idempotencyKey: this.options.idempotencyKey(),
|
|
427
|
-
});
|
|
428
|
-
const raw = await this.options.gateway.fetch(request);
|
|
429
|
-
if (raw.status === 404) {
|
|
430
|
-
await raw.body?.cancel().catch(() => undefined);
|
|
431
|
-
return null;
|
|
432
|
-
}
|
|
433
|
-
const response = await checkedResponse(
|
|
434
|
-
raw,
|
|
435
|
-
this.options.maxMetadataResponseBytes,
|
|
436
|
-
);
|
|
437
|
-
return objectMetadata(response.headers);
|
|
438
|
-
}
|
|
439
383
|
}
|
|
440
384
|
|
|
441
|
-
class ManagedRuntimeStorageObject implements
|
|
385
|
+
class ManagedRuntimeStorageObject implements ObjectStoreObject {
|
|
442
386
|
constructor(
|
|
443
387
|
readonly key: string,
|
|
444
388
|
private readonly response: Response,
|
|
445
389
|
) {}
|
|
446
390
|
|
|
447
|
-
get body(): ReadableStream | null {
|
|
391
|
+
get body(): ReadableStream<Uint8Array> | null {
|
|
448
392
|
return this.response.body;
|
|
449
393
|
}
|
|
450
394
|
|
|
451
|
-
get
|
|
452
|
-
|
|
395
|
+
get contentType(): string | undefined {
|
|
396
|
+
const value = this.response.headers.get("content-type");
|
|
397
|
+
return value === null || value.length === 0 ? undefined : value;
|
|
453
398
|
}
|
|
454
399
|
|
|
455
|
-
get
|
|
400
|
+
get etag(): string | undefined {
|
|
456
401
|
return this.response.headers.get("etag") ?? undefined;
|
|
457
402
|
}
|
|
458
403
|
|
|
459
|
-
get
|
|
460
|
-
return
|
|
404
|
+
get byteLength(): number | undefined {
|
|
405
|
+
return parseContentLength(this.response.headers.get("content-length"));
|
|
461
406
|
}
|
|
407
|
+
}
|
|
462
408
|
|
|
463
|
-
|
|
464
|
-
|
|
409
|
+
function parseContentLength(value: string | null): number | undefined {
|
|
410
|
+
if (value === null) return undefined;
|
|
411
|
+
if (!/^\d+$/u.test(value)) {
|
|
412
|
+
throw new ManagedRuntimeGatewayError(
|
|
413
|
+
"managed_runtime_object_content_length_invalid",
|
|
414
|
+
502,
|
|
415
|
+
false,
|
|
416
|
+
);
|
|
465
417
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
418
|
+
const parsed = Number(value);
|
|
419
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
420
|
+
throw new ManagedRuntimeGatewayError(
|
|
421
|
+
"managed_runtime_object_content_length_invalid",
|
|
422
|
+
502,
|
|
423
|
+
false,
|
|
424
|
+
);
|
|
469
425
|
}
|
|
426
|
+
return parsed;
|
|
427
|
+
}
|
|
470
428
|
|
|
471
|
-
|
|
472
|
-
|
|
429
|
+
/**
|
|
430
|
+
* Validate a successful object response without consuming its body. The
|
|
431
|
+
* returned stream enforces the configured byte ceiling as it is read.
|
|
432
|
+
*/
|
|
433
|
+
async function checkedStreamingResponse(
|
|
434
|
+
response: Response,
|
|
435
|
+
maxBytes: number,
|
|
436
|
+
): Promise<Response> {
|
|
437
|
+
const declaredLength = parseContentLength(
|
|
438
|
+
response.headers.get("content-length"),
|
|
439
|
+
);
|
|
440
|
+
if (declaredLength !== undefined && declaredLength > maxBytes) {
|
|
441
|
+
await response.body?.cancel("managed_runtime_response_too_large");
|
|
442
|
+
throw new ManagedRuntimeGatewayError(
|
|
443
|
+
"managed_runtime_response_too_large",
|
|
444
|
+
502,
|
|
445
|
+
false,
|
|
446
|
+
);
|
|
473
447
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
448
|
+
if (!response.ok) {
|
|
449
|
+
const failure = await managedRuntimeGatewayFailure(response);
|
|
450
|
+
throw new ManagedRuntimeGatewayError(
|
|
451
|
+
failure?.code ?? "managed_runtime_request_failed",
|
|
452
|
+
failure?.status ?? response.status,
|
|
453
|
+
failure?.retryable ?? false,
|
|
454
|
+
);
|
|
477
455
|
}
|
|
456
|
+
if (!response.body) return response;
|
|
457
|
+
return new Response(boundedReadableStream(response.body, maxBytes), {
|
|
458
|
+
status: response.status,
|
|
459
|
+
statusText: response.statusText,
|
|
460
|
+
headers: response.headers,
|
|
461
|
+
});
|
|
478
462
|
}
|
|
479
463
|
|
|
480
|
-
function
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
Object.values(decoded).some((value) => typeof value !== "string")
|
|
492
|
-
) {
|
|
493
|
-
throw new Error("invalid");
|
|
494
|
-
}
|
|
495
|
-
customMetadata = decoded as Record<string, string>;
|
|
496
|
-
} catch {
|
|
497
|
-
throw new ManagedRuntimeGatewayError(
|
|
498
|
-
"managed_runtime_object_metadata_invalid",
|
|
499
|
-
502,
|
|
500
|
-
false,
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
const httpMetadata = {
|
|
505
|
-
...(headers.get("content-type")
|
|
506
|
-
? { contentType: headers.get("content-type")! }
|
|
507
|
-
: {}),
|
|
508
|
-
...(headers.get("cache-control")
|
|
509
|
-
? { cacheControl: headers.get("cache-control")! }
|
|
510
|
-
: {}),
|
|
511
|
-
...(headers.get("content-disposition")
|
|
512
|
-
? { contentDisposition: headers.get("content-disposition")! }
|
|
513
|
-
: {}),
|
|
514
|
-
...(headers.get("content-encoding")
|
|
515
|
-
? { contentEncoding: headers.get("content-encoding")! }
|
|
516
|
-
: {}),
|
|
517
|
-
...(headers.get("content-language")
|
|
518
|
-
? { contentLanguage: headers.get("content-language")! }
|
|
519
|
-
: {}),
|
|
520
|
-
};
|
|
521
|
-
return {
|
|
522
|
-
...(headers.get("content-type")
|
|
523
|
-
? { contentType: headers.get("content-type")! }
|
|
524
|
-
: {}),
|
|
525
|
-
...(contentLength !== null &&
|
|
526
|
-
/^\d+$/u.test(contentLength) &&
|
|
527
|
-
Number.isSafeInteger(Number(contentLength))
|
|
528
|
-
? { contentLength: Number(contentLength) }
|
|
529
|
-
: {}),
|
|
530
|
-
...(headers.get("etag") ? { etag: headers.get("etag")! } : {}),
|
|
531
|
-
...(Object.keys(httpMetadata).length === 0 ? {} : { httpMetadata }),
|
|
532
|
-
...(customMetadata === undefined ? {} : { customMetadata }),
|
|
464
|
+
function boundedReadableStream(
|
|
465
|
+
stream: ReadableStream<Uint8Array>,
|
|
466
|
+
maxBytes: number,
|
|
467
|
+
): ReadableStream<Uint8Array> {
|
|
468
|
+
const reader = stream.getReader();
|
|
469
|
+
let size = 0;
|
|
470
|
+
let finished = false;
|
|
471
|
+
const finish = async (): Promise<void> => {
|
|
472
|
+
if (finished) return;
|
|
473
|
+
finished = true;
|
|
474
|
+
reader.releaseLock();
|
|
533
475
|
};
|
|
476
|
+
return new ReadableStream<Uint8Array>(
|
|
477
|
+
{
|
|
478
|
+
async pull(controller) {
|
|
479
|
+
try {
|
|
480
|
+
const result = await reader.read();
|
|
481
|
+
if (result.done) {
|
|
482
|
+
controller.close();
|
|
483
|
+
await finish();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
size += result.value.byteLength;
|
|
487
|
+
if (size > maxBytes) {
|
|
488
|
+
await reader.cancel("managed_runtime_response_too_large");
|
|
489
|
+
controller.error(
|
|
490
|
+
new ManagedRuntimeGatewayError(
|
|
491
|
+
"managed_runtime_response_too_large",
|
|
492
|
+
502,
|
|
493
|
+
false,
|
|
494
|
+
),
|
|
495
|
+
);
|
|
496
|
+
await finish();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
controller.enqueue(result.value);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
controller.error(error);
|
|
502
|
+
await finish();
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
async cancel(reason) {
|
|
506
|
+
await reader.cancel(reason).catch(() => undefined);
|
|
507
|
+
await finish();
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
{ highWaterMark: 0 },
|
|
511
|
+
);
|
|
534
512
|
}
|
|
535
513
|
|
|
536
514
|
async function checkedResponse(
|