@bitcobblers/wod-wiki-storage 0.0.0-stage → 0.35.2116
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 +405 -0
- package/dist/chunk-DAGT3P7M.mjs.map +1 -0
- package/dist/index.cjs +623 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.mjs +206 -0
- package/dist/index.mjs.map +1 -0
- package/dist/sql/index.cjs +1779 -0
- package/dist/sql/index.cjs.map +1 -0
- package/dist/sql/index.d.cts +220 -0
- package/dist/sql/index.d.ts +220 -0
- package/dist/sql/index.mjs +1409 -0
- package/dist/sql/index.mjs.map +1 -0
- package/dist/wire-C7ZtlBKe.d.cts +489 -0
- package/dist/wire-C7ZtlBKe.d.ts +489 -0
- package/package.json +50 -4
- package/README.md +0 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/schema.ts
|
|
4
|
+
var DB_VERSION = 28;
|
|
5
|
+
var COLUMN_TYPES = {
|
|
6
|
+
notes: { date: "number", createdAt: "number" },
|
|
7
|
+
page: { createdAt: "number" },
|
|
8
|
+
// page.date is a civil 'YYYY-MM-DD' string → TEXT
|
|
9
|
+
page_notes: { position: "number", createdAt: "number" },
|
|
10
|
+
tags: { createdAt: "number" },
|
|
11
|
+
tag_types: { createdAt: "number" },
|
|
12
|
+
note_tags: {},
|
|
13
|
+
segments: {
|
|
14
|
+
version: "number",
|
|
15
|
+
position: "number",
|
|
16
|
+
createdAt: "number",
|
|
17
|
+
updatedAt: "number",
|
|
18
|
+
isHistory: "boolean"
|
|
19
|
+
},
|
|
20
|
+
sessions: {
|
|
21
|
+
segmentVersion: "number",
|
|
22
|
+
version: "number",
|
|
23
|
+
startTime: "number",
|
|
24
|
+
endTime: "number",
|
|
25
|
+
duration: "number",
|
|
26
|
+
roundsCompleted: "number",
|
|
27
|
+
totalRounds: "number",
|
|
28
|
+
repsCompleted: "number",
|
|
29
|
+
completed: "boolean",
|
|
30
|
+
createdAt: "number"
|
|
31
|
+
},
|
|
32
|
+
attachments: { createdAt: "number" },
|
|
33
|
+
events: {
|
|
34
|
+
timestamp: "number",
|
|
35
|
+
segmentVersion: "number",
|
|
36
|
+
line: "number",
|
|
37
|
+
sourceStatementId: "number",
|
|
38
|
+
stackLevel: "number",
|
|
39
|
+
"timeSpan.started": "number",
|
|
40
|
+
"timeSpan.ended": "number"
|
|
41
|
+
},
|
|
42
|
+
field_catalog: {},
|
|
43
|
+
field_sources: {},
|
|
44
|
+
field_values: {},
|
|
45
|
+
field_catalog_meta: {},
|
|
46
|
+
efforts: { "baseAttributes.met": "number" },
|
|
47
|
+
block_index: {
|
|
48
|
+
segmentVersion: "number",
|
|
49
|
+
position: "number",
|
|
50
|
+
createdAt: "number",
|
|
51
|
+
isStatic: "boolean"
|
|
52
|
+
},
|
|
53
|
+
block_efforts: { createdAt: "number", isStatic: "boolean" },
|
|
54
|
+
meta: {},
|
|
55
|
+
memberships: { createdAt: "number" }
|
|
56
|
+
};
|
|
57
|
+
var STORE_DEFS = [
|
|
58
|
+
// 1. notes
|
|
59
|
+
{ name: "notes", keyPath: ["id"], indexes: [
|
|
60
|
+
{ name: "by-date", keyPath: ["date"] },
|
|
61
|
+
{ name: "by-created", keyPath: ["createdAt"] },
|
|
62
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
63
|
+
] },
|
|
64
|
+
// 2. page
|
|
65
|
+
{ name: "page", keyPath: ["id"], indexes: [
|
|
66
|
+
{ name: "by-date", keyPath: ["date"], unique: true },
|
|
67
|
+
{ name: "by-slug", keyPath: ["slug"], unique: true },
|
|
68
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
69
|
+
] },
|
|
70
|
+
// 2b. page_notes (V22)
|
|
71
|
+
{ name: "page_notes", keyPath: ["id"], indexes: [
|
|
72
|
+
{ name: "by-page", keyPath: ["pageId"] },
|
|
73
|
+
{ name: "by-note", keyPath: ["noteId"] },
|
|
74
|
+
{ name: "by-page-note", keyPath: ["pageId", "noteId"], unique: true },
|
|
75
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
76
|
+
] },
|
|
77
|
+
// 3. tags
|
|
78
|
+
{ name: "tags", keyPath: ["id"], indexes: [
|
|
79
|
+
{ name: "by-label", keyPath: ["label"], unique: true },
|
|
80
|
+
{ name: "by-type", keyPath: ["type"] },
|
|
81
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
82
|
+
] },
|
|
83
|
+
// 3a. tag_types (V23)
|
|
84
|
+
{ name: "tag_types", keyPath: ["id"], indexes: [
|
|
85
|
+
{ name: "by-name", keyPath: ["name"], unique: true },
|
|
86
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
87
|
+
] },
|
|
88
|
+
// 4. note_tags
|
|
89
|
+
{ name: "note_tags", keyPath: ["id"], indexes: [
|
|
90
|
+
{ name: "by-note", keyPath: ["noteId"] },
|
|
91
|
+
{ name: "by-tag", keyPath: ["tagId"] },
|
|
92
|
+
{ name: "by-note-tag", keyPath: ["noteId", "tagId"], unique: true },
|
|
93
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
94
|
+
] },
|
|
95
|
+
// 5. segments
|
|
96
|
+
{ name: "segments", keyPath: ["id", "version"], indexes: [
|
|
97
|
+
{ name: "by-note", keyPath: ["noteId"] },
|
|
98
|
+
{ name: "by-type", keyPath: ["dataType"] },
|
|
99
|
+
{ name: "by-page", keyPath: ["pageId"] },
|
|
100
|
+
{ name: "by-history", keyPath: ["isHistory"] },
|
|
101
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
102
|
+
] },
|
|
103
|
+
// 6. sessions
|
|
104
|
+
{ name: "sessions", keyPath: ["id"], indexes: [
|
|
105
|
+
{ name: "by-segment", keyPath: ["segmentId"] },
|
|
106
|
+
{ name: "by-note", keyPath: ["noteId"] },
|
|
107
|
+
{ name: "by-completed", keyPath: ["createdAt"] },
|
|
108
|
+
{ name: "by-content", keyPath: ["blockContentId"] },
|
|
109
|
+
{ name: "by-block", keyPath: ["blockId"] },
|
|
110
|
+
{ name: "by-page", keyPath: ["pageId"] },
|
|
111
|
+
{ name: "by-origin", keyPath: ["origin"] },
|
|
112
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
113
|
+
] },
|
|
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
|
+
// 8. attachments
|
|
118
|
+
{ name: "attachments", keyPath: ["id"], indexes: [
|
|
119
|
+
{ name: "by-note", keyPath: ["noteId"] },
|
|
120
|
+
{ name: "by-time", keyPath: ["createdAt"] },
|
|
121
|
+
{ name: "by-page", keyPath: ["pageId"] },
|
|
122
|
+
{ name: "by-result", keyPath: ["resultId"] },
|
|
123
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
124
|
+
] },
|
|
125
|
+
// 9. events
|
|
126
|
+
{ name: "events", keyPath: ["id"], indexes: [
|
|
127
|
+
{ name: "by-timestamp", keyPath: ["timestamp"] },
|
|
128
|
+
{ name: "by-result-grain", keyPath: ["resultId", "grain"] },
|
|
129
|
+
{ name: "by-content-grain", keyPath: ["blockContentId", "grain"] },
|
|
130
|
+
{ name: "by-effort", keyPath: ["effortSlug"] },
|
|
131
|
+
{ name: "by-outputType", keyPath: ["outputType"] },
|
|
132
|
+
{ name: "by-grain", keyPath: ["grain"] },
|
|
133
|
+
{ name: "by-metric-date", keyPath: ["metricDateKeys"], multiEntry: true },
|
|
134
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
135
|
+
] },
|
|
136
|
+
// 10. field catalog
|
|
137
|
+
{ name: "field_catalog", keyPath: ["id"], indexes: [
|
|
138
|
+
{ name: "by-path", keyPath: ["path"] }
|
|
139
|
+
] },
|
|
140
|
+
{ name: "field_sources", keyPath: ["id"], indexes: [] },
|
|
141
|
+
{ name: "field_values", keyPath: ["key"], indexes: [
|
|
142
|
+
{ name: "by-field", keyPath: ["fieldId"] }
|
|
143
|
+
] },
|
|
144
|
+
{ name: "field_catalog_meta", keyPath: ["id"], indexes: [] },
|
|
145
|
+
// 11. efforts
|
|
146
|
+
{ name: "efforts", keyPath: ["slug"], indexes: [
|
|
147
|
+
{ name: "by-discipline", keyPath: ["baseAttributes.discipline"] },
|
|
148
|
+
{ name: "by-source", keyPath: ["registrySource"] },
|
|
149
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
150
|
+
] },
|
|
151
|
+
// 12. block_index
|
|
152
|
+
{ name: "block_index", keyPath: ["id"], indexes: [
|
|
153
|
+
{ name: "by-note", keyPath: ["noteId"] },
|
|
154
|
+
{ name: "by-content", keyPath: ["blockContentId"] },
|
|
155
|
+
{ name: "by-type", keyPath: ["dataType"] },
|
|
156
|
+
{ name: "by-source", keyPath: ["sourceId"] },
|
|
157
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
158
|
+
] },
|
|
159
|
+
// 13. block_efforts
|
|
160
|
+
{ name: "block_efforts", keyPath: ["id"], indexes: [
|
|
161
|
+
{ name: "by-note", keyPath: ["noteId"] },
|
|
162
|
+
{ name: "by-effort", keyPath: ["effortSlug"] },
|
|
163
|
+
{ name: "by-block", keyPath: ["blockContentId"] },
|
|
164
|
+
{ name: "by-user", keyPath: ["userId"] }
|
|
165
|
+
] },
|
|
166
|
+
// 14. meta
|
|
167
|
+
{ name: "meta", keyPath: ["key"], indexes: [] },
|
|
168
|
+
// 15. memberships (V25, system — survives wipe; truth lives in
|
|
169
|
+
// localStorage locally, in the SQL table on the API)
|
|
170
|
+
{ name: "memberships", keyPath: ["id"], indexes: [], system: true }
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
// src/membership.ts
|
|
174
|
+
var DEFAULT_MEMBERSHIP_ID = "default";
|
|
175
|
+
function createDefaultMembership(now = Date.now()) {
|
|
176
|
+
return { id: DEFAULT_MEMBERSHIP_ID, displayName: "Anonymous", createdAt: now };
|
|
177
|
+
}
|
|
178
|
+
var USER_OWNED_STORES = [
|
|
179
|
+
"notes",
|
|
180
|
+
"page",
|
|
181
|
+
"page_notes",
|
|
182
|
+
"tags",
|
|
183
|
+
"tag_types",
|
|
184
|
+
"note_tags",
|
|
185
|
+
"segments",
|
|
186
|
+
"sessions",
|
|
187
|
+
"attachments",
|
|
188
|
+
"events",
|
|
189
|
+
"efforts",
|
|
190
|
+
"block_index",
|
|
191
|
+
"block_efforts"
|
|
192
|
+
];
|
|
193
|
+
var STAMPABLE = Object.fromEntries(
|
|
194
|
+
USER_OWNED_STORES.map((s) => [s, true])
|
|
195
|
+
);
|
|
196
|
+
function stampUserOwned(store, value, userId) {
|
|
197
|
+
if (!STAMPABLE[store]) return value;
|
|
198
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
199
|
+
const row = value;
|
|
200
|
+
if (row.seedOrigin === "seed") return value;
|
|
201
|
+
if (row.userId === userId) return value;
|
|
202
|
+
return { ...row, userId };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/wire.ts
|
|
206
|
+
function isRange(arg) {
|
|
207
|
+
return arg !== null && typeof arg === "object" && !Array.isArray(arg) && ("only" in arg || "lower" in arg || "upper" in arg || "lowerOpen" in arg || "upperOpen" in arg);
|
|
208
|
+
}
|
|
209
|
+
function extractKeyPart(value, dottedPath) {
|
|
210
|
+
let cur = value;
|
|
211
|
+
for (const part of dottedPath.split(".")) {
|
|
212
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
213
|
+
const row = cur;
|
|
214
|
+
if (!(part in row)) return void 0;
|
|
215
|
+
cur = row[part];
|
|
216
|
+
}
|
|
217
|
+
if (cur === null || typeof cur === "object" || cur === void 0) return void 0;
|
|
218
|
+
return cur;
|
|
219
|
+
}
|
|
220
|
+
function keyFromValue(def, value) {
|
|
221
|
+
const parts = def.keyPath.map((p) => extractKeyPart(value, p));
|
|
222
|
+
for (const [i, part] of parts.entries()) {
|
|
223
|
+
if (part === void 0) {
|
|
224
|
+
throw new Error(`keyFromValue(${def.name}): keyPath part '${def.keyPath[i]}' missing on value`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const key = parts;
|
|
228
|
+
return key.length === 1 ? key[0] : key;
|
|
229
|
+
}
|
|
230
|
+
function encodeKey(key) {
|
|
231
|
+
return encodeURIComponent(JSON.stringify(key));
|
|
232
|
+
}
|
|
233
|
+
function rangeFromIDB(query) {
|
|
234
|
+
if (query === void 0) return void 0;
|
|
235
|
+
if (!isRange(query)) {
|
|
236
|
+
const key = query;
|
|
237
|
+
return { only: key };
|
|
238
|
+
}
|
|
239
|
+
if (query.only !== void 0) return query;
|
|
240
|
+
const wire = {};
|
|
241
|
+
if (query.lower !== void 0 && query.lower !== null) {
|
|
242
|
+
wire.lower = query.lower;
|
|
243
|
+
wire.lowerOpen = query.lowerOpen === true;
|
|
244
|
+
}
|
|
245
|
+
if (query.upper !== void 0 && query.upper !== null) {
|
|
246
|
+
wire.upper = query.upper;
|
|
247
|
+
wire.upperOpen = query.upperOpen === true;
|
|
248
|
+
}
|
|
249
|
+
return wire.lower !== void 0 || wire.upper !== void 0 ? wire : void 0;
|
|
250
|
+
}
|
|
251
|
+
function compareKeys(a, b) {
|
|
252
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
253
|
+
if (!Array.isArray(a) || !Array.isArray(b)) return Array.isArray(a) ? 1 : -1;
|
|
254
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
255
|
+
const c = compareKeys(a[i], b[i]);
|
|
256
|
+
if (c !== 0) return c;
|
|
257
|
+
}
|
|
258
|
+
return a.length - b.length;
|
|
259
|
+
}
|
|
260
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
261
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
262
|
+
}
|
|
263
|
+
if (typeof a === "number" || typeof b === "number") return typeof a === "number" ? -1 : 1;
|
|
264
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
265
|
+
}
|
|
266
|
+
function keyInRange(key, range) {
|
|
267
|
+
if (range.only !== void 0) return compareKeys(key, range.only) === 0;
|
|
268
|
+
if (range.lower !== void 0) {
|
|
269
|
+
const c = compareKeys(key, range.lower);
|
|
270
|
+
if (c < 0 || c === 0 && range.lowerOpen === true) return false;
|
|
271
|
+
}
|
|
272
|
+
if (range.upper !== void 0) {
|
|
273
|
+
const c = compareKeys(key, range.upper);
|
|
274
|
+
if (c > 0 || c === 0 && range.upperOpen === true) return false;
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/domain.ts
|
|
280
|
+
var DOMAIN_PROJECTION_VERSION = 1;
|
|
281
|
+
function isObject(value) {
|
|
282
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
283
|
+
}
|
|
284
|
+
function optionalStrings(row, fields) {
|
|
285
|
+
return fields.every((field) => row[field] === void 0 || typeof row[field] === "string");
|
|
286
|
+
}
|
|
287
|
+
function finiteNumbers(row, fields) {
|
|
288
|
+
return fields.every((field) => typeof row[field] === "number" && Number.isFinite(row[field]));
|
|
289
|
+
}
|
|
290
|
+
function isNote(value) {
|
|
291
|
+
return isObject(value) && typeof value.id === "string" && typeof value.title === "string" && 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"));
|
|
292
|
+
}
|
|
293
|
+
function isBlock(value) {
|
|
294
|
+
return isObject(value) && typeof value.id === "string" && typeof value.noteId === "string" && typeof value.segmentId === "string" && finiteNumbers(value, ["segmentVersion", "createdAt"]) && typeof value.dataType === "string" && 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");
|
|
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");
|
|
298
|
+
}
|
|
299
|
+
function isTag(value) {
|
|
300
|
+
return isObject(value) && typeof value.id === "string" && typeof value.label === "string" && finiteNumbers(value, ["createdAt"]) && optionalStrings(value, ["type"]);
|
|
301
|
+
}
|
|
302
|
+
function isPage(value) {
|
|
303
|
+
return isObject(value) && typeof value.id === "string" && finiteNumbers(value, ["createdAt"]) && optionalStrings(value, ["date", "slug", "title"]);
|
|
304
|
+
}
|
|
305
|
+
function isLink(value) {
|
|
306
|
+
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
|
+
}
|
|
308
|
+
function isEntry(value) {
|
|
309
|
+
return isObject(value) && isNote(value.note) && Array.isArray(value.segments) && value.segments.every(isSegment) && 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
|
+
}
|
|
311
|
+
function parseDomainQueryResult(value, plan) {
|
|
312
|
+
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) {
|
|
313
|
+
throw new Error("ApiStorage: invalid or stale domain projection response");
|
|
314
|
+
}
|
|
315
|
+
const counts = { projectionVersion: DOMAIN_PROJECTION_VERSION, selectedCount: value.selectedCount, matchedCount: value.matchedCount };
|
|
316
|
+
switch (plan) {
|
|
317
|
+
case "notes":
|
|
318
|
+
if (value.rows.every(isNote)) return { ...counts, plan, rows: value.rows };
|
|
319
|
+
break;
|
|
320
|
+
case "blocks":
|
|
321
|
+
if (value.rows.every(isBlock)) return { ...counts, plan, rows: value.rows };
|
|
322
|
+
break;
|
|
323
|
+
case "entries":
|
|
324
|
+
if (value.rows.every(isEntry)) return { ...counts, plan, rows: value.rows };
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
throw new Error("ApiStorage: invalid domain query rows");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/overlay.ts
|
|
331
|
+
var bufferSlot = (store, key) => `${store}\0${JSON.stringify(key)}`;
|
|
332
|
+
function isDirty(store, buffer, cleared) {
|
|
333
|
+
if (cleared?.has(store)) return true;
|
|
334
|
+
if (!buffer) return false;
|
|
335
|
+
const prefix = `${store}\0`;
|
|
336
|
+
for (const slot of buffer.keys()) {
|
|
337
|
+
if (slot.startsWith(prefix)) return true;
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
function bufferedGet(store, key, buffer) {
|
|
342
|
+
if (!buffer) return void 0;
|
|
343
|
+
const entry = buffer.get(bufferSlot(store, key));
|
|
344
|
+
if (!entry) return void 0;
|
|
345
|
+
return entry.kind === "put" ? { hit: true, value: entry.value } : { hit: false };
|
|
346
|
+
}
|
|
347
|
+
function overlayRows(def, store, buffer, cleared, rows, count, range, indexName) {
|
|
348
|
+
if (!buffer || !isDirty(store, buffer, cleared)) return count !== void 0 ? rows.slice(0, count) : rows;
|
|
349
|
+
const out = /* @__PURE__ */ new Map();
|
|
350
|
+
if (!cleared?.has(store)) {
|
|
351
|
+
for (const row of rows) out.set(JSON.stringify(keyFromValue(def, row)), row);
|
|
352
|
+
}
|
|
353
|
+
const prefix = `${store}\0`;
|
|
354
|
+
for (const [slot, entry] of buffer) {
|
|
355
|
+
if (!slot.startsWith(prefix)) continue;
|
|
356
|
+
if (entry.kind === "put") out.set(slot.slice(prefix.length), entry.value);
|
|
357
|
+
else out.delete(slot.slice(prefix.length));
|
|
358
|
+
}
|
|
359
|
+
const index = indexName ? def.indexes.find((i) => i.name === indexName) : void 0;
|
|
360
|
+
if (indexName && !index) throw new Error(`unknown index '${indexName}' for ${store}`);
|
|
361
|
+
const keyOf = (row) => {
|
|
362
|
+
if (!index) return keyFromValue(def, row);
|
|
363
|
+
const parts = index.keyPath.map((path) => extractKeyPart(row, path));
|
|
364
|
+
if (parts.some((part) => part === void 0)) return void 0;
|
|
365
|
+
const keys = parts.filter((part) => part !== void 0);
|
|
366
|
+
return keys.length === 1 ? keys[0] : keys;
|
|
367
|
+
};
|
|
368
|
+
const matches = (row) => {
|
|
369
|
+
if (index?.multiEntry) {
|
|
370
|
+
let values = row;
|
|
371
|
+
for (const part of index.keyPath[0].split(".")) {
|
|
372
|
+
if (values === null || typeof values !== "object" || !(part in values)) return false;
|
|
373
|
+
values = Reflect.get(values, part);
|
|
374
|
+
}
|
|
375
|
+
return Array.isArray(values) && values.some((value) => (typeof value === "string" || typeof value === "number") && (!range || keyInRange(value, range)));
|
|
376
|
+
}
|
|
377
|
+
const key = keyOf(row);
|
|
378
|
+
return key !== void 0 && (!range || keyInRange(key, range));
|
|
379
|
+
};
|
|
380
|
+
const merged = [...out.values()].filter(matches);
|
|
381
|
+
merged.sort((a, b) => {
|
|
382
|
+
const primary = compareKeys(keyFromValue(def, a), keyFromValue(def, b));
|
|
383
|
+
if (index?.multiEntry) return primary;
|
|
384
|
+
const ak = keyOf(a), bk = keyOf(b);
|
|
385
|
+
return ak !== void 0 && bk !== void 0 ? compareKeys(ak, bk) || primary : primary;
|
|
386
|
+
});
|
|
387
|
+
return count !== void 0 ? merged.slice(0, count) : merged;
|
|
388
|
+
}
|
|
389
|
+
function bufferToOps(storeNames, buffer, cleared) {
|
|
390
|
+
const known = new Set(storeNames);
|
|
391
|
+
const ops = [];
|
|
392
|
+
for (const store of cleared) {
|
|
393
|
+
if (known.has(store)) ops.push({ op: "clear", store });
|
|
394
|
+
}
|
|
395
|
+
for (const [slot, entry] of buffer) {
|
|
396
|
+
const sep = slot.indexOf("\0");
|
|
397
|
+
const store = slot.slice(0, sep);
|
|
398
|
+
if (!known.has(store)) continue;
|
|
399
|
+
if (entry.kind === "put") ops.push({ op: "put", store, value: entry.value });
|
|
400
|
+
else ops.push({ op: "delete", store, key: JSON.parse(slot.slice(sep + 1)) });
|
|
401
|
+
}
|
|
402
|
+
return ops;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/api.ts
|
|
406
|
+
async function request(http, method, path, init) {
|
|
407
|
+
const qs = init?.params?.toString();
|
|
408
|
+
const res = await http.fetchImpl(`${http.baseUrl}/v1${path}${qs ? `?${qs}` : ""}`, {
|
|
409
|
+
method,
|
|
410
|
+
headers: init?.body !== void 0 ? { "content-type": "application/json" } : void 0,
|
|
411
|
+
body: init?.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
412
|
+
});
|
|
413
|
+
if (!res.ok) throw new Error(`ApiStorage: ${method} /v1${path} \u2192 ${res.status}`);
|
|
414
|
+
return res;
|
|
415
|
+
}
|
|
416
|
+
async function remoteGet(http, store, key) {
|
|
417
|
+
const res = await http.fetchImpl(`${http.baseUrl}/v1/${store}/get?key=${encodeKey(key)}`);
|
|
418
|
+
if (res.status === 404) return void 0;
|
|
419
|
+
if (!res.ok) throw new Error(`ApiStorage: GET /v1/${store}/get \u2192 ${res.status}`);
|
|
420
|
+
return await res.json();
|
|
421
|
+
}
|
|
422
|
+
async function remoteList(http, store, path, range, count) {
|
|
423
|
+
const params = new URLSearchParams();
|
|
424
|
+
if (range) params.set("range", JSON.stringify(range));
|
|
425
|
+
if (count !== void 0) params.set("count", String(count));
|
|
426
|
+
const res = await request(http, "GET", `/${store}/${path}`, { params });
|
|
427
|
+
return await res.json();
|
|
428
|
+
}
|
|
429
|
+
async function remoteCount(http, store, range, indexName) {
|
|
430
|
+
const params = new URLSearchParams();
|
|
431
|
+
if (range) params.set("range", JSON.stringify(range));
|
|
432
|
+
if (indexName !== void 0) params.set("index", indexName);
|
|
433
|
+
const res = await request(http, "GET", `/${store}/count`, { params });
|
|
434
|
+
return (await res.json()).count;
|
|
435
|
+
}
|
|
436
|
+
async function remoteDelete(http, store, keyOrRange) {
|
|
437
|
+
await request(http, "DELETE", `/${store}`, { params: new URLSearchParams({ key: JSON.stringify(keyOrRange) }) });
|
|
438
|
+
}
|
|
439
|
+
var ApiStorage = class {
|
|
440
|
+
http;
|
|
441
|
+
defs;
|
|
442
|
+
/** Injected identity: user-owned rows are stamped with this on write. */
|
|
443
|
+
getUserId;
|
|
444
|
+
constructor(baseUrl = "/api", fetchImpl = globalThis.fetch.bind(globalThis), options = {}) {
|
|
445
|
+
this.http = { baseUrl, fetchImpl };
|
|
446
|
+
this.getUserId = options.getUserId;
|
|
447
|
+
this.defs = Object.fromEntries(STORE_DEFS.map((d) => [d.name, d]));
|
|
448
|
+
}
|
|
449
|
+
def(store) {
|
|
450
|
+
const def = this.defs[store];
|
|
451
|
+
if (!def) throw new Error(`ApiStorage: unknown store '${store}'`);
|
|
452
|
+
return def;
|
|
453
|
+
}
|
|
454
|
+
makeView(store, buffer, cleared) {
|
|
455
|
+
return new ApiStore(this.http, this.def(store), store, buffer, cleared, this.getUserId);
|
|
456
|
+
}
|
|
457
|
+
readonly(store) {
|
|
458
|
+
return this.makeView(store);
|
|
459
|
+
}
|
|
460
|
+
readwrite(store) {
|
|
461
|
+
return this.makeView(store);
|
|
462
|
+
}
|
|
463
|
+
async queryDomain(query) {
|
|
464
|
+
const res = await this.http.fetchImpl(`${this.http.baseUrl}/v1/query`, {
|
|
465
|
+
method: "POST",
|
|
466
|
+
headers: { "content-type": "application/json" },
|
|
467
|
+
body: JSON.stringify(query)
|
|
468
|
+
});
|
|
469
|
+
if (res.status === 409) {
|
|
470
|
+
const error = await res.json();
|
|
471
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "domain_projection_unavailable") return void 0;
|
|
472
|
+
}
|
|
473
|
+
if (!res.ok) throw new Error(`ApiStorage: POST /v1/query \u2192 ${res.status}`);
|
|
474
|
+
return parseDomainQueryResult(await res.json(), query.plan);
|
|
475
|
+
}
|
|
476
|
+
async transaction(_stores, _mode, fn) {
|
|
477
|
+
const buffer = /* @__PURE__ */ new Map();
|
|
478
|
+
const cleared = /* @__PURE__ */ new Set();
|
|
479
|
+
const tx = {
|
|
480
|
+
readonly: (store) => this.makeView(store, buffer, cleared),
|
|
481
|
+
readwrite: (store) => this.makeView(store, buffer, cleared)
|
|
482
|
+
};
|
|
483
|
+
const result = await fn(tx);
|
|
484
|
+
await this.flush(buffer, cleared);
|
|
485
|
+
return result;
|
|
486
|
+
}
|
|
487
|
+
/** Sends buffered ops as one server-side SQL transaction. */
|
|
488
|
+
async flush(buffer, cleared) {
|
|
489
|
+
const ops = bufferToOps(STORE_DEFS.map((d) => d.name), buffer, cleared);
|
|
490
|
+
if (ops.length > 0) await request(this.http, "POST", "/tx", { body: { ops } });
|
|
491
|
+
}
|
|
492
|
+
async wipe() {
|
|
493
|
+
await request(this.http, "POST", "/wipe");
|
|
494
|
+
}
|
|
495
|
+
async close() {
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
var ApiStore = class {
|
|
499
|
+
constructor(http, def, store, buffer, cleared, getUserId) {
|
|
500
|
+
this.http = http;
|
|
501
|
+
this.def = def;
|
|
502
|
+
this.store = store;
|
|
503
|
+
this.buffer = buffer;
|
|
504
|
+
this.cleared = cleared;
|
|
505
|
+
this.getUserId = getUserId;
|
|
506
|
+
}
|
|
507
|
+
http;
|
|
508
|
+
def;
|
|
509
|
+
store;
|
|
510
|
+
buffer;
|
|
511
|
+
cleared;
|
|
512
|
+
getUserId;
|
|
513
|
+
/** True when this tx has buffered writes for this store. */
|
|
514
|
+
dirty() {
|
|
515
|
+
return isDirty(this.store, this.buffer, this.cleared);
|
|
516
|
+
}
|
|
517
|
+
async get(key) {
|
|
518
|
+
const range = isRange(key) ? key : void 0;
|
|
519
|
+
let k;
|
|
520
|
+
if (range) {
|
|
521
|
+
if (range.only === void 0) {
|
|
522
|
+
throw new Error("ApiStorage.get: needs a plain key or {only}; bounded ranges match no single row");
|
|
523
|
+
}
|
|
524
|
+
k = range.only;
|
|
525
|
+
} else {
|
|
526
|
+
k = key;
|
|
527
|
+
}
|
|
528
|
+
const state = bufferedGet(this.store, k, this.buffer);
|
|
529
|
+
if (state) return state.hit ? state.value : void 0;
|
|
530
|
+
if (this.cleared?.has(this.store)) return void 0;
|
|
531
|
+
return remoteGet(this.http, this.store, k);
|
|
532
|
+
}
|
|
533
|
+
async getAll(query, count) {
|
|
534
|
+
const range = rangeFromIDB(query);
|
|
535
|
+
return this.overlay(await this.serverRows("all", range, this.dirty() ? void 0 : count), count, range);
|
|
536
|
+
}
|
|
537
|
+
async getAllFromIndex(indexName, query, count) {
|
|
538
|
+
const range = rangeFromIDB(query);
|
|
539
|
+
return this.overlay(await this.serverRows(`index/${encodeURIComponent(indexName)}`, range, this.dirty() ? void 0 : count), count, range, indexName);
|
|
540
|
+
}
|
|
541
|
+
async count(query) {
|
|
542
|
+
const range = rangeFromIDB(query);
|
|
543
|
+
if (!this.dirty()) return remoteCount(this.http, this.store, range);
|
|
544
|
+
return this.overlay(await this.serverRows("all", range), void 0, range).length;
|
|
545
|
+
}
|
|
546
|
+
async countFromIndex(indexName, query) {
|
|
547
|
+
const range = rangeFromIDB(query);
|
|
548
|
+
if (!this.dirty()) return remoteCount(this.http, this.store, range, indexName);
|
|
549
|
+
return this.overlay(await this.serverRows(`index/${encodeURIComponent(indexName)}`, range), void 0, range, indexName).length;
|
|
550
|
+
}
|
|
551
|
+
/** 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 Promise.resolve([]);
|
|
554
|
+
return remoteList(this.http, this.store, path, range, count);
|
|
555
|
+
}
|
|
556
|
+
/** Applies buffered puts and deletes before range filtering, ordering and paging. */
|
|
557
|
+
overlay(rows, count, range, indexName) {
|
|
558
|
+
return overlayRows(this.def, this.store, this.buffer, this.cleared, rows, count, range, indexName);
|
|
559
|
+
}
|
|
560
|
+
async put(value, _key) {
|
|
561
|
+
const userId = this.getUserId?.();
|
|
562
|
+
const stored = userId ? stampUserOwned(this.store, value, userId) : value;
|
|
563
|
+
const key = keyFromValue(this.def, stored);
|
|
564
|
+
if (this.buffer) {
|
|
565
|
+
this.buffer.set(bufferSlot(this.store, key), { kind: "put", value: stored });
|
|
566
|
+
return key;
|
|
567
|
+
}
|
|
568
|
+
await request(this.http, "POST", "/tx", { body: { ops: [{ op: "put", store: this.store, value: stored }] } });
|
|
569
|
+
return key;
|
|
570
|
+
}
|
|
571
|
+
async delete(key) {
|
|
572
|
+
const range = isRange(key) ? key : void 0;
|
|
573
|
+
if (range) {
|
|
574
|
+
if (this.buffer) {
|
|
575
|
+
const rows = await this.getAll(range);
|
|
576
|
+
for (const row of rows) {
|
|
577
|
+
const key2 = keyFromValue(this.def, row);
|
|
578
|
+
this.buffer.set(bufferSlot(this.store, key2), { kind: "delete", key: key2 });
|
|
579
|
+
}
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
await remoteDelete(this.http, this.store, range);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const k = key;
|
|
586
|
+
if (this.buffer) {
|
|
587
|
+
this.buffer.set(bufferSlot(this.store, k), { kind: "delete", key: k });
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
await remoteDelete(this.http, this.store, k);
|
|
591
|
+
}
|
|
592
|
+
async clear() {
|
|
593
|
+
if (this.buffer) {
|
|
594
|
+
this.cleared?.add(this.store);
|
|
595
|
+
const prefix = `${this.store}\0`;
|
|
596
|
+
for (const slot of [...this.buffer.keys()]) {
|
|
597
|
+
if (slot.startsWith(prefix)) this.buffer.delete(slot);
|
|
598
|
+
}
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
await request(this.http, "POST", "/tx", { body: { ops: [{ op: "clear", store: this.store }] } });
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
exports.ApiStorage = ApiStorage;
|
|
606
|
+
exports.COLUMN_TYPES = COLUMN_TYPES;
|
|
607
|
+
exports.DB_VERSION = DB_VERSION;
|
|
608
|
+
exports.DEFAULT_MEMBERSHIP_ID = DEFAULT_MEMBERSHIP_ID;
|
|
609
|
+
exports.DOMAIN_PROJECTION_VERSION = DOMAIN_PROJECTION_VERSION;
|
|
610
|
+
exports.STORE_DEFS = STORE_DEFS;
|
|
611
|
+
exports.USER_OWNED_STORES = USER_OWNED_STORES;
|
|
612
|
+
exports.compareKeys = compareKeys;
|
|
613
|
+
exports.createDefaultMembership = createDefaultMembership;
|
|
614
|
+
exports.encodeKey = encodeKey;
|
|
615
|
+
exports.extractKeyPart = extractKeyPart;
|
|
616
|
+
exports.isRange = isRange;
|
|
617
|
+
exports.keyFromValue = keyFromValue;
|
|
618
|
+
exports.keyInRange = keyInRange;
|
|
619
|
+
exports.parseDomainQueryResult = parseDomainQueryResult;
|
|
620
|
+
exports.rangeFromIDB = rangeFromIDB;
|
|
621
|
+
exports.stampUserOwned = stampUserOwned;
|
|
622
|
+
//# sourceMappingURL=index.cjs.map
|
|
623
|
+
//# sourceMappingURL=index.cjs.map
|