void 0.7.3 → 0.7.4
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/dist/{better-auth-shared-APuDaPqW.mjs → better-auth-shared-BrfZ78iY.mjs} +55 -5
- package/dist/cli/cli.mjs +8 -8
- package/dist/{db-ClNu7vYQ.mjs → db-RqxhOT2E.mjs} +1 -1
- package/dist/{deploy-BkjqNk9U.mjs → deploy-CRIz77mw.mjs} +243 -56
- package/dist/index.mjs +56 -22
- package/dist/{init-CPny6w9D.mjs → init-FMZ29UZW.mjs} +2 -2
- package/dist/{plugin-inference-oZ6Ybu2_.mjs → plugin-inference-CCtRkt9O.mjs} +41 -12
- package/dist/{prepare-DKkx-2Kt.mjs → prepare-PqAbhLte.mjs} +2 -2
- package/dist/{preset-DFvePt0l.mjs → preset-jKn1hQwP.mjs} +1 -1
- package/dist/runtime/better-auth-pg.mjs +1 -1
- package/dist/runtime/better-auth.mjs +1 -1
- package/dist/runtime/live-client.d.mts +34 -0
- package/dist/runtime/live-client.mjs +283 -0
- package/dist/runtime/live-server.d.mts +13 -0
- package/dist/runtime/live-server.mjs +417 -0
- package/dist/runtime/live.d.mts +107 -0
- package/dist/runtime/live.mjs +409 -0
- package/dist/runtime/migration-handler.mjs +34 -1
- package/dist/runtime/sse-client.d.mts +22 -0
- package/dist/runtime/sse-client.mjs +36 -0
- package/dist/runtime/sse.d.mts +35 -0
- package/dist/runtime/sse.mjs +172 -0
- package/dist/{scan-C6HMEIdW.mjs → scan-QtRTdh94.mjs} +1 -1
- package/package.json +27 -2
- package/skills/void/docs/guide/live.md +230 -0
- package/skills/void/docs/guide/sse.md +187 -0
- package/skills/void/docs/index.md +12 -15
- package/skills/void/docs/reference/api.md +4 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { eventStream } from "./sse.mjs";
|
|
2
|
+
import { __getLiveBindingName } from "./live.mjs";
|
|
3
|
+
//#region src/runtime/live-server.ts
|
|
4
|
+
const GENERATION_KEY = "connection:generation";
|
|
5
|
+
const TOPIC_PRUNE_ALARM_DELAY_MS = 6e4;
|
|
6
|
+
const textEncoder = new TextEncoder();
|
|
7
|
+
function createLiveDurableObject() {
|
|
8
|
+
class VoidLiveStreamDurableObject {
|
|
9
|
+
connection = null;
|
|
10
|
+
publishChain = Promise.resolve();
|
|
11
|
+
constructor(state, env) {
|
|
12
|
+
this.state = state;
|
|
13
|
+
this.env = env;
|
|
14
|
+
}
|
|
15
|
+
async fetch(request) {
|
|
16
|
+
const { pathname } = new URL(request.url);
|
|
17
|
+
if (request.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
|
|
18
|
+
if (pathname === "/connect") return this.connect(await request.json());
|
|
19
|
+
if (pathname === "/control") return this.control(await request.json());
|
|
20
|
+
if (pathname === "/deliver") return this.deliver(await request.json());
|
|
21
|
+
if (pathname === "/check") return this.check(await request.json());
|
|
22
|
+
if (pathname === "/register") return this.register(await request.json());
|
|
23
|
+
if (pathname === "/unregister") return this.unregister(await request.json());
|
|
24
|
+
if (pathname === "/publish") return this.enqueuePublish(request);
|
|
25
|
+
return new Response("Not Found", { status: 404 });
|
|
26
|
+
}
|
|
27
|
+
async alarm() {
|
|
28
|
+
await this.pruneAllSubscribers();
|
|
29
|
+
}
|
|
30
|
+
enqueuePublish(request) {
|
|
31
|
+
const run = this.publishChain.catch(() => {}).then(async () => {
|
|
32
|
+
return this.publish(await request.json());
|
|
33
|
+
});
|
|
34
|
+
this.publishChain = run.then(() => {}, () => {});
|
|
35
|
+
return run;
|
|
36
|
+
}
|
|
37
|
+
async connect(message) {
|
|
38
|
+
const previous = this.connection;
|
|
39
|
+
if (previous) {
|
|
40
|
+
const active = [...previous.subscriptions.values()].filter((sub) => sub.active);
|
|
41
|
+
for (const subscription of previous.subscriptions.values()) subscription.active = false;
|
|
42
|
+
await previous.stream.close().catch(() => {});
|
|
43
|
+
await Promise.all(active.map((sub) => this.unregisterTopic(message.streamId, message.connectionId, sub)));
|
|
44
|
+
}
|
|
45
|
+
const generation = (await this.state.storage.get(GENERATION_KEY) ?? 0) + 1;
|
|
46
|
+
await this.state.storage.put(GENERATION_KEY, generation);
|
|
47
|
+
return eventStream(async (stream) => {
|
|
48
|
+
const subscriptions = previous?.owner === message.owner ? previous.subscriptions : /* @__PURE__ */ new Map();
|
|
49
|
+
for (const subscription of subscriptions.values()) subscription.active = false;
|
|
50
|
+
this.connection = {
|
|
51
|
+
stream,
|
|
52
|
+
owner: message.owner,
|
|
53
|
+
generation,
|
|
54
|
+
subscriptions,
|
|
55
|
+
writeChain: Promise.resolve(),
|
|
56
|
+
queued: 0,
|
|
57
|
+
limits: message.limits
|
|
58
|
+
};
|
|
59
|
+
await stream.comment("connected");
|
|
60
|
+
await stream.closed;
|
|
61
|
+
if (this.connection?.generation === generation) {
|
|
62
|
+
const active = [...this.connection.subscriptions.values()].filter((sub) => sub.active);
|
|
63
|
+
this.connection = null;
|
|
64
|
+
await Promise.all(active.map((sub) => this.unregisterTopic(message.streamId, message.connectionId, sub)));
|
|
65
|
+
}
|
|
66
|
+
}, { keepAlive: {
|
|
67
|
+
intervalMs: 15e3,
|
|
68
|
+
comment: "keep-alive"
|
|
69
|
+
} });
|
|
70
|
+
}
|
|
71
|
+
async control(message) {
|
|
72
|
+
const connection = this.connection;
|
|
73
|
+
if (!connection) return controlBatchError("CONNECTION_NOT_FOUND", "live: connection is not open.", 404);
|
|
74
|
+
if (connection.owner !== message.owner) return controlBatchError("OWNER_MISMATCH", "live: connection owner mismatch.", 403);
|
|
75
|
+
const results = [];
|
|
76
|
+
for (const operation of message.operations) {
|
|
77
|
+
if (operation.kind === "unsubscribe") {
|
|
78
|
+
const existing = connection.subscriptions.get(operation.id);
|
|
79
|
+
if (existing) {
|
|
80
|
+
existing.active = false;
|
|
81
|
+
await this.unregisterTopic(message.streamId, message.connectionId, existing);
|
|
82
|
+
connection.subscriptions.delete(operation.id);
|
|
83
|
+
}
|
|
84
|
+
results.push({
|
|
85
|
+
ok: true,
|
|
86
|
+
kind: "unsubscribe",
|
|
87
|
+
id: operation.id
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!connection.subscriptions.has(operation.id) && activeSubscriptionCount(connection) >= message.limits.maxSubscriptionsPerConnection) {
|
|
92
|
+
results.push({
|
|
93
|
+
ok: false,
|
|
94
|
+
kind: "subscribe",
|
|
95
|
+
id: operation.id,
|
|
96
|
+
error: {
|
|
97
|
+
code: "SUBSCRIPTION_LIMIT",
|
|
98
|
+
message: "live: subscription limit reached."
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const topicKey = await topicDigest(message.streamId, operation.topic);
|
|
104
|
+
const existing = connection.subscriptions.get(operation.id);
|
|
105
|
+
const revision = (existing?.revision ?? 0) + 1;
|
|
106
|
+
const row = {
|
|
107
|
+
streamId: message.streamId,
|
|
108
|
+
topic: operation.topic,
|
|
109
|
+
topicKey,
|
|
110
|
+
connectionId: message.connectionId,
|
|
111
|
+
subscriptionId: operation.id,
|
|
112
|
+
generation: connection.generation,
|
|
113
|
+
revision,
|
|
114
|
+
lastSeenAt: Date.now()
|
|
115
|
+
};
|
|
116
|
+
const response = await this.topicStub(message.streamId, topicKey).fetch(internalUrl("/register"), {
|
|
117
|
+
method: "POST",
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
...row,
|
|
120
|
+
limits: message.limits
|
|
121
|
+
})
|
|
122
|
+
});
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
results.push({
|
|
125
|
+
ok: false,
|
|
126
|
+
kind: "subscribe",
|
|
127
|
+
id: operation.id,
|
|
128
|
+
error: response.status === 409 ? {
|
|
129
|
+
code: "TOPIC_FULL",
|
|
130
|
+
message: "live: topic subscription limit reached."
|
|
131
|
+
} : {
|
|
132
|
+
code: "INTERNAL_ERROR",
|
|
133
|
+
message: await response.text()
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (existing) {
|
|
139
|
+
existing.active = false;
|
|
140
|
+
await this.unregisterTopic(message.streamId, message.connectionId, existing).catch(() => {});
|
|
141
|
+
}
|
|
142
|
+
connection.subscriptions.set(operation.id, {
|
|
143
|
+
id: operation.id,
|
|
144
|
+
topic: operation.topic,
|
|
145
|
+
topicKey,
|
|
146
|
+
generation: connection.generation,
|
|
147
|
+
revision,
|
|
148
|
+
active: true
|
|
149
|
+
});
|
|
150
|
+
results.push({
|
|
151
|
+
ok: true,
|
|
152
|
+
kind: "subscribe",
|
|
153
|
+
id: operation.id,
|
|
154
|
+
topic: operation.topic
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return Response.json({
|
|
158
|
+
accepted: true,
|
|
159
|
+
connectionId: message.connectionId,
|
|
160
|
+
results
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async deliver(message) {
|
|
164
|
+
const connection = this.connection;
|
|
165
|
+
if (!connection) return new Response("connection missing", { status: 410 });
|
|
166
|
+
let delivered = 0;
|
|
167
|
+
const stale = [];
|
|
168
|
+
for (const [index, row] of message.subscriptions.entries()) {
|
|
169
|
+
if (isStaleSubscriber(row, connection)) {
|
|
170
|
+
stale.push(index);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (connection.queued >= connection.limits.maxQueuedEventsPerConnection) {
|
|
174
|
+
await connection.stream.close().catch(() => {});
|
|
175
|
+
this.connection = null;
|
|
176
|
+
return new Response("connection queue full", { status: 410 });
|
|
177
|
+
}
|
|
178
|
+
const event = {
|
|
179
|
+
subscriptionId: row.subscriptionId,
|
|
180
|
+
topic: message.topic,
|
|
181
|
+
...message.type !== void 0 && { type: message.type },
|
|
182
|
+
...message.eventId !== void 0 && { eventId: message.eventId },
|
|
183
|
+
data: message.data
|
|
184
|
+
};
|
|
185
|
+
if (encodedLiveEventSize(event) > connection.limits.maxEncodedEventSize) return payloadTooLargeResponse(connection.limits.maxEncodedEventSize);
|
|
186
|
+
connection.queued++;
|
|
187
|
+
const activeConnection = connection;
|
|
188
|
+
connection.writeChain = connection.writeChain.catch(() => {}).then(() => connection.stream.send({
|
|
189
|
+
event: "message",
|
|
190
|
+
data: event
|
|
191
|
+
})).catch(() => {
|
|
192
|
+
if (this.connection === activeConnection) this.connection = null;
|
|
193
|
+
connection.stream.close().catch(() => {});
|
|
194
|
+
}).finally(() => {
|
|
195
|
+
connection.queued--;
|
|
196
|
+
});
|
|
197
|
+
delivered++;
|
|
198
|
+
}
|
|
199
|
+
return Response.json({
|
|
200
|
+
delivered,
|
|
201
|
+
stale
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
check(message) {
|
|
205
|
+
const connection = this.connection;
|
|
206
|
+
if (!connection) return new Response("connection missing", { status: 410 });
|
|
207
|
+
const stale = [];
|
|
208
|
+
for (const [index, row] of message.subscriptions.entries()) if (isStaleSubscriber(row, connection)) stale.push(index);
|
|
209
|
+
return Response.json({ stale });
|
|
210
|
+
}
|
|
211
|
+
async register(row) {
|
|
212
|
+
const prefix = subscriberPrefix(row.streamId, row.topicKey);
|
|
213
|
+
const current = await this.state.storage.list({ prefix });
|
|
214
|
+
const deletedKeys = /* @__PURE__ */ new Set();
|
|
215
|
+
for (const [key, value] of current) {
|
|
216
|
+
if (value.connectionId === row.connectionId && value.generation < row.generation) {
|
|
217
|
+
deletedKeys.add(key);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (value.connectionId === row.connectionId && value.subscriptionId === row.subscriptionId) deletedKeys.add(key);
|
|
221
|
+
}
|
|
222
|
+
let activeRows = [...current].filter(([key]) => !deletedKeys.has(key));
|
|
223
|
+
if (activeRows.length >= row.limits.maxSubscriptionsPerTopic) {
|
|
224
|
+
for (const key of await this.findStaleSubscriberKeys(activeRows, row.limits)) deletedKeys.add(key);
|
|
225
|
+
activeRows = activeRows.filter(([key]) => !deletedKeys.has(key));
|
|
226
|
+
}
|
|
227
|
+
if (activeRows.length >= row.limits.maxSubscriptionsPerTopic) return new Response("topic full", { status: 409 });
|
|
228
|
+
if (deletedKeys.size > 0) await this.state.storage.delete([...deletedKeys]);
|
|
229
|
+
await this.state.storage.put(subscriberKey(row), withoutLimits(row));
|
|
230
|
+
await this.schedulePruneAlarm();
|
|
231
|
+
return Response.json({ ok: true });
|
|
232
|
+
}
|
|
233
|
+
async unregister(row) {
|
|
234
|
+
if (!row.streamId || !row.topicKey) return Response.json({ ok: true });
|
|
235
|
+
const key = row.connectionId && row.subscriptionId && row.generation != null && row.revision != null ? subscriberKey(row) : void 0;
|
|
236
|
+
if (key) await this.state.storage.delete(key);
|
|
237
|
+
return Response.json({ ok: true });
|
|
238
|
+
}
|
|
239
|
+
async publish(message) {
|
|
240
|
+
const rows = await this.state.storage.list({ prefix: subscriberPrefix(message.streamId, message.topicKey) });
|
|
241
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
242
|
+
for (const [key, row] of rows) {
|
|
243
|
+
const list = grouped.get(row.connectionId) ?? [];
|
|
244
|
+
list.push({
|
|
245
|
+
key,
|
|
246
|
+
row
|
|
247
|
+
});
|
|
248
|
+
grouped.set(row.connectionId, list);
|
|
249
|
+
}
|
|
250
|
+
for (const { row } of [...grouped.values()].flat()) if (encodedLiveEventSize({
|
|
251
|
+
subscriptionId: row.subscriptionId,
|
|
252
|
+
topic: message.topic,
|
|
253
|
+
...message.type !== void 0 && { type: message.type },
|
|
254
|
+
...message.eventId !== void 0 && { eventId: message.eventId },
|
|
255
|
+
data: message.data
|
|
256
|
+
}) > message.limits.maxEncodedEventSize) return payloadTooLargeResponse(message.limits.maxEncodedEventSize);
|
|
257
|
+
for (const [connectionId, items] of grouped) {
|
|
258
|
+
const response = await fetchWithTimeout(this.connectionStub(message.streamId, connectionId), internalUrl("/deliver"), {
|
|
259
|
+
method: "POST",
|
|
260
|
+
body: JSON.stringify({
|
|
261
|
+
streamId: message.streamId,
|
|
262
|
+
topic: message.topic,
|
|
263
|
+
data: message.data,
|
|
264
|
+
type: message.type,
|
|
265
|
+
eventId: message.eventId,
|
|
266
|
+
subscriptions: items.map((item) => item.row)
|
|
267
|
+
})
|
|
268
|
+
}, message.limits.deliveryAttemptTimeoutMs);
|
|
269
|
+
if (!response || response.status === 410 || response.status === 404) {
|
|
270
|
+
await this.state.storage.delete(items.map((item) => item.key));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (response.status === 413) return response;
|
|
274
|
+
if (response.ok) {
|
|
275
|
+
const staleKeys = (await response.json().catch(() => null))?.stale.map((index) => items[index]?.key).filter((key) => typeof key === "string");
|
|
276
|
+
if (staleKeys?.length) await this.state.storage.delete(staleKeys);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return Response.json({ ok: true });
|
|
280
|
+
}
|
|
281
|
+
async pruneAllSubscribers() {
|
|
282
|
+
const rows = await this.state.storage.list({ prefix: "sub:" });
|
|
283
|
+
const staleKeys = await this.findStaleSubscriberKeys([...rows], { deliveryAttemptTimeoutMs: 1500 });
|
|
284
|
+
if (staleKeys.length > 0) await this.state.storage.delete(staleKeys);
|
|
285
|
+
if (rows.size > staleKeys.length) await this.schedulePruneAlarm();
|
|
286
|
+
}
|
|
287
|
+
async findStaleSubscriberKeys(entries, limits) {
|
|
288
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
289
|
+
for (const [key, row] of entries) {
|
|
290
|
+
const list = grouped.get(row.connectionId) ?? [];
|
|
291
|
+
list.push({
|
|
292
|
+
key,
|
|
293
|
+
row
|
|
294
|
+
});
|
|
295
|
+
grouped.set(row.connectionId, list);
|
|
296
|
+
}
|
|
297
|
+
const staleKeys = [];
|
|
298
|
+
for (const [connectionId, items] of grouped) {
|
|
299
|
+
const response = await fetchWithTimeout(this.connectionStub(items[0].row.streamId, connectionId), internalUrl("/check"), {
|
|
300
|
+
method: "POST",
|
|
301
|
+
body: JSON.stringify({ subscriptions: items.map((item) => item.row) })
|
|
302
|
+
}, limits.deliveryAttemptTimeoutMs);
|
|
303
|
+
if (!response || response.status === 410 || response.status === 404) {
|
|
304
|
+
staleKeys.push(...items.map((item) => item.key));
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (response.ok) {
|
|
308
|
+
const result = await response.json().catch(() => null);
|
|
309
|
+
for (const index of result?.stale ?? []) {
|
|
310
|
+
const key = items[index]?.key;
|
|
311
|
+
if (key) staleKeys.push(key);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return staleKeys;
|
|
316
|
+
}
|
|
317
|
+
async schedulePruneAlarm() {
|
|
318
|
+
await this.state.storage.setAlarm?.(Date.now() + TOPIC_PRUNE_ALARM_DELAY_MS);
|
|
319
|
+
}
|
|
320
|
+
topicStub(streamId, topicKey) {
|
|
321
|
+
const binding = this.env[__getLiveBindingName()];
|
|
322
|
+
return binding.get(binding.idFromName(topicInstanceName(streamId, topicKey)));
|
|
323
|
+
}
|
|
324
|
+
connectionStub(streamId, connectionId) {
|
|
325
|
+
const binding = this.env[__getLiveBindingName()];
|
|
326
|
+
return binding.get(binding.idFromName(connectionInstanceName(streamId, connectionId)));
|
|
327
|
+
}
|
|
328
|
+
unregisterTopic(streamId, connectionId, subscription) {
|
|
329
|
+
return this.topicStub(streamId, subscription.topicKey).fetch(internalUrl("/unregister"), {
|
|
330
|
+
method: "POST",
|
|
331
|
+
body: JSON.stringify({
|
|
332
|
+
streamId,
|
|
333
|
+
topic: subscription.topic,
|
|
334
|
+
topicKey: subscription.topicKey,
|
|
335
|
+
connectionId,
|
|
336
|
+
subscriptionId: subscription.id,
|
|
337
|
+
revision: subscription.revision,
|
|
338
|
+
generation: subscription.generation
|
|
339
|
+
})
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return VoidLiveStreamDurableObject;
|
|
344
|
+
}
|
|
345
|
+
function controlBatchError(code, message, status) {
|
|
346
|
+
return Response.json({
|
|
347
|
+
accepted: false,
|
|
348
|
+
error: {
|
|
349
|
+
code,
|
|
350
|
+
message
|
|
351
|
+
}
|
|
352
|
+
}, { status });
|
|
353
|
+
}
|
|
354
|
+
function activeSubscriptionCount(connection) {
|
|
355
|
+
let count = 0;
|
|
356
|
+
for (const subscription of connection.subscriptions.values()) if (subscription.active) count++;
|
|
357
|
+
return count;
|
|
358
|
+
}
|
|
359
|
+
function isStaleSubscriber(row, connection) {
|
|
360
|
+
if (row.generation !== connection.generation) return true;
|
|
361
|
+
const subscription = connection.subscriptions.get(row.subscriptionId);
|
|
362
|
+
return !subscription || !subscription.active || subscription.revision !== row.revision;
|
|
363
|
+
}
|
|
364
|
+
function encodedLiveEventSize(event) {
|
|
365
|
+
return textEncoder.encode(JSON.stringify(event)).byteLength;
|
|
366
|
+
}
|
|
367
|
+
function payloadTooLargeResponse(maxEncodedEventSize) {
|
|
368
|
+
return Response.json({
|
|
369
|
+
accepted: false,
|
|
370
|
+
error: {
|
|
371
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
372
|
+
message: `live: encoded event exceeds ${maxEncodedEventSize} bytes.`
|
|
373
|
+
}
|
|
374
|
+
}, { status: 413 });
|
|
375
|
+
}
|
|
376
|
+
async function fetchWithTimeout(stub, input, init, timeoutMs) {
|
|
377
|
+
const fetchPromise = stub.fetch(input, init);
|
|
378
|
+
fetchPromise.catch(() => {});
|
|
379
|
+
let timeout;
|
|
380
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
381
|
+
timeout = setTimeout(() => resolve(null), Math.max(1, timeoutMs));
|
|
382
|
+
});
|
|
383
|
+
try {
|
|
384
|
+
return await Promise.race([fetchPromise, timeoutPromise]);
|
|
385
|
+
} catch {
|
|
386
|
+
return null;
|
|
387
|
+
} finally {
|
|
388
|
+
if (timeout) clearTimeout(timeout);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function withoutLimits(row) {
|
|
392
|
+
const { limits: _limits, ...rest } = row;
|
|
393
|
+
return rest;
|
|
394
|
+
}
|
|
395
|
+
function subscriberPrefix(streamId, topicKey) {
|
|
396
|
+
return `sub:${streamId}:${topicKey}:`;
|
|
397
|
+
}
|
|
398
|
+
function subscriberKey(row) {
|
|
399
|
+
return `${subscriberPrefix(row.streamId, row.topicKey)}${row.connectionId}:${row.subscriptionId}:${row.generation}:${row.revision}`;
|
|
400
|
+
}
|
|
401
|
+
async function topicDigest(streamId, topic) {
|
|
402
|
+
const bytes = new TextEncoder().encode(`${streamId}\u0000${topic}`);
|
|
403
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
404
|
+
const binary = String.fromCharCode(...new Uint8Array(digest));
|
|
405
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
406
|
+
}
|
|
407
|
+
function connectionInstanceName(streamId, connectionId) {
|
|
408
|
+
return `live:${streamId}:connection:${connectionId}`;
|
|
409
|
+
}
|
|
410
|
+
function topicInstanceName(streamId, topicKey) {
|
|
411
|
+
return `live:${streamId}:topic:${topicKey}`;
|
|
412
|
+
}
|
|
413
|
+
function internalUrl(path) {
|
|
414
|
+
return `https://void.live${path}`;
|
|
415
|
+
}
|
|
416
|
+
//#endregion
|
|
417
|
+
export { createLiveDurableObject };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { r as AuthUser } from "../auth-DrfOTMmr.mjs";
|
|
3
|
+
import { i as CloudEnv, n as CloudContext } from "../handler-dKQWyF-G.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/runtime/live.d.ts
|
|
6
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
7
|
+
type LiveControlRequest = {
|
|
8
|
+
connectionId: string;
|
|
9
|
+
operations: Array<LiveControlOperation>;
|
|
10
|
+
};
|
|
11
|
+
type LiveControlOperation = {
|
|
12
|
+
kind: "subscribe";
|
|
13
|
+
id: string;
|
|
14
|
+
topic: string;
|
|
15
|
+
lastEventId?: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "unsubscribe";
|
|
18
|
+
id: string;
|
|
19
|
+
};
|
|
20
|
+
type LiveControlErrorCode = "BAD_REQUEST" | "CONNECTION_CLOSED" | "CONNECTION_NOT_FOUND" | "FORBIDDEN" | "INVALID_SUBSCRIPTION_ID" | "INVALID_TOPIC" | "OWNER_MISMATCH" | "SUBSCRIPTION_LIMIT" | "TOPIC_FULL" | "PAYLOAD_TOO_LARGE" | "INTERNAL_ERROR";
|
|
21
|
+
type LiveControlError = {
|
|
22
|
+
code: LiveControlErrorCode;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
type LiveControlOperationResult = {
|
|
26
|
+
ok: true;
|
|
27
|
+
kind: "subscribe" | "unsubscribe";
|
|
28
|
+
id: string;
|
|
29
|
+
topic?: string;
|
|
30
|
+
} | {
|
|
31
|
+
ok: false;
|
|
32
|
+
kind: "subscribe" | "unsubscribe";
|
|
33
|
+
id: string;
|
|
34
|
+
error: LiveControlError;
|
|
35
|
+
};
|
|
36
|
+
type LiveControlResponse = {
|
|
37
|
+
accepted: true;
|
|
38
|
+
connectionId: string;
|
|
39
|
+
results: Array<LiveControlOperationResult>;
|
|
40
|
+
} | {
|
|
41
|
+
accepted: false;
|
|
42
|
+
error: LiveControlError;
|
|
43
|
+
};
|
|
44
|
+
type LiveClientEvent<Data = unknown> = {
|
|
45
|
+
subscriptionId: string;
|
|
46
|
+
topic: string;
|
|
47
|
+
type?: string;
|
|
48
|
+
eventId?: string;
|
|
49
|
+
data: Data;
|
|
50
|
+
};
|
|
51
|
+
type LivePublishOptions = {
|
|
52
|
+
type?: string;
|
|
53
|
+
eventId?: string;
|
|
54
|
+
};
|
|
55
|
+
type LiveConnectOptions = {
|
|
56
|
+
owner?: string | null;
|
|
57
|
+
user?: unknown;
|
|
58
|
+
};
|
|
59
|
+
type LiveControlOptions = LiveConnectOptions;
|
|
60
|
+
type LiveHookContext = {
|
|
61
|
+
request: Request;
|
|
62
|
+
env: CloudEnv["Bindings"];
|
|
63
|
+
user: AuthUser | unknown | null;
|
|
64
|
+
owner: string | null;
|
|
65
|
+
};
|
|
66
|
+
type LiveConnectHookContext = LiveHookContext & {
|
|
67
|
+
connection: {
|
|
68
|
+
id: string;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
type LiveSubscribeHookContext = LiveHookContext & {
|
|
72
|
+
connection: {
|
|
73
|
+
id: string;
|
|
74
|
+
};
|
|
75
|
+
subscription: {
|
|
76
|
+
id: string;
|
|
77
|
+
};
|
|
78
|
+
topic: string;
|
|
79
|
+
lastEventId?: string;
|
|
80
|
+
};
|
|
81
|
+
type LiveStreamOptions = {
|
|
82
|
+
id: string;
|
|
83
|
+
allowAnonymousControl?: boolean;
|
|
84
|
+
identifyConnection?: (ctx: LiveHookContext) => MaybePromise<string | null | undefined>;
|
|
85
|
+
onConnect?: (ctx: LiveConnectHookContext) => MaybePromise<Response | void>;
|
|
86
|
+
onSubscribe?: (ctx: LiveSubscribeHookContext) => MaybePromise<Response | void>;
|
|
87
|
+
limits?: Partial<LiveLimits>;
|
|
88
|
+
};
|
|
89
|
+
type LiveLimits = {
|
|
90
|
+
maxSubscriptionsPerConnection: number;
|
|
91
|
+
maxSubscriptionsPerTopic: number;
|
|
92
|
+
maxOperationsPerControlRequest: number;
|
|
93
|
+
maxQueuedEventsPerConnection: number;
|
|
94
|
+
maxEncodedEventSize: number;
|
|
95
|
+
deliveryAttemptTimeoutMs: number;
|
|
96
|
+
};
|
|
97
|
+
type LiveStream = {
|
|
98
|
+
connect(ctx: CloudContext, options?: LiveConnectOptions): Promise<Response>;
|
|
99
|
+
control(ctx: CloudContext, options?: LiveControlOptions): Promise<Response>;
|
|
100
|
+
publish(topic: string, data: unknown, options?: LivePublishOptions): Promise<void>;
|
|
101
|
+
withEnv(env: unknown): Pick<LiveStream, "publish">;
|
|
102
|
+
};
|
|
103
|
+
declare function __setLiveBindingName(name: string): void;
|
|
104
|
+
declare function __getLiveBindingName(): string;
|
|
105
|
+
declare function defineLiveStream(options: LiveStreamOptions): LiveStream;
|
|
106
|
+
//#endregion
|
|
107
|
+
export { LiveClientEvent, LiveConnectHookContext, LiveConnectOptions, LiveControlError, LiveControlErrorCode, LiveControlOperation, LiveControlOperationResult, LiveControlOptions, LiveControlRequest, LiveControlResponse, LiveHookContext, LiveLimits, LivePublishOptions, LiveStream, LiveStreamOptions, LiveSubscribeHookContext, __getLiveBindingName, __setLiveBindingName, defineLiveStream };
|