@instantdb/platform 1.0.65 → 1.0.66
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/.turbo/turbo-build.log +8 -8
- package/__tests__/src/backupDownload.test.ts +34 -18
- package/dist/commonjs/backupDownload.d.ts +1 -13
- package/dist/commonjs/backupDownload.d.ts.map +1 -1
- package/dist/commonjs/backupDownload.js +8 -84
- package/dist/commonjs/backupDownload.js.map +1 -1
- package/dist/esm/backupDownload.d.ts +1 -13
- package/dist/esm/backupDownload.d.ts.map +1 -1
- package/dist/esm/backupDownload.js +8 -84
- package/dist/esm/backupDownload.js.map +1 -1
- package/dist/standalone/index.js +1114 -1156
- package/dist/standalone/index.umd.cjs +21 -21
- package/package.json +4 -4
- package/src/backupDownload.ts +16 -112
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instantdb/platform",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.66",
|
|
4
4
|
"description": "Instant's platform package for managing Instant apps.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/instantdb/instant/tree/main/client/packages/platform",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@babel/parser": "^8.0.0-beta.0",
|
|
57
57
|
"@babel/types": "^8.0.0-beta.0",
|
|
58
|
-
"@instantdb/core": "1.0.
|
|
59
|
-
"@instantdb/version": "1.0.
|
|
60
|
-
"@instantdb/webhooks": "1.0.
|
|
58
|
+
"@instantdb/core": "1.0.66",
|
|
59
|
+
"@instantdb/version": "1.0.66",
|
|
60
|
+
"@instantdb/webhooks": "1.0.66"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"test": "vitest",
|
package/src/backupDownload.ts
CHANGED
|
@@ -76,21 +76,9 @@ export type DownloadBackupArchiveOpts = {
|
|
|
76
76
|
) => Promise<BackupArchiveWriter>;
|
|
77
77
|
signal?: AbortSignal;
|
|
78
78
|
onProgress?: (progress: BackupDownloadProgress) => void;
|
|
79
|
-
/**
|
|
80
|
-
* How many entries to fetch ahead of the one currently being written into
|
|
81
|
-
* the archive.
|
|
82
|
-
*
|
|
83
|
-
* Only fetch *initiation* is parallelised, the bodies are still written in
|
|
84
|
-
* order and consumed one at a time, so an in-flight prefetched body buffers
|
|
85
|
-
* only to its stream's high-water mark and memory stays bounded. Defaults to
|
|
86
|
-
* {@link DEFAULT_PREFETCH}.
|
|
87
|
-
*/
|
|
88
|
-
prefetch?: number;
|
|
89
79
|
/**
|
|
90
80
|
* Retry policy for *opening* an entry's body (fetching its presigned URL and
|
|
91
|
-
* getting a live response).
|
|
92
|
-
* while earlier entries write, so a queued connection can be reset before we
|
|
93
|
-
* read it.
|
|
81
|
+
* getting a live response).
|
|
94
82
|
* `attempts` is the total number of tries (default {@link DEFAULT_FETCH_ATTEMPTS});
|
|
95
83
|
* `delayMs` is the base for exponential backoff (default {@link DEFAULT_RETRY_DELAY_MS}).
|
|
96
84
|
*
|
|
@@ -102,7 +90,6 @@ export type DownloadBackupArchiveOpts = {
|
|
|
102
90
|
retry?: { attempts?: number; delayMs?: number };
|
|
103
91
|
};
|
|
104
92
|
|
|
105
|
-
const DEFAULT_PREFETCH = 4;
|
|
106
93
|
const DEFAULT_FETCH_ATTEMPTS = 3;
|
|
107
94
|
const DEFAULT_RETRY_DELAY_MS = 500;
|
|
108
95
|
const MAX_RETRY_DELAY_MS = 5000;
|
|
@@ -172,9 +159,9 @@ function replayFrom(
|
|
|
172
159
|
* naming the entry.
|
|
173
160
|
*
|
|
174
161
|
* The first chunk is read inside the retry scope, so a body that connects but
|
|
175
|
-
* fails on its first read
|
|
176
|
-
*
|
|
177
|
-
*
|
|
162
|
+
* fails on its first read is re-fetched too, since nothing has been written
|
|
163
|
+
* to the archive yet. Only failures once bytes are flowing are treated as
|
|
164
|
+
* unrecoverable.
|
|
178
165
|
*/
|
|
179
166
|
async function openWithRetry(
|
|
180
167
|
open: () => Promise<ReadableStream<Uint8Array>>,
|
|
@@ -212,10 +199,9 @@ async function openWithRetry(
|
|
|
212
199
|
}
|
|
213
200
|
|
|
214
201
|
/**
|
|
215
|
-
* One archive entry whose body fetch has
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* fully written.
|
|
202
|
+
* One archive entry whose body fetch has been started. `onWriting` runs when
|
|
203
|
+
* the encoder begins consuming it (so progress reflects the entry actually
|
|
204
|
+
* streaming); `onAdded` runs once it's fully written.
|
|
219
205
|
*/
|
|
220
206
|
type PreparedEntry = {
|
|
221
207
|
name: string;
|
|
@@ -225,93 +211,12 @@ type PreparedEntry = {
|
|
|
225
211
|
};
|
|
226
212
|
|
|
227
213
|
/**
|
|
228
|
-
* A thunk that
|
|
229
|
-
*
|
|
214
|
+
* A thunk that fetches one entry (presigned URL + body) and resolves once the
|
|
215
|
+
* body stream is available — not once it's fully downloaded. Called only when
|
|
216
|
+
* the entry is about to be written, so its body never downloads ahead.
|
|
230
217
|
*/
|
|
231
218
|
type EntryThunk = () => Promise<PreparedEntry>;
|
|
232
219
|
|
|
233
|
-
/**
|
|
234
|
-
* Wraps an ordered stream of entry thunks, keeping up to `lookahead` fetches
|
|
235
|
-
* in flight while yielding the prepared entries in their original order. A
|
|
236
|
-
* background producer pulls thunks and starts their fetches as space frees up;
|
|
237
|
-
* the consumer awaits each in turn. Preserves order and backpressure: at most
|
|
238
|
-
* `lookahead` bodies are ever in flight, and the producer parks when the
|
|
239
|
-
* pipeline is full or the source is waiting for more work.
|
|
240
|
-
*/
|
|
241
|
-
async function* prefetchEntries(
|
|
242
|
-
thunks: AsyncIterable<EntryThunk>,
|
|
243
|
-
lookahead: number,
|
|
244
|
-
): AsyncGenerator<PreparedEntry> {
|
|
245
|
-
const pipeline: Promise<PreparedEntry>[] = [];
|
|
246
|
-
const state: {
|
|
247
|
-
done: boolean;
|
|
248
|
-
producerError: unknown;
|
|
249
|
-
// Woken when the producer pushes an entry (or finishes).
|
|
250
|
-
onItem: (() => void) | null;
|
|
251
|
-
// Woken when the consumer frees a pipeline slot.
|
|
252
|
-
onSpace: (() => void) | null;
|
|
253
|
-
} = { done: false, producerError: null, onItem: null, onSpace: null };
|
|
254
|
-
|
|
255
|
-
const wakeItem = () => {
|
|
256
|
-
const w = state.onItem;
|
|
257
|
-
state.onItem = null;
|
|
258
|
-
if (w) w();
|
|
259
|
-
};
|
|
260
|
-
const wakeSpace = () => {
|
|
261
|
-
const w = state.onSpace;
|
|
262
|
-
state.onSpace = null;
|
|
263
|
-
if (w) w();
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// Never rejects: a failure to produce the next thunk (or start its fetch)
|
|
267
|
-
// lands in state.producerError for the consumer to throw in order.
|
|
268
|
-
const producer = (async () => {
|
|
269
|
-
try {
|
|
270
|
-
for await (const thunk of thunks) {
|
|
271
|
-
while (pipeline.length >= lookahead) {
|
|
272
|
-
await new Promise<void>((resolve) => {
|
|
273
|
-
state.onSpace = resolve;
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
const started = thunk();
|
|
277
|
-
// The consumer awaits `started` in order; attach a no-op catch so a
|
|
278
|
-
// fetch that rejects before then isn't reported as unhandled.
|
|
279
|
-
started.catch(() => {});
|
|
280
|
-
pipeline.push(started);
|
|
281
|
-
wakeItem();
|
|
282
|
-
}
|
|
283
|
-
} catch (e) {
|
|
284
|
-
state.producerError = e;
|
|
285
|
-
} finally {
|
|
286
|
-
state.done = true;
|
|
287
|
-
wakeItem();
|
|
288
|
-
}
|
|
289
|
-
})();
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
while (true) {
|
|
293
|
-
if (pipeline.length === 0) {
|
|
294
|
-
if (state.done) {
|
|
295
|
-
if (state.producerError) throw state.producerError;
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
await new Promise<void>((resolve) => {
|
|
299
|
-
state.onItem = resolve;
|
|
300
|
-
});
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
const entry = await pipeline.shift()!;
|
|
304
|
-
wakeSpace();
|
|
305
|
-
yield entry;
|
|
306
|
-
}
|
|
307
|
-
} finally {
|
|
308
|
-
// On early exit (abort/error), let the producer unwind — its own signal
|
|
309
|
-
// teardown resolves the source's waits — without blocking here.
|
|
310
|
-
wakeSpace();
|
|
311
|
-
producer.catch(() => {});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
220
|
/**
|
|
316
221
|
* Downloads a backup into a single archive written to `opts.sink`: entries
|
|
317
222
|
* in the canonical restore order (`config.json`, then the
|
|
@@ -459,9 +364,9 @@ export async function downloadBackupArchive(
|
|
|
459
364
|
// entities/*.jsonl shards, then files/<locationId>. In particular ALL entity
|
|
460
365
|
// files must be written before ANY storage file. listFiles returns the
|
|
461
366
|
// entity files in write order; this generator yields thunks for them in
|
|
462
|
-
// order, then drains the storage queue. The
|
|
463
|
-
//
|
|
464
|
-
//
|
|
367
|
+
// order, then drains the storage queue. The consumer calls each thunk in turn
|
|
368
|
+
// and writes it to completion before the next, so bodies download one at a
|
|
369
|
+
// time and never ahead of the encoder.
|
|
465
370
|
const thunks = (async function* (): AsyncGenerator<EntryThunk> {
|
|
466
371
|
const files = await manager.listFiles(backup.id, { signal });
|
|
467
372
|
if (files.length === 0) {
|
|
@@ -562,9 +467,6 @@ export async function downloadBackupArchive(
|
|
|
562
467
|
if (storageError) throw storageError;
|
|
563
468
|
})();
|
|
564
469
|
|
|
565
|
-
const prefetch = finitePositiveInt(opts.prefetch, DEFAULT_PREFETCH);
|
|
566
|
-
const entries = prefetchEntries(thunks, prefetch);
|
|
567
|
-
|
|
568
470
|
const sinkWriter = opts.sink.getWriter();
|
|
569
471
|
try {
|
|
570
472
|
// Sink the archive encoder writes into: it tallies the encoded size for
|
|
@@ -587,7 +489,9 @@ export async function downloadBackupArchive(
|
|
|
587
489
|
});
|
|
588
490
|
|
|
589
491
|
const writer = await createWriter(countingSink, signal);
|
|
590
|
-
|
|
492
|
+
|
|
493
|
+
for await (const thunk of thunks) {
|
|
494
|
+
const entry = await thunk();
|
|
591
495
|
entry.onWriting();
|
|
592
496
|
await writer.add(entry.name, entry.input, {
|
|
593
497
|
lastModDate: backup.backupAt,
|