@takosjp/yurucommu-core 3.4.5 → 4.1.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/README.en.md +3 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/packages/api/package.json +1 -1
- package/src/backend/index.ts +44 -16
- package/src/backend/lib/blocklist-purge.ts +6 -6
- package/src/backend/public.ts +69 -4
- package/src/backend/routes/account-teardown.ts +2 -2
- package/src/backend/routes/apps.ts +4 -5
- package/src/backend/routes/media.ts +8 -5
- 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 +655 -320
- package/src/backend/runtime/cloudflare.ts +20 -58
- package/src/backend/runtime/edge-facades.ts +336 -0
- package/src/backend/runtime/edge-kv.ts +204 -0
- package/src/backend/runtime/edge-objects.ts +120 -0
- package/src/backend/runtime/edge-queue.ts +135 -0
- package/src/backend/runtime/edge-sql.ts +207 -0
- package/src/backend/runtime/lane.ts +291 -0
- package/src/backend/runtime/managed-relational.ts +70 -23
- package/src/backend/runtime/managed-runtime.ts +117 -139
- package/src/backend/runtime/s3-fetch.ts +380 -0
- package/src/backend/runtime/sqlite-proxy-rows.ts +309 -0
- package/src/backend/runtime/types.ts +29 -61
- package/src/backend/server.ts +5 -0
- package/src/backend/types.ts +13 -4
- package/src/db/index.ts +2 -1
package/README.en.md
CHANGED
|
@@ -87,6 +87,9 @@ Takos core. See [`AGENTS.md`](AGENTS.md) for the full product boundary.
|
|
|
87
87
|
|
|
88
88
|
## Documentation
|
|
89
89
|
|
|
90
|
+
- [Runtime lanes](docs/design/runtime-lanes.md) — how the bindings differ between
|
|
91
|
+
a raw-Cloudflare deployment and one on a host that projects the portable
|
|
92
|
+
facades, and how a deployment declares which it is
|
|
90
93
|
- [Deployment guide](https://yurucommu.com/help/deployment.html)
|
|
91
94
|
- [Getting started](https://yurucommu.com/help/getting-started.html)
|
|
92
95
|
- [Help site](https://yurucommu.com/help/)
|
package/README.md
CHANGED
|
@@ -86,6 +86,8 @@ Yurucommu は ActivityPub 連合・コンテンツ配送・ユーザー identity
|
|
|
86
86
|
|
|
87
87
|
## ドキュメント
|
|
88
88
|
|
|
89
|
+
- [Runtime lanes](docs/design/runtime-lanes.md) — raw Cloudflare binding と
|
|
90
|
+
portable facade で binding の形が変わる点と、その宣言方法
|
|
89
91
|
- [Deployment guide](https://yurucommu.com/help/deployment.html)
|
|
90
92
|
- [Getting started](https://yurucommu.com/help/getting-started.html)
|
|
91
93
|
- [Help site](https://yurucommu.com/help/)
|
package/package.json
CHANGED
package/src/backend/index.ts
CHANGED
|
@@ -5,9 +5,12 @@ import type { Env, EnvVars, Variables } from "./types.ts";
|
|
|
5
5
|
import { extractActorFromSession } from "./lib/session-actor.ts";
|
|
6
6
|
import { isBackendPath } from "./lib/backend-paths.ts";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
resolveRuntimeLane,
|
|
9
|
+
wrapRuntimeBindings,
|
|
10
|
+
wrapRuntimeMessageBatch,
|
|
11
|
+
type PortableWorkerBindings,
|
|
12
|
+
} from "./runtime/lane.ts";
|
|
13
|
+
import type { EdgeQueueBatch } from "./runtime/edge-facades.ts";
|
|
11
14
|
import {
|
|
12
15
|
getMobileOidcAudience,
|
|
13
16
|
getOidcClientCredentials,
|
|
@@ -992,15 +995,17 @@ export async function handleYurucommuQueueBatch(
|
|
|
992
995
|
batch.ackAll();
|
|
993
996
|
}
|
|
994
997
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
998
|
+
/**
|
|
999
|
+
* Bindings that are not the runtime ports, and so pass through whichever lane
|
|
1000
|
+
* wrapper runs. Durable Objects live here: Takoform's Worker Version form has
|
|
1001
|
+
* no Durable Object binding, so on the portable lane both are simply unbound
|
|
1002
|
+
* and the routes that need them answer 503, exactly as on a Cloudflare
|
|
1003
|
+
* deployment that did not declare them.
|
|
1004
|
+
*/
|
|
1005
|
+
type PassthroughBindings = EnvVars & {
|
|
999
1006
|
ASSETS?: Fetcher;
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
// Signaling hub Durable Object namespace (call feature). wrapCloudflareBindings
|
|
1003
|
-
// spreads it through untouched (it is not DB/MEDIA/KV/ASSETS) so app code and
|
|
1007
|
+
// Signaling hub Durable Object namespace (call feature). The lane wrappers
|
|
1008
|
+
// spread it through untouched (it is not DB/MEDIA/KV/ASSETS) so app code and
|
|
1004
1009
|
// the rtc routes read it as c.env.CALL_SIGNALING.
|
|
1005
1010
|
CALL_SIGNALING?: DurableObjectNamespace;
|
|
1006
1011
|
// Per-user realtime event stream Durable Object namespace. Same pass-through
|
|
@@ -1009,6 +1014,26 @@ type WorkerBindings = EnvVars & {
|
|
|
1009
1014
|
REALTIME_STREAM?: DurableObjectNamespace;
|
|
1010
1015
|
};
|
|
1011
1016
|
|
|
1017
|
+
/**
|
|
1018
|
+
* What this Worker may be handed.
|
|
1019
|
+
*
|
|
1020
|
+
* Raw Cloudflare bindings, or the portable facades a wrapper host projects.
|
|
1021
|
+
* `wrapRuntimeBindings` decides which by reading the deployment's declared
|
|
1022
|
+
* `YURUCOMMU_RUNTIME_LANE` and proving it against the bindings that actually
|
|
1023
|
+
* arrived; see runtime/lane.ts.
|
|
1024
|
+
*/
|
|
1025
|
+
type WorkerBindings = PassthroughBindings &
|
|
1026
|
+
(
|
|
1027
|
+
| {
|
|
1028
|
+
DB: D1Database;
|
|
1029
|
+
MEDIA?: R2Bucket;
|
|
1030
|
+
KV: KVNamespace;
|
|
1031
|
+
DELIVERY_QUEUE?: Queue<DeliveryQueueMessageV1>;
|
|
1032
|
+
DELIVERY_DLQ?: Queue<DeliveryDlqMessageV1>;
|
|
1033
|
+
}
|
|
1034
|
+
| PortableWorkerBindings
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1012
1037
|
function isMaterializedRuntimeEnv(
|
|
1013
1038
|
bindings: WorkerBindings | Env,
|
|
1014
1039
|
): bindings is Env {
|
|
@@ -1021,16 +1046,19 @@ export default {
|
|
|
1021
1046
|
bindings: WorkerBindings,
|
|
1022
1047
|
ctx: ExecutionContext,
|
|
1023
1048
|
): Promise<Response> {
|
|
1024
|
-
return app.fetch(request,
|
|
1049
|
+
return app.fetch(request, wrapRuntimeBindings(bindings), ctx);
|
|
1025
1050
|
},
|
|
1026
1051
|
|
|
1027
1052
|
async queue(
|
|
1028
|
-
batch:
|
|
1053
|
+
batch:
|
|
1054
|
+
| MessageBatch<DeliveryQueueMessageV1 | DeliveryDlqMessageV1>
|
|
1055
|
+
| EdgeQueueBatch,
|
|
1029
1056
|
bindings: WorkerBindings,
|
|
1030
1057
|
): Promise<void> {
|
|
1058
|
+
const lane = resolveRuntimeLane(bindings.YURUCOMMU_RUNTIME_LANE);
|
|
1031
1059
|
return handleYurucommuQueueBatch(
|
|
1032
|
-
|
|
1033
|
-
|
|
1060
|
+
wrapRuntimeMessageBatch(batch, lane),
|
|
1061
|
+
wrapRuntimeBindings(bindings),
|
|
1034
1062
|
);
|
|
1035
1063
|
},
|
|
1036
1064
|
|
|
@@ -1041,7 +1069,7 @@ export default {
|
|
|
1041
1069
|
): Promise<void> {
|
|
1042
1070
|
const env = isMaterializedRuntimeEnv(bindings)
|
|
1043
1071
|
? bindings
|
|
1044
|
-
:
|
|
1072
|
+
: wrapRuntimeBindings(bindings);
|
|
1045
1073
|
await runYurucommuRetention(env);
|
|
1046
1074
|
},
|
|
1047
1075
|
};
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
storyVotes,
|
|
25
25
|
} from "../../db/index.ts";
|
|
26
26
|
import type { Database } from "../../db/index.ts";
|
|
27
|
-
import type {
|
|
27
|
+
import type { ObjectStore } from "../runtime/types.ts";
|
|
28
28
|
import { chunkForInClause, D1_IN_CHUNK } from "./chunk.ts";
|
|
29
29
|
import { normalizeDomain } from "./blocklist.ts";
|
|
30
30
|
import { isSameActivityPubActor } from "./activitypub-actor-identity.ts";
|
|
@@ -112,7 +112,7 @@ function activityPubUrlHostMatchesDomain(
|
|
|
112
112
|
async function purgeObjects(
|
|
113
113
|
db: Database,
|
|
114
114
|
apIds: string[],
|
|
115
|
-
media?:
|
|
115
|
+
media?: ObjectStore,
|
|
116
116
|
): Promise<void> {
|
|
117
117
|
if (apIds.length === 0) return;
|
|
118
118
|
const parentRows = await db
|
|
@@ -544,7 +544,7 @@ async function purgeActorInteractionEdges(
|
|
|
544
544
|
async function purgeMatchingObjects(
|
|
545
545
|
db: Database,
|
|
546
546
|
where: SQL,
|
|
547
|
-
media?:
|
|
547
|
+
media?: ObjectStore,
|
|
548
548
|
onPageDeleted?: (count: number) => void,
|
|
549
549
|
): Promise<void> {
|
|
550
550
|
let cursor: string | undefined;
|
|
@@ -604,7 +604,7 @@ async function purgeMatchingActivities(
|
|
|
604
604
|
async function purgeActorObjects(
|
|
605
605
|
db: Database,
|
|
606
606
|
blockedApId: string,
|
|
607
|
-
media?:
|
|
607
|
+
media?: ObjectStore,
|
|
608
608
|
onPageDeleted?: (count: number) => void,
|
|
609
609
|
): Promise<void> {
|
|
610
610
|
let cursor: string | undefined;
|
|
@@ -666,7 +666,7 @@ async function purgeActorActivities(
|
|
|
666
666
|
export async function purgeActorContent(
|
|
667
667
|
db: Database,
|
|
668
668
|
blockedApId: string,
|
|
669
|
-
media?:
|
|
669
|
+
media?: ObjectStore,
|
|
670
670
|
): Promise<BlocklistContentPurgeResult> {
|
|
671
671
|
let deletedObjects = 0;
|
|
672
672
|
let deletedActivities = 0;
|
|
@@ -704,7 +704,7 @@ export async function purgeActorContent(
|
|
|
704
704
|
export async function purgeDomainContent(
|
|
705
705
|
db: Database,
|
|
706
706
|
domainOrUrl: string,
|
|
707
|
-
media?:
|
|
707
|
+
media?: ObjectStore,
|
|
708
708
|
): Promise<BlocklistContentPurgeResult> {
|
|
709
709
|
const domain = normalizeDomain(domainOrUrl);
|
|
710
710
|
if (!domain) {
|
package/src/backend/public.ts
CHANGED
|
@@ -36,13 +36,78 @@ export {
|
|
|
36
36
|
createManagedRelationalDatabase,
|
|
37
37
|
type ManagedRelationalDatabaseOptions,
|
|
38
38
|
} from "./runtime/managed-relational.ts";
|
|
39
|
+
// The portable lane: the binding facades a wrapper host projects, and the lane
|
|
40
|
+
// selector that proves a deployment's declared lane against the bindings that
|
|
41
|
+
// actually arrived.
|
|
42
|
+
export {
|
|
43
|
+
DEFAULT_RUNTIME_LANE,
|
|
44
|
+
RUNTIME_LANE_VAR,
|
|
45
|
+
RUNTIME_LANES,
|
|
46
|
+
RuntimeLaneError,
|
|
47
|
+
assertRuntimeLaneBindings,
|
|
48
|
+
resolveRuntimeLane,
|
|
49
|
+
type CloudflareWorkerBindings,
|
|
50
|
+
type PortableWorkerBindings,
|
|
51
|
+
type RuntimeLane,
|
|
52
|
+
wrapPortableBindings,
|
|
53
|
+
wrapRuntimeBindings,
|
|
54
|
+
wrapRuntimeMessageBatch,
|
|
55
|
+
} from "./runtime/lane.ts";
|
|
56
|
+
export {
|
|
57
|
+
EDGE_KV_MAX_EXPIRATION_TTL_SECONDS,
|
|
58
|
+
EDGE_KV_MIN_EXPIRATION_TTL_SECONDS,
|
|
59
|
+
isEdgeObjectsBinding,
|
|
60
|
+
isEdgeQueueBatch,
|
|
61
|
+
isEdgeSqlBinding,
|
|
62
|
+
isNativeD1Database,
|
|
63
|
+
isNativeR2Bucket,
|
|
64
|
+
type EdgeKvBinding,
|
|
65
|
+
type EdgeObjectsBinding,
|
|
66
|
+
type EdgeQueueBatch,
|
|
67
|
+
type EdgeQueueBinding,
|
|
68
|
+
type EdgeSqlBinding,
|
|
69
|
+
type EdgeSqlResult,
|
|
70
|
+
type EdgeSqlValue,
|
|
71
|
+
} from "./runtime/edge-facades.ts";
|
|
72
|
+
export {
|
|
73
|
+
EdgeKeyValueOptionError,
|
|
74
|
+
EdgeKeyValueStore,
|
|
75
|
+
EdgeKeyValueValueError,
|
|
76
|
+
wrapEdgeKv,
|
|
77
|
+
} from "./runtime/edge-kv.ts";
|
|
78
|
+
export {
|
|
79
|
+
EdgeSqlShapeError,
|
|
80
|
+
createEdgeSqlDatabase,
|
|
81
|
+
} from "./runtime/edge-sql.ts";
|
|
82
|
+
export {
|
|
83
|
+
ProxyColumnMismatchError,
|
|
84
|
+
positionalRow,
|
|
85
|
+
rewriteProjection,
|
|
86
|
+
type ProjectedStatement,
|
|
87
|
+
type RewrittenStatement,
|
|
88
|
+
} from "./runtime/sqlite-proxy-rows.ts";
|
|
89
|
+
export {
|
|
90
|
+
EdgeQueueShapeError,
|
|
91
|
+
wrapEdgeMessageBatch,
|
|
92
|
+
wrapEdgeQueue,
|
|
93
|
+
} from "./runtime/edge-queue.ts";
|
|
94
|
+
export {
|
|
95
|
+
EdgeObjectStorage,
|
|
96
|
+
EdgeObjectsShapeError,
|
|
97
|
+
wrapEdgeObjects,
|
|
98
|
+
} from "./runtime/edge-objects.ts";
|
|
39
99
|
export type {
|
|
40
100
|
IKeyValueStore,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
101
|
+
ObjectStore,
|
|
102
|
+
ObjectStoreBody,
|
|
103
|
+
ObjectStoreObject,
|
|
104
|
+
ObjectStorePutOptions,
|
|
45
105
|
} from "./runtime/types.ts";
|
|
106
|
+
export {
|
|
107
|
+
createS3FetchObjectStore,
|
|
108
|
+
S3FetchObjectStoreError,
|
|
109
|
+
type S3ObjectFetcher,
|
|
110
|
+
} from "./runtime/s3-fetch.ts";
|
|
46
111
|
export type {
|
|
47
112
|
IQueueBatch,
|
|
48
113
|
IQueueMessage,
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
} from "../../db/index.ts";
|
|
48
48
|
import type { Database } from "../../db/index.ts";
|
|
49
49
|
import type { Env } from "../types.ts";
|
|
50
|
-
import type {
|
|
50
|
+
import type { ObjectStore } from "../runtime/types.ts";
|
|
51
51
|
import { activityApId, generateId } from "../federation-helpers.ts";
|
|
52
52
|
import { chunkForInClause } from "../lib/chunk.ts";
|
|
53
53
|
import { snapshotAndEnqueueFollowerDeliveries } from "../lib/delivery/queue-batching.ts";
|
|
@@ -80,7 +80,7 @@ interface BatchableDb {
|
|
|
80
80
|
*/
|
|
81
81
|
export async function purgeActorMediaUploads(
|
|
82
82
|
db: Database,
|
|
83
|
-
media:
|
|
83
|
+
media: ObjectStore | undefined,
|
|
84
84
|
apId: string,
|
|
85
85
|
): Promise<void> {
|
|
86
86
|
const uploads = await db
|
|
@@ -230,7 +230,7 @@ appsApiRoutes.post(
|
|
|
230
230
|
}
|
|
231
231
|
const r2Key = `${appPrefix}${normalizedPath}`;
|
|
232
232
|
await media.put(r2Key, contentBytes, {
|
|
233
|
-
|
|
233
|
+
contentType,
|
|
234
234
|
});
|
|
235
235
|
|
|
236
236
|
results.push({ path: normalizedPath, status: "uploaded" });
|
|
@@ -276,14 +276,13 @@ appsServeRoutes.get("/:clientId/:appName/*", async (c) => {
|
|
|
276
276
|
|
|
277
277
|
const object = await media.get(r2Key);
|
|
278
278
|
if (object) {
|
|
279
|
-
const contentType =
|
|
280
|
-
object.httpMetadata?.contentType ?? inferContentType(filePath);
|
|
279
|
+
const contentType = object.contentType ?? inferContentType(filePath);
|
|
281
280
|
const headers = createHostedHeaders(
|
|
282
281
|
contentType,
|
|
283
282
|
filePath.includes("/assets/")
|
|
284
283
|
? "public, max-age=31536000, immutable"
|
|
285
284
|
: "public, max-age=3600",
|
|
286
|
-
object.
|
|
285
|
+
object.etag,
|
|
287
286
|
);
|
|
288
287
|
return new Response(object.body, { headers });
|
|
289
288
|
}
|
|
@@ -296,7 +295,7 @@ appsServeRoutes.get("/:clientId/:appName/*", async (c) => {
|
|
|
296
295
|
const headers = createHostedHeaders(
|
|
297
296
|
"text/html; charset=utf-8",
|
|
298
297
|
"no-cache",
|
|
299
|
-
indexObject.
|
|
298
|
+
indexObject.etag,
|
|
300
299
|
);
|
|
301
300
|
return new Response(indexObject.body, { headers });
|
|
302
301
|
}
|
|
@@ -234,8 +234,12 @@ media.post("/upload", async (c) => {
|
|
|
234
234
|
// transcode pipeline, and buffering a 40MB video would pressure the Worker
|
|
235
235
|
// memory budget.
|
|
236
236
|
if (isVideo) {
|
|
237
|
+
// The Blob is handed over whole rather than as a bare stream: an
|
|
238
|
+
// ObjectStore adapter reads `File.size` from it, so the portable
|
|
239
|
+
// `edge.objects` lane can declare the length while streaming instead of
|
|
240
|
+
// buffering the whole video in the Worker to discover it.
|
|
237
241
|
await media.put(r2Key, file, {
|
|
238
|
-
|
|
242
|
+
contentType,
|
|
239
243
|
});
|
|
240
244
|
} else {
|
|
241
245
|
const original = new Uint8Array(await file.arrayBuffer());
|
|
@@ -247,7 +251,7 @@ media.post("/upload", async (c) => {
|
|
|
247
251
|
cleaned.byteOffset + cleaned.byteLength,
|
|
248
252
|
) as ArrayBuffer;
|
|
249
253
|
await media.put(r2Key, cleanedBuffer, {
|
|
250
|
-
|
|
254
|
+
contentType,
|
|
251
255
|
});
|
|
252
256
|
}
|
|
253
257
|
|
|
@@ -579,13 +583,12 @@ async function serveMediaByR2Key(c: MediaContext, r2Key: string) {
|
|
|
579
583
|
const object = await media.get(r2Key);
|
|
580
584
|
if (!object) return c.notFound();
|
|
581
585
|
|
|
582
|
-
const contentType =
|
|
583
|
-
object.httpMetadata?.contentType || "application/octet-stream";
|
|
586
|
+
const contentType = object.contentType || "application/octet-stream";
|
|
584
587
|
const cacheScope = authResult.isPublic ? "public" : "private";
|
|
585
588
|
const maxAge = contentType.startsWith("video/")
|
|
586
589
|
? CACHE_MAX_AGE_VIDEO
|
|
587
590
|
: CACHE_MAX_AGE_IMAGE;
|
|
588
|
-
const etag = object.
|
|
591
|
+
const etag = object.etag;
|
|
589
592
|
|
|
590
593
|
if (!object.body) {
|
|
591
594
|
return c.body(null, 200, {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import { and, asc, eq, gt, inArray, isNull, or, sql } from "drizzle-orm";
|
|
23
23
|
import type { D1Statement, Database } from "../../../db/index.ts";
|
|
24
|
-
import type {
|
|
24
|
+
import type { ObjectStore } from "../../runtime/types.ts";
|
|
25
25
|
import {
|
|
26
26
|
activities,
|
|
27
27
|
actors,
|
|
@@ -80,7 +80,7 @@ async function deleteAttachedMediaUploadsForObject(
|
|
|
80
80
|
db: Database,
|
|
81
81
|
obj: CascadeObject,
|
|
82
82
|
removedObjectApIds: ReadonlySet<string>,
|
|
83
|
-
media?:
|
|
83
|
+
media?: ObjectStore,
|
|
84
84
|
): Promise<{
|
|
85
85
|
mediaKeys: string[];
|
|
86
86
|
mediaUploadIds: string[];
|
|
@@ -191,7 +191,7 @@ async function deleteAttachedMediaUploadsForObject(
|
|
|
191
191
|
* system's already-accepted media failure mode.
|
|
192
192
|
*/
|
|
193
193
|
export async function purgeMediaBlobs(
|
|
194
|
-
media:
|
|
194
|
+
media: ObjectStore | undefined,
|
|
195
195
|
keys: string[],
|
|
196
196
|
): Promise<void> {
|
|
197
197
|
if (!media || keys.length === 0) return;
|
|
@@ -220,7 +220,7 @@ export async function reapReplacedMediaUrl(
|
|
|
220
220
|
db: Database,
|
|
221
221
|
oldUrl: string | null | undefined,
|
|
222
222
|
uploaderApId: string,
|
|
223
|
-
media?:
|
|
223
|
+
media?: ObjectStore,
|
|
224
224
|
): Promise<void> {
|
|
225
225
|
try {
|
|
226
226
|
if (!oldUrl || !oldUrl.startsWith("/media/")) return;
|
|
@@ -303,7 +303,7 @@ export async function reapReplacedMediaUrl(
|
|
|
303
303
|
export async function deleteObjectCascade(
|
|
304
304
|
db: Database,
|
|
305
305
|
objectApId: string,
|
|
306
|
-
media?:
|
|
306
|
+
media?: ObjectStore,
|
|
307
307
|
): Promise<string[]> {
|
|
308
308
|
return await deleteObjectsCascade(db, [objectApId], media);
|
|
309
309
|
}
|
|
@@ -316,7 +316,7 @@ export async function deleteObjectCascade(
|
|
|
316
316
|
export async function prepareObjectDeleteCascade(
|
|
317
317
|
db: Database,
|
|
318
318
|
objectApId: string,
|
|
319
|
-
media?:
|
|
319
|
+
media?: ObjectStore,
|
|
320
320
|
): Promise<{
|
|
321
321
|
mediaKeys: string[];
|
|
322
322
|
statements: readonly [D1Statement, ...D1Statement[]];
|
|
@@ -392,7 +392,7 @@ export async function prepareObjectDeleteCascade(
|
|
|
392
392
|
export async function deleteObjectsCascade(
|
|
393
393
|
db: Database,
|
|
394
394
|
objectApIds: string[],
|
|
395
|
-
media?:
|
|
395
|
+
media?: ObjectStore,
|
|
396
396
|
): Promise<string[]> {
|
|
397
397
|
const uniqueApIds = [...new Set(objectApIds)];
|
|
398
398
|
if (uniqueApIds.length === 0) return [];
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
objects,
|
|
9
9
|
storyVotes,
|
|
10
10
|
} from "../../../db/index.ts";
|
|
11
|
-
import type {
|
|
11
|
+
import type { ObjectStore } from "../../runtime/types.ts";
|
|
12
12
|
import {
|
|
13
13
|
formatUsername,
|
|
14
14
|
objectApId,
|
|
@@ -314,7 +314,7 @@ export function buildAuthor(
|
|
|
314
314
|
|
|
315
315
|
export async function cleanupExpiredStories(
|
|
316
316
|
db: Database,
|
|
317
|
-
media?:
|
|
317
|
+
media?: ObjectStore,
|
|
318
318
|
): Promise<number> {
|
|
319
319
|
const now = new Date().toISOString();
|
|
320
320
|
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
purgeMediaBlobs,
|
|
19
19
|
} from "../posts/delete-cascade.ts";
|
|
20
20
|
import type { Env, Variables } from "../../types.ts";
|
|
21
|
-
import type {
|
|
21
|
+
import type { ObjectStore } from "../../runtime/types.ts";
|
|
22
22
|
import {
|
|
23
23
|
activityApId,
|
|
24
24
|
actorApId,
|
|
@@ -61,10 +61,7 @@ const stories = new Hono<{ Bindings: Env; Variables: Variables }>();
|
|
|
61
61
|
// at most one fallback pass runs at a time per isolate.
|
|
62
62
|
let expiredStoryCleanupInFlight = false;
|
|
63
63
|
|
|
64
|
-
function maybeCleanupExpiredStories(
|
|
65
|
-
db: Database,
|
|
66
|
-
media?: IObjectStorage,
|
|
67
|
-
): void {
|
|
64
|
+
function maybeCleanupExpiredStories(db: Database, media?: ObjectStore): void {
|
|
68
65
|
if (expiredStoryCleanupInFlight) return;
|
|
69
66
|
if (Math.random() >= 0.01) return; // ~1% of feed requests per isolate
|
|
70
67
|
|