@takosjp/yurucommu-core 3.4.1 → 3.4.3
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
CHANGED
package/src/backend/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ import type {
|
|
|
56
56
|
MessageBatch,
|
|
57
57
|
Queue,
|
|
58
58
|
R2Bucket,
|
|
59
|
+
ScheduledController,
|
|
59
60
|
} from "@cloudflare/workers-types";
|
|
60
61
|
import type {
|
|
61
62
|
DeliveryDlqMessageV1,
|
|
@@ -66,6 +67,7 @@ import {
|
|
|
66
67
|
handleDeliveryQueueBatch,
|
|
67
68
|
} from "./lib/delivery/queue.ts";
|
|
68
69
|
import { enqueuePendingNotificationPushJobs } from "./lib/notification-push.ts";
|
|
70
|
+
import { runYurucommuRetention } from "./retention.ts";
|
|
69
71
|
|
|
70
72
|
type YurucommuApp = Hono<{ Bindings: Env; Variables: Variables }>;
|
|
71
73
|
|
|
@@ -994,6 +996,12 @@ type WorkerBindings = EnvVars & {
|
|
|
994
996
|
REALTIME_STREAM?: DurableObjectNamespace;
|
|
995
997
|
};
|
|
996
998
|
|
|
999
|
+
function isMaterializedRuntimeEnv(
|
|
1000
|
+
bindings: WorkerBindings | Env,
|
|
1001
|
+
): bindings is Env {
|
|
1002
|
+
return "DB_INSTANCE" in bindings && !!bindings.DB_INSTANCE;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
997
1005
|
export default {
|
|
998
1006
|
async fetch(
|
|
999
1007
|
request: Request,
|
|
@@ -1012,4 +1020,15 @@ export default {
|
|
|
1012
1020
|
wrapCloudflareBindings(bindings),
|
|
1013
1021
|
);
|
|
1014
1022
|
},
|
|
1023
|
+
|
|
1024
|
+
async scheduled(
|
|
1025
|
+
_controller: ScheduledController,
|
|
1026
|
+
bindings: WorkerBindings | Env,
|
|
1027
|
+
_ctx: ExecutionContext,
|
|
1028
|
+
): Promise<void> {
|
|
1029
|
+
const env = isMaterializedRuntimeEnv(bindings)
|
|
1030
|
+
? bindings
|
|
1031
|
+
: wrapCloudflareBindings(bindings);
|
|
1032
|
+
await runYurucommuRetention(env);
|
|
1033
|
+
},
|
|
1015
1034
|
};
|
package/src/backend/public.ts
CHANGED
|
@@ -9,6 +9,12 @@ export {
|
|
|
9
9
|
type YurucommuBackendDiscoveryOptionsV1,
|
|
10
10
|
type YurucommuBackendPluginV1,
|
|
11
11
|
} from "./index.ts";
|
|
12
|
+
export {
|
|
13
|
+
runYurucommuRetention,
|
|
14
|
+
YurucommuRetentionError,
|
|
15
|
+
type YurucommuRetentionResult,
|
|
16
|
+
type YurucommuRetentionStep,
|
|
17
|
+
} from "./retention.ts";
|
|
12
18
|
export { default } from "./index.ts";
|
|
13
19
|
export { default as app } from "./index.ts";
|
|
14
20
|
export { type Database, getDb, getDbSQLite } from "../db/index.ts";
|
|
@@ -19,7 +25,10 @@ export {
|
|
|
19
25
|
} from "./runtime/cloudflare.ts";
|
|
20
26
|
export {
|
|
21
27
|
ManagedRuntimeGatewayError,
|
|
28
|
+
createManagedRuntimeKeyValueStore,
|
|
29
|
+
createManagedRuntimeObjectStorage,
|
|
22
30
|
createManagedRuntimeQueueProducer,
|
|
31
|
+
type ManagedRuntimeDataAdapterOptions,
|
|
23
32
|
type ManagedRuntimeGateway,
|
|
24
33
|
type ManagedRuntimeQueueProducerOptions,
|
|
25
34
|
} from "./runtime/managed-runtime.ts";
|
|
@@ -27,6 +36,13 @@ export {
|
|
|
27
36
|
createManagedRelationalDatabase,
|
|
28
37
|
type ManagedRelationalDatabaseOptions,
|
|
29
38
|
} from "./runtime/managed-relational.ts";
|
|
39
|
+
export type {
|
|
40
|
+
IKeyValueStore,
|
|
41
|
+
IObjectStorage,
|
|
42
|
+
ListObjectsResult,
|
|
43
|
+
ObjectMetadata,
|
|
44
|
+
StorageObject,
|
|
45
|
+
} from "./runtime/types.ts";
|
|
30
46
|
export type {
|
|
31
47
|
IQueueBatch,
|
|
32
48
|
IQueueMessage,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Env } from "./types.ts";
|
|
2
|
+
import { enqueuePendingNotificationPushJobs } from "./lib/notification-push.ts";
|
|
3
|
+
import { reapDrainedTombstones } from "./routes/actors.ts";
|
|
4
|
+
import { cleanupExpiredStories } from "./routes/stories/query-helpers.ts";
|
|
5
|
+
|
|
6
|
+
export type YurucommuRetentionStep =
|
|
7
|
+
"expired_stories" | "drained_tombstones" | "notification_push";
|
|
8
|
+
|
|
9
|
+
export interface YurucommuRetentionResult {
|
|
10
|
+
readonly expiredStories: number;
|
|
11
|
+
readonly reapedTombstones: number;
|
|
12
|
+
readonly enqueuedNotificationPushJobs: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Identifies the exact bounded retention step that failed. Scheduled callers
|
|
17
|
+
* must reject the invocation instead of treating a partial or skipped sweep as
|
|
18
|
+
* success; the original database/runtime error remains available as `cause`.
|
|
19
|
+
*/
|
|
20
|
+
export class YurucommuRetentionError extends Error {
|
|
21
|
+
constructor(
|
|
22
|
+
readonly step: YurucommuRetentionStep,
|
|
23
|
+
cause: unknown,
|
|
24
|
+
) {
|
|
25
|
+
super(`yurucommu retention failed at ${step}`, { cause });
|
|
26
|
+
this.name = "YurucommuRetentionError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run one bounded retention pass against the already-materialized runtime.
|
|
32
|
+
*
|
|
33
|
+
* This deliberately reuses the same race-safe cleanup paths as request/queue
|
|
34
|
+
* handling:
|
|
35
|
+
* - Story expiry uses the canonical object cascade and purges its media last.
|
|
36
|
+
* - Tombstones remain until every Delete delivery has drained.
|
|
37
|
+
* - Notification push performs bounded pusher/job retention, stale-job
|
|
38
|
+
* recovery, and enqueues due durable outbox rows when a queue is available.
|
|
39
|
+
*
|
|
40
|
+
* Steps are awaited sequentially because D1 is the shared authority. Any
|
|
41
|
+
* failure rejects with its exact step; callers must retry the cron invocation
|
|
42
|
+
* rather than silently acknowledging incomplete retention.
|
|
43
|
+
*/
|
|
44
|
+
export async function runYurucommuRetention(
|
|
45
|
+
env: Env,
|
|
46
|
+
): Promise<YurucommuRetentionResult> {
|
|
47
|
+
if (!env?.DB_INSTANCE) {
|
|
48
|
+
throw new TypeError("Yurucommu retention requires DB_INSTANCE");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const expiredStories = await retentionStep("expired_stories", () =>
|
|
52
|
+
cleanupExpiredStories(env.DB_INSTANCE, env.MEDIA),
|
|
53
|
+
);
|
|
54
|
+
const reapedTombstones = await retentionStep("drained_tombstones", () =>
|
|
55
|
+
reapDrainedTombstones(env.DB_INSTANCE),
|
|
56
|
+
);
|
|
57
|
+
const enqueuedNotificationPushJobs = await retentionStep(
|
|
58
|
+
"notification_push",
|
|
59
|
+
() => enqueuePendingNotificationPushJobs(env),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
expiredStories,
|
|
64
|
+
reapedTombstones,
|
|
65
|
+
enqueuedNotificationPushJobs,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function retentionStep<T>(
|
|
70
|
+
step: YurucommuRetentionStep,
|
|
71
|
+
run: () => Promise<T>,
|
|
72
|
+
): Promise<T> {
|
|
73
|
+
try {
|
|
74
|
+
return await run();
|
|
75
|
+
} catch (cause) {
|
|
76
|
+
throw new YurucommuRetentionError(step, cause);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -286,11 +286,11 @@ export async function cancelTombstoneDelete(
|
|
|
286
286
|
return deleteActivityIds.length;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
// Best-effort, opportunistic tombstone reaping on the read path.
|
|
290
|
-
// has
|
|
291
|
-
//
|
|
292
|
-
// isolate
|
|
293
|
-
//
|
|
289
|
+
// Best-effort, opportunistic tombstone reaping on the read path. The public
|
|
290
|
+
// Worker has a scheduled retention handler too; this remains as a fallback for
|
|
291
|
+
// self-hosted runtimes without cron. It is guarded so at most one pass runs per
|
|
292
|
+
// isolate. Tombstones are already excluded from every serving query, so a
|
|
293
|
+
// missed sweep only delays storage/key-material reclamation.
|
|
294
294
|
let tombstoneReapInFlight = false;
|
|
295
295
|
|
|
296
296
|
export function maybeReapDrainedTombstones(db: Database): void {
|
|
@@ -62,13 +62,11 @@ const stories = new Hono<{ Bindings: Env; Variables: Variables }>();
|
|
|
62
62
|
|
|
63
63
|
// Best-effort, opportunistic retention of expired stories.
|
|
64
64
|
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// below ensures at most one sweep runs at a time per isolate, so a burst of
|
|
71
|
-
// feed requests cannot kick off several concurrent full-table delete sweeps.
|
|
65
|
+
// The public Worker also exposes a scheduled retention handler. Keep this
|
|
66
|
+
// probabilistic read-path pass as a self-hosting fallback for runtimes that do
|
|
67
|
+
// not configure a cron trigger. Expired stories are already excluded from every
|
|
68
|
+
// read query, so a missed sweep affects storage only. The guard below ensures
|
|
69
|
+
// at most one fallback pass runs at a time per isolate.
|
|
72
70
|
let expiredStoryCleanupInFlight = false;
|
|
73
71
|
|
|
74
72
|
function maybeCleanupExpiredStories(
|