@lunora/client 1.0.0-alpha.28 → 1.0.0-alpha.29
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/auth/index.d.mts +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.mjs +1 -60
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +1 -21
- package/dist/packem_shared/CONFLICT_ERROR_CODE-B2SU0CaJ.mjs +1 -0
- package/dist/packem_shared/ClientServiceWorker-CRQ5yl7-.mjs +1 -0
- package/dist/packem_shared/DEFAULT_MAX_BUFFER-TBn2s7Rz.mjs +1 -0
- package/dist/packem_shared/LunoraClient-DDEoZdgP.mjs +1 -0
- package/dist/packem_shared/OfflineQueue-B603_q8m.mjs +1 -0
- package/dist/packem_shared/SKIP-CArzT3Vl.mjs +1 -0
- package/dist/packem_shared/SubscriptionRegistry-CdrOD7sZ.mjs +1 -0
- package/dist/packem_shared/TabCoordinator-ijLic02x.mjs +1 -0
- package/dist/packem_shared/applyDelta-DXqQORbM.mjs +1 -0
- package/dist/packem_shared/createAsyncStoragePersistence-BRLqeXsC.mjs +1 -0
- package/dist/packem_shared/createClientQuery-CrdRoicO.mjs +1 -0
- package/dist/packem_shared/createInMemoryBookmarkStorage-DCd5Ej-t.mjs +1 -0
- package/dist/packem_shared/createInMemoryPersistence-CQYXy8UP.mjs +1 -0
- package/dist/packem_shared/createInMemoryQueryCache-DjCKrLjk.mjs +1 -0
- package/dist/packem_shared/createLocalStore-B3Tw_Tw1.mjs +1 -0
- package/dist/packem_shared/createMutationRunner-lQXH8Amk.mjs +1 -0
- package/dist/packem_shared/createMutatorRunner-BVzo0FBZ.mjs +1 -0
- package/dist/packem_shared/createReconnect-CFT7YRSR.mjs +1 -0
- package/dist/packem_shared/createReply-DF0jRsv-.mjs +1 -0
- package/dist/packem_shared/createServerClient-B7KqO8tv.mjs +1 -0
- package/dist/packem_shared/createSnapshotPrecondition-DOKRAJ4b.mjs +1 -0
- package/dist/packem_shared/deserializePreloaded-CHfOFpka.mjs +1 -0
- package/dist/packem_shared/getServerSession-CGjgRBAE.mjs +1 -0
- package/dist/packem_shared/httpStream-Cz4_QyjV.mjs +11 -0
- package/dist/packem_shared/idb-utility-C-DR8nG1.mjs +1 -0
- package/dist/packem_shared/local-store-C53xDvEr.mjs +1 -0
- package/dist/packem_shared/{lunora-client.d-B1iZb2Ph.d.mts → lunora-client.d-Bt65hm_E.d.mts} +135 -1
- package/dist/packem_shared/{lunora-client.d-B1iZb2Ph.d.ts → lunora-client.d-Bt65hm_E.d.ts} +135 -1
- package/dist/packem_shared/offline-queue-D73Dq95V.mjs +1 -0
- package/dist/packem_shared/{preload.d-CLwoXl3R.d.mts → preload.d-BmztUzzG.d.mts} +1 -1
- package/dist/packem_shared/{preload.d-Cqb4Kh2I.d.ts → preload.d-DGGRMWB5.d.ts} +1 -1
- package/dist/packem_shared/preloadQuery-DjwSjFwt.mjs +1 -0
- package/dist/packem_shared/wire-key-D_zOxXK4.mjs +1 -0
- package/dist/pagination/index.mjs +1 -61
- package/dist/query/index.d.mts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.mjs +1 -1
- package/dist/ssr/index.d.mts +3 -3
- package/dist/ssr/index.d.ts +3 -3
- package/dist/ssr/index.mjs +1 -4
- package/dist/upload.mjs +1 -24
- package/package.json +1 -1
- package/dist/packem_shared/CONFLICT_ERROR_CODE-B8gQ8tyU.mjs +0 -33
- package/dist/packem_shared/ClientServiceWorker-C3PAFwy0.mjs +0 -100
- package/dist/packem_shared/DEFAULT_MAX_BUFFER-7hFnzNk9.mjs +0 -110
- package/dist/packem_shared/LunoraClient-DKATLGHY.mjs +0 -3631
- package/dist/packem_shared/OfflineQueue-BgarnAub.mjs +0 -1
- package/dist/packem_shared/SKIP-vItZChkw.mjs +0 -50
- package/dist/packem_shared/SubscriptionRegistry-CxS_Inha.mjs +0 -31
- package/dist/packem_shared/TabCoordinator-D_5oNTTt.mjs +0 -258
- package/dist/packem_shared/applyDelta-CRKZ1PBt.mjs +0 -81
- package/dist/packem_shared/createAsyncStoragePersistence-1Z5BZ8RC.mjs +0 -45
- package/dist/packem_shared/createClientQuery-dJZg1ohm.mjs +0 -80
- package/dist/packem_shared/createInMemoryBookmarkStorage-BoN7a7TH.mjs +0 -11
- package/dist/packem_shared/createInMemoryPersistence-DZ2VHWgm.mjs +0 -79
- package/dist/packem_shared/createInMemoryQueryCache-DiaGZkA2.mjs +0 -112
- package/dist/packem_shared/createLocalStore-BtqUmOQA.mjs +0 -2
- package/dist/packem_shared/createMutationRunner-BqsavzvG.mjs +0 -21
- package/dist/packem_shared/createMutatorRunner-BETvCd0p.mjs +0 -31
- package/dist/packem_shared/createReconnect-Di_-oHH7.mjs +0 -22
- package/dist/packem_shared/createReply-lI4tVS2w.mjs +0 -36
- package/dist/packem_shared/createServerClient-Bp2r9tcB.mjs +0 -11
- package/dist/packem_shared/createSnapshotPrecondition-CxQ1T4ZP.mjs +0 -18
- package/dist/packem_shared/deserializePreloaded-C0eJTY_W.mjs +0 -4
- package/dist/packem_shared/getServerSession-8jXewqxd.mjs +0 -13
- package/dist/packem_shared/httpStream-DIdL8NEw.mjs +0 -168
- package/dist/packem_shared/idb-utility-DrSVX43Q.mjs +0 -48
- package/dist/packem_shared/local-store-DIq-UWfD.mjs +0 -106
- package/dist/packem_shared/offline-queue-N-1JvYb4.mjs +0 -211
- package/dist/packem_shared/preloadQuery-lobFkD2Z.mjs +0 -13
- package/dist/packem_shared/wire-key-Djie6aaR.mjs +0 -266
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
const isStaleVersion = (current, stamped) => current !== void 0 && stamped !== current;
|
|
2
|
-
|
|
3
|
-
let idCounter = 0;
|
|
4
|
-
const nextId = () => {
|
|
5
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
6
|
-
return crypto.randomUUID();
|
|
7
|
-
}
|
|
8
|
-
idCounter += 1;
|
|
9
|
-
const entropy = typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function" ? [...crypto.getRandomValues(new Uint8Array(8))].map((byte) => byte.toString(16).padStart(2, "0")).join("") : (
|
|
10
|
-
// eslint-disable-next-line sonarjs/pseudo-random -- non-cryptographic uniqueness entropy, not a security token; only reached when neither crypto.randomUUID nor crypto.getRandomValues exists
|
|
11
|
-
Math.random().toString(16).slice(2, 12)
|
|
12
|
-
);
|
|
13
|
-
return `m_${Date.now().toString(36)}_${idCounter.toString(36)}_${entropy}`;
|
|
14
|
-
};
|
|
15
|
-
const reportPersistenceError = (handler, operation, error, mutationId) => {
|
|
16
|
-
if (handler) {
|
|
17
|
-
handler({ error, mutationId, operation });
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
console.warn(`[lunora] offline-queue persistence ${operation} failed`, error);
|
|
21
|
-
};
|
|
22
|
-
class OfflineQueue {
|
|
23
|
-
/** Opt-in to queueing mutations before the targeted shard's first connect. */
|
|
24
|
-
queueBeforeFirstConnect;
|
|
25
|
-
maxItems;
|
|
26
|
-
onPersistenceError;
|
|
27
|
-
persistence;
|
|
28
|
-
onEvict;
|
|
29
|
-
onSizeChange;
|
|
30
|
-
/** App/schema version stamped on persisted writes; mismatched records are purged on hydrate. */
|
|
31
|
-
version;
|
|
32
|
-
items = [];
|
|
33
|
-
constructor(options = {}, deps = {}) {
|
|
34
|
-
this.maxItems = options.maxItems ?? 1e3;
|
|
35
|
-
this.queueBeforeFirstConnect = options.queueBeforeFirstConnect ?? false;
|
|
36
|
-
this.onPersistenceError = options.onPersistenceError;
|
|
37
|
-
this.persistence = deps.persistence;
|
|
38
|
-
this.onEvict = deps.onEvict;
|
|
39
|
-
this.onSizeChange = deps.onSizeChange;
|
|
40
|
-
this.version = deps.version;
|
|
41
|
-
}
|
|
42
|
-
get size() {
|
|
43
|
-
return this.items.length;
|
|
44
|
-
}
|
|
45
|
-
enqueue(entry) {
|
|
46
|
-
const item = entry;
|
|
47
|
-
item.id ??= nextId();
|
|
48
|
-
this.items.push(item);
|
|
49
|
-
this.persistence?.append({
|
|
50
|
-
args: item.args,
|
|
51
|
-
functionPath: item.functionPath,
|
|
52
|
-
id: item.id,
|
|
53
|
-
identity: item.identity,
|
|
54
|
-
shardKey: item.shardKey,
|
|
55
|
-
...this.version === void 0 ? {} : { version: this.version }
|
|
56
|
-
}).catch((error) => {
|
|
57
|
-
reportPersistenceError(this.onPersistenceError, "append", error, item.id);
|
|
58
|
-
});
|
|
59
|
-
while (this.items.length > this.maxItems) {
|
|
60
|
-
const dropped = this.items.shift();
|
|
61
|
-
if (dropped) {
|
|
62
|
-
if (dropped.id) {
|
|
63
|
-
this.persistence?.remove(dropped.id).catch((error2) => {
|
|
64
|
-
reportPersistenceError(this.onPersistenceError, "remove", error2, dropped.id);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
const error = new Error("offline queue overflow");
|
|
68
|
-
error.code = "OFFLINE_QUEUE_OVERFLOW";
|
|
69
|
-
dropped.reject(error);
|
|
70
|
-
this.onEvict?.(dropped, error);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
this.notifySize();
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Restore mutations persisted in a prior session and re-queue them in FIFO
|
|
77
|
-
* order. Restored entries already live in durable storage, so they are not
|
|
78
|
-
* re-appended; they carry no-op `resolve`/`reject` (the original awaiter is
|
|
79
|
-
* gone after a reload). No-op when no persistence adapter is configured.
|
|
80
|
-
* Returns the distinct shard keys of the restored writes so the caller can
|
|
81
|
-
* open their sockets to trigger a flush.
|
|
82
|
-
*
|
|
83
|
-
* `hydrate()` runs post-construction (the caller awaits an async durable-store
|
|
84
|
-
* load), so a mutation issued while offline during that boot window is
|
|
85
|
-
* enqueued into `items` *before* this method's `await` resolves. Restored
|
|
86
|
-
* records are therefore `unshift`-ed ahead of whatever is already queued
|
|
87
|
-
* rather than `push`-ed to the end: the durable store's persist order is
|
|
88
|
-
* authoritative (a prior-session write is always older than anything from
|
|
89
|
-
* this session), so replaying a same-session boot-time write before an
|
|
90
|
-
* older restored write on the same document would let last-writer-wins
|
|
91
|
-
* silently clobber the newer data with the stale one.
|
|
92
|
-
*/
|
|
93
|
-
async hydrate() {
|
|
94
|
-
if (!this.persistence) {
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
let persisted;
|
|
98
|
-
try {
|
|
99
|
-
persisted = await this.persistence.load();
|
|
100
|
-
} catch (error) {
|
|
101
|
-
reportPersistenceError(this.onPersistenceError, "load", error);
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
const shardKeys = /* @__PURE__ */ new Set();
|
|
105
|
-
const restored = [];
|
|
106
|
-
for (const mutation of persisted) {
|
|
107
|
-
if (this.items.some((item) => item.id === mutation.id) || restored.some((item) => item.id === mutation.id)) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (isStaleVersion(this.version, mutation.version)) {
|
|
111
|
-
this.persistence.remove(mutation.id).catch((error) => {
|
|
112
|
-
reportPersistenceError(this.onPersistenceError, "remove", error, mutation.id);
|
|
113
|
-
});
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
restored.push({
|
|
117
|
-
args: mutation.args,
|
|
118
|
-
functionPath: mutation.functionPath,
|
|
119
|
-
id: mutation.id,
|
|
120
|
-
identity: mutation.identity,
|
|
121
|
-
reject: () => void 0,
|
|
122
|
-
resolve: () => void 0,
|
|
123
|
-
shardKey: mutation.shardKey
|
|
124
|
-
});
|
|
125
|
-
shardKeys.add(mutation.shardKey);
|
|
126
|
-
}
|
|
127
|
-
this.items.unshift(...restored);
|
|
128
|
-
this.notifySize();
|
|
129
|
-
return [...shardKeys];
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Remove and return queued mutations. With no `predicate`, drains the whole
|
|
133
|
-
* queue. With one, drains only matching entries (preserving FIFO order) and
|
|
134
|
-
* leaves the rest queued — used to flush a single shard's writes when its
|
|
135
|
-
* socket reconnects while other shards are still down.
|
|
136
|
-
*/
|
|
137
|
-
drain(predicate) {
|
|
138
|
-
if (!predicate) {
|
|
139
|
-
const drained2 = [...this.items];
|
|
140
|
-
this.items.length = 0;
|
|
141
|
-
this.notifySize();
|
|
142
|
-
return drained2;
|
|
143
|
-
}
|
|
144
|
-
const drained = [];
|
|
145
|
-
const kept = [];
|
|
146
|
-
for (const item of this.items) {
|
|
147
|
-
(predicate(item) ? drained : kept).push(item);
|
|
148
|
-
}
|
|
149
|
-
this.items.length = 0;
|
|
150
|
-
this.items.push(...kept);
|
|
151
|
-
this.notifySize();
|
|
152
|
-
return drained;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Return previously-drained mutations to the front of the queue, preserving
|
|
156
|
-
* their FIFO order, without re-persisting them — they were never unpersisted,
|
|
157
|
-
* so durable storage still holds them. Used when a flush aborts on a transient
|
|
158
|
-
* transport failure: the unreplayed writes stay queued for the next reconnect.
|
|
159
|
-
*/
|
|
160
|
-
requeue(items) {
|
|
161
|
-
if (items.length === 0) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
this.items.unshift(...items);
|
|
165
|
-
this.notifySize();
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Remove mutations whose precondition evaluates to `false` (stale/dirty
|
|
169
|
-
* writes that should not replay) and reject each with an
|
|
170
|
-
* `OFFLINE_PRECONDITION_FAILED` error. The valid (admitted) mutations stay
|
|
171
|
-
* queued in FIFO order. Returns the drained stale entries.
|
|
172
|
-
*
|
|
173
|
-
* Called during reconnect before the flush cycle to weed out writes whose
|
|
174
|
-
* assumptions no longer hold (e.g. a document was deleted by another client).
|
|
175
|
-
*/
|
|
176
|
-
drainConflict() {
|
|
177
|
-
const conflicted = [];
|
|
178
|
-
const kept = [];
|
|
179
|
-
for (const item of this.items) {
|
|
180
|
-
if (item.precondition !== void 0 && !item.precondition()) {
|
|
181
|
-
const error = new Error("offline mutation skipped: precondition failed before replay");
|
|
182
|
-
error.code = "OFFLINE_PRECONDITION_FAILED";
|
|
183
|
-
item.reject(error);
|
|
184
|
-
conflicted.push(item);
|
|
185
|
-
} else {
|
|
186
|
-
kept.push(item);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (conflicted.length > 0) {
|
|
190
|
-
this.items.length = 0;
|
|
191
|
-
this.items.push(...kept);
|
|
192
|
-
this.notifySize();
|
|
193
|
-
}
|
|
194
|
-
return conflicted;
|
|
195
|
-
}
|
|
196
|
-
clear() {
|
|
197
|
-
for (const item of this.items) {
|
|
198
|
-
const error = new Error("CLIENT_CLOSED");
|
|
199
|
-
error.code = "CLIENT_CLOSED";
|
|
200
|
-
item.reject(error);
|
|
201
|
-
}
|
|
202
|
-
this.items.length = 0;
|
|
203
|
-
this.notifySize();
|
|
204
|
-
}
|
|
205
|
-
/** Notify the size observer (the client's pending-sync count) after any change. */
|
|
206
|
-
notifySize() {
|
|
207
|
-
this.onSizeChange?.(this.items.length);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export { OfflineQueue as O, isStaleVersion as i, nextId as n, reportPersistenceError as r };
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const preloadQuery = async (client, function_, args, options = {}) => {
|
|
2
|
-
const value = await client.query(function_, args, options);
|
|
3
|
-
return {
|
|
4
|
-
__lunoraPreloaded: true,
|
|
5
|
-
args: args ?? {},
|
|
6
|
-
functionPath: function_.__lunoraRef,
|
|
7
|
-
shardKey: options.shardKey,
|
|
8
|
-
value
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
const preloadedQueryResult = (preloaded) => preloaded.value;
|
|
12
|
-
|
|
13
|
-
export { preloadQuery, preloadedQueryResult };
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
const compareKeys = (a, b) => {
|
|
2
|
-
if (a < b) {
|
|
3
|
-
return -1;
|
|
4
|
-
}
|
|
5
|
-
return a > b ? 1 : 0;
|
|
6
|
-
};
|
|
7
|
-
const stableStringify = (value) => {
|
|
8
|
-
if (value === void 0) {
|
|
9
|
-
return "null";
|
|
10
|
-
}
|
|
11
|
-
if (typeof value === "bigint") {
|
|
12
|
-
throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");
|
|
13
|
-
}
|
|
14
|
-
if (value === null || typeof value !== "object") {
|
|
15
|
-
return JSON.stringify(value);
|
|
16
|
-
}
|
|
17
|
-
if (Array.isArray(value)) {
|
|
18
|
-
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
19
|
-
}
|
|
20
|
-
const proto = Object.getPrototypeOf(value);
|
|
21
|
-
if (proto !== null && proto !== Object.prototype) {
|
|
22
|
-
const name = value.constructor?.name ?? "value";
|
|
23
|
-
throw new TypeError(
|
|
24
|
-
`stableStringify: cannot use a ${name} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
const record = value;
|
|
28
|
-
const keys = Object.keys(record).toSorted(compareKeys);
|
|
29
|
-
const parts = [];
|
|
30
|
-
for (const key of keys) {
|
|
31
|
-
const raw = record[key];
|
|
32
|
-
if (raw === void 0) {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
|
|
36
|
-
}
|
|
37
|
-
return `{${parts.join(",")}}`;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const toBase64 = (bytes) => {
|
|
41
|
-
let binary = "";
|
|
42
|
-
const chunk = 32768;
|
|
43
|
-
for (let index = 0; index < bytes.length; index += chunk) {
|
|
44
|
-
binary += String.fromCharCode(...bytes.subarray(index, index + chunk));
|
|
45
|
-
}
|
|
46
|
-
return btoa(binary);
|
|
47
|
-
};
|
|
48
|
-
const fromBase64 = (base64) => {
|
|
49
|
-
const binary = atob(base64);
|
|
50
|
-
const bytes = new Uint8Array(binary.length);
|
|
51
|
-
for (let index = 0; index < binary.length; index += 1) {
|
|
52
|
-
bytes[index] = binary.codePointAt(index) ?? 0;
|
|
53
|
-
}
|
|
54
|
-
return bytes;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const TAG = "$lunora.wire$";
|
|
58
|
-
const MAX_DEPTH = 64;
|
|
59
|
-
const MAX_BIGINT_DIGITS = 1024;
|
|
60
|
-
const UNSAFE_KEY = "__proto__";
|
|
61
|
-
const TYPED_ARRAY_CTORS = {
|
|
62
|
-
BigInt64Array,
|
|
63
|
-
BigUint64Array,
|
|
64
|
-
Float32Array,
|
|
65
|
-
Float64Array,
|
|
66
|
-
Int8Array,
|
|
67
|
-
Int16Array,
|
|
68
|
-
Int32Array,
|
|
69
|
-
Uint8Array,
|
|
70
|
-
Uint8ClampedArray,
|
|
71
|
-
Uint16Array,
|
|
72
|
-
Uint32Array
|
|
73
|
-
};
|
|
74
|
-
const ERROR_CTORS = {
|
|
75
|
-
Error,
|
|
76
|
-
EvalError,
|
|
77
|
-
RangeError,
|
|
78
|
-
ReferenceError,
|
|
79
|
-
SyntaxError,
|
|
80
|
-
TypeError,
|
|
81
|
-
URIError
|
|
82
|
-
};
|
|
83
|
-
const encodeWire = (value, depth = 0) => {
|
|
84
|
-
if (depth > MAX_DEPTH) {
|
|
85
|
-
throw new RangeError(`wire-codec: value nesting exceeds the ${MAX_DEPTH}-level limit`);
|
|
86
|
-
}
|
|
87
|
-
if (value === void 0) {
|
|
88
|
-
return [TAG, "undefined"];
|
|
89
|
-
}
|
|
90
|
-
if (value === null) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
const kind = typeof value;
|
|
94
|
-
if (kind === "bigint") {
|
|
95
|
-
return [TAG, "bigint", value.toString()];
|
|
96
|
-
}
|
|
97
|
-
if (kind === "number") {
|
|
98
|
-
const numeric = value;
|
|
99
|
-
if (Number.isNaN(numeric)) {
|
|
100
|
-
return [TAG, "nan"];
|
|
101
|
-
}
|
|
102
|
-
if (numeric === Infinity) {
|
|
103
|
-
return [TAG, "inf"];
|
|
104
|
-
}
|
|
105
|
-
if (numeric === -Infinity) {
|
|
106
|
-
return [TAG, "-inf"];
|
|
107
|
-
}
|
|
108
|
-
return numeric;
|
|
109
|
-
}
|
|
110
|
-
if (kind !== "object") {
|
|
111
|
-
return value;
|
|
112
|
-
}
|
|
113
|
-
if (value instanceof Date) {
|
|
114
|
-
return [TAG, "date", encodeWire(value.getTime(), depth + 1)];
|
|
115
|
-
}
|
|
116
|
-
if (value instanceof Error) {
|
|
117
|
-
const error = value;
|
|
118
|
-
const properties = {};
|
|
119
|
-
for (const key of Object.keys(error)) {
|
|
120
|
-
if (error[key] !== void 0) {
|
|
121
|
-
properties[key] = encodeWire(error[key], depth + 1);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
const encodedError = [TAG, "error", error.name, error.message, properties];
|
|
125
|
-
if (error.cause !== void 0) {
|
|
126
|
-
encodedError.push(encodeWire(error.cause, depth + 1));
|
|
127
|
-
}
|
|
128
|
-
return encodedError;
|
|
129
|
-
}
|
|
130
|
-
if (value instanceof URL) {
|
|
131
|
-
return [TAG, "url", value.href];
|
|
132
|
-
}
|
|
133
|
-
if (value instanceof Map) {
|
|
134
|
-
return [TAG, "map", [...value.entries()].map(([k, v]) => [encodeWire(k, depth + 1), encodeWire(v, depth + 1)])];
|
|
135
|
-
}
|
|
136
|
-
if (value instanceof Set) {
|
|
137
|
-
return [TAG, "set", [...value].map((item) => encodeWire(item, depth + 1))];
|
|
138
|
-
}
|
|
139
|
-
if (value instanceof ArrayBuffer) {
|
|
140
|
-
return [TAG, "bytes", toBase64(new Uint8Array(value)), "ArrayBuffer"];
|
|
141
|
-
}
|
|
142
|
-
if (ArrayBuffer.isView(value)) {
|
|
143
|
-
const view = value;
|
|
144
|
-
const ctorName = view.constructor.name;
|
|
145
|
-
const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
146
|
-
return ctorName === "Uint8Array" ? [TAG, "bytes", toBase64(bytes)] : [TAG, "bytes", toBase64(bytes), ctorName];
|
|
147
|
-
}
|
|
148
|
-
if (Array.isArray(value)) {
|
|
149
|
-
const encoded = value.map((item) => encodeWire(item, depth + 1));
|
|
150
|
-
return encoded.length > 0 && encoded[0] === TAG ? [TAG, "arr", encoded] : encoded;
|
|
151
|
-
}
|
|
152
|
-
const proto = Object.getPrototypeOf(value);
|
|
153
|
-
if (proto !== null && proto !== Object.prototype) {
|
|
154
|
-
const name = value.constructor?.name ?? "value";
|
|
155
|
-
throw new TypeError(
|
|
156
|
-
`wire-codec: cannot encode a ${name} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
const source = value;
|
|
160
|
-
const result = {};
|
|
161
|
-
for (const key of Object.keys(source)) {
|
|
162
|
-
const field = source[key];
|
|
163
|
-
if (field !== void 0) {
|
|
164
|
-
result[key] = encodeWire(field, depth + 1);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return result;
|
|
168
|
-
};
|
|
169
|
-
const decodeWire = (value, depth = 0) => {
|
|
170
|
-
if (depth > MAX_DEPTH) {
|
|
171
|
-
throw new RangeError(`wire-codec: value nesting exceeds the ${MAX_DEPTH}-level limit`);
|
|
172
|
-
}
|
|
173
|
-
if (value === null || typeof value !== "object") {
|
|
174
|
-
return value;
|
|
175
|
-
}
|
|
176
|
-
if (Array.isArray(value)) {
|
|
177
|
-
if (value[0] === TAG) {
|
|
178
|
-
const tag = value[1];
|
|
179
|
-
switch (tag) {
|
|
180
|
-
case "-inf": {
|
|
181
|
-
return -Infinity;
|
|
182
|
-
}
|
|
183
|
-
case "arr": {
|
|
184
|
-
return value[2].map((item) => decodeWire(item, depth + 1));
|
|
185
|
-
}
|
|
186
|
-
case "bigint": {
|
|
187
|
-
const raw = value[2];
|
|
188
|
-
if (typeof raw !== "string" || raw.length > MAX_BIGINT_DIGITS || !/^-?\d+$/.test(raw)) {
|
|
189
|
-
throw new RangeError(`wire-codec: invalid or over-long bigint (max ${MAX_BIGINT_DIGITS} digits)`);
|
|
190
|
-
}
|
|
191
|
-
return BigInt(raw);
|
|
192
|
-
}
|
|
193
|
-
case "date": {
|
|
194
|
-
return new Date(decodeWire(value[2], depth + 1));
|
|
195
|
-
}
|
|
196
|
-
case "map": {
|
|
197
|
-
return new Map(value[2].map(([k, v]) => [decodeWire(k, depth + 1), decodeWire(v, depth + 1)]));
|
|
198
|
-
}
|
|
199
|
-
case "set": {
|
|
200
|
-
return new Set(value[2].map((item) => decodeWire(item, depth + 1)));
|
|
201
|
-
}
|
|
202
|
-
case "url": {
|
|
203
|
-
return new URL(value[2]);
|
|
204
|
-
}
|
|
205
|
-
case "error": {
|
|
206
|
-
const name = value[2];
|
|
207
|
-
const message = value[3];
|
|
208
|
-
const Ctor = (Object.hasOwn(ERROR_CTORS, name) ? ERROR_CTORS[name] : void 0) ?? Error;
|
|
209
|
-
const error = new Ctor(message);
|
|
210
|
-
if (error.name !== name) {
|
|
211
|
-
Object.defineProperty(error, "name", { configurable: true, value: name, writable: true });
|
|
212
|
-
}
|
|
213
|
-
const props = decodeWire(value[4], depth + 1);
|
|
214
|
-
for (const key of Object.keys(props)) {
|
|
215
|
-
if (key === UNSAFE_KEY) {
|
|
216
|
-
Object.defineProperty(error, key, { configurable: true, enumerable: true, value: props[key], writable: true });
|
|
217
|
-
} else {
|
|
218
|
-
error[key] = props[key];
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if (value.length > 5) {
|
|
222
|
-
Object.defineProperty(error, "cause", { configurable: true, value: decodeWire(value[5], depth + 1), writable: true });
|
|
223
|
-
}
|
|
224
|
-
return error;
|
|
225
|
-
}
|
|
226
|
-
case "bytes": {
|
|
227
|
-
const bytes = fromBase64(value[2]);
|
|
228
|
-
const ctorName = value[3] ?? "Uint8Array";
|
|
229
|
-
if (ctorName === "ArrayBuffer") {
|
|
230
|
-
return bytes.buffer.byteLength === bytes.byteLength ? bytes.buffer : bytes.slice().buffer;
|
|
231
|
-
}
|
|
232
|
-
const Ctor = Object.hasOwn(TYPED_ARRAY_CTORS, ctorName) ? TYPED_ARRAY_CTORS[ctorName] : void 0;
|
|
233
|
-
return Ctor ? new Ctor(bytes.slice().buffer) : bytes;
|
|
234
|
-
}
|
|
235
|
-
case "inf": {
|
|
236
|
-
return Infinity;
|
|
237
|
-
}
|
|
238
|
-
case "nan": {
|
|
239
|
-
return Number.NaN;
|
|
240
|
-
}
|
|
241
|
-
case "undefined": {
|
|
242
|
-
return void 0;
|
|
243
|
-
}
|
|
244
|
-
default: {
|
|
245
|
-
return value.map((item) => decodeWire(item, depth + 1));
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return value.map((item) => decodeWire(item, depth + 1));
|
|
250
|
-
}
|
|
251
|
-
const source = value;
|
|
252
|
-
const result = {};
|
|
253
|
-
for (const key of Object.keys(source)) {
|
|
254
|
-
const decoded = decodeWire(source[key], depth + 1);
|
|
255
|
-
if (key === UNSAFE_KEY) {
|
|
256
|
-
Object.defineProperty(result, key, { configurable: true, enumerable: true, value: decoded, writable: true });
|
|
257
|
-
} else {
|
|
258
|
-
result[key] = decoded;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return result;
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
const stableWireKey = (value) => stableStringify(encodeWire(value));
|
|
265
|
-
|
|
266
|
-
export { decodeWire as d, encodeWire as e, stableWireKey as s };
|