@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.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KNOWN_SECTION_WIDGETS, MetricType, validateNoteBlocks, parseDocumentSections, blockContentId, reconcileNoteBlocks, mapNoteBlockChanges } from '@bitcobblers/wod-wiki-core';
|
|
2
2
|
export * from '@bitcobblers/wod-wiki-core';
|
|
3
|
-
import { dialectRegistry, realtimeProcessorRegistry, summaryProcessorRegistry, getHints,
|
|
3
|
+
import { dialectRegistry, realtimeProcessorRegistry, summaryProcessorRegistry, getHints, parseEffortFile, parseEquipmentFile } from '@bitcobblers/wod-wiki-lang';
|
|
4
4
|
export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AmrapLogicStrategy, AnalyticsEngine, AnalyticsTransformer, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BlockBuilder, ButtonBehavior, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalculationRegistry, CardioDialect, ChildSelectionBehavior, ChildrenStrategy, ChoiceGroupMetric, ClimbDialect, ClimbMetricType, CountdownTimerBehavior, CountupTimerBehavior, CrossFitDialect, DEFAULT_RESOLVER_OPTIONS, DEFAULT_RUNTIME_OPTIONS, DEFAULT_UNRESOLVED_EFFORT_MET, DIM_LENGTH, DIM_MASS, DIM_TIME, DIM_ZERO, DialectStack, EFFORT_DATA_METRIC_TYPE, EFFORT_REGISTRY_ORIGINS, EffortFallbackStrategy, EffortResolver, ErrorAction, EventBus, ExecutionContext, ExitBehavior, FixtureSyntaxError, GenericGroupStrategy, GenericLoopStrategy, GenericTimerStrategy, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, IntervalLogicStrategy, JitCompiler, LabelingBehavior, LocalRuntimeSubscription, LookupRegistry, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, NextEvent, OutputEmitter, PRODUCTION_STRATEGIES, PopBlockAction, PushBlockAction, RegisterEventHandlerAction, Registry, ReportOutputBehavior, ReportOutputStrategy, RoundsMetric, RuntimeAdapter, RuntimeClock, RuntimeFactory, RuntimeLogger, RuntimeMemory, RuntimeObservers, RuntimeStack, STORE_CALCS, STREAM_ATOMS, ScriptRuntime, SessionRootStrategy, SnapshotClock, SoundCueBehavior, SoundStrategy, StandardAnalyticsProfile, StartSessionAction, StartWorkoutAction, SubscriptionManager, TimeSpan, TypedMemoryReference, UNITS, UnitsDialect, UnregisterEventHandlerAction, VISIBILITY_ICONS, VISIBILITY_LABELS, WhiteboardScript, WodDialect, YogaDialect, buildTooltip, bundledEfforts, calculateElapsed, canonicalizeType, collapseUnresolvedChoices, compareStatement, compileLineForm, compoundName, computeColumnLabel, computeLabel, createAnalyticsEngineForBlock, createCalcEngine, createCompiler, createDialectStack, createMetricPresentationPolicy, createMockClock, createParser, dialectRegistry, dialectStack, diffStatement, evaluate, evaluateCalculateDefinitions, extractMetrics, extractStatements, findBestFuzzyMatch, findUnresolvedChoices, frozenNow, getAnalyticsFromLogs, getHints, getMetricVisibility, getRuntimeNowMs, hasHint, hintMetric, hintsToContainer, isCastSubscription, isChoiceResolved, isTimeLikeMetric, isWithinThreshold, levenshteinDistance, metricPresentation, normalizeForFuzzy, outputNodeId, outputNodeIds, parseCalcLine, parseCalculateBlock, parseExpression, parseMetricLine, parseScript, realtimeProcessorRegistry, renderMetric, strategyRegistry, summaryProcessorRegistry, toStoredOutputStatement, truthy, wallClockNow, whiteboardParser, whiteboardScript, whiteboardScriptLanguage, writeChoiceSelection } from '@bitcobblers/wod-wiki-lang';
|
|
5
5
|
export { BlockTimerDisplay, ScriptRuntimeProvider, useActiveControls, useBlockMemory, useLiveAnalytics, useMemorySubscription, useNextPreview, useOutputStatements, usePrimaryTimer, useRoundDisplay, useRuntimeExecution, useScriptRuntime, useSecondaryTimers, useSnapshotBlocks, useStackDisplayRows, useStackFragmentSources, useStackSnapshot, useStackTimers, useTimerDisplay, useTimerElapsed } from '@bitcobblers/wod-wiki-lang/react';
|
|
6
6
|
import { WQL_TYPED_TAG_KEYS } from '@bitcobblers/wod-wiki-wql';
|
|
@@ -246,43 +246,6 @@ function extractTypedTags(markdown, typedKeys = WQL_TYPED_TAG_KEYS) {
|
|
|
246
246
|
}
|
|
247
247
|
return results.filter((t) => t.label.length > 0);
|
|
248
248
|
}
|
|
249
|
-
function markdownToEffort(raw) {
|
|
250
|
-
const match = FRONTMATTER_BLOCK_RE.exec(raw);
|
|
251
|
-
if (!match) return null;
|
|
252
|
-
const meta = readFrontmatterMeta(raw);
|
|
253
|
-
const body = raw.slice(match[0].length).trim();
|
|
254
|
-
const id = scalarOf(meta["id"]);
|
|
255
|
-
const slug = scalarOf(meta["slug"]);
|
|
256
|
-
const label = scalarOf(meta["label"]);
|
|
257
|
-
if (!id || !slug || !label) return null;
|
|
258
|
-
const baseAttributes = {};
|
|
259
|
-
const blockRe = /^\s{2,}([^:]+):\s*(.+)$/;
|
|
260
|
-
let inAttrs = false;
|
|
261
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
262
|
-
if (/^baseAttributes:/.test(line)) {
|
|
263
|
-
inAttrs = true;
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
if (inAttrs) {
|
|
267
|
-
const attr = blockRe.exec(line);
|
|
268
|
-
if (attr && /^ {2,}/.test(line)) {
|
|
269
|
-
const v = attr[2].trim();
|
|
270
|
-
baseAttributes[attr[1].trim()] = /^\d+(\.\d+)?$/.test(v) ? Number(v) : v;
|
|
271
|
-
} else if (/^\S/.test(line)) {
|
|
272
|
-
inAttrs = false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (typeof baseAttributes["met"] !== "number") return null;
|
|
277
|
-
return {
|
|
278
|
-
id,
|
|
279
|
-
slug,
|
|
280
|
-
label,
|
|
281
|
-
aliases: asList(meta["aliases"]),
|
|
282
|
-
baseAttributes,
|
|
283
|
-
...body ? { body } : {}
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
249
|
function effortToMarkdown(effort) {
|
|
287
250
|
const lines = ["---", `id: ${effort.id}`, `slug: ${effort.slug}`, `label: ${effort.label}`];
|
|
288
251
|
if (effort.aliases && effort.aliases.length > 0) {
|
|
@@ -298,8 +261,15 @@ function effortToMarkdown(effort) {
|
|
|
298
261
|
return `${lines.join("\n")}
|
|
299
262
|
${(effort.body ?? "").trim()}`.trimEnd() + "\n";
|
|
300
263
|
}
|
|
301
|
-
function
|
|
302
|
-
|
|
264
|
+
function equipmentToMarkdown(equipment) {
|
|
265
|
+
const lines = ["---", "type: equipment", `id: ${equipment.id}`, `slug: ${equipment.slug}`, `label: ${equipment.label}`];
|
|
266
|
+
if (equipment.aliases && equipment.aliases.length > 0) {
|
|
267
|
+
lines.push("aliases:");
|
|
268
|
+
for (const alias of equipment.aliases) lines.push(` - ${alias}`);
|
|
269
|
+
}
|
|
270
|
+
lines.push("---", "");
|
|
271
|
+
return `${lines.join("\n")}
|
|
272
|
+
${(equipment.body ?? "").trim()}`.trimEnd() + "\n";
|
|
303
273
|
}
|
|
304
274
|
function equipmentToCsv(labels) {
|
|
305
275
|
return ["label", ...labels].map((l) => escapeCsvField(l)).join("\n") + "\n";
|
|
@@ -648,18 +618,19 @@ function csvToSessionRows(csv) {
|
|
|
648
618
|
}
|
|
649
619
|
|
|
650
620
|
// src/version.ts
|
|
651
|
-
var VERSION = "0.
|
|
652
|
-
var GIT_SHA = "
|
|
653
|
-
var BUILD_TIME = "2026-10-
|
|
621
|
+
var VERSION = "0.37.2139";
|
|
622
|
+
var GIT_SHA = "491ec71";
|
|
623
|
+
var BUILD_TIME = "2026-10-17T15:36:01.834Z";
|
|
654
624
|
var SEMVER = {
|
|
655
|
-
version: "0.
|
|
656
|
-
gitSha: "
|
|
657
|
-
buildTime: "2026-10-
|
|
625
|
+
version: "0.37.2139",
|
|
626
|
+
gitSha: "491ec71",
|
|
627
|
+
buildTime: "2026-10-17T15:36:01.834Z"
|
|
658
628
|
};
|
|
659
|
-
var SEED_SCHEMA =
|
|
629
|
+
var SEED_SCHEMA = 9;
|
|
660
630
|
var SEED_REGISTERED_WIDGETS = KNOWN_SECTION_WIDGETS;
|
|
661
631
|
var SEED_BROADCAST_CHANNEL = "wodwiki.seed";
|
|
662
632
|
var EFFORTS_CHUNK_ID = "efforts";
|
|
633
|
+
var EQUIPMENT_CHUNK_ID = "equipment";
|
|
663
634
|
var CANVAS_CHUNK_ID = "canvas";
|
|
664
635
|
var DASHBOARDS_CHUNK_ID = "dashboards";
|
|
665
636
|
var TEMPLATE_CHUNK_ID = "template";
|
|
@@ -685,6 +656,7 @@ function seedNoteSourceId(path, content, chunkId) {
|
|
|
685
656
|
if (chunkId === CANVAS_CHUNK_ID) return `guides:${canvasRouteSlug(content) ?? under}`;
|
|
686
657
|
if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
|
|
687
658
|
if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
|
|
659
|
+
if (chunkId === EQUIPMENT_CHUNK_ID) return "equipment";
|
|
688
660
|
if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
|
|
689
661
|
if (chunkId.startsWith("collection.")) {
|
|
690
662
|
const catalog = catalogForChunkId(chunkId);
|
|
@@ -693,141 +665,6 @@ function seedNoteSourceId(path, content, chunkId) {
|
|
|
693
665
|
if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
|
|
694
666
|
return void 0;
|
|
695
667
|
}
|
|
696
|
-
|
|
697
|
-
// src/seed/frontmatter.ts
|
|
698
|
-
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
699
|
-
function unquote(value) {
|
|
700
|
-
return value.replace(
|
|
701
|
-
/^(['"])(.*)\1$/,
|
|
702
|
-
(_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
function parseFrontmatter(raw) {
|
|
706
|
-
const match = raw.match(FRONTMATTER_RE);
|
|
707
|
-
if (!match) return { meta: {}, body: raw };
|
|
708
|
-
return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
|
|
709
|
-
}
|
|
710
|
-
function parseMetaLines(lines) {
|
|
711
|
-
const meta = {};
|
|
712
|
-
for (let i = 0; i < lines.length; i++) {
|
|
713
|
-
const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
|
|
714
|
-
if (!keyMatch) continue;
|
|
715
|
-
const key = keyMatch[1];
|
|
716
|
-
const rawVal = keyMatch[2].trim();
|
|
717
|
-
if (rawVal === "") {
|
|
718
|
-
const items = [];
|
|
719
|
-
while (i + 1 < lines.length) {
|
|
720
|
-
const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
|
|
721
|
-
if (!itemMatch) break;
|
|
722
|
-
items.push(itemMatch[1].trim());
|
|
723
|
-
i++;
|
|
724
|
-
}
|
|
725
|
-
meta[key] = items.length > 0 ? items : "";
|
|
726
|
-
} else {
|
|
727
|
-
const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
|
|
728
|
-
const value = unquote(rawVal);
|
|
729
|
-
const num2 = Number(value);
|
|
730
|
-
meta[key] = !wasQuoted && value !== "" && !isNaN(num2) ? num2 : value;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
return meta;
|
|
734
|
-
}
|
|
735
|
-
function getScalar(meta, key) {
|
|
736
|
-
const value = meta[key];
|
|
737
|
-
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
738
|
-
}
|
|
739
|
-
function getList(meta, key) {
|
|
740
|
-
const value = meta[key];
|
|
741
|
-
return Array.isArray(value) ? value : [];
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// src/seed/effort-file.ts
|
|
745
|
-
function parseDiscipline(val) {
|
|
746
|
-
if (!val) return void 0;
|
|
747
|
-
const normalized = unquoteYaml(val.trim()).toLowerCase();
|
|
748
|
-
if (isEffortDiscipline(normalized)) return normalized;
|
|
749
|
-
console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
|
|
750
|
-
return void 0;
|
|
751
|
-
}
|
|
752
|
-
function unquoteYaml(val) {
|
|
753
|
-
if (val.startsWith('"') && val.endsWith('"')) {
|
|
754
|
-
return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
755
|
-
}
|
|
756
|
-
if (val.startsWith("'") && val.endsWith("'")) {
|
|
757
|
-
return val.slice(1, -1).replace(/\\'/g, "'");
|
|
758
|
-
}
|
|
759
|
-
return val;
|
|
760
|
-
}
|
|
761
|
-
function coerceNestedScalar(raw) {
|
|
762
|
-
if (raw === "true") return true;
|
|
763
|
-
if (raw === "false") return false;
|
|
764
|
-
if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
|
|
765
|
-
return raw;
|
|
766
|
-
}
|
|
767
|
-
function readNestedSection(lines, key) {
|
|
768
|
-
const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
|
|
769
|
-
if (start === -1) return void 0;
|
|
770
|
-
const result = {};
|
|
771
|
-
let subKey = null;
|
|
772
|
-
for (let i = start + 1; i < lines.length; i++) {
|
|
773
|
-
const line = lines[i];
|
|
774
|
-
if (/^\S/.test(line)) break;
|
|
775
|
-
const trimmed = line.trim();
|
|
776
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
777
|
-
const indent = line.length - line.trimStart().length;
|
|
778
|
-
const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
|
|
779
|
-
if (!m) continue;
|
|
780
|
-
const [, k, rawVal] = m;
|
|
781
|
-
if (indent === 2) {
|
|
782
|
-
if (rawVal === "") {
|
|
783
|
-
subKey = k;
|
|
784
|
-
result[k] = {};
|
|
785
|
-
} else {
|
|
786
|
-
subKey = null;
|
|
787
|
-
result[k] = coerceNestedScalar(rawVal);
|
|
788
|
-
}
|
|
789
|
-
} else if (indent >= 4 && subKey !== null) {
|
|
790
|
-
result[subKey][k] = coerceNestedScalar(rawVal);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
794
|
-
}
|
|
795
|
-
function parseEffortFile(raw) {
|
|
796
|
-
const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
797
|
-
if (!blockMatch) return null;
|
|
798
|
-
const { meta, body } = parseFrontmatter(raw);
|
|
799
|
-
const innerLines = blockMatch[1].split(/\r?\n/);
|
|
800
|
-
const id = getScalar(meta, "id");
|
|
801
|
-
const slug = getScalar(meta, "slug");
|
|
802
|
-
const label = getScalar(meta, "label");
|
|
803
|
-
const aliases = getList(meta, "aliases");
|
|
804
|
-
const baseAttrs = readNestedSection(innerLines, "baseAttributes");
|
|
805
|
-
const hints = readNestedSection(innerLines, "hints");
|
|
806
|
-
const derivation = readNestedSection(innerLines, "derivation");
|
|
807
|
-
const metRaw = baseAttrs?.met;
|
|
808
|
-
const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
|
|
809
|
-
if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
|
|
810
|
-
const discipline = parseDiscipline(
|
|
811
|
-
typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
|
|
812
|
-
);
|
|
813
|
-
const intensityTier = baseAttrs?.intensityTier;
|
|
814
|
-
const trimmedBody = body.trim();
|
|
815
|
-
return {
|
|
816
|
-
id: String(id),
|
|
817
|
-
slug: String(slug),
|
|
818
|
-
label: String(label),
|
|
819
|
-
aliases,
|
|
820
|
-
baseAttributes: {
|
|
821
|
-
met,
|
|
822
|
-
...discipline ? { discipline } : {},
|
|
823
|
-
...intensityTier ? { intensityTier } : {}
|
|
824
|
-
},
|
|
825
|
-
registrySource: "bundled",
|
|
826
|
-
...trimmedBody ? { body: trimmedBody } : {},
|
|
827
|
-
...hints ? { hints } : {},
|
|
828
|
-
...derivation ? { derivation } : {}
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
668
|
function object(value) {
|
|
832
669
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
833
670
|
}
|
|
@@ -840,7 +677,8 @@ function nonnegative(value) {
|
|
|
840
677
|
function assertSeedManifest(value) {
|
|
841
678
|
if (!object(value) || value.schema !== SEED_SCHEMA) throw new Error("Incompatible seed schema; rebuild seed artifacts");
|
|
842
679
|
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");
|
|
843
|
-
for (const key of ["versionLabel", "commit"]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error("invalid seed manifest metadata");
|
|
680
|
+
for (const key of ["versionLabel", "commit", "name", "description"]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error("invalid seed manifest metadata");
|
|
681
|
+
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");
|
|
844
682
|
if (value.dirty !== void 0 && typeof value.dirty !== "boolean") throw new Error("invalid seed manifest metadata");
|
|
845
683
|
const ids = /* @__PURE__ */ new Set(), paths = /* @__PURE__ */ new Set();
|
|
846
684
|
for (const chunk of value.chunks) {
|
|
@@ -854,7 +692,7 @@ function assertSeedRows(value) {
|
|
|
854
692
|
if (!Array.isArray(value)) throw new Error("seed chunk is not an array");
|
|
855
693
|
const paths = /* @__PURE__ */ new Set();
|
|
856
694
|
for (const row of value) {
|
|
857
|
-
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");
|
|
695
|
+
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");
|
|
858
696
|
paths.add(row.path);
|
|
859
697
|
for (const [id, block] of Object.entries(row.blocks)) {
|
|
860
698
|
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");
|
|
@@ -885,6 +723,15 @@ function assertSeedRows(value) {
|
|
|
885
723
|
if (!object(projection) || link.noteId !== projection.noteId || link.blockContentId !== projection.blockContentId || !Number.isFinite(link.createdAt)) throw new Error("invalid seed effort metadata");
|
|
886
724
|
links.add(String(link.id));
|
|
887
725
|
}
|
|
726
|
+
const equipmentLinks = /* @__PURE__ */ new Set();
|
|
727
|
+
for (const link of row.blockEquipment) {
|
|
728
|
+
if (!object(link) || typeof link.blockId !== "string" || typeof link.noteId !== "string" || typeof link.equipmentSlug !== "string" || !link.equipmentSlug) throw new Error("invalid seed equipment identity");
|
|
729
|
+
const block = row.blocks[link.blockId];
|
|
730
|
+
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");
|
|
731
|
+
const projection = row.blockIndex.find((p) => object(p) && p.blockId === link.blockId);
|
|
732
|
+
if (!object(projection) || link.noteId !== projection.noteId || link.blockContentId !== projection.blockContentId || !Number.isFinite(link.createdAt)) throw new Error("invalid seed equipment metadata");
|
|
733
|
+
equipmentLinks.add(String(link.id));
|
|
734
|
+
}
|
|
888
735
|
}
|
|
889
736
|
return value;
|
|
890
737
|
}
|
|
@@ -922,6 +769,40 @@ function refreshedBlocks(existing, rawContent, sourceRevision) {
|
|
|
922
769
|
let ordinal = 0;
|
|
923
770
|
return reconcileNoteBlocks(rawContent, { sourceRevision, blocks: mapped, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${existing.id}:r${sourceRevision}:${ordinal++}` });
|
|
924
771
|
}
|
|
772
|
+
async function applyDefinitionChunk(defStore, parse, family, rows, ids, owned, existingById, allIds) {
|
|
773
|
+
const projections = [];
|
|
774
|
+
for (const [i, row] of rows.entries()) {
|
|
775
|
+
if (!owned.has(ids[i])) continue;
|
|
776
|
+
const parsed = parse(row.content);
|
|
777
|
+
if (!parsed) continue;
|
|
778
|
+
projections.push({ ...parsed, registrySource: "bundled", ownerNoteId: ids[i], sourceId: family, sourcePath: row.path });
|
|
779
|
+
}
|
|
780
|
+
const prior = await defStore.getMany(projections.map((p) => p.slug));
|
|
781
|
+
for (const [i, p] of projections.entries()) {
|
|
782
|
+
const before = prior[i];
|
|
783
|
+
if (before?.registrySource === "bundled" && before.ownerNoteId && before.ownerNoteId !== p.ownerNoteId) {
|
|
784
|
+
throw new Error(`Seed definition collision: '${p.slug}' is already owned by another seed source (note ${before.ownerNoteId})`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
const changed = projections.filter((p, i) => (!prior[i] || prior[i]?.registrySource === "bundled") && !equal(p, prior[i]));
|
|
788
|
+
if (changed.length) await defStore.putMany(changed);
|
|
789
|
+
const wantedSlugs = new Set(projections.map((p) => p.slug));
|
|
790
|
+
const removedBySlug = /* @__PURE__ */ new Map();
|
|
791
|
+
for (const id of allIds) {
|
|
792
|
+
if (!owned.has(id)) continue;
|
|
793
|
+
const note = existingById.get(id);
|
|
794
|
+
const slug = note ? parse(note.rawContent)?.slug : void 0;
|
|
795
|
+
if (slug && !wantedSlugs.has(slug)) removedBySlug.set(slug, id);
|
|
796
|
+
}
|
|
797
|
+
const removedSlugs = [...removedBySlug.keys()];
|
|
798
|
+
const old = await defStore.getMany(removedSlugs);
|
|
799
|
+
for (const [i, row] of old.entries()) {
|
|
800
|
+
if (row?.registrySource === "bundled" && removedBySlug.get(removedSlugs[i]) === row.ownerNoteId) {
|
|
801
|
+
await defStore.delete(removedSlugs[i]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return changed.length;
|
|
805
|
+
}
|
|
925
806
|
async function putChanged(store, values, keys) {
|
|
926
807
|
if (!values.length) return;
|
|
927
808
|
const prior = await store.getMany(keys);
|
|
@@ -931,7 +812,7 @@ async function putChanged(store, values, keys) {
|
|
|
931
812
|
async function applySeedChunk(storage, write) {
|
|
932
813
|
assertSeedManifest(write.manifest);
|
|
933
814
|
assertSeedRows(write.rows);
|
|
934
|
-
const result = { totalNotes: 0, totalEfforts: 0, totalBlocks: 0, skippedUserOwned: 0, deleted: 0, addedNotes: 0, updatedNotes: 0 };
|
|
815
|
+
const result = { totalNotes: 0, totalEfforts: 0, totalEquipment: 0, totalBlocks: 0, skippedUserOwned: 0, deleted: 0, addedNotes: 0, updatedNotes: 0 };
|
|
935
816
|
const ids = await Promise.all(write.rows.map((row) => seedNoteId(row.path)));
|
|
936
817
|
const pagesBySlug = /* @__PURE__ */ new Map();
|
|
937
818
|
const linkIds = /* @__PURE__ */ new Map();
|
|
@@ -946,7 +827,7 @@ async function applySeedChunk(storage, write) {
|
|
|
946
827
|
if (!tagIds.has(label)) tagIds.set(label, await seedNoteId(`tag:${label}`));
|
|
947
828
|
}
|
|
948
829
|
}
|
|
949
|
-
return storage.transaction(["notes", "block_revisions", "block_index", "block_efforts", "page", "page_notes", "tags", "note_tags", "efforts", "meta"], "readwrite", async (tx) => {
|
|
830
|
+
return storage.transaction(["notes", "block_revisions", "block_index", "block_efforts", "block_equipment", "page", "page_notes", "tags", "note_tags", "efforts", "equipment", "meta"], "readwrite", async (tx) => {
|
|
950
831
|
const metaStore = tx.readwrite("meta");
|
|
951
832
|
const actualMeta = await metaStore.get(write.meta.key);
|
|
952
833
|
if (!equal(actualMeta, write.expectedMeta)) throw new Error("Seed checkpoint changed during import");
|
|
@@ -963,14 +844,34 @@ async function applySeedChunk(storage, write) {
|
|
|
963
844
|
return !note || note.seedOrigin === "seed" && (declared.has(id) || note.seedChunkId === write.chunkId);
|
|
964
845
|
});
|
|
965
846
|
const owned = new Set(ownedIds);
|
|
847
|
+
{
|
|
848
|
+
let checkpoints;
|
|
849
|
+
for (const [i, id] of ids.entries()) {
|
|
850
|
+
const existing = existingById.get(id);
|
|
851
|
+
if (!existing || existing.seedOrigin !== "seed") continue;
|
|
852
|
+
if (checkpoints === void 0) {
|
|
853
|
+
checkpoints = [];
|
|
854
|
+
for (const row of await metaStore.getAll()) {
|
|
855
|
+
const meta = row;
|
|
856
|
+
if (!meta || typeof meta.key !== "string" || !meta.key.startsWith("seed:") || meta.key === write.meta.key) continue;
|
|
857
|
+
const noteIds = /* @__PURE__ */ new Set();
|
|
858
|
+
for (const chunk of Object.values(meta.chunks ?? {})) for (const noteId of chunk.noteIds ?? []) noteIds.add(noteId);
|
|
859
|
+
checkpoints.push({ key: meta.key, noteIds });
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const owner = checkpoints.find((checkpoint) => checkpoint.noteIds.has(id));
|
|
863
|
+
if (owner) throw new Error(`Seed note collision: '${write.rows[i].path}' is already owned by ${owner.key}`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
966
866
|
const revisionStore = tx.readwrite("block_revisions");
|
|
967
867
|
const historyIds = ids.filter((id, i) => owned.has(id) && existingById.get(id)?.rawContent !== write.rows[i].content);
|
|
968
868
|
const histories = await revisionStore.getAllFromIndexMany("by-note", historyIds);
|
|
969
869
|
const history = new Map(historyIds.map((id, i) => [id, histories[i]]));
|
|
970
|
-
const indexStore = tx.readwrite("block_index"), effortLinks = tx.readwrite("block_efforts");
|
|
870
|
+
const indexStore = tx.readwrite("block_index"), effortLinks = tx.readwrite("block_efforts"), equipmentLinks = tx.readwrite("block_equipment");
|
|
971
871
|
const tagLinks = tx.readwrite("note_tags"), pageLinks = tx.readwrite("page_notes");
|
|
972
872
|
const currentIndexes = await indexStore.getAllFromIndexMany("by-note", ownedIds);
|
|
973
873
|
const currentEfforts = await effortLinks.getAllFromIndexMany("by-note", ownedIds);
|
|
874
|
+
const currentEquipment = await equipmentLinks.getAllFromIndexMany("by-note", ownedIds);
|
|
974
875
|
const currentTags = await tagLinks.getAllFromIndexMany("by-note", ownedIds);
|
|
975
876
|
const currentPages = await pageLinks.getAllFromIndexMany("by-note", ownedIds);
|
|
976
877
|
const slot = new Map(ownedIds.map((id, i) => [id, i]));
|
|
@@ -979,7 +880,7 @@ async function applySeedChunk(storage, write) {
|
|
|
979
880
|
const matches = await tagsStore.getAllFromIndexMany("by-label", labels);
|
|
980
881
|
const tagsByLabel = new Map(labels.map((label, i) => [label, matches[i][0]]));
|
|
981
882
|
const changedTags = [], changedNotes = [], revisions = [], pages = [], pageNotes = [], noteTags = [];
|
|
982
|
-
const blocks = [], blockEfforts = [];
|
|
883
|
+
const blocks = [], blockEfforts = [], blockEquipment = [];
|
|
983
884
|
for (const [i, row] of write.rows.entries()) {
|
|
984
885
|
const id = ids[i], existing = existingById.get(id);
|
|
985
886
|
if (!owned.has(id)) {
|
|
@@ -1006,7 +907,14 @@ async function applySeedChunk(storage, write) {
|
|
|
1006
907
|
const title = typeof fm["title"] === "string" && fm["title"].trim() ? fm["title"].trim() : titleFromPath(row.path);
|
|
1007
908
|
const sourceId = write.chunkId ? seedNoteSourceId(row.path, row.content, write.chunkId) : void 0;
|
|
1008
909
|
if (!sourceId) throw new Error(`Unknown seed source family ${write.chunkId}`);
|
|
1009
|
-
const
|
|
910
|
+
const dateMatch = typeof fm["date"] === "string" ? fm["date"].match(/^(\d{4})-(\d{2})-(\d{2})$/) : null;
|
|
911
|
+
let date;
|
|
912
|
+
if (dateMatch) {
|
|
913
|
+
const ts = Date.parse(`${fm["date"]}T12:00:00Z`);
|
|
914
|
+
const real = Number.isFinite(ts) && new Date(ts).getUTCMonth() === Number(dateMatch[2]) - 1 && new Date(ts).getUTCDate() === Number(dateMatch[3]);
|
|
915
|
+
if (!real) throw new Error(`invalid frontmatter date '${fm["date"]}' in '${row.path}' \u2014 expected a real calendar day (YYYY-MM-DD)`);
|
|
916
|
+
date = ts;
|
|
917
|
+
}
|
|
1010
918
|
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 };
|
|
1011
919
|
if (date === void 0) delete note.date;
|
|
1012
920
|
if (!equal(note, existing)) changedNotes.push(note);
|
|
@@ -1028,6 +936,13 @@ async function applySeedChunk(storage, write) {
|
|
|
1028
936
|
const prior = currentEfforts[slot.get(id)].find((b) => b.id === link.id);
|
|
1029
937
|
if (!prior || prior.isStatic === true) blockEfforts.push(prior ? { ...link, createdAt: prior.createdAt } : link);
|
|
1030
938
|
}
|
|
939
|
+
for (const compiled of row.blockEquipment) {
|
|
940
|
+
const blockId = remap.get(compiled.blockId);
|
|
941
|
+
if (!blockId || compiledById.get(compiled.blockId)?.dataType !== "wod") throw new Error("Orphan seed equipment link");
|
|
942
|
+
const link = { ...compiled, id: `${id}:${blockId}:${compiled.equipmentSlug}`, noteId: id, blockId, sourcePath: row.path };
|
|
943
|
+
const prior = currentEquipment[slot.get(id)].find((b) => b.id === link.id);
|
|
944
|
+
if (!prior || prior.isStatic === true) blockEquipment.push(prior ? { ...link, createdAt: prior.createdAt } : link);
|
|
945
|
+
}
|
|
1031
946
|
const n = slot.get(id);
|
|
1032
947
|
const wantedTags = /* @__PURE__ */ new Set();
|
|
1033
948
|
for (const item of extractTypedTags(row.content)) {
|
|
@@ -1056,9 +971,10 @@ async function applySeedChunk(storage, write) {
|
|
|
1056
971
|
result.totalNotes++;
|
|
1057
972
|
result.totalBlocks += Object.keys(map).length;
|
|
1058
973
|
}
|
|
1059
|
-
const wantedBlocks = new Set(blocks.map((b) => b.id)), wantedEfforts = new Set(blockEfforts.map((b) => b.id));
|
|
974
|
+
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));
|
|
1060
975
|
for (const rows of currentIndexes) for (const row of rows) if (row.isStatic === true && !wantedBlocks.has(row.id)) await indexStore.delete(row.id);
|
|
1061
976
|
for (const rows of currentEfforts) for (const row of rows) if (row.isStatic === true && !wantedEfforts.has(row.id)) await effortLinks.delete(row.id);
|
|
977
|
+
for (const rows of currentEquipment) for (const row of rows) if (row.isStatic === true && !wantedEquipment.has(row.id)) await equipmentLinks.delete(row.id);
|
|
1062
978
|
for (const id of goneIds) {
|
|
1063
979
|
if (!owned.has(id) || !existingById.get(id)) continue;
|
|
1064
980
|
const n = slot.get(id);
|
|
@@ -1067,25 +983,17 @@ async function applySeedChunk(storage, write) {
|
|
|
1067
983
|
for (const link of currentPages[n]) await pageLinks.delete(link.id);
|
|
1068
984
|
result.deleted++;
|
|
1069
985
|
}
|
|
1070
|
-
const effortStore = tx.readwrite("efforts");
|
|
1071
986
|
if (write.chunkId === EFFORTS_CHUNK_ID) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
result.totalEfforts = changed.length;
|
|
1077
|
-
const wantedSlugs = new Set(efforts.map((effort) => effort.slug));
|
|
1078
|
-
const removed = allIds.map((id) => {
|
|
1079
|
-
const note = existingById.get(id);
|
|
1080
|
-
return owned.has(id) && note ? parseEffortFile(note.rawContent)?.slug : void 0;
|
|
1081
|
-
}).filter((slug) => !!slug && !wantedSlugs.has(slug));
|
|
1082
|
-
const old = await effortStore.getMany(removed);
|
|
1083
|
-
for (const [i, effort] of old.entries()) if (effort?.registrySource === "bundled") await effortStore.delete(removed[i]);
|
|
987
|
+
result.totalEfforts = await applyDefinitionChunk(tx.readwrite("efforts"), parseEffortFile, "efforts", write.rows, ids, owned, existingById, allIds);
|
|
988
|
+
}
|
|
989
|
+
if (write.chunkId === EQUIPMENT_CHUNK_ID) {
|
|
990
|
+
result.totalEquipment = await applyDefinitionChunk(tx.readwrite("equipment"), parseEquipmentFile, "equipment", write.rows, ids, owned, existingById, allIds);
|
|
1084
991
|
}
|
|
1085
992
|
if (changedNotes.length) await noteStore.putMany(changedNotes);
|
|
1086
993
|
if (revisions.length) await revisionStore.putMany(revisions);
|
|
1087
994
|
await putChanged(indexStore, blocks, blocks.map((b) => b.id));
|
|
1088
995
|
await putChanged(effortLinks, blockEfforts, blockEfforts.map((b) => b.id));
|
|
996
|
+
await putChanged(equipmentLinks, blockEquipment, blockEquipment.map((b) => b.id));
|
|
1089
997
|
if (changedTags.length) await tagsStore.putMany([...new Map(changedTags.map((t) => [t.id, t])).values()]);
|
|
1090
998
|
if (noteTags.length) await tagLinks.putMany(noteTags);
|
|
1091
999
|
await putChanged(tx.readwrite("page"), pages, pages.map((p) => p.id));
|
|
@@ -1095,6 +1003,6 @@ async function applySeedChunk(storage, write) {
|
|
|
1095
1003
|
});
|
|
1096
1004
|
}
|
|
1097
1005
|
|
|
1098
|
-
export { BUILD_TIME, CANVAS_CHUNK_ID, DASHBOARDS_CHUNK_ID, EFFORTS_CHUNK_ID, GIT_SHA, NOTE_ID_FRONTMATTER_KEY, SEED_BROADCAST_CHANNEL, SEED_REGISTERED_WIDGETS, SEED_SCHEMA, SEMVER, SESSION_CSV_HEADER, TEMPLATE_CHUNK_ID, VERSION, applySeedChunk, assertSeedManifest, assertSeedRows, buildStatementTree, canvasRouteSlug, catalogForChunkId, composeNoteMarkdown, createIRFile, csvToEquipmentLabels, csvToSessionRows, defineLanguagePack, effortToMarkdown, emptySeedMeta, equipmentToCsv, extractNoteIdentity, extractTypedTags, getRegisteredLanguagePacks, inMemoryEventStore, injectIdentityFrontmatter, isIRFile, listLanguagePacks,
|
|
1006
|
+
export { BUILD_TIME, CANVAS_CHUNK_ID, DASHBOARDS_CHUNK_ID, EFFORTS_CHUNK_ID, EQUIPMENT_CHUNK_ID, GIT_SHA, NOTE_ID_FRONTMATTER_KEY, SEED_BROADCAST_CHANNEL, SEED_REGISTERED_WIDGETS, SEED_SCHEMA, SEMVER, SESSION_CSV_HEADER, TEMPLATE_CHUNK_ID, VERSION, applySeedChunk, assertSeedManifest, assertSeedRows, buildStatementTree, canvasRouteSlug, catalogForChunkId, composeNoteMarkdown, createIRFile, csvToEquipmentLabels, csvToSessionRows, defineLanguagePack, effortToMarkdown, emptySeedMeta, equipmentToCsv, equipmentToMarkdown, extractNoteIdentity, extractTypedTags, getRegisteredLanguagePacks, inMemoryEventStore, injectIdentityFrontmatter, isIRFile, listLanguagePacks, parseCsvLine, readFrontmatterMeta, registerLanguagePack, seedMetaKey, seedNoteId, seedNoteSourceId, sessionToCsv, splitCsvRecords, statementToNode, unregisterLanguagePack };
|
|
1099
1007
|
//# sourceMappingURL=index.mjs.map
|
|
1100
1008
|
//# sourceMappingURL=index.mjs.map
|