@bitcobblers/wod-wiki-storage 0.39.2142 → 0.40.2144

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.
@@ -0,0 +1,134 @@
1
+ import { keyFromValue, compareKeys, extractKeyPart, keyInRange } from './chunk-IMNG2HY5.mjs';
2
+ import { validateNoteBlocks } from '@bitcobblers/wod-wiki-core';
3
+
4
+ var DOMAIN_PROJECTION_VERSION = 2;
5
+ function isObject(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function optionalStrings(row, fields) {
9
+ return fields.every((field) => row[field] === void 0 || typeof row[field] === "string");
10
+ }
11
+ function finiteNumbers(row, fields) {
12
+ return fields.every((field) => typeof row[field] === "number" && Number.isFinite(row[field]));
13
+ }
14
+ function isNote(value) {
15
+ 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", "notebookId", "sourcePath", "seedOrigin", "seedChunkId"]) || value.tags !== void 0 && (!Array.isArray(value.tags) || !value.tags.every((tag) => typeof tag === "string"))) return false;
16
+ try {
17
+ validateNoteBlocks(value.rawContent, value.blocks, value.sourceRevision);
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+ function isBlock(value) {
24
+ 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");
25
+ }
26
+ function isTag(value) {
27
+ return isObject(value) && typeof value.id === "string" && typeof value.label === "string" && finiteNumbers(value, ["createdAt"]) && optionalStrings(value, ["type"]);
28
+ }
29
+ function isPage(value) {
30
+ return isObject(value) && typeof value.id === "string" && finiteNumbers(value, ["createdAt"]) && optionalStrings(value, ["date", "slug", "title"]);
31
+ }
32
+ function isLink(value) {
33
+ 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"]));
34
+ }
35
+ function isEntry(value) {
36
+ 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);
37
+ }
38
+ function parseDomainQueryResult(value, plan) {
39
+ 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) {
40
+ throw new Error("ApiStorage: invalid or stale domain projection response");
41
+ }
42
+ const counts = { projectionVersion: DOMAIN_PROJECTION_VERSION, selectedCount: value.selectedCount, matchedCount: value.matchedCount };
43
+ switch (plan) {
44
+ case "notes":
45
+ if (value.rows.every(isNote)) return { ...counts, plan, rows: value.rows };
46
+ break;
47
+ case "blocks":
48
+ if (value.rows.every(isBlock)) return { ...counts, plan, rows: value.rows };
49
+ break;
50
+ case "entries":
51
+ if (value.rows.every(isEntry)) return { ...counts, plan, rows: value.rows };
52
+ break;
53
+ }
54
+ throw new Error("ApiStorage: invalid domain query rows");
55
+ }
56
+
57
+ // src/overlay.ts
58
+ var bufferSlot = (store, key) => `${store}\0${JSON.stringify(key)}`;
59
+ function isDirty(store, buffer, cleared) {
60
+ if (cleared?.has(store)) return true;
61
+ if (!buffer) return false;
62
+ const prefix = `${store}\0`;
63
+ for (const slot of buffer.keys()) {
64
+ if (slot.startsWith(prefix)) return true;
65
+ }
66
+ return false;
67
+ }
68
+ function bufferedGet(store, key, buffer) {
69
+ if (!buffer) return void 0;
70
+ const entry = buffer.get(bufferSlot(store, key));
71
+ if (!entry) return void 0;
72
+ return entry.kind === "put" ? { hit: true, value: entry.value } : { hit: false };
73
+ }
74
+ function overlayRows(def, store, buffer, cleared, rows, count, range, indexName) {
75
+ if (!buffer || !isDirty(store, buffer, cleared)) return count !== void 0 ? rows.slice(0, count) : rows;
76
+ const out = /* @__PURE__ */ new Map();
77
+ if (!cleared?.has(store)) {
78
+ for (const row of rows) out.set(JSON.stringify(keyFromValue(def, row)), row);
79
+ }
80
+ const prefix = `${store}\0`;
81
+ for (const [slot, entry] of buffer) {
82
+ if (!slot.startsWith(prefix)) continue;
83
+ if (entry.kind === "put") out.set(slot.slice(prefix.length), entry.value);
84
+ else out.delete(slot.slice(prefix.length));
85
+ }
86
+ const index = indexName ? def.indexes.find((i) => i.name === indexName) : void 0;
87
+ if (indexName && !index) throw new Error(`unknown index '${indexName}' for ${store}`);
88
+ const keyOf = (row) => {
89
+ if (!index) return keyFromValue(def, row);
90
+ const parts = index.keyPath.map((path) => extractKeyPart(row, path));
91
+ if (parts.some((part) => part === void 0)) return void 0;
92
+ const keys = parts.filter((part) => part !== void 0);
93
+ return keys.length === 1 ? keys[0] : keys;
94
+ };
95
+ const matches = (row) => {
96
+ if (index?.multiEntry) {
97
+ let values = row;
98
+ for (const part of index.keyPath[0].split(".")) {
99
+ if (values === null || typeof values !== "object" || !(part in values)) return false;
100
+ values = Reflect.get(values, part);
101
+ }
102
+ return Array.isArray(values) && values.some((value) => (typeof value === "string" || typeof value === "number") && (!range || keyInRange(value, range)));
103
+ }
104
+ const key = keyOf(row);
105
+ return key !== void 0 && (!range || keyInRange(key, range));
106
+ };
107
+ const merged = [...out.values()].filter(matches);
108
+ merged.sort((a, b) => {
109
+ const primary = compareKeys(keyFromValue(def, a), keyFromValue(def, b));
110
+ if (index?.multiEntry) return primary;
111
+ const ak = keyOf(a), bk = keyOf(b);
112
+ return ak !== void 0 && bk !== void 0 ? compareKeys(ak, bk) || primary : primary;
113
+ });
114
+ return count !== void 0 ? merged.slice(0, count) : merged;
115
+ }
116
+ function bufferToOps(storeNames, buffer, cleared) {
117
+ const known = new Set(storeNames);
118
+ const ops = [];
119
+ for (const store of cleared) {
120
+ if (known.has(store)) ops.push({ op: "clear", store });
121
+ }
122
+ for (const [slot, entry] of buffer) {
123
+ const sep = slot.indexOf("\0");
124
+ const store = slot.slice(0, sep);
125
+ if (!known.has(store)) continue;
126
+ if (entry.kind === "put") ops.push({ op: "put", store, value: entry.value });
127
+ else ops.push({ op: "delete", store, key: JSON.parse(slot.slice(sep + 1)) });
128
+ }
129
+ return ops;
130
+ }
131
+
132
+ export { DOMAIN_PROJECTION_VERSION, bufferSlot, bufferToOps, bufferedGet, isDirty, overlayRows, parseDomainQueryResult };
133
+ //# sourceMappingURL=chunk-JFVXIJJI.mjs.map
134
+ //# sourceMappingURL=chunk-JFVXIJJI.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/domain.ts","../src/overlay.ts"],"names":[],"mappings":"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","file":"chunk-JFVXIJJI.mjs","sourcesContent":["import type { BlockIndexRow, Note, Page, PageNote, Tag } from './entities';\nimport { validateNoteBlocks } from '@bitcobblers/wod-wiki-core';\n\nexport const DOMAIN_PROJECTION_VERSION = 2;\n\nexport type DomainPredicate =\n | { field: 'id' | 'noteId' | 'type' | 'catalog' | 'source' | 'tags' | 'effort'; values: string[]; negate?: boolean }\n | { field: 'text'; value: string }\n | { field: 'date'; start: number; end: number; endExclusive?: boolean }\n | { field: 'page'; value: boolean }\n | { field: 'defaultNotes'; feeds: boolean }\n | { field: 'sourceFence' };\n\nexport type DomainOrder = {\n field: 'id' | 'createdAt' | 'date' | 'title' | 'noteTitle' | 'dataType' | 'position';\n direction: 'asc' | 'desc';\n};\n\nexport type DomainQuery = {\n plan: 'notes' | 'blocks' | 'entries';\n selection?: DomainPredicate[];\n filters?: DomainPredicate[];\n order?: DomainOrder[];\n offset?: number;\n limit?: number;\n};\n\nexport interface DomainEntry {\n note: Note;\n tags: Tag[];\n links: PageNote[];\n pages: Page[];\n}\n\nexport type DomainQueryResult = {\n projectionVersion: typeof DOMAIN_PROJECTION_VERSION;\n selectedCount: number;\n matchedCount: number;\n} & (\n | { plan: 'notes'; rows: Note[] }\n | { plan: 'blocks'; rows: BlockIndexRow[] }\n | { plan: 'entries'; rows: DomainEntry[] }\n);\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction optionalStrings(row: Record<string, unknown>, fields: string[]): boolean {\n return fields.every(field => row[field] === undefined || typeof row[field] === 'string');\n}\n\nfunction finiteNumbers(row: Record<string, unknown>, fields: string[]): boolean {\n return fields.every(field => typeof row[field] === 'number' && Number.isFinite(row[field]));\n}\n\nfunction isNote(value: unknown): value is Note {\n if (!isObject(value) || typeof value.id !== 'string' || typeof value.title !== 'string'\n || typeof value.rawContent !== 'string' || !Number.isSafeInteger(value.sourceRevision)\n || typeof value.sourceRevision !== 'number' || value.sourceRevision < 1 || !isObject(value.blocks)\n || !finiteNumbers(value, ['createdAt'])\n || (value.date !== undefined && !finiteNumbers(value, ['date']))\n || !optionalStrings(value, ['type', 'sourceId', 'catalog', 'notebookId', 'sourcePath', 'seedOrigin', 'seedChunkId'])\n || (value.tags !== undefined && (!Array.isArray(value.tags) || !value.tags.every(tag => typeof tag === 'string')))) return false;\n try {\n validateNoteBlocks(value.rawContent, value.blocks as Note['blocks'], value.sourceRevision);\n return true;\n } catch { return false; }\n}\n\nfunction isBlock(value: unknown): value is BlockIndexRow {\n return isObject(value) && typeof value.id === 'string' && typeof value.noteId === 'string'\n && typeof value.blockId === 'string' && finiteNumbers(value, ['blockVersion', 'sourceRevision', 'from', 'to', 'createdAt'])\n && value.id === `${value.noteId}:${value.blockId}`\n && typeof value.blockVersion === 'number' && Number.isSafeInteger(value.blockVersion) && value.blockVersion > 0\n && typeof value.sourceRevision === 'number' && Number.isSafeInteger(value.sourceRevision) && value.sourceRevision > 0\n && typeof value.from === 'number' && Number.isSafeInteger(value.from) && value.from >= 0\n && typeof value.to === 'number' && Number.isSafeInteger(value.to) && value.to > value.from\n && ['wod', 'query', 'widget', 'embed'].includes(String(value.dataType)) && typeof value.rawContent === 'string'\n && typeof value.noteTitle === 'string'\n && optionalStrings(value, ['blockContentId', 'sourceId', 'sourcePath', 'routeId'])\n && (value.position === undefined || finiteNumbers(value, ['position']))\n && (value.isStatic === undefined || typeof value.isStatic === 'boolean');\n}\n\n\nfunction isTag(value: unknown): value is Tag {\n return isObject(value) && typeof value.id === 'string' && typeof value.label === 'string'\n && finiteNumbers(value, ['createdAt']) && optionalStrings(value, ['type']);\n}\n\nfunction isPage(value: unknown): value is Page {\n return isObject(value) && typeof value.id === 'string' && finiteNumbers(value, ['createdAt'])\n && optionalStrings(value, ['date', 'slug', 'title']);\n}\n\nfunction isLink(value: unknown): value is PageNote {\n return isObject(value) && typeof value.id === 'string' && typeof value.noteId === 'string'\n && typeof value.pageId === 'string' && finiteNumbers(value, ['createdAt'])\n && (value.position === undefined || finiteNumbers(value, ['position']));\n}\n\nfunction isEntry(value: unknown): value is DomainEntry {\n return isObject(value) && isNote(value.note)\n && Array.isArray(value.tags) && value.tags.every(isTag)\n && Array.isArray(value.links) && value.links.every(isLink)\n && Array.isArray(value.pages) && value.pages.every(isPage);\n}\n\nexport function parseDomainQueryResult(value: unknown, plan: DomainQuery['plan']): DomainQueryResult {\n if (!isObject(value) || value.projectionVersion !== DOMAIN_PROJECTION_VERSION\n || value.plan !== plan || !Array.isArray(value.rows)\n || typeof value.selectedCount !== 'number' || !Number.isSafeInteger(value.selectedCount) || value.selectedCount < 0\n || typeof value.matchedCount !== 'number' || !Number.isSafeInteger(value.matchedCount) || value.matchedCount < 0\n || value.matchedCount > value.selectedCount || value.rows.length > value.matchedCount) {\n throw new Error('ApiStorage: invalid or stale domain projection response');\n }\n const counts = { projectionVersion: DOMAIN_PROJECTION_VERSION, selectedCount: value.selectedCount, matchedCount: value.matchedCount } as const;\n switch (plan) {\n case 'notes':\n if (value.rows.every(isNote)) return { ...counts, plan, rows: value.rows };\n break;\n case 'blocks':\n if (value.rows.every(isBlock)) return { ...counts, plan, rows: value.rows };\n break;\n case 'entries':\n if (value.rows.every(isEntry)) return { ...counts, plan, rows: value.rows };\n break;\n }\n throw new Error('ApiStorage: invalid domain query rows');\n}\n","/**\n * Transaction write-buffer + read-overlay helpers shared by the HTTP\n * ApiStorage and the SQL SqlStorage backends. A transaction buffers puts and\n * deletes; reads overlay the buffer on backend rows in op order, so\n * read-your-writes holds without round-tripping uncommitted data.\n */\n\nimport type { StoreName } from './contract';\nimport {\n compareKeys,\n extractKeyPart,\n keyFromValue,\n keyInRange,\n type KeyDTO,\n type RangeDTO,\n type TxOp,\n} from './wire';\nimport type { StoreDef } from './schema';\n\nexport interface BufferedPut { kind: 'put'; value: unknown }\nexport interface BufferedDelete { kind: 'delete'; key: KeyDTO }\nexport type BufferEntry = BufferedPut | BufferedDelete;\n\n// Buffer slot key: store + \\u0000 + raw JSON of the key (JSON text never\n// contains a literal \\u0000, so the separator is unambiguous).\nexport const bufferSlot = (store: StoreName, key: KeyDTO): string => `${store}\\u0000${JSON.stringify(key)}`;\n\n/** True when this tx has buffered writes (puts, deletes or a clear) for the store. */\nexport function isDirty(store: StoreName, buffer?: Map<string, BufferEntry>, cleared?: Set<StoreName>): boolean {\n if (cleared?.has(store)) return true;\n if (!buffer) return false;\n const prefix = `${store}\\u0000`;\n for (const slot of buffer.keys()) {\n if (slot.startsWith(prefix)) return true;\n }\n return false;\n}\n\n/** Point lookup against the buffer; undefined means consult the backend. */\nexport function bufferedGet<T>(store: StoreName, key: KeyDTO, buffer?: Map<string, BufferEntry>):\n | { hit: true; value: T }\n | { hit: false }\n | undefined {\n if (!buffer) return undefined;\n const entry = buffer.get(bufferSlot(store, key));\n if (!entry) return undefined;\n return entry.kind === 'put' ? { hit: true, value: entry.value as T } : { hit: false };\n}\n\n/**\n * Applies buffered puts and deletes before range filtering, ordering and\n * paging. Rows missing an index field are not in the index (IDB semantics);\n * multi-entry keys match when any entry is in range.\n */\nexport function overlayRows<T>(\n def: StoreDef,\n store: StoreName,\n buffer: Map<string, BufferEntry> | undefined,\n cleared: Set<StoreName> | undefined,\n rows: T[],\n count?: number,\n range?: RangeDTO,\n indexName?: string,\n): T[] {\n if (!buffer || !isDirty(store, buffer, cleared)) return count !== undefined ? rows.slice(0, count) : rows;\n const out = new Map<string, T>();\n if (!cleared?.has(store)) {\n // Out-map keys are raw JSON key text — same form as buffer slot suffixes.\n for (const row of rows) out.set(JSON.stringify(keyFromValue(def, row)), row);\n }\n const prefix = `${store}\\u0000`;\n for (const [slot, entry] of buffer) {\n if (!slot.startsWith(prefix)) continue;\n if (entry.kind === 'put') out.set(slot.slice(prefix.length), entry.value as T);\n else out.delete(slot.slice(prefix.length));\n }\n // Dirty reads fetch the complete candidate range: buffered deletes can\n // remove the remote head, and puts can change index membership or order.\n const index = indexName ? def.indexes.find(i => i.name === indexName) : undefined;\n if (indexName && !index) throw new Error(`unknown index '${indexName}' for ${store}`);\n const keyOf = (row: T): KeyDTO | undefined => {\n if (!index) return keyFromValue(def, row);\n const parts = index.keyPath.map(path => extractKeyPart(row, path));\n if (parts.some(part => part === undefined)) return undefined;\n const keys = parts.filter((part): part is string | number => part !== undefined);\n return keys.length === 1 ? keys[0] : keys;\n };\n const matches = (row: T): boolean => {\n if (index?.multiEntry) {\n let values: unknown = row;\n for (const part of index.keyPath[0].split('.')) {\n if (values === null || typeof values !== 'object' || !(part in values)) return false;\n values = Reflect.get(values, part);\n }\n return Array.isArray(values) && values.some(value =>\n (typeof value === 'string' || typeof value === 'number') && (!range || keyInRange(value, range)));\n }\n const key = keyOf(row);\n return key !== undefined && (!range || keyInRange(key, range));\n };\n const merged = [...out.values()].filter(matches);\n merged.sort((a, b) => {\n const primary = compareKeys(keyFromValue(def, a), keyFromValue(def, b));\n if (index?.multiEntry) return primary;\n const ak = keyOf(a), bk = keyOf(b);\n return ak !== undefined && bk !== undefined ? compareKeys(ak, bk) || primary : primary;\n });\n return count !== undefined ? merged.slice(0, count) : merged;\n}\n\n/** Drains the tx buffer into wire ops (clears first, then puts/deletes in op order). */\nexport function bufferToOps(storeNames: Iterable<StoreName>, buffer: Map<string, BufferEntry>, cleared: Set<StoreName>): TxOp[] {\n const known = new Set(storeNames);\n const ops: TxOp[] = [];\n for (const store of cleared) {\n if (known.has(store)) ops.push({ op: 'clear', store });\n }\n for (const [slot, entry] of buffer) {\n const sep = slot.indexOf('\\u0000');\n const store = slot.slice(0, sep) as StoreName;\n if (!known.has(store)) continue;\n if (entry.kind === 'put') ops.push({ op: 'put', store, value: entry.value });\n else ops.push({ op: 'delete', store, key: JSON.parse(slot.slice(sep + 1)) as KeyDTO });\n }\n return ops;\n}\n"]}
@@ -0,0 +1,43 @@
1
+ // src/policy.ts
2
+ var USER_DELETED_SEED_PREFIX = "user-deleted-seed:";
3
+ function userDeletedSeedKey(noteId) {
4
+ return `${USER_DELETED_SEED_PREFIX}${noteId}`;
5
+ }
6
+ function userDeletedSeedNoteId(key) {
7
+ if (typeof key !== "string" || !key.startsWith(USER_DELETED_SEED_PREFIX)) return void 0;
8
+ return key.slice(USER_DELETED_SEED_PREFIX.length) || void 0;
9
+ }
10
+ function isSeedMetaKey(key) {
11
+ return typeof key === "string" && key.startsWith("seed:");
12
+ }
13
+ function collectUserScopeContext(notes) {
14
+ const seedNoteIds = /* @__PURE__ */ new Set();
15
+ for (const row of notes) {
16
+ if (row !== null && typeof row === "object" && "seedOrigin" in row && row.seedOrigin === "seed" && "id" in row && typeof row.id === "string") {
17
+ seedNoteIds.add(row.id);
18
+ }
19
+ }
20
+ return { seedNoteIds };
21
+ }
22
+ function isUserOwnedRow(store, row, ctx) {
23
+ if (row === null || typeof row !== "object") return true;
24
+ switch (store) {
25
+ case "notes":
26
+ return !("seedOrigin" in row && row.seedOrigin === "seed");
27
+ case "block_index":
28
+ case "block_efforts":
29
+ case "block_equipment":
30
+ return !("isStatic" in row && row.isStatic === true && "noteId" in row && typeof row.noteId === "string" && ctx.seedNoteIds.has(row.noteId));
31
+ case "efforts":
32
+ case "equipment":
33
+ return !("registrySource" in row && row.registrySource === "bundled");
34
+ case "meta":
35
+ return !isSeedMetaKey("key" in row ? row.key : void 0);
36
+ default:
37
+ return true;
38
+ }
39
+ }
40
+
41
+ export { USER_DELETED_SEED_PREFIX, collectUserScopeContext, isSeedMetaKey, isUserOwnedRow, userDeletedSeedKey, userDeletedSeedNoteId };
42
+ //# sourceMappingURL=chunk-MOGQICXM.mjs.map
43
+ //# sourceMappingURL=chunk-MOGQICXM.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/policy.ts"],"names":[],"mappings":";AAyBO,IAAM,wBAAA,GAA2B;AAIjC,SAAS,mBAAmB,MAAA,EAAwB;AACzD,EAAA,OAAO,CAAA,EAAG,wBAAwB,CAAA,EAAG,MAAM,CAAA,CAAA;AAC7C;AAGO,SAAS,sBAAsB,GAAA,EAAkC;AACtE,EAAA,IAAI,OAAO,QAAQ,QAAA,IAAY,CAAC,IAAI,UAAA,CAAW,wBAAwB,GAAG,OAAO,MAAA;AACjF,EAAA,OAAO,GAAA,CAAI,KAAA,CAAM,wBAAA,CAAyB,MAAM,CAAA,IAAK,MAAA;AACvD;AAKO,SAAS,cAAc,GAAA,EAAuB;AACnD,EAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,WAAW,OAAO,CAAA;AAC1D;AAQO,SAAS,wBAAwB,KAAA,EAA6C;AACnF,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAAY;AACpC,EAAA,KAAA,MAAW,OAAO,KAAA,EAAO;AACvB,IAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,OAAO,GAAA,KAAQ,YAAY,YAAA,IAAgB,GAAA,IAC1D,GAAA,CAAI,UAAA,KAAe,UAAU,IAAA,IAAQ,GAAA,IAAO,OAAO,GAAA,CAAI,OAAO,QAAA,EAAU;AAC3E,MAAA,WAAA,CAAY,GAAA,CAAI,IAAI,EAAE,CAAA;AAAA,IACxB;AAAA,EACF;AACA,EAAA,OAAO,EAAE,WAAA,EAAY;AACvB;AAOO,SAAS,cAAA,CAAe,KAAA,EAAkB,GAAA,EAAc,GAAA,EAAgC;AAC7F,EAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,OAAO,GAAA,KAAQ,UAAU,OAAO,IAAA;AACpD,EAAA,QAAQ,KAAA;AAAO,IACb,KAAK,OAAA;AACH,MAAA,OAAO,EAAE,YAAA,IAAgB,GAAA,IAAO,GAAA,CAAI,UAAA,KAAe,MAAA,CAAA;AAAA,IACrD,KAAK,aAAA;AAAA,IACL,KAAK,eAAA;AAAA,IACL,KAAK,iBAAA;AAGH,MAAA,OAAO,EAAE,UAAA,IAAc,GAAA,IAAO,GAAA,CAAI,QAAA,KAAa,QAC1C,QAAA,IAAY,GAAA,IAAO,OAAO,GAAA,CAAI,WAAW,QAAA,IACzC,GAAA,CAAI,WAAA,CAAY,GAAA,CAAI,IAAI,MAAM,CAAA,CAAA;AAAA,IACrC,KAAK,SAAA;AAAA,IACL,KAAK,WAAA;AAGH,MAAA,OAAO,EAAE,gBAAA,IAAoB,GAAA,IAAO,GAAA,CAAI,cAAA,KAAmB,SAAA,CAAA;AAAA,IAC7D,KAAK,MAAA;AACH,MAAA,OAAO,CAAC,aAAA,CAAc,KAAA,IAAS,GAAA,GAAM,GAAA,CAAI,MAAM,MAAS,CAAA;AAAA,IAC1D;AACE,MAAA,OAAO,IAAA;AAAA;AAEb","file":"chunk-MOGQICXM.mjs","sourcesContent":["/**\n * Shared user-scope sync policy (versioned-databases-backups-review §3.4).\n *\n * ONE ownership classification governs outbox journals, bootstrap\n * snapshots, the schema32 outbox cutover and cross-schema conversions. Rows\n * are classified by actual provenance — `seedOrigin` on notes, `isStatic`\n * plus owner note on projections, `registrySource` on bundled definitions,\n * `seed:` prefixes on meta checkpoints — never from `userId` or the ABSENCE\n * of a marker: uncertain rows are KEPT until their derivation and\n * reconstruction are proven.\n *\n * Excluded rows are exactly the ones the seed bundle re-derives: seed\n * notes, their static projections, bundled definitions and import\n * checkpoints. Everything else — sessions, events, attachments, revision\n * history, tags/pages/links, field-catalog data, non-seed meta — is user\n * data and always retained. Immutable block_revisions are kept\n * unconditionally until dependency closure is proven: sessions pin\n * revisions a re-import may not reproduce.\n */\n\nimport type { StoreName } from './contract';\n\n/** Durable seed-deletion intent: written atomically with the delete by\n * StorageService.deleteNote, honored by seed re-import (applySeedChunk) and\n * carried in every user-scope snapshot so fresh devices suppress too. */\nexport const USER_DELETED_SEED_PREFIX = 'user-deleted-seed:';\n\n/** Key constants shared by the writer (StorageService), the suppression\n * reader (seed runtime) and the policy passes — a durable contract. */\nexport function userDeletedSeedKey(noteId: string): string {\n return `${USER_DELETED_SEED_PREFIX}${noteId}`;\n}\n\n/** Inverse helper for readers; undefined for anything else. */\nexport function userDeletedSeedNoteId(key: unknown): string | undefined {\n if (typeof key !== 'string' || !key.startsWith(USER_DELETED_SEED_PREFIX)) return undefined;\n return key.slice(USER_DELETED_SEED_PREFIX.length) || undefined;\n}\n\n/** The one meta key family a user-scope snapshot excludes: import\n * checkpoints/claims (`seed:*`) are concurrency state re-derived from the\n * bundle. `user-deleted-seed:*` tombstones and every other key stay in. */\nexport function isSeedMetaKey(key: unknown): boolean {\n return typeof key === 'string' && key.startsWith('seed:');\n}\n\n/** Prior rows the policy needs: a projection's provenance is its owner\n * note's, so every policy pass collects seed note ids first. */\nexport interface UserScopeContext {\n seedNoteIds: ReadonlySet<string>;\n}\n\nexport function collectUserScopeContext(notes: readonly unknown[]): UserScopeContext {\n const seedNoteIds = new Set<string>();\n for (const row of notes) {\n if (row !== null && typeof row === 'object' && 'seedOrigin' in row\n && row.seedOrigin === 'seed' && 'id' in row && typeof row.id === 'string') {\n seedNoteIds.add(row.id);\n }\n }\n return { seedNoteIds };\n}\n\n/**\n * True when the row is user data a user-scope snapshot must retain.\n * Store-level default is true (keep uncertain rows); only provably\n * seed-rebuildable rows return false.\n */\nexport function isUserOwnedRow(store: StoreName, row: unknown, ctx: UserScopeContext): boolean {\n if (row === null || typeof row !== 'object') return true;\n switch (store) {\n case 'notes':\n return !('seedOrigin' in row && row.seedOrigin === 'seed');\n case 'block_index':\n case 'block_efforts':\n case 'block_equipment':\n // Static projections of seed notes are rebuilt by re-import; anything\n // else touching a seed note (user-created links included) is kept.\n return !('isStatic' in row && row.isStatic === true\n && 'noteId' in row && typeof row.noteId === 'string'\n && ctx.seedNoteIds.has(row.noteId));\n case 'efforts':\n case 'equipment':\n // 'bundled' is the provenance marker the seed importer writes;\n // 'user' and 'synthetic-unresolved' derive from user data.\n return !('registrySource' in row && row.registrySource === 'bundled');\n case 'meta':\n return !isSeedMetaKey('key' in row ? row.key : undefined);\n default:\n return true;\n }\n}\n"]}