@instantdb/core 0.22.139 → 0.22.140
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/commonjs/Connection.d.ts +2 -1
- package/dist/commonjs/Connection.d.ts.map +1 -1
- package/dist/commonjs/Connection.js +6 -2
- package/dist/commonjs/Connection.js.map +1 -1
- package/dist/commonjs/Reactor.d.ts +12 -0
- package/dist/commonjs/Reactor.d.ts.map +1 -1
- package/dist/commonjs/Reactor.js +63 -18
- package/dist/commonjs/Reactor.js.map +1 -1
- package/dist/commonjs/Stream.d.ts +112 -0
- package/dist/commonjs/Stream.d.ts.map +1 -0
- package/dist/commonjs/Stream.js +708 -0
- package/dist/commonjs/Stream.js.map +1 -0
- package/dist/commonjs/index.d.ts +41 -2
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +41 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/esm/Connection.d.ts +2 -1
- package/dist/esm/Connection.d.ts.map +1 -1
- package/dist/esm/Connection.js +6 -2
- package/dist/esm/Connection.js.map +1 -1
- package/dist/esm/Reactor.d.ts +12 -0
- package/dist/esm/Reactor.d.ts.map +1 -1
- package/dist/esm/Reactor.js +53 -9
- package/dist/esm/Reactor.js.map +1 -1
- package/dist/esm/Stream.d.ts +112 -0
- package/dist/esm/Stream.d.ts.map +1 -0
- package/dist/esm/Stream.js +701 -0
- package/dist/esm/Stream.js.map +1 -0
- package/dist/esm/index.d.ts +41 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +42 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/standalone/index.js +2203 -1604
- package/dist/standalone/index.umd.cjs +3 -3
- package/package.json +2 -2
- package/src/Connection.ts +6 -2
- package/src/Reactor.js +56 -10
- package/src/Stream.ts +1016 -0
- package/src/index.ts +71 -1
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.InstantStream = void 0;
|
|
7
|
+
const id_ts_1 = __importDefault(require("./utils/id.js"));
|
|
8
|
+
const Reactor_js_1 = require("./Reactor.js");
|
|
9
|
+
const InstantError_ts_1 = require("./InstantError.js");
|
|
10
|
+
function createWriteStream({ WStream, opts, startStream, appendStream, registerStream, }) {
|
|
11
|
+
const clientId = opts.clientId;
|
|
12
|
+
let streamId_ = null;
|
|
13
|
+
let controller_ = null;
|
|
14
|
+
const reconnectToken = (0, id_ts_1.default)();
|
|
15
|
+
let isDone = false;
|
|
16
|
+
let closed = false;
|
|
17
|
+
const closeCbs = [];
|
|
18
|
+
const streamIdCbs = [];
|
|
19
|
+
let disconnected = false;
|
|
20
|
+
// Chunks that we haven't been notified are flushed to disk
|
|
21
|
+
let bufferOffset = 0;
|
|
22
|
+
let bufferByteSize = 0;
|
|
23
|
+
const buffer = [];
|
|
24
|
+
const encoder = new TextEncoder();
|
|
25
|
+
function markClosed() {
|
|
26
|
+
closed = true;
|
|
27
|
+
for (const cb of closeCbs) {
|
|
28
|
+
cb();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function addCloseCb(cb) {
|
|
32
|
+
closeCbs.push(cb);
|
|
33
|
+
return () => {
|
|
34
|
+
const i = closeCbs.indexOf(cb);
|
|
35
|
+
if (i !== -1) {
|
|
36
|
+
closeCbs.splice(i, 1);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function addStreamIdCb(cb) {
|
|
41
|
+
streamIdCbs.push(cb);
|
|
42
|
+
return () => {
|
|
43
|
+
const i = streamIdCbs.indexOf(cb);
|
|
44
|
+
if (i !== -1) {
|
|
45
|
+
streamIdCbs.splice(i, 1);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function setStreamId(streamId) {
|
|
50
|
+
streamId_ = streamId;
|
|
51
|
+
for (const cb of streamIdCbs) {
|
|
52
|
+
cb(streamId_);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function onDisconnect() {
|
|
56
|
+
disconnected = true;
|
|
57
|
+
}
|
|
58
|
+
// Clears data from our buffer after it has been flushed to a file
|
|
59
|
+
function discardFlushed(offset) {
|
|
60
|
+
let chunkOffset = bufferOffset;
|
|
61
|
+
let segmentsToDrop = 0;
|
|
62
|
+
let droppedSegmentsByteLen = 0;
|
|
63
|
+
for (const { byteLen } of buffer) {
|
|
64
|
+
const nextChunkOffset = chunkOffset + byteLen;
|
|
65
|
+
if (nextChunkOffset > offset) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
chunkOffset = nextChunkOffset;
|
|
69
|
+
segmentsToDrop++;
|
|
70
|
+
droppedSegmentsByteLen += byteLen;
|
|
71
|
+
}
|
|
72
|
+
if (segmentsToDrop > 0) {
|
|
73
|
+
bufferOffset += droppedSegmentsByteLen;
|
|
74
|
+
bufferByteSize -= droppedSegmentsByteLen;
|
|
75
|
+
buffer.splice(0, segmentsToDrop);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function onConnectionReconnect() {
|
|
79
|
+
const result = await startStream({
|
|
80
|
+
clientId,
|
|
81
|
+
reconnectToken,
|
|
82
|
+
});
|
|
83
|
+
switch (result.type) {
|
|
84
|
+
case 'ok': {
|
|
85
|
+
const { streamId, offset } = result;
|
|
86
|
+
streamId_ = streamId;
|
|
87
|
+
discardFlushed(offset);
|
|
88
|
+
if (buffer.length) {
|
|
89
|
+
appendStream({
|
|
90
|
+
streamId: streamId,
|
|
91
|
+
chunks: buffer.map((b) => b.chunk),
|
|
92
|
+
offset: bufferOffset,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
disconnected = false;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'disconnect': {
|
|
99
|
+
onDisconnect();
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'error': {
|
|
103
|
+
if (controller_) {
|
|
104
|
+
controller_.error(result.error);
|
|
105
|
+
markClosed();
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// When the append fails, we'll just try to reconnect and start again
|
|
112
|
+
function onAppendFailed() {
|
|
113
|
+
onDisconnect();
|
|
114
|
+
onConnectionReconnect();
|
|
115
|
+
}
|
|
116
|
+
function onFlush({ offset, done }) {
|
|
117
|
+
discardFlushed(offset);
|
|
118
|
+
if (done) {
|
|
119
|
+
isDone = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function error(controller, error) {
|
|
123
|
+
markClosed();
|
|
124
|
+
controller.error(error);
|
|
125
|
+
}
|
|
126
|
+
function ensureSetup(controller) {
|
|
127
|
+
if (isDone) {
|
|
128
|
+
error(controller, new InstantError_ts_1.InstantError('Stream has been closed.'));
|
|
129
|
+
}
|
|
130
|
+
if (!streamId_) {
|
|
131
|
+
error(controller, new InstantError_ts_1.InstantError('Stream has not been initialized.'));
|
|
132
|
+
}
|
|
133
|
+
return streamId_;
|
|
134
|
+
}
|
|
135
|
+
async function start(controller) {
|
|
136
|
+
controller_ = controller;
|
|
137
|
+
let tryAgain = true;
|
|
138
|
+
let attempts = 0;
|
|
139
|
+
while (tryAgain) {
|
|
140
|
+
// rate-limit after the first few failed connects
|
|
141
|
+
let nextAttempt = Date.now() + Math.min(15000, 500 * (attempts - 1));
|
|
142
|
+
tryAgain = false;
|
|
143
|
+
const result = await startStream({
|
|
144
|
+
clientId: opts.clientId,
|
|
145
|
+
reconnectToken,
|
|
146
|
+
});
|
|
147
|
+
switch (result.type) {
|
|
148
|
+
case 'ok': {
|
|
149
|
+
const { streamId, offset } = result;
|
|
150
|
+
if (offset !== 0) {
|
|
151
|
+
const e = new InstantError_ts_1.InstantError('Write stream is corrupted');
|
|
152
|
+
error(controller, e);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
setStreamId(streamId);
|
|
156
|
+
registerStream(streamId, {
|
|
157
|
+
onDisconnect,
|
|
158
|
+
onFlush,
|
|
159
|
+
onConnectionReconnect,
|
|
160
|
+
onAppendFailed,
|
|
161
|
+
});
|
|
162
|
+
disconnected = false;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
case 'disconnect': {
|
|
166
|
+
tryAgain = true;
|
|
167
|
+
onDisconnect();
|
|
168
|
+
attempts++;
|
|
169
|
+
await new Promise((resolve) => {
|
|
170
|
+
// Try again immediately for the first two attempts, then back off
|
|
171
|
+
setTimeout(resolve, nextAttempt - Date.now());
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'error': {
|
|
176
|
+
error(controller, result.error);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
class WStreamEnhanced extends WStream {
|
|
183
|
+
constructor(sink, strategy) {
|
|
184
|
+
super(sink, strategy);
|
|
185
|
+
}
|
|
186
|
+
async streamId() {
|
|
187
|
+
if (streamId_) {
|
|
188
|
+
return streamId_;
|
|
189
|
+
}
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
const cleanupFns = [];
|
|
192
|
+
const cleanup = () => {
|
|
193
|
+
for (const f of cleanupFns) {
|
|
194
|
+
f();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
const resolveCb = (streamId) => {
|
|
198
|
+
resolve(streamId);
|
|
199
|
+
cleanup();
|
|
200
|
+
};
|
|
201
|
+
const rejectCb = () => {
|
|
202
|
+
reject(new InstantError_ts_1.InstantError('Stream is closed.'));
|
|
203
|
+
cleanup();
|
|
204
|
+
};
|
|
205
|
+
cleanupFns.push(addStreamIdCb(resolveCb));
|
|
206
|
+
cleanupFns.push(addCloseCb(rejectCb));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const stream = new WStreamEnhanced({
|
|
211
|
+
// TODO(dww): accept a storage so that write streams can survive across
|
|
212
|
+
// browser restarts
|
|
213
|
+
async start(controller) {
|
|
214
|
+
try {
|
|
215
|
+
await start(controller);
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
error(controller, e);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
write(chunk, controller) {
|
|
222
|
+
const streamId = ensureSetup(controller);
|
|
223
|
+
if (streamId) {
|
|
224
|
+
const byteLen = encoder.encode(chunk).length;
|
|
225
|
+
buffer.push({ chunk, byteLen });
|
|
226
|
+
const offset = bufferOffset + bufferByteSize;
|
|
227
|
+
bufferByteSize += byteLen;
|
|
228
|
+
if (!disconnected) {
|
|
229
|
+
appendStream({ streamId, chunks: [chunk], offset });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
close() {
|
|
234
|
+
if (streamId_) {
|
|
235
|
+
appendStream({
|
|
236
|
+
streamId: streamId_,
|
|
237
|
+
chunks: [],
|
|
238
|
+
offset: bufferOffset + bufferByteSize,
|
|
239
|
+
isDone: true,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
markClosed();
|
|
243
|
+
},
|
|
244
|
+
abort(reason) {
|
|
245
|
+
if (streamId_) {
|
|
246
|
+
appendStream({
|
|
247
|
+
streamId: streamId_,
|
|
248
|
+
chunks: [],
|
|
249
|
+
offset: bufferOffset + bufferByteSize,
|
|
250
|
+
isDone: true,
|
|
251
|
+
abortReason: reason,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
markClosed();
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
stream,
|
|
259
|
+
addCloseCb,
|
|
260
|
+
closed() {
|
|
261
|
+
return closed;
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
class StreamIterator {
|
|
266
|
+
items = [];
|
|
267
|
+
resolvers = [];
|
|
268
|
+
isClosed = false;
|
|
269
|
+
constructor() { }
|
|
270
|
+
push(item) {
|
|
271
|
+
if (this.isClosed)
|
|
272
|
+
return;
|
|
273
|
+
const resolve = this.resolvers.shift();
|
|
274
|
+
if (resolve) {
|
|
275
|
+
resolve({ value: item, done: false });
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
this.items.push(item);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
close() {
|
|
282
|
+
this.isClosed = true;
|
|
283
|
+
while (this.resolvers.length > 0) {
|
|
284
|
+
const resolve = this.resolvers.shift();
|
|
285
|
+
resolve({ value: undefined, done: true });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async *[Symbol.asyncIterator]() {
|
|
289
|
+
while (true) {
|
|
290
|
+
if (this.items.length > 0) {
|
|
291
|
+
yield this.items.shift();
|
|
292
|
+
}
|
|
293
|
+
else if (this.isClosed) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const { value, done } = await new Promise((resolve) => {
|
|
298
|
+
this.resolvers.push(resolve);
|
|
299
|
+
});
|
|
300
|
+
if (done || !value) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
yield value;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function createReadStream({ RStream, opts, startStream, cancelStream, }) {
|
|
309
|
+
let seenOffset = opts.byteOffset || 0;
|
|
310
|
+
let canceled = false;
|
|
311
|
+
const decoder = new TextDecoder('utf-8');
|
|
312
|
+
const encoder = new TextEncoder();
|
|
313
|
+
let eventId;
|
|
314
|
+
let closed = false;
|
|
315
|
+
const closeCbs = [];
|
|
316
|
+
function markClosed() {
|
|
317
|
+
closed = true;
|
|
318
|
+
for (const cb of closeCbs) {
|
|
319
|
+
cb();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function addCloseCb(cb) {
|
|
323
|
+
closeCbs.push(cb);
|
|
324
|
+
return () => {
|
|
325
|
+
const i = closeCbs.indexOf(cb);
|
|
326
|
+
if (i !== -1) {
|
|
327
|
+
closeCbs.splice(i, 1);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function error(controller, e) {
|
|
332
|
+
controller.error(e);
|
|
333
|
+
markClosed();
|
|
334
|
+
}
|
|
335
|
+
let fetchFailures = 0;
|
|
336
|
+
async function runStartStream(opts, controller) {
|
|
337
|
+
eventId = (0, id_ts_1.default)();
|
|
338
|
+
const streamOpts = { ...(opts || {}), eventId };
|
|
339
|
+
for await (const item of startStream(streamOpts)) {
|
|
340
|
+
if (canceled) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (item.type === 'reconnect') {
|
|
344
|
+
return { retry: true };
|
|
345
|
+
}
|
|
346
|
+
if (item.type === 'error') {
|
|
347
|
+
error(controller, item.error);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (item.offset > seenOffset) {
|
|
351
|
+
error(controller, new InstantError_ts_1.InstantError('Stream is corrupted.'));
|
|
352
|
+
canceled = true;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
let discardLen = seenOffset - item.offset;
|
|
356
|
+
if (item.files && item.files.length) {
|
|
357
|
+
const fetchAbort = new AbortController();
|
|
358
|
+
let nextFetch = fetch(item.files[0].url, {
|
|
359
|
+
signal: fetchAbort.signal,
|
|
360
|
+
});
|
|
361
|
+
for (let i = 0; i < item.files.length; i++) {
|
|
362
|
+
const nextFile = item.files[i + 1];
|
|
363
|
+
const thisFetch = nextFetch;
|
|
364
|
+
const res = await thisFetch;
|
|
365
|
+
if (nextFile) {
|
|
366
|
+
nextFetch = fetch(nextFile.url, { signal: fetchAbort.signal });
|
|
367
|
+
}
|
|
368
|
+
if (!res.ok) {
|
|
369
|
+
fetchFailures++;
|
|
370
|
+
if (fetchFailures > 10) {
|
|
371
|
+
error(controller, new InstantError_ts_1.InstantError('Unable to process stream.'));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
return { retry: true };
|
|
375
|
+
}
|
|
376
|
+
if (res.body) {
|
|
377
|
+
for await (const bodyChunk of res.body) {
|
|
378
|
+
if (canceled) {
|
|
379
|
+
fetchAbort.abort();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
let chunk = bodyChunk;
|
|
383
|
+
if (discardLen > 0) {
|
|
384
|
+
chunk = bodyChunk.subarray(discardLen);
|
|
385
|
+
discardLen -= bodyChunk.length - chunk.length;
|
|
386
|
+
}
|
|
387
|
+
if (!chunk.length) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
seenOffset += chunk.length;
|
|
391
|
+
const s = decoder.decode(chunk);
|
|
392
|
+
controller.enqueue(s);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
// RN doesn't support request.body
|
|
397
|
+
const bodyChunk = await res.arrayBuffer();
|
|
398
|
+
let chunk = bodyChunk;
|
|
399
|
+
if (canceled) {
|
|
400
|
+
fetchAbort.abort();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (discardLen > 0) {
|
|
404
|
+
chunk = new Uint8Array(bodyChunk).subarray(discardLen);
|
|
405
|
+
discardLen -= bodyChunk.byteLength - chunk.length;
|
|
406
|
+
}
|
|
407
|
+
if (!chunk.byteLength) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
seenOffset += chunk.byteLength;
|
|
411
|
+
const s = decoder.decode(chunk);
|
|
412
|
+
controller.enqueue(s);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
fetchFailures = 0;
|
|
417
|
+
if (item.content) {
|
|
418
|
+
let content = item.content;
|
|
419
|
+
let encoded = encoder.encode(item.content);
|
|
420
|
+
if (discardLen > 0) {
|
|
421
|
+
const remaining = encoded.subarray(discardLen);
|
|
422
|
+
discardLen -= encoded.length - remaining.length;
|
|
423
|
+
if (!remaining.length) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
encoded = remaining;
|
|
427
|
+
content = decoder.decode(remaining);
|
|
428
|
+
}
|
|
429
|
+
seenOffset += encoded.length;
|
|
430
|
+
controller.enqueue(content);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function start(controller) {
|
|
435
|
+
let retry = true;
|
|
436
|
+
let attempts = 0;
|
|
437
|
+
while (retry) {
|
|
438
|
+
retry = false;
|
|
439
|
+
let nextAttempt = Date.now() + Math.min(15000, 500 * (attempts - 1));
|
|
440
|
+
const res = await runStartStream({ ...opts, offset: seenOffset }, controller);
|
|
441
|
+
if (res?.retry) {
|
|
442
|
+
retry = true;
|
|
443
|
+
attempts++;
|
|
444
|
+
if (nextAttempt < Date.now() - 300000) {
|
|
445
|
+
// reset attempts if we last tried 5 minutes ago
|
|
446
|
+
attempts = 0;
|
|
447
|
+
}
|
|
448
|
+
await new Promise((resolve) => {
|
|
449
|
+
setTimeout(resolve, nextAttempt - Date.now());
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (!canceled && !closed) {
|
|
454
|
+
controller.close();
|
|
455
|
+
markClosed();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const stream = new RStream({
|
|
459
|
+
start(controller) {
|
|
460
|
+
start(controller);
|
|
461
|
+
},
|
|
462
|
+
cancel(_reason) {
|
|
463
|
+
canceled = true;
|
|
464
|
+
if (eventId) {
|
|
465
|
+
cancelStream({ eventId });
|
|
466
|
+
}
|
|
467
|
+
markClosed();
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
return {
|
|
471
|
+
stream,
|
|
472
|
+
addCloseCb,
|
|
473
|
+
closed() {
|
|
474
|
+
return closed;
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
class InstantStream {
|
|
479
|
+
trySend;
|
|
480
|
+
WStream;
|
|
481
|
+
RStream;
|
|
482
|
+
writeStreams = {};
|
|
483
|
+
startWriteStreamCbs = {};
|
|
484
|
+
readStreamIterators = {};
|
|
485
|
+
log;
|
|
486
|
+
activeStreams = new Set();
|
|
487
|
+
constructor({ WStream, RStream, trySend, log, }) {
|
|
488
|
+
this.WStream = WStream;
|
|
489
|
+
this.RStream = RStream;
|
|
490
|
+
this.trySend = trySend;
|
|
491
|
+
this.log = log;
|
|
492
|
+
}
|
|
493
|
+
createWriteStream(opts) {
|
|
494
|
+
const { stream, addCloseCb } = createWriteStream({
|
|
495
|
+
WStream: this.WStream,
|
|
496
|
+
startStream: this.startWriteStream.bind(this),
|
|
497
|
+
appendStream: this.appendStream.bind(this),
|
|
498
|
+
registerStream: this.registerWriteStream.bind(this),
|
|
499
|
+
opts,
|
|
500
|
+
});
|
|
501
|
+
this.activeStreams.add(stream);
|
|
502
|
+
addCloseCb(() => {
|
|
503
|
+
this.activeStreams.delete(stream);
|
|
504
|
+
});
|
|
505
|
+
return stream;
|
|
506
|
+
}
|
|
507
|
+
createReadStream(opts) {
|
|
508
|
+
const { stream, addCloseCb } = createReadStream({
|
|
509
|
+
RStream: this.RStream,
|
|
510
|
+
opts,
|
|
511
|
+
startStream: this.startReadStream.bind(this),
|
|
512
|
+
cancelStream: this.cancelReadStream.bind(this),
|
|
513
|
+
});
|
|
514
|
+
this.activeStreams.add(stream);
|
|
515
|
+
addCloseCb(() => {
|
|
516
|
+
this.activeStreams.delete(stream);
|
|
517
|
+
});
|
|
518
|
+
return stream;
|
|
519
|
+
}
|
|
520
|
+
startWriteStream(opts) {
|
|
521
|
+
const eventId = (0, id_ts_1.default)();
|
|
522
|
+
let resolve = null;
|
|
523
|
+
const promise = new Promise((r) => {
|
|
524
|
+
resolve = r;
|
|
525
|
+
});
|
|
526
|
+
this.startWriteStreamCbs[eventId] = resolve;
|
|
527
|
+
const msg = {
|
|
528
|
+
op: 'start-stream',
|
|
529
|
+
'client-id': opts.clientId,
|
|
530
|
+
'reconnect-token': opts.reconnectToken,
|
|
531
|
+
};
|
|
532
|
+
this.trySend(eventId, msg);
|
|
533
|
+
return promise;
|
|
534
|
+
}
|
|
535
|
+
registerWriteStream(streamId, cbs) {
|
|
536
|
+
this.writeStreams[streamId] = cbs;
|
|
537
|
+
}
|
|
538
|
+
appendStream({ streamId, chunks, isDone, offset, abortReason, }) {
|
|
539
|
+
const msg = {
|
|
540
|
+
op: 'append-stream',
|
|
541
|
+
'stream-id': streamId,
|
|
542
|
+
chunks,
|
|
543
|
+
offset,
|
|
544
|
+
done: !!isDone,
|
|
545
|
+
};
|
|
546
|
+
if (abortReason) {
|
|
547
|
+
msg['abort-reason'] = abortReason;
|
|
548
|
+
}
|
|
549
|
+
this.trySend((0, id_ts_1.default)(), msg);
|
|
550
|
+
}
|
|
551
|
+
onAppendFailed(msg) {
|
|
552
|
+
const cbs = this.writeStreams[msg['stream-id']];
|
|
553
|
+
if (cbs) {
|
|
554
|
+
cbs.onAppendFailed();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
onStartStreamOk(msg) {
|
|
558
|
+
const cb = this.startWriteStreamCbs[msg['client-event-id']];
|
|
559
|
+
if (!cb) {
|
|
560
|
+
this.log.info('No stream for start-stream-ok', msg);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
cb({ type: 'ok', streamId: msg['stream-id'], offset: msg.offset });
|
|
564
|
+
delete this.startWriteStreamCbs[msg['client-event-id']];
|
|
565
|
+
}
|
|
566
|
+
onStreamFlushed(msg) {
|
|
567
|
+
const streamId = msg['stream-id'];
|
|
568
|
+
const cbs = this.writeStreams[streamId];
|
|
569
|
+
if (!cbs) {
|
|
570
|
+
this.log.info('No stream cbs for stream-flushed', msg);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
cbs.onFlush({ offset: msg.offset, done: msg.done });
|
|
574
|
+
if (msg.done) {
|
|
575
|
+
delete this.writeStreams[streamId];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
startReadStream({ eventId, clientId, streamId, offset, }) {
|
|
579
|
+
const msg = { op: 'subscribe-stream' };
|
|
580
|
+
if (!streamId && !clientId) {
|
|
581
|
+
throw new Error('Must provide one of streamId or clientId to subscribe to the stream.');
|
|
582
|
+
}
|
|
583
|
+
if (streamId) {
|
|
584
|
+
msg['stream-id'] = streamId;
|
|
585
|
+
}
|
|
586
|
+
if (clientId) {
|
|
587
|
+
msg['client-id'] = clientId;
|
|
588
|
+
}
|
|
589
|
+
if (offset) {
|
|
590
|
+
msg['offset'] = offset;
|
|
591
|
+
}
|
|
592
|
+
const iterator = new StreamIterator();
|
|
593
|
+
this.readStreamIterators[eventId] = iterator;
|
|
594
|
+
this.trySend(eventId, msg);
|
|
595
|
+
return iterator;
|
|
596
|
+
}
|
|
597
|
+
cancelReadStream({ eventId }) {
|
|
598
|
+
const msg = {
|
|
599
|
+
op: 'unsubscribe-stream',
|
|
600
|
+
'subscribe-event-id': eventId,
|
|
601
|
+
};
|
|
602
|
+
this.trySend((0, id_ts_1.default)(), msg);
|
|
603
|
+
delete this.readStreamIterators[eventId];
|
|
604
|
+
}
|
|
605
|
+
onStreamAppend(msg) {
|
|
606
|
+
const eventId = msg['client-event-id'];
|
|
607
|
+
const iterator = this.readStreamIterators[eventId];
|
|
608
|
+
if (!iterator) {
|
|
609
|
+
this.log.info('No iterator for read stream', msg);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (msg.error) {
|
|
613
|
+
if (msg.retry) {
|
|
614
|
+
iterator.push({ type: 'reconnect' });
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
iterator.push({
|
|
618
|
+
type: 'error',
|
|
619
|
+
error: new InstantError_ts_1.InstantError(msg.error),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
iterator.close();
|
|
623
|
+
delete this.readStreamIterators[eventId];
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (msg.files?.length || msg.content) {
|
|
627
|
+
iterator.push({
|
|
628
|
+
type: 'append',
|
|
629
|
+
offset: msg.offset,
|
|
630
|
+
files: msg.files,
|
|
631
|
+
content: msg.content,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
if (msg.done) {
|
|
635
|
+
iterator.close();
|
|
636
|
+
delete this.readStreamIterators[eventId];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
onConnectionStatusChange(status) {
|
|
640
|
+
// Tell the writers to retry:
|
|
641
|
+
for (const cb of Object.values(this.startWriteStreamCbs)) {
|
|
642
|
+
cb({ type: 'disconnect' });
|
|
643
|
+
}
|
|
644
|
+
this.startWriteStreamCbs = {};
|
|
645
|
+
if (status !== Reactor_js_1.STATUS.AUTHENTICATED) {
|
|
646
|
+
// Notify the writers that they've been disconnected
|
|
647
|
+
for (const { onDisconnect } of Object.values(this.writeStreams)) {
|
|
648
|
+
onDisconnect();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
// Notify the writers that they need to reconnect
|
|
653
|
+
for (const { onConnectionReconnect } of Object.values(this.writeStreams)) {
|
|
654
|
+
onConnectionReconnect();
|
|
655
|
+
}
|
|
656
|
+
// Notify the readers that they need to reconnect
|
|
657
|
+
for (const iterator of Object.values(this.readStreamIterators)) {
|
|
658
|
+
iterator.push({ type: 'reconnect' });
|
|
659
|
+
iterator.close();
|
|
660
|
+
}
|
|
661
|
+
this.readStreamIterators = {};
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
onRecieveError(msg) {
|
|
665
|
+
const ev = msg['original-event'];
|
|
666
|
+
switch (ev.op) {
|
|
667
|
+
case 'append-stream': {
|
|
668
|
+
const streamId = ev['stream-id'];
|
|
669
|
+
const cbs = this.writeStreams[streamId];
|
|
670
|
+
cbs?.onAppendFailed();
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
case 'start-stream': {
|
|
674
|
+
const eventId = msg['client-event-id'];
|
|
675
|
+
const cb = this.startWriteStreamCbs[eventId];
|
|
676
|
+
if (cb) {
|
|
677
|
+
cb({
|
|
678
|
+
type: 'error',
|
|
679
|
+
error: new InstantError_ts_1.InstantError(msg.message || 'Unknown error', msg.hint),
|
|
680
|
+
});
|
|
681
|
+
delete this.startWriteStreamCbs[eventId];
|
|
682
|
+
}
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
case 'subscribe-stream': {
|
|
686
|
+
const eventId = msg['client-event-id'];
|
|
687
|
+
const iterator = this.readStreamIterators[eventId];
|
|
688
|
+
if (iterator) {
|
|
689
|
+
iterator.push({
|
|
690
|
+
type: 'error',
|
|
691
|
+
error: new InstantError_ts_1.InstantError(msg.message || 'Unknown error', msg.hint),
|
|
692
|
+
});
|
|
693
|
+
iterator.close();
|
|
694
|
+
delete this.readStreamIterators[eventId];
|
|
695
|
+
}
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
case 'unsubscribe-stream': {
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
hasActiveStreams() {
|
|
704
|
+
return this.activeStreams.size > 0;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
exports.InstantStream = InstantStream;
|
|
708
|
+
//# sourceMappingURL=Stream.js.map
|