@bitcobblers/wod-wiki-engine 0.37.2139 → 0.37.2140

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 CHANGED
@@ -617,16 +617,34 @@ function csvToSessionRows(csv) {
617
617
  }
618
618
 
619
619
  // src/version.ts
620
- var VERSION = "0.37.2139";
621
- var GIT_SHA = "491ec71";
622
- var BUILD_TIME = "2026-10-17T15:36:01.834Z";
620
+ var VERSION = "0.37.2140";
621
+ var GIT_SHA = "bcf7960";
622
+ var BUILD_TIME = "2026-10-18T21:43:29.081Z";
623
623
  var SEMVER = {
624
- version: "0.37.2139",
625
- gitSha: "491ec71",
626
- buildTime: "2026-10-17T15:36:01.834Z"
624
+ version: "0.37.2140",
625
+ gitSha: "bcf7960",
626
+ buildTime: "2026-10-18T21:43:29.081Z"
627
627
  };
628
628
  var SEED_SCHEMA = 9;
629
629
  var SEED_REGISTERED_WIDGETS = wodWikiCore.KNOWN_SECTION_WIDGETS;
630
+ var SEED_PRECEDENCE_RANK = {
631
+ core: 0,
632
+ community: 1,
633
+ imported: 2,
634
+ user: 3
635
+ };
636
+ function seedSourceTier(sourceIdOrMetaKey, explicitTier) {
637
+ if (explicitTier) return explicitTier;
638
+ if (!sourceIdOrMetaKey) return "core";
639
+ if (sourceIdOrMetaKey === "user") return "user";
640
+ const id = sourceIdOrMetaKey.startsWith("seed:") ? sourceIdOrMetaKey.slice(5) : sourceIdOrMetaKey;
641
+ if (id === "core" || id === "efforts" || id === "equipment") return "core";
642
+ if (id === "community" || id.startsWith("community-") || id === "common") return "community";
643
+ return "imported";
644
+ }
645
+ function seedPrecedenceRank(sourceIdOrMetaKey, explicitTier) {
646
+ return SEED_PRECEDENCE_RANK[seedSourceTier(sourceIdOrMetaKey, explicitTier)];
647
+ }
630
648
  var SEED_BROADCAST_CHANNEL = "wodwiki.seed";
631
649
  var EFFORTS_CHUNK_ID = "efforts";
632
650
  var EQUIPMENT_CHUNK_ID = "equipment";
@@ -768,36 +786,79 @@ function refreshedBlocks(existing, rawContent, sourceRevision) {
768
786
  let ordinal = 0;
769
787
  return wodWikiCore.reconcileNoteBlocks(rawContent, { sourceRevision, blocks: mapped, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${existing.id}:r${sourceRevision}:${ordinal++}` });
770
788
  }
771
- async function applyDefinitionChunk(defStore, parse, family, rows, ids, owned, existingById, allIds) {
789
+ async function applyDefinitionChunk(defStore, noteStore, parse, family, seedSource, incomingRank, otherCheckpoints, priorIds, rows, ids, owned, existingById, allIds) {
790
+ const priorIdSet = new Set(priorIds);
791
+ const maxOtherRankForNote = (noteId) => {
792
+ let max = -1;
793
+ for (const cp of otherCheckpoints) {
794
+ if (cp.noteIds.has(noteId) && cp.rank > max) max = cp.rank;
795
+ }
796
+ return max;
797
+ };
772
798
  const projections = [];
773
799
  for (const [i, row] of rows.entries()) {
774
800
  if (!owned.has(ids[i])) continue;
775
801
  const parsed = parse(row.content);
776
802
  if (!parsed) continue;
777
- projections.push({ ...parsed, registrySource: "bundled", ownerNoteId: ids[i], sourceId: family, sourcePath: row.path });
803
+ projections.push({ ...parsed, registrySource: "bundled", ownerNoteId: ids[i], sourceId: seedSource, sourcePath: row.path });
778
804
  }
779
805
  const prior = await defStore.getMany(projections.map((p) => p.slug));
780
- for (const [i, p] of projections.entries()) {
806
+ const changed = projections.filter((p, i) => {
781
807
  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})`);
808
+ if (!before) return true;
809
+ if (before.registrySource !== "bundled") return false;
810
+ let beforeRank;
811
+ if (before.ownerNoteId === p.ownerNoteId || before.ownerNoteId && priorIdSet.has(before.ownerNoteId) && maxOtherRankForNote(before.ownerNoteId) < incomingRank) {
812
+ beforeRank = incomingRank;
813
+ } else {
814
+ const cpRank = before.ownerNoteId ? maxOtherRankForNote(before.ownerNoteId) : -1;
815
+ beforeRank = cpRank >= 0 ? cpRank : seedPrecedenceRank(before.sourceId);
784
816
  }
785
- }
786
- const changed = projections.filter((p, i) => (!prior[i] || prior[i]?.registrySource === "bundled") && !equal(p, prior[i]));
817
+ return incomingRank >= beforeRank && !equal(p, before);
818
+ });
787
819
  if (changed.length) await defStore.putMany(changed);
788
820
  const wantedSlugs = new Set(projections.map((p) => p.slug));
789
821
  const removedBySlug = /* @__PURE__ */ new Map();
790
822
  for (const id of allIds) {
791
- if (!owned.has(id)) continue;
792
823
  const note = existingById.get(id);
793
- const slug = note ? parse(note.rawContent)?.slug : void 0;
824
+ if (!note || note.seedOrigin !== "seed") continue;
825
+ const slug = parse(note.rawContent)?.slug;
794
826
  if (slug && !wantedSlugs.has(slug)) removedBySlug.set(slug, id);
795
827
  }
796
828
  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]);
829
+ if (removedSlugs.length) {
830
+ const old = await defStore.getMany(removedSlugs);
831
+ for (const [i, row] of old.entries()) {
832
+ const slug = removedSlugs[i];
833
+ if (!row || row.registrySource !== "bundled") continue;
834
+ const removedNoteId = removedBySlug.get(slug);
835
+ const isOwnedByThisSource = row.ownerNoteId === removedNoteId && (!row.sourceId || row.sourceId === seedSource || row.sourceId === family);
836
+ if (!isOwnedByThisSource) continue;
837
+ let restored = false;
838
+ for (const cp of otherCheckpoints) {
839
+ const candidateIds = cp.meta.chunks?.[family]?.noteIds ?? [];
840
+ if (!candidateIds.length) continue;
841
+ const candidateNotes = await noteStore.getMany(candidateIds);
842
+ for (const candidateNote of candidateNotes) {
843
+ if (!candidateNote || candidateNote.seedOrigin !== "seed") continue;
844
+ const parsed = parse(candidateNote.rawContent);
845
+ if (parsed && parsed.slug === slug) {
846
+ await defStore.put({
847
+ ...parsed,
848
+ registrySource: "bundled",
849
+ ownerNoteId: candidateNote.id,
850
+ sourceId: cp.sourceId,
851
+ sourcePath: candidateNote.sourcePath ?? ""
852
+ });
853
+ restored = true;
854
+ break;
855
+ }
856
+ }
857
+ if (restored) break;
858
+ }
859
+ if (!restored) {
860
+ await defStore.delete(slug);
861
+ }
801
862
  }
802
863
  }
803
864
  return changed.length;
@@ -838,30 +899,41 @@ async function applySeedChunk(storage, write) {
838
899
  const allIds = [.../* @__PURE__ */ new Set([...ids, ...goneIds])];
839
900
  const stored = await noteStore.getMany(allIds);
840
901
  const existingById = new Map(allIds.map((id, i) => [id, stored[i]]));
902
+ const seedSource = write.meta.key.startsWith("seed:") ? write.meta.key.slice(5) : write.meta.key;
903
+ const incomingRank = seedPrecedenceRank(write.meta.key, write.meta.tier);
904
+ const otherCheckpoints = [];
905
+ for (const row of await metaStore.getAll()) {
906
+ const meta = row;
907
+ if (!meta || typeof meta.key !== "string" || !meta.key.startsWith("seed:") || meta.key === write.meta.key) continue;
908
+ const noteIds = /* @__PURE__ */ new Set();
909
+ for (const chunk of Object.values(meta.chunks ?? {})) {
910
+ for (const noteId of chunk.noteIds ?? []) noteIds.add(noteId);
911
+ }
912
+ otherCheckpoints.push({
913
+ key: meta.key,
914
+ sourceId: meta.key.slice(5),
915
+ rank: seedPrecedenceRank(meta.key, meta.tier),
916
+ meta,
917
+ noteIds
918
+ });
919
+ }
920
+ otherCheckpoints.sort((a, b) => b.rank - a.rank);
921
+ const maxOtherRankForNote = (noteId) => {
922
+ let max = -1;
923
+ for (const cp of otherCheckpoints) {
924
+ if (cp.noteIds.has(noteId) && cp.rank > max) max = cp.rank;
925
+ }
926
+ return max;
927
+ };
841
928
  const ownedIds = allIds.filter((id) => {
842
929
  const note = existingById.get(id);
843
- return !note || note.seedOrigin === "seed" && (declared.has(id) || note.seedChunkId === write.chunkId);
930
+ if (!note) return declared.has(id);
931
+ if (note.seedOrigin !== "seed") return false;
932
+ const otherRank = maxOtherRankForNote(id);
933
+ if (declared.has(id)) return incomingRank >= otherRank;
934
+ return otherRank < 0 && note.seedChunkId === write.chunkId;
844
935
  });
845
936
  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
- }
865
937
  const revisionStore = tx.readwrite("block_revisions");
866
938
  const historyIds = ids.filter((id, i) => owned.has(id) && existingById.get(id)?.rawContent !== write.rows[i].content);
867
939
  const histories = await revisionStore.getAllFromIndexMany("by-note", historyIds);
@@ -883,7 +955,7 @@ async function applySeedChunk(storage, write) {
883
955
  for (const [i, row] of write.rows.entries()) {
884
956
  const id = ids[i], existing = existingById.get(id);
885
957
  if (!owned.has(id)) {
886
- result.skippedUserOwned++;
958
+ if (existing && existing.seedOrigin !== "seed") result.skippedUserOwned++;
887
959
  continue;
888
960
  }
889
961
  if (!existing) result.addedNotes++;
@@ -983,10 +1055,10 @@ async function applySeedChunk(storage, write) {
983
1055
  result.deleted++;
984
1056
  }
985
1057
  if (write.chunkId === EFFORTS_CHUNK_ID) {
986
- result.totalEfforts = await applyDefinitionChunk(tx.readwrite("efforts"), wodWikiLang.parseEffortFile, "efforts", write.rows, ids, owned, existingById, allIds);
1058
+ result.totalEfforts = await applyDefinitionChunk(tx.readwrite("efforts"), noteStore, wodWikiLang.parseEffortFile, "efforts", seedSource, incomingRank, otherCheckpoints, priorIds, write.rows, ids, owned, existingById, allIds);
987
1059
  }
988
1060
  if (write.chunkId === EQUIPMENT_CHUNK_ID) {
989
- result.totalEquipment = await applyDefinitionChunk(tx.readwrite("equipment"), wodWikiLang.parseEquipmentFile, "equipment", write.rows, ids, owned, existingById, allIds);
1061
+ result.totalEquipment = await applyDefinitionChunk(tx.readwrite("equipment"), noteStore, wodWikiLang.parseEquipmentFile, "equipment", seedSource, incomingRank, otherCheckpoints, priorIds, write.rows, ids, owned, existingById, allIds);
990
1062
  }
991
1063
  if (changedNotes.length) await noteStore.putMany(changedNotes);
992
1064
  if (revisions.length) await revisionStore.putMany(revisions);
@@ -1042,6 +1114,10 @@ Object.defineProperty(exports, "ButtonBehavior", {
1042
1114
  enumerable: true,
1043
1115
  get: function () { return wodWikiLang.ButtonBehavior; }
1044
1116
  });
1117
+ Object.defineProperty(exports, "CATALOG_PRECEDENCE_RANK", {
1118
+ enumerable: true,
1119
+ get: function () { return wodWikiLang.CATALOG_PRECEDENCE_RANK; }
1120
+ });
1045
1121
  Object.defineProperty(exports, "CONSUMED_HINTS", {
1046
1122
  enumerable: true,
1047
1123
  get: function () { return wodWikiLang.CONSUMED_HINTS; }
@@ -1442,6 +1518,10 @@ Object.defineProperty(exports, "createParser", {
1442
1518
  enumerable: true,
1443
1519
  get: function () { return wodWikiLang.createParser; }
1444
1520
  });
1521
+ Object.defineProperty(exports, "dedupeEntitiesByPrecedence", {
1522
+ enumerable: true,
1523
+ get: function () { return wodWikiLang.dedupeEntitiesByPrecedence; }
1524
+ });
1445
1525
  Object.defineProperty(exports, "dialectRegistry", {
1446
1526
  enumerable: true,
1447
1527
  get: function () { return wodWikiLang.dialectRegistry; }
@@ -1574,6 +1654,22 @@ Object.defineProperty(exports, "renderMetric", {
1574
1654
  enumerable: true,
1575
1655
  get: function () { return wodWikiLang.renderMetric; }
1576
1656
  });
1657
+ Object.defineProperty(exports, "resolveEntityRank", {
1658
+ enumerable: true,
1659
+ get: function () { return wodWikiLang.resolveEntityRank; }
1660
+ });
1661
+ Object.defineProperty(exports, "resolveEntityTier", {
1662
+ enumerable: true,
1663
+ get: function () { return wodWikiLang.resolveEntityTier; }
1664
+ });
1665
+ Object.defineProperty(exports, "resolveSourceRank", {
1666
+ enumerable: true,
1667
+ get: function () { return wodWikiLang.resolveSourceRank; }
1668
+ });
1669
+ Object.defineProperty(exports, "resolveSourceTier", {
1670
+ enumerable: true,
1671
+ get: function () { return wodWikiLang.resolveSourceTier; }
1672
+ });
1577
1673
  Object.defineProperty(exports, "strategyRegistry", {
1578
1674
  enumerable: true,
1579
1675
  get: function () { return wodWikiLang.strategyRegistry; }
@@ -1898,6 +1994,7 @@ exports.EQUIPMENT_CHUNK_ID = EQUIPMENT_CHUNK_ID;
1898
1994
  exports.GIT_SHA = GIT_SHA;
1899
1995
  exports.NOTE_ID_FRONTMATTER_KEY = NOTE_ID_FRONTMATTER_KEY;
1900
1996
  exports.SEED_BROADCAST_CHANNEL = SEED_BROADCAST_CHANNEL;
1997
+ exports.SEED_PRECEDENCE_RANK = SEED_PRECEDENCE_RANK;
1901
1998
  exports.SEED_REGISTERED_WIDGETS = SEED_REGISTERED_WIDGETS;
1902
1999
  exports.SEED_SCHEMA = SEED_SCHEMA;
1903
2000
  exports.SEMVER = SEMVER;
@@ -1932,6 +2029,8 @@ exports.registerLanguagePack = registerLanguagePack;
1932
2029
  exports.seedMetaKey = seedMetaKey;
1933
2030
  exports.seedNoteId = seedNoteId;
1934
2031
  exports.seedNoteSourceId = seedNoteSourceId;
2032
+ exports.seedPrecedenceRank = seedPrecedenceRank;
2033
+ exports.seedSourceTier = seedSourceTier;
1935
2034
  exports.sessionToCsv = sessionToCsv;
1936
2035
  exports.splitCsvRecords = splitCsvRecords;
1937
2036
  exports.statementToNode = statementToNode;