@bitcobblers/wod-wiki-storage 0.35.2116 → 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.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var wodWikiCore = require('@bitcobblers/wod-wiki-core');
|
|
4
|
+
|
|
3
5
|
// src/schema.ts
|
|
4
|
-
var DB_VERSION =
|
|
6
|
+
var DB_VERSION = 29;
|
|
5
7
|
var COLUMN_TYPES = {
|
|
6
8
|
notes: { date: "number", createdAt: "number" },
|
|
7
9
|
page: { createdAt: "number" },
|
|
@@ -10,16 +12,9 @@ var COLUMN_TYPES = {
|
|
|
10
12
|
tags: { createdAt: "number" },
|
|
11
13
|
tag_types: { createdAt: "number" },
|
|
12
14
|
note_tags: {},
|
|
13
|
-
|
|
14
|
-
version: "number",
|
|
15
|
-
position: "number",
|
|
16
|
-
createdAt: "number",
|
|
17
|
-
updatedAt: "number",
|
|
18
|
-
isHistory: "boolean"
|
|
19
|
-
},
|
|
15
|
+
block_revisions: { version: "number", savedAt: "number" },
|
|
20
16
|
sessions: {
|
|
21
|
-
|
|
22
|
-
version: "number",
|
|
17
|
+
blockVersion: "number",
|
|
23
18
|
startTime: "number",
|
|
24
19
|
endTime: "number",
|
|
25
20
|
duration: "number",
|
|
@@ -32,7 +27,7 @@ var COLUMN_TYPES = {
|
|
|
32
27
|
attachments: { createdAt: "number" },
|
|
33
28
|
events: {
|
|
34
29
|
timestamp: "number",
|
|
35
|
-
|
|
30
|
+
blockVersion: "number",
|
|
36
31
|
line: "number",
|
|
37
32
|
sourceStatementId: "number",
|
|
38
33
|
stackLevel: "number",
|
|
@@ -45,7 +40,10 @@ var COLUMN_TYPES = {
|
|
|
45
40
|
field_catalog_meta: {},
|
|
46
41
|
efforts: { "baseAttributes.met": "number" },
|
|
47
42
|
block_index: {
|
|
48
|
-
|
|
43
|
+
blockVersion: "number",
|
|
44
|
+
sourceRevision: "number",
|
|
45
|
+
from: "number",
|
|
46
|
+
to: "number",
|
|
49
47
|
position: "number",
|
|
50
48
|
createdAt: "number",
|
|
51
49
|
isStatic: "boolean"
|
|
@@ -92,17 +90,12 @@ var STORE_DEFS = [
|
|
|
92
90
|
{ name: "by-note-tag", keyPath: ["noteId", "tagId"], unique: true },
|
|
93
91
|
{ name: "by-user", keyPath: ["userId"] }
|
|
94
92
|
] },
|
|
95
|
-
|
|
96
|
-
{ name: "segments", keyPath: ["id", "version"], indexes: [
|
|
93
|
+
{ name: "block_revisions", keyPath: ["noteId", "blockId", "version"], indexes: [
|
|
97
94
|
{ name: "by-note", keyPath: ["noteId"] },
|
|
98
|
-
{ name: "by-
|
|
99
|
-
{ name: "by-page", keyPath: ["pageId"] },
|
|
100
|
-
{ name: "by-history", keyPath: ["isHistory"] },
|
|
101
|
-
{ name: "by-user", keyPath: ["userId"] }
|
|
95
|
+
{ name: "by-block", keyPath: ["noteId", "blockId"] }
|
|
102
96
|
] },
|
|
103
97
|
// 6. sessions
|
|
104
98
|
{ name: "sessions", keyPath: ["id"], indexes: [
|
|
105
|
-
{ name: "by-segment", keyPath: ["segmentId"] },
|
|
106
99
|
{ name: "by-note", keyPath: ["noteId"] },
|
|
107
100
|
{ name: "by-completed", keyPath: ["createdAt"] },
|
|
108
101
|
{ name: "by-content", keyPath: ["blockContentId"] },
|
|
@@ -111,9 +104,6 @@ var STORE_DEFS = [
|
|
|
111
104
|
{ name: "by-origin", keyPath: ["origin"] },
|
|
112
105
|
{ name: "by-user", keyPath: ["userId"] }
|
|
113
106
|
] },
|
|
114
|
-
// 7. results — removed in V26: legacy alias of sessions. The V26 upgrade
|
|
115
|
-
// migrates any residual rows into `sessions` (projecting pre-V21 data.logs
|
|
116
|
-
// into event rows) before dropping the store; no compatibility alias.
|
|
117
107
|
// 8. attachments
|
|
118
108
|
{ name: "attachments", keyPath: ["id"], indexes: [
|
|
119
109
|
{ name: "by-note", keyPath: ["noteId"] },
|
|
@@ -182,7 +172,6 @@ var USER_OWNED_STORES = [
|
|
|
182
172
|
"tags",
|
|
183
173
|
"tag_types",
|
|
184
174
|
"note_tags",
|
|
185
|
-
"segments",
|
|
186
175
|
"sessions",
|
|
187
176
|
"attachments",
|
|
188
177
|
"events",
|
|
@@ -243,7 +232,7 @@ function rangeFromIDB(query) {
|
|
|
243
232
|
wire.lowerOpen = query.lowerOpen === true;
|
|
244
233
|
}
|
|
245
234
|
if (query.upper !== void 0 && query.upper !== null) {
|
|
246
|
-
wire.upper = query.upper;
|
|
235
|
+
wire.upper = Array.isArray(query.upper) ? query.upper.map((p) => Array.isArray(p) && p.length === 0 ? "\uFFFF" : p) : query.upper;
|
|
247
236
|
wire.upperOpen = query.upperOpen === true;
|
|
248
237
|
}
|
|
249
238
|
return wire.lower !== void 0 || wire.upper !== void 0 ? wire : void 0;
|
|
@@ -275,9 +264,7 @@ function keyInRange(key, range) {
|
|
|
275
264
|
}
|
|
276
265
|
return true;
|
|
277
266
|
}
|
|
278
|
-
|
|
279
|
-
// src/domain.ts
|
|
280
|
-
var DOMAIN_PROJECTION_VERSION = 1;
|
|
267
|
+
var DOMAIN_PROJECTION_VERSION = 2;
|
|
281
268
|
function isObject(value) {
|
|
282
269
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
283
270
|
}
|
|
@@ -288,13 +275,16 @@ function finiteNumbers(row, fields) {
|
|
|
288
275
|
return fields.every((field) => typeof row[field] === "number" && Number.isFinite(row[field]));
|
|
289
276
|
}
|
|
290
277
|
function isNote(value) {
|
|
291
|
-
|
|
278
|
+
if (!isObject(value) || typeof value.id !== "string" || typeof value.title !== "string" || typeof value.rawContent !== "string" || !Number.isSafeInteger(value.sourceRevision) || typeof value.sourceRevision !== "number" || value.sourceRevision < 1 || !isObject(value.blocks) || !finiteNumbers(value, ["createdAt"]) || value.date !== void 0 && !finiteNumbers(value, ["date"]) || !optionalStrings(value, ["type", "sourceId", "catalog", "sourcePath", "seedOrigin", "seedChunkId"]) || value.tags !== void 0 && (!Array.isArray(value.tags) || !value.tags.every((tag) => typeof tag === "string"))) return false;
|
|
279
|
+
try {
|
|
280
|
+
wodWikiCore.validateNoteBlocks(value.rawContent, value.blocks, value.sourceRevision);
|
|
281
|
+
return true;
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
292
285
|
}
|
|
293
286
|
function isBlock(value) {
|
|
294
|
-
return isObject(value) && typeof value.id === "string" && typeof value.noteId === "string" && typeof value.
|
|
295
|
-
}
|
|
296
|
-
function isSegment(value) {
|
|
297
|
-
return isObject(value) && typeof value.id === "string" && typeof value.noteId === "string" && finiteNumbers(value, ["version", "createdAt"]) && typeof value.dataType === "string" && typeof value.rawContent === "string" && (value.data === null || isObject(value.data)) && optionalStrings(value, ["sourceContent", "pageId"]) && (value.position === void 0 || finiteNumbers(value, ["position"])) && (value.isHistory === void 0 || typeof value.isHistory === "boolean");
|
|
287
|
+
return isObject(value) && typeof value.id === "string" && typeof value.noteId === "string" && typeof value.blockId === "string" && finiteNumbers(value, ["blockVersion", "sourceRevision", "from", "to", "createdAt"]) && value.id === `${value.noteId}:${value.blockId}` && typeof value.blockVersion === "number" && Number.isSafeInteger(value.blockVersion) && value.blockVersion > 0 && typeof value.sourceRevision === "number" && Number.isSafeInteger(value.sourceRevision) && value.sourceRevision > 0 && typeof value.from === "number" && Number.isSafeInteger(value.from) && value.from >= 0 && typeof value.to === "number" && Number.isSafeInteger(value.to) && value.to > value.from && ["wod", "query", "widget", "embed"].includes(String(value.dataType)) && typeof value.rawContent === "string" && typeof value.noteTitle === "string" && optionalStrings(value, ["blockContentId", "sourceId", "sourcePath", "routeId"]) && (value.position === void 0 || finiteNumbers(value, ["position"])) && (value.isStatic === void 0 || typeof value.isStatic === "boolean");
|
|
298
288
|
}
|
|
299
289
|
function isTag(value) {
|
|
300
290
|
return isObject(value) && typeof value.id === "string" && typeof value.label === "string" && finiteNumbers(value, ["createdAt"]) && optionalStrings(value, ["type"]);
|
|
@@ -306,7 +296,7 @@ function isLink(value) {
|
|
|
306
296
|
return isObject(value) && typeof value.id === "string" && typeof value.noteId === "string" && typeof value.pageId === "string" && finiteNumbers(value, ["createdAt"]) && (value.position === void 0 || finiteNumbers(value, ["position"]));
|
|
307
297
|
}
|
|
308
298
|
function isEntry(value) {
|
|
309
|
-
return isObject(value) && isNote(value.note) && Array.isArray(value.
|
|
299
|
+
return isObject(value) && isNote(value.note) && Array.isArray(value.tags) && value.tags.every(isTag) && Array.isArray(value.links) && value.links.every(isLink) && Array.isArray(value.pages) && value.pages.every(isPage);
|
|
310
300
|
}
|
|
311
301
|
function parseDomainQueryResult(value, plan) {
|
|
312
302
|
if (!isObject(value) || value.projectionVersion !== DOMAIN_PROJECTION_VERSION || value.plan !== plan || !Array.isArray(value.rows) || typeof value.selectedCount !== "number" || !Number.isSafeInteger(value.selectedCount) || value.selectedCount < 0 || typeof value.matchedCount !== "number" || !Number.isSafeInteger(value.matchedCount) || value.matchedCount < 0 || value.matchedCount > value.selectedCount || value.rows.length > value.matchedCount) {
|
|
@@ -402,6 +392,100 @@ function bufferToOps(storeNames, buffer, cleared) {
|
|
|
402
392
|
return ops;
|
|
403
393
|
}
|
|
404
394
|
|
|
395
|
+
// src/sql/errors.ts
|
|
396
|
+
var StorageError = class extends Error {
|
|
397
|
+
constructor(status, message, code) {
|
|
398
|
+
super(message);
|
|
399
|
+
this.status = status;
|
|
400
|
+
this.code = code;
|
|
401
|
+
this.name = "StorageError";
|
|
402
|
+
}
|
|
403
|
+
status;
|
|
404
|
+
code;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/validation.ts
|
|
408
|
+
function rowsEqual(left, right) {
|
|
409
|
+
if (Object.is(left, right)) return true;
|
|
410
|
+
if (left instanceof ArrayBuffer && right instanceof ArrayBuffer) {
|
|
411
|
+
const a2 = new Uint8Array(left), b2 = new Uint8Array(right);
|
|
412
|
+
return a2.length === b2.length && a2.every((value, i) => value === b2[i]);
|
|
413
|
+
}
|
|
414
|
+
if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) return false;
|
|
415
|
+
if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, i) => rowsEqual(value, right[i]));
|
|
416
|
+
const a = Object.keys(left).filter((key) => Reflect.get(left, key) !== void 0);
|
|
417
|
+
const b = Object.keys(right).filter((key) => Reflect.get(right, key) !== void 0);
|
|
418
|
+
return a.length === b.length && a.every((key) => Object.hasOwn(right, key) && rowsEqual(Reflect.get(left, key), Reflect.get(right, key)));
|
|
419
|
+
}
|
|
420
|
+
function record(value) {
|
|
421
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new StorageError(400, "storage row must be an object");
|
|
422
|
+
return value;
|
|
423
|
+
}
|
|
424
|
+
function validateStorageRow(store, value) {
|
|
425
|
+
if (store !== "notes" && store !== "block_revisions" && store !== "sessions" && store !== "block_index") return;
|
|
426
|
+
const row = record(value);
|
|
427
|
+
const positive = (field) => typeof row[field] === "number" && Number.isSafeInteger(row[field]) && row[field] > 0;
|
|
428
|
+
const required = (field) => typeof row[field] === "string" && row[field].length > 0;
|
|
429
|
+
if (store === "notes") {
|
|
430
|
+
if (!required("id") || typeof row.title !== "string" || typeof row.rawContent !== "string" || !positive("sourceRevision") || typeof row.createdAt !== "number" || !Number.isFinite(row.createdAt) || "segments" in row || "segmentIds" in row || row.date !== void 0 && (typeof row.date !== "number" || !Number.isFinite(row.date)) || row.seedOrigin !== void 0 && row.seedOrigin !== "seed" && row.seedOrigin !== "user" || row.seedVersion !== void 0 && !positive("seedVersion") || row.tags !== void 0 && (!Array.isArray(row.tags) || !row.tags.every((tag) => typeof tag === "string")) || ["type", "sourceId", "sourcePath", "catalog", "seedChunkId"].some((field) => row[field] !== void 0 && typeof row[field] !== "string")) {
|
|
431
|
+
throw new StorageError(400, "invalid whole Note", "invalid_note_blocks");
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
wodWikiCore.validateNoteBlocks(row.rawContent, record(row.blocks), row.sourceRevision);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
throw new StorageError(400, error instanceof Error ? error.message : "invalid block map", "invalid_note_blocks");
|
|
437
|
+
}
|
|
438
|
+
} else if (store === "block_revisions") {
|
|
439
|
+
if (!required("noteId") || !required("blockId") || !positive("version") || typeof row.rawContent !== "string" || typeof row.savedAt !== "number" || !Number.isFinite(row.savedAt) || !["wod", "query", "widget", "embed"].includes(String(row.kind)) || (row.kind === "wod" ? row.dialect !== "time" && row.dialect !== "log" : row.dialect !== void 0 || row.sport !== void 0) || row.sport !== void 0 && typeof row.sport !== "string") throw new StorageError(400, "invalid block revision", "invalid_note_blocks");
|
|
440
|
+
try {
|
|
441
|
+
wodWikiCore.validateNoteBlocks(row.rawContent, { [String(row.blockId)]: {
|
|
442
|
+
id: String(row.blockId),
|
|
443
|
+
kind: row.kind,
|
|
444
|
+
version: row.version,
|
|
445
|
+
sourceRevision: 1,
|
|
446
|
+
from: 0,
|
|
447
|
+
to: row.rawContent.length,
|
|
448
|
+
...row.dialect === void 0 ? {} : { dialect: row.dialect },
|
|
449
|
+
...row.sport === void 0 ? {} : { sport: row.sport }
|
|
450
|
+
} }, 1);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
throw new StorageError(400, error instanceof Error ? error.message : "invalid revision source", "invalid_note_blocks");
|
|
453
|
+
}
|
|
454
|
+
} else if (store === "sessions") {
|
|
455
|
+
if (!required("noteId") || !required("blockId") || !positive("blockVersion") || ["segmentId", "segmentVersion", "version", "rawContent", "script", "serializedScript", "source", "data", "logs"].some((field) => field in row)) {
|
|
456
|
+
throw new StorageError(400, "Session requires immutable block pin", "invalid_session_pin");
|
|
457
|
+
}
|
|
458
|
+
} else if (!required("noteId") || !required("blockId") || !positive("blockVersion") || !positive("sourceRevision") || row.id !== `${row.noteId}:${row.blockId}` || !Number.isSafeInteger(row.from) || !Number.isSafeInteger(row.to) || "segmentId" in row || "segmentVersion" in row || !["wod", "query", "widget", "embed"].includes(String(row.dataType)) || typeof row.rawContent !== "string" || typeof row.noteTitle !== "string" || typeof row.from !== "number" || typeof row.to !== "number" || row.from < 0 || row.to <= row.from) {
|
|
459
|
+
throw new StorageError(400, "invalid actionable block projection", "invalid_note_blocks");
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function validateNoteWrite(previous, value) {
|
|
463
|
+
if (previous === void 0 || rowsEqual(previous, value)) return;
|
|
464
|
+
const before = record(previous), after = record(value);
|
|
465
|
+
if (typeof before.sourceRevision !== "number" || typeof after.sourceRevision !== "number" || after.sourceRevision < before.sourceRevision || (!rowsEqual(before.blocks, after.blocks) || before.rawContent !== after.rawContent) && after.sourceRevision <= before.sourceRevision) throw new StorageError(409, "stale Note source revision", "transaction_conflict");
|
|
466
|
+
}
|
|
467
|
+
function validateSessionWrite(previous, value) {
|
|
468
|
+
if (previous === void 0) return;
|
|
469
|
+
const before = record(previous), after = record(value);
|
|
470
|
+
if (before.noteId !== after.noteId || before.blockId !== after.blockId || before.blockVersion !== after.blockVersion)
|
|
471
|
+
throw new StorageError(409, "Session pin is immutable", "invalid_session_pin");
|
|
472
|
+
}
|
|
473
|
+
async function validateStoredPins(notes, sessions, getRevisions) {
|
|
474
|
+
const keys = [];
|
|
475
|
+
for (const note of notes) for (const block of Object.values(note.blocks)) keys.push([note.id, block.id, block.version]);
|
|
476
|
+
for (const session of sessions) keys.push([session.noteId, session.blockId, session.blockVersion]);
|
|
477
|
+
const revisions = await getRevisions(keys);
|
|
478
|
+
let i = 0;
|
|
479
|
+
for (const note of notes) for (const block of Object.values(note.blocks)) {
|
|
480
|
+
const revision = revisions[i++];
|
|
481
|
+
if (!revision || revision.kind !== block.kind || revision.rawContent !== note.rawContent.slice(block.from, block.to) || revision.dialect !== block.dialect || revision.sport !== block.sport) throw new StorageError(409, "Note block has no matching immutable revision", "invalid_note_blocks");
|
|
482
|
+
}
|
|
483
|
+
for (let sessionIndex = 0; sessionIndex < sessions.length; sessionIndex++) {
|
|
484
|
+
const revision = revisions[i++];
|
|
485
|
+
if (!revision || revision.kind !== "wod") throw new StorageError(409, "Session pin must reference a saved workout revision", "invalid_session_pin");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
405
489
|
// src/api.ts
|
|
406
490
|
async function request(http, method, path, init) {
|
|
407
491
|
const qs = init?.params?.toString();
|
|
@@ -451,14 +535,14 @@ var ApiStorage = class {
|
|
|
451
535
|
if (!def) throw new Error(`ApiStorage: unknown store '${store}'`);
|
|
452
536
|
return def;
|
|
453
537
|
}
|
|
454
|
-
makeView(store, buffer, cleared) {
|
|
455
|
-
return new ApiStore(this.http, this.def(store), store, buffer, cleared, this.getUserId);
|
|
538
|
+
makeView(store, buffer, cleared, checks, mutable = false) {
|
|
539
|
+
return new ApiStore(this.http, this.def(store), store, buffer, cleared, this.getUserId, checks, mutable);
|
|
456
540
|
}
|
|
457
541
|
readonly(store) {
|
|
458
542
|
return this.makeView(store);
|
|
459
543
|
}
|
|
460
544
|
readwrite(store) {
|
|
461
|
-
return this.makeView(store);
|
|
545
|
+
return this.makeView(store, void 0, void 0, void 0, true);
|
|
462
546
|
}
|
|
463
547
|
async queryDomain(query) {
|
|
464
548
|
const res = await this.http.fetchImpl(`${this.http.baseUrl}/v1/query`, {
|
|
@@ -473,21 +557,28 @@ var ApiStorage = class {
|
|
|
473
557
|
if (!res.ok) throw new Error(`ApiStorage: POST /v1/query \u2192 ${res.status}`);
|
|
474
558
|
return parseDomainQueryResult(await res.json(), query.plan);
|
|
475
559
|
}
|
|
476
|
-
async transaction(
|
|
560
|
+
async transaction(stores, mode, fn) {
|
|
477
561
|
const buffer = /* @__PURE__ */ new Map();
|
|
478
562
|
const cleared = /* @__PURE__ */ new Set();
|
|
563
|
+
const checks = /* @__PURE__ */ new Map();
|
|
564
|
+
const scoped = new Set(stores);
|
|
565
|
+
const view = (store, mutable) => {
|
|
566
|
+
if (!scoped.has(store)) throw new Error("store outside transaction scope");
|
|
567
|
+
if (mutable && mode !== "readwrite") throw new Error("read-only transaction");
|
|
568
|
+
return this.makeView(store, buffer, cleared, checks, mutable);
|
|
569
|
+
};
|
|
479
570
|
const tx = {
|
|
480
|
-
readonly: (store) =>
|
|
481
|
-
readwrite: (store) =>
|
|
571
|
+
readonly: (store) => view(store, false),
|
|
572
|
+
readwrite: (store) => view(store, true)
|
|
482
573
|
};
|
|
483
574
|
const result = await fn(tx);
|
|
484
|
-
await this.flush(buffer, cleared);
|
|
575
|
+
await this.flush(buffer, cleared, checks);
|
|
485
576
|
return result;
|
|
486
577
|
}
|
|
487
578
|
/** Sends buffered ops as one server-side SQL transaction. */
|
|
488
|
-
async flush(buffer, cleared) {
|
|
579
|
+
async flush(buffer, cleared, checks) {
|
|
489
580
|
const ops = bufferToOps(STORE_DEFS.map((d) => d.name), buffer, cleared);
|
|
490
|
-
if (ops.length > 0) await request(this.http, "POST", "/tx", { body: { ops } });
|
|
581
|
+
if (ops.length > 0) await request(this.http, "POST", "/tx", { body: { ops: [...checks.values(), ...ops] } });
|
|
491
582
|
}
|
|
492
583
|
async wipe() {
|
|
493
584
|
await request(this.http, "POST", "/wipe");
|
|
@@ -496,13 +587,15 @@ var ApiStorage = class {
|
|
|
496
587
|
}
|
|
497
588
|
};
|
|
498
589
|
var ApiStore = class {
|
|
499
|
-
constructor(http, def, store, buffer, cleared, getUserId) {
|
|
590
|
+
constructor(http, def, store, buffer, cleared, getUserId, checks, mutable = false) {
|
|
500
591
|
this.http = http;
|
|
501
592
|
this.def = def;
|
|
502
593
|
this.store = store;
|
|
503
594
|
this.buffer = buffer;
|
|
504
595
|
this.cleared = cleared;
|
|
505
596
|
this.getUserId = getUserId;
|
|
597
|
+
this.checks = checks;
|
|
598
|
+
this.mutable = mutable;
|
|
506
599
|
}
|
|
507
600
|
http;
|
|
508
601
|
def;
|
|
@@ -510,6 +603,8 @@ var ApiStore = class {
|
|
|
510
603
|
buffer;
|
|
511
604
|
cleared;
|
|
512
605
|
getUserId;
|
|
606
|
+
checks;
|
|
607
|
+
mutable;
|
|
513
608
|
/** True when this tx has buffered writes for this store. */
|
|
514
609
|
dirty() {
|
|
515
610
|
return isDirty(this.store, this.buffer, this.cleared);
|
|
@@ -528,7 +623,16 @@ var ApiStore = class {
|
|
|
528
623
|
const state = bufferedGet(this.store, k, this.buffer);
|
|
529
624
|
if (state) return state.hit ? state.value : void 0;
|
|
530
625
|
if (this.cleared?.has(this.store)) return void 0;
|
|
531
|
-
|
|
626
|
+
const value = await remoteGet(this.http, this.store, k);
|
|
627
|
+
const slot = bufferSlot(this.store, k);
|
|
628
|
+
if (!this.checks?.has(slot)) this.checks?.set(slot, { op: "check", store: this.store, key: k, value: structuredClone(value ?? null) });
|
|
629
|
+
return value;
|
|
630
|
+
}
|
|
631
|
+
async getMany(keys) {
|
|
632
|
+
return Promise.all(keys.map((key) => this.get(key)));
|
|
633
|
+
}
|
|
634
|
+
async getAllFromIndexMany(indexName, keys) {
|
|
635
|
+
return Promise.all(keys.map((key) => this.getAllFromIndex(indexName, key)));
|
|
532
636
|
}
|
|
533
637
|
async getAll(query, count) {
|
|
534
638
|
const range = rangeFromIDB(query);
|
|
@@ -549,18 +653,28 @@ var ApiStore = class {
|
|
|
549
653
|
return this.overlay(await this.serverRows(`index/${encodeURIComponent(indexName)}`, range), void 0, range, indexName).length;
|
|
550
654
|
}
|
|
551
655
|
/** Server rows for a list path, or none when this tx cleared the store. */
|
|
552
|
-
serverRows(path, range, count) {
|
|
553
|
-
if (this.cleared?.has(this.store)) return
|
|
554
|
-
|
|
656
|
+
async serverRows(path, range, count) {
|
|
657
|
+
if (this.cleared?.has(this.store)) return [];
|
|
658
|
+
const rows = await remoteList(this.http, this.store, path, range, count);
|
|
659
|
+
for (const row of rows) {
|
|
660
|
+
const key = keyFromValue(this.def, row), slot = bufferSlot(this.store, key);
|
|
661
|
+
if (!this.checks?.has(slot)) this.checks?.set(slot, { op: "check", store: this.store, key, value: structuredClone(row) });
|
|
662
|
+
}
|
|
663
|
+
return rows;
|
|
555
664
|
}
|
|
556
665
|
/** Applies buffered puts and deletes before range filtering, ordering and paging. */
|
|
557
666
|
overlay(rows, count, range, indexName) {
|
|
558
667
|
return overlayRows(this.def, this.store, this.buffer, this.cleared, rows, count, range, indexName);
|
|
559
668
|
}
|
|
560
669
|
async put(value, _key) {
|
|
670
|
+
if (!this.mutable) throw new Error("read-only store");
|
|
561
671
|
const userId = this.getUserId?.();
|
|
562
672
|
const stored = userId ? stampUserOwned(this.store, value, userId) : value;
|
|
563
673
|
const key = keyFromValue(this.def, stored);
|
|
674
|
+
if (this.store === "block_revisions") {
|
|
675
|
+
const previous = this.buffer?.get(bufferSlot(this.store, key));
|
|
676
|
+
if (previous?.kind === "put" && !rowsEqual(previous.value, stored)) throw new Error("immutable block revision collision");
|
|
677
|
+
}
|
|
564
678
|
if (this.buffer) {
|
|
565
679
|
this.buffer.set(bufferSlot(this.store, key), { kind: "put", value: stored });
|
|
566
680
|
return key;
|
|
@@ -568,7 +682,21 @@ var ApiStore = class {
|
|
|
568
682
|
await request(this.http, "POST", "/tx", { body: { ops: [{ op: "put", store: this.store, value: stored }] } });
|
|
569
683
|
return key;
|
|
570
684
|
}
|
|
685
|
+
async putMany(values, _keys) {
|
|
686
|
+
if (!this.mutable) throw new Error("read-only store");
|
|
687
|
+
if (this.buffer) {
|
|
688
|
+
const keys2 = [];
|
|
689
|
+
for (const value of values) keys2.push(await this.put(value));
|
|
690
|
+
return keys2;
|
|
691
|
+
}
|
|
692
|
+
const stored = values.map((value) => this.getUserId ? stampUserOwned(this.store, value, this.getUserId()) : value);
|
|
693
|
+
const keys = stored.map((value) => keyFromValue(this.def, value));
|
|
694
|
+
await request(this.http, "POST", "/tx", { body: { ops: stored.map((value) => ({ op: "put", store: this.store, value })) } });
|
|
695
|
+
return keys;
|
|
696
|
+
}
|
|
571
697
|
async delete(key) {
|
|
698
|
+
if (!this.mutable) throw new Error("read-only store");
|
|
699
|
+
if (this.store === "block_revisions") throw new Error("block history requires explicit wipe");
|
|
572
700
|
const range = isRange(key) ? key : void 0;
|
|
573
701
|
if (range) {
|
|
574
702
|
if (this.buffer) {
|
|
@@ -590,6 +718,8 @@ var ApiStore = class {
|
|
|
590
718
|
await remoteDelete(this.http, this.store, k);
|
|
591
719
|
}
|
|
592
720
|
async clear() {
|
|
721
|
+
if (!this.mutable) throw new Error("read-only store");
|
|
722
|
+
if (this.store === "block_revisions") throw new Error("block history requires explicit wipe");
|
|
593
723
|
if (this.buffer) {
|
|
594
724
|
this.cleared?.add(this.store);
|
|
595
725
|
const prefix = `${this.store}\0`;
|
|
@@ -618,6 +748,11 @@ exports.keyFromValue = keyFromValue;
|
|
|
618
748
|
exports.keyInRange = keyInRange;
|
|
619
749
|
exports.parseDomainQueryResult = parseDomainQueryResult;
|
|
620
750
|
exports.rangeFromIDB = rangeFromIDB;
|
|
751
|
+
exports.rowsEqual = rowsEqual;
|
|
621
752
|
exports.stampUserOwned = stampUserOwned;
|
|
753
|
+
exports.validateNoteWrite = validateNoteWrite;
|
|
754
|
+
exports.validateSessionWrite = validateSessionWrite;
|
|
755
|
+
exports.validateStorageRow = validateStorageRow;
|
|
756
|
+
exports.validateStoredPins = validateStoredPins;
|
|
622
757
|
//# sourceMappingURL=index.cjs.map
|
|
623
758
|
//# sourceMappingURL=index.cjs.map
|