@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instantdb/platform",
3
- "version": "1.0.65",
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.65",
59
- "@instantdb/version": "1.0.65",
60
- "@instantdb/webhooks": "1.0.65"
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",
@@ -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). Prefetching keeps body connections open, paused,
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 the shape of a prefetched connection reset while it
176
- * sat idle is re-fetched too, since nothing has been written to the archive
177
- * yet. Only failures once bytes are flowing are treated as unrecoverable.
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 already been started. `onWriting`
216
- * runs when the encoder begins consuming it (so progress reflects the entry
217
- * actually streaming, not one prefetched ahead); `onAdded` runs once it's
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 starts fetching one entry (presigned URL + body) and resolves
229
- * once the body stream is available — not once it's fully downloaded.
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 thunks are consumed through
463
- // prefetchEntries, which starts a bounded number of the fetches ahead of the
464
- // encoder while keeping this order and one-at-a-time writing.
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
- for await (const entry of entries) {
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,