@zeronsh/orbit 0.3.3 → 0.3.5
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/{chunk-BJPEQCCN.js → chunk-B575ZNM2.js} +140 -22
- package/dist/chunk-B575ZNM2.js.map +1 -0
- package/dist/{chunk-O43HYETA.js → chunk-K4VPAXH7.js} +3 -3
- package/dist/{chunk-O43HYETA.js.map → chunk-K4VPAXH7.js.map} +1 -1
- package/dist/{chunk-77GV6UT3.js → chunk-ZYDDINVC.js} +3 -3
- package/dist/{chunk-77GV6UT3.js.map → chunk-ZYDDINVC.js.map} +1 -1
- package/dist/client.d.ts +28 -0
- package/dist/client.js +1 -1
- package/dist/drizzle/cli/bin.js +3 -3
- package/dist/drizzle/cli.js +3 -3
- package/dist/drizzle.js +2 -2
- package/dist/orm-core.js +2 -2
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-BJPEQCCN.js.map +0 -1
|
@@ -407,7 +407,7 @@ var Store = class {
|
|
|
407
407
|
// persistence
|
|
408
408
|
#kv;
|
|
409
409
|
#dirtyRows = /* @__PURE__ */ new Set();
|
|
410
|
-
//
|
|
410
|
+
// `${table}\u0000${pkKey}` (NUL separator: cannot appear in a table name)
|
|
411
411
|
#dirtyPending = /* @__PURE__ */ new Set();
|
|
412
412
|
#cleared = false;
|
|
413
413
|
#flushTimer;
|
|
@@ -521,6 +521,20 @@ var Store = class {
|
|
|
521
521
|
pendingMutations() {
|
|
522
522
|
return this.#pending.map((p) => p.mutation).filter((m) => m !== void 0);
|
|
523
523
|
}
|
|
524
|
+
/** Drop a single pending mutation by id — e.g. an undeliverable "poison" mutation
|
|
525
|
+
* the server keeps rejecting (oversized frame). Removes its optimistic overlay and,
|
|
526
|
+
* on the next flush, its persisted `p/` entry, so it is neither replayed on reconnect
|
|
527
|
+
* nor restored on reload. Returns whether such a mutation was present. */
|
|
528
|
+
dropPending(id) {
|
|
529
|
+
const idx = this.#pending.findIndex((p2) => p2.id === id);
|
|
530
|
+
if (idx === -1) return false;
|
|
531
|
+
const [p] = this.#pending.splice(idx, 1);
|
|
532
|
+
this.#dirtyPending.add(p.id);
|
|
533
|
+
for (const op of p.ops) this.#touch(op.tableName, this.#key(op.tableName, op.value));
|
|
534
|
+
this.#scheduleFlush();
|
|
535
|
+
this.#notify();
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
524
538
|
/** All rows for `table` with the optimistic overlay applied. */
|
|
525
539
|
effectiveRows(table2) {
|
|
526
540
|
const merged = new Map(this.#table(table2));
|
|
@@ -592,29 +606,36 @@ var Store = class {
|
|
|
592
606
|
}
|
|
593
607
|
const kv = this.#kv;
|
|
594
608
|
if (!kv) return;
|
|
609
|
+
const clearOps = [];
|
|
595
610
|
if (this.#cleared) {
|
|
596
|
-
const
|
|
597
|
-
for (const [k] of await kv.entries("e/")) dels.push(kv.del(k));
|
|
598
|
-
await Promise.all(dels);
|
|
611
|
+
for (const [k] of await kv.entries("e/")) clearOps.push({ type: "del", key: k });
|
|
599
612
|
this.#cleared = false;
|
|
600
613
|
}
|
|
601
|
-
const
|
|
614
|
+
const ops = [];
|
|
602
615
|
for (const dk of this.#dirtyRows) {
|
|
603
616
|
const sep = dk.indexOf("\0");
|
|
604
617
|
const table2 = dk.slice(0, sep);
|
|
605
618
|
const pkKey2 = dk.slice(sep + 1);
|
|
606
619
|
const row = this.#tables.get(table2)?.get(pkKey2);
|
|
607
620
|
const key = `e/${table2}/${pkKey2}`;
|
|
608
|
-
|
|
621
|
+
ops.push(row ? { type: "set", key, value: { t: table2, k: pkKey2, v: row } } : { type: "del", key });
|
|
609
622
|
}
|
|
610
623
|
this.#dirtyRows.clear();
|
|
611
624
|
for (const id of this.#dirtyPending) {
|
|
612
625
|
const p = this.#pending.find((x) => x.id === id);
|
|
613
|
-
|
|
626
|
+
ops.push(p ? { type: "set", key: `p/${id}`, value: p } : { type: "del", key: `p/${id}` });
|
|
614
627
|
}
|
|
615
628
|
this.#dirtyPending.clear();
|
|
616
|
-
|
|
617
|
-
if (
|
|
629
|
+
const cookieDirty = this.#cookieDirty && this.#cookie !== void 0;
|
|
630
|
+
if (kv.batch) {
|
|
631
|
+
if (cookieDirty) ops.push({ type: "set", key: "cookie", value: this.#cookie });
|
|
632
|
+
await kv.batch([...clearOps, ...ops]);
|
|
633
|
+
if (cookieDirty) this.#cookieDirty = false;
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
await Promise.all(clearOps.map((o) => kv.del(o.key)));
|
|
637
|
+
await Promise.all(ops.map((o) => o.type === "set" ? kv.set(o.key, o.value) : kv.del(o.key)));
|
|
638
|
+
if (cookieDirty) {
|
|
618
639
|
await kv.set("cookie", this.#cookie);
|
|
619
640
|
this.#cookieDirty = false;
|
|
620
641
|
}
|
|
@@ -638,6 +659,22 @@ function typeRank(v) {
|
|
|
638
659
|
return 4;
|
|
639
660
|
}
|
|
640
661
|
}
|
|
662
|
+
function compareStringsUtf8(a, b) {
|
|
663
|
+
if (a === b) return 0;
|
|
664
|
+
const ia = a[Symbol.iterator]();
|
|
665
|
+
const ib = b[Symbol.iterator]();
|
|
666
|
+
for (; ; ) {
|
|
667
|
+
const na = ia.next();
|
|
668
|
+
const nb = ib.next();
|
|
669
|
+
if (na.done) return nb.done ? 0 : -1;
|
|
670
|
+
if (nb.done) return 1;
|
|
671
|
+
if (na.value !== nb.value) {
|
|
672
|
+
const ca = na.value.codePointAt(0);
|
|
673
|
+
const cb = nb.value.codePointAt(0);
|
|
674
|
+
if (ca !== cb) return ca < cb ? -1 : 1;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
641
678
|
function compareValues(a, b) {
|
|
642
679
|
if (isNull(a) && isNull(b)) return 0;
|
|
643
680
|
if (isNull(a)) return -1;
|
|
@@ -645,7 +682,7 @@ function compareValues(a, b) {
|
|
|
645
682
|
if (typeof a === typeof b) {
|
|
646
683
|
if (typeof a === "number") return a < b ? -1 : a > b ? 1 : 0;
|
|
647
684
|
if (typeof a === "boolean") return a === b ? 0 : a ? 1 : -1;
|
|
648
|
-
if (typeof a === "string") return a
|
|
685
|
+
if (typeof a === "string") return compareStringsUtf8(a, b);
|
|
649
686
|
return 0;
|
|
650
687
|
}
|
|
651
688
|
return typeRank(a) - typeRank(b);
|
|
@@ -1679,6 +1716,8 @@ var Orbit = class {
|
|
|
1679
1716
|
#onError;
|
|
1680
1717
|
/** Whether the id was supplied explicitly (then we never override it from the KV). */
|
|
1681
1718
|
#idFromOpts;
|
|
1719
|
+
/** Releases the Web-Locks persistence leadership (held by the first tab). */
|
|
1720
|
+
#releasePersistLock;
|
|
1682
1721
|
constructor(opts) {
|
|
1683
1722
|
this.#opts = opts;
|
|
1684
1723
|
this.#schema = opts.schema;
|
|
@@ -1797,6 +1836,7 @@ var Orbit = class {
|
|
|
1797
1836
|
close() {
|
|
1798
1837
|
this.#closed = true;
|
|
1799
1838
|
this.#ws?.close();
|
|
1839
|
+
this.#releasePersistLock?.();
|
|
1800
1840
|
}
|
|
1801
1841
|
// --- internals ----------------------------------------------------------
|
|
1802
1842
|
/** Resolve the client context (a value or sync getter); `{}` when unset. */
|
|
@@ -1804,8 +1844,39 @@ var Orbit = class {
|
|
|
1804
1844
|
const c = this.#context;
|
|
1805
1845
|
return typeof c === "function" ? c() : c ?? {};
|
|
1806
1846
|
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Web-Locks single-writer election for the persisted cache. Two tabs sharing one
|
|
1849
|
+
* IndexedDB (and the restored clientID) would each flush their own dirty rows,
|
|
1850
|
+
* pending mutations, and cookie into the same flat keyspace with no coordination —
|
|
1851
|
+
* last-writer-wins can persist a cookie covering rows only the *other* tab wrote,
|
|
1852
|
+
* and both tabs would drive the same server-side CVR. Only the first tab (the
|
|
1853
|
+
* lock holder) gets persistence; later tabs run memory-only with their own fresh
|
|
1854
|
+
* clientID (a full server sync — correct, just uncached). The lock auto-releases
|
|
1855
|
+
* when the tab dies, so the next reload elects a new leader. Environments without
|
|
1856
|
+
* Web Locks (Node, tests, old browsers) keep today's behavior.
|
|
1857
|
+
*/
|
|
1858
|
+
async #acquirePersistLock() {
|
|
1859
|
+
const locks = globalThis.navigator?.locks;
|
|
1860
|
+
if (!locks) return true;
|
|
1861
|
+
return new Promise((resolve2) => {
|
|
1862
|
+
const req = locks.request("zeronsh-orbit-persist", { ifAvailable: true }, (lock) => {
|
|
1863
|
+
if (lock === null) {
|
|
1864
|
+
resolve2(false);
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
resolve2(true);
|
|
1868
|
+
return new Promise((release) => {
|
|
1869
|
+
this.#releasePersistLock = release;
|
|
1870
|
+
});
|
|
1871
|
+
});
|
|
1872
|
+
void Promise.resolve(req).catch(() => resolve2(true));
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1807
1875
|
/** Hydrate persisted state (if any), restore unconfirmed mutations, then connect. */
|
|
1808
1876
|
async #init() {
|
|
1877
|
+
if (this.#kv && !await this.#acquirePersistLock()) {
|
|
1878
|
+
this.#kv = void 0;
|
|
1879
|
+
}
|
|
1809
1880
|
if (this.#kv) {
|
|
1810
1881
|
try {
|
|
1811
1882
|
await this.#store.hydrate(this.#kv);
|
|
@@ -1851,8 +1922,13 @@ var Orbit = class {
|
|
|
1851
1922
|
requestID: Math.random().toString(36).slice(2)
|
|
1852
1923
|
}];
|
|
1853
1924
|
this.#unconfirmedPushes.set(id, msg);
|
|
1854
|
-
|
|
1855
|
-
|
|
1925
|
+
const kv = this.#kv;
|
|
1926
|
+
if (kv) {
|
|
1927
|
+
void kv.set("nextMutationID", this.#nextMutationID).catch(() => {
|
|
1928
|
+
}).then(() => this.#send(msg));
|
|
1929
|
+
} else {
|
|
1930
|
+
this.#send(msg);
|
|
1931
|
+
}
|
|
1856
1932
|
}
|
|
1857
1933
|
async #connect() {
|
|
1858
1934
|
if (this.#closed || this.#connecting) return;
|
|
@@ -1874,10 +1950,29 @@ var Orbit = class {
|
|
|
1874
1950
|
ws.addEventListener("message", (ev) => {
|
|
1875
1951
|
this.#onMessage(JSON.parse(ev.data));
|
|
1876
1952
|
});
|
|
1877
|
-
ws.addEventListener("close", () => {
|
|
1953
|
+
ws.addEventListener("close", (ev) => {
|
|
1878
1954
|
this.#connecting = false;
|
|
1879
1955
|
if (this.#ws === ws) this.#ws = void 0;
|
|
1880
1956
|
this.#poke = null;
|
|
1957
|
+
if (ev.code === 1009 && this.#unconfirmedPushes.size > 0) {
|
|
1958
|
+
let poisonId;
|
|
1959
|
+
let maxSize = -1;
|
|
1960
|
+
for (const [id, msg] of this.#unconfirmedPushes) {
|
|
1961
|
+
const size = JSON.stringify(msg).length;
|
|
1962
|
+
if (size > maxSize) {
|
|
1963
|
+
maxSize = size;
|
|
1964
|
+
poisonId = id;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
if (poisonId !== void 0) {
|
|
1968
|
+
this.#unconfirmedPushes.delete(poisonId);
|
|
1969
|
+
this.#store.dropPending(poisonId);
|
|
1970
|
+
this.#onError?.({
|
|
1971
|
+
kind: "mutation-too-large",
|
|
1972
|
+
message: `dropped mutation ${poisonId} (~${maxSize} bytes): server closed with code 1009 (message too big)`
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1881
1976
|
this.#scheduleReconnect();
|
|
1882
1977
|
});
|
|
1883
1978
|
ws.addEventListener("error", () => ws.close());
|
|
@@ -1955,6 +2050,12 @@ var MemoryKV = class {
|
|
|
1955
2050
|
async entries(prefix) {
|
|
1956
2051
|
return [...this.#m].filter(([k]) => k.startsWith(prefix));
|
|
1957
2052
|
}
|
|
2053
|
+
async batch(ops) {
|
|
2054
|
+
for (const op of ops) {
|
|
2055
|
+
if (op.type === "set") this.#m.set(op.key, op.value);
|
|
2056
|
+
else this.#m.delete(op.key);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
1958
2059
|
};
|
|
1959
2060
|
function wrap(req) {
|
|
1960
2061
|
return new Promise((resolve2, reject) => {
|
|
@@ -1987,16 +2088,33 @@ var IDBKV = class {
|
|
|
1987
2088
|
}
|
|
1988
2089
|
async entries(prefix) {
|
|
1989
2090
|
const s = await this.#store("readonly");
|
|
1990
|
-
const
|
|
1991
|
-
const
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2091
|
+
const successor = prefix.slice(0, -1) + String.fromCharCode(prefix.charCodeAt(prefix.length - 1) + 1);
|
|
2092
|
+
const range = IDBKeyRange.bound(prefix, successor, false, true);
|
|
2093
|
+
const [keys, vals] = await Promise.all([wrap(s.getAllKeys(range)), wrap(s.getAll(range))]);
|
|
2094
|
+
return keys.map((k, i) => [String(k), vals[i]]);
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* All ops in ONE readwrite transaction: atomic under a crash (IndexedDB
|
|
2098
|
+
* transactions are all-or-nothing) and one commit instead of one per key —
|
|
2099
|
+
* the flush of a large poke goes from N transactions to 1.
|
|
2100
|
+
*/
|
|
2101
|
+
async batch(ops) {
|
|
2102
|
+
if (ops.length === 0) return;
|
|
2103
|
+
const db = await this.#dbp;
|
|
2104
|
+
await new Promise((resolve2, reject) => {
|
|
2105
|
+
const tx = db.transaction("kv", "readwrite");
|
|
2106
|
+
const s = tx.objectStore("kv");
|
|
2107
|
+
for (const op of ops) {
|
|
2108
|
+
if (op.type === "set") s.put(op.value, op.key);
|
|
2109
|
+
else s.delete(op.key);
|
|
2110
|
+
}
|
|
2111
|
+
tx.oncomplete = () => resolve2();
|
|
2112
|
+
tx.onerror = () => reject(tx.error);
|
|
2113
|
+
tx.onabort = () => reject(tx.error);
|
|
2114
|
+
});
|
|
1997
2115
|
}
|
|
1998
2116
|
};
|
|
1999
2117
|
|
|
2000
2118
|
export { IDBKV, MaterializedView, MemoryKV, MemorySource, MemorySourceProvider, Orbit, PROTOCOL_VERSION, Query, QueryManager, SchemaQuery, SourceConnection, Store, StoreProvider, TypedQuery, View, boolean, buildPipeline, buildSchemaQueries, collectOps, compareValues, createBuilder, createOrbitApi, createSchema, defineMutation, defineQuery, evaluate, hashAST, hashString, json, nodeToRow, number, optional, parseTTL, relationships, string, table, tablesOf, unwrapSingular, validateArgs, valuesEqual };
|
|
2001
|
-
//# sourceMappingURL=chunk-
|
|
2002
|
-
//# sourceMappingURL=chunk-
|
|
2119
|
+
//# sourceMappingURL=chunk-B575ZNM2.js.map
|
|
2120
|
+
//# sourceMappingURL=chunk-B575ZNM2.js.map
|