reflectdb 0.1.2 → 0.2.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.md +483 -17
- package/dist/cjs/client/index.cjs +0 -1
- package/dist/cjs/client/storage/indexeddb.cjs +0 -1
- package/dist/cjs/core/index.cjs +0 -1
- package/dist/cjs/htmx/index.cjs +1720 -0
- package/dist/cjs/htmx/index.d.cts +760 -0
- package/dist/cjs/react/index.cjs +0 -1
- package/dist/cjs/server/drizzle.cjs +15 -4
- package/dist/cjs/server/ephemeral/index.cjs +0 -1
- package/dist/cjs/server/ephemeral/redis.cjs +0 -1
- package/dist/cjs/server/index.cjs +34 -13
- package/dist/cjs/server/index.d.cts +14 -0
- package/dist/cjs/server/storage/object/index.cjs +2284 -0
- package/dist/cjs/server/storage/object/index.d.cts +578 -0
- package/dist/cjs/svelte/index.cjs +0 -1
- package/dist/cjs/transport/bun-ws.cjs +0 -1
- package/dist/cjs/transport/polling.cjs +0 -1
- package/dist/cjs/transport/sse.cjs +52 -2
- package/dist/cjs/transport/sse.d.cts +34 -0
- package/dist/cjs/transport/ws.cjs +0 -1
- package/dist/cjs/vanilla/index.cjs +0 -1
- package/dist/client/index.js +1 -1
- package/dist/client/storage/indexeddb.js +1 -1
- package/dist/core/index.js +1 -1
- package/dist/htmx/index.d.ts +760 -0
- package/dist/htmx/index.js +297 -0
- package/dist/react/index.js +1 -1
- package/dist/server/drizzle.js +3 -2
- package/dist/server/ephemeral/index.js +1 -1
- package/dist/server/ephemeral/redis.js +1 -1
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.js +19 -6
- package/dist/server/storage/object/index.d.ts +578 -0
- package/dist/server/storage/object/index.js +2232 -0
- package/dist/shared/{esm-z1xse19c.js → esm-5ahpq25j.js} +4 -4
- package/dist/shared/esm-dcs8qa5n.js +263 -0
- package/dist/shared/esm-f11s9zpb.js +15 -0
- package/dist/shared/esm-g5h4a88j.js +9 -0
- package/dist/shared/{esm-ck88h30s.js → esm-xtkhzxg2.js} +1 -1
- package/dist/svelte/index.js +1 -1
- package/dist/transport/bun-ws.js +1 -1
- package/dist/transport/polling.js +1 -1
- package/dist/transport/sse.d.ts +34 -0
- package/dist/transport/sse.js +53 -2
- package/dist/transport/ws.js +1 -1
- package/dist/vanilla/index.js +5 -261
- package/package.json +26 -3
- package/dist/shared/esm-k7kedp3y.js +0 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
nodeRequire
|
|
3
|
+
} from "./esm-f11s9zpb.js";
|
|
3
4
|
|
|
4
5
|
// src/server/factory.ts
|
|
5
6
|
function getByDottedPath(root, path) {
|
|
@@ -226,12 +227,11 @@ function defineTable(adapter, opts = {}) {
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
// src/server/drizzle.ts
|
|
229
|
-
var requireFn = createRequire(import.meta.url);
|
|
230
230
|
var cachedDrizzle;
|
|
231
231
|
function loadDrizzleSync() {
|
|
232
232
|
if (cachedDrizzle === undefined) {
|
|
233
233
|
try {
|
|
234
|
-
const mod =
|
|
234
|
+
const mod = nodeRequire("drizzle-orm");
|
|
235
235
|
cachedDrizzle = { eq: mod.eq, sql: mod.sql, getTableName: mod.getTableName };
|
|
236
236
|
} catch {
|
|
237
237
|
cachedDrizzle = null;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SyncClient,
|
|
3
|
+
pushSafely
|
|
4
|
+
} from "./esm-8qbr4y0d.js";
|
|
5
|
+
|
|
6
|
+
// src/vanilla/sync.ts
|
|
7
|
+
function createBrowserWsTransport(url) {
|
|
8
|
+
let ws = null;
|
|
9
|
+
let handler = null;
|
|
10
|
+
let intentionalClose = false;
|
|
11
|
+
const sendQueue = [];
|
|
12
|
+
function ensureConnected() {
|
|
13
|
+
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {
|
|
14
|
+
return ws;
|
|
15
|
+
}
|
|
16
|
+
const socket = new WebSocket(url);
|
|
17
|
+
socket.onopen = () => {
|
|
18
|
+
for (const msg of sendQueue) {
|
|
19
|
+
socket.send(JSON.stringify(msg));
|
|
20
|
+
}
|
|
21
|
+
sendQueue.length = 0;
|
|
22
|
+
};
|
|
23
|
+
socket.onmessage = (event) => {
|
|
24
|
+
handler?.(JSON.parse(event.data));
|
|
25
|
+
};
|
|
26
|
+
socket.onclose = () => {
|
|
27
|
+
ws = null;
|
|
28
|
+
if (!intentionalClose) {
|
|
29
|
+
handler?.({ type: "disconnect", reason: "transport_closed" });
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
ws = socket;
|
|
33
|
+
return socket;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
async send(message) {
|
|
37
|
+
const socket = ensureConnected();
|
|
38
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
39
|
+
socket.send(JSON.stringify(message));
|
|
40
|
+
} else {
|
|
41
|
+
sendQueue.push(message);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
subscribe(h) {
|
|
45
|
+
handler = h;
|
|
46
|
+
},
|
|
47
|
+
async close() {
|
|
48
|
+
intentionalClose = true;
|
|
49
|
+
if (ws) {
|
|
50
|
+
ws.close();
|
|
51
|
+
ws = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function createSync(config) {
|
|
57
|
+
const clientId = config.clientId ?? `browser-${crypto.randomUUID().slice(0, 8)}`;
|
|
58
|
+
const transport = createBrowserWsTransport(config.url);
|
|
59
|
+
const managedTables = new Set(config.tables ?? []);
|
|
60
|
+
const unmanagedRefCounts = new Map;
|
|
61
|
+
const unmanagedParamKeys = new Map;
|
|
62
|
+
function stableKey(obj) {
|
|
63
|
+
if (!obj)
|
|
64
|
+
return "";
|
|
65
|
+
const keys = Object.keys(obj).sort();
|
|
66
|
+
return keys.map((k) => `${k}:${JSON.stringify(obj[k])}`).join("|");
|
|
67
|
+
}
|
|
68
|
+
const client = new SyncClient({
|
|
69
|
+
clientId,
|
|
70
|
+
transport,
|
|
71
|
+
token: config.token,
|
|
72
|
+
storage: config.storage,
|
|
73
|
+
onReauth: () => config.onReauth?.() ?? Promise.reject(new Error("No onReauth handler")),
|
|
74
|
+
onError: (err) => config.onError?.(err)
|
|
75
|
+
});
|
|
76
|
+
async function connect() {
|
|
77
|
+
if (config.storage) {
|
|
78
|
+
await client.init();
|
|
79
|
+
}
|
|
80
|
+
await client.connect();
|
|
81
|
+
for (const table of managedTables) {
|
|
82
|
+
await client.sync(table);
|
|
83
|
+
}
|
|
84
|
+
await client.resume();
|
|
85
|
+
}
|
|
86
|
+
async function close() {
|
|
87
|
+
await client.close();
|
|
88
|
+
}
|
|
89
|
+
function getState() {
|
|
90
|
+
return client.getState();
|
|
91
|
+
}
|
|
92
|
+
function onStateChange(cb) {
|
|
93
|
+
let lastState = client.getState();
|
|
94
|
+
return client.subscribe(() => {
|
|
95
|
+
const current = client.getState();
|
|
96
|
+
if (current !== lastState) {
|
|
97
|
+
lastState = current;
|
|
98
|
+
cb(current);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function getPendingCount() {
|
|
103
|
+
return client.getPendingCount();
|
|
104
|
+
}
|
|
105
|
+
function onPendingChange(cb) {
|
|
106
|
+
let lastCount = client.getPendingCount();
|
|
107
|
+
return client.subscribe(() => {
|
|
108
|
+
const current = client.getPendingCount();
|
|
109
|
+
if (current !== lastCount) {
|
|
110
|
+
lastCount = current;
|
|
111
|
+
cb(current);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function sync(table, options) {
|
|
116
|
+
const isManaged = managedTables.has(table);
|
|
117
|
+
const paramKey = stableKey(options?.params);
|
|
118
|
+
if (!isManaged) {
|
|
119
|
+
const currentParamKey = unmanagedParamKeys.get(table);
|
|
120
|
+
const count = unmanagedRefCounts.get(table) ?? 0;
|
|
121
|
+
if (count === 0 || currentParamKey !== paramKey) {
|
|
122
|
+
if (currentParamKey !== undefined && currentParamKey !== paramKey) {
|
|
123
|
+
client.unsync(table);
|
|
124
|
+
}
|
|
125
|
+
client.sync(table, options?.params, options?.window ? { window: options.window } : undefined);
|
|
126
|
+
client.scheduleBootstrap();
|
|
127
|
+
unmanagedParamKeys.set(table, paramKey);
|
|
128
|
+
}
|
|
129
|
+
unmanagedRefCounts.set(table, count + 1);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
getRows(getOptions) {
|
|
133
|
+
return client.getRows(table, {
|
|
134
|
+
includeDeleted: getOptions?.includeDeleted ?? options?.includeDeleted
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
insert(rowId, payload) {
|
|
138
|
+
client.insert(table, rowId, payload);
|
|
139
|
+
pushSafely(client);
|
|
140
|
+
},
|
|
141
|
+
update(rowId, payload) {
|
|
142
|
+
client.update(table, rowId, payload);
|
|
143
|
+
pushSafely(client);
|
|
144
|
+
},
|
|
145
|
+
remove(rowId) {
|
|
146
|
+
client.delete(table, rowId);
|
|
147
|
+
pushSafely(client);
|
|
148
|
+
},
|
|
149
|
+
onChange(cb) {
|
|
150
|
+
return client.subscribeTable(table, cb);
|
|
151
|
+
},
|
|
152
|
+
destroy() {
|
|
153
|
+
if (isManaged)
|
|
154
|
+
return;
|
|
155
|
+
const count = (unmanagedRefCounts.get(table) ?? 1) - 1;
|
|
156
|
+
if (count <= 0) {
|
|
157
|
+
unmanagedRefCounts.delete(table);
|
|
158
|
+
unmanagedParamKeys.delete(table);
|
|
159
|
+
client.unsync(table);
|
|
160
|
+
} else {
|
|
161
|
+
unmanagedRefCounts.set(table, count);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function getRow(table, rowId) {
|
|
167
|
+
return client.getRow(table, rowId);
|
|
168
|
+
}
|
|
169
|
+
function onRowChange(table, rowId, cb) {
|
|
170
|
+
return client.subscribeTable(table, () => {
|
|
171
|
+
cb(client.getRow(table, rowId));
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function ephemeral(ephConfig) {
|
|
175
|
+
let events = {};
|
|
176
|
+
const timers = new Map;
|
|
177
|
+
const changeListeners = new Set;
|
|
178
|
+
function notifyChange() {
|
|
179
|
+
for (const listener of changeListeners) {
|
|
180
|
+
listener(events);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const unsub = client.subscribeEphemeral(ephConfig.key, (event) => {
|
|
184
|
+
events = { ...events, [event.clientId]: event.data };
|
|
185
|
+
if (ephConfig.ttlMs) {
|
|
186
|
+
const existing = timers.get(event.clientId);
|
|
187
|
+
if (existing) {
|
|
188
|
+
clearTimeout(existing);
|
|
189
|
+
}
|
|
190
|
+
const timer = setTimeout(() => {
|
|
191
|
+
const next = { ...events };
|
|
192
|
+
delete next[event.clientId];
|
|
193
|
+
events = next;
|
|
194
|
+
timers.delete(event.clientId);
|
|
195
|
+
notifyChange();
|
|
196
|
+
}, ephConfig.ttlMs);
|
|
197
|
+
timers.set(event.clientId, timer);
|
|
198
|
+
}
|
|
199
|
+
notifyChange();
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
getEvents() {
|
|
203
|
+
return events;
|
|
204
|
+
},
|
|
205
|
+
broadcast(data) {
|
|
206
|
+
return client.sendEphemeral({
|
|
207
|
+
key: ephConfig.key,
|
|
208
|
+
userId: ephConfig.userId,
|
|
209
|
+
data,
|
|
210
|
+
ttlMs: ephConfig.ttlMs
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
onChange(cb) {
|
|
214
|
+
changeListeners.add(cb);
|
|
215
|
+
return () => {
|
|
216
|
+
changeListeners.delete(cb);
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
destroy() {
|
|
220
|
+
unsub();
|
|
221
|
+
for (const timer of timers.values()) {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
}
|
|
224
|
+
timers.clear();
|
|
225
|
+
changeListeners.clear();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function getTotalCount(table) {
|
|
230
|
+
return client.getTotalCount(table);
|
|
231
|
+
}
|
|
232
|
+
function onTotalCountChange(table, cb) {
|
|
233
|
+
let lastCount = client.getTotalCount(table);
|
|
234
|
+
return client.subscribeTable(table, () => {
|
|
235
|
+
const current = client.getTotalCount(table);
|
|
236
|
+
if (current !== lastCount) {
|
|
237
|
+
lastCount = current;
|
|
238
|
+
cb(current);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function loadMore(table, count) {
|
|
243
|
+
client.loadMore(table, count);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
client,
|
|
247
|
+
connect,
|
|
248
|
+
close,
|
|
249
|
+
getState,
|
|
250
|
+
onStateChange,
|
|
251
|
+
getPendingCount,
|
|
252
|
+
onPendingChange,
|
|
253
|
+
sync,
|
|
254
|
+
getRow,
|
|
255
|
+
onRowChange,
|
|
256
|
+
ephemeral,
|
|
257
|
+
getTotalCount,
|
|
258
|
+
onTotalCountChange,
|
|
259
|
+
loadMore
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export { createBrowserWsTransport, createSync };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/server/node-require.ts
|
|
2
|
+
var cached;
|
|
3
|
+
function nodeRequire(id) {
|
|
4
|
+
if (!cached) {
|
|
5
|
+
const proc = globalThis.process;
|
|
6
|
+
const mod = proc?.getBuiltinModule?.("node:module");
|
|
7
|
+
if (!mod?.createRequire) {
|
|
8
|
+
throw new Error(`Cannot resolve "${id}": this runtime has no CommonJS resolver (process.getBuiltinModule is unavailable). reflectdb requires Node >= 22.3 or Bun >= 1.1.30 on the server.`);
|
|
9
|
+
}
|
|
10
|
+
cached = mod.createRequire(import.meta.url);
|
|
11
|
+
}
|
|
12
|
+
return cached(id);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { nodeRequire };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export { __require };
|
package/dist/svelte/index.js
CHANGED
package/dist/transport/bun-ws.js
CHANGED
package/dist/transport/sse.d.ts
CHANGED
|
@@ -189,17 +189,51 @@ interface SseServerConfig {
|
|
|
189
189
|
* Default: 1 MB.
|
|
190
190
|
*/
|
|
191
191
|
maxMessageBytes?: number;
|
|
192
|
+
/**
|
|
193
|
+
* Return replies from the POST that produced them instead of assuming they
|
|
194
|
+
* can be streamed.
|
|
195
|
+
*
|
|
196
|
+
* Normally SSE is server→client only: a client POSTs a message and every
|
|
197
|
+
* reply — `hello_ack`, snapshots, op acks — comes back down the held stream.
|
|
198
|
+
* That works only while the POST and the stream are handled by the SAME
|
|
199
|
+
* process. On a serverless platform (Vercel, Lambda, Workers) they are two
|
|
200
|
+
* separate invocations, so those replies would be enqueued onto a stream the
|
|
201
|
+
* POST's process does not have, and the client would hang at the handshake.
|
|
202
|
+
*
|
|
203
|
+
* With this set, `collectReplies` runs a message and hands back what it
|
|
204
|
+
* produced so the HTTP handler can put it in the POST response body. The
|
|
205
|
+
* stream is then only used for what it is actually good at: pushing OTHER
|
|
206
|
+
* clients' changes. Pair it with `serverless: true` on the client transport.
|
|
207
|
+
*
|
|
208
|
+
* Off by default — a single-process server should keep streaming everything,
|
|
209
|
+
* and turning this on there would deliver each reply twice.
|
|
210
|
+
*/
|
|
211
|
+
serverless?: boolean;
|
|
192
212
|
}
|
|
193
213
|
declare function createSseServerTransport(cfg?: SseServerConfig): ServerTransport & {
|
|
194
214
|
handleSubscribe(clientId: string, controller: ReadableStreamDefaultController, lastEventId?: string): void;
|
|
195
215
|
handleMessage(clientId: string, message: ClientMessage): void;
|
|
196
216
|
handleDisconnect(clientId: string): void;
|
|
197
217
|
createEventStream(clientId: string, lastEventId?: string): ReadableStream;
|
|
218
|
+
collectReplies(clientId: string, message: ClientMessage, settle?: () => Promise<void>): Promise<ServerMessage[]>;
|
|
198
219
|
};
|
|
199
220
|
interface SseClientConfig {
|
|
200
221
|
eventUrl: string;
|
|
201
222
|
messageUrl: string;
|
|
202
223
|
headers?: Record<string, string>;
|
|
224
|
+
/**
|
|
225
|
+
* Read replies out of the POST response instead of waiting for them on the
|
|
226
|
+
* stream. Must match `serverless: true` on the server transport.
|
|
227
|
+
*
|
|
228
|
+
* Needed wherever the POST and the event stream are handled by different
|
|
229
|
+
* processes — Vercel, Lambda, Workers. There, replies to a POST are produced
|
|
230
|
+
* by a process that does not own this client's stream, so they can never be
|
|
231
|
+
* streamed and the handshake never completes.
|
|
232
|
+
*
|
|
233
|
+
* Leave it off for a single-process server: replies stream normally there,
|
|
234
|
+
* and turning it on would deliver every one of them twice.
|
|
235
|
+
*/
|
|
236
|
+
serverless?: boolean;
|
|
203
237
|
}
|
|
204
238
|
declare function createSseClientTransport(config: SseClientConfig): ClientTransport;
|
|
205
239
|
export { SseClientConfig, SseServerConfig, createSseClientTransport, createSseServerTransport };
|
package/dist/transport/sse.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TransportSendError
|
|
3
3
|
} from "../shared/esm-3tkwvysa.js";
|
|
4
|
-
import"../shared/esm-
|
|
4
|
+
import"../shared/esm-g5h4a88j.js";
|
|
5
5
|
|
|
6
6
|
// src/transport/sse.ts
|
|
7
|
+
var COLLECT_STABLE_TICKS = 3;
|
|
8
|
+
var MAX_COLLECT_TICKS = 60;
|
|
7
9
|
function createSseServerTransport(cfg = {}) {
|
|
8
10
|
const controllers = new Map;
|
|
9
11
|
const buffers = new Map;
|
|
10
12
|
const counters = new Map;
|
|
11
13
|
const replayBufferSize = cfg.replayBufferSize ?? 256;
|
|
14
|
+
const serverless = cfg.serverless ?? false;
|
|
15
|
+
const collecting = new Map;
|
|
16
|
+
const connected = new Set;
|
|
12
17
|
const maxMessageBytes = cfg.maxMessageBytes ?? 1e6;
|
|
13
18
|
let messageHandler = null;
|
|
14
19
|
let connectHandler = null;
|
|
@@ -56,7 +61,10 @@ data: ${data}
|
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
|
-
|
|
64
|
+
if (!connected.has(clientId)) {
|
|
65
|
+
connected.add(clientId);
|
|
66
|
+
connectHandler?.(clientId, new Request("https://sse-connect"));
|
|
67
|
+
}
|
|
60
68
|
},
|
|
61
69
|
handleMessage(clientId, message) {
|
|
62
70
|
let byteLen;
|
|
@@ -74,6 +82,7 @@ data: ${data}
|
|
|
74
82
|
},
|
|
75
83
|
handleDisconnect(clientId) {
|
|
76
84
|
controllers.delete(clientId);
|
|
85
|
+
connected.delete(clientId);
|
|
77
86
|
disconnectHandler?.(clientId);
|
|
78
87
|
},
|
|
79
88
|
createEventStream(clientId, lastEventId) {
|
|
@@ -87,6 +96,11 @@ data: ${data}
|
|
|
87
96
|
});
|
|
88
97
|
},
|
|
89
98
|
async send(clientId, message) {
|
|
99
|
+
const sink = collecting.get(clientId);
|
|
100
|
+
if (sink) {
|
|
101
|
+
sink.push(message);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
90
104
|
const id = nextId(clientId);
|
|
91
105
|
const encoded = encode(id, message);
|
|
92
106
|
const controller = controllers.get(clientId);
|
|
@@ -108,6 +122,31 @@ data: ${data}
|
|
|
108
122
|
throw new TransportSendError(clientId, `sse replay buffer overflow (size ${replayBufferSize}); undelivered frames dropped`);
|
|
109
123
|
}
|
|
110
124
|
},
|
|
125
|
+
async collectReplies(clientId, message, settle) {
|
|
126
|
+
if (!serverless) {
|
|
127
|
+
this.handleMessage(clientId, message);
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
if (!connected.has(clientId)) {
|
|
131
|
+
connected.add(clientId);
|
|
132
|
+
connectHandler?.(clientId, new Request("https://sse-connect"));
|
|
133
|
+
}
|
|
134
|
+
const sink = [];
|
|
135
|
+
collecting.set(clientId, sink);
|
|
136
|
+
try {
|
|
137
|
+
this.handleMessage(clientId, message);
|
|
138
|
+
await (settle?.() ?? Promise.resolve());
|
|
139
|
+
let stable = 0;
|
|
140
|
+
for (let i = 0;i < MAX_COLLECT_TICKS && stable < COLLECT_STABLE_TICKS; i++) {
|
|
141
|
+
const before = sink.length;
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
143
|
+
stable = sink.length === before ? stable + 1 : 0;
|
|
144
|
+
}
|
|
145
|
+
} finally {
|
|
146
|
+
collecting.delete(clientId);
|
|
147
|
+
}
|
|
148
|
+
return sink;
|
|
149
|
+
},
|
|
111
150
|
async broadcast(_roomId, message, exclude) {
|
|
112
151
|
for (const [clientId, controller] of controllers) {
|
|
113
152
|
if (clientId === exclude)
|
|
@@ -173,6 +212,18 @@ function createSseClientTransport(config) {
|
|
|
173
212
|
if (!res.ok) {
|
|
174
213
|
throw new Error(`SSE send failed: ${res.status} ${res.statusText}`);
|
|
175
214
|
}
|
|
215
|
+
if (!config.serverless)
|
|
216
|
+
return;
|
|
217
|
+
let replies;
|
|
218
|
+
try {
|
|
219
|
+
const body = await res.json();
|
|
220
|
+
replies = Array.isArray(body) ? body : body.messages ?? [];
|
|
221
|
+
} catch {
|
|
222
|
+
console.warn("[reflectdb] SSE: serverless mode is on but the POST returned no JSON replies. " + "Set `serverless: true` on createSseServerTransport and return " + "`collectReplies(...)` from the message endpoint.");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
for (const reply of replies)
|
|
226
|
+
handler?.(reply);
|
|
176
227
|
},
|
|
177
228
|
subscribe(h) {
|
|
178
229
|
handler = h;
|