@bitcobblers/wod-wiki-storage 0.35.2115 → 0.35.2117
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-DAGT3P7M.mjs → chunk-Y576CUFQ.mjs} +118 -36
- package/dist/chunk-Y576CUFQ.mjs.map +1 -0
- package/dist/index.cjs +182 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.mjs +62 -16
- package/dist/index.mjs.map +1 -1
- package/dist/sql/index.cjs +507 -286
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +16 -61
- package/dist/sql/index.d.ts +16 -61
- package/dist/sql/index.mjs +422 -270
- package/dist/sql/index.mjs.map +1 -1
- package/dist/wire-Cfvz-I0h.d.cts +207 -0
- package/dist/wire-Cfvz-I0h.d.ts +207 -0
- package/package.json +6 -9
- package/dist/chunk-DAGT3P7M.mjs.map +0 -1
- package/dist/wire-C7ZtlBKe.d.cts +0 -489
- package/dist/wire-C7ZtlBKe.d.ts +0 -489
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { STORE_DEFS, parseDomainQueryResult, bufferToOps, isDirty, isRange, bufferedGet, rangeFromIDB, overlayRows, stampUserOwned,
|
|
2
|
-
export { COLUMN_TYPES, DB_VERSION, DEFAULT_MEMBERSHIP_ID, DOMAIN_PROJECTION_VERSION, STORE_DEFS, USER_OWNED_STORES, compareKeys, createDefaultMembership, encodeKey, extractKeyPart, isRange, keyFromValue, keyInRange, parseDomainQueryResult, rangeFromIDB, stampUserOwned } from './chunk-
|
|
1
|
+
import { STORE_DEFS, parseDomainQueryResult, bufferToOps, isDirty, isRange, bufferedGet, bufferSlot, rangeFromIDB, keyFromValue, overlayRows, stampUserOwned, rowsEqual, encodeKey } from './chunk-Y576CUFQ.mjs';
|
|
2
|
+
export { COLUMN_TYPES, DB_VERSION, DEFAULT_MEMBERSHIP_ID, DOMAIN_PROJECTION_VERSION, STORE_DEFS, USER_OWNED_STORES, compareKeys, createDefaultMembership, encodeKey, extractKeyPart, isRange, keyFromValue, keyInRange, parseDomainQueryResult, rangeFromIDB, rowsEqual, stampUserOwned, validateNoteWrite, validateSessionWrite, validateStorageRow, validateStoredPins } from './chunk-Y576CUFQ.mjs';
|
|
3
3
|
|
|
4
4
|
// src/api.ts
|
|
5
5
|
async function request(http, method, path, init) {
|
|
@@ -50,14 +50,14 @@ var ApiStorage = class {
|
|
|
50
50
|
if (!def) throw new Error(`ApiStorage: unknown store '${store}'`);
|
|
51
51
|
return def;
|
|
52
52
|
}
|
|
53
|
-
makeView(store, buffer, cleared) {
|
|
54
|
-
return new ApiStore(this.http, this.def(store), store, buffer, cleared, this.getUserId);
|
|
53
|
+
makeView(store, buffer, cleared, checks, mutable = false) {
|
|
54
|
+
return new ApiStore(this.http, this.def(store), store, buffer, cleared, this.getUserId, checks, mutable);
|
|
55
55
|
}
|
|
56
56
|
readonly(store) {
|
|
57
57
|
return this.makeView(store);
|
|
58
58
|
}
|
|
59
59
|
readwrite(store) {
|
|
60
|
-
return this.makeView(store);
|
|
60
|
+
return this.makeView(store, void 0, void 0, void 0, true);
|
|
61
61
|
}
|
|
62
62
|
async queryDomain(query) {
|
|
63
63
|
const res = await this.http.fetchImpl(`${this.http.baseUrl}/v1/query`, {
|
|
@@ -72,21 +72,28 @@ var ApiStorage = class {
|
|
|
72
72
|
if (!res.ok) throw new Error(`ApiStorage: POST /v1/query \u2192 ${res.status}`);
|
|
73
73
|
return parseDomainQueryResult(await res.json(), query.plan);
|
|
74
74
|
}
|
|
75
|
-
async transaction(
|
|
75
|
+
async transaction(stores, mode, fn) {
|
|
76
76
|
const buffer = /* @__PURE__ */ new Map();
|
|
77
77
|
const cleared = /* @__PURE__ */ new Set();
|
|
78
|
+
const checks = /* @__PURE__ */ new Map();
|
|
79
|
+
const scoped = new Set(stores);
|
|
80
|
+
const view = (store, mutable) => {
|
|
81
|
+
if (!scoped.has(store)) throw new Error("store outside transaction scope");
|
|
82
|
+
if (mutable && mode !== "readwrite") throw new Error("read-only transaction");
|
|
83
|
+
return this.makeView(store, buffer, cleared, checks, mutable);
|
|
84
|
+
};
|
|
78
85
|
const tx = {
|
|
79
|
-
readonly: (store) =>
|
|
80
|
-
readwrite: (store) =>
|
|
86
|
+
readonly: (store) => view(store, false),
|
|
87
|
+
readwrite: (store) => view(store, true)
|
|
81
88
|
};
|
|
82
89
|
const result = await fn(tx);
|
|
83
|
-
await this.flush(buffer, cleared);
|
|
90
|
+
await this.flush(buffer, cleared, checks);
|
|
84
91
|
return result;
|
|
85
92
|
}
|
|
86
93
|
/** Sends buffered ops as one server-side SQL transaction. */
|
|
87
|
-
async flush(buffer, cleared) {
|
|
94
|
+
async flush(buffer, cleared, checks) {
|
|
88
95
|
const ops = bufferToOps(STORE_DEFS.map((d) => d.name), buffer, cleared);
|
|
89
|
-
if (ops.length > 0) await request(this.http, "POST", "/tx", { body: { ops } });
|
|
96
|
+
if (ops.length > 0) await request(this.http, "POST", "/tx", { body: { ops: [...checks.values(), ...ops] } });
|
|
90
97
|
}
|
|
91
98
|
async wipe() {
|
|
92
99
|
await request(this.http, "POST", "/wipe");
|
|
@@ -95,13 +102,15 @@ var ApiStorage = class {
|
|
|
95
102
|
}
|
|
96
103
|
};
|
|
97
104
|
var ApiStore = class {
|
|
98
|
-
constructor(http, def, store, buffer, cleared, getUserId) {
|
|
105
|
+
constructor(http, def, store, buffer, cleared, getUserId, checks, mutable = false) {
|
|
99
106
|
this.http = http;
|
|
100
107
|
this.def = def;
|
|
101
108
|
this.store = store;
|
|
102
109
|
this.buffer = buffer;
|
|
103
110
|
this.cleared = cleared;
|
|
104
111
|
this.getUserId = getUserId;
|
|
112
|
+
this.checks = checks;
|
|
113
|
+
this.mutable = mutable;
|
|
105
114
|
}
|
|
106
115
|
http;
|
|
107
116
|
def;
|
|
@@ -109,6 +118,8 @@ var ApiStore = class {
|
|
|
109
118
|
buffer;
|
|
110
119
|
cleared;
|
|
111
120
|
getUserId;
|
|
121
|
+
checks;
|
|
122
|
+
mutable;
|
|
112
123
|
/** True when this tx has buffered writes for this store. */
|
|
113
124
|
dirty() {
|
|
114
125
|
return isDirty(this.store, this.buffer, this.cleared);
|
|
@@ -127,7 +138,16 @@ var ApiStore = class {
|
|
|
127
138
|
const state = bufferedGet(this.store, k, this.buffer);
|
|
128
139
|
if (state) return state.hit ? state.value : void 0;
|
|
129
140
|
if (this.cleared?.has(this.store)) return void 0;
|
|
130
|
-
|
|
141
|
+
const value = await remoteGet(this.http, this.store, k);
|
|
142
|
+
const slot = bufferSlot(this.store, k);
|
|
143
|
+
if (!this.checks?.has(slot)) this.checks?.set(slot, { op: "check", store: this.store, key: k, value: structuredClone(value ?? null) });
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
async getMany(keys) {
|
|
147
|
+
return Promise.all(keys.map((key) => this.get(key)));
|
|
148
|
+
}
|
|
149
|
+
async getAllFromIndexMany(indexName, keys) {
|
|
150
|
+
return Promise.all(keys.map((key) => this.getAllFromIndex(indexName, key)));
|
|
131
151
|
}
|
|
132
152
|
async getAll(query, count) {
|
|
133
153
|
const range = rangeFromIDB(query);
|
|
@@ -148,18 +168,28 @@ var ApiStore = class {
|
|
|
148
168
|
return this.overlay(await this.serverRows(`index/${encodeURIComponent(indexName)}`, range), void 0, range, indexName).length;
|
|
149
169
|
}
|
|
150
170
|
/** Server rows for a list path, or none when this tx cleared the store. */
|
|
151
|
-
serverRows(path, range, count) {
|
|
152
|
-
if (this.cleared?.has(this.store)) return
|
|
153
|
-
|
|
171
|
+
async serverRows(path, range, count) {
|
|
172
|
+
if (this.cleared?.has(this.store)) return [];
|
|
173
|
+
const rows = await remoteList(this.http, this.store, path, range, count);
|
|
174
|
+
for (const row of rows) {
|
|
175
|
+
const key = keyFromValue(this.def, row), slot = bufferSlot(this.store, key);
|
|
176
|
+
if (!this.checks?.has(slot)) this.checks?.set(slot, { op: "check", store: this.store, key, value: structuredClone(row) });
|
|
177
|
+
}
|
|
178
|
+
return rows;
|
|
154
179
|
}
|
|
155
180
|
/** Applies buffered puts and deletes before range filtering, ordering and paging. */
|
|
156
181
|
overlay(rows, count, range, indexName) {
|
|
157
182
|
return overlayRows(this.def, this.store, this.buffer, this.cleared, rows, count, range, indexName);
|
|
158
183
|
}
|
|
159
184
|
async put(value, _key) {
|
|
185
|
+
if (!this.mutable) throw new Error("read-only store");
|
|
160
186
|
const userId = this.getUserId?.();
|
|
161
187
|
const stored = userId ? stampUserOwned(this.store, value, userId) : value;
|
|
162
188
|
const key = keyFromValue(this.def, stored);
|
|
189
|
+
if (this.store === "block_revisions") {
|
|
190
|
+
const previous = this.buffer?.get(bufferSlot(this.store, key));
|
|
191
|
+
if (previous?.kind === "put" && !rowsEqual(previous.value, stored)) throw new Error("immutable block revision collision");
|
|
192
|
+
}
|
|
163
193
|
if (this.buffer) {
|
|
164
194
|
this.buffer.set(bufferSlot(this.store, key), { kind: "put", value: stored });
|
|
165
195
|
return key;
|
|
@@ -167,7 +197,21 @@ var ApiStore = class {
|
|
|
167
197
|
await request(this.http, "POST", "/tx", { body: { ops: [{ op: "put", store: this.store, value: stored }] } });
|
|
168
198
|
return key;
|
|
169
199
|
}
|
|
200
|
+
async putMany(values, _keys) {
|
|
201
|
+
if (!this.mutable) throw new Error("read-only store");
|
|
202
|
+
if (this.buffer) {
|
|
203
|
+
const keys2 = [];
|
|
204
|
+
for (const value of values) keys2.push(await this.put(value));
|
|
205
|
+
return keys2;
|
|
206
|
+
}
|
|
207
|
+
const stored = values.map((value) => this.getUserId ? stampUserOwned(this.store, value, this.getUserId()) : value);
|
|
208
|
+
const keys = stored.map((value) => keyFromValue(this.def, value));
|
|
209
|
+
await request(this.http, "POST", "/tx", { body: { ops: stored.map((value) => ({ op: "put", store: this.store, value })) } });
|
|
210
|
+
return keys;
|
|
211
|
+
}
|
|
170
212
|
async delete(key) {
|
|
213
|
+
if (!this.mutable) throw new Error("read-only store");
|
|
214
|
+
if (this.store === "block_revisions") throw new Error("block history requires explicit wipe");
|
|
171
215
|
const range = isRange(key) ? key : void 0;
|
|
172
216
|
if (range) {
|
|
173
217
|
if (this.buffer) {
|
|
@@ -189,6 +233,8 @@ var ApiStore = class {
|
|
|
189
233
|
await remoteDelete(this.http, this.store, k);
|
|
190
234
|
}
|
|
191
235
|
async clear() {
|
|
236
|
+
if (!this.mutable) throw new Error("read-only store");
|
|
237
|
+
if (this.store === "block_revisions") throw new Error("block history requires explicit wipe");
|
|
192
238
|
if (this.buffer) {
|
|
193
239
|
this.cleared?.add(this.store);
|
|
194
240
|
const prefix = `${this.store}\0`;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api.ts"],"names":["key"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * ApiStorage — fetch-based IStorage backend speaking the pinned /v1 wire\n * protocol (implemented by wod-wiki-api). Reads overlay a per-tx write buffer\n * on top of server reads (buffered put replaces a row, buffered delete removes\n * it, matched by encoded key); writes buffer only and flush as ONE\n * POST /v1/tx on fn success. A thrown fn discards the buffer (no request).\n *\n * Signature extension over IStorage: query/delete args also accept plain\n * RangeDTO literals so non-IDB runtimes (Bun, node, tests) can drive this\n * client without an IDBKeyRange global. Transactional range deletes expand\n * matching primary keys into scalar delete ops on the existing /tx wire.\n */\n\nimport type {\n IReadWriteStore,\n IReadOnlyStore,\n IStorage,\n IStorageTransaction,\n StoreName,\n StoreType,\n} from './contract';\nimport { STORE_DEFS, type StoreDef } from './schema';\nimport { stampUserOwned } from './membership';\nimport { parseDomainQueryResult, type DomainQuery, type DomainQueryResult } from './domain';\nimport {\n encodeKey,\n isRange,\n keyFromValue,\n rangeFromIDB,\n type KeyDTO,\n type RangeDTO,\n} from './wire';\nimport {\n bufferedGet,\n bufferSlot,\n bufferToOps,\n isDirty,\n overlayRows,\n type BufferEntry,\n} from './overlay';\n\n/** Query/delete argument: IndexedDB key forms plus the wire RangeDTO. */\ntype QueryArg = IDBValidKey | IDBKeyRange | RangeDTO;\n\ninterface Http { baseUrl: string; fetchImpl: typeof fetch }\n\n// ── HTTP layer (module functions; one shared client instance passes `Http`) ──\n\nasync function request(\n http: Http,\n method: string,\n path: string,\n init?: { body?: unknown; params?: URLSearchParams },\n): Promise<Response> {\n const qs = init?.params?.toString();\n const res = await http.fetchImpl(`${http.baseUrl}/v1${path}${qs ? `?${qs}` : ''}`, {\n method,\n headers: init?.body !== undefined ? { 'content-type': 'application/json' } : undefined,\n body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,\n });\n if (!res.ok) throw new Error(`ApiStorage: ${method} /v1${path} → ${res.status}`);\n return res;\n}\n\nasync function remoteGet<T>(http: Http, store: StoreName, key: KeyDTO): Promise<T | undefined> {\n const res = await http.fetchImpl(`${http.baseUrl}/v1/${store}/get?key=${encodeKey(key)}`);\n if (res.status === 404) return undefined;\n if (!res.ok) throw new Error(`ApiStorage: GET /v1/${store}/get → ${res.status}`);\n return (await res.json()) as T;\n}\n\nasync function remoteList<T>(\n http: Http,\n store: StoreName,\n path: string,\n range: RangeDTO | undefined,\n count?: number,\n): Promise<T[]> {\n const params = new URLSearchParams();\n if (range) params.set('range', JSON.stringify(range));\n if (count !== undefined) params.set('count', String(count));\n const res = await request(http, 'GET', `/${store}/${path}`, { params });\n return (await res.json()) as T[];\n}\n\nasync function remoteCount(http: Http, store: StoreName, range: RangeDTO | undefined, indexName?: string): Promise<number> {\n const params = new URLSearchParams();\n if (range) params.set('range', JSON.stringify(range));\n if (indexName !== undefined) params.set('index', indexName);\n const res = await request(http, 'GET', `/${store}/count`, { params });\n return ((await res.json()) as { count: number }).count;\n}\n\n/** DELETE /v1/:store?key= — key or range as JSON; URLSearchParams owns the percent-encoding (encodeKey would double-encode). */\nasync function remoteDelete(http: Http, store: StoreName, keyOrRange: KeyDTO | RangeDTO): Promise<void> {\n await request(http, 'DELETE', `/${store}`, { params: new URLSearchParams({ key: JSON.stringify(keyOrRange) }) });\n}\n\nexport class ApiStorage implements IStorage {\n private readonly http: Http;\n private readonly defs: Record<StoreName, StoreDef>;\n /** Injected identity: user-owned rows are stamped with this on write. */\n private readonly getUserId?: () => string;\n\n constructor(\n baseUrl: string = '/api',\n fetchImpl: typeof fetch = globalThis.fetch.bind(globalThis),\n options: { getUserId?: () => string } = {},\n ) {\n this.http = { baseUrl, fetchImpl };\n this.getUserId = options.getUserId;\n // Keys are StoreName by construction (STORE_DEFS[].name is typed so).\n this.defs = Object.fromEntries(STORE_DEFS.map((d) => [d.name, d])) as Record<StoreName, StoreDef>;\n }\n\n def(store: StoreName): StoreDef {\n const def = this.defs[store];\n if (!def) throw new Error(`ApiStorage: unknown store '${store}'`);\n return def;\n }\n\n private makeView<K extends StoreName>(\n store: K,\n buffer?: Map<string, BufferEntry>,\n cleared?: Set<StoreName>,\n ): IReadWriteStore<StoreType<K>> {\n return new ApiStore<StoreType<K>>(this.http, this.def(store), store, buffer, cleared, this.getUserId);\n }\n\n readonly<K extends StoreName>(store: K): IReadOnlyStore<StoreType<K>> {\n return this.makeView(store);\n }\n\n readwrite<K extends StoreName>(store: K): IReadWriteStore<StoreType<K>> {\n return this.makeView(store);\n }\n async queryDomain(query: DomainQuery): Promise<DomainQueryResult | undefined> {\n const res = await this.http.fetchImpl(`${this.http.baseUrl}/v1/query`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(query),\n });\n if (res.status === 409) {\n const error: unknown = await res.json();\n if (typeof error === 'object' && error !== null && 'code' in error\n && error.code === 'domain_projection_unavailable') return undefined;\n }\n if (!res.ok) throw new Error(`ApiStorage: POST /v1/query → ${res.status}`);\n return parseDomainQueryResult(await res.json(), query.plan);\n }\n\n async transaction<K extends StoreName, R>(\n _stores: K[],\n _mode: 'readonly' | 'readwrite',\n fn: (tx: IStorageTransaction) => Promise<R>,\n ): Promise<R> {\n const buffer = new Map<string, BufferEntry>();\n const cleared = new Set<StoreName>();\n const tx: IStorageTransaction = {\n readonly: <S extends StoreName>(store: S) => this.makeView(store, buffer, cleared),\n readwrite: <S extends StoreName>(store: S) => this.makeView(store, buffer, cleared),\n };\n const result = await fn(tx);\n await this.flush(buffer, cleared);\n return result;\n }\n\n /** Sends buffered ops as one server-side SQL transaction. */\n private async flush(buffer: Map<string, BufferEntry>, cleared: Set<StoreName>): Promise<void> {\n const ops = bufferToOps(STORE_DEFS.map(d => d.name), buffer, cleared);\n if (ops.length > 0) await request(this.http, 'POST', '/tx', { body: { ops } });\n }\n\n async wipe(): Promise<void> {\n await request(this.http, 'POST', '/wipe');\n }\n\n async close(): Promise<void> {\n // stateless HTTP client — nothing to release\n }\n}\n\n/**\n * Store view. Outside a transaction (`buffer` undefined) every write posts\n * immediately; inside one, writes land in the shared buffer and reads overlay\n * it on server responses in op order, matching rows by encoded primary key.\n */\nclass ApiStore<T> implements IReadWriteStore<T> {\n constructor(\n private readonly http: Http,\n private readonly def: StoreDef,\n private readonly store: StoreName,\n private readonly buffer?: Map<string, BufferEntry>,\n private readonly cleared?: Set<StoreName>,\n private readonly getUserId?: () => string,\n ) {}\n\n /** True when this tx has buffered writes for this store. */\n private dirty(): boolean {\n return isDirty(this.store, this.buffer, this.cleared);\n }\n\n async get(key: IDBValidKey | RangeDTO): Promise<T | undefined> {\n const range = isRange(key) ? key : undefined;\n let k: KeyDTO;\n if (range) {\n if (range.only === undefined) {\n throw new Error('ApiStorage.get: needs a plain key or {only}; bounded ranges match no single row');\n }\n k = range.only;\n } else {\n // IDBValidKey ⊃ KeyDTO (Date/ArrayBuffer keys are outside the wire subset).\n k = key as KeyDTO;\n }\n const state = bufferedGet<T>(this.store, k, this.buffer);\n if (state) return state.hit ? state.value : undefined;\n if (this.cleared?.has(this.store)) return undefined;\n return remoteGet<T>(this.http, this.store, k);\n }\n\n async getAll(query?: QueryArg, count?: number): Promise<T[]> {\n const range = rangeFromIDB(query);\n return this.overlay(await this.serverRows('all', range, this.dirty() ? undefined : count), count, range);\n }\n\n async getAllFromIndex(indexName: string, query?: QueryArg, count?: number): Promise<T[]> {\n const range = rangeFromIDB(query);\n return this.overlay(await this.serverRows(`index/${encodeURIComponent(indexName)}`, range, this.dirty() ? undefined : count), count, range, indexName);\n }\n\n async count(query?: QueryArg): Promise<number> {\n const range = rangeFromIDB(query);\n if (!this.dirty()) return remoteCount(this.http, this.store, range);\n return this.overlay(await this.serverRows('all', range), undefined, range).length;\n }\n\n async countFromIndex(indexName: string, query?: QueryArg): Promise<number> {\n const range = rangeFromIDB(query);\n if (!this.dirty()) return remoteCount(this.http, this.store, range, indexName);\n return this.overlay(await this.serverRows(`index/${encodeURIComponent(indexName)}`, range), undefined, range, indexName).length;\n }\n\n /** Server rows for a list path, or none when this tx cleared the store. */\n private serverRows(path: string, range: RangeDTO | undefined, count?: number): Promise<T[]> {\n if (this.cleared?.has(this.store)) return Promise.resolve([]);\n return remoteList<T>(this.http, this.store, path, range, count);\n }\n\n /** Applies buffered puts and deletes before range filtering, ordering and paging. */\n private overlay(rows: T[], count?: number, range?: RangeDTO, indexName?: string): T[] {\n return overlayRows<T>(this.def, this.store, this.buffer, this.cleared, rows, count, range, indexName);\n }\n\n async put(value: T, _key?: IDBValidKey): Promise<IDBValidKey> {\n const userId = this.getUserId?.();\n const stored = userId ? (stampUserOwned(this.store, value, userId) as T) : value;\n const key = keyFromValue(this.def, stored);\n if (this.buffer) {\n this.buffer.set(bufferSlot(this.store, key), { kind: 'put', value: stored });\n return key;\n }\n await request(this.http, 'POST', '/tx', { body: { ops: [{ op: 'put', store: this.store, value: stored }] } });\n return key;\n }\n\n async delete(key: IDBValidKey | IDBKeyRange | RangeDTO): Promise<void> {\n const range = isRange(key) ? key : undefined;\n if (range) {\n if (this.buffer) {\n const rows = await this.getAll(range);\n for (const row of rows) {\n const key = keyFromValue(this.def, row);\n this.buffer.set(bufferSlot(this.store, key), { kind: 'delete', key });\n }\n return;\n }\n await remoteDelete(this.http, this.store, range);\n return;\n }\n // IDBValidKey ⊃ KeyDTO (exotic keys pass through lossy).\n const k = key as KeyDTO;\n if (this.buffer) {\n this.buffer.set(bufferSlot(this.store, k), { kind: 'delete', key: k });\n return;\n }\n await remoteDelete(this.http, this.store, k);\n }\n\n async clear(): Promise<void> {\n if (this.buffer) {\n this.cleared?.add(this.store);\n const prefix = `${this.store}\\u0000`;\n for (const slot of [...this.buffer.keys()]) {\n if (slot.startsWith(prefix)) this.buffer.delete(slot);\n }\n return;\n }\n await request(this.http, 'POST', '/tx', { body: { ops: [{ op: 'clear', store: this.store }] } });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/api.ts"],"names":["keys","key"],"mappings":";;;;AAkDA,eAAe,OAAA,CACb,IAAA,EACA,MAAA,EACA,IAAA,EACA,IAAA,EACmB;AACnB,EAAA,MAAM,EAAA,GAAK,IAAA,EAAM,MAAA,EAAQ,QAAA,EAAS;AAClC,EAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,SAAA,CAAU,GAAG,IAAA,CAAK,OAAO,CAAA,GAAA,EAAM,IAAI,GAAG,EAAA,GAAK,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA,EAAI;AAAA,IACjF,MAAA;AAAA,IACA,SAAS,IAAA,EAAM,IAAA,KAAS,SAAY,EAAE,cAAA,EAAgB,oBAAmB,GAAI,MAAA;AAAA,IAC7E,IAAA,EAAM,MAAM,IAAA,KAAS,MAAA,GAAY,KAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,GAAI;AAAA,GAC9D,CAAA;AACD,EAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,MAAM,CAAA,IAAA,EAAO,IAAI,CAAA,QAAA,EAAM,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC/E,EAAA,OAAO,GAAA;AACT;AAEA,eAAe,SAAA,CAAa,IAAA,EAAY,KAAA,EAAkB,GAAA,EAAqC;AAC7F,EAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,IAAA,EAAO,KAAK,CAAA,SAAA,EAAY,SAAA,CAAU,GAAG,CAAC,CAAA,CAAE,CAAA;AACxF,EAAA,IAAI,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK,OAAO,MAAA;AAC/B,EAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,KAAK,CAAA,YAAA,EAAU,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC/E,EAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AACzB;AAEA,eAAe,UAAA,CACb,IAAA,EACA,KAAA,EACA,IAAA,EACA,OACA,KAAA,EACc;AACd,EAAA,MAAM,MAAA,GAAS,IAAI,eAAA,EAAgB;AACnC,EAAA,IAAI,OAAO,MAAA,CAAO,GAAA,CAAI,SAAS,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AACpD,EAAA,IAAI,UAAU,MAAA,EAAW,MAAA,CAAO,IAAI,OAAA,EAAS,MAAA,CAAO,KAAK,CAAC,CAAA;AAC1D,EAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,IAAA,EAAM,KAAA,EAAO,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,EAAI,EAAE,MAAA,EAAQ,CAAA;AACtE,EAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AACzB;AAEA,eAAe,WAAA,CAAY,IAAA,EAAY,KAAA,EAAkB,KAAA,EAA6B,SAAA,EAAqC;AACzH,EAAA,MAAM,MAAA,GAAS,IAAI,eAAA,EAAgB;AACnC,EAAA,IAAI,OAAO,MAAA,CAAO,GAAA,CAAI,SAAS,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AACpD,EAAA,IAAI,SAAA,KAAc,MAAA,EAAW,MAAA,CAAO,GAAA,CAAI,SAAS,SAAS,CAAA;AAC1D,EAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,IAAA,EAAM,KAAA,EAAO,IAAI,KAAK,CAAA,MAAA,CAAA,EAAU,EAAE,MAAA,EAAQ,CAAA;AACpE,EAAA,OAAA,CAAS,MAAM,GAAA,CAAI,IAAA,EAAK,EAAyB,KAAA;AACnD;AAGA,eAAe,YAAA,CAAa,IAAA,EAAY,KAAA,EAAkB,UAAA,EAA8C;AACtG,EAAA,MAAM,QAAQ,IAAA,EAAM,QAAA,EAAU,IAAI,KAAK,CAAA,CAAA,EAAI,EAAE,MAAA,EAAQ,IAAI,eAAA,CAAgB,EAAE,KAAK,IAAA,CAAK,SAAA,CAAU,UAAU,CAAA,EAAG,GAAG,CAAA;AACjH;AAEO,IAAM,aAAN,MAAqC;AAAA,EACzB,IAAA;AAAA,EACA,IAAA;AAAA;AAAA,EAEA,SAAA;AAAA,EAEjB,WAAA,CACE,OAAA,GAAkB,MAAA,EAClB,SAAA,GAA0B,UAAA,CAAW,KAAA,CAAM,IAAA,CAAK,UAAU,CAAA,EAC1D,OAAA,GAAwC,EAAC,EACzC;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,OAAA,EAAS,SAAA,EAAU;AACjC,IAAA,IAAA,CAAK,YAAY,OAAA,CAAQ,SAAA;AAEzB,IAAA,IAAA,CAAK,IAAA,GAAO,MAAA,CAAO,WAAA,CAAY,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,IAAA,EAAM,CAAC,CAAC,CAAC,CAAA;AAAA,EACnE;AAAA,EAEA,IAAI,KAAA,EAA4B;AAC9B,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA;AAC3B,IAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,KAAA,CAAM,CAAA,2BAAA,EAA8B,KAAK,CAAA,CAAA,CAAG,CAAA;AAChE,IAAA,OAAO,GAAA;AAAA,EACT;AAAA,EAEQ,SACN,KAAA,EACA,MAAA,EACA,OAAA,EACA,MAAA,EACA,UAAU,KAAA,EACqB;AAC/B,IAAA,OAAO,IAAI,QAAA,CAAuB,IAAA,CAAK,IAAA,EAAM,KAAK,GAAA,CAAI,KAAK,CAAA,EAAG,KAAA,EAAO,MAAA,EAAQ,OAAA,EAAS,IAAA,CAAK,SAAA,EAAW,QAAQ,OAAO,CAAA;AAAA,EACvH;AAAA,EAEA,SAA8B,KAAA,EAAwC;AACpE,IAAA,OAAO,IAAA,CAAK,SAAS,KAAK,CAAA;AAAA,EAC5B;AAAA,EAEA,UAA+B,KAAA,EAAyC;AACtE,IAAA,OAAO,KAAK,QAAA,CAAS,KAAA,EAAO,MAAA,EAAW,MAAA,EAAW,QAAW,IAAI,CAAA;AAAA,EACnE;AAAA,EACA,MAAM,YAAY,KAAA,EAA4D;AAC5E,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,IAAA,CAAK,UAAU,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA,SAAA,CAAA,EAAa;AAAA,MACrE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,KAAK;AAAA,KAC3B,CAAA;AACD,IAAA,IAAI,GAAA,CAAI,WAAW,GAAA,EAAK;AACtB,MAAA,MAAM,KAAA,GAAiB,MAAM,GAAA,CAAI,IAAA,EAAK;AACtC,MAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,UAAU,KAAA,IACxD,KAAA,CAAM,IAAA,KAAS,+BAAA,EAAiC,OAAO,MAAA;AAAA,IAC9D;AACA,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,kCAAA,EAAgC,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACzE,IAAA,OAAO,uBAAuB,MAAM,GAAA,CAAI,IAAA,EAAK,EAAG,MAAM,IAAI,CAAA;AAAA,EAC5D;AAAA,EAEA,MAAM,WAAA,CACJ,MAAA,EACA,IAAA,EACA,EAAA,EACY;AACZ,IAAA,MAAM,MAAA,uBAAa,GAAA,EAAyB;AAC5C,IAAA,MAAM,OAAA,uBAAc,GAAA,EAAe;AACnC,IAAA,MAAM,MAAA,uBAAa,GAAA,EAA0C;AAC7D,IAAA,MAAM,MAAA,GAAS,IAAI,GAAA,CAAe,MAAM,CAAA;AACxC,IAAA,MAAM,IAAA,GAAO,CAAsB,KAAA,EAAU,OAAA,KAAqB;AAChE,MAAA,IAAI,CAAC,OAAO,GAAA,CAAI,KAAK,GAAG,MAAM,IAAI,MAAM,iCAAiC,CAAA;AACzE,MAAA,IAAI,WAAW,IAAA,KAAS,WAAA,EAAa,MAAM,IAAI,MAAM,uBAAuB,CAAA;AAC5E,MAAA,OAAO,KAAK,QAAA,CAAS,KAAA,EAAO,MAAA,EAAQ,OAAA,EAAS,QAAQ,OAAO,CAAA;AAAA,IAC9D,CAAA;AACA,IAAA,MAAM,EAAA,GAA0B;AAAA,MAC9B,QAAA,EAAU,CAAsB,KAAA,KAAa,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,MAC9D,SAAA,EAAW,CAAsB,KAAA,KAAa,IAAA,CAAK,OAAO,IAAI;AAAA,KAChE;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAAG,EAAE,CAAA;AAC1B,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAS,MAAM,CAAA;AACxC,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA,EAGA,MAAc,KAAA,CAAM,MAAA,EAAkC,OAAA,EAAyB,MAAA,EAAkE;AAC/I,IAAA,MAAM,GAAA,GAAM,YAAY,UAAA,CAAW,GAAA,CAAI,OAAK,CAAA,CAAE,IAAI,CAAA,EAAG,MAAA,EAAQ,OAAO,CAAA;AACpE,IAAA,IAAI,GAAA,CAAI,SAAS,CAAA,EAAG,MAAM,QAAQ,IAAA,CAAK,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,EAAE,IAAA,EAAM,EAAE,GAAA,EAAK,CAAC,GAAG,MAAA,CAAO,MAAA,IAAU,GAAG,GAAG,CAAA,EAAE,EAAG,CAAA;AAAA,EAC7G;AAAA,EAEA,MAAM,IAAA,GAAsB;AAC1B,IAAA,MAAM,OAAA,CAAQ,IAAA,CAAK,IAAA,EAAM,MAAA,EAAQ,OAAO,CAAA;AAAA,EAC1C;AAAA,EAEA,MAAM,KAAA,GAAuB;AAAA,EAE7B;AACF;AAOA,IAAM,WAAN,MAAgD;AAAA,EAC9C,WAAA,CACmB,MACA,GAAA,EACA,KAAA,EACA,QACA,OAAA,EACA,SAAA,EACA,MAAA,EACA,OAAA,GAAU,KAAA,EAC3B;AARiB,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,GAAA,GAAA,GAAA;AACA,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,EAChB;AAAA,EARgB,IAAA;AAAA,EACA,GAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA;AAAA,EAIX,KAAA,GAAiB;AACvB,IAAA,OAAO,QAAQ,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,MAAA,EAAQ,KAAK,OAAO,CAAA;AAAA,EACtD;AAAA,EAEA,MAAM,IAAI,GAAA,EAAqD;AAC7D,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAG,CAAA,GAAI,GAAA,GAAM,MAAA;AACnC,IAAA,IAAI,CAAA;AACJ,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAI,KAAA,CAAM,SAAS,MAAA,EAAW;AAC5B,QAAA,MAAM,IAAI,MAAM,iFAAiF,CAAA;AAAA,MACnG;AACA,MAAA,CAAA,GAAI,KAAA,CAAM,IAAA;AAAA,IACZ,CAAA,MAAO;AAEL,MAAA,CAAA,GAAI,GAAA;AAAA,IACN;AACA,IAAA,MAAM,QAAQ,WAAA,CAAe,IAAA,CAAK,KAAA,EAAO,CAAA,EAAG,KAAK,MAAM,CAAA;AACvD,IAAA,IAAI,KAAA,EAAO,OAAO,KAAA,CAAM,GAAA,GAAM,MAAM,KAAA,GAAQ,MAAA;AAC5C,IAAA,IAAI,KAAK,OAAA,EAAS,GAAA,CAAI,IAAA,CAAK,KAAK,GAAG,OAAO,MAAA;AAC1C,IAAA,MAAM,QAAQ,MAAM,SAAA,CAAa,KAAK,IAAA,EAAM,IAAA,CAAK,OAAO,CAAC,CAAA;AACzD,IAAA,MAAM,IAAA,GAAO,UAAA,CAAW,IAAA,CAAK,KAAA,EAAO,CAAC,CAAA;AACrC,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ,GAAA,CAAI,IAAI,CAAA,EAAG,IAAA,CAAK,MAAA,EAAQ,GAAA,CAAI,IAAA,EAAM,EAAE,IAAI,OAAA,EAAS,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,GAAA,EAAK,CAAA,EAAG,OAAO,eAAA,CAAgB,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA;AACrI,IAAA,OAAO,KAAA;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,IAAA,EAAiD;AAC7D,IAAA,OAAO,OAAA,CAAQ,IAAI,IAAA,CAAK,GAAA,CAAI,SAAO,IAAA,CAAK,GAAA,CAAI,GAAG,CAAC,CAAC,CAAA;AAAA,EACnD;AAAA,EAEA,MAAM,mBAAA,CAAoB,SAAA,EAAmB,IAAA,EAAqC;AAChF,IAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,KAAO,KAAK,eAAA,CAAgB,SAAA,EAAW,GAAG,CAAC,CAAC,CAAA;AAAA,EAC1E;AAAA,EAEA,MAAM,MAAA,CAAO,KAAA,EAAkB,KAAA,EAA8B;AAC3D,IAAA,MAAM,KAAA,GAAQ,aAAa,KAAK,CAAA;AAChC,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,MAAM,IAAA,CAAK,WAAW,KAAA,EAAO,KAAA,EAAO,IAAA,CAAK,KAAA,EAAM,GAAI,MAAA,GAAY,KAAK,CAAA,EAAG,OAAO,KAAK,CAAA;AAAA,EACzG;AAAA,EAEA,MAAM,eAAA,CAAgB,SAAA,EAAmB,KAAA,EAAkB,KAAA,EAA8B;AACvF,IAAA,MAAM,KAAA,GAAQ,aAAa,KAAK,CAAA;AAChC,IAAA,OAAO,KAAK,OAAA,CAAQ,MAAM,KAAK,UAAA,CAAW,CAAA,MAAA,EAAS,mBAAmB,SAAS,CAAC,IAAI,KAAA,EAAO,IAAA,CAAK,OAAM,GAAI,MAAA,GAAY,KAAK,CAAA,EAAG,KAAA,EAAO,OAAO,SAAS,CAAA;AAAA,EACvJ;AAAA,EAEA,MAAM,MAAM,KAAA,EAAmC;AAC7C,IAAA,MAAM,KAAA,GAAQ,aAAa,KAAK,CAAA;AAChC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAM,EAAG,OAAO,YAAY,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,KAAA,EAAO,KAAK,CAAA;AAClE,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,MAAM,IAAA,CAAK,UAAA,CAAW,OAAO,KAAK,CAAA,EAAG,MAAA,EAAW,KAAK,CAAA,CAAE,MAAA;AAAA,EAC7E;AAAA,EAEA,MAAM,cAAA,CAAe,SAAA,EAAmB,KAAA,EAAmC;AACzE,IAAA,MAAM,KAAA,GAAQ,aAAa,KAAK,CAAA;AAChC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAM,EAAG,OAAO,WAAA,CAAY,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,SAAS,CAAA;AAC7E,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,MAAM,IAAA,CAAK,WAAW,CAAA,MAAA,EAAS,kBAAA,CAAmB,SAAS,CAAC,IAAI,KAAK,CAAA,EAAG,MAAA,EAAW,KAAA,EAAO,SAAS,CAAA,CAAE,MAAA;AAAA,EAC3H;AAAA;AAAA,EAGA,MAAc,UAAA,CAAW,IAAA,EAAc,KAAA,EAA6B,KAAA,EAA8B;AAChG,IAAA,IAAI,KAAK,OAAA,EAAS,GAAA,CAAI,KAAK,KAAK,CAAA,SAAU,EAAC;AAC3C,IAAA,MAAM,IAAA,GAAO,MAAM,UAAA,CAAc,IAAA,CAAK,MAAM,IAAA,CAAK,KAAA,EAAO,IAAA,EAAM,KAAA,EAAO,KAAK,CAAA;AAC1E,IAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,MAAA,MAAM,GAAA,GAAM,YAAA,CAAa,IAAA,CAAK,GAAA,EAAK,GAAG,GAAG,IAAA,GAAO,UAAA,CAAW,IAAA,CAAK,KAAA,EAAO,GAAG,CAAA;AAC1E,MAAA,IAAI,CAAC,KAAK,MAAA,EAAQ,GAAA,CAAI,IAAI,CAAA,EAAG,IAAA,CAAK,QAAQ,GAAA,CAAI,IAAA,EAAM,EAAE,EAAA,EAAI,OAAA,EAAS,OAAO,IAAA,CAAK,KAAA,EAAO,KAAK,KAAA,EAAO,eAAA,CAAgB,GAAG,CAAA,EAAG,CAAA;AAAA,IAC1H;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGQ,OAAA,CAAQ,IAAA,EAAW,KAAA,EAAgB,KAAA,EAAkB,SAAA,EAAyB;AACpF,IAAA,OAAO,WAAA,CAAe,IAAA,CAAK,GAAA,EAAK,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,OAAA,EAAS,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,SAAS,CAAA;AAAA,EACtG;AAAA,EAEA,MAAM,GAAA,CAAI,KAAA,EAAU,IAAA,EAA0C;AAC5D,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,EAAS,MAAM,IAAI,MAAM,iBAAiB,CAAA;AACpD,IAAA,MAAM,MAAA,GAAS,KAAK,SAAA,IAAY;AAChC,IAAA,MAAM,SAAS,MAAA,GAAU,cAAA,CAAe,KAAK,KAAA,EAAO,KAAA,EAAO,MAAM,CAAA,GAAU,KAAA;AAC3E,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,IAAA,CAAK,GAAA,EAAK,MAAM,CAAA;AACzC,IAAA,IAAI,IAAA,CAAK,UAAU,iBAAA,EAAmB;AACpC,MAAA,MAAM,QAAA,GAAW,KAAK,MAAA,EAAQ,GAAA,CAAI,WAAW,IAAA,CAAK,KAAA,EAAO,GAAG,CAAC,CAAA;AAC7D,MAAA,IAAI,QAAA,EAAU,IAAA,KAAS,KAAA,IAAS,CAAC,SAAA,CAAU,QAAA,CAAS,KAAA,EAAO,MAAM,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,oCAAoC,CAAA;AAAA,IAC1H;AACA,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,KAAA,EAAO,GAAG,CAAA,EAAG,EAAE,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,CAAA;AAC3E,MAAA,OAAO,GAAA;AAAA,IACT;AACA,IAAA,MAAM,OAAA,CAAQ,KAAK,IAAA,EAAM,MAAA,EAAQ,OAAO,EAAE,IAAA,EAAM,EAAE,GAAA,EAAK,CAAC,EAAE,EAAA,EAAI,KAAA,EAAO,OAAO,IAAA,CAAK,KAAA,EAAO,OAAO,MAAA,EAAQ,CAAA,EAAE,EAAG,CAAA;AAC5G,IAAA,OAAO,GAAA;AAAA,EACT;AAAA,EAEA,MAAM,OAAA,CAAQ,MAAA,EAAa,KAAA,EAA+C;AACxE,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,EAAS,MAAM,IAAI,MAAM,iBAAiB,CAAA;AACpD,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,MAAMA,QAAsB,EAAC;AAC7B,MAAA,KAAA,MAAW,KAAA,IAAS,QAAQA,KAAAA,CAAK,KAAK,MAAM,IAAA,CAAK,GAAA,CAAI,KAAK,CAAC,CAAA;AAC3D,MAAA,OAAOA,KAAAA;AAAA,IACT;AACA,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS,KAAK,SAAA,GAAY,cAAA,CAAe,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,IAAA,CAAK,SAAA,EAAW,IAAI,KAAK,CAAA;AAC/G,IAAA,MAAM,IAAA,GAAO,OAAO,GAAA,CAAI,CAAA,KAAA,KAAS,aAAa,IAAA,CAAK,GAAA,EAAK,KAAK,CAAC,CAAA;AAC9D,IAAA,MAAM,OAAA,CAAQ,KAAK,IAAA,EAAM,MAAA,EAAQ,OAAO,EAAE,IAAA,EAAM,EAAE,GAAA,EAAK,MAAA,CAAO,GAAA,CAAI,YAAU,EAAE,EAAA,EAAI,OAAO,KAAA,EAAO,IAAA,CAAK,OAAO,KAAA,EAAM,CAAE,CAAA,EAAE,EAAG,CAAA;AACzH,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,GAAA,EAA0D;AACrE,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,EAAS,MAAM,IAAI,MAAM,iBAAiB,CAAA;AACpD,IAAA,IAAI,KAAK,KAAA,KAAU,iBAAA,EAAmB,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAC5F,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAG,CAAA,GAAI,GAAA,GAAM,MAAA;AACnC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAI,KAAK,MAAA,EAAQ;AACf,QAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA;AACpC,QAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,UAAA,MAAMC,IAAAA,GAAM,YAAA,CAAa,IAAA,CAAK,GAAA,EAAK,GAAG,CAAA;AACtC,UAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,KAAA,EAAOA,IAAG,CAAA,EAAG,EAAE,IAAA,EAAM,QAAA,EAAU,GAAA,EAAAA,IAAAA,EAAK,CAAA;AAAA,QACtE;AACA,QAAA;AAAA,MACF;AACA,MAAA,MAAM,YAAA,CAAa,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,OAAO,KAAK,CAAA;AAC/C,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,CAAA,GAAI,GAAA;AACV,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,KAAA,EAAO,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,QAAA,EAAU,GAAA,EAAK,CAAA,EAAG,CAAA;AACrE,MAAA;AAAA,IACF;AACA,IAAA,MAAM,YAAA,CAAa,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,OAAO,CAAC,CAAA;AAAA,EAC7C;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,EAAS,MAAM,IAAI,MAAM,iBAAiB,CAAA;AACpD,IAAA,IAAI,KAAK,KAAA,KAAU,iBAAA,EAAmB,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAC5F,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAA,CAAK,OAAA,EAAS,GAAA,CAAI,IAAA,CAAK,KAAK,CAAA;AAC5B,MAAA,MAAM,MAAA,GAAS,CAAA,EAAG,IAAA,CAAK,KAAK,CAAA,EAAA,CAAA;AAC5B,MAAA,KAAA,MAAW,QAAQ,CAAC,GAAG,KAAK,MAAA,CAAO,IAAA,EAAM,CAAA,EAAG;AAC1C,QAAA,IAAI,KAAK,UAAA,CAAW,MAAM,GAAG,IAAA,CAAK,MAAA,CAAO,OAAO,IAAI,CAAA;AAAA,MACtD;AACA,MAAA;AAAA,IACF;AACA,IAAA,MAAM,QAAQ,IAAA,CAAK,IAAA,EAAM,QAAQ,KAAA,EAAO,EAAE,MAAM,EAAE,GAAA,EAAK,CAAC,EAAE,EAAA,EAAI,SAAS,KAAA,EAAO,IAAA,CAAK,OAAO,CAAA,IAAK,CAAA;AAAA,EACjG;AACF,CAAA","file":"index.mjs","sourcesContent":["/**\n * ApiStorage — fetch-based IStorage backend speaking the pinned /v1 wire\n * protocol (implemented by wod-wiki-api). Reads overlay a per-tx write buffer\n * on top of server reads (buffered put replaces a row, buffered delete removes\n * it, matched by encoded key); writes buffer only and flush as ONE\n * POST /v1/tx on fn success. A thrown fn discards the buffer (no request).\n *\n * Signature extension over IStorage: query/delete args also accept plain\n * RangeDTO literals so non-IDB runtimes (Bun, node, tests) can drive this\n * client without an IDBKeyRange global. Transactional range deletes expand\n * matching primary keys into scalar delete ops on the existing /tx wire.\n */\n\nimport type {\n IReadWriteStore,\n IReadOnlyStore,\n IStorage,\n IStorageTransaction,\n StoreName,\n StoreType,\n} from './contract';\nimport { STORE_DEFS, type StoreDef } from './schema';\nimport { stampUserOwned } from './membership';\nimport { parseDomainQueryResult, type DomainQuery, type DomainQueryResult } from './domain';\nimport {\n encodeKey,\n isRange,\n keyFromValue,\n rangeFromIDB,\n type KeyDTO,\n type RangeDTO,\n type TxOp,\n} from './wire';\nimport {\n bufferedGet,\n bufferSlot,\n bufferToOps,\n isDirty,\n overlayRows,\n type BufferEntry,\n} from './overlay';\nimport { rowsEqual } from './validation';\n\n/** Query/delete argument: IndexedDB key forms plus the wire RangeDTO. */\ntype QueryArg = IDBValidKey | IDBKeyRange | RangeDTO;\n\ninterface Http { baseUrl: string; fetchImpl: typeof fetch }\n\n// ── HTTP layer (module functions; one shared client instance passes `Http`) ──\n\nasync function request(\n http: Http,\n method: string,\n path: string,\n init?: { body?: unknown; params?: URLSearchParams },\n): Promise<Response> {\n const qs = init?.params?.toString();\n const res = await http.fetchImpl(`${http.baseUrl}/v1${path}${qs ? `?${qs}` : ''}`, {\n method,\n headers: init?.body !== undefined ? { 'content-type': 'application/json' } : undefined,\n body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,\n });\n if (!res.ok) throw new Error(`ApiStorage: ${method} /v1${path} → ${res.status}`);\n return res;\n}\n\nasync function remoteGet<T>(http: Http, store: StoreName, key: KeyDTO): Promise<T | undefined> {\n const res = await http.fetchImpl(`${http.baseUrl}/v1/${store}/get?key=${encodeKey(key)}`);\n if (res.status === 404) return undefined;\n if (!res.ok) throw new Error(`ApiStorage: GET /v1/${store}/get → ${res.status}`);\n return (await res.json()) as T;\n}\n\nasync function remoteList<T>(\n http: Http,\n store: StoreName,\n path: string,\n range: RangeDTO | undefined,\n count?: number,\n): Promise<T[]> {\n const params = new URLSearchParams();\n if (range) params.set('range', JSON.stringify(range));\n if (count !== undefined) params.set('count', String(count));\n const res = await request(http, 'GET', `/${store}/${path}`, { params });\n return (await res.json()) as T[];\n}\n\nasync function remoteCount(http: Http, store: StoreName, range: RangeDTO | undefined, indexName?: string): Promise<number> {\n const params = new URLSearchParams();\n if (range) params.set('range', JSON.stringify(range));\n if (indexName !== undefined) params.set('index', indexName);\n const res = await request(http, 'GET', `/${store}/count`, { params });\n return ((await res.json()) as { count: number }).count;\n}\n\n/** DELETE /v1/:store?key= — key or range as JSON; URLSearchParams owns the percent-encoding (encodeKey would double-encode). */\nasync function remoteDelete(http: Http, store: StoreName, keyOrRange: KeyDTO | RangeDTO): Promise<void> {\n await request(http, 'DELETE', `/${store}`, { params: new URLSearchParams({ key: JSON.stringify(keyOrRange) }) });\n}\n\nexport class ApiStorage implements IStorage {\n private readonly http: Http;\n private readonly defs: Record<StoreName, StoreDef>;\n /** Injected identity: user-owned rows are stamped with this on write. */\n private readonly getUserId?: () => string;\n\n constructor(\n baseUrl: string = '/api',\n fetchImpl: typeof fetch = globalThis.fetch.bind(globalThis),\n options: { getUserId?: () => string } = {},\n ) {\n this.http = { baseUrl, fetchImpl };\n this.getUserId = options.getUserId;\n // Keys are StoreName by construction (STORE_DEFS[].name is typed so).\n this.defs = Object.fromEntries(STORE_DEFS.map((d) => [d.name, d])) as Record<StoreName, StoreDef>;\n }\n\n def(store: StoreName): StoreDef {\n const def = this.defs[store];\n if (!def) throw new Error(`ApiStorage: unknown store '${store}'`);\n return def;\n }\n\n private makeView<K extends StoreName>(\n store: K,\n buffer?: Map<string, BufferEntry>,\n cleared?: Set<StoreName>,\n checks?: Map<string, Extract<TxOp, {op: 'check'}>>,\n mutable = false,\n ): IReadWriteStore<StoreType<K>> {\n return new ApiStore<StoreType<K>>(this.http, this.def(store), store, buffer, cleared, this.getUserId, checks, mutable);\n }\n\n readonly<K extends StoreName>(store: K): IReadOnlyStore<StoreType<K>> {\n return this.makeView(store);\n }\n\n readwrite<K extends StoreName>(store: K): IReadWriteStore<StoreType<K>> {\n return this.makeView(store, undefined, undefined, undefined, true);\n }\n async queryDomain(query: DomainQuery): Promise<DomainQueryResult | undefined> {\n const res = await this.http.fetchImpl(`${this.http.baseUrl}/v1/query`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(query),\n });\n if (res.status === 409) {\n const error: unknown = await res.json();\n if (typeof error === 'object' && error !== null && 'code' in error\n && error.code === 'domain_projection_unavailable') return undefined;\n }\n if (!res.ok) throw new Error(`ApiStorage: POST /v1/query → ${res.status}`);\n return parseDomainQueryResult(await res.json(), query.plan);\n }\n\n async transaction<K extends StoreName, R>(\n stores: K[],\n mode: 'readonly' | 'readwrite',\n fn: (tx: IStorageTransaction) => Promise<R>,\n ): Promise<R> {\n const buffer = new Map<string, BufferEntry>();\n const cleared = new Set<StoreName>();\n const checks = new Map<string, Extract<TxOp, {op: 'check'}>>();\n const scoped = new Set<StoreName>(stores);\n const view = <S extends StoreName>(store: S, mutable: boolean) => {\n if (!scoped.has(store)) throw new Error('store outside transaction scope');\n if (mutable && mode !== 'readwrite') throw new Error('read-only transaction');\n return this.makeView(store, buffer, cleared, checks, mutable);\n };\n const tx: IStorageTransaction = {\n readonly: <S extends StoreName>(store: S) => view(store, false),\n readwrite: <S extends StoreName>(store: S) => view(store, true),\n };\n const result = await fn(tx);\n await this.flush(buffer, cleared, checks);\n return result;\n }\n\n /** Sends buffered ops as one server-side SQL transaction. */\n private async flush(buffer: Map<string, BufferEntry>, cleared: Set<StoreName>, checks: Map<string, Extract<TxOp, {op: 'check'}>>): Promise<void> {\n const ops = bufferToOps(STORE_DEFS.map(d => d.name), buffer, cleared);\n if (ops.length > 0) await request(this.http, 'POST', '/tx', { body: { ops: [...checks.values(), ...ops] } });\n }\n\n async wipe(): Promise<void> {\n await request(this.http, 'POST', '/wipe');\n }\n\n async close(): Promise<void> {\n // stateless HTTP client — nothing to release\n }\n}\n\n/**\n * Store view. Outside a transaction (`buffer` undefined) every write posts\n * immediately; inside one, writes land in the shared buffer and reads overlay\n * it on server responses in op order, matching rows by encoded primary key.\n */\nclass ApiStore<T> implements IReadWriteStore<T> {\n constructor(\n private readonly http: Http,\n private readonly def: StoreDef,\n private readonly store: StoreName,\n private readonly buffer?: Map<string, BufferEntry>,\n private readonly cleared?: Set<StoreName>,\n private readonly getUserId?: () => string,\n private readonly checks?: Map<string, Extract<TxOp, {op: 'check'}>>,\n private readonly mutable = false,\n ) {}\n\n /** True when this tx has buffered writes for this store. */\n private dirty(): boolean {\n return isDirty(this.store, this.buffer, this.cleared);\n }\n\n async get(key: IDBValidKey | RangeDTO): Promise<T | undefined> {\n const range = isRange(key) ? key : undefined;\n let k: KeyDTO;\n if (range) {\n if (range.only === undefined) {\n throw new Error('ApiStorage.get: needs a plain key or {only}; bounded ranges match no single row');\n }\n k = range.only;\n } else {\n // IDBValidKey ⊃ KeyDTO (Date/ArrayBuffer keys are outside the wire subset).\n k = key as KeyDTO;\n }\n const state = bufferedGet<T>(this.store, k, this.buffer);\n if (state) return state.hit ? state.value : undefined;\n if (this.cleared?.has(this.store)) return undefined;\n const value = await remoteGet<T>(this.http, this.store, k);\n const slot = bufferSlot(this.store, k);\n if (!this.checks?.has(slot)) this.checks?.set(slot, { op: 'check', store: this.store, key: k, value: structuredClone(value ?? null) });\n return value;\n }\n\n async getMany(keys: IDBValidKey[]): Promise<(T | undefined)[]> {\n return Promise.all(keys.map(key => this.get(key)));\n }\n\n async getAllFromIndexMany(indexName: string, keys: IDBValidKey[]): Promise<T[][]> {\n return Promise.all(keys.map(key => this.getAllFromIndex(indexName, key)));\n }\n\n async getAll(query?: QueryArg, count?: number): Promise<T[]> {\n const range = rangeFromIDB(query);\n return this.overlay(await this.serverRows('all', range, this.dirty() ? undefined : count), count, range);\n }\n\n async getAllFromIndex(indexName: string, query?: QueryArg, count?: number): Promise<T[]> {\n const range = rangeFromIDB(query);\n return this.overlay(await this.serverRows(`index/${encodeURIComponent(indexName)}`, range, this.dirty() ? undefined : count), count, range, indexName);\n }\n\n async count(query?: QueryArg): Promise<number> {\n const range = rangeFromIDB(query);\n if (!this.dirty()) return remoteCount(this.http, this.store, range);\n return this.overlay(await this.serverRows('all', range), undefined, range).length;\n }\n\n async countFromIndex(indexName: string, query?: QueryArg): Promise<number> {\n const range = rangeFromIDB(query);\n if (!this.dirty()) return remoteCount(this.http, this.store, range, indexName);\n return this.overlay(await this.serverRows(`index/${encodeURIComponent(indexName)}`, range), undefined, range, indexName).length;\n }\n\n /** Server rows for a list path, or none when this tx cleared the store. */\n private async serverRows(path: string, range: RangeDTO | undefined, count?: number): Promise<T[]> {\n if (this.cleared?.has(this.store)) return [];\n const rows = await remoteList<T>(this.http, this.store, path, range, count);\n for (const row of rows) {\n const key = keyFromValue(this.def, row), slot = bufferSlot(this.store, key);\n if (!this.checks?.has(slot)) this.checks?.set(slot, { op: 'check', store: this.store, key, value: structuredClone(row) });\n }\n return rows;\n }\n\n /** Applies buffered puts and deletes before range filtering, ordering and paging. */\n private overlay(rows: T[], count?: number, range?: RangeDTO, indexName?: string): T[] {\n return overlayRows<T>(this.def, this.store, this.buffer, this.cleared, rows, count, range, indexName);\n }\n\n async put(value: T, _key?: IDBValidKey): Promise<IDBValidKey> {\n if (!this.mutable) throw new Error('read-only store');\n const userId = this.getUserId?.();\n const stored = userId ? (stampUserOwned(this.store, value, userId) as T) : value;\n const key = keyFromValue(this.def, stored);\n if (this.store === 'block_revisions') {\n const previous = this.buffer?.get(bufferSlot(this.store, key));\n if (previous?.kind === 'put' && !rowsEqual(previous.value, stored)) throw new Error('immutable block revision collision');\n }\n if (this.buffer) {\n this.buffer.set(bufferSlot(this.store, key), { kind: 'put', value: stored });\n return key;\n }\n await request(this.http, 'POST', '/tx', { body: { ops: [{ op: 'put', store: this.store, value: stored }] } });\n return key;\n }\n\n async putMany(values: T[], _keys?: IDBValidKey[]): Promise<IDBValidKey[]> {\n if (!this.mutable) throw new Error('read-only store');\n if (this.buffer) {\n const keys: IDBValidKey[] = [];\n for (const value of values) keys.push(await this.put(value));\n return keys;\n }\n const stored = values.map(value => this.getUserId ? stampUserOwned(this.store, value, this.getUserId()) : value);\n const keys = stored.map(value => keyFromValue(this.def, value));\n await request(this.http, 'POST', '/tx', { body: { ops: stored.map(value => ({ op: 'put', store: this.store, value })) } });\n return keys;\n }\n\n async delete(key: IDBValidKey | IDBKeyRange | RangeDTO): Promise<void> {\n if (!this.mutable) throw new Error('read-only store');\n if (this.store === 'block_revisions') throw new Error('block history requires explicit wipe');\n const range = isRange(key) ? key : undefined;\n if (range) {\n if (this.buffer) {\n const rows = await this.getAll(range);\n for (const row of rows) {\n const key = keyFromValue(this.def, row);\n this.buffer.set(bufferSlot(this.store, key), { kind: 'delete', key });\n }\n return;\n }\n await remoteDelete(this.http, this.store, range);\n return;\n }\n // IDBValidKey ⊃ KeyDTO (exotic keys pass through lossy).\n const k = key as KeyDTO;\n if (this.buffer) {\n this.buffer.set(bufferSlot(this.store, k), { kind: 'delete', key: k });\n return;\n }\n await remoteDelete(this.http, this.store, k);\n }\n\n async clear(): Promise<void> {\n if (!this.mutable) throw new Error('read-only store');\n if (this.store === 'block_revisions') throw new Error('block history requires explicit wipe');\n if (this.buffer) {\n this.cleared?.add(this.store);\n const prefix = `${this.store}\\u0000`;\n for (const slot of [...this.buffer.keys()]) {\n if (slot.startsWith(prefix)) this.buffer.delete(slot);\n }\n return;\n }\n await request(this.http, 'POST', '/tx', { body: { ops: [{ op: 'clear', store: this.store }] } });\n }\n}\n"]}
|