yamlover 0.3.23 → 0.3.24

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/server.js CHANGED
@@ -62331,6 +62331,7 @@ var require_ignore = __commonJS({
62331
62331
  // src/server/engine-api.ts
62332
62332
  import path10 from "node:path";
62333
62333
  import fs10 from "node:fs";
62334
+ import os from "node:os";
62334
62335
  import { fileURLToPath as fileURLToPath3 } from "node:url";
62335
62336
 
62336
62337
  // ../parser/ts/src/ir.ts
@@ -62535,7 +62536,7 @@ function pathOf(chain) {
62535
62536
 
62536
62537
  // ../engine/ts/src/store.ts
62537
62538
  import { DatabaseSync } from "node:sqlite";
62538
- var SCHEMA_VERSION = 5;
62539
+ var SCHEMA_VERSION = 6;
62539
62540
  var SCHEMA = `
62540
62541
  CREATE TABLE IF NOT EXISTS node (
62541
62542
  path TEXT PRIMARY KEY,
@@ -62565,8 +62566,14 @@ CREATE TABLE IF NOT EXISTS file (
62565
62566
  CREATE TABLE IF NOT EXISTS dangling (
62566
62567
  from_path TEXT NOT NULL, -- the entry holding the pointer
62567
62568
  raw TEXT NOT NULL, -- the pointer text as authored
62568
- reason TEXT NOT NULL -- why it did not resolve
62569
+ reason TEXT NOT NULL, -- why it did not resolve (externals: the authority)
62570
+ holder TEXT NOT NULL, -- the mapping that holds the entry
62571
+ label TEXT, -- the entry's key; null for keyless/positional
62572
+ pos INTEGER, -- the entry's index in the holder (source order)
62573
+ edge TEXT NOT NULL, -- ref | back (as authored)
62574
+ external INTEGER NOT NULL DEFAULT 0 -- 1 = an external-authority link, not an error
62569
62575
  );
62576
+ CREATE INDEX IF NOT EXISTS dangling_holder ON dangling (holder);
62570
62577
  `;
62571
62578
  function encodeScalarValue(v) {
62572
62579
  if (typeof v === "number" && !Number.isFinite(v)) return JSON.stringify({ "@nonfinite": String(v) });
@@ -62631,10 +62638,10 @@ var Store = class {
62631
62638
  insNode.run(path11, node.kind, format, value, hash, size, isArray ? 1 : 0, meta);
62632
62639
  if (parent !== null) insEdge.run(parent, path11, label, "contain", pos);
62633
62640
  });
62634
- const insDangling = this.db.prepare("INSERT INTO dangling (from_path, raw, reason) VALUES (?, ?, ?)");
62641
+ const insDangling = this.db.prepare(INSERT_DANGLING);
62635
62642
  for (const r2 of resolveDocument(doc)) {
62636
62643
  if (r2.target.kind === "node") insEdge.run(r2.holder, r2.target.path, r2.label, r2.edge, r2.pos);
62637
- else if (r2.target.kind === "unresolved") insDangling.run(r2.from, r2.raw, r2.target.reason);
62644
+ else insDanglingRef(insDangling, r2);
62638
62645
  }
62639
62646
  if (files) {
62640
62647
  const insFile = this.db.prepare("INSERT OR REPLACE INTO file (path, hash, size, mtime_ms) VALUES (?, ?, ?, ?)");
@@ -62675,7 +62682,7 @@ var Store = class {
62675
62682
  const insEdge = this.db.prepare(
62676
62683
  `INSERT INTO edge (from_path, to_path, label, kind, pos) VALUES (?, ?, ?, ?, ?)`
62677
62684
  );
62678
- const insDangling = this.db.prepare("INSERT INTO dangling (from_path, raw, reason) VALUES (?, ?, ?)");
62685
+ const insDangling = this.db.prepare(INSERT_DANGLING);
62679
62686
  const insFile = this.db.prepare("INSERT OR REPLACE INTO file (path, hash, size, mtime_ms) VALUES (?, ?, ?, ?)");
62680
62687
  this.db.exec("BEGIN");
62681
62688
  try {
@@ -62684,7 +62691,7 @@ var Store = class {
62684
62691
  };
62685
62692
  delUnder("path", "node");
62686
62693
  delUnder("from_path", "edge");
62687
- delUnder("from_path", "dangling");
62694
+ delUnder("holder", "dangling");
62688
62695
  if (relPrefix) this.db.prepare("DELETE FROM file WHERE substr(path,1,?) = ?").run(relPrefix.length, relPrefix);
62689
62696
  walkNodes(doc.root, ":", (p2, node, parent, label, pos) => {
62690
62697
  if (!underP(p2)) return;
@@ -62701,7 +62708,7 @@ var Store = class {
62701
62708
  for (const r2 of edges) {
62702
62709
  if (!underP(r2.holder)) continue;
62703
62710
  if (r2.target.kind === "node") insEdge.run(r2.holder, r2.target.path, r2.label, r2.edge, r2.pos);
62704
- else if (r2.target.kind === "unresolved") insDangling.run(r2.from, r2.raw, r2.target.reason);
62711
+ else insDanglingRef(insDangling, r2);
62705
62712
  }
62706
62713
  for (const f2 of files) insFile.run(f2.path, f2.hash, f2.size, f2.mtimeMs);
62707
62714
  this.db.exec("COMMIT");
@@ -62835,14 +62842,27 @@ var Store = class {
62835
62842
  throw e3;
62836
62843
  }
62837
62844
  }
62838
- /** The pointers that failed to resolve at index time (ENGINE.md: reported, never dropped). */
62845
+ /** The pointers that failed to resolve at index time (ENGINE.md: reported, never dropped).
62846
+ * External-authority links are not errors, so they are excluded here. */
62839
62847
  dangling() {
62840
- return this.db.prepare("SELECT * FROM dangling").all().map((r2) => ({
62848
+ return this.db.prepare("SELECT * FROM dangling WHERE external = 0").all().map((r2) => ({
62841
62849
  from: r2.from_path,
62842
62850
  raw: r2.raw,
62843
62851
  reason: r2.reason
62844
62852
  }));
62845
62853
  }
62854
+ /** A node's pointer ENTRIES with no local target — unresolved (dangling) plus external-
62855
+ * authority links — in source order. These are the entries {@link entries} cannot list (no
62856
+ * edge exists), returned so a projection can still show the authored pointer in place. */
62857
+ unrealizedRefs(holder) {
62858
+ return this.db.prepare("SELECT label, pos, raw, edge, external FROM dangling WHERE holder = ? ORDER BY pos").all(holder).map((r2) => ({
62859
+ label: r2.label ?? null,
62860
+ pos: r2.pos ?? null,
62861
+ raw: r2.raw,
62862
+ edge: r2.edge,
62863
+ external: r2.external === 1
62864
+ }));
62865
+ }
62846
62866
  /** A node's attributes (null if no such path). */
62847
62867
  node(path11) {
62848
62868
  const row = this.db.prepare("SELECT * FROM node WHERE path = ?").get(path11);
@@ -62905,6 +62925,13 @@ var Store = class {
62905
62925
  return { out, in: [...inc, ...derivedIn] };
62906
62926
  }
62907
62927
  };
62928
+ var INSERT_DANGLING = "INSERT INTO dangling (from_path, raw, reason, holder, label, pos, edge, external) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
62929
+ function insDanglingRef(stmt, r2) {
62930
+ if (r2.target.kind === "node") return;
62931
+ const ext = r2.target.kind === "external";
62932
+ const reason = r2.target.kind === "external" ? r2.target.authority : r2.target.reason;
62933
+ stmt.run(r2.from, r2.raw, reason, r2.anchor ? "" : r2.holder, r2.label, r2.pos, r2.edge, ext ? 1 : 0);
62934
+ }
62908
62935
  function walkNodes(node, path11, visit2, parent = null, label = null, pos = null) {
62909
62936
  visit2(path11, node, parent, label, pos);
62910
62937
  if (!node.entries) return;
@@ -62915,6 +62942,7 @@ function walkNodes(node, path11, visit2, parent = null, label = null, pos = null
62915
62942
  });
62916
62943
  }
62917
62944
  function formatFromMeta(node) {
62945
+ if (node.meta?.derivedFormat) return node.meta.derivedFormat;
62918
62946
  const s2 = node.meta?.schema;
62919
62947
  if (!s2) return null;
62920
62948
  if (isPointer(s2)) {
@@ -64916,7 +64944,7 @@ function applyMeta(node, meta) {
64916
64944
  if (!m) continue;
64917
64945
  if (m.uniqueItems) e3.value = { ...e3.value, meta: { ...e3.value.meta, set: true } };
64918
64946
  if (e3.value.kind === "blob") continue;
64919
- if (m.format && !e3.value.meta?.schema) e3.value = { ...e3.value, meta: { ...e3.value.meta, schema: inlineFormat(m.format) } };
64947
+ if (m.format && !e3.value.meta?.schema) e3.value = { ...e3.value, meta: { ...e3.value.meta, derivedFormat: m.format } };
64920
64948
  }
64921
64949
  return node;
64922
64950
  }
@@ -64935,7 +64963,7 @@ function blob(abs, format, ctx) {
64935
64963
  }
64936
64964
  function textScalar(abs, format, ctx) {
64937
64965
  const text = readTracked(ctx, abs).toString("utf8");
64938
- return { kind: "scalar", value: text, raw: text, meta: { schema: inlineFormat(format) } };
64966
+ return { kind: "scalar", value: text, raw: text, meta: { derivedFormat: format } };
64939
64967
  }
64940
64968
  function parsedScalar(abs, ext, ctx) {
64941
64969
  const lang = ext === ".json" || ext === ".json5" || ext === ".json5p" ? "json5p" : ext === ".yaml" || ext === ".yml" ? "yaml" : "yamlover";
@@ -64983,7 +65011,7 @@ function applySchemas(root, defsRoot, builtinDefs) {
64983
65011
  return v && !isPointer(v) && v.kind === "scalar" && v.value != null ? String(v.value) : null;
64984
65012
  };
64985
65013
  const hasFormat = (inst) => {
64986
- if (inst.kind === "blob") return true;
65014
+ if (inst.kind === "blob" || inst.meta?.derivedFormat) return true;
64987
65015
  const s2 = inst.meta?.schema;
64988
65016
  return !!s2 && !isPointer(s2) && s2.kind === "mapping" && !!field(s2, "format");
64989
65017
  };
@@ -65019,7 +65047,7 @@ function applySchemas(root, defsRoot, builtinDefs) {
65019
65047
  const stype = str(s2, "type");
65020
65048
  const named = name && (stype === "object" || stype === "variant" || stype === "mixed" || !!field(s2, "allOf"));
65021
65049
  const fmt = str(s2, "format") ?? (named ? `x-yamlover-${name}` : null);
65022
- if (fmt && !hasFormat(inst)) inst.meta = { ...inst.meta, schema: inlineFormat(fmt) };
65050
+ if (fmt && !hasFormat(inst)) inst.meta = { ...inst.meta, derivedFormat: fmt };
65023
65051
  if (stype === "object" || stype === "variant" || stype === "mixed" || stype === "array" || field(s2, "items")) {
65024
65052
  const props = field(s2, "properties");
65025
65053
  const addl = field(s2, "additionalProperties");
@@ -65044,9 +65072,6 @@ function applySchemas(root, defsRoot, builtinDefs) {
65044
65072
  };
65045
65073
  walk(root);
65046
65074
  }
65047
- function inlineFormat(format) {
65048
- return { kind: "mapping", entries: [{ key: "format", edge: "contain", value: { kind: "scalar", value: format, raw: format } }], array: false };
65049
- }
65050
65075
  function applyBody(dir, node, ctx) {
65051
65076
  const file = path2.join(dir, YAMLOVER_DIR, "body.yamlover");
65052
65077
  if (!fs2.existsSync(file)) return node;
@@ -65189,11 +65214,21 @@ var DOC_FORMATS = {
65189
65214
  import * as path3 from "node:path";
65190
65215
 
65191
65216
  // ../parser/ts/src/serialize-common.ts
65217
+ var LossyError = class extends Error {
65218
+ };
65192
65219
  function anchorBody(a2) {
65193
65220
  return renderPointer(a2.path) + (a2.ordinal ? "[]" : "");
65194
65221
  }
65195
65222
 
65196
65223
  // ../parser/ts/src/serialize-yamlover.ts
65224
+ function schemaText(v) {
65225
+ const text = isPointer(v) ? `*${renderPointer(v)}` : schemaNodeText(v);
65226
+ if (/[>\n]/.test(text)) throw new LossyError(`a !!<\u2026> schema tag cannot contain ">" or a newline: ${text}`);
65227
+ return text;
65228
+ }
65229
+ function schemaTagToken(v) {
65230
+ return `!!<${schemaText(v)}>`;
65231
+ }
65197
65232
  function pointerToken(raw) {
65198
65233
  if (raw !== raw.trim()) return `*'${raw.replace(/'/g, "''")}'`;
65199
65234
  return "*" + raw;
@@ -65222,6 +65257,73 @@ function hasUnquotedSpace(s2) {
65222
65257
  }
65223
65258
  return false;
65224
65259
  }
65260
+ function dq(s2) {
65261
+ return JSON.stringify(s2);
65262
+ }
65263
+ function keyText(key) {
65264
+ const needsQuote = key === "" || key !== key.trim() || /[\u0000-\u001f\u007f]/.test(key) || key.includes(": ") || // splitKV would split at the inner colon
65265
+ key === "-" || key.startsWith("- ") || /^['"]/.test(key) || key.includes("\\");
65266
+ if (needsQuote) return dq(key);
65267
+ return key.replace(/[/[\]*&#~?!()<>=|]/g, (c3) => "\\" + c3);
65268
+ }
65269
+ function schemaNodeText(n2) {
65270
+ if (n2.kind === "mapping") {
65271
+ const ents = n2.entries;
65272
+ const keyed = ents.filter((e3) => e3.key !== null);
65273
+ if (keyed.length === ents.length && ents.length === 1 && ents[0].edge !== "back") {
65274
+ const e3 = ents[0];
65275
+ const v = isPointer(e3.value) ? flowPtr(e3.value) : flowText(e3.value);
65276
+ return `${keyText(e3.key)}: ${v}`;
65277
+ }
65278
+ if (keyed.length > 0) {
65279
+ throw new LossyError("an inline !!<\u2026> schema holds at most one top-level key");
65280
+ }
65281
+ }
65282
+ return flowText(n2);
65283
+ }
65284
+ function flowText(n2) {
65285
+ if (n2.kind === "blob") throw new LossyError("a blob has no flow form");
65286
+ if (n2.meta?.schema !== void 0 || n2.meta?.set === true) {
65287
+ throw new LossyError("tags inside an inline !!<\u2026> schema are not serializable");
65288
+ }
65289
+ const ents = n2.entries ?? [];
65290
+ if (n2.kind === "scalar") {
65291
+ if (ents.length > 0) throw new LossyError("a value-plus-fields node has no flow form");
65292
+ return flowTok(n2);
65293
+ }
65294
+ if (ents.length === 0) return n2.array ? "[]" : "{}";
65295
+ const keyed = ents.filter((e3) => e3.key !== null);
65296
+ if (keyed.length > 0 && keyed.length < ents.length) throw new LossyError("a mixed container has no flow form");
65297
+ if (ents.some((e3) => e3.edge === "back")) throw new LossyError("back-edges inside an inline !!<\u2026> schema are not serializable");
65298
+ const item = (e3) => {
65299
+ const v = isPointer(e3.value) ? flowPtr(e3.value) : flowText(e3.value);
65300
+ return e3.key === null ? v : `${flowKey(e3.key)}: ${v}`;
65301
+ };
65302
+ return keyed.length === 0 ? `[${ents.map(item).join(", ")}]` : `{${ents.map(item).join(", ")}}`;
65303
+ }
65304
+ function nonFinite(v) {
65305
+ return Number.isNaN(v) ? ".nan" : v === Infinity ? ".inf" : "-.inf";
65306
+ }
65307
+ function flowTok(s2) {
65308
+ const v = s2.value;
65309
+ if (v === null) return "null";
65310
+ if (typeof v === "boolean") return v ? "true" : "false";
65311
+ if (typeof v === "number") {
65312
+ if (!Number.isFinite(v)) return nonFinite(v);
65313
+ return String(v);
65314
+ }
65315
+ if (v !== "" && /^[^,:[\]{}'"#\s]+$/.test(v) && !"*&~!|>".includes(v[0]) && plainScalar(v).value === v) return v;
65316
+ if (/[\n\r\u0000-\u0008\u000b-\u001f\u007f]/.test(v)) throw new LossyError("a control character has no flow form inside a !!<\u2026> tag");
65317
+ return `'${v.replace(/'/g, "''")}'`;
65318
+ }
65319
+ function flowPtr(p2) {
65320
+ const compact2 = renderPointer(p2, { spaced: false });
65321
+ if (/['"\s]/.test(compact2)) throw new LossyError(`pointer "*${compact2}" does not fit a flow context`);
65322
+ return `*${compact2}`;
65323
+ }
65324
+ function flowKey(k) {
65325
+ return /^[\w.$/-]+$/.test(k) ? k : `'${k.replace(/'/g, "''")}'`;
65326
+ }
65225
65327
 
65226
65328
  // ../engine/ts/src/rewrite.ts
65227
65329
  function under(p2, x2) {
@@ -65556,7 +65658,9 @@ var DEFAULT_SETTINGS = {
65556
65658
  exports: [],
65557
65659
  annotations: ":annotations",
65558
65660
  tags: ":tags",
65559
- sidecars: "per-directory"
65661
+ sidecars: "per-directory",
65662
+ width: void 0,
65663
+ theme: void 0
65560
65664
  };
65561
65665
  var DEFAULT_SETTINGS_SOURCE = `# .yamlover/settings.yamlover \u2014 project settings for this root (created with defaults).
65562
65666
  # Settings are DEFAULTS, never constraints: they only say where the server CREATES things when you
@@ -65569,6 +65673,7 @@ var DEFAULT_SETTINGS_SOURCE = `# .yamlover/settings.yamlover \u2014 project sett
65569
65673
  annotations: *:: annotations # create new annotations in :annotations
65570
65674
  tags: *:: tags # create new tags in :tags
65571
65675
  sidecars: per-directory # where derived thumbnail/crop blobs go (enum, not a path)
65676
+ # width: 100 # reading width (ch) for prose views; browser settings override it
65572
65677
  `;
65573
65678
  function ensureSettingsFile(absRoot) {
65574
65679
  const file = path6.join(absRoot, ".yamlover", "settings.yamlover");
@@ -65592,9 +65697,20 @@ function loadSettings(absRoot) {
65592
65697
  exports: exportsSetting(valueAt(root, "exports")),
65593
65698
  annotations: locationSetting(valueAt(root, "annotations"), DEFAULT_SETTINGS.annotations),
65594
65699
  tags: locationSetting(valueAt(root, "tags"), DEFAULT_SETTINGS.tags),
65595
- sidecars: sidecarLocation(valueAt(root, "sidecars"), DEFAULT_SETTINGS.sidecars)
65700
+ sidecars: sidecarLocation(valueAt(root, "sidecars"), DEFAULT_SETTINGS.sidecars),
65701
+ width: intSetting(valueAt(root, "width"), 20, 400),
65702
+ theme: enumSetting(valueAt(root, "theme"), ["dark", "light"])
65596
65703
  };
65597
65704
  }
65705
+ function intSetting(v, min, max) {
65706
+ if (v === void 0 || isPointer(v) || v.kind !== "scalar") return void 0;
65707
+ const n2 = typeof v.value === "number" ? v.value : NaN;
65708
+ return Number.isInteger(n2) && n2 >= min && n2 <= max ? n2 : void 0;
65709
+ }
65710
+ function enumSetting(v, values) {
65711
+ if (v === void 0 || isPointer(v) || v.kind !== "scalar") return void 0;
65712
+ return values.includes(v.value) ? v.value : void 0;
65713
+ }
65598
65714
  function uriSetting(v) {
65599
65715
  if (v === void 0) return void 0;
65600
65716
  if (isPointer(v)) return v.base.scope === "link" ? v.base.authority : void 0;
@@ -66434,7 +66550,11 @@ function findGitRoot(start) {
66434
66550
 
66435
66551
  // src/server/node-kind.ts
66436
66552
  function ownedEntries(s2, p2) {
66437
- return s2.entries(p2).filter((e3) => e3.kind !== "back");
66553
+ const own = s2.entries(p2).filter((e3) => e3.kind !== "back");
66554
+ const unrealized = s2.unrealizedRefs(p2).filter((u2) => u2.edge !== "back");
66555
+ if (unrealized.length === 0) return own;
66556
+ for (const u2 of unrealized) own.push({ to: "", label: u2.label, pos: u2.pos, kind: "ref", raw: u2.raw });
66557
+ return own.sort((a2, b) => (a2.pos ?? 0) - (b.pos ?? 0));
66438
66558
  }
66439
66559
  function displayKind(s2, p2, row) {
66440
66560
  const ents = ownedEntries(s2, p2);
@@ -66529,6 +66649,7 @@ function createHandlers(dataRoot, opts = {}) {
66529
66649
  const rootName = path10.basename(path10.resolve(dataRoot)) || "/";
66530
66650
  const dbPath = path10.join(dataRoot, ".yamlover", "index.db");
66531
66651
  const settingsFile = path10.join(dataRoot, ".yamlover", "settings.yamlover");
66652
+ const settingsRel = path10.relative(dataRoot, settingsFile).split(path10.sep).join("/");
66532
66653
  if (opts.ensureSettings) {
66533
66654
  try {
66534
66655
  ensureSettingsFile(dataRoot);
@@ -66555,6 +66676,7 @@ function createHandlers(dataRoot, opts = {}) {
66555
66676
  };
66556
66677
  const broadcast = (diff2) => {
66557
66678
  if (diff2.added.length + diff2.changed.length + diff2.removed.length + diff2.moved.length === 0) return;
66679
+ if (diff2.changed.includes(settingsRel) || diff2.added.includes(settingsRel)) settings = loadSettings(dataRoot);
66558
66680
  const toClient = (rel) => segsToStr(rel.split("/"));
66559
66681
  sseWrite({
66560
66682
  type: "diff",
@@ -66867,6 +66989,17 @@ function createHandlers(dataRoot, opts = {}) {
66867
66989
  ).then((body) => sendJson(res, 200, body)).catch((e3) => sendJson(res, 400, { error: String(e3.message || e3) }));
66868
66990
  return;
66869
66991
  }
66992
+ if (req.method === "POST" && url.pathname === "/api/preview") {
66993
+ readBody(req).then((data) => sendJson(res, 200, previewPayload(String(data.source ?? "")))).catch((e3) => sendJson(res, 400, { error: String(e3.message || e3) }));
66994
+ return;
66995
+ }
66996
+ if (req.method === "POST" && url.pathname === "/api/edit-text") {
66997
+ readBody(req).then((data) => {
66998
+ const d = data;
66999
+ sendJson(res, 200, { source: applyTextEdits(String(d.source ?? ""), Array.isArray(d.edits) ? d.edits : []) });
67000
+ }).catch((e3) => sendJson(res, 400, { error: String(e3.message || e3) }));
67001
+ return;
67002
+ }
66870
67003
  if (req.method === "POST" && url.pathname === "/api/mv") {
66871
67004
  readBody(req).then(
66872
67005
  (data) => enqueue(async () => {
@@ -66912,20 +67045,6 @@ function createHandlers(dataRoot, opts = {}) {
66912
67045
  sendJson(res, 200, { source, settings, path: ":.yamlover:settings.yamlover" });
66913
67046
  return;
66914
67047
  }
66915
- if (req.method === "POST" && url.pathname === "/api/config") {
66916
- readBody(req).then(
66917
- (data) => enqueue(async () => {
66918
- const source = String(data?.source ?? "");
66919
- parseYamlover(source, settingsFile);
66920
- fs10.mkdirSync(path10.dirname(settingsFile), { recursive: true });
66921
- fs10.writeFileSync(settingsFile, source);
66922
- settings = loadSettings(dataRoot);
66923
- broadcast(await doReindexFile(settingsFile));
66924
- return { ok: true, settings };
66925
- })
66926
- ).then((body) => sendJson(res, 200, body)).catch((e3) => sendJson(res, 400, { error: String(e3.message || e3) }));
66927
- return;
66928
- }
66929
67048
  const segs = strToSegs(url.searchParams.get("path") || ":");
66930
67049
  const p2 = storePath(segs);
66931
67050
  const depth = parseDepth(url.searchParams.get("depth"));
@@ -67074,12 +67193,12 @@ var hasSubchapterChild = (s2, p2) => s2.children(p2).some((c3) => {
67074
67193
  });
67075
67194
  function downstreamEntries(s2, p2) {
67076
67195
  const isSet = !!s2.node(p2)?.meta?.set;
67077
- let own = s2.entries(p2).filter((e3) => e3.kind !== "back" && !(e3.kind === "contain" && isHidden(s2, e3.to)));
67078
- const seen = new Set(own.map((e3) => relKey(e3.label, e3.to)));
67196
+ let own = ownedEntries(s2, p2).filter((e3) => !(e3.kind === "contain" && isHidden(s2, e3.to)));
67197
+ const seen = new Set(own.map((e3) => relKey(e3.label, e3.to || e3.raw || "")));
67079
67198
  if (isSet) {
67080
67199
  const kept = /* @__PURE__ */ new Set();
67081
67200
  own = own.filter((e3) => {
67082
- const k = relKey(e3.label, e3.to);
67201
+ const k = relKey(e3.label, e3.to || e3.raw || "");
67083
67202
  if (kept.has(k)) return false;
67084
67203
  kept.add(k);
67085
67204
  return true;
@@ -67108,6 +67227,7 @@ function projectValue(dataRoot, s2, segs, depth, top) {
67108
67227
  const currentDoc = documentRootSegs(s2, segs);
67109
67228
  const project = (c3) => {
67110
67229
  if (c3.kind === "contain") return projectValue(dataRoot, s2, [...segs, c3.label ?? c3.pos ?? 0], depth - 1, false);
67230
+ if (!c3.to) return refMarker(c3.raw ?? "", null);
67111
67231
  const targetSegs = storePathToSegs(c3.to);
67112
67232
  return depth === Infinity ? refMarker(refPointerText(s2, targetSegs, currentDoc), segsToStr(targetSegs)) : linkMarker(dataRoot, s2, targetSegs);
67113
67233
  };
@@ -67145,8 +67265,25 @@ function irNodeAt(doc, segs) {
67145
67265
  }
67146
67266
  return node;
67147
67267
  }
67268
+ function scalarRawToken(node) {
67269
+ if (isPointer(node) || node.kind !== "scalar") return void 0;
67270
+ const raw = node.raw;
67271
+ if (raw == null || raw.includes("\n")) return void 0;
67272
+ const v = node.value;
67273
+ if (typeof v === "string" && v.includes("\n")) return void 0;
67274
+ const plain = v === null ? "null" : String(v);
67275
+ return raw === plain ? void 0 : raw;
67276
+ }
67148
67277
  function tagOf(n2) {
67149
- return n2.meta?.set ? "!!set" : void 0;
67278
+ const parts = [];
67279
+ if (n2.meta?.schema !== void 0) {
67280
+ try {
67281
+ parts.push(schemaTagToken(n2.meta.schema));
67282
+ } catch {
67283
+ }
67284
+ }
67285
+ if (n2.meta?.set) parts.push("!!set");
67286
+ return parts.length ? parts.join(" ") : void 0;
67150
67287
  }
67151
67288
  function nodeDeco(bucket, node) {
67152
67289
  const anchors = (node.meta?.anchors ?? []).map(anchorBody);
@@ -67163,7 +67300,9 @@ function collectComments(doc, segs, depth) {
67163
67300
  {
67164
67301
  const self2 = {};
67165
67302
  nodeDeco(self2, root);
67166
- if (self2.anchors || self2.tag || self2.valueTrailing) out[""] = self2;
67303
+ const raw = scalarRawToken(root);
67304
+ if (raw) self2.raw = raw;
67305
+ if (self2.anchors || self2.tag || self2.valueTrailing || self2.raw) out[""] = self2;
67167
67306
  }
67168
67307
  const head = (root.meta?.head ?? []).map((c3) => c3.text);
67169
67308
  if (head.length > 0) out.$head = head;
@@ -67186,7 +67325,11 @@ function collectComments(doc, segs, depth) {
67186
67325
  const leadComment = e3.meta?.comments?.find((c3) => c3.placement === "leading");
67187
67326
  if (e3.meta?.blankBefore || leadComment?.blankBefore) bucket.blankBefore = true;
67188
67327
  if (isPointer(e3.value)) bucket.pointer = renderPointer(e3.value);
67189
- else nodeDeco(bucket, e3.value);
67328
+ else {
67329
+ nodeDeco(bucket, e3.value);
67330
+ const raw = scalarRawToken(e3.value);
67331
+ if (raw) bucket.raw = raw;
67332
+ }
67190
67333
  if (Object.keys(bucket).length > 0) out[rel + cont] = bucket;
67191
67334
  if (e3.edge === "contain" && !isPointer(e3.value)) walk(e3.value, rel + cont, d - 1, false);
67192
67335
  }
@@ -67203,7 +67346,7 @@ function projectSchema(dataRoot, s2, segs, depth, top) {
67203
67346
  const schema = { type: typeName(s2, p2, row) };
67204
67347
  if (row.format) schema.format = row.format;
67205
67348
  const kids = downstreamEntries(s2, p2);
67206
- const sub = (c3) => c3.kind === "contain" ? projectSchema(dataRoot, s2, [...segs, c3.label ?? c3.pos ?? 0], depth - 1, false) : linkMarker(dataRoot, s2, storePathToSegs(c3.to));
67349
+ const sub = (c3) => c3.kind === "contain" ? projectSchema(dataRoot, s2, [...segs, c3.label ?? c3.pos ?? 0], depth - 1, false) : c3.to ? linkMarker(dataRoot, s2, storePathToSegs(c3.to)) : refMarker(c3.raw ?? "", null);
67207
67350
  if (k === "object" || k === "mix" || k === "omni") {
67208
67351
  const props = {};
67209
67352
  for (const c3 of kids) props[c3.label ?? `[${c3.pos}]`] = sub(c3);
@@ -67235,7 +67378,9 @@ function linkMarker(dataRoot, s2, segs) {
67235
67378
  else if (k === "omni" || k === "mix") {
67236
67379
  info.count = ownedEntries(s2, p2).length;
67237
67380
  if (k === "omni") info.value = wireScalar(row.value);
67238
- } else info.count = s2.children(p2).filter((c3) => !isHidden(s2, c3.to)).length;
67381
+ } else {
67382
+ info.count = ownedEntries(s2, p2).filter((e3) => !(e3.kind === "contain" && isHidden(s2, e3.to))).length;
67383
+ }
67239
67384
  if (row.format === TAG_FORMAT) {
67240
67385
  const c3 = s2.node(p2 + ":color")?.value;
67241
67386
  if (typeof c3 === "string") info.color = c3;
@@ -67663,16 +67808,24 @@ function nearestDirSegs(dataRoot, segs) {
67663
67808
  }
67664
67809
  return null;
67665
67810
  }
67666
- function chapterSource(dataRoot, s2, segs) {
67811
+ var YAMLOVER_ONLY = /\.yamlover$/i;
67812
+ var BLOCK_YAML = /\.(ya?ml|yamlover)$/i;
67813
+ var JSON_FILE = /\.(json|json5|json5p)$/i;
67814
+ function resolveBacking(dataRoot, s2, segs) {
67667
67815
  const docSegs = documentRootSegs(s2, segs);
67668
67816
  const docFs = path10.resolve(dataRoot, ...docSegs.map(String));
67669
67817
  const dirBacked = fs10.existsSync(docFs) && fs10.statSync(docFs).isDirectory();
67670
67818
  const bodyFile = dirBacked ? path10.join(docFs, ".yamlover", "body.yamlover") : docFs;
67671
- if (!bodyFile.endsWith(".yamlover") || !fs10.existsSync(bodyFile)) {
67672
- throw new Error("unsupported chapter source (need a .yamlover body)");
67673
- }
67674
67819
  return { docSegs, bodyFile, dirBacked };
67675
67820
  }
67821
+ function chapterSource(dataRoot, s2, segs, allow = YAMLOVER_ONLY) {
67822
+ const r2 = resolveBacking(dataRoot, s2, segs);
67823
+ const okExt = r2.dirBacked ? r2.bodyFile.endsWith(".yamlover") : allow.test(r2.bodyFile);
67824
+ if (!okExt || !fs10.existsSync(r2.bodyFile)) {
67825
+ throw new Error("unsupported edit source (need a .yamlover / .yaml / .yml body)");
67826
+ }
67827
+ return r2;
67828
+ }
67676
67829
  function pasteIntoChapter(dataRoot, s2, segs, name, bytes) {
67677
67830
  const { docSegs, bodyFile, dirBacked } = chapterSource(dataRoot, s2, segs);
67678
67831
  const writeDirSegs = dirBacked ? docSegs : docSegs.slice(0, -1);
@@ -67902,8 +68055,11 @@ function textChunkLines(text, indent) {
67902
68055
  function chapterEntries(lines, r2) {
67903
68056
  const starts = [];
67904
68057
  if (r2.marker >= 0) {
67905
- const inline = lines[r2.marker].replace(/^\s*-\s*/, "");
67906
- if (inline.trim()) starts.push({ key: /^-/.test(inline) ? null : inline.match(/^([^:\s]+):/)?.[1] ?? null, start: r2.marker, inline: true });
68058
+ const raw = lines[r2.marker].replace(/^\s*/, "");
68059
+ if (raw === "-" || raw.startsWith("- ")) {
68060
+ const inline = raw.replace(/^-\s*/, "");
68061
+ if (inline.trim()) starts.push({ key: /^-/.test(inline) ? null : inline.match(/^([^:\s]+):/)?.[1] ?? null, start: r2.marker, inline: true });
68062
+ }
67907
68063
  }
67908
68064
  for (let i2 = r2.lo; i2 < r2.hi; i2++) {
67909
68065
  if (!isContentLine2(lines[i2])) continue;
@@ -67934,7 +68090,8 @@ function entryHead(lines, e3) {
67934
68090
  function isContainerEntry(lines, e3, childIndent) {
67935
68091
  const head = entryHead(lines, e3);
67936
68092
  if (/^[|>][+-]?\d*$/.test(head.trim())) return itemHasFields(lines, e3, childIndent);
67937
- if (/^[^\s"'*|>#-][^:]*:(\s|$)/.test(head)) return true;
68093
+ const bare = head.replace(/\s+#.*$/, "");
68094
+ if (/^[^\s"'*|>#-][^:]*:(\s|$)/.test(bare)) return true;
67938
68095
  for (let i2 = e3.start + 1; i2 < e3.end; i2++) {
67939
68096
  if (!isContentLine2(lines[i2])) continue;
67940
68097
  const ind = indentOf2(lines[i2]);
@@ -68156,6 +68313,51 @@ function editChapterSource(src, within, op, valueSrc, meta) {
68156
68313
  assignAt(lines, reachChapter(lines, within.slice(0, -1)), within[within.length - 1], op, valueSrc, meta);
68157
68314
  return lines.join("\n");
68158
68315
  }
68316
+ function jsonValueSpan(src, es, ee) {
68317
+ const last = src[ee - 1];
68318
+ if (last === '"' || last === "'") {
68319
+ for (let j2 = ee - 2; j2 >= es; j2--) {
68320
+ if (src[j2] !== last) continue;
68321
+ let bs = 0;
68322
+ for (let k = j2 - 1; k >= es && src[k] === "\\"; k--) bs++;
68323
+ if (bs % 2 === 0) return [j2, ee];
68324
+ }
68325
+ throw new Error("could not locate the string value in the source");
68326
+ }
68327
+ let j = ee;
68328
+ while (j > es && !/[\s,:{}[\]]/.test(src[j - 1])) j--;
68329
+ return [j, ee];
68330
+ }
68331
+ function yamloverScalarToJsonToken(valueSrc) {
68332
+ const root = parseYamlover(valueSrc, "<edit>").root;
68333
+ if (root.kind !== "scalar") throw new Error("only a scalar value is editable in a JSON file");
68334
+ const v = root.value;
68335
+ if (v === null) return "null";
68336
+ if (typeof v === "boolean") return v ? "true" : "false";
68337
+ if (typeof v === "number") return /^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(root.raw) ? root.raw : String(v);
68338
+ return JSON.stringify(v);
68339
+ }
68340
+ function editJsonScalar(src, within, token) {
68341
+ if (within.length === 0) throw new Error("cannot edit the JSON document root as a scalar value");
68342
+ let node = parseJson5p(src, "<edit>").root;
68343
+ for (let k = 0; k < within.length; k++) {
68344
+ const seg = within[k];
68345
+ const entries = node.entries ?? [];
68346
+ const entry = typeof seg === "number" ? entries.filter((e3) => e3.edge === "contain" && e3.key === null)[seg] : entries.find((e3) => e3.edge === "contain" && e3.key === seg);
68347
+ if (!entry) throw new Error(`no entry at ${typeof seg === "number" ? `[${seg}]` : seg}`);
68348
+ const value = entry.value;
68349
+ if (k === within.length - 1) {
68350
+ if (isPointer(value) || value.kind !== "scalar") throw new Error("only a scalar value is editable in a JSON file");
68351
+ const span = entry.meta?.span;
68352
+ if (!span) throw new Error("no source span for the target value");
68353
+ const [vs, ve] = jsonValueSpan(src, span.start, span.end);
68354
+ return src.slice(0, vs) + token + src.slice(ve);
68355
+ }
68356
+ if (isPointer(value) || value.kind !== "mapping") throw new Error(`cannot descend into a non-container at ${typeof seg === "number" ? `[${seg}]` : seg}`);
68357
+ node = value;
68358
+ }
68359
+ throw new Error("empty JSON edit path");
68360
+ }
68159
68361
  var isPointerValue = (src) => /^\*\S*$/.test(src.trim()) && !src.includes("\n");
68160
68362
  function isPlainDir(dataRoot, s2, segs) {
68161
68363
  if (!segs.every((g) => typeof g === "string")) return false;
@@ -68178,6 +68380,7 @@ function bornAsDocument(dataRoot, e3, tag) {
68178
68380
  }
68179
68381
  function applyEdits2(dataRoot, s2, edits) {
68180
68382
  const byFile = /* @__PURE__ */ new Map();
68383
+ const jsonByFile = /* @__PURE__ */ new Map();
68181
68384
  const appended = [];
68182
68385
  const created = [];
68183
68386
  for (const e3 of edits) {
@@ -68191,7 +68394,15 @@ function applyEdits2(dataRoot, s2, edits) {
68191
68394
  created.push(segsToStr([...editSegs, final]));
68192
68395
  continue;
68193
68396
  }
68194
- const { docSegs, bodyFile, dirBacked } = chapterSource(dataRoot, s2, editSegs);
68397
+ const backing = resolveBacking(dataRoot, s2, editSegs);
68398
+ if (!backing.dirBacked && JSON_FILE.test(backing.bodyFile)) {
68399
+ if (op !== "emplace") throw new Error("only scalar value edits (emplace) are supported for JSON files");
68400
+ const jlist = jsonByFile.get(backing.bodyFile) ?? [];
68401
+ jlist.push({ within: editSegs.slice(backing.docSegs.length), valueSrc: String(e3.yamlover ?? "") });
68402
+ jsonByFile.set(backing.bodyFile, jlist);
68403
+ continue;
68404
+ }
68405
+ const { docSegs, bodyFile, dirBacked } = chapterSource(dataRoot, s2, editSegs, BLOCK_YAML);
68195
68406
  if (op === "insert" && !e3.concrete?.includes("/")) {
68196
68407
  const last = editSegs[editSegs.length - 1];
68197
68408
  if (typeof last !== "number") appended.push(editSegs);
@@ -68232,8 +68443,55 @@ function applyEdits2(dataRoot, s2, edits) {
68232
68443
  fs10.writeFileSync(bodyFile, src);
68233
68444
  touched.push(bodyFile);
68234
68445
  }
68446
+ for (const [file, jedits] of jsonByFile) {
68447
+ let src = fs10.readFileSync(file, "utf8");
68448
+ for (const j of jedits) {
68449
+ src = editJsonScalar(src, j.within, yamloverScalarToJsonToken(j.valueSrc));
68450
+ }
68451
+ fs10.writeFileSync(file, src);
68452
+ touched.push(file);
68453
+ }
68235
68454
  return { touched, created, appended };
68236
68455
  }
68456
+ var PREVIEW_ROOT = path10.join(os.tmpdir(), ".yamlover-preview-nonexistent");
68457
+ function previewPayload(source) {
68458
+ const doc = parseYamlover(source, "<preview>");
68459
+ doc.root.meta = { ...doc.root.meta, documentRoot: true, ...doc.head?.length ? { head: doc.head } : {} };
68460
+ const s2 = new Store(":memory:");
68461
+ try {
68462
+ s2.indexDocument(doc);
68463
+ const row = s2.node(":");
68464
+ return {
68465
+ path: ":",
68466
+ type: tocType(s2, ":", row),
68467
+ format: row.format ?? null,
68468
+ ...facetsOf(s2, ":", row),
68469
+ concrete: "yamlover",
68470
+ // by construction — the document IS yamlover text
68471
+ documentPath: ":",
68472
+ title: titleOf(s2, ":"),
68473
+ description: descriptionOf(s2, ":"),
68474
+ value: projectValue(PREVIEW_ROOT, s2, [], Infinity, true),
68475
+ comments: collectComments(doc, [], Infinity),
68476
+ relations: {}
68477
+ // a standalone document has no project around it
68478
+ };
68479
+ } finally {
68480
+ s2.close();
68481
+ }
68482
+ }
68483
+ function applyTextEdits(src, edits) {
68484
+ let out = src;
68485
+ for (const e3 of edits) {
68486
+ if (e3.concrete || e3.name) throw new Error("`concrete`/`name` are only for file-backed edits");
68487
+ const valueSrc = String(e3.yamlover ?? "");
68488
+ const meta = e3.meta === void 0 ? void 0 : e3.meta === null ? null : String(e3.meta);
68489
+ if (valueSrc && !isPointerValue(valueSrc)) parseYamlover(valueSrc, "<edit>");
68490
+ out = editChapterSource(out, strToSegs(String(e3.path ?? "")), String(e3.op ?? ""), valueSrc, meta);
68491
+ }
68492
+ parseYamlover(out, "<edit-result>");
68493
+ return out;
68494
+ }
68237
68495
  function firstContentIndent2(lines) {
68238
68496
  for (const l2 of lines) if (isContentLine2(l2)) return indentOf2(l2);
68239
68497
  return 0;
@@ -68354,8 +68612,10 @@ function notFound(res, url) {
68354
68612
  sendJson(res, 404, { error: `no such node/endpoint: ${url.pathname}?${url.searchParams}` });
68355
68613
  }
68356
68614
  export {
68615
+ applyTextEdits,
68357
68616
  createHandlers,
68358
- mergeAgentDoc
68617
+ mergeAgentDoc,
68618
+ previewPayload
68359
68619
  };
68360
68620
  /*! Bundled license information:
68361
68621
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamlover",
3
- "version": "0.3.23",
3
+ "version": "0.3.24",
4
4
  "description": "Browse a yamlover tree in the web: npx yamlover <root> serves a React SPA over a directory.",
5
5
  "type": "module",
6
6
  "license": "MIT",