@takosjp/yurucommu-core 3.3.0 → 3.4.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.
- package/migrations/0022_inbound_dispatch_claims.sql +17 -0
- package/package.json +3 -2
- package/packages/api/package.json +1 -1
- package/packages/api/src/index.ts +1 -0
- package/packages/api/src/lib/api/notifications.ts +1 -0
- package/packages/api/src/lib/api/posts.ts +1 -0
- package/packages/api/src/lib/rtc-client.ts +1 -3
- package/packages/api/src/types/call.ts +2 -10
- package/packages/api/src/types/index.ts +3 -0
- package/packages/api/src/types/realtime.ts +139 -0
- package/src/backend/index.ts +54 -12
- package/src/backend/lib/delivery/queue-batching.ts +53 -36
- package/src/backend/lib/delivery/queue-delivery.ts +3 -3
- package/src/backend/lib/delivery/queue.ts +17 -9
- package/src/backend/lib/delivery/types.ts +12 -0
- package/src/backend/lib/notification-push.ts +2 -2
- package/src/backend/lib/oauth-providers.ts +9 -0
- package/src/backend/lib/strip-image-metadata.ts +50 -30
- package/src/backend/lib/unread-counts.ts +63 -2
- package/src/backend/middleware/bearer-auth.ts +24 -9
- package/src/backend/public.ts +25 -1
- package/src/backend/routes/activitypub/handlers/actor-inbox-handlers.ts +4 -3
- package/src/backend/routes/activitypub/handlers/inbox-content-handlers.ts +204 -5
- package/src/backend/routes/activitypub/handlers/inbox-follow-handlers.ts +78 -53
- package/src/backend/routes/activitypub/handlers/inbox-interaction-handlers.ts +66 -2
- package/src/backend/routes/activitypub/handlers/inbox-shared-helpers.ts +35 -12
- package/src/backend/routes/activitypub/inbox-addressing.ts +236 -0
- package/src/backend/routes/activitypub/inbox-types.ts +8 -0
- package/src/backend/routes/activitypub/inbox.ts +410 -205
- package/src/backend/routes/activitypub/outbox.ts +0 -0
- package/src/backend/routes/auth.ts +2 -1
- package/src/backend/routes/communities/messages.ts +53 -17
- package/src/backend/routes/dm/messages.ts +47 -7
- package/src/backend/routes/dm/read-archive.ts +27 -0
- package/src/backend/routes/dm/typing.ts +16 -0
- package/src/backend/routes/notifications.ts +25 -0
- package/src/backend/routes/posts/post-helpers.ts +42 -23
- package/src/backend/routes/realtime/index.ts +67 -0
- package/src/backend/routes/rtc/index.ts +5 -1
- package/src/backend/runtime/call-hub-core.ts +13 -3
- package/src/backend/runtime/cloudflare.ts +63 -2
- package/src/backend/runtime/managed-relational.ts +197 -0
- package/src/backend/runtime/managed-runtime.ts +631 -0
- package/src/backend/runtime/queue.ts +40 -0
- package/src/backend/runtime/realtime-hub.ts +257 -0
- package/src/backend/runtime/realtime-stream-do.ts +323 -0
- package/src/backend/server.ts +15 -18
- package/src/backend/types.ts +13 -2
- package/src/db/d1-write.ts +270 -0
- package/src/db/index.ts +17 -0
- package/src/db/schema/federation.ts +19 -0
- package/src/db/schema/index.ts +1 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TAKOSUMI_MANAGED_RELATIONAL_RUNTIME_CONTRACT,
|
|
3
|
+
managedRelationalBatchGatewayRequest,
|
|
4
|
+
managedRelationalConnection,
|
|
5
|
+
parseManagedRelationalBatchResponse,
|
|
6
|
+
type ManagedRelationalMethod,
|
|
7
|
+
type ManagedRelationalParameter,
|
|
8
|
+
} from "@takosjp/takosumi-contract/managed-relational-runtime";
|
|
9
|
+
import { parseManagedRuntimeConnectionMaterialization } from "@takosjp/takosumi-contract/managed-runtime-connections";
|
|
10
|
+
import {
|
|
11
|
+
drizzle as drizzleProxy,
|
|
12
|
+
type AsyncBatchRemoteCallback,
|
|
13
|
+
type AsyncRemoteCallback,
|
|
14
|
+
} from "drizzle-orm/sqlite-proxy";
|
|
15
|
+
|
|
16
|
+
import * as schema from "../../db/schema.ts";
|
|
17
|
+
import { ManagedRuntimeGatewayError } from "./managed-runtime.ts";
|
|
18
|
+
import type { ManagedRuntimeGateway } from "./managed-runtime.ts";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_MAX_RELATIONAL_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
21
|
+
|
|
22
|
+
export interface ManagedRelationalDatabaseOptions {
|
|
23
|
+
readonly materialization: unknown;
|
|
24
|
+
readonly gateway: ManagedRuntimeGateway;
|
|
25
|
+
readonly alias: string;
|
|
26
|
+
readonly idempotencyKey?: () => string;
|
|
27
|
+
readonly maxResponseBytes?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Provider-neutral Drizzle adapter for a host-issued RelationalDatabase.
|
|
32
|
+
*
|
|
33
|
+
* Every callback is one bounded prepared statement. Drizzle `batch()` maps to
|
|
34
|
+
* one ordered-atomic host call; transaction-control and migration SQL are
|
|
35
|
+
* deliberately unavailable on this request path.
|
|
36
|
+
*/
|
|
37
|
+
export function createManagedRelationalDatabase(
|
|
38
|
+
options: ManagedRelationalDatabaseOptions,
|
|
39
|
+
) {
|
|
40
|
+
const materialization = parseManagedRuntimeConnectionMaterialization(
|
|
41
|
+
options.materialization,
|
|
42
|
+
);
|
|
43
|
+
const connection = managedRelationalConnection(
|
|
44
|
+
materialization,
|
|
45
|
+
options.alias,
|
|
46
|
+
);
|
|
47
|
+
const idempotencyKey =
|
|
48
|
+
options.idempotencyKey ??
|
|
49
|
+
(() => `yurucommu.relational:${crypto.randomUUID()}`);
|
|
50
|
+
const maxResponseBytes =
|
|
51
|
+
options.maxResponseBytes ?? DEFAULT_MAX_RELATIONAL_RESPONSE_BYTES;
|
|
52
|
+
if (
|
|
53
|
+
!Number.isSafeInteger(maxResponseBytes) ||
|
|
54
|
+
maxResponseBytes < 1 ||
|
|
55
|
+
maxResponseBytes > DEFAULT_MAX_RELATIONAL_RESPONSE_BYTES
|
|
56
|
+
) {
|
|
57
|
+
throw new TypeError("managed_relational_response_limit_invalid");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const execute = async (
|
|
61
|
+
statements: readonly {
|
|
62
|
+
readonly sql: string;
|
|
63
|
+
readonly params: readonly unknown[];
|
|
64
|
+
readonly method: ManagedRelationalMethod;
|
|
65
|
+
}[],
|
|
66
|
+
) => {
|
|
67
|
+
const canonical = statements.map((statement) => ({
|
|
68
|
+
sql: statement.sql,
|
|
69
|
+
params: statement.params.map(relationalParameter),
|
|
70
|
+
method: statement.method,
|
|
71
|
+
}));
|
|
72
|
+
const request = managedRelationalBatchGatewayRequest(connection.authority, {
|
|
73
|
+
statements: canonical,
|
|
74
|
+
idempotencyKey: idempotencyKey(),
|
|
75
|
+
});
|
|
76
|
+
const response = await boundedResponse(
|
|
77
|
+
await options.gateway.fetch(request),
|
|
78
|
+
maxResponseBytes,
|
|
79
|
+
);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const value = (await response.json().catch(() => undefined)) as unknown;
|
|
82
|
+
const code =
|
|
83
|
+
isRecord(value) && typeof value.error === "string"
|
|
84
|
+
? value.error
|
|
85
|
+
: "managed_relational_request_failed";
|
|
86
|
+
throw new ManagedRuntimeGatewayError(
|
|
87
|
+
code,
|
|
88
|
+
response.status,
|
|
89
|
+
response.status === 429 || response.status >= 500,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const value = parseManagedRelationalBatchResponse(
|
|
93
|
+
await response.json(),
|
|
94
|
+
canonical.length,
|
|
95
|
+
);
|
|
96
|
+
return value.results;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const callback: AsyncRemoteCallback = async (sql, params, method) => {
|
|
100
|
+
const [result] = await execute([{ sql, params, method }]);
|
|
101
|
+
if (!result) throw new Error("managed_relational_result_missing");
|
|
102
|
+
return drizzleResult(result, method);
|
|
103
|
+
};
|
|
104
|
+
const batchCallback: AsyncBatchRemoteCallback = async (batch) => {
|
|
105
|
+
const results = await execute(batch);
|
|
106
|
+
return results.map((result, index) =>
|
|
107
|
+
drizzleResult(result, batch[index]!.method),
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return drizzleProxy(callback, batchCallback, { schema });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function drizzleResult(
|
|
115
|
+
result: Awaited<
|
|
116
|
+
ReturnType<typeof parseManagedRelationalBatchResponse>
|
|
117
|
+
>["results"][number],
|
|
118
|
+
method: ManagedRelationalMethod,
|
|
119
|
+
) {
|
|
120
|
+
return {
|
|
121
|
+
// sqlite-proxy exposes mutable `any[]` at this boundary even though the
|
|
122
|
+
// public runtime contract is intentionally immutable. Copy here so the
|
|
123
|
+
// provider-neutral contract never leaks a mutable result reference.
|
|
124
|
+
rows:
|
|
125
|
+
method === "get"
|
|
126
|
+
? [...(result.rows[0] ?? [])]
|
|
127
|
+
: result.rows.map((row) => [...row]),
|
|
128
|
+
meta: result.meta,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function relationalParameter(value: unknown): ManagedRelationalParameter {
|
|
133
|
+
if (value === null || typeof value === "string") return value;
|
|
134
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
135
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
136
|
+
throw new TypeError("managed_relational_parameter_unsupported");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function boundedResponse(
|
|
140
|
+
response: Response,
|
|
141
|
+
maxBytes: number,
|
|
142
|
+
): Promise<Response> {
|
|
143
|
+
const declaredLength = response.headers.get("content-length");
|
|
144
|
+
if (
|
|
145
|
+
declaredLength !== null &&
|
|
146
|
+
(!/^\d+$/u.test(declaredLength) || Number(declaredLength) > maxBytes)
|
|
147
|
+
) {
|
|
148
|
+
throw new ManagedRuntimeGatewayError(
|
|
149
|
+
"managed_relational_response_too_large",
|
|
150
|
+
502,
|
|
151
|
+
false,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const reader = response.body?.getReader();
|
|
155
|
+
if (!reader) {
|
|
156
|
+
return new Response(null, {
|
|
157
|
+
status: response.status,
|
|
158
|
+
statusText: response.statusText,
|
|
159
|
+
headers: response.headers,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const chunks: Uint8Array[] = [];
|
|
163
|
+
let size = 0;
|
|
164
|
+
try {
|
|
165
|
+
while (true) {
|
|
166
|
+
const { done, value } = await reader.read();
|
|
167
|
+
if (done) break;
|
|
168
|
+
size += value.byteLength;
|
|
169
|
+
if (size > maxBytes) {
|
|
170
|
+
await reader.cancel("managed_relational_response_too_large");
|
|
171
|
+
throw new ManagedRuntimeGatewayError(
|
|
172
|
+
"managed_relational_response_too_large",
|
|
173
|
+
502,
|
|
174
|
+
false,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
chunks.push(value);
|
|
178
|
+
}
|
|
179
|
+
} finally {
|
|
180
|
+
reader.releaseLock();
|
|
181
|
+
}
|
|
182
|
+
const body = new Uint8Array(size);
|
|
183
|
+
let offset = 0;
|
|
184
|
+
for (const chunk of chunks) {
|
|
185
|
+
body.set(chunk, offset);
|
|
186
|
+
offset += chunk.byteLength;
|
|
187
|
+
}
|
|
188
|
+
return new Response(body, {
|
|
189
|
+
status: response.status,
|
|
190
|
+
statusText: response.statusText,
|
|
191
|
+
headers: response.headers,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
196
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
197
|
+
}
|