@feathq/web-sdk 0.3.0 → 0.4.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/dist/index.cjs +72 -2
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +72 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -287,6 +287,10 @@ var DatafileStream = class {
|
|
|
287
287
|
const datafile = parsePut(ev.data);
|
|
288
288
|
if (datafile) this.options.onPut(datafile);
|
|
289
289
|
});
|
|
290
|
+
source.addEventListener("patch", (ev) => {
|
|
291
|
+
const patch = parsePatch(ev.data);
|
|
292
|
+
if (patch) this.options.onPatch(patch);
|
|
293
|
+
});
|
|
290
294
|
source.addEventListener("error", () => {
|
|
291
295
|
if (!this.warned) {
|
|
292
296
|
this.warned = true;
|
|
@@ -313,9 +317,37 @@ function parsePut(data) {
|
|
|
313
317
|
}
|
|
314
318
|
return null;
|
|
315
319
|
}
|
|
320
|
+
function parsePatch(data) {
|
|
321
|
+
if (typeof data !== "string") return null;
|
|
322
|
+
try {
|
|
323
|
+
const p = JSON.parse(data);
|
|
324
|
+
if (!p || typeof p !== "object") return null;
|
|
325
|
+
if (!Number.isInteger(p.from) || !Number.isInteger(p.to)) return null;
|
|
326
|
+
if (p.to <= p.from) return null;
|
|
327
|
+
if (typeof p.etag !== "string" || typeof p.generatedAt !== "string") return null;
|
|
328
|
+
return {
|
|
329
|
+
from: p.from,
|
|
330
|
+
to: p.to,
|
|
331
|
+
etag: p.etag,
|
|
332
|
+
generatedAt: p.generatedAt,
|
|
333
|
+
flags: isRecord(p.flags) ? p.flags : {},
|
|
334
|
+
removedFlags: isStringArray(p.removedFlags) ? p.removedFlags : [],
|
|
335
|
+
segments: isRecord(p.segments) ? p.segments : {},
|
|
336
|
+
removedSegments: isStringArray(p.removedSegments) ? p.removedSegments : []
|
|
337
|
+
};
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
function isRecord(v) {
|
|
343
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
344
|
+
}
|
|
345
|
+
function isStringArray(v) {
|
|
346
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
347
|
+
}
|
|
316
348
|
|
|
317
349
|
// src/version.ts
|
|
318
|
-
var SDK_VERSION = "0.
|
|
350
|
+
var SDK_VERSION = "0.4.0";
|
|
319
351
|
|
|
320
352
|
// src/client.ts
|
|
321
353
|
var CLIENT_SIDE_PREFIX = "feat_cs_";
|
|
@@ -574,6 +606,34 @@ var FeatWebClient = class {
|
|
|
574
606
|
if (publish) this.broadcast?.publish(datafile, etag);
|
|
575
607
|
await this.recomputeCache();
|
|
576
608
|
}
|
|
609
|
+
// Apply an incremental SSE `patch`. Version-gated and atomic: a patch is only
|
|
610
|
+
// adopted when its `from` exactly matches the version we currently hold, so a
|
|
611
|
+
// missed intermediate patch (a gap) or a duplicate is ignored rather than
|
|
612
|
+
// applied out of order. On a match we merge the changed flags/segments, drop
|
|
613
|
+
// the removed keys, and advance version/etag/generatedAt to the patch's `to`,
|
|
614
|
+
// then hand the rebuilt datafile to adoptDatafile - the single version-ordered
|
|
615
|
+
// adopt path - so the cache recompute, etag write, cache save, and sibling-tab
|
|
616
|
+
// broadcast all go through the same code as a full `put` (no drift, no HTTP
|
|
617
|
+
// refetch). adoptDatafile's `version <= held` guard is satisfied because the
|
|
618
|
+
// wire invariant `to > from` holds and `from` equals the held version.
|
|
619
|
+
async applyPatch(patch) {
|
|
620
|
+
if (this.closed) return;
|
|
621
|
+
const current = this.datafile;
|
|
622
|
+
if (!current || current.version !== patch.from) return;
|
|
623
|
+
const flags = { ...current.flags, ...patch.flags };
|
|
624
|
+
for (const key of patch.removedFlags) delete flags[key];
|
|
625
|
+
const segments = { ...current.segments, ...patch.segments };
|
|
626
|
+
for (const key of patch.removedSegments) delete segments[key];
|
|
627
|
+
const next = {
|
|
628
|
+
...current,
|
|
629
|
+
flags,
|
|
630
|
+
segments,
|
|
631
|
+
version: patch.to,
|
|
632
|
+
etag: patch.etag,
|
|
633
|
+
generatedAt: patch.generatedAt
|
|
634
|
+
};
|
|
635
|
+
return this.adoptDatafile(next, patch.etag, true);
|
|
636
|
+
}
|
|
577
637
|
// Reconcile the SSE connection with the current streaming policy. Called
|
|
578
638
|
// whenever an input to that policy changes: ready, a `change` (un)subscribe,
|
|
579
639
|
// or close.
|
|
@@ -614,6 +674,16 @@ var FeatWebClient = class {
|
|
|
614
674
|
void this.adoptDatafile(datafile, datafile.etag, true).catch((err) => {
|
|
615
675
|
console.warn("feat: streamed datafile update failed:", messageOf(err));
|
|
616
676
|
});
|
|
677
|
+
},
|
|
678
|
+
// A `patch` carries an incremental delta. Apply it only when it builds
|
|
679
|
+
// on the version we currently hold; a pure version gap is ignored. The
|
|
680
|
+
// SSE connection stays healthy in that case and does NOT reconnect or
|
|
681
|
+
// re-seed, so recovery comes solely from the safety-net poll. Reconnect
|
|
682
|
+
// re-seeding (a fresh full `put`) only happens on an actual stream drop.
|
|
683
|
+
onPatch: (patch) => {
|
|
684
|
+
void this.applyPatch(patch).catch((err) => {
|
|
685
|
+
console.warn("feat: streamed datafile patch failed:", messageOf(err));
|
|
686
|
+
});
|
|
617
687
|
}
|
|
618
688
|
});
|
|
619
689
|
}
|
|
@@ -685,7 +755,7 @@ function assertHttpsUrl(url) {
|
|
|
685
755
|
}
|
|
686
756
|
|
|
687
757
|
// src/index.ts
|
|
688
|
-
var SDK_VERSION2 = "0.
|
|
758
|
+
var SDK_VERSION2 = "0.4.0";
|
|
689
759
|
|
|
690
760
|
exports.FeatWebClient = FeatWebClient;
|
|
691
761
|
exports.SDK_VERSION = SDK_VERSION2;
|
package/dist/index.d.cts
CHANGED
|
@@ -92,12 +92,13 @@ declare class FeatWebClient {
|
|
|
92
92
|
private fetchDatafile;
|
|
93
93
|
private adoptFromBroadcast;
|
|
94
94
|
private adoptDatafile;
|
|
95
|
+
private applyPatch;
|
|
95
96
|
private maybeUpdateStream;
|
|
96
97
|
private wantsStream;
|
|
97
98
|
private openStream;
|
|
98
99
|
private recomputeCache;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
declare const SDK_VERSION = "0.
|
|
102
|
+
declare const SDK_VERSION = "0.4.0";
|
|
102
103
|
|
|
103
104
|
export { type ChangeEvent, type EventSourceConstructor, FeatWebClient, type FeatWebClientConfig, type FlagEventMap, SDK_VERSION };
|
package/dist/index.d.ts
CHANGED
|
@@ -92,12 +92,13 @@ declare class FeatWebClient {
|
|
|
92
92
|
private fetchDatafile;
|
|
93
93
|
private adoptFromBroadcast;
|
|
94
94
|
private adoptDatafile;
|
|
95
|
+
private applyPatch;
|
|
95
96
|
private maybeUpdateStream;
|
|
96
97
|
private wantsStream;
|
|
97
98
|
private openStream;
|
|
98
99
|
private recomputeCache;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
declare const SDK_VERSION = "0.
|
|
102
|
+
declare const SDK_VERSION = "0.4.0";
|
|
102
103
|
|
|
103
104
|
export { type ChangeEvent, type EventSourceConstructor, FeatWebClient, type FeatWebClientConfig, type FlagEventMap, SDK_VERSION };
|
package/dist/index.js
CHANGED
|
@@ -285,6 +285,10 @@ var DatafileStream = class {
|
|
|
285
285
|
const datafile = parsePut(ev.data);
|
|
286
286
|
if (datafile) this.options.onPut(datafile);
|
|
287
287
|
});
|
|
288
|
+
source.addEventListener("patch", (ev) => {
|
|
289
|
+
const patch = parsePatch(ev.data);
|
|
290
|
+
if (patch) this.options.onPatch(patch);
|
|
291
|
+
});
|
|
288
292
|
source.addEventListener("error", () => {
|
|
289
293
|
if (!this.warned) {
|
|
290
294
|
this.warned = true;
|
|
@@ -311,9 +315,37 @@ function parsePut(data) {
|
|
|
311
315
|
}
|
|
312
316
|
return null;
|
|
313
317
|
}
|
|
318
|
+
function parsePatch(data) {
|
|
319
|
+
if (typeof data !== "string") return null;
|
|
320
|
+
try {
|
|
321
|
+
const p = JSON.parse(data);
|
|
322
|
+
if (!p || typeof p !== "object") return null;
|
|
323
|
+
if (!Number.isInteger(p.from) || !Number.isInteger(p.to)) return null;
|
|
324
|
+
if (p.to <= p.from) return null;
|
|
325
|
+
if (typeof p.etag !== "string" || typeof p.generatedAt !== "string") return null;
|
|
326
|
+
return {
|
|
327
|
+
from: p.from,
|
|
328
|
+
to: p.to,
|
|
329
|
+
etag: p.etag,
|
|
330
|
+
generatedAt: p.generatedAt,
|
|
331
|
+
flags: isRecord(p.flags) ? p.flags : {},
|
|
332
|
+
removedFlags: isStringArray(p.removedFlags) ? p.removedFlags : [],
|
|
333
|
+
segments: isRecord(p.segments) ? p.segments : {},
|
|
334
|
+
removedSegments: isStringArray(p.removedSegments) ? p.removedSegments : []
|
|
335
|
+
};
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
function isRecord(v) {
|
|
341
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
342
|
+
}
|
|
343
|
+
function isStringArray(v) {
|
|
344
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
345
|
+
}
|
|
314
346
|
|
|
315
347
|
// src/version.ts
|
|
316
|
-
var SDK_VERSION = "0.
|
|
348
|
+
var SDK_VERSION = "0.4.0";
|
|
317
349
|
|
|
318
350
|
// src/client.ts
|
|
319
351
|
var CLIENT_SIDE_PREFIX = "feat_cs_";
|
|
@@ -572,6 +604,34 @@ var FeatWebClient = class {
|
|
|
572
604
|
if (publish) this.broadcast?.publish(datafile, etag);
|
|
573
605
|
await this.recomputeCache();
|
|
574
606
|
}
|
|
607
|
+
// Apply an incremental SSE `patch`. Version-gated and atomic: a patch is only
|
|
608
|
+
// adopted when its `from` exactly matches the version we currently hold, so a
|
|
609
|
+
// missed intermediate patch (a gap) or a duplicate is ignored rather than
|
|
610
|
+
// applied out of order. On a match we merge the changed flags/segments, drop
|
|
611
|
+
// the removed keys, and advance version/etag/generatedAt to the patch's `to`,
|
|
612
|
+
// then hand the rebuilt datafile to adoptDatafile - the single version-ordered
|
|
613
|
+
// adopt path - so the cache recompute, etag write, cache save, and sibling-tab
|
|
614
|
+
// broadcast all go through the same code as a full `put` (no drift, no HTTP
|
|
615
|
+
// refetch). adoptDatafile's `version <= held` guard is satisfied because the
|
|
616
|
+
// wire invariant `to > from` holds and `from` equals the held version.
|
|
617
|
+
async applyPatch(patch) {
|
|
618
|
+
if (this.closed) return;
|
|
619
|
+
const current = this.datafile;
|
|
620
|
+
if (!current || current.version !== patch.from) return;
|
|
621
|
+
const flags = { ...current.flags, ...patch.flags };
|
|
622
|
+
for (const key of patch.removedFlags) delete flags[key];
|
|
623
|
+
const segments = { ...current.segments, ...patch.segments };
|
|
624
|
+
for (const key of patch.removedSegments) delete segments[key];
|
|
625
|
+
const next = {
|
|
626
|
+
...current,
|
|
627
|
+
flags,
|
|
628
|
+
segments,
|
|
629
|
+
version: patch.to,
|
|
630
|
+
etag: patch.etag,
|
|
631
|
+
generatedAt: patch.generatedAt
|
|
632
|
+
};
|
|
633
|
+
return this.adoptDatafile(next, patch.etag, true);
|
|
634
|
+
}
|
|
575
635
|
// Reconcile the SSE connection with the current streaming policy. Called
|
|
576
636
|
// whenever an input to that policy changes: ready, a `change` (un)subscribe,
|
|
577
637
|
// or close.
|
|
@@ -612,6 +672,16 @@ var FeatWebClient = class {
|
|
|
612
672
|
void this.adoptDatafile(datafile, datafile.etag, true).catch((err) => {
|
|
613
673
|
console.warn("feat: streamed datafile update failed:", messageOf(err));
|
|
614
674
|
});
|
|
675
|
+
},
|
|
676
|
+
// A `patch` carries an incremental delta. Apply it only when it builds
|
|
677
|
+
// on the version we currently hold; a pure version gap is ignored. The
|
|
678
|
+
// SSE connection stays healthy in that case and does NOT reconnect or
|
|
679
|
+
// re-seed, so recovery comes solely from the safety-net poll. Reconnect
|
|
680
|
+
// re-seeding (a fresh full `put`) only happens on an actual stream drop.
|
|
681
|
+
onPatch: (patch) => {
|
|
682
|
+
void this.applyPatch(patch).catch((err) => {
|
|
683
|
+
console.warn("feat: streamed datafile patch failed:", messageOf(err));
|
|
684
|
+
});
|
|
615
685
|
}
|
|
616
686
|
});
|
|
617
687
|
}
|
|
@@ -683,6 +753,6 @@ function assertHttpsUrl(url) {
|
|
|
683
753
|
}
|
|
684
754
|
|
|
685
755
|
// src/index.ts
|
|
686
|
-
var SDK_VERSION2 = "0.
|
|
756
|
+
var SDK_VERSION2 = "0.4.0";
|
|
687
757
|
|
|
688
758
|
export { FeatWebClient, SDK_VERSION2 as SDK_VERSION };
|
package/package.json
CHANGED