@pylonsync/sync 0.3.227 → 0.3.229

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.
@@ -196,6 +196,42 @@ export class TestServer {
196
196
  return s;
197
197
  }
198
198
 
199
+ /** Inject a one-shot outcome for the NEXT push: a `network` failure
200
+ * (the fetch rejects with no status — simulating offline / connection
201
+ * reset) or an HTTP `status` (e.g. 403 permanent rejection, 503
202
+ * transient). Lets tests exercise the transient-vs-permanent
203
+ * classification in pushInner. */
204
+ private nextPushOutcome:
205
+ | { kind: "network" }
206
+ | { kind: "status"; status: number }
207
+ | null = null;
208
+ primeNextPushOutcome(
209
+ o: { kind: "network" } | { kind: "status"; status: number },
210
+ ): void {
211
+ this.nextPushOutcome = o;
212
+ }
213
+ consumeNextPushOutcome():
214
+ | { kind: "network" }
215
+ | { kind: "status"; status: number }
216
+ | null {
217
+ const o = this.nextPushOutcome;
218
+ this.nextPushOutcome = null;
219
+ return o;
220
+ }
221
+
222
+ /** Make the NEXT delta pull report `has_more: true` once, exercising
223
+ * the change-log tail-pull recursion in pullInner (the path that
224
+ * self-deadlocked before the pullInner() fix). */
225
+ private nextPullHasMore = false;
226
+ primeNextPullHasMore(): void {
227
+ this.nextPullHasMore = true;
228
+ }
229
+ consumeNextPullHasMore(): boolean {
230
+ const v = this.nextPullHasMore;
231
+ this.nextPullHasMore = false;
232
+ return v;
233
+ }
234
+
199
235
  // ---- Entity data --------------------------------------------------------
200
236
 
201
237
  /** Bulk-seed rows for an entity AND emit insert events into the
@@ -259,6 +259,10 @@ async function handle(
259
259
  };
260
260
  }
261
261
  const resp = await server.pull(token, since);
262
+ // One-shot has_more on a delta pull → drives the tail-pull recursion.
263
+ if (since > 0 && server.consumeNextPullHasMore()) {
264
+ return { status: 200, body: { ...resp, has_more: true } };
265
+ }
262
266
  return { status: 200, body: resp };
263
267
  }
264
268
 
@@ -275,6 +279,18 @@ async function handle(
275
279
 
276
280
  // /api/sync/push — accept ops from optimistic mutations.
277
281
  if (url.endsWith("/api/sync/push") && method === "POST") {
282
+ const outcome = server.consumeNextPushOutcome();
283
+ if (outcome?.kind === "network") {
284
+ // Reject like a real offline fetch: no HTTP status → the engine
285
+ // classifies it as TRANSIENT (keep pending, retry).
286
+ throw new Error("simulated network failure (offline)");
287
+ }
288
+ if (outcome?.kind === "status") {
289
+ return {
290
+ status: outcome.status,
291
+ body: { error: { code: "PUSH_REJECTED" } },
292
+ };
293
+ }
278
294
  return { status: 200, body: { ops: [] } };
279
295
  }
280
296