@bitcobblers/wod-wiki-engine 0.36.2136 → 0.37.2139
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.cjs +112 -203
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -22
- package/dist/index.d.ts +25 -22
- package/dist/index.mjs +112 -204
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -245,43 +245,6 @@ function extractTypedTags(markdown, typedKeys = wodWikiWql.WQL_TYPED_TAG_KEYS) {
|
|
|
245
245
|
}
|
|
246
246
|
return results.filter((t) => t.label.length > 0);
|
|
247
247
|
}
|
|
248
|
-
function markdownToEffort(raw) {
|
|
249
|
-
const match = FRONTMATTER_BLOCK_RE.exec(raw);
|
|
250
|
-
if (!match) return null;
|
|
251
|
-
const meta = readFrontmatterMeta(raw);
|
|
252
|
-
const body = raw.slice(match[0].length).trim();
|
|
253
|
-
const id = scalarOf(meta["id"]);
|
|
254
|
-
const slug = scalarOf(meta["slug"]);
|
|
255
|
-
const label = scalarOf(meta["label"]);
|
|
256
|
-
if (!id || !slug || !label) return null;
|
|
257
|
-
const baseAttributes = {};
|
|
258
|
-
const blockRe = /^\s{2,}([^:]+):\s*(.+)$/;
|
|
259
|
-
let inAttrs = false;
|
|
260
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
261
|
-
if (/^baseAttributes:/.test(line)) {
|
|
262
|
-
inAttrs = true;
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
if (inAttrs) {
|
|
266
|
-
const attr = blockRe.exec(line);
|
|
267
|
-
if (attr && /^ {2,}/.test(line)) {
|
|
268
|
-
const v = attr[2].trim();
|
|
269
|
-
baseAttributes[attr[1].trim()] = /^\d+(\.\d+)?$/.test(v) ? Number(v) : v;
|
|
270
|
-
} else if (/^\S/.test(line)) {
|
|
271
|
-
inAttrs = false;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (typeof baseAttributes["met"] !== "number") return null;
|
|
276
|
-
return {
|
|
277
|
-
id,
|
|
278
|
-
slug,
|
|
279
|
-
label,
|
|
280
|
-
aliases: asList(meta["aliases"]),
|
|
281
|
-
baseAttributes,
|
|
282
|
-
...body ? { body } : {}
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
248
|
function effortToMarkdown(effort) {
|
|
286
249
|
const lines = ["---", `id: ${effort.id}`, `slug: ${effort.slug}`, `label: ${effort.label}`];
|
|
287
250
|
if (effort.aliases && effort.aliases.length > 0) {
|
|
@@ -297,8 +260,15 @@ function effortToMarkdown(effort) {
|
|
|
297
260
|
return `${lines.join("\n")}
|
|
298
261
|
${(effort.body ?? "").trim()}`.trimEnd() + "\n";
|
|
299
262
|
}
|
|
300
|
-
function
|
|
301
|
-
|
|
263
|
+
function equipmentToMarkdown(equipment) {
|
|
264
|
+
const lines = ["---", "type: equipment", `id: ${equipment.id}`, `slug: ${equipment.slug}`, `label: ${equipment.label}`];
|
|
265
|
+
if (equipment.aliases && equipment.aliases.length > 0) {
|
|
266
|
+
lines.push("aliases:");
|
|
267
|
+
for (const alias of equipment.aliases) lines.push(` - ${alias}`);
|
|
268
|
+
}
|
|
269
|
+
lines.push("---", "");
|
|
270
|
+
return `${lines.join("\n")}
|
|
271
|
+
${(equipment.body ?? "").trim()}`.trimEnd() + "\n";
|
|
302
272
|
}
|
|
303
273
|
function equipmentToCsv(labels) {
|
|
304
274
|
return ["label", ...labels].map((l) => escapeCsvField(l)).join("\n") + "\n";
|
|
@@ -647,18 +617,19 @@ function csvToSessionRows(csv) {
|
|
|
647
617
|
}
|
|
648
618
|
|
|
649
619
|
// src/version.ts
|
|
650
|
-
var VERSION = "0.
|
|
651
|
-
var GIT_SHA = "
|
|
652
|
-
var BUILD_TIME = "2026-10-
|
|
620
|
+
var VERSION = "0.37.2139";
|
|
621
|
+
var GIT_SHA = "491ec71";
|
|
622
|
+
var BUILD_TIME = "2026-10-17T15:36:01.834Z";
|
|
653
623
|
var SEMVER = {
|
|
654
|
-
version: "0.
|
|
655
|
-
gitSha: "
|
|
656
|
-
buildTime: "2026-10-
|
|
624
|
+
version: "0.37.2139",
|
|
625
|
+
gitSha: "491ec71",
|
|
626
|
+
buildTime: "2026-10-17T15:36:01.834Z"
|
|
657
627
|
};
|
|
658
|
-
var SEED_SCHEMA =
|
|
628
|
+
var SEED_SCHEMA = 9;
|
|
659
629
|
var SEED_REGISTERED_WIDGETS = wodWikiCore.KNOWN_SECTION_WIDGETS;
|
|
660
630
|
var SEED_BROADCAST_CHANNEL = "wodwiki.seed";
|
|
661
631
|
var EFFORTS_CHUNK_ID = "efforts";
|
|
632
|
+
var EQUIPMENT_CHUNK_ID = "equipment";
|
|
662
633
|
var CANVAS_CHUNK_ID = "canvas";
|
|
663
634
|
var DASHBOARDS_CHUNK_ID = "dashboards";
|
|
664
635
|
var TEMPLATE_CHUNK_ID = "template";
|
|
@@ -684,6 +655,7 @@ function seedNoteSourceId(path, content, chunkId) {
|
|
|
684
655
|
if (chunkId === CANVAS_CHUNK_ID) return `guides:${canvasRouteSlug(content) ?? under}`;
|
|
685
656
|
if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
|
|
686
657
|
if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
|
|
658
|
+
if (chunkId === EQUIPMENT_CHUNK_ID) return "equipment";
|
|
687
659
|
if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
|
|
688
660
|
if (chunkId.startsWith("collection.")) {
|
|
689
661
|
const catalog = catalogForChunkId(chunkId);
|
|
@@ -692,141 +664,6 @@ function seedNoteSourceId(path, content, chunkId) {
|
|
|
692
664
|
if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
|
|
693
665
|
return void 0;
|
|
694
666
|
}
|
|
695
|
-
|
|
696
|
-
// src/seed/frontmatter.ts
|
|
697
|
-
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
698
|
-
function unquote(value) {
|
|
699
|
-
return value.replace(
|
|
700
|
-
/^(['"])(.*)\1$/,
|
|
701
|
-
(_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
function parseFrontmatter(raw) {
|
|
705
|
-
const match = raw.match(FRONTMATTER_RE);
|
|
706
|
-
if (!match) return { meta: {}, body: raw };
|
|
707
|
-
return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
|
|
708
|
-
}
|
|
709
|
-
function parseMetaLines(lines) {
|
|
710
|
-
const meta = {};
|
|
711
|
-
for (let i = 0; i < lines.length; i++) {
|
|
712
|
-
const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
|
|
713
|
-
if (!keyMatch) continue;
|
|
714
|
-
const key = keyMatch[1];
|
|
715
|
-
const rawVal = keyMatch[2].trim();
|
|
716
|
-
if (rawVal === "") {
|
|
717
|
-
const items = [];
|
|
718
|
-
while (i + 1 < lines.length) {
|
|
719
|
-
const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
|
|
720
|
-
if (!itemMatch) break;
|
|
721
|
-
items.push(itemMatch[1].trim());
|
|
722
|
-
i++;
|
|
723
|
-
}
|
|
724
|
-
meta[key] = items.length > 0 ? items : "";
|
|
725
|
-
} else {
|
|
726
|
-
const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
|
|
727
|
-
const value = unquote(rawVal);
|
|
728
|
-
const num2 = Number(value);
|
|
729
|
-
meta[key] = !wasQuoted && value !== "" && !isNaN(num2) ? num2 : value;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
return meta;
|
|
733
|
-
}
|
|
734
|
-
function getScalar(meta, key) {
|
|
735
|
-
const value = meta[key];
|
|
736
|
-
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
737
|
-
}
|
|
738
|
-
function getList(meta, key) {
|
|
739
|
-
const value = meta[key];
|
|
740
|
-
return Array.isArray(value) ? value : [];
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// src/seed/effort-file.ts
|
|
744
|
-
function parseDiscipline(val) {
|
|
745
|
-
if (!val) return void 0;
|
|
746
|
-
const normalized = unquoteYaml(val.trim()).toLowerCase();
|
|
747
|
-
if (wodWikiLang.isEffortDiscipline(normalized)) return normalized;
|
|
748
|
-
console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
|
|
749
|
-
return void 0;
|
|
750
|
-
}
|
|
751
|
-
function unquoteYaml(val) {
|
|
752
|
-
if (val.startsWith('"') && val.endsWith('"')) {
|
|
753
|
-
return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
754
|
-
}
|
|
755
|
-
if (val.startsWith("'") && val.endsWith("'")) {
|
|
756
|
-
return val.slice(1, -1).replace(/\\'/g, "'");
|
|
757
|
-
}
|
|
758
|
-
return val;
|
|
759
|
-
}
|
|
760
|
-
function coerceNestedScalar(raw) {
|
|
761
|
-
if (raw === "true") return true;
|
|
762
|
-
if (raw === "false") return false;
|
|
763
|
-
if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
|
|
764
|
-
return raw;
|
|
765
|
-
}
|
|
766
|
-
function readNestedSection(lines, key) {
|
|
767
|
-
const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
|
|
768
|
-
if (start === -1) return void 0;
|
|
769
|
-
const result = {};
|
|
770
|
-
let subKey = null;
|
|
771
|
-
for (let i = start + 1; i < lines.length; i++) {
|
|
772
|
-
const line = lines[i];
|
|
773
|
-
if (/^\S/.test(line)) break;
|
|
774
|
-
const trimmed = line.trim();
|
|
775
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
776
|
-
const indent = line.length - line.trimStart().length;
|
|
777
|
-
const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
|
|
778
|
-
if (!m) continue;
|
|
779
|
-
const [, k, rawVal] = m;
|
|
780
|
-
if (indent === 2) {
|
|
781
|
-
if (rawVal === "") {
|
|
782
|
-
subKey = k;
|
|
783
|
-
result[k] = {};
|
|
784
|
-
} else {
|
|
785
|
-
subKey = null;
|
|
786
|
-
result[k] = coerceNestedScalar(rawVal);
|
|
787
|
-
}
|
|
788
|
-
} else if (indent >= 4 && subKey !== null) {
|
|
789
|
-
result[subKey][k] = coerceNestedScalar(rawVal);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
793
|
-
}
|
|
794
|
-
function parseEffortFile(raw) {
|
|
795
|
-
const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
796
|
-
if (!blockMatch) return null;
|
|
797
|
-
const { meta, body } = parseFrontmatter(raw);
|
|
798
|
-
const innerLines = blockMatch[1].split(/\r?\n/);
|
|
799
|
-
const id = getScalar(meta, "id");
|
|
800
|
-
const slug = getScalar(meta, "slug");
|
|
801
|
-
const label = getScalar(meta, "label");
|
|
802
|
-
const aliases = getList(meta, "aliases");
|
|
803
|
-
const baseAttrs = readNestedSection(innerLines, "baseAttributes");
|
|
804
|
-
const hints = readNestedSection(innerLines, "hints");
|
|
805
|
-
const derivation = readNestedSection(innerLines, "derivation");
|
|
806
|
-
const metRaw = baseAttrs?.met;
|
|
807
|
-
const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
|
|
808
|
-
if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
|
|
809
|
-
const discipline = parseDiscipline(
|
|
810
|
-
typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
|
|
811
|
-
);
|
|
812
|
-
const intensityTier = baseAttrs?.intensityTier;
|
|
813
|
-
const trimmedBody = body.trim();
|
|
814
|
-
return {
|
|
815
|
-
id: String(id),
|
|
816
|
-
slug: String(slug),
|
|
817
|
-
label: String(label),
|
|
818
|
-
aliases,
|
|
819
|
-
baseAttributes: {
|
|
820
|
-
met,
|
|
821
|
-
...discipline ? { discipline } : {},
|
|
822
|
-
...intensityTier ? { intensityTier } : {}
|
|
823
|
-
},
|
|
824
|
-
registrySource: "bundled",
|
|
825
|
-
...trimmedBody ? { body: trimmedBody } : {},
|
|
826
|
-
...hints ? { hints } : {},
|
|
827
|
-
...derivation ? { derivation } : {}
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
667
|
function object(value) {
|
|
831
668
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
832
669
|
}
|
|
@@ -839,7 +676,8 @@ function nonnegative(value) {
|
|
|
839
676
|
function assertSeedManifest(value) {
|
|
840
677
|
if (!object(value) || value.schema !== SEED_SCHEMA) throw new Error("Incompatible seed schema; rebuild seed artifacts");
|
|
841
678
|
if (!nonnegative(value.version) || typeof value.builtAt !== "string" || !Number.isFinite(Date.parse(value.builtAt)) || !Array.isArray(value.chunks)) throw new Error("seed manifest failed shape validation");
|
|
842
|
-
for (const key of ["versionLabel", "commit"]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error("invalid seed manifest metadata");
|
|
679
|
+
for (const key of ["versionLabel", "commit", "name", "description"]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error("invalid seed manifest metadata");
|
|
680
|
+
if (value.repositoryUrl !== void 0 && (typeof value.repositoryUrl !== "string" || !/^https:\/\//.test(value.repositoryUrl) || new URL(value.repositoryUrl).username || new URL(value.repositoryUrl).password)) throw new Error("invalid seed repository URL");
|
|
843
681
|
if (value.dirty !== void 0 && typeof value.dirty !== "boolean") throw new Error("invalid seed manifest metadata");
|
|
844
682
|
const ids = /* @__PURE__ */ new Set(), paths = /* @__PURE__ */ new Set();
|
|
845
683
|
for (const chunk of value.chunks) {
|
|
@@ -853,7 +691,7 @@ function assertSeedRows(value) {
|
|
|
853
691
|
if (!Array.isArray(value)) throw new Error("seed chunk is not an array");
|
|
854
692
|
const paths = /* @__PURE__ */ new Set();
|
|
855
693
|
for (const row of value) {
|
|
856
|
-
if (!object(row) || typeof row.path !== "string" || !row.path || row.path.startsWith("/") || row.path.split("/").includes("..") || paths.has(row.path) || typeof row.content !== "string" || !object(row.blocks) || !Array.isArray(row.blockIndex) || !Array.isArray(row.blockEfforts)) throw new Error("seed chunk row failed shape validation");
|
|
694
|
+
if (!object(row) || typeof row.path !== "string" || !row.path || row.path.startsWith("/") || row.path.split("/").includes("..") || paths.has(row.path) || typeof row.content !== "string" || !object(row.blocks) || !Array.isArray(row.blockIndex) || !Array.isArray(row.blockEfforts) || !Array.isArray(row.blockEquipment)) throw new Error("seed chunk row failed shape validation");
|
|
857
695
|
paths.add(row.path);
|
|
858
696
|
for (const [id, block] of Object.entries(row.blocks)) {
|
|
859
697
|
if (!object(block) || block.id !== id || !id || block.version !== 1 || block.sourceRevision !== 1 || !nonnegative(block.from) || !positive(block.to) || block.from >= block.to || block.to > row.content.length || !["wod", "query", "widget", "embed"].includes(String(block.kind)) || (block.kind === "wod" ? !["time", "log"].includes(String(block.dialect)) : block.dialect !== void 0) || block.sport !== void 0 && typeof block.sport !== "string") throw new Error("invalid seed block range or revision");
|
|
@@ -884,6 +722,15 @@ function assertSeedRows(value) {
|
|
|
884
722
|
if (!object(projection) || link.noteId !== projection.noteId || link.blockContentId !== projection.blockContentId || !Number.isFinite(link.createdAt)) throw new Error("invalid seed effort metadata");
|
|
885
723
|
links.add(String(link.id));
|
|
886
724
|
}
|
|
725
|
+
const equipmentLinks = /* @__PURE__ */ new Set();
|
|
726
|
+
for (const link of row.blockEquipment) {
|
|
727
|
+
if (!object(link) || typeof link.blockId !== "string" || typeof link.noteId !== "string" || typeof link.equipmentSlug !== "string" || !link.equipmentSlug) throw new Error("invalid seed equipment identity");
|
|
728
|
+
const block = row.blocks[link.blockId];
|
|
729
|
+
if (link.id !== `${link.noteId}:${link.blockId}:${link.equipmentSlug}` || equipmentLinks.has(String(link.id)) || !object(block) || block.kind !== "wod" || !indexed.has(link.blockId) || link.sourcePath !== row.path || link.isStatic !== true || typeof link.createdAt !== "number") throw new Error("orphan or incompatible seed equipment link");
|
|
730
|
+
const projection = row.blockIndex.find((p) => object(p) && p.blockId === link.blockId);
|
|
731
|
+
if (!object(projection) || link.noteId !== projection.noteId || link.blockContentId !== projection.blockContentId || !Number.isFinite(link.createdAt)) throw new Error("invalid seed equipment metadata");
|
|
732
|
+
equipmentLinks.add(String(link.id));
|
|
733
|
+
}
|
|
887
734
|
}
|
|
888
735
|
return value;
|
|
889
736
|
}
|
|
@@ -921,6 +768,40 @@ function refreshedBlocks(existing, rawContent, sourceRevision) {
|
|
|
921
768
|
let ordinal = 0;
|
|
922
769
|
return wodWikiCore.reconcileNoteBlocks(rawContent, { sourceRevision, blocks: mapped, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${existing.id}:r${sourceRevision}:${ordinal++}` });
|
|
923
770
|
}
|
|
771
|
+
async function applyDefinitionChunk(defStore, parse, family, rows, ids, owned, existingById, allIds) {
|
|
772
|
+
const projections = [];
|
|
773
|
+
for (const [i, row] of rows.entries()) {
|
|
774
|
+
if (!owned.has(ids[i])) continue;
|
|
775
|
+
const parsed = parse(row.content);
|
|
776
|
+
if (!parsed) continue;
|
|
777
|
+
projections.push({ ...parsed, registrySource: "bundled", ownerNoteId: ids[i], sourceId: family, sourcePath: row.path });
|
|
778
|
+
}
|
|
779
|
+
const prior = await defStore.getMany(projections.map((p) => p.slug));
|
|
780
|
+
for (const [i, p] of projections.entries()) {
|
|
781
|
+
const before = prior[i];
|
|
782
|
+
if (before?.registrySource === "bundled" && before.ownerNoteId && before.ownerNoteId !== p.ownerNoteId) {
|
|
783
|
+
throw new Error(`Seed definition collision: '${p.slug}' is already owned by another seed source (note ${before.ownerNoteId})`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const changed = projections.filter((p, i) => (!prior[i] || prior[i]?.registrySource === "bundled") && !equal(p, prior[i]));
|
|
787
|
+
if (changed.length) await defStore.putMany(changed);
|
|
788
|
+
const wantedSlugs = new Set(projections.map((p) => p.slug));
|
|
789
|
+
const removedBySlug = /* @__PURE__ */ new Map();
|
|
790
|
+
for (const id of allIds) {
|
|
791
|
+
if (!owned.has(id)) continue;
|
|
792
|
+
const note = existingById.get(id);
|
|
793
|
+
const slug = note ? parse(note.rawContent)?.slug : void 0;
|
|
794
|
+
if (slug && !wantedSlugs.has(slug)) removedBySlug.set(slug, id);
|
|
795
|
+
}
|
|
796
|
+
const removedSlugs = [...removedBySlug.keys()];
|
|
797
|
+
const old = await defStore.getMany(removedSlugs);
|
|
798
|
+
for (const [i, row] of old.entries()) {
|
|
799
|
+
if (row?.registrySource === "bundled" && removedBySlug.get(removedSlugs[i]) === row.ownerNoteId) {
|
|
800
|
+
await defStore.delete(removedSlugs[i]);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return changed.length;
|
|
804
|
+
}
|
|
924
805
|
async function putChanged(store, values, keys) {
|
|
925
806
|
if (!values.length) return;
|
|
926
807
|
const prior = await store.getMany(keys);
|
|
@@ -930,7 +811,7 @@ async function putChanged(store, values, keys) {
|
|
|
930
811
|
async function applySeedChunk(storage, write) {
|
|
931
812
|
assertSeedManifest(write.manifest);
|
|
932
813
|
assertSeedRows(write.rows);
|
|
933
|
-
const result = { totalNotes: 0, totalEfforts: 0, totalBlocks: 0, skippedUserOwned: 0, deleted: 0, addedNotes: 0, updatedNotes: 0 };
|
|
814
|
+
const result = { totalNotes: 0, totalEfforts: 0, totalEquipment: 0, totalBlocks: 0, skippedUserOwned: 0, deleted: 0, addedNotes: 0, updatedNotes: 0 };
|
|
934
815
|
const ids = await Promise.all(write.rows.map((row) => seedNoteId(row.path)));
|
|
935
816
|
const pagesBySlug = /* @__PURE__ */ new Map();
|
|
936
817
|
const linkIds = /* @__PURE__ */ new Map();
|
|
@@ -945,7 +826,7 @@ async function applySeedChunk(storage, write) {
|
|
|
945
826
|
if (!tagIds.has(label)) tagIds.set(label, await seedNoteId(`tag:${label}`));
|
|
946
827
|
}
|
|
947
828
|
}
|
|
948
|
-
return storage.transaction(["notes", "block_revisions", "block_index", "block_efforts", "page", "page_notes", "tags", "note_tags", "efforts", "meta"], "readwrite", async (tx) => {
|
|
829
|
+
return storage.transaction(["notes", "block_revisions", "block_index", "block_efforts", "block_equipment", "page", "page_notes", "tags", "note_tags", "efforts", "equipment", "meta"], "readwrite", async (tx) => {
|
|
949
830
|
const metaStore = tx.readwrite("meta");
|
|
950
831
|
const actualMeta = await metaStore.get(write.meta.key);
|
|
951
832
|
if (!equal(actualMeta, write.expectedMeta)) throw new Error("Seed checkpoint changed during import");
|
|
@@ -962,14 +843,34 @@ async function applySeedChunk(storage, write) {
|
|
|
962
843
|
return !note || note.seedOrigin === "seed" && (declared.has(id) || note.seedChunkId === write.chunkId);
|
|
963
844
|
});
|
|
964
845
|
const owned = new Set(ownedIds);
|
|
846
|
+
{
|
|
847
|
+
let checkpoints;
|
|
848
|
+
for (const [i, id] of ids.entries()) {
|
|
849
|
+
const existing = existingById.get(id);
|
|
850
|
+
if (!existing || existing.seedOrigin !== "seed") continue;
|
|
851
|
+
if (checkpoints === void 0) {
|
|
852
|
+
checkpoints = [];
|
|
853
|
+
for (const row of await metaStore.getAll()) {
|
|
854
|
+
const meta = row;
|
|
855
|
+
if (!meta || typeof meta.key !== "string" || !meta.key.startsWith("seed:") || meta.key === write.meta.key) continue;
|
|
856
|
+
const noteIds = /* @__PURE__ */ new Set();
|
|
857
|
+
for (const chunk of Object.values(meta.chunks ?? {})) for (const noteId of chunk.noteIds ?? []) noteIds.add(noteId);
|
|
858
|
+
checkpoints.push({ key: meta.key, noteIds });
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const owner = checkpoints.find((checkpoint) => checkpoint.noteIds.has(id));
|
|
862
|
+
if (owner) throw new Error(`Seed note collision: '${write.rows[i].path}' is already owned by ${owner.key}`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
965
865
|
const revisionStore = tx.readwrite("block_revisions");
|
|
966
866
|
const historyIds = ids.filter((id, i) => owned.has(id) && existingById.get(id)?.rawContent !== write.rows[i].content);
|
|
967
867
|
const histories = await revisionStore.getAllFromIndexMany("by-note", historyIds);
|
|
968
868
|
const history = new Map(historyIds.map((id, i) => [id, histories[i]]));
|
|
969
|
-
const indexStore = tx.readwrite("block_index"), effortLinks = tx.readwrite("block_efforts");
|
|
869
|
+
const indexStore = tx.readwrite("block_index"), effortLinks = tx.readwrite("block_efforts"), equipmentLinks = tx.readwrite("block_equipment");
|
|
970
870
|
const tagLinks = tx.readwrite("note_tags"), pageLinks = tx.readwrite("page_notes");
|
|
971
871
|
const currentIndexes = await indexStore.getAllFromIndexMany("by-note", ownedIds);
|
|
972
872
|
const currentEfforts = await effortLinks.getAllFromIndexMany("by-note", ownedIds);
|
|
873
|
+
const currentEquipment = await equipmentLinks.getAllFromIndexMany("by-note", ownedIds);
|
|
973
874
|
const currentTags = await tagLinks.getAllFromIndexMany("by-note", ownedIds);
|
|
974
875
|
const currentPages = await pageLinks.getAllFromIndexMany("by-note", ownedIds);
|
|
975
876
|
const slot = new Map(ownedIds.map((id, i) => [id, i]));
|
|
@@ -978,7 +879,7 @@ async function applySeedChunk(storage, write) {
|
|
|
978
879
|
const matches = await tagsStore.getAllFromIndexMany("by-label", labels);
|
|
979
880
|
const tagsByLabel = new Map(labels.map((label, i) => [label, matches[i][0]]));
|
|
980
881
|
const changedTags = [], changedNotes = [], revisions = [], pages = [], pageNotes = [], noteTags = [];
|
|
981
|
-
const blocks = [], blockEfforts = [];
|
|
882
|
+
const blocks = [], blockEfforts = [], blockEquipment = [];
|
|
982
883
|
for (const [i, row] of write.rows.entries()) {
|
|
983
884
|
const id = ids[i], existing = existingById.get(id);
|
|
984
885
|
if (!owned.has(id)) {
|
|
@@ -1005,7 +906,14 @@ async function applySeedChunk(storage, write) {
|
|
|
1005
906
|
const title = typeof fm["title"] === "string" && fm["title"].trim() ? fm["title"].trim() : titleFromPath(row.path);
|
|
1006
907
|
const sourceId = write.chunkId ? seedNoteSourceId(row.path, row.content, write.chunkId) : void 0;
|
|
1007
908
|
if (!sourceId) throw new Error(`Unknown seed source family ${write.chunkId}`);
|
|
1008
|
-
const
|
|
909
|
+
const dateMatch = typeof fm["date"] === "string" ? fm["date"].match(/^(\d{4})-(\d{2})-(\d{2})$/) : null;
|
|
910
|
+
let date;
|
|
911
|
+
if (dateMatch) {
|
|
912
|
+
const ts = Date.parse(`${fm["date"]}T12:00:00Z`);
|
|
913
|
+
const real = Number.isFinite(ts) && new Date(ts).getUTCMonth() === Number(dateMatch[2]) - 1 && new Date(ts).getUTCDate() === Number(dateMatch[3]);
|
|
914
|
+
if (!real) throw new Error(`invalid frontmatter date '${fm["date"]}' in '${row.path}' \u2014 expected a real calendar day (YYYY-MM-DD)`);
|
|
915
|
+
date = ts;
|
|
916
|
+
}
|
|
1009
917
|
const note = { ...existing, id, title, rawContent: row.content, sourceRevision, blocks: map, createdAt: write.manifest.version, ...date === void 0 ? {} : { date }, type: write.chunkId === DASHBOARDS_CHUNK_ID ? "dashboard" : "note", sourceId, catalog: catalogForChunkId(write.chunkId ?? ""), seedOrigin: "seed", seedVersion: write.manifest.version, seedChunkId: write.chunkId ?? void 0, sourcePath: row.path };
|
|
1010
918
|
if (date === void 0) delete note.date;
|
|
1011
919
|
if (!equal(note, existing)) changedNotes.push(note);
|
|
@@ -1027,6 +935,13 @@ async function applySeedChunk(storage, write) {
|
|
|
1027
935
|
const prior = currentEfforts[slot.get(id)].find((b) => b.id === link.id);
|
|
1028
936
|
if (!prior || prior.isStatic === true) blockEfforts.push(prior ? { ...link, createdAt: prior.createdAt } : link);
|
|
1029
937
|
}
|
|
938
|
+
for (const compiled of row.blockEquipment) {
|
|
939
|
+
const blockId = remap.get(compiled.blockId);
|
|
940
|
+
if (!blockId || compiledById.get(compiled.blockId)?.dataType !== "wod") throw new Error("Orphan seed equipment link");
|
|
941
|
+
const link = { ...compiled, id: `${id}:${blockId}:${compiled.equipmentSlug}`, noteId: id, blockId, sourcePath: row.path };
|
|
942
|
+
const prior = currentEquipment[slot.get(id)].find((b) => b.id === link.id);
|
|
943
|
+
if (!prior || prior.isStatic === true) blockEquipment.push(prior ? { ...link, createdAt: prior.createdAt } : link);
|
|
944
|
+
}
|
|
1030
945
|
const n = slot.get(id);
|
|
1031
946
|
const wantedTags = /* @__PURE__ */ new Set();
|
|
1032
947
|
for (const item of extractTypedTags(row.content)) {
|
|
@@ -1055,9 +970,10 @@ async function applySeedChunk(storage, write) {
|
|
|
1055
970
|
result.totalNotes++;
|
|
1056
971
|
result.totalBlocks += Object.keys(map).length;
|
|
1057
972
|
}
|
|
1058
|
-
const wantedBlocks = new Set(blocks.map((b) => b.id)), wantedEfforts = new Set(blockEfforts.map((b) => b.id));
|
|
973
|
+
const wantedBlocks = new Set(blocks.map((b) => b.id)), wantedEfforts = new Set(blockEfforts.map((b) => b.id)), wantedEquipment = new Set(blockEquipment.map((b) => b.id));
|
|
1059
974
|
for (const rows of currentIndexes) for (const row of rows) if (row.isStatic === true && !wantedBlocks.has(row.id)) await indexStore.delete(row.id);
|
|
1060
975
|
for (const rows of currentEfforts) for (const row of rows) if (row.isStatic === true && !wantedEfforts.has(row.id)) await effortLinks.delete(row.id);
|
|
976
|
+
for (const rows of currentEquipment) for (const row of rows) if (row.isStatic === true && !wantedEquipment.has(row.id)) await equipmentLinks.delete(row.id);
|
|
1061
977
|
for (const id of goneIds) {
|
|
1062
978
|
if (!owned.has(id) || !existingById.get(id)) continue;
|
|
1063
979
|
const n = slot.get(id);
|
|
@@ -1066,25 +982,17 @@ async function applySeedChunk(storage, write) {
|
|
|
1066
982
|
for (const link of currentPages[n]) await pageLinks.delete(link.id);
|
|
1067
983
|
result.deleted++;
|
|
1068
984
|
}
|
|
1069
|
-
const effortStore = tx.readwrite("efforts");
|
|
1070
985
|
if (write.chunkId === EFFORTS_CHUNK_ID) {
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
result.totalEfforts = changed.length;
|
|
1076
|
-
const wantedSlugs = new Set(efforts.map((effort) => effort.slug));
|
|
1077
|
-
const removed = allIds.map((id) => {
|
|
1078
|
-
const note = existingById.get(id);
|
|
1079
|
-
return owned.has(id) && note ? parseEffortFile(note.rawContent)?.slug : void 0;
|
|
1080
|
-
}).filter((slug) => !!slug && !wantedSlugs.has(slug));
|
|
1081
|
-
const old = await effortStore.getMany(removed);
|
|
1082
|
-
for (const [i, effort] of old.entries()) if (effort?.registrySource === "bundled") await effortStore.delete(removed[i]);
|
|
986
|
+
result.totalEfforts = await applyDefinitionChunk(tx.readwrite("efforts"), wodWikiLang.parseEffortFile, "efforts", write.rows, ids, owned, existingById, allIds);
|
|
987
|
+
}
|
|
988
|
+
if (write.chunkId === EQUIPMENT_CHUNK_ID) {
|
|
989
|
+
result.totalEquipment = await applyDefinitionChunk(tx.readwrite("equipment"), wodWikiLang.parseEquipmentFile, "equipment", write.rows, ids, owned, existingById, allIds);
|
|
1083
990
|
}
|
|
1084
991
|
if (changedNotes.length) await noteStore.putMany(changedNotes);
|
|
1085
992
|
if (revisions.length) await revisionStore.putMany(revisions);
|
|
1086
993
|
await putChanged(indexStore, blocks, blocks.map((b) => b.id));
|
|
1087
994
|
await putChanged(effortLinks, blockEfforts, blockEfforts.map((b) => b.id));
|
|
995
|
+
await putChanged(equipmentLinks, blockEquipment, blockEquipment.map((b) => b.id));
|
|
1088
996
|
if (changedTags.length) await tagsStore.putMany([...new Map(changedTags.map((t) => [t.id, t])).values()]);
|
|
1089
997
|
if (noteTags.length) await tagLinks.putMany(noteTags);
|
|
1090
998
|
await putChanged(tx.readwrite("page"), pages, pages.map((p) => p.id));
|
|
@@ -1986,6 +1894,7 @@ exports.BUILD_TIME = BUILD_TIME;
|
|
|
1986
1894
|
exports.CANVAS_CHUNK_ID = CANVAS_CHUNK_ID;
|
|
1987
1895
|
exports.DASHBOARDS_CHUNK_ID = DASHBOARDS_CHUNK_ID;
|
|
1988
1896
|
exports.EFFORTS_CHUNK_ID = EFFORTS_CHUNK_ID;
|
|
1897
|
+
exports.EQUIPMENT_CHUNK_ID = EQUIPMENT_CHUNK_ID;
|
|
1989
1898
|
exports.GIT_SHA = GIT_SHA;
|
|
1990
1899
|
exports.NOTE_ID_FRONTMATTER_KEY = NOTE_ID_FRONTMATTER_KEY;
|
|
1991
1900
|
exports.SEED_BROADCAST_CHANNEL = SEED_BROADCAST_CHANNEL;
|
|
@@ -2009,6 +1918,7 @@ exports.defineLanguagePack = defineLanguagePack;
|
|
|
2009
1918
|
exports.effortToMarkdown = effortToMarkdown;
|
|
2010
1919
|
exports.emptySeedMeta = emptySeedMeta;
|
|
2011
1920
|
exports.equipmentToCsv = equipmentToCsv;
|
|
1921
|
+
exports.equipmentToMarkdown = equipmentToMarkdown;
|
|
2012
1922
|
exports.extractNoteIdentity = extractNoteIdentity;
|
|
2013
1923
|
exports.extractTypedTags = extractTypedTags;
|
|
2014
1924
|
exports.getRegisteredLanguagePacks = getRegisteredLanguagePacks;
|
|
@@ -2016,7 +1926,6 @@ exports.inMemoryEventStore = inMemoryEventStore;
|
|
|
2016
1926
|
exports.injectIdentityFrontmatter = injectIdentityFrontmatter;
|
|
2017
1927
|
exports.isIRFile = isIRFile;
|
|
2018
1928
|
exports.listLanguagePacks = listLanguagePacks;
|
|
2019
|
-
exports.markdownToEffort = markdownToEffort;
|
|
2020
1929
|
exports.parseCsvLine = parseCsvLine;
|
|
2021
1930
|
exports.readFrontmatterMeta = readFrontmatterMeta;
|
|
2022
1931
|
exports.registerLanguagePack = registerLanguagePack;
|