@bitcobblers/wod-wiki-storage 0.35.2116 → 0.35.2119

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.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { STORE_DEFS, parseDomainQueryResult, bufferToOps, isDirty, isRange, bufferedGet, rangeFromIDB, overlayRows, stampUserOwned, keyFromValue, bufferSlot, encodeKey } from './chunk-DAGT3P7M.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, stampUserOwned } from './chunk-DAGT3P7M.mjs';
1
+ import { STORE_DEFS, parseDomainQueryResult, bufferToOps, isDirty, isRange, bufferedGet, bufferSlot, rangeFromIDB, keyFromValue, overlayRows, stampUserOwned, rowsEqual, encodeKey } from './chunk-7ZCBM5NS.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-7ZCBM5NS.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(_stores, _mode, fn) {
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) => this.makeView(store, buffer, cleared),
80
- readwrite: (store) => this.makeView(store, buffer, cleared)
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
- return remoteGet(this.http, this.store, k);
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 Promise.resolve([]);
153
- return remoteList(this.http, this.store, path, range, count);
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`;
@@ -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":"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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 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"]}