yamlover 0.3.0

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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/bin/yamlover.js +202 -0
  4. package/dist/server.js +4681 -0
  5. package/index.html +12 -0
  6. package/package.json +72 -0
  7. package/src/client/App.tsx +372 -0
  8. package/src/client/NodeView.tsx +422 -0
  9. package/src/client/TaskStrip.tsx +34 -0
  10. package/src/client/Tree.tsx +97 -0
  11. package/src/client/api.ts +186 -0
  12. package/src/client/icons.ts +91 -0
  13. package/src/client/links.tsx +108 -0
  14. package/src/client/live.ts +42 -0
  15. package/src/client/main.tsx +10 -0
  16. package/src/client/paste-html.ts +228 -0
  17. package/src/client/paste-links.ts +42 -0
  18. package/src/client/paths.ts +109 -0
  19. package/src/client/render.tsx +326 -0
  20. package/src/client/renderers/annotate.tsx +507 -0
  21. package/src/client/renderers/asciidoc.tsx +35 -0
  22. package/src/client/renderers/chapter.tsx +138 -0
  23. package/src/client/renderers/csv.tsx +233 -0
  24. package/src/client/renderers/decoded.tsx +72 -0
  25. package/src/client/renderers/djvu.tsx +97 -0
  26. package/src/client/renderers/doc.tsx +40 -0
  27. package/src/client/renderers/docx.tsx +49 -0
  28. package/src/client/renderers/epub.tsx +147 -0
  29. package/src/client/renderers/explorer.tsx +209 -0
  30. package/src/client/renderers/fb2.tsx +149 -0
  31. package/src/client/renderers/headings.ts +69 -0
  32. package/src/client/renderers/heic.tsx +23 -0
  33. package/src/client/renderers/imagemap.tsx +157 -0
  34. package/src/client/renderers/kml.ts +46 -0
  35. package/src/client/renderers/latex.tsx +36 -0
  36. package/src/client/renderers/map.tsx +205 -0
  37. package/src/client/renderers/marklower.tsx +119 -0
  38. package/src/client/renderers/markup.tsx +64 -0
  39. package/src/client/renderers/media.tsx +19 -0
  40. package/src/client/renderers/panzoom.ts +101 -0
  41. package/src/client/renderers/pdf.tsx +176 -0
  42. package/src/client/renderers/plaintext.tsx +120 -0
  43. package/src/client/renderers/plantuml.tsx +82 -0
  44. package/src/client/renderers/psd.tsx +25 -0
  45. package/src/client/renderers/registry.tsx +389 -0
  46. package/src/client/renderers/rtf.tsx +210 -0
  47. package/src/client/renderers/spreadsheet.tsx +105 -0
  48. package/src/client/renderers/tag.tsx +113 -0
  49. package/src/client/renderers/text.tsx +41 -0
  50. package/src/client/renderers/tiff.tsx +33 -0
  51. package/src/client/styles.css +1115 -0
  52. package/src/client/vendor/README.md +30 -0
  53. package/src/client/vendor/djvu.js +15535 -0
  54. package/src/client/vite-env.d.ts +31 -0
  55. package/src/server/api.ts +147 -0
  56. package/src/server/engine-api.ts +1442 -0
  57. package/src/server/gitignore.ts +81 -0
  58. package/src/server/node-kind.ts +48 -0
  59. package/src/server/tasks.ts +83 -0
  60. package/src/server/yamlover.ts +1133 -0
package/dist/server.js ADDED
@@ -0,0 +1,4681 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // node_modules/ignore/index.js
28
+ var require_ignore = __commonJS({
29
+ "node_modules/ignore/index.js"(exports, module) {
30
+ function makeArray(subject) {
31
+ return Array.isArray(subject) ? subject : [subject];
32
+ }
33
+ var EMPTY = "";
34
+ var SPACE = " ";
35
+ var ESCAPE = "\\";
36
+ var REGEX_TEST_BLANK_LINE = /^\s+$/;
37
+ var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
38
+ var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
39
+ var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
40
+ var REGEX_SPLITALL_CRLF = /\r?\n/g;
41
+ var REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/;
42
+ var SLASH = "/";
43
+ var TMP_KEY_IGNORE = "node-ignore";
44
+ if (typeof Symbol !== "undefined") {
45
+ TMP_KEY_IGNORE = Symbol.for("node-ignore");
46
+ }
47
+ var KEY_IGNORE = TMP_KEY_IGNORE;
48
+ var define = (object, key, value) => Object.defineProperty(object, key, { value });
49
+ var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
50
+ var RETURN_FALSE = () => false;
51
+ var sanitizeRange = (range) => range.replace(
52
+ REGEX_REGEXP_RANGE,
53
+ (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
54
+ );
55
+ var cleanRangeBackSlash = (slashes) => {
56
+ const { length } = slashes;
57
+ return slashes.slice(0, length - length % 2);
58
+ };
59
+ var REPLACERS = [
60
+ [
61
+ // remove BOM
62
+ // TODO:
63
+ // Other similar zero-width characters?
64
+ /^\uFEFF/,
65
+ () => EMPTY
66
+ ],
67
+ // > Trailing spaces are ignored unless they are quoted with backslash ("\")
68
+ [
69
+ // (a\ ) -> (a )
70
+ // (a ) -> (a)
71
+ // (a ) -> (a)
72
+ // (a \ ) -> (a )
73
+ /((?:\\\\)*?)(\\?\s+)$/,
74
+ (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
75
+ ],
76
+ // replace (\ ) with ' '
77
+ // (\ ) -> ' '
78
+ // (\\ ) -> '\\ '
79
+ // (\\\ ) -> '\\ '
80
+ [
81
+ /(\\+?)\s/g,
82
+ (_, m1) => {
83
+ const { length } = m1;
84
+ return m1.slice(0, length - length % 2) + SPACE;
85
+ }
86
+ ],
87
+ // Escape metacharacters
88
+ // which is written down by users but means special for regular expressions.
89
+ // > There are 12 characters with special meanings:
90
+ // > - the backslash \,
91
+ // > - the caret ^,
92
+ // > - the dollar sign $,
93
+ // > - the period or dot .,
94
+ // > - the vertical bar or pipe symbol |,
95
+ // > - the question mark ?,
96
+ // > - the asterisk or star *,
97
+ // > - the plus sign +,
98
+ // > - the opening parenthesis (,
99
+ // > - the closing parenthesis ),
100
+ // > - and the opening square bracket [,
101
+ // > - the opening curly brace {,
102
+ // > These special characters are often called "metacharacters".
103
+ [
104
+ /[\\$.|*+(){^]/g,
105
+ (match) => `\\${match}`
106
+ ],
107
+ [
108
+ // > a question mark (?) matches a single character
109
+ /(?!\\)\?/g,
110
+ () => "[^/]"
111
+ ],
112
+ // leading slash
113
+ [
114
+ // > A leading slash matches the beginning of the pathname.
115
+ // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
116
+ // A leading slash matches the beginning of the pathname
117
+ /^\//,
118
+ () => "^"
119
+ ],
120
+ // replace special metacharacter slash after the leading slash
121
+ [
122
+ /\//g,
123
+ () => "\\/"
124
+ ],
125
+ [
126
+ // > A leading "**" followed by a slash means match in all directories.
127
+ // > For example, "**/foo" matches file or directory "foo" anywhere,
128
+ // > the same as pattern "foo".
129
+ // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
130
+ // > under directory "foo".
131
+ // Notice that the '*'s have been replaced as '\\*'
132
+ /^\^*\\\*\\\*\\\//,
133
+ // '**/foo' <-> 'foo'
134
+ () => "^(?:.*\\/)?"
135
+ ],
136
+ // starting
137
+ [
138
+ // there will be no leading '/'
139
+ // (which has been replaced by section "leading slash")
140
+ // If starts with '**', adding a '^' to the regular expression also works
141
+ /^(?=[^^])/,
142
+ function startingReplacer() {
143
+ return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
144
+ }
145
+ ],
146
+ // two globstars
147
+ [
148
+ // Use lookahead assertions so that we could match more than one `'/**'`
149
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
150
+ // Zero, one or several directories
151
+ // should not use '*', or it will be replaced by the next replacer
152
+ // Check if it is not the last `'/**'`
153
+ (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
154
+ ],
155
+ // normal intermediate wildcards
156
+ [
157
+ // Never replace escaped '*'
158
+ // ignore rule '\*' will match the path '*'
159
+ // 'abc.*/' -> go
160
+ // 'abc.*' -> skip this rule,
161
+ // coz trailing single wildcard will be handed by [trailing wildcard]
162
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
163
+ // '*.js' matches '.js'
164
+ // '*.js' doesn't match 'abc'
165
+ (_, p1, p2) => {
166
+ const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
167
+ return p1 + unescaped;
168
+ }
169
+ ],
170
+ [
171
+ // unescape, revert step 3 except for back slash
172
+ // For example, if a user escape a '\\*',
173
+ // after step 3, the result will be '\\\\\\*'
174
+ /\\\\\\(?=[$.|*+(){^])/g,
175
+ () => ESCAPE
176
+ ],
177
+ [
178
+ // '\\\\' -> '\\'
179
+ /\\\\/g,
180
+ () => ESCAPE
181
+ ],
182
+ [
183
+ // > The range notation, e.g. [a-zA-Z],
184
+ // > can be used to match one of the characters in a range.
185
+ // `\` is escaped by step 3
186
+ /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
187
+ (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
188
+ ],
189
+ // ending
190
+ [
191
+ // 'js' will not match 'js.'
192
+ // 'ab' will not match 'abc'
193
+ /(?:[^*])$/,
194
+ // WTF!
195
+ // https://git-scm.com/docs/gitignore
196
+ // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
197
+ // which re-fixes #24, #38
198
+ // > If there is a separator at the end of the pattern then the pattern
199
+ // > will only match directories, otherwise the pattern can match both
200
+ // > files and directories.
201
+ // 'js*' will not match 'a.js'
202
+ // 'js/' will not match 'a.js'
203
+ // 'js' will match 'a.js' and 'a.js/'
204
+ (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
205
+ ],
206
+ // trailing wildcard
207
+ [
208
+ /(\^|\\\/)?\\\*$/,
209
+ (_, p1) => {
210
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
211
+ return `${prefix}(?=$|\\/$)`;
212
+ }
213
+ ]
214
+ ];
215
+ var regexCache = /* @__PURE__ */ Object.create(null);
216
+ var makeRegex = (pattern, ignoreCase) => {
217
+ let source = regexCache[pattern];
218
+ if (!source) {
219
+ source = REPLACERS.reduce(
220
+ (prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
221
+ pattern
222
+ );
223
+ regexCache[pattern] = source;
224
+ }
225
+ return ignoreCase ? new RegExp(source, "i") : new RegExp(source);
226
+ };
227
+ var isString = (subject) => typeof subject === "string";
228
+ var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
229
+ var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF);
230
+ var IgnoreRule = class {
231
+ constructor(origin, pattern, negative, regex) {
232
+ this.origin = origin;
233
+ this.pattern = pattern;
234
+ this.negative = negative;
235
+ this.regex = regex;
236
+ }
237
+ };
238
+ var createRule = (pattern, ignoreCase) => {
239
+ const origin = pattern;
240
+ let negative = false;
241
+ if (pattern.indexOf("!") === 0) {
242
+ negative = true;
243
+ pattern = pattern.substr(1);
244
+ }
245
+ pattern = pattern.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
246
+ const regex = makeRegex(pattern, ignoreCase);
247
+ return new IgnoreRule(
248
+ origin,
249
+ pattern,
250
+ negative,
251
+ regex
252
+ );
253
+ };
254
+ var throwError = (message, Ctor) => {
255
+ throw new Ctor(message);
256
+ };
257
+ var checkPath = (path8, originalPath, doThrow) => {
258
+ if (!isString(path8)) {
259
+ return doThrow(
260
+ `path must be a string, but got \`${originalPath}\``,
261
+ TypeError
262
+ );
263
+ }
264
+ if (!path8) {
265
+ return doThrow(`path must not be empty`, TypeError);
266
+ }
267
+ if (checkPath.isNotRelative(path8)) {
268
+ const r = "`path.relative()`d";
269
+ return doThrow(
270
+ `path should be a ${r} string, but got "${originalPath}"`,
271
+ RangeError
272
+ );
273
+ }
274
+ return true;
275
+ };
276
+ var isNotRelative = (path8) => REGEX_TEST_INVALID_PATH.test(path8);
277
+ checkPath.isNotRelative = isNotRelative;
278
+ checkPath.convert = (p) => p;
279
+ var Ignore = class {
280
+ constructor({
281
+ ignorecase = true,
282
+ ignoreCase = ignorecase,
283
+ allowRelativePaths = false
284
+ } = {}) {
285
+ define(this, KEY_IGNORE, true);
286
+ this._rules = [];
287
+ this._ignoreCase = ignoreCase;
288
+ this._allowRelativePaths = allowRelativePaths;
289
+ this._initCache();
290
+ }
291
+ _initCache() {
292
+ this._ignoreCache = /* @__PURE__ */ Object.create(null);
293
+ this._testCache = /* @__PURE__ */ Object.create(null);
294
+ }
295
+ _addPattern(pattern) {
296
+ if (pattern && pattern[KEY_IGNORE]) {
297
+ this._rules = this._rules.concat(pattern._rules);
298
+ this._added = true;
299
+ return;
300
+ }
301
+ if (checkPattern(pattern)) {
302
+ const rule = createRule(pattern, this._ignoreCase);
303
+ this._added = true;
304
+ this._rules.push(rule);
305
+ }
306
+ }
307
+ // @param {Array<string> | string | Ignore} pattern
308
+ add(pattern) {
309
+ this._added = false;
310
+ makeArray(
311
+ isString(pattern) ? splitPattern(pattern) : pattern
312
+ ).forEach(this._addPattern, this);
313
+ if (this._added) {
314
+ this._initCache();
315
+ }
316
+ return this;
317
+ }
318
+ // legacy
319
+ addPattern(pattern) {
320
+ return this.add(pattern);
321
+ }
322
+ // | ignored : unignored
323
+ // negative | 0:0 | 0:1 | 1:0 | 1:1
324
+ // -------- | ------- | ------- | ------- | --------
325
+ // 0 | TEST | TEST | SKIP | X
326
+ // 1 | TESTIF | SKIP | TEST | X
327
+ // - SKIP: always skip
328
+ // - TEST: always test
329
+ // - TESTIF: only test if checkUnignored
330
+ // - X: that never happen
331
+ // @param {boolean} whether should check if the path is unignored,
332
+ // setting `checkUnignored` to `false` could reduce additional
333
+ // path matching.
334
+ // @returns {TestResult} true if a file is ignored
335
+ _testOne(path8, checkUnignored) {
336
+ let ignored = false;
337
+ let unignored = false;
338
+ this._rules.forEach((rule) => {
339
+ const { negative } = rule;
340
+ if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
341
+ return;
342
+ }
343
+ const matched = rule.regex.test(path8);
344
+ if (matched) {
345
+ ignored = !negative;
346
+ unignored = negative;
347
+ }
348
+ });
349
+ return {
350
+ ignored,
351
+ unignored
352
+ };
353
+ }
354
+ // @returns {TestResult}
355
+ _test(originalPath, cache, checkUnignored, slices) {
356
+ const path8 = originalPath && checkPath.convert(originalPath);
357
+ checkPath(
358
+ path8,
359
+ originalPath,
360
+ this._allowRelativePaths ? RETURN_FALSE : throwError
361
+ );
362
+ return this._t(path8, cache, checkUnignored, slices);
363
+ }
364
+ _t(path8, cache, checkUnignored, slices) {
365
+ if (path8 in cache) {
366
+ return cache[path8];
367
+ }
368
+ if (!slices) {
369
+ slices = path8.split(SLASH);
370
+ }
371
+ slices.pop();
372
+ if (!slices.length) {
373
+ return cache[path8] = this._testOne(path8, checkUnignored);
374
+ }
375
+ const parent = this._t(
376
+ slices.join(SLASH) + SLASH,
377
+ cache,
378
+ checkUnignored,
379
+ slices
380
+ );
381
+ return cache[path8] = parent.ignored ? parent : this._testOne(path8, checkUnignored);
382
+ }
383
+ ignores(path8) {
384
+ return this._test(path8, this._ignoreCache, false).ignored;
385
+ }
386
+ createFilter() {
387
+ return (path8) => !this.ignores(path8);
388
+ }
389
+ filter(paths) {
390
+ return makeArray(paths).filter(this.createFilter());
391
+ }
392
+ // @returns {TestResult}
393
+ test(path8) {
394
+ return this._test(path8, this._testCache, true);
395
+ }
396
+ };
397
+ var factory = (options) => new Ignore(options);
398
+ var isPathValid = (path8) => checkPath(path8 && checkPath.convert(path8), path8, RETURN_FALSE);
399
+ factory.isPathValid = isPathValid;
400
+ factory.default = factory;
401
+ module.exports = factory;
402
+ if (
403
+ // Detect `process` so that it can run in browsers.
404
+ typeof process !== "undefined" && (process.env && process.env.IGNORE_TEST_WIN32 || process.platform === "win32")
405
+ ) {
406
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
407
+ checkPath.convert = makePosix;
408
+ const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
409
+ checkPath.isNotRelative = (path8) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path8) || isNotRelative(path8);
410
+ }
411
+ }
412
+ });
413
+
414
+ // src/server/engine-api.ts
415
+ import path7 from "node:path";
416
+ import fs6 from "node:fs";
417
+
418
+ // ../parser/ts/src/ir.ts
419
+ function isPointer(v) {
420
+ return v.kind === "pointer";
421
+ }
422
+ function toPlain(node) {
423
+ const ents = node.entries ?? [];
424
+ if (ents.length === 0) {
425
+ if (node.kind === "scalar") return node.value;
426
+ if (node.kind === "blob") throw new Error("toPlain: a blob has no plain JSON form");
427
+ return node.array ? [] : {};
428
+ }
429
+ if (node.kind === "mapping" && (node.array ?? ents.every((e2) => e2.key === null))) {
430
+ return ents.map(entryPlain);
431
+ }
432
+ const o = {};
433
+ if (node.kind === "scalar") o.$value = node.value;
434
+ ents.forEach((e2, i) => {
435
+ o[e2.key ?? String(i)] = entryPlain(e2);
436
+ });
437
+ return o;
438
+ }
439
+ function entryPlain(e2) {
440
+ if (isPointer(e2.value)) throw new Error("toPlain: unresolved pointer has no plain form");
441
+ return toPlain(e2.value);
442
+ }
443
+
444
+ // ../engine/ts/src/resolve.ts
445
+ function resolveDocument(doc) {
446
+ const chains = buildChains(doc.root);
447
+ const { edges: anchorEdges, keys } = realizeAnchors(doc, chains);
448
+ const out = [];
449
+ const walk = (node, docRoot) => {
450
+ if (!node.entries && !node.meta?.anchors) return;
451
+ const chain = chains.get(node);
452
+ const base = pathOf(chain);
453
+ const dr = node.meta?.documentRoot ? base : docRoot;
454
+ const prefix = base === ":" ? "" : base;
455
+ node.entries?.forEach((e2, i) => {
456
+ const seg = e2.key != null ? ":" + e2.key : "[" + i + "]";
457
+ if (isPointer(e2.value)) {
458
+ const target = resolve(doc, chains, chain, e2.value, /* @__PURE__ */ new Set([e2.value]), keys);
459
+ out.push({ from: prefix + seg, holder: base, label: e2.key, pos: i, raw: e2.value.raw, edge: e2.edge, target, ptr: e2.value, docRoot: dr });
460
+ } else {
461
+ walk(e2.value, dr);
462
+ }
463
+ });
464
+ for (const ae of anchorEdges) {
465
+ if (ae.node !== node) continue;
466
+ const raw = "&" + ae.anchor.path.raw + (ae.anchor.ordinal ? "[]" : "");
467
+ out.push({
468
+ from: base,
469
+ holder: base,
470
+ label: anchorKeyOf(ae.anchor),
471
+ pos: (node.entries?.length ?? 0) + ae.idx,
472
+ raw,
473
+ edge: "back",
474
+ target: ae.target,
475
+ ptr: ae.anchor.path,
476
+ docRoot: dr,
477
+ anchor: true
478
+ });
479
+ }
480
+ };
481
+ walk(doc.root, ":");
482
+ return out;
483
+ }
484
+ function anchorKeyOf(a) {
485
+ return a.ordinal ? null : lastKey(a.path);
486
+ }
487
+ function lastKey(p) {
488
+ const last = p.steps[p.steps.length - 1];
489
+ return last && last.sel === "key" ? last.name : null;
490
+ }
491
+ function realizeAnchors(doc, chains) {
492
+ const edges = [];
493
+ const keys = /* @__PURE__ */ new Map();
494
+ for (const [node, chain] of chains) {
495
+ const anchors = node.meta?.anchors;
496
+ if (!anchors) continue;
497
+ anchors.forEach((a, idx) => {
498
+ const fromChain = chain.slice(0, -1);
499
+ const containerPtr = a.ordinal ? a.path : { ...a.path, steps: a.path.steps.slice(0, -1) };
500
+ let target;
501
+ if (fromChain.length === 0 && (containerPtr.base.scope === "current" || containerPtr.base.scope === "parent")) {
502
+ target = { kind: "unresolved", reason: "a relative anchor on the document root has no container" };
503
+ } else {
504
+ target = resolve(doc, chains, fromChain.length > 0 ? fromChain : [doc.root], containerPtr, /* @__PURE__ */ new Set([containerPtr]));
505
+ }
506
+ edges.push({ node, chain, anchor: a, idx, target });
507
+ if (!a.ordinal && target.kind === "node") {
508
+ const k = lastKey(a.path);
509
+ if (k !== null) {
510
+ let m = keys.get(target.node);
511
+ if (!m) {
512
+ m = /* @__PURE__ */ new Map();
513
+ keys.set(target.node, m);
514
+ }
515
+ if (!m.has(k)) m.set(k, node);
516
+ }
517
+ }
518
+ });
519
+ }
520
+ return { edges, keys };
521
+ }
522
+ function resolve(doc, chains, fromChain, ptr, visited, anchorKeys) {
523
+ const root = doc.root;
524
+ let steps = ptr.steps;
525
+ let chain;
526
+ const linkAuthority = ptr.base.scope === "link" ? ptr.base.authority : "";
527
+ let isLink = false;
528
+ const external = () => ({ kind: "external", authority: linkAuthority, steps: ptr.steps });
529
+ switch (ptr.base.scope) {
530
+ case "link": {
531
+ isLink = true;
532
+ chain = chains.get(root) ?? [root];
533
+ steps = [{ sel: "key", name: ptr.base.authority }, ...ptr.steps];
534
+ break;
535
+ }
536
+ case "document": {
537
+ let docRoot = root;
538
+ for (let k = fromChain.length - 1; k >= 0; k--) {
539
+ if (fromChain[k].meta?.documentRoot) {
540
+ docRoot = fromChain[k];
541
+ break;
542
+ }
543
+ }
544
+ chain = chains.get(docRoot) ?? [docRoot];
545
+ break;
546
+ }
547
+ case "parent":
548
+ chain = fromChain.slice(0, -1);
549
+ break;
550
+ case "current":
551
+ chain = fromChain;
552
+ break;
553
+ }
554
+ for (const st of steps) {
555
+ if (st.sel === "parent") {
556
+ if (chain.length <= 1) return isLink ? external() : { kind: "unresolved", reason: '".." above the document root' };
557
+ chain = chain.slice(0, -1);
558
+ continue;
559
+ }
560
+ const node = chain[chain.length - 1];
561
+ if (node === void 0) return { kind: "unresolved", reason: "empty resolution scope" };
562
+ const entry = st.sel === "key" ? node.entries?.find((e2) => e2.key === st.name) : node.entries?.[st.n];
563
+ if (!entry) {
564
+ if (st.sel === "key") {
565
+ const via = anchorKeys?.get(node)?.get(st.name);
566
+ if (via) {
567
+ chain = chains.get(via) ?? [via];
568
+ continue;
569
+ }
570
+ }
571
+ if (!node.entries) return isLink ? external() : { kind: "unresolved", reason: "step into a node with no fields" };
572
+ return isLink ? external() : { kind: "unresolved", reason: `no ${st.sel === "key" ? `key "${st.name}"` : `index [${st.n}]`}` };
573
+ }
574
+ if (isPointer(entry.value)) {
575
+ if (visited.has(entry.value)) return { kind: "unresolved", reason: "pointer cycle" };
576
+ const next = new Set(visited);
577
+ next.add(entry.value);
578
+ const r = resolve(doc, chains, chain, entry.value, next, anchorKeys);
579
+ if (r.kind !== "node") return r;
580
+ chain = chains.get(r.node) ?? [r.node];
581
+ } else {
582
+ chain = chains.get(entry.value) ?? [...chain, entry.value];
583
+ }
584
+ }
585
+ const target = chain[chain.length - 1];
586
+ return { kind: "node", node: target, path: pathOf(chain) };
587
+ }
588
+ function buildChains(root) {
589
+ const m = /* @__PURE__ */ new Map();
590
+ const walk = (node, chain) => {
591
+ const c = [...chain, node];
592
+ m.set(node, c);
593
+ if (node.entries) {
594
+ for (const e2 of node.entries) if (!isPointer(e2.value)) walk(e2.value, c);
595
+ }
596
+ };
597
+ walk(root, []);
598
+ return m;
599
+ }
600
+ function pathOf(chain) {
601
+ let s = "";
602
+ for (let i = 1; i < chain.length; i++) {
603
+ const parent = chain[i - 1];
604
+ if (!parent.entries) {
605
+ s += ":?";
606
+ continue;
607
+ }
608
+ const idx = parent.entries.findIndex((e3) => e3.value === chain[i]);
609
+ const e2 = parent.entries[idx];
610
+ s += e2 && e2.key != null ? ":" + e2.key : "[" + idx + "]";
611
+ }
612
+ return s === "" ? ":" : s;
613
+ }
614
+
615
+ // ../engine/ts/src/store.ts
616
+ import { DatabaseSync } from "node:sqlite";
617
+ var SCHEMA_VERSION = 4;
618
+ var SCHEMA = `
619
+ CREATE TABLE IF NOT EXISTS node (
620
+ path TEXT PRIMARY KEY,
621
+ type TEXT NOT NULL, -- mapping | scalar | blob
622
+ format TEXT, -- blob/meta format (e.g. image/png, text/markdown)
623
+ value TEXT, -- scalar self-value, JSON-encoded (scalar/omni only)
624
+ content_hash TEXT, -- blob bytes hash (xxh3/BLAKE3 later)
625
+ size INTEGER, -- blob byte size
626
+ is_array INTEGER NOT NULL DEFAULT 0, -- projection hint: 1 \u21D2 all-keyless (pure sequence)
627
+ meta TEXT -- JSON: schema ref, span, \u2026
628
+ );
629
+ CREATE TABLE IF NOT EXISTS edge (
630
+ from_path TEXT NOT NULL,
631
+ to_path TEXT NOT NULL,
632
+ label TEXT, -- relation name (entry key); null for keyless
633
+ kind TEXT NOT NULL, -- contain | ref | back | derived
634
+ pos INTEGER -- order within parent (contain) for stable TOC order
635
+ );
636
+ CREATE INDEX IF NOT EXISTS edge_from ON edge (from_path);
637
+ CREATE INDEX IF NOT EXISTS edge_to ON edge (to_path);
638
+ CREATE TABLE IF NOT EXISTS file (
639
+ path TEXT PRIMARY KEY, -- filesystem path relative to the indexed root (POSIX)
640
+ hash TEXT, -- xxh64:\u2026 of the bytes; NULL until the hasher reaches a large blob
641
+ size INTEGER NOT NULL,
642
+ mtime_ms REAL NOT NULL -- (size, mtime) match \u21D2 reuse hash without re-reading
643
+ );
644
+ CREATE TABLE IF NOT EXISTS dangling (
645
+ from_path TEXT NOT NULL, -- the entry holding the pointer
646
+ raw TEXT NOT NULL, -- the pointer text as authored
647
+ reason TEXT NOT NULL -- why it did not resolve
648
+ );
649
+ `;
650
+ var Store = class {
651
+ db;
652
+ /** True while the DB holds no usable index (a new file, or a schema-version mismatch dropped
653
+ * it) — the caller must run a full re-index before serving from it. Cleared by the first
654
+ * successful {@link indexDocument}. */
655
+ _stale;
656
+ get stale() {
657
+ return this._stale;
658
+ }
659
+ constructor(dbPath) {
660
+ this.db = new DatabaseSync(dbPath);
661
+ this.db.exec("PRAGMA journal_mode = WAL;");
662
+ const ver = this.db.prepare("PRAGMA user_version").get().user_version;
663
+ if (ver !== SCHEMA_VERSION) {
664
+ this.db.exec("DROP TABLE IF EXISTS node; DROP TABLE IF EXISTS edge; DROP TABLE IF EXISTS file; DROP TABLE IF EXISTS dangling;");
665
+ this.db.exec(`PRAGMA user_version = ${SCHEMA_VERSION};`);
666
+ }
667
+ this.db.exec(SCHEMA);
668
+ this._stale = ver !== SCHEMA_VERSION;
669
+ }
670
+ close() {
671
+ this.db.close();
672
+ }
673
+ /** Rebuild the whole index from one resolved document: clear, then insert nodes + edges in
674
+ * a single transaction. (v1 = one document per DB; the directory walker feeds it; ENGINE.md
675
+ * derived/cache contract — re-runnable from scratch at any time.) `files` is the walk's file
676
+ * manifest (hash cache + diff base — replaced wholesale); pointers that fail to resolve are
677
+ * recorded in `dangling` instead of being silently dropped. */
678
+ indexDocument(doc, files) {
679
+ const insNode = this.db.prepare(
680
+ `INSERT OR REPLACE INTO node (path, type, format, value, content_hash, size, is_array, meta)
681
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
682
+ );
683
+ const insEdge = this.db.prepare(
684
+ `INSERT INTO edge (from_path, to_path, label, kind, pos) VALUES (?, ?, ?, ?, ?)`
685
+ );
686
+ this.db.exec("BEGIN");
687
+ try {
688
+ this.db.exec("DELETE FROM node; DELETE FROM edge; DELETE FROM dangling;");
689
+ if (files) this.db.exec("DELETE FROM file;");
690
+ walkNodes(doc.root, ":", (path8, node, parent, label, pos) => {
691
+ const meta = node.meta ? JSON.stringify(node.meta) : null;
692
+ const owned = node.entries?.filter((e2) => e2.edge !== "back") ?? [];
693
+ const isArray = node.array || node.kind === "mapping" && owned.length > 0 && owned.every((e2) => e2.key === null);
694
+ const value = node.kind === "scalar" ? JSON.stringify(node.value) : null;
695
+ const format = node.kind === "blob" ? node.format : formatFromMeta(node);
696
+ const hash = node.kind === "blob" ? node.contentHash : null;
697
+ const size = node.kind === "blob" ? node.size : null;
698
+ insNode.run(path8, node.kind, format, value, hash, size, isArray ? 1 : 0, meta);
699
+ if (parent !== null) insEdge.run(parent, path8, label, "contain", pos);
700
+ });
701
+ const insDangling = this.db.prepare("INSERT INTO dangling (from_path, raw, reason) VALUES (?, ?, ?)");
702
+ for (const r of resolveDocument(doc)) {
703
+ if (r.target.kind === "node") insEdge.run(r.holder, r.target.path, r.label, r.edge, r.pos);
704
+ else if (r.target.kind === "unresolved") insDangling.run(r.from, r.raw, r.target.reason);
705
+ }
706
+ if (files) {
707
+ const insFile = this.db.prepare("INSERT OR REPLACE INTO file (path, hash, size, mtime_ms) VALUES (?, ?, ?, ?)");
708
+ for (const f of files) insFile.run(f.path, f.hash, f.size, f.mtimeMs);
709
+ }
710
+ this.db.exec("COMMIT");
711
+ this._stale = false;
712
+ } catch (e2) {
713
+ this.db.exec("ROLLBACK");
714
+ throw e2;
715
+ }
716
+ }
717
+ /** Incrementally add ONE annotation document at `annStorePath`, with a forward `target` ref edge
718
+ * to `targetStorePath` and (when given) a keyless `back` edge to `tagStorePath` — the applied
719
+ * tag's `~-` membership. This avoids a full re-walk/rebuild (which re-reads and re-hashes every
720
+ * blob in the served tree, blocking the server) on every save. The root is FORCED to the
721
+ * annotation format so the material's backlink lookup ({@link relationships} `.in`) finds it; the
722
+ * `target`/`~-` pointers can't resolve in isolation (they are project-scoped `//…`), so their
723
+ * edges are added directly from the known targets. */
724
+ addAnnotation(annStorePath, targetStorePath, doc, tagStorePath) {
725
+ const insNode = this.db.prepare(
726
+ `INSERT OR REPLACE INTO node (path, type, format, value, content_hash, size, is_array, meta)
727
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
728
+ );
729
+ const insEdge = this.db.prepare(
730
+ `INSERT INTO edge (from_path, to_path, label, kind, pos) VALUES (?, ?, ?, ?, ?)`
731
+ );
732
+ this.db.exec("BEGIN");
733
+ try {
734
+ walkNodes(doc.root, annStorePath, (p, node, parent, label, pos) => {
735
+ const meta = node.meta ? JSON.stringify(node.meta) : null;
736
+ const isArray = node.array || node.kind === "mapping" && (node.entries?.every((e2) => e2.key === null) ?? false);
737
+ const value = node.kind === "scalar" ? JSON.stringify(node.value) : null;
738
+ const format = p === annStorePath ? "x-yamlover-annotation" : node.kind === "blob" ? node.format : formatFromMeta(node);
739
+ const hash = node.kind === "blob" ? node.contentHash : null;
740
+ const size = node.kind === "blob" ? node.size : null;
741
+ insNode.run(p, node.kind, format, value, hash, size, isArray ? 1 : 0, meta);
742
+ if (parent !== null) insEdge.run(parent, p, label, "contain", pos);
743
+ });
744
+ insEdge.run(annStorePath, targetStorePath, "target", "ref", 0);
745
+ if (tagStorePath) {
746
+ const pos = (doc.root.entries ?? []).findIndex((e2) => e2.edge === "back" && e2.key === null);
747
+ insEdge.run(annStorePath, tagStorePath, null, "back", pos >= 0 ? pos : 0);
748
+ }
749
+ this.db.exec("COMMIT");
750
+ } catch (e2) {
751
+ this.db.exec("ROLLBACK");
752
+ throw e2;
753
+ }
754
+ }
755
+ /** Incrementally remove one annotation: its node subtree and every edge touching it. */
756
+ removeAnnotation(annStorePath) {
757
+ const like = annStorePath + ":%";
758
+ this.db.exec("BEGIN");
759
+ try {
760
+ this.db.prepare("DELETE FROM node WHERE path = ? OR path LIKE ?").run(annStorePath, like);
761
+ this.db.prepare("DELETE FROM edge WHERE from_path = ? OR from_path LIKE ? OR to_path = ? OR to_path LIKE ?").run(annStorePath, like, annStorePath, like);
762
+ this.db.exec("COMMIT");
763
+ } catch (e2) {
764
+ this.db.exec("ROLLBACK");
765
+ throw e2;
766
+ }
767
+ }
768
+ /** Incrementally add ONE named tag under a taxonomy node (tag creation's write path — the
769
+ * {@link addAnnotation} of tags): upsert any missing taxonomy ancestors as plain mappings (a
770
+ * fresh `/tags` has no rows yet), then the tag's subtree and its containment edge. Approximate
771
+ * by design — the next reconcile re-walks the edited taxonomy body and trues everything up;
772
+ * these rows only have to answer queries until then. */
773
+ addTag(taxonomyStorePath, name, pos, node) {
774
+ const insNode = this.db.prepare(
775
+ `INSERT OR REPLACE INTO node (path, type, format, value, content_hash, size, is_array, meta)
776
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
777
+ );
778
+ const insEdge = this.db.prepare(
779
+ `INSERT INTO edge (from_path, to_path, label, kind, pos) VALUES (?, ?, ?, ?, ?)`
780
+ );
781
+ const hasNode = this.db.prepare("SELECT 1 FROM node WHERE path = ?");
782
+ this.db.exec("BEGIN");
783
+ try {
784
+ const segs = taxonomyStorePath === ":" ? [] : taxonomyStorePath.slice(1).split(":");
785
+ for (let i = 1; i <= segs.length; i++) {
786
+ const p = ":" + segs.slice(0, i).join(":");
787
+ if (hasNode.get(p)) continue;
788
+ insNode.run(p, "mapping", null, null, null, null, 0, null);
789
+ insEdge.run(i === 1 ? ":" : ":" + segs.slice(0, i - 1).join(":"), p, segs[i - 1], "contain", null);
790
+ }
791
+ const tagPath = (taxonomyStorePath === ":" ? "" : taxonomyStorePath) + ":" + name;
792
+ walkNodes(node, tagPath, (p, n, parent, label, ps) => {
793
+ const meta = n.meta ? JSON.stringify(n.meta) : null;
794
+ const isArray = n.array || n.kind === "mapping" && (n.entries?.every((e2) => e2.key === null) ?? false);
795
+ const value = n.kind === "scalar" ? JSON.stringify(n.value) : null;
796
+ const format = n.kind === "blob" ? n.format : formatFromMeta(n);
797
+ insNode.run(p, n.kind, format, value, n.kind === "blob" ? n.contentHash : null, n.kind === "blob" ? n.size : null, isArray ? 1 : 0, meta);
798
+ if (parent !== null) insEdge.run(parent, p, label, "contain", ps);
799
+ });
800
+ insEdge.run(taxonomyStorePath, tagPath, name, "contain", pos);
801
+ this.db.exec("COMMIT");
802
+ } catch (e2) {
803
+ this.db.exec("ROLLBACK");
804
+ throw e2;
805
+ }
806
+ }
807
+ /** The persisted file manifest, keyed by root-relative path — the previous walk's view of the
808
+ * filesystem. Feeds the walker's hash cache and the change diff. */
809
+ manifest() {
810
+ const out = /* @__PURE__ */ new Map();
811
+ for (const r of this.db.prepare("SELECT * FROM file").all()) {
812
+ out.set(r.path, { path: r.path, hash: r.hash ?? null, size: r.size, mtimeMs: r.mtime_ms });
813
+ }
814
+ return out;
815
+ }
816
+ /** Manifest entries still lacking a content hash (large blobs the walk never read), smallest
817
+ * first so the background hasher shows progress early. */
818
+ unhashedFiles(limit = -1) {
819
+ return this.db.prepare("SELECT * FROM file WHERE hash IS NULL ORDER BY size ASC, path ASC LIMIT ?").all(limit).map((r) => ({ path: r.path, hash: null, size: r.size, mtimeMs: r.mtime_ms }));
820
+ }
821
+ /** Fill in one file's content hash after the fact (the background hasher): updates the
822
+ * manifest row and the matching blob node, guarded by (size, mtimeMs) — returns false
823
+ * (writing nothing) when the file changed since it was queued. */
824
+ setFileHash(relPath, hash, size, mtimeMs) {
825
+ this.db.exec("BEGIN");
826
+ try {
827
+ const r = this.db.prepare("UPDATE file SET hash = ? WHERE path = ? AND size = ? AND mtime_ms = ?").run(hash, relPath, size, mtimeMs);
828
+ if (Number(r.changes) === 0) {
829
+ this.db.exec("ROLLBACK");
830
+ return false;
831
+ }
832
+ this.db.prepare("UPDATE node SET content_hash = ? WHERE path = ? AND type = 'blob'").run(hash, ":" + relPath.split("/").join(":"));
833
+ this.db.exec("COMMIT");
834
+ return true;
835
+ } catch (e2) {
836
+ this.db.exec("ROLLBACK");
837
+ throw e2;
838
+ }
839
+ }
840
+ /** The pointers that failed to resolve at index time (ENGINE.md: reported, never dropped). */
841
+ dangling() {
842
+ return this.db.prepare("SELECT * FROM dangling").all().map((r) => ({
843
+ from: r.from_path,
844
+ raw: r.raw,
845
+ reason: r.reason
846
+ }));
847
+ }
848
+ /** A node's attributes (null if no such path). */
849
+ node(path8) {
850
+ const row = this.db.prepare("SELECT * FROM node WHERE path = ?").get(path8);
851
+ return row ? rowToNode(row) : null;
852
+ }
853
+ /** Direct containment children of a node, in source order (by `pos`). */
854
+ children(path8) {
855
+ return this.db.prepare("SELECT to_path, label, pos FROM edge WHERE from_path = ? AND kind = 'contain' ORDER BY pos").all(path8).map((r) => ({ to: r.to_path, label: r.label ?? null, pos: r.pos ?? null }));
856
+ }
857
+ /** ALL of a node's entries in source order — containment children AND `*`/`~` pointer
858
+ * entries (ref/back). Unlike {@link children} this keeps positional pointers (`- *file`)
859
+ * in place, so an array that mixes inline values and pointers (e.g. a chapter's `chunks`)
860
+ * projects in full. `kind` says whether `to` is an owned child (contain) or a pointer target. */
861
+ entries(path8) {
862
+ return this.db.prepare("SELECT to_path, label, pos, kind FROM edge WHERE from_path = ? AND kind IN ('contain','ref','back') ORDER BY pos").all(path8).map((r) => ({ to: r.to_path, label: r.label ?? null, pos: r.pos ?? null, kind: r.kind }));
863
+ }
864
+ /** Whether a node has any containment children (expandable in the TOC). */
865
+ hasChildren(path8) {
866
+ const r = this.db.prepare("SELECT 1 FROM edge WHERE from_path = ? AND kind = 'contain' LIMIT 1").get(path8);
867
+ return r != null;
868
+ }
869
+ /** The containment subtree rooted at `path`, up to `depth` levels (TOC). A recursive CTE
870
+ * over `contain` edges — the acyclic spine; ordered by `pos` at each level. */
871
+ toc(path8 = ":", depth = Infinity) {
872
+ const rows = this.db.prepare(
873
+ `WITH RECURSIVE sub(from_path, to_path, label, pos, lvl) AS (
874
+ SELECT from_path, to_path, label, pos, 1 FROM edge
875
+ WHERE kind = 'contain' AND from_path = ?
876
+ UNION ALL
877
+ SELECT e.from_path, e.to_path, e.label, e.pos, sub.lvl + 1 FROM edge e
878
+ JOIN sub ON e.from_path = sub.to_path
879
+ WHERE e.kind = 'contain' AND sub.lvl < ?
880
+ )
881
+ SELECT s.to_path AS path, s.from_path AS parent, s.label, s.pos, n.type, n.format
882
+ FROM sub s JOIN node n ON n.path = s.to_path
883
+ ORDER BY s.lvl, s.from_path, s.pos`
884
+ ).all(path8, depth === Infinity ? Number.MAX_SAFE_INTEGER : depth);
885
+ const byPath = /* @__PURE__ */ new Map();
886
+ const root = { path: path8, label: null, type: this.node(path8)?.type ?? "mapping", format: null, children: [] };
887
+ byPath.set(path8, root);
888
+ for (const r of rows) {
889
+ const tn = {
890
+ path: r.path,
891
+ label: r.label ?? null,
892
+ type: r.type,
893
+ format: r.format ?? null,
894
+ children: []
895
+ };
896
+ byPath.set(tn.path, tn);
897
+ (byPath.get(r.parent) ?? root).children.push(tn);
898
+ }
899
+ return root.children;
900
+ }
901
+ /** Edges in/out of a node, by kind. `derived` inverses are computed here on demand (never
902
+ * stored) per ENGINE.md — for each ref/back into/out of the node, the reverse direction. */
903
+ relationships(path8) {
904
+ const out = this.db.prepare("SELECT * FROM edge WHERE from_path = ? AND kind != 'contain'").all(path8).map(rowToEdge);
905
+ const inc = this.db.prepare("SELECT * FROM edge WHERE to_path = ? AND kind != 'contain'").all(path8).map(rowToEdge);
906
+ const derivedIn = inc.filter((e2) => e2.kind === "ref" || e2.kind === "back").map((e2) => ({ from: e2.to, to: e2.from, label: e2.label, kind: "derived", pos: null }));
907
+ return { out, in: [...inc, ...derivedIn] };
908
+ }
909
+ };
910
+ function walkNodes(node, path8, visit, parent = null, label = null, pos = null) {
911
+ visit(path8, node, parent, label, pos);
912
+ if (!node.entries) return;
913
+ node.entries.forEach((e2, i) => {
914
+ if (isPointer(e2.value)) return;
915
+ const childPath = (path8 === ":" ? "" : path8) + (e2.key != null ? ":" + e2.key : "[" + i + "]");
916
+ walkNodes(e2.value, childPath, visit, path8, e2.key, i);
917
+ });
918
+ }
919
+ function formatFromMeta(node) {
920
+ const s = node.meta?.schema;
921
+ if (!s) return null;
922
+ if (isPointer(s)) {
923
+ const last = s.steps[s.steps.length - 1];
924
+ return last?.sel === "key" ? `x-yamlover-${last.name}` : null;
925
+ }
926
+ if (s.kind !== "mapping") return null;
927
+ const f = s.entries?.find((e2) => e2.key === "format");
928
+ if (f && !isPointer(f.value) && f.value.kind === "scalar") return String(f.value.value);
929
+ return null;
930
+ }
931
+ function rowToNode(r) {
932
+ return {
933
+ path: r.path,
934
+ type: r.type,
935
+ format: r.format ?? null,
936
+ value: r.value != null ? JSON.parse(r.value) : null,
937
+ content_hash: r.content_hash ?? null,
938
+ size: r.size ?? null,
939
+ is_array: !!r.is_array,
940
+ meta: r.meta != null ? JSON.parse(r.meta) : null
941
+ };
942
+ }
943
+ function rowToEdge(r) {
944
+ return {
945
+ from: r.from_path,
946
+ to: r.to_path,
947
+ label: r.label ?? null,
948
+ kind: r.kind,
949
+ pos: r.pos ?? null
950
+ };
951
+ }
952
+
953
+ // ../engine/ts/src/walk.ts
954
+ import fs from "node:fs";
955
+ import path from "node:path";
956
+ import { setImmediate as yieldLoop } from "node:timers/promises";
957
+
958
+ // ../engine/ts/node_modules/xxhash-wasm/esm/xxhash-wasm.js
959
+ var t = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 48, 8, 96, 3, 127, 127, 127, 1, 127, 96, 3, 127, 127, 127, 0, 96, 2, 127, 127, 0, 96, 1, 127, 1, 127, 96, 3, 127, 127, 126, 1, 126, 96, 3, 126, 127, 127, 1, 126, 96, 2, 127, 126, 0, 96, 1, 127, 1, 126, 3, 11, 10, 0, 0, 2, 1, 3, 4, 5, 6, 1, 7, 5, 3, 1, 0, 1, 7, 85, 9, 3, 109, 101, 109, 2, 0, 5, 120, 120, 104, 51, 50, 0, 0, 6, 105, 110, 105, 116, 51, 50, 0, 2, 8, 117, 112, 100, 97, 116, 101, 51, 50, 0, 3, 8, 100, 105, 103, 101, 115, 116, 51, 50, 0, 4, 5, 120, 120, 104, 54, 52, 0, 5, 6, 105, 110, 105, 116, 54, 52, 0, 7, 8, 117, 112, 100, 97, 116, 101, 54, 52, 0, 8, 8, 100, 105, 103, 101, 115, 116, 54, 52, 0, 9, 10, 251, 22, 10, 242, 1, 1, 4, 127, 32, 0, 32, 1, 106, 33, 3, 32, 1, 65, 16, 79, 4, 127, 32, 3, 65, 16, 107, 33, 6, 32, 2, 65, 168, 136, 141, 161, 2, 106, 33, 3, 32, 2, 65, 137, 235, 208, 208, 7, 107, 33, 4, 32, 2, 65, 207, 140, 162, 142, 6, 106, 33, 5, 3, 64, 32, 3, 32, 0, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 33, 3, 32, 4, 32, 0, 65, 4, 106, 34, 0, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 33, 4, 32, 2, 32, 0, 65, 4, 106, 34, 0, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 33, 2, 32, 5, 32, 0, 65, 4, 106, 34, 0, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 33, 5, 32, 6, 32, 0, 65, 4, 106, 34, 0, 79, 13, 0, 11, 32, 2, 65, 12, 119, 32, 5, 65, 18, 119, 106, 32, 4, 65, 7, 119, 106, 32, 3, 65, 1, 119, 106, 5, 32, 2, 65, 177, 207, 217, 178, 1, 106, 11, 32, 1, 106, 32, 0, 32, 1, 65, 15, 113, 16, 1, 11, 146, 1, 0, 32, 1, 32, 2, 106, 33, 2, 3, 64, 32, 1, 65, 4, 106, 32, 2, 75, 69, 4, 64, 32, 0, 32, 1, 40, 2, 0, 65, 189, 220, 202, 149, 124, 108, 106, 65, 17, 119, 65, 175, 214, 211, 190, 2, 108, 33, 0, 32, 1, 65, 4, 106, 33, 1, 12, 1, 11, 11, 3, 64, 32, 1, 32, 2, 79, 69, 4, 64, 32, 0, 32, 1, 45, 0, 0, 65, 177, 207, 217, 178, 1, 108, 106, 65, 11, 119, 65, 177, 243, 221, 241, 121, 108, 33, 0, 32, 1, 65, 1, 106, 33, 1, 12, 1, 11, 11, 32, 0, 32, 0, 65, 15, 118, 115, 65, 247, 148, 175, 175, 120, 108, 34, 0, 65, 13, 118, 32, 0, 115, 65, 189, 220, 202, 149, 124, 108, 34, 0, 65, 16, 118, 32, 0, 115, 11, 63, 0, 32, 0, 65, 8, 106, 32, 1, 65, 168, 136, 141, 161, 2, 106, 54, 2, 0, 32, 0, 65, 12, 106, 32, 1, 65, 137, 235, 208, 208, 7, 107, 54, 2, 0, 32, 0, 65, 16, 106, 32, 1, 54, 2, 0, 32, 0, 65, 20, 106, 32, 1, 65, 207, 140, 162, 142, 6, 106, 54, 2, 0, 11, 195, 4, 1, 6, 127, 32, 1, 32, 2, 106, 33, 6, 32, 0, 65, 24, 106, 33, 4, 32, 0, 65, 40, 106, 40, 2, 0, 33, 3, 32, 0, 32, 0, 40, 2, 0, 32, 2, 106, 54, 2, 0, 32, 0, 65, 4, 106, 34, 5, 32, 5, 40, 2, 0, 32, 2, 65, 16, 79, 32, 0, 40, 2, 0, 65, 16, 79, 114, 114, 54, 2, 0, 32, 2, 32, 3, 106, 65, 16, 73, 4, 64, 32, 3, 32, 4, 106, 32, 1, 32, 2, 252, 10, 0, 0, 32, 0, 65, 40, 106, 32, 2, 32, 3, 106, 54, 2, 0, 15, 11, 32, 3, 4, 64, 32, 3, 32, 4, 106, 32, 1, 65, 16, 32, 3, 107, 34, 2, 252, 10, 0, 0, 32, 0, 65, 8, 106, 34, 3, 32, 3, 40, 2, 0, 32, 4, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 54, 2, 0, 32, 0, 65, 12, 106, 34, 3, 32, 3, 40, 2, 0, 32, 4, 65, 4, 106, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 54, 2, 0, 32, 0, 65, 16, 106, 34, 3, 32, 3, 40, 2, 0, 32, 4, 65, 8, 106, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 54, 2, 0, 32, 0, 65, 20, 106, 34, 3, 32, 3, 40, 2, 0, 32, 4, 65, 12, 106, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 54, 2, 0, 32, 0, 65, 40, 106, 65, 0, 54, 2, 0, 32, 1, 32, 2, 106, 33, 1, 11, 32, 1, 32, 6, 65, 16, 107, 77, 4, 64, 32, 6, 65, 16, 107, 33, 8, 32, 0, 65, 8, 106, 40, 2, 0, 33, 2, 32, 0, 65, 12, 106, 40, 2, 0, 33, 3, 32, 0, 65, 16, 106, 40, 2, 0, 33, 5, 32, 0, 65, 20, 106, 40, 2, 0, 33, 7, 3, 64, 32, 2, 32, 1, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 33, 2, 32, 3, 32, 1, 65, 4, 106, 34, 1, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 33, 3, 32, 5, 32, 1, 65, 4, 106, 34, 1, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 33, 5, 32, 7, 32, 1, 65, 4, 106, 34, 1, 40, 2, 0, 65, 247, 148, 175, 175, 120, 108, 106, 65, 13, 119, 65, 177, 243, 221, 241, 121, 108, 33, 7, 32, 8, 32, 1, 65, 4, 106, 34, 1, 79, 13, 0, 11, 32, 0, 65, 8, 106, 32, 2, 54, 2, 0, 32, 0, 65, 12, 106, 32, 3, 54, 2, 0, 32, 0, 65, 16, 106, 32, 5, 54, 2, 0, 32, 0, 65, 20, 106, 32, 7, 54, 2, 0, 11, 32, 1, 32, 6, 73, 4, 64, 32, 4, 32, 1, 32, 6, 32, 1, 107, 34, 1, 252, 10, 0, 0, 32, 0, 65, 40, 106, 32, 1, 54, 2, 0, 11, 11, 97, 1, 1, 127, 32, 0, 65, 16, 106, 40, 2, 0, 33, 1, 32, 0, 65, 4, 106, 40, 2, 0, 4, 127, 32, 1, 65, 12, 119, 32, 0, 65, 20, 106, 40, 2, 0, 65, 18, 119, 106, 32, 0, 65, 12, 106, 40, 2, 0, 65, 7, 119, 106, 32, 0, 65, 8, 106, 40, 2, 0, 65, 1, 119, 106, 5, 32, 1, 65, 177, 207, 217, 178, 1, 106, 11, 32, 0, 40, 2, 0, 106, 32, 0, 65, 24, 106, 32, 0, 65, 40, 106, 40, 2, 0, 16, 1, 11, 255, 3, 2, 3, 126, 1, 127, 32, 0, 32, 1, 106, 33, 6, 32, 1, 65, 32, 79, 4, 126, 32, 6, 65, 32, 107, 33, 6, 32, 2, 66, 214, 235, 130, 238, 234, 253, 137, 245, 224, 0, 124, 33, 3, 32, 2, 66, 177, 169, 172, 193, 173, 184, 212, 166, 61, 125, 33, 4, 32, 2, 66, 249, 234, 208, 208, 231, 201, 161, 228, 225, 0, 124, 33, 5, 3, 64, 32, 3, 32, 0, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 3, 32, 4, 32, 0, 65, 8, 106, 34, 0, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 4, 32, 2, 32, 0, 65, 8, 106, 34, 0, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 2, 32, 5, 32, 0, 65, 8, 106, 34, 0, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 5, 32, 6, 32, 0, 65, 8, 106, 34, 0, 79, 13, 0, 11, 32, 2, 66, 12, 137, 32, 5, 66, 18, 137, 124, 32, 4, 66, 7, 137, 124, 32, 3, 66, 1, 137, 124, 32, 3, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 32, 4, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 32, 2, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 32, 5, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 5, 32, 2, 66, 197, 207, 217, 178, 241, 229, 186, 234, 39, 124, 11, 32, 1, 173, 124, 32, 0, 32, 1, 65, 31, 113, 16, 6, 11, 134, 2, 0, 32, 1, 32, 2, 106, 33, 2, 3, 64, 32, 2, 32, 1, 65, 8, 106, 79, 4, 64, 32, 1, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 32, 0, 133, 66, 27, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 33, 0, 32, 1, 65, 8, 106, 33, 1, 12, 1, 11, 11, 32, 1, 65, 4, 106, 32, 2, 77, 4, 64, 32, 0, 32, 1, 53, 2, 0, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 23, 137, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 249, 243, 221, 241, 153, 246, 153, 171, 22, 124, 33, 0, 32, 1, 65, 4, 106, 33, 1, 11, 3, 64, 32, 1, 32, 2, 73, 4, 64, 32, 0, 32, 1, 49, 0, 0, 66, 197, 207, 217, 178, 241, 229, 186, 234, 39, 126, 133, 66, 11, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 0, 32, 1, 65, 1, 106, 33, 1, 12, 1, 11, 11, 32, 0, 32, 0, 66, 33, 136, 133, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 34, 0, 32, 0, 66, 29, 136, 133, 66, 249, 243, 221, 241, 153, 246, 153, 171, 22, 126, 34, 0, 32, 0, 66, 32, 136, 133, 11, 77, 0, 32, 0, 65, 8, 106, 32, 1, 66, 214, 235, 130, 238, 234, 253, 137, 245, 224, 0, 124, 55, 3, 0, 32, 0, 65, 16, 106, 32, 1, 66, 177, 169, 172, 193, 173, 184, 212, 166, 61, 125, 55, 3, 0, 32, 0, 65, 24, 106, 32, 1, 55, 3, 0, 32, 0, 65, 32, 106, 32, 1, 66, 249, 234, 208, 208, 231, 201, 161, 228, 225, 0, 124, 55, 3, 0, 11, 244, 4, 2, 3, 127, 4, 126, 32, 1, 32, 2, 106, 33, 5, 32, 0, 65, 40, 106, 33, 4, 32, 0, 65, 200, 0, 106, 40, 2, 0, 33, 3, 32, 0, 32, 0, 41, 3, 0, 32, 2, 173, 124, 55, 3, 0, 32, 2, 32, 3, 106, 65, 32, 73, 4, 64, 32, 3, 32, 4, 106, 32, 1, 32, 2, 252, 10, 0, 0, 32, 0, 65, 200, 0, 106, 32, 2, 32, 3, 106, 54, 2, 0, 15, 11, 32, 3, 4, 64, 32, 3, 32, 4, 106, 32, 1, 65, 32, 32, 3, 107, 34, 2, 252, 10, 0, 0, 32, 0, 65, 8, 106, 34, 3, 32, 3, 41, 3, 0, 32, 4, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 55, 3, 0, 32, 0, 65, 16, 106, 34, 3, 32, 3, 41, 3, 0, 32, 4, 65, 8, 106, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 55, 3, 0, 32, 0, 65, 24, 106, 34, 3, 32, 3, 41, 3, 0, 32, 4, 65, 16, 106, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 55, 3, 0, 32, 0, 65, 32, 106, 34, 3, 32, 3, 41, 3, 0, 32, 4, 65, 24, 106, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 55, 3, 0, 32, 0, 65, 200, 0, 106, 65, 0, 54, 2, 0, 32, 1, 32, 2, 106, 33, 1, 11, 32, 1, 65, 32, 106, 32, 5, 77, 4, 64, 32, 5, 65, 32, 107, 33, 2, 32, 0, 65, 8, 106, 41, 3, 0, 33, 6, 32, 0, 65, 16, 106, 41, 3, 0, 33, 7, 32, 0, 65, 24, 106, 41, 3, 0, 33, 8, 32, 0, 65, 32, 106, 41, 3, 0, 33, 9, 3, 64, 32, 6, 32, 1, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 6, 32, 7, 32, 1, 65, 8, 106, 34, 1, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 7, 32, 8, 32, 1, 65, 8, 106, 34, 1, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 8, 32, 9, 32, 1, 65, 8, 106, 34, 1, 41, 3, 0, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 124, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 33, 9, 32, 2, 32, 1, 65, 8, 106, 34, 1, 79, 13, 0, 11, 32, 0, 65, 8, 106, 32, 6, 55, 3, 0, 32, 0, 65, 16, 106, 32, 7, 55, 3, 0, 32, 0, 65, 24, 106, 32, 8, 55, 3, 0, 32, 0, 65, 32, 106, 32, 9, 55, 3, 0, 11, 32, 1, 32, 5, 73, 4, 64, 32, 4, 32, 1, 32, 5, 32, 1, 107, 34, 1, 252, 10, 0, 0, 32, 0, 65, 200, 0, 106, 32, 1, 54, 2, 0, 11, 11, 188, 2, 1, 5, 126, 32, 0, 65, 24, 106, 41, 3, 0, 33, 1, 32, 0, 41, 3, 0, 34, 2, 66, 32, 90, 4, 126, 32, 0, 65, 8, 106, 41, 3, 0, 34, 3, 66, 1, 137, 32, 0, 65, 16, 106, 41, 3, 0, 34, 4, 66, 7, 137, 124, 32, 1, 66, 12, 137, 32, 0, 65, 32, 106, 41, 3, 0, 34, 5, 66, 18, 137, 124, 124, 32, 3, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 32, 4, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 32, 1, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 32, 5, 66, 207, 214, 211, 190, 210, 199, 171, 217, 66, 126, 66, 31, 137, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 133, 66, 135, 149, 175, 175, 152, 182, 222, 155, 158, 127, 126, 66, 157, 163, 181, 234, 131, 177, 141, 138, 250, 0, 125, 5, 32, 1, 66, 197, 207, 217, 178, 241, 229, 186, 234, 39, 124, 11, 32, 2, 124, 32, 0, 65, 40, 106, 32, 2, 66, 31, 131, 167, 16, 6, 11]);
960
+ async function e() {
961
+ return function(t2) {
962
+ const { exports: { mem: e2, xxh32: n, xxh64: r, init32: i, update32: a, digest32: o, init64: s, update64: u, digest64: c } } = t2;
963
+ let h = new Uint8Array(e2.buffer);
964
+ function g(t3, n2) {
965
+ if (e2.buffer.byteLength < t3 + n2) {
966
+ const r2 = Math.ceil((t3 + n2 - e2.buffer.byteLength) / 65536);
967
+ e2.grow(r2), h = new Uint8Array(e2.buffer);
968
+ }
969
+ }
970
+ function f(t3, e3, n2, r2, i2, a2) {
971
+ g(t3);
972
+ const o2 = new Uint8Array(t3);
973
+ return h.set(o2), n2(0, e3), o2.set(h.subarray(0, t3)), { update(e4) {
974
+ let n3;
975
+ return h.set(o2), "string" == typeof e4 ? (g(3 * e4.length, t3), n3 = w.encodeInto(e4, h.subarray(t3)).written) : (g(e4.byteLength, t3), h.set(e4, t3), n3 = e4.byteLength), r2(0, t3, n3), o2.set(h.subarray(0, t3)), this;
976
+ }, digest: () => (h.set(o2), a2(i2(0))) };
977
+ }
978
+ function y(t3) {
979
+ return t3 >>> 0;
980
+ }
981
+ const b = 2n ** 64n - 1n;
982
+ function d(t3) {
983
+ return t3 & b;
984
+ }
985
+ const w = new TextEncoder(), l = 0, p = 0n;
986
+ function x(t3, e3 = l) {
987
+ return g(3 * t3.length, 0), y(n(0, w.encodeInto(t3, h).written, e3));
988
+ }
989
+ function L(t3, e3 = p) {
990
+ return g(3 * t3.length, 0), d(r(0, w.encodeInto(t3, h).written, e3));
991
+ }
992
+ return { h32: x, h32ToString: (t3, e3 = l) => x(t3, e3).toString(16).padStart(8, "0"), h32Raw: (t3, e3 = l) => (g(t3.byteLength, 0), h.set(t3), y(n(0, t3.byteLength, e3))), create32: (t3 = l) => f(48, t3, i, a, o, y), h64: L, h64ToString: (t3, e3 = p) => L(t3, e3).toString(16).padStart(16, "0"), h64Raw: (t3, e3 = p) => (g(t3.byteLength, 0), h.set(t3), d(r(0, t3.byteLength, e3))), create64: (t3 = p) => f(88, t3, s, u, c, d) };
993
+ }((await WebAssembly.instantiate(t)).instance);
994
+ }
995
+
996
+ // ../parser/ts/src/pointer.ts
997
+ var LINK_RE = /^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\//;
998
+ function parsePointer(raw) {
999
+ if (looksColon(raw)) return parseColon(raw);
1000
+ const lm = LINK_RE.exec(raw);
1001
+ if (lm) {
1002
+ const after = raw.slice(lm[0].length);
1003
+ const { seg, rest } = readUntilSlash(after);
1004
+ const base2 = { scope: "link", authority: unescape(seg) };
1005
+ return { kind: "pointer", base: base2, steps: parsePath(rest), raw };
1006
+ }
1007
+ if (raw[0] === "/") {
1008
+ return { kind: "pointer", base: { scope: "document" }, steps: parsePath(raw), raw };
1009
+ }
1010
+ const segs = splitSlash(raw);
1011
+ let base = { scope: "current" };
1012
+ if (segs.length > 0 && segs[0] === "..") {
1013
+ base = { scope: "parent" };
1014
+ segs.shift();
1015
+ }
1016
+ const steps = [];
1017
+ for (const s of segs) {
1018
+ if (s === "") continue;
1019
+ steps.push(...segToSteps(s));
1020
+ }
1021
+ return { kind: "pointer", base, steps, raw };
1022
+ }
1023
+ function looksColon(raw) {
1024
+ if (raw.startsWith(":")) return true;
1025
+ if (LINK_RE.test(raw)) return false;
1026
+ let q = null;
1027
+ for (let i = 0; i < raw.length; i++) {
1028
+ const c = raw[i];
1029
+ if (c === "\\" && q === null) {
1030
+ i++;
1031
+ continue;
1032
+ }
1033
+ if (q !== null) {
1034
+ if (c === q) q = null;
1035
+ continue;
1036
+ }
1037
+ if (c === "'" || c === '"') {
1038
+ q = c;
1039
+ continue;
1040
+ }
1041
+ if (c === ":") return true;
1042
+ }
1043
+ return false;
1044
+ }
1045
+ function parseColon(raw) {
1046
+ let rest = raw;
1047
+ let base;
1048
+ if (rest.startsWith(":::")) {
1049
+ rest = rest.slice(3);
1050
+ const portions2 = splitColon(rest);
1051
+ const auth = portions2.shift();
1052
+ if (auth === void 0 || auth === "") throw new SyntaxError(`pointer: ":::" needs an authority in "${raw}"`);
1053
+ base = { scope: "link", authority: portionName(auth), world: true };
1054
+ return { kind: "pointer", base, steps: portionsToSteps(portions2), raw };
1055
+ }
1056
+ if (rest.startsWith("::")) {
1057
+ rest = rest.slice(2);
1058
+ const portions2 = splitColon(rest);
1059
+ const auth = portions2.shift();
1060
+ if (auth === void 0 || auth === "") throw new SyntaxError(`pointer: "::" needs a first portion in "${raw}"`);
1061
+ base = { scope: "link", authority: portionName(auth) };
1062
+ return { kind: "pointer", base, steps: portionsToSteps(portions2), raw };
1063
+ }
1064
+ if (rest.startsWith(":")) {
1065
+ return { kind: "pointer", base: { scope: "document" }, steps: portionsToSteps(splitColon(rest.slice(1))), raw };
1066
+ }
1067
+ const portions = splitColon(rest);
1068
+ base = { scope: "current" };
1069
+ if (portions.length > 0 && portions[0] === "..") {
1070
+ base = { scope: "parent" };
1071
+ portions.shift();
1072
+ }
1073
+ return { kind: "pointer", base, steps: portionsToSteps(portions), raw };
1074
+ }
1075
+ function splitColon(s) {
1076
+ const out = [];
1077
+ let cur = "";
1078
+ let q = null;
1079
+ for (let i = 0; i < s.length; i++) {
1080
+ const c = s[i];
1081
+ if (q !== null) {
1082
+ cur += c;
1083
+ if (c === q) q = null;
1084
+ continue;
1085
+ }
1086
+ if (c === "\\" && i + 1 < s.length) {
1087
+ cur += c + s[i + 1];
1088
+ i++;
1089
+ continue;
1090
+ }
1091
+ if (c === "'" || c === '"') {
1092
+ q = c;
1093
+ cur += c;
1094
+ continue;
1095
+ }
1096
+ if (c === ":") {
1097
+ out.push(cur.trim());
1098
+ cur = "";
1099
+ continue;
1100
+ }
1101
+ cur += c;
1102
+ }
1103
+ out.push(cur.trim());
1104
+ return out.filter((p) => p !== "");
1105
+ }
1106
+ function portionsToSteps(portions) {
1107
+ const out = [];
1108
+ for (const p of portions) out.push(...portionToSteps(p));
1109
+ return out;
1110
+ }
1111
+ function portionToSteps(p) {
1112
+ if (p === "..") return [{ sel: "parent" }];
1113
+ let name = "";
1114
+ let i = 0;
1115
+ if (p[0] === "'" || p[0] === '"') {
1116
+ const q = p[0];
1117
+ i = 1;
1118
+ for (; i < p.length; i++) {
1119
+ if (p[i] === q) {
1120
+ if (q === "'" && p[i + 1] === "'") {
1121
+ name += q;
1122
+ i++;
1123
+ continue;
1124
+ }
1125
+ i++;
1126
+ break;
1127
+ }
1128
+ if (q === '"' && p[i] === "\\" && i + 1 < p.length) {
1129
+ name += p[i + 1];
1130
+ i++;
1131
+ continue;
1132
+ }
1133
+ name += p[i];
1134
+ }
1135
+ } else {
1136
+ for (; i < p.length; i++) {
1137
+ const c = p[i];
1138
+ if (c === "\\" && i + 1 < p.length) {
1139
+ name += p[i + 1];
1140
+ i++;
1141
+ continue;
1142
+ }
1143
+ if (c === "[") break;
1144
+ if (c === " " || c === " ") throw new SyntaxError(`pointer: a key containing a space must be quoted ("${p}")`);
1145
+ name += c;
1146
+ }
1147
+ }
1148
+ const steps = [];
1149
+ if (name !== "") steps.push({ sel: "key", name });
1150
+ while (i < p.length) {
1151
+ if (p[i] !== "[") throw new SyntaxError(`pointer: malformed portion "${p}"`);
1152
+ let j = i + 1;
1153
+ let digits = "";
1154
+ while (j < p.length && p[j] >= "0" && p[j] <= "9") {
1155
+ digits += p[j];
1156
+ j++;
1157
+ }
1158
+ if (digits === "" || p[j] !== "]") throw new SyntaxError(`pointer: malformed index in "${p}"`);
1159
+ steps.push({ sel: "index", n: Number.parseInt(digits, 10) });
1160
+ i = j + 1;
1161
+ }
1162
+ return steps;
1163
+ }
1164
+ function portionName(p) {
1165
+ const steps = portionToSteps(p);
1166
+ if (steps.length !== 1 || steps[0].sel !== "key") throw new SyntaxError(`pointer: bad authority portion "${p}"`);
1167
+ return steps[0].name;
1168
+ }
1169
+ function renderPointer(p, opts = {}) {
1170
+ const spaced = opts.spaced !== false;
1171
+ const sep2 = spaced ? ": " : ":";
1172
+ const toks = [];
1173
+ for (const st of p.steps) {
1174
+ if (st.sel === "parent") toks.push("..");
1175
+ else if (st.sel === "key") toks.push(colonSegment(st.name));
1176
+ else if (toks.length > 0 && !toks[toks.length - 1].endsWith("..")) toks[toks.length - 1] += `[${st.n}]`;
1177
+ else toks.push(`[${st.n}]`);
1178
+ }
1179
+ const body = toks.join(sep2);
1180
+ switch (p.base.scope) {
1181
+ case "current":
1182
+ return body;
1183
+ case "parent":
1184
+ return body === "" ? ".." : ".." + sep2 + body;
1185
+ case "document":
1186
+ return ":" + (body === "" ? "" : (spaced ? " " : "") + body);
1187
+ case "link": {
1188
+ const ladder = p.base.world === true ? ":::" : "::";
1189
+ const auth = colonSegment(p.base.authority);
1190
+ return ladder + (spaced ? " " : "") + auth + (body === "" ? "" : sep2 + body);
1191
+ }
1192
+ }
1193
+ }
1194
+ function colonSegment(name) {
1195
+ if (name === "..") return "\\.\\.";
1196
+ if (/[ \t]/.test(name) || /^['"]/.test(name)) return `'${name.replace(/'/g, "''")}'`;
1197
+ return name.replace(/[\\:[\]*&#~?!()<>=|]/g, (c) => "\\" + c);
1198
+ }
1199
+ function parsePath(path8) {
1200
+ const segs = splitSlash(path8);
1201
+ const out = [];
1202
+ for (let i = 0; i < segs.length; i++) {
1203
+ if (segs[i] === "") continue;
1204
+ out.push(...segToSteps(segs[i]));
1205
+ }
1206
+ return out;
1207
+ }
1208
+ function splitSlash(s) {
1209
+ const out = [];
1210
+ let cur = "";
1211
+ for (let i = 0; i < s.length; i++) {
1212
+ const c = s[i];
1213
+ if (c === "\\" && i + 1 < s.length) {
1214
+ cur += c + s[i + 1];
1215
+ i++;
1216
+ continue;
1217
+ }
1218
+ if (c === "/") {
1219
+ out.push(cur);
1220
+ cur = "";
1221
+ continue;
1222
+ }
1223
+ cur += c;
1224
+ }
1225
+ out.push(cur);
1226
+ return out;
1227
+ }
1228
+ function readUntilSlash(s) {
1229
+ let i = 0;
1230
+ let seg = "";
1231
+ for (; i < s.length; i++) {
1232
+ const c = s[i];
1233
+ if (c === "\\" && i + 1 < s.length) {
1234
+ seg += c + s[i + 1];
1235
+ i++;
1236
+ continue;
1237
+ }
1238
+ if (c === "/") break;
1239
+ seg += c;
1240
+ }
1241
+ return { seg, rest: s.slice(i) };
1242
+ }
1243
+ function segToSteps(seg) {
1244
+ if (seg === "..") return [{ sel: "parent" }];
1245
+ let i = 0;
1246
+ let name = "";
1247
+ for (; i < seg.length; i++) {
1248
+ const c = seg[i];
1249
+ if (c === "\\" && i + 1 < seg.length) {
1250
+ name += c + seg[i + 1];
1251
+ i++;
1252
+ continue;
1253
+ }
1254
+ if (c === "[") break;
1255
+ name += c;
1256
+ }
1257
+ const steps = [];
1258
+ if (name !== "") steps.push({ sel: "key", name: unescape(name) });
1259
+ while (i < seg.length) {
1260
+ if (seg[i] !== "[") throw new SyntaxError(`pointer: malformed segment "${seg}"`);
1261
+ let j = i + 1;
1262
+ let digits = "";
1263
+ while (j < seg.length && seg[j] >= "0" && seg[j] <= "9") {
1264
+ digits += seg[j];
1265
+ j++;
1266
+ }
1267
+ if (digits === "" || seg[j] !== "]") throw new SyntaxError(`pointer: malformed index in "${seg}"`);
1268
+ steps.push({ sel: "index", n: Number.parseInt(digits, 10) });
1269
+ i = j + 1;
1270
+ }
1271
+ return steps;
1272
+ }
1273
+ function unescape(s) {
1274
+ let out = "";
1275
+ for (let i = 0; i < s.length; i++) {
1276
+ if (s[i] === "\\" && i + 1 < s.length) {
1277
+ out += s[i + 1];
1278
+ i++;
1279
+ continue;
1280
+ }
1281
+ out += s[i];
1282
+ }
1283
+ return out;
1284
+ }
1285
+ function makeAnchor(body, fail) {
1286
+ let ordinal = false;
1287
+ if (body.endsWith("[]") && !body.endsWith("\\[]")) {
1288
+ ordinal = true;
1289
+ body = body.slice(0, -2);
1290
+ }
1291
+ const path8 = parsePointer(body);
1292
+ if (!ordinal) {
1293
+ const last = path8.steps[path8.steps.length - 1];
1294
+ if (last === void 0) fail('an anchor path needs a key segment (or a trailing "[]" for ordinal membership)');
1295
+ if (last.sel !== "key") fail('an anchor may not claim a position \u2014 use a trailing "[]" for ordinal membership');
1296
+ }
1297
+ return { path: path8, ...ordinal ? { ordinal: true } : {} };
1298
+ }
1299
+ function escapeSegment(name) {
1300
+ if (name === "..") return "\\.\\.";
1301
+ return name.replace(/[\\/:[\]*&#~?!()<>=|]/g, (c) => "\\" + c);
1302
+ }
1303
+
1304
+ // ../parser/ts/src/yamlover.ts
1305
+ function parseYamlover(src, uri = "<yamlover>") {
1306
+ const raw = [];
1307
+ const lineStarts = [];
1308
+ const sep2 = /\r\n|\r|\n/g;
1309
+ let at = 0;
1310
+ for (let m = sep2.exec(src); m !== null; m = sep2.exec(src)) {
1311
+ raw.push(src.slice(at, m.index));
1312
+ lineStarts.push(at);
1313
+ at = m.index + m[0].length;
1314
+ }
1315
+ raw.push(src.slice(at));
1316
+ lineStarts.push(at);
1317
+ const p = new Block(lex(raw), raw, uri, lineStarts);
1318
+ for (let k = 0; k < p.lines.length; k++) {
1319
+ const l = p.lines[k];
1320
+ if (l.indent === 0 && (l.text === "---" || l.text.startsWith("--- ") || l.text === "...")) {
1321
+ p.i = k;
1322
+ p.fail('multi-document streams ("---" / "...") are not yet supported (Phase 2c)');
1323
+ }
1324
+ }
1325
+ let rootSchema;
1326
+ const first = p.peek();
1327
+ if (first && /^!!<[^>]*>$/.test(first.text.trim())) {
1328
+ rootSchema = parseSchemaRef(first.text.trim().slice(3, -1), { block: p, lineN: first.n, col: first.indent + 3 });
1329
+ p.i++;
1330
+ }
1331
+ let root = p.node(0) ?? nul();
1332
+ if (isPointer(root)) throw new SyntaxError(`yamlover: a top-level pointer is not allowed in ${uri}`);
1333
+ if (p.i < p.lines.length && p.peek().indent === 0) {
1334
+ root = p.mergeCont(root, p.container(0));
1335
+ }
1336
+ if (p.i < p.lines.length) p.fail("unexpected content");
1337
+ if (rootSchema !== void 0) root.meta = { ...root.meta, schema: rootSchema };
1338
+ return { root, source: { concrete: "yamlover", uri } };
1339
+ }
1340
+ function lex(raw) {
1341
+ const out = [];
1342
+ for (let n = 0; n < raw.length; n++) {
1343
+ const line = raw[n];
1344
+ let indent = 0;
1345
+ while (indent < line.length && line[indent] === " ") indent++;
1346
+ const content = stripComment(line.slice(indent)).replace(/\s+$/, "");
1347
+ if (content === "") continue;
1348
+ out.push({ indent, text: content, n });
1349
+ }
1350
+ return out;
1351
+ }
1352
+ function stripComment(s) {
1353
+ let inS = false;
1354
+ let inD = false;
1355
+ for (let i = 0; i < s.length; i++) {
1356
+ const c = s[i];
1357
+ if (inD && c === "\\") {
1358
+ i++;
1359
+ continue;
1360
+ }
1361
+ if (c === "'" && !inD) inS = !inS;
1362
+ else if (c === '"' && !inS) inD = !inD;
1363
+ else if (c === "#" && !inS && !inD && (i === 0 || s[i - 1] === " " || s[i - 1] === " ")) {
1364
+ return s.slice(0, i);
1365
+ }
1366
+ }
1367
+ return s;
1368
+ }
1369
+ var Block = class {
1370
+ lines;
1371
+ raw;
1372
+ // all source lines (for block scalars, which keep blanks and `#`)
1373
+ uri;
1374
+ // source path/id, surfaced in parse-error messages
1375
+ lineStarts;
1376
+ // absolute offset of each raw line's first character
1377
+ i = 0;
1378
+ constructor(lines, raw, uri = "<yamlover>", lineStarts = []) {
1379
+ this.lines = lines;
1380
+ this.raw = raw;
1381
+ this.uri = uri;
1382
+ this.lineStarts = lineStarts;
1383
+ }
1384
+ /** Absolute span of `len` chars starting at column `col` of raw line `lineN`. Valid
1385
+ * because every Line.text is a prefix-aligned slice of its raw line (lex strips only
1386
+ * the comment/trailing-ws SUFFIX), so tracked columns are raw-line columns. */
1387
+ spanAt(lineN, col, len) {
1388
+ const start = (this.lineStarts[lineN] ?? 0) + col;
1389
+ return { uri: this.uri, start, end: start + len };
1390
+ }
1391
+ peek() {
1392
+ return this.lines[this.i];
1393
+ }
1394
+ /** Read ONE `&` anchor token at the head of `text` (URIs.md §`&`): `&path`, `&path[]`,
1395
+ * or the quoted form `&'path with spaces'`. Plain tokens run to unescaped whitespace.
1396
+ * Returns the Anchor plus the remaining text/column. */
1397
+ anchorToken(text, lineN, col) {
1398
+ let body;
1399
+ let tokenLen;
1400
+ if (text[1] === "'" || text[1] === '"') {
1401
+ const q = text[1];
1402
+ let j = 2;
1403
+ while (j < text.length) {
1404
+ if (q === '"' && text[j] === "\\") {
1405
+ j += 2;
1406
+ continue;
1407
+ }
1408
+ if (text[j] === q) {
1409
+ if (q === "'" && text[j + 1] === "'") {
1410
+ j += 2;
1411
+ continue;
1412
+ }
1413
+ break;
1414
+ }
1415
+ j++;
1416
+ }
1417
+ if (j >= text.length) this.fail("unterminated quoted anchor path");
1418
+ body = quotedScalar(text.slice(1, j + 1)).value;
1419
+ tokenLen = j + 1;
1420
+ } else if (hasSeparatorColon(text.slice(1))) {
1421
+ body = text.slice(1).trim();
1422
+ tokenLen = text.length;
1423
+ } else {
1424
+ let j = 1;
1425
+ while (j < text.length && text[j] !== " " && text[j] !== " ") {
1426
+ if (text[j] === "\\" && j + 1 < text.length) j++;
1427
+ j++;
1428
+ }
1429
+ body = text.slice(1, j);
1430
+ tokenLen = j;
1431
+ }
1432
+ const anchor = makeAnchor(body, (m) => this.fail(m));
1433
+ anchor.path.span = this.spanAt(lineN, col, tokenLen);
1434
+ const a = adv(text, tokenLen, col);
1435
+ return { anchor, rest: a.rest, col: a.col };
1436
+ }
1437
+ /** Attach anchors to a (non-pointer) node's meta, appending to any it already has. */
1438
+ attachAnchors(value, anchors) {
1439
+ if (anchors.length === 0) return;
1440
+ if (isPointer(value)) this.fail("cannot anchor a pointer");
1441
+ value.meta = { ...value.meta, anchors: [...value.meta?.anchors ?? [], ...anchors] };
1442
+ }
1443
+ /** Merge a container() CONTINUATION into a node that already holds its value: entries
1444
+ * and anchors append. A continuation carrying its own value line is a second value —
1445
+ * an error — except the bare anchors-only case (a synthesized null with no raw). */
1446
+ mergeCont(v, cont) {
1447
+ const bare = cont.kind === "scalar" && cont.value === null && cont.raw === "" && (cont.entries?.length ?? 0) === 0;
1448
+ if (cont.kind === "scalar" && !bare) this.fail("a node may carry at most one scalar value line");
1449
+ const entries = [...v.entries ?? [], ...cont.entries ?? []];
1450
+ const out = {
1451
+ ...v,
1452
+ ...entries.length > 0 ? { entries, array: cont.kind === "mapping" ? cont.array : v.array } : {}
1453
+ };
1454
+ this.attachAnchors(out, cont.meta?.anchors ?? []);
1455
+ return out;
1456
+ }
1457
+ fail(msg) {
1458
+ const l = this.lines[this.i] ?? this.lines[this.i - 1];
1459
+ const where = l ? `${this.uri}:${l.n + 1}` : this.uri;
1460
+ const ctx = l ? `
1461
+ ${l.n + 1} | ${this.raw[l.n]}` : "";
1462
+ throw new SyntaxError(`yamlover: ${msg} at ${where}${ctx}`);
1463
+ }
1464
+ /** Parse a block node whose content lives at a column >= minIndent (null if none). */
1465
+ node(minIndent) {
1466
+ const l = this.peek();
1467
+ if (!l || l.indent < minIndent) return null;
1468
+ if (/^!!(mix|omni|set)(?=\s|$)/.test(l.text)) {
1469
+ this.i++;
1470
+ return this.valueAfter(l.text, l.indent - 1, l.n, l.indent);
1471
+ }
1472
+ if (isSeqLine(l.text) || isBackSeqLine(l.text) || l.text.startsWith("&") || splitKV(l.text)) {
1473
+ return this.container(l.indent);
1474
+ }
1475
+ this.i++;
1476
+ let v = this.valueInline(
1477
+ l.text,
1478
+ l.indent,
1479
+ /*allowBlock*/
1480
+ true,
1481
+ l.n,
1482
+ l.indent
1483
+ );
1484
+ const nxt = this.peek();
1485
+ if (nxt && nxt.indent === l.indent && nxt.indent >= minIndent && !isPointer(v) && v.kind === "scalar") {
1486
+ v = this.mergeCont(v, this.container(l.indent));
1487
+ }
1488
+ return v;
1489
+ }
1490
+ /**
1491
+ * One ordered container at `indent` — yamlover's single container model. Entries keep
1492
+ * their order (→ integer keys [0],[1],…); each MAY also carry a string key. So keyless
1493
+ * (`- value`) and keyed (`key: value`) entries can be MIXED in one node ("partially
1494
+ * ordered, partially keyed"). `array` is just a projection hint: true iff all-keyless.
1495
+ *
1496
+ * `keylessOnly` is for the same-indent sequence-under-a-key case, where a keyed line at
1497
+ * this indent is a SIBLING of the outer key, not a member — so we stop at it.
1498
+ */
1499
+ container(indent, keylessOnly = false) {
1500
+ const entries = [];
1501
+ const anchors = [];
1502
+ let self;
1503
+ for (; ; ) {
1504
+ const l = this.peek();
1505
+ if (!l || l.indent !== indent) break;
1506
+ let value;
1507
+ let entry;
1508
+ if (l.text.startsWith("&")) {
1509
+ if (keylessOnly) break;
1510
+ this.i++;
1511
+ let { rest, col } = { rest: l.text, col: l.indent };
1512
+ while (rest.startsWith("&")) {
1513
+ const r = this.anchorToken(rest, l.n, col);
1514
+ anchors.push(r.anchor);
1515
+ ({ rest, col } = r);
1516
+ }
1517
+ if (rest !== "") {
1518
+ const v = this.valueInline(
1519
+ rest,
1520
+ indent,
1521
+ /*allowBlock*/
1522
+ false,
1523
+ l.n,
1524
+ col
1525
+ );
1526
+ if (isPointer(v) || v.kind !== "scalar") this.fail("only a scalar value may share a line with an own-line anchor");
1527
+ if (self !== void 0) this.fail("a node may carry at most one scalar value line");
1528
+ self = v;
1529
+ }
1530
+ continue;
1531
+ }
1532
+ if (isSeqLine(l.text)) {
1533
+ this.i++;
1534
+ const afterDash = l.text.slice(1);
1535
+ const lead = afterDash.length - afterDash.trimStart().length;
1536
+ const contentCol = l.indent + 1 + lead;
1537
+ const rest = afterDash.trim();
1538
+ if (rest === "") {
1539
+ value = this.node(indent + 1) ?? nul();
1540
+ } else if (!/^(!!<|\*|&)/.test(rest) && splitKV(rest)) {
1541
+ this.i--;
1542
+ this.lines[this.i] = { indent: contentCol, text: rest, n: l.n };
1543
+ value = this.container(contentCol);
1544
+ } else {
1545
+ value = this.valueAfter(rest, indent, l.n, contentCol);
1546
+ }
1547
+ entry = { key: null, edge: isPointer(value) ? "ref" : "contain", value };
1548
+ } else if (isBackSeqLine(l.text)) {
1549
+ if (keylessOnly) break;
1550
+ this.i++;
1551
+ const a = adv(l.text, 2, l.indent);
1552
+ const rest = a.rest;
1553
+ if (!rest.startsWith("*")) this.fail('a "~-" entry needs a pointer value (the container that holds this node)');
1554
+ const ptr = parsePointer(unquoteIfQuoted(rest.slice(1)));
1555
+ ptr.span = this.spanAt(l.n, a.col, rest.length);
1556
+ value = ptr;
1557
+ entry = { key: null, edge: "back", value };
1558
+ } else {
1559
+ if (keylessOnly) break;
1560
+ if (/^~[ \t]/.test(l.text)) this.fail('the "~" sigil must sit tight against the key or "-" marker (write "~key:" or "~-")');
1561
+ const kv = splitKV(l.text);
1562
+ if (!kv) {
1563
+ this.i++;
1564
+ const v = this.valueInline(
1565
+ l.text,
1566
+ indent,
1567
+ /*allowBlock*/
1568
+ false,
1569
+ l.n,
1570
+ l.indent
1571
+ );
1572
+ if (isPointer(v) || v.kind !== "scalar") this.fail("unexpected content (expected an entry, an anchor, or a scalar value line)");
1573
+ if (self !== void 0) this.fail("a node may carry at most one scalar value line");
1574
+ self = v;
1575
+ continue;
1576
+ }
1577
+ this.i++;
1578
+ let key = kv.key;
1579
+ let back = false;
1580
+ if (key.startsWith("~")) {
1581
+ back = true;
1582
+ key = key.slice(1);
1583
+ }
1584
+ value = this.valueAfter(kv.rest, indent, l.n, l.indent + kv.restCol);
1585
+ entry = { key: unquoteKey(key), edge: back ? "back" : isPointer(value) ? "ref" : "contain", value };
1586
+ }
1587
+ entries.push(entry);
1588
+ }
1589
+ const owned = entries.filter((e2) => e2.edge !== "back");
1590
+ const array = owned.length > 0 && owned.every((e2) => e2.key === null);
1591
+ const node = self !== void 0 ? { ...self, entries, array } : entries.length === 0 ? nul() : { kind: "mapping", entries, array };
1592
+ this.attachAnchors(node, anchors);
1593
+ return node;
1594
+ }
1595
+ /** The value after `key:` or `- ` (inline `rest`, with a possible deeper block).
1596
+ * `col` = the column of `rest` in raw line `srcLineN` (span tracking). */
1597
+ valueAfter(rest, parentIndent, srcLineN, col) {
1598
+ ({ rest, col } = adv(rest, 0, col));
1599
+ let schema;
1600
+ if (rest.startsWith("!!<")) {
1601
+ const close = rest.indexOf(">");
1602
+ if (close < 0) this.fail('unterminated "!!<\u2026>" schema tag');
1603
+ schema = parseSchemaRef(rest.slice(3, close), { block: this, lineN: srcLineN, col: col + 3 });
1604
+ ({ rest, col } = adv(rest, close + 1, col));
1605
+ }
1606
+ let typeTag;
1607
+ const tag = /^!!(mix|omni|set)(?=\s|$)/.exec(rest);
1608
+ if (tag) {
1609
+ typeTag = tag[1];
1610
+ ({ rest, col } = adv(rest, tag[0].length, col));
1611
+ }
1612
+ const anchors = [];
1613
+ while (rest.startsWith("&")) {
1614
+ const r = this.anchorToken(rest, srcLineN, col);
1615
+ anchors.push(r.anchor);
1616
+ ({ rest, col } = r);
1617
+ }
1618
+ let value;
1619
+ if (/^[|>][+-]?\d*$/.test(rest)) {
1620
+ value = this.blockScalar(rest, parentIndent, srcLineN);
1621
+ value = this.attachFields(value, parentIndent);
1622
+ } else if (rest === "") {
1623
+ const nxt = this.peek();
1624
+ if (nxt && nxt.indent === parentIndent && isSeqLine(nxt.text)) {
1625
+ value = this.container(
1626
+ parentIndent,
1627
+ /*keylessOnly*/
1628
+ true
1629
+ );
1630
+ } else {
1631
+ value = this.node(parentIndent + 1) ?? nul();
1632
+ }
1633
+ } else {
1634
+ value = this.valueInline(
1635
+ rest,
1636
+ parentIndent,
1637
+ /*allowBlock*/
1638
+ false,
1639
+ srcLineN,
1640
+ col
1641
+ );
1642
+ value = this.attachFields(value, parentIndent);
1643
+ }
1644
+ this.attachAnchors(value, anchors);
1645
+ if (typeTag === "set" && !isPointer(value)) value.meta = { ...value.meta, set: true };
1646
+ if (schema !== void 0 && !isPointer(value)) value.meta = { ...value.meta, schema };
1647
+ return value;
1648
+ }
1649
+ /** A node = value + fields: a scalar value (inline OR a block scalar) followed by a DEEPER
1650
+ * block attaches that block's (positional and/or keyed) entries onto the scalar — one node
1651
+ * carrying its value and its fields (the `!!omni` shape). Non-scalars/pointers pass through. */
1652
+ attachFields(value, parentIndent) {
1653
+ const nxt = this.peek();
1654
+ if (nxt && nxt.indent > parentIndent && !isPointer(value) && value.kind !== "blob") {
1655
+ return this.mergeCont(value, this.container(nxt.indent));
1656
+ }
1657
+ return value;
1658
+ }
1659
+ /** A block scalar (`|` literal / `>` folded, with `-`/`+` chomping) introduced by the
1660
+ * indicator on line `srcLineN`. Reads RAW lines (blanks + `#` are significant), de-indents
1661
+ * by the block's own indent, and advances past the consumed lines. */
1662
+ blockScalar(indicator, parentIndent, srcLineN) {
1663
+ const folded = indicator[0] === ">";
1664
+ let chomp = "clip";
1665
+ for (const ch of indicator.slice(1)) {
1666
+ if (ch === "-") chomp = "strip";
1667
+ else if (ch === "+") chomp = "keep";
1668
+ }
1669
+ const lines = [];
1670
+ let blockIndent = -1;
1671
+ let lastN = srcLineN;
1672
+ for (let n = srcLineN + 1; n < this.raw.length; n++) {
1673
+ const r = this.raw[n];
1674
+ let ind = 0;
1675
+ while (ind < r.length && r[ind] === " ") ind++;
1676
+ if (ind >= r.length) {
1677
+ lines.push("");
1678
+ lastN = n;
1679
+ continue;
1680
+ }
1681
+ if (ind <= parentIndent) break;
1682
+ if (blockIndent >= 0 && ind < blockIndent) break;
1683
+ if (blockIndent < 0) blockIndent = ind;
1684
+ lines.push(r.slice(blockIndent));
1685
+ lastN = n;
1686
+ }
1687
+ while (this.peek() && this.peek().n <= lastN) this.i++;
1688
+ let last = -1;
1689
+ for (let i = 0; i < lines.length; i++) if (lines[i] !== "") last = i;
1690
+ const core = lines.slice(0, last + 1);
1691
+ let body = folded ? foldLines(core) : core.join("\n");
1692
+ if (chomp === "keep") body += "\n".repeat(lines.length - (last + 1) + (last >= 0 ? 1 : 0));
1693
+ else if (chomp === "clip" && last >= 0) body += "\n";
1694
+ return { kind: "scalar", value: body, raw: body };
1695
+ }
1696
+ /** Parse a single-line inline value: flow, pointer, anchor, quoted or plain scalar.
1697
+ * `col` = the column of `text` in raw line `lineN` (span tracking). */
1698
+ valueInline(text, parentIndent, allowBlock, lineN, col) {
1699
+ ({ rest: text, col } = adv(text, 0, col));
1700
+ const c = text[0];
1701
+ if (c === "{" || c === "[") {
1702
+ return new Flow(text, this.uri, (this.lineStarts[lineN] ?? 0) + col).parse();
1703
+ }
1704
+ if (c === "*") {
1705
+ const p = parsePointer(unquoteIfQuoted(text.slice(1)));
1706
+ p.span = this.spanAt(lineN, col, text.length);
1707
+ return p;
1708
+ }
1709
+ if (c === "&") {
1710
+ const anchors = [];
1711
+ let rest = text;
1712
+ let acol = col;
1713
+ while (rest.startsWith("&")) {
1714
+ const r = this.anchorToken(rest, lineN, acol);
1715
+ anchors.push(r.anchor);
1716
+ ({ rest, col: acol } = r);
1717
+ }
1718
+ const v = rest === "" ? allowBlock ? this.node(parentIndent + 1) ?? nul() : nul() : this.valueInline(rest, parentIndent, allowBlock, lineN, acol);
1719
+ this.attachAnchors(v, anchors);
1720
+ return v;
1721
+ }
1722
+ if (c === "'" || c === '"') return quotedScalar(text);
1723
+ return plainScalar(text);
1724
+ }
1725
+ };
1726
+ var Flow = class {
1727
+ s;
1728
+ i = 0;
1729
+ uri;
1730
+ base;
1731
+ // absolute offset of s[0] in the source (span tracking)
1732
+ constructor(s, uri = "<flow>", base = 0) {
1733
+ this.s = s;
1734
+ this.uri = uri;
1735
+ this.base = base;
1736
+ }
1737
+ fail(msg) {
1738
+ throw new SyntaxError(`yamlover (flow): ${msg} at offset ${this.i}`);
1739
+ }
1740
+ parse() {
1741
+ this.ws();
1742
+ const v = this.value();
1743
+ this.ws();
1744
+ if (this.i < this.s.length) this.fail("trailing characters in flow value");
1745
+ return v;
1746
+ }
1747
+ ws() {
1748
+ while (this.i < this.s.length && " ".includes(this.s[this.i])) this.i++;
1749
+ }
1750
+ value() {
1751
+ const c = this.s[this.i];
1752
+ if (c === "{") return this.map();
1753
+ if (c === "[") return this.seq();
1754
+ if (c === "*") {
1755
+ const start = this.i;
1756
+ this.i++;
1757
+ let txt;
1758
+ if (this.s[this.i] === "'" || this.s[this.i] === '"') {
1759
+ txt = this.quoted().value;
1760
+ } else {
1761
+ const st = this.i;
1762
+ let depth = 0;
1763
+ while (this.i < this.s.length) {
1764
+ const ch = this.s[this.i];
1765
+ if (ch === "\\" && this.i + 1 < this.s.length) {
1766
+ this.i += 2;
1767
+ continue;
1768
+ }
1769
+ if (ch === "[") depth++;
1770
+ else if (ch === "]") {
1771
+ if (depth === 0) break;
1772
+ depth--;
1773
+ } else if (ch === "," || ch === "}") break;
1774
+ this.i++;
1775
+ }
1776
+ txt = this.s.slice(st, this.i).trim();
1777
+ }
1778
+ const p = parsePointer(txt);
1779
+ p.span = { uri: this.uri, start: this.base + start, end: this.base + this.i };
1780
+ return p;
1781
+ }
1782
+ if (c === "&") {
1783
+ const start = this.i;
1784
+ this.i++;
1785
+ let body;
1786
+ if (this.s[this.i] === "'" || this.s[this.i] === '"') {
1787
+ body = this.quoted().value;
1788
+ } else {
1789
+ const st = this.i;
1790
+ while (this.i < this.s.length && !" ,]}".includes(this.s[this.i])) {
1791
+ if (this.s[this.i] === "\\" && this.i + 1 < this.s.length) this.i++;
1792
+ this.i++;
1793
+ }
1794
+ body = this.s.slice(st, this.i);
1795
+ }
1796
+ const anchor = makeAnchor(body, (m) => this.fail(m));
1797
+ anchor.path.span = { uri: this.uri, start: this.base + start, end: this.base + this.i };
1798
+ this.ws();
1799
+ const v = this.value();
1800
+ if (isPointer(v)) this.fail("cannot anchor a pointer");
1801
+ v.meta = { ...v.meta, anchors: [...v.meta?.anchors ?? [], anchor] };
1802
+ return v;
1803
+ }
1804
+ if (c === "'" || c === '"') return this.quoted();
1805
+ const tok = this.readPlain();
1806
+ if (tok === "") this.fail("expected a value");
1807
+ return plainScalar(tok);
1808
+ }
1809
+ map() {
1810
+ this.i++;
1811
+ const entries = [];
1812
+ for (; ; ) {
1813
+ this.ws();
1814
+ if (this.s[this.i] === "}") {
1815
+ this.i++;
1816
+ break;
1817
+ }
1818
+ if (this.i >= this.s.length) this.fail("unterminated flow map");
1819
+ let back = false;
1820
+ if (this.s[this.i] === "~") {
1821
+ back = true;
1822
+ this.i++;
1823
+ }
1824
+ const key = this.s[this.i] === "'" || this.s[this.i] === '"' ? this.quoted().value : unquoteKey(this.readPlain(":,}"));
1825
+ this.ws();
1826
+ if (this.s[this.i] !== ":") this.fail('expected ":" in flow map');
1827
+ this.i++;
1828
+ this.ws();
1829
+ const v = this.value();
1830
+ entries.push({ key, edge: back ? "back" : isPointer(v) ? "ref" : "contain", value: v });
1831
+ this.ws();
1832
+ if (this.s[this.i] === ",") {
1833
+ this.i++;
1834
+ continue;
1835
+ }
1836
+ if (this.s[this.i] === "}") {
1837
+ this.i++;
1838
+ break;
1839
+ }
1840
+ this.fail('expected "," or "}"');
1841
+ }
1842
+ return { kind: "mapping", entries, array: false };
1843
+ }
1844
+ seq() {
1845
+ this.i++;
1846
+ const entries = [];
1847
+ for (; ; ) {
1848
+ this.ws();
1849
+ if (this.s[this.i] === "]") {
1850
+ this.i++;
1851
+ break;
1852
+ }
1853
+ if (this.i >= this.s.length) this.fail("unterminated flow seq");
1854
+ const v = this.value();
1855
+ entries.push({ key: null, edge: isPointer(v) ? "ref" : "contain", value: v });
1856
+ this.ws();
1857
+ if (this.s[this.i] === ",") {
1858
+ this.i++;
1859
+ continue;
1860
+ }
1861
+ if (this.s[this.i] === "]") {
1862
+ this.i++;
1863
+ break;
1864
+ }
1865
+ this.fail('expected "," or "]"');
1866
+ }
1867
+ return { kind: "mapping", entries, array: true };
1868
+ }
1869
+ quoted() {
1870
+ const start = this.i;
1871
+ const q = this.s[this.i];
1872
+ this.i++;
1873
+ while (this.i < this.s.length) {
1874
+ const c = this.s[this.i];
1875
+ if (q === '"' && c === "\\") {
1876
+ this.i += 2;
1877
+ continue;
1878
+ }
1879
+ if (q === "'" && c === "'" && this.s[this.i + 1] === "'") {
1880
+ this.i += 2;
1881
+ continue;
1882
+ }
1883
+ this.i++;
1884
+ if (c === q) break;
1885
+ }
1886
+ return quotedScalar(this.s.slice(start, this.i));
1887
+ }
1888
+ /** Read a plain scalar up to a stop char (default flow stops). */
1889
+ readPlain(stop = ",:[]{} ") {
1890
+ const start = this.i;
1891
+ while (this.i < this.s.length && !stop.includes(this.s[this.i])) this.i++;
1892
+ return this.s.slice(start, this.i).trim();
1893
+ }
1894
+ };
1895
+ function isSeqLine(text) {
1896
+ return text === "-" || text.startsWith("- ");
1897
+ }
1898
+ function isBackSeqLine(text) {
1899
+ return text === "~-" || text.startsWith("~- ");
1900
+ }
1901
+ function splitKV(text) {
1902
+ let inS = false;
1903
+ let inD = false;
1904
+ for (let i = 0; i < text.length; i++) {
1905
+ const c = text[i];
1906
+ if (c === "'" && !inD) inS = !inS;
1907
+ else if (c === '"' && !inS) inD = !inD;
1908
+ else if (c === ":" && !inS && !inD) {
1909
+ const next = text[i + 1];
1910
+ if (next === void 0 || next === " " || next === " ") {
1911
+ const after = text.slice(i + 1);
1912
+ const lead = after.length - after.trimStart().length;
1913
+ return { key: text.slice(0, i).trim(), rest: after.trim(), restCol: i + 1 + lead };
1914
+ }
1915
+ }
1916
+ }
1917
+ return null;
1918
+ }
1919
+ function adv(s, k, col) {
1920
+ const sliced = s.slice(k);
1921
+ const lead = sliced.length - sliced.trimStart().length;
1922
+ return { rest: sliced.trim(), col: col + k + lead };
1923
+ }
1924
+ function parseSchemaRef(src, at) {
1925
+ const lead = src.length - src.trimStart().length;
1926
+ src = src.trim();
1927
+ if (src.startsWith("*")) {
1928
+ const p = parsePointer(src.slice(1));
1929
+ if (at !== void 0) p.span = at.block.spanAt(at.lineN, at.col + lead, src.length);
1930
+ return p;
1931
+ }
1932
+ return parseYamlover(src).root;
1933
+ }
1934
+ function foldLines(lines) {
1935
+ let out = "";
1936
+ for (let i = 0; i < lines.length; i++) {
1937
+ if (i > 0) out += lines[i] === "" || lines[i - 1] === "" ? "\n" : " ";
1938
+ out += lines[i];
1939
+ }
1940
+ return out;
1941
+ }
1942
+ function unquoteIfQuoted(s) {
1943
+ s = s.trim();
1944
+ if ((s[0] === "'" || s[0] === '"') && s[s.length - 1] === s[0]) return quotedScalar(s).value;
1945
+ return s;
1946
+ }
1947
+ function hasSeparatorColon(s) {
1948
+ let q = null;
1949
+ for (let i = 0; i < s.length; i++) {
1950
+ const c = s[i];
1951
+ if (q !== null) {
1952
+ if (c === q) q = null;
1953
+ continue;
1954
+ }
1955
+ if (c === "\\") {
1956
+ i++;
1957
+ continue;
1958
+ }
1959
+ if (c === "'" || c === '"') {
1960
+ q = c;
1961
+ continue;
1962
+ }
1963
+ if (c === ":") return true;
1964
+ }
1965
+ return false;
1966
+ }
1967
+ function unquoteKey(key) {
1968
+ key = key.trim();
1969
+ if ((key[0] === "'" || key[0] === '"') && key[key.length - 1] === key[0]) return quotedScalar(key).value;
1970
+ return backslashUnescape(key);
1971
+ }
1972
+ function quotedScalar(text) {
1973
+ const q = text[0];
1974
+ const body = text.slice(1, text.length - 1);
1975
+ if (q === "'") return { kind: "scalar", value: body.replace(/''/g, "'"), raw: text };
1976
+ let out = "";
1977
+ for (let i = 0; i < body.length; i++) {
1978
+ if (body[i] === "\\") {
1979
+ const c = body[++i];
1980
+ switch (c) {
1981
+ case "n":
1982
+ out += "\n";
1983
+ break;
1984
+ case "t":
1985
+ out += " ";
1986
+ break;
1987
+ case "r":
1988
+ out += "\r";
1989
+ break;
1990
+ case '"':
1991
+ out += '"';
1992
+ break;
1993
+ case "\\":
1994
+ out += "\\";
1995
+ break;
1996
+ case "/":
1997
+ out += "/";
1998
+ break;
1999
+ case "b":
2000
+ out += "\b";
2001
+ break;
2002
+ case "f":
2003
+ out += "\f";
2004
+ break;
2005
+ case "0":
2006
+ out += "\0";
2007
+ break;
2008
+ case "u":
2009
+ out += String.fromCharCode(parseInt(body.slice(i + 1, i + 5), 16));
2010
+ i += 4;
2011
+ break;
2012
+ case "x":
2013
+ out += String.fromCharCode(parseInt(body.slice(i + 1, i + 3), 16));
2014
+ i += 2;
2015
+ break;
2016
+ default:
2017
+ out += c ?? "";
2018
+ }
2019
+ } else out += body[i];
2020
+ }
2021
+ return { kind: "scalar", value: out, raw: text };
2022
+ }
2023
+ function plainScalar(text) {
2024
+ const t2 = text.trim();
2025
+ if (t2 === "" || t2 === "~" || t2 === "null" || t2 === "Null" || t2 === "NULL") return { kind: "scalar", value: null, raw: text };
2026
+ if (t2 === "true" || t2 === "True" || t2 === "TRUE") return { kind: "scalar", value: true, raw: text };
2027
+ if (t2 === "false" || t2 === "False" || t2 === "FALSE") return { kind: "scalar", value: false, raw: text };
2028
+ if (/^[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(t2)) return { kind: "scalar", value: Number(t2), raw: text };
2029
+ if (/^[-+]?0x[0-9a-fA-F]+$/.test(t2)) return { kind: "scalar", value: Number(t2), raw: text };
2030
+ return { kind: "scalar", value: t2, raw: text };
2031
+ }
2032
+ function backslashUnescape(s) {
2033
+ let out = "";
2034
+ for (let i = 0; i < s.length; i++) {
2035
+ if (s[i] === "\\" && i + 1 < s.length) {
2036
+ out += s[i + 1];
2037
+ i++;
2038
+ continue;
2039
+ }
2040
+ out += s[i];
2041
+ }
2042
+ return out;
2043
+ }
2044
+ function nul() {
2045
+ return { kind: "scalar", value: null, raw: "" };
2046
+ }
2047
+
2048
+ // ../parser/ts/src/json5p.ts
2049
+ var WS = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\v", "\f", "\xA0", "\uFEFF"]);
2050
+ function parseJson5p(src, uri = "<json5p>") {
2051
+ const p = new Parser(src, uri);
2052
+ p.ws();
2053
+ const root = p.value();
2054
+ p.ws();
2055
+ if (p.i < src.length) p.fail("trailing characters");
2056
+ if (isPointer(root)) return p.fail("a top-level pointer is not allowed");
2057
+ return { root, source: { concrete: "json5p", uri } };
2058
+ }
2059
+ var Parser = class {
2060
+ src;
2061
+ uri;
2062
+ i = 0;
2063
+ constructor(src, uri) {
2064
+ this.src = src;
2065
+ this.uri = uri;
2066
+ }
2067
+ fail(msg) {
2068
+ throw new SyntaxError(`json5p: ${msg} at offset ${this.i}`);
2069
+ }
2070
+ peek() {
2071
+ return this.src[this.i];
2072
+ }
2073
+ eof() {
2074
+ return this.i >= this.src.length;
2075
+ }
2076
+ ws() {
2077
+ for (; ; ) {
2078
+ const c = this.src[this.i];
2079
+ if (c === void 0) return;
2080
+ if (WS.has(c)) {
2081
+ this.i++;
2082
+ continue;
2083
+ }
2084
+ if (c === "/" && this.src[this.i + 1] === "/") {
2085
+ this.i += 2;
2086
+ while (this.i < this.src.length && this.src[this.i] !== "\n" && this.src[this.i] !== "\r") this.i++;
2087
+ continue;
2088
+ }
2089
+ if (c === "/" && this.src[this.i + 1] === "*") {
2090
+ this.i += 2;
2091
+ while (this.i < this.src.length && !(this.src[this.i] === "*" && this.src[this.i + 1] === "/")) this.i++;
2092
+ if (this.i >= this.src.length) this.fail("unterminated block comment");
2093
+ this.i += 2;
2094
+ continue;
2095
+ }
2096
+ return;
2097
+ }
2098
+ }
2099
+ value() {
2100
+ const c = this.peek();
2101
+ if (c === void 0) this.fail("unexpected end of input");
2102
+ if (c === "{") return this.object();
2103
+ if (c === "[") return this.array();
2104
+ if (c === "*") return this.pointer();
2105
+ if (c === "&") return this.anchored();
2106
+ if (c === '"' || c === "'") return this.string();
2107
+ return this.numberOrKeyword();
2108
+ }
2109
+ object() {
2110
+ this.i++;
2111
+ const entries = [];
2112
+ for (; ; ) {
2113
+ this.ws();
2114
+ const c = this.peek();
2115
+ if (c === "}") {
2116
+ this.i++;
2117
+ break;
2118
+ }
2119
+ if (c === void 0) this.fail("unterminated object");
2120
+ let back = false;
2121
+ if (this.peek() === "~") {
2122
+ back = true;
2123
+ this.i++;
2124
+ }
2125
+ if (back && this.peek() === "*") {
2126
+ entries.push({ key: null, edge: "back", value: this.pointer() });
2127
+ } else {
2128
+ const key = this.key();
2129
+ this.ws();
2130
+ if (this.peek() !== ":") this.fail('expected ":" after key');
2131
+ this.i++;
2132
+ this.ws();
2133
+ const v = this.value();
2134
+ entries.push(makeEntry(key, back, v));
2135
+ }
2136
+ this.ws();
2137
+ const n = this.peek();
2138
+ if (n === ",") {
2139
+ this.i++;
2140
+ continue;
2141
+ }
2142
+ if (n === "}") {
2143
+ this.i++;
2144
+ break;
2145
+ }
2146
+ this.fail('expected "," or "}"');
2147
+ }
2148
+ return { kind: "mapping", entries, array: false };
2149
+ }
2150
+ array() {
2151
+ this.i++;
2152
+ const entries = [];
2153
+ for (; ; ) {
2154
+ this.ws();
2155
+ const c = this.peek();
2156
+ if (c === "]") {
2157
+ this.i++;
2158
+ break;
2159
+ }
2160
+ if (c === void 0) this.fail("unterminated array");
2161
+ if (c === "~") {
2162
+ this.i++;
2163
+ if (this.peek() !== "*") this.fail('expected a pointer after "~" (keyless back member)');
2164
+ entries.push({ key: null, edge: "back", value: this.pointer() });
2165
+ } else {
2166
+ entries.push(makeEntry(null, false, this.value()));
2167
+ }
2168
+ this.ws();
2169
+ const n = this.peek();
2170
+ if (n === ",") {
2171
+ this.i++;
2172
+ continue;
2173
+ }
2174
+ if (n === "]") {
2175
+ this.i++;
2176
+ break;
2177
+ }
2178
+ this.fail('expected "," or "]"');
2179
+ }
2180
+ return { kind: "mapping", entries, array: true };
2181
+ }
2182
+ key() {
2183
+ const c = this.peek();
2184
+ if (c === '"' || c === "'") return this.string().value;
2185
+ return this.identifier();
2186
+ }
2187
+ identifier() {
2188
+ const start = this.i;
2189
+ const c = this.peek();
2190
+ if (c === void 0 || !isIdStart(c)) this.fail("expected an identifier key");
2191
+ this.i++;
2192
+ while (!this.eof() && isIdPart(this.src[this.i])) this.i++;
2193
+ return this.src.slice(start, this.i);
2194
+ }
2195
+ pointer() {
2196
+ const start = this.i;
2197
+ this.i++;
2198
+ const c = this.peek();
2199
+ if (c !== '"' && c !== "'") this.fail('a json5p pointer must be a quoted string after "*"');
2200
+ const s = this.string();
2201
+ const p = parsePointer(s.value);
2202
+ p.span = { uri: this.uri, start, end: this.i };
2203
+ return p;
2204
+ }
2205
+ anchored() {
2206
+ const start = this.i;
2207
+ this.i++;
2208
+ const c = this.peek();
2209
+ const body = c === '"' || c === "'" ? this.string().value : this.identifier();
2210
+ const anchor = makeAnchor(body, (m) => this.fail(m));
2211
+ anchor.path.span = { uri: this.uri, start, end: this.i };
2212
+ this.ws();
2213
+ const v = this.value();
2214
+ if (isPointer(v)) this.fail("cannot anchor a pointer");
2215
+ v.meta = { ...v.meta, anchors: [anchor, ...v.meta?.anchors ?? []] };
2216
+ return v;
2217
+ }
2218
+ string() {
2219
+ const quote = this.src[this.i];
2220
+ const start = this.i;
2221
+ this.i++;
2222
+ let out = "";
2223
+ for (; ; ) {
2224
+ if (this.eof()) this.fail("unterminated string");
2225
+ const c = this.src[this.i];
2226
+ if (c === quote) {
2227
+ this.i++;
2228
+ break;
2229
+ }
2230
+ if (c === "\\") {
2231
+ out += this.escape();
2232
+ continue;
2233
+ }
2234
+ out += c;
2235
+ this.i++;
2236
+ }
2237
+ return { kind: "scalar", value: out, raw: this.src.slice(start, this.i) };
2238
+ }
2239
+ escape() {
2240
+ this.i++;
2241
+ const c = this.src[this.i];
2242
+ if (c === void 0) this.fail("bad escape sequence");
2243
+ this.i++;
2244
+ switch (c) {
2245
+ case '"':
2246
+ return '"';
2247
+ case "'":
2248
+ return "'";
2249
+ case "\\":
2250
+ return "\\";
2251
+ case "/":
2252
+ return "/";
2253
+ case "b":
2254
+ return "\b";
2255
+ case "f":
2256
+ return "\f";
2257
+ case "n":
2258
+ return "\n";
2259
+ case "r":
2260
+ return "\r";
2261
+ case "t":
2262
+ return " ";
2263
+ case "v":
2264
+ return "\v";
2265
+ case "0":
2266
+ return "\0";
2267
+ case "x": {
2268
+ const h = this.src.slice(this.i, this.i + 2);
2269
+ this.i += 2;
2270
+ return String.fromCharCode(parseInt(h, 16));
2271
+ }
2272
+ case "u": {
2273
+ const h = this.src.slice(this.i, this.i + 4);
2274
+ this.i += 4;
2275
+ return String.fromCharCode(parseInt(h, 16));
2276
+ }
2277
+ case "\n":
2278
+ return "";
2279
+ case "\r":
2280
+ if (this.src[this.i] === "\n") this.i++;
2281
+ return "";
2282
+ default:
2283
+ return c;
2284
+ }
2285
+ }
2286
+ numberOrKeyword() {
2287
+ const start = this.i;
2288
+ while (!this.eof() && !isDelim(this.src[this.i])) this.i++;
2289
+ const tok = this.src.slice(start, this.i);
2290
+ if (tok === "") this.fail(`unexpected character ${JSON.stringify(this.src[this.i])}`);
2291
+ if (tok === "true") return scalar(true, tok);
2292
+ if (tok === "false") return scalar(false, tok);
2293
+ if (tok === "null") return scalar(null, tok);
2294
+ const n = json5number(tok);
2295
+ if (n === void 0) this.fail(`invalid token "${tok}"`);
2296
+ return scalar(n, tok);
2297
+ }
2298
+ };
2299
+ function makeEntry(key, back, v) {
2300
+ const edge = back ? "back" : isPointer(v) ? "ref" : "contain";
2301
+ return { key, edge, value: v };
2302
+ }
2303
+ function scalar(value, raw) {
2304
+ return { kind: "scalar", value, raw };
2305
+ }
2306
+ function isIdStart(c) {
2307
+ return /[A-Za-z_$]/.test(c) || c.charCodeAt(0) > 127;
2308
+ }
2309
+ function isIdPart(c) {
2310
+ return /[A-Za-z0-9_$]/.test(c) || c.charCodeAt(0) > 127;
2311
+ }
2312
+ function isDelim(c) {
2313
+ return WS.has(c) || c === "," || c === ":" || c === "{" || c === "}" || c === "[" || c === "]" || c === '"' || c === "'" || c === "/";
2314
+ }
2315
+ function json5number(tok) {
2316
+ if (/^[+-]?0[xX][0-9a-fA-F]+$/.test(tok)) return Number(tok);
2317
+ if (/^[+-]?Infinity$/.test(tok)) return tok[0] === "-" ? -Infinity : Infinity;
2318
+ if (/^[+-]?NaN$/.test(tok)) return NaN;
2319
+ if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(tok)) return Number(tok);
2320
+ return void 0;
2321
+ }
2322
+
2323
+ // ../engine/ts/src/walk.ts
2324
+ var { h64Raw, create64 } = await e();
2325
+ var hashBytes = (bytes) => "xxh64:" + h64Raw(bytes).toString(16).padStart(16, "0");
2326
+ var YAMLOVER_DIR = ".yamlover";
2327
+ var MAX_TEXT_BYTES = 1 << 20;
2328
+ var MAX_DOC_BYTES = 64 << 20;
2329
+ var HASH_INLINE_MAX = 1 << 20;
2330
+ var HASH_CHUNK = 8 << 20;
2331
+ function walkTree(absDir, opts = {}) {
2332
+ const g = walkTreeGen(absDir, opts);
2333
+ let r = g.next();
2334
+ while (!r.done) r = g.next();
2335
+ return r.value;
2336
+ }
2337
+ async function walkTreeAsync(absDir, opts = {}) {
2338
+ const g = walkTreeGen(absDir, opts);
2339
+ const every = Math.max(1, opts.yieldEvery ?? 50);
2340
+ let r = g.next();
2341
+ while (!r.done) {
2342
+ opts.onProgress?.({ done: r.value.done, message: r.value.path });
2343
+ if (r.value.done % every === 0) await yieldLoop();
2344
+ r = g.next();
2345
+ }
2346
+ return r.value;
2347
+ }
2348
+ function* walkTreeGen(absDir, opts = {}) {
2349
+ const ctx = { root: path.resolve(absDir), opts, files: /* @__PURE__ */ new Map(), count: 0 };
2350
+ const root = yield* dirNode(ctx.root, ctx);
2351
+ root.meta = { ...root.meta, documentRoot: true };
2352
+ const defsRoot = findDefsRoot(absDir);
2353
+ const defsDir = path.join(defsRoot, "$defs");
2354
+ const arrayRoot = root.array || (root.entries?.length ? root.entries.every((e2) => e2.key === null) : false);
2355
+ if (!arrayRoot && fs.existsSync(defsDir) && root.entries && !root.entries.some((e2) => e2.key === "yamlover")) {
2356
+ const shared = [{ key: "$defs", edge: "contain", value: yield* dirNode(defsDir, ctx) }];
2357
+ const tagsDir = path.join(defsRoot, "tags");
2358
+ if (fs.existsSync(tagsDir)) shared.push({ key: "tags", edge: "contain", value: yield* dirNode(tagsDir, ctx) });
2359
+ root.entries.push({ key: "yamlover", edge: "contain", value: { kind: "mapping", entries: shared, array: false } });
2360
+ }
2361
+ applySchemas(root, defsRoot);
2362
+ return {
2363
+ doc: { root, source: { concrete: "directory", uri: absDir } },
2364
+ files: [...ctx.files.values()]
2365
+ };
2366
+ }
2367
+ function reindex(store, absDir, opts = {}) {
2368
+ const prev = store.stale ? /* @__PURE__ */ new Map() : store.manifest();
2369
+ const { doc, files } = walkTree(absDir, { ...opts, cache: opts.cache ?? manifestCache(prev) });
2370
+ store.indexDocument(doc, files);
2371
+ return diffManifest(prev, files);
2372
+ }
2373
+ async function reindexAsync(store, absDir, opts = {}) {
2374
+ const prev = store.stale ? /* @__PURE__ */ new Map() : store.manifest();
2375
+ const onProgress = opts.onProgress;
2376
+ const total = onProgress ? await countChildren(path.resolve(absDir), opts) : void 0;
2377
+ const { doc, files } = await walkTreeAsync(absDir, {
2378
+ ...opts,
2379
+ cache: opts.cache ?? manifestCache(prev),
2380
+ onProgress: onProgress && ((p) => onProgress({ ...p, total }))
2381
+ });
2382
+ onProgress?.({ done: total ?? files.length, total, message: "writing index\u2026" });
2383
+ await yieldLoop();
2384
+ store.indexDocument(doc, files);
2385
+ return diffManifest(prev, files);
2386
+ }
2387
+ function manifestCache(prev) {
2388
+ return (rel, size, mtimeMs) => {
2389
+ const r = prev.get(rel);
2390
+ return r && r.size === size && r.mtimeMs === mtimeMs ? r.hash : null;
2391
+ };
2392
+ }
2393
+ async function countChildren(absRoot, opts) {
2394
+ let n = 0;
2395
+ const visit = async (dir) => {
2396
+ let entries;
2397
+ try {
2398
+ entries = await fs.promises.readdir(dir, { withFileTypes: true });
2399
+ } catch {
2400
+ return;
2401
+ }
2402
+ for (const e2 of entries) {
2403
+ if (e2.name === YAMLOVER_DIR || e2.name.startsWith(".")) continue;
2404
+ const abs = path.join(dir, e2.name);
2405
+ if (opts.ignore?.(abs)) continue;
2406
+ n++;
2407
+ const isDir = e2.isDirectory() || e2.isSymbolicLink() && (await fs.promises.stat(abs).catch(() => null))?.isDirectory();
2408
+ if (isDir) await visit(abs);
2409
+ }
2410
+ };
2411
+ await visit(absRoot);
2412
+ return n;
2413
+ }
2414
+ function diffManifest(prev, files) {
2415
+ let added = [], removed = [];
2416
+ const changed = [];
2417
+ const current = new Map(files.map((f) => [f.path, f]));
2418
+ for (const f of files) {
2419
+ const old = prev.get(f.path);
2420
+ if (!old) added.push(f.path);
2421
+ else if (old.size !== f.size || old.mtimeMs !== f.mtimeMs) changed.push(f.path);
2422
+ }
2423
+ for (const p of prev.keys()) if (!current.has(p)) removed.push(p);
2424
+ const moved = [];
2425
+ if (removed.length > 0 && added.length > 0) {
2426
+ const matched = /* @__PURE__ */ new Set();
2427
+ const tiers = [(f) => f.hash, (f) => `${f.size}:${f.mtimeMs}`];
2428
+ for (const key of tiers) {
2429
+ const outs = groupBy(removed.filter((p) => !matched.has(p)).map((p) => prev.get(p)), key);
2430
+ const ins = groupBy(added.filter((p) => !matched.has(p)).map((p) => current.get(p)), key);
2431
+ for (const [k, o] of outs) {
2432
+ const i = ins.get(k);
2433
+ if (o.length !== 1 || i?.length !== 1) continue;
2434
+ const [from, to] = [o[0], i[0]];
2435
+ if (from.hash && to.hash && from.hash !== to.hash) continue;
2436
+ moved.push({ from: from.path, to: to.path });
2437
+ matched.add(from.path);
2438
+ matched.add(to.path);
2439
+ }
2440
+ }
2441
+ if (matched.size > 0) {
2442
+ added = added.filter((p) => !matched.has(p));
2443
+ removed = removed.filter((p) => !matched.has(p));
2444
+ }
2445
+ }
2446
+ return { added, changed, removed, moved };
2447
+ }
2448
+ function groupBy(list, key) {
2449
+ const m = /* @__PURE__ */ new Map();
2450
+ for (const f of list) {
2451
+ const k = key(f);
2452
+ if (k != null) m.set(k, [...m.get(k) ?? [], f]);
2453
+ }
2454
+ return m;
2455
+ }
2456
+ async function hashFileAsync(abs, onChunk) {
2457
+ const fh = await fs.promises.open(abs, "r");
2458
+ try {
2459
+ const hasher = create64();
2460
+ const buf = Buffer.alloc(HASH_CHUNK);
2461
+ let done = 0;
2462
+ for (; ; ) {
2463
+ const { bytesRead } = await fh.read(buf, 0, buf.length);
2464
+ if (bytesRead === 0) break;
2465
+ hasher.update(bytesRead === buf.length ? buf : buf.subarray(0, bytesRead));
2466
+ done += bytesRead;
2467
+ onChunk?.(done);
2468
+ }
2469
+ return "xxh64:" + hasher.digest().toString(16).padStart(16, "0");
2470
+ } finally {
2471
+ await fh.close();
2472
+ }
2473
+ }
2474
+ function record(ctx, abs, hash, size, mtimeMs) {
2475
+ const rel = path.relative(ctx.root, abs).split(path.sep).join("/");
2476
+ if (rel.startsWith("..")) return;
2477
+ ctx.files.set(rel, { path: rel, hash, size, mtimeMs });
2478
+ }
2479
+ function readTracked(ctx, abs) {
2480
+ const stat = fs.statSync(abs);
2481
+ const bytes = fs.readFileSync(abs);
2482
+ record(ctx, abs, hashBytes(bytes), stat.size, stat.mtimeMs);
2483
+ return bytes;
2484
+ }
2485
+ function loadMeta(dir, ctx) {
2486
+ const file = path.join(dir, YAMLOVER_DIR, "meta.yamlover");
2487
+ if (!fs.existsSync(file)) return {};
2488
+ try {
2489
+ const plain = toPlain(parseYamlover(readTracked(ctx, file).toString("utf8"), file).root);
2490
+ const props = plain?.properties ?? {};
2491
+ return props && typeof props === "object" ? props : {};
2492
+ } catch {
2493
+ return {};
2494
+ }
2495
+ }
2496
+ function* dirNode(dir, ctx) {
2497
+ const meta = loadMeta(dir, ctx);
2498
+ const names = fs.readdirSync(dir).filter((n) => n !== YAMLOVER_DIR && !n.startsWith(".")).filter((n) => !ctx.opts.ignore?.(path.join(dir, n))).sort();
2499
+ const entries = [];
2500
+ for (const name of names) {
2501
+ const abs = path.join(dir, name);
2502
+ const child = yield* childNode(abs, meta[name], ctx);
2503
+ entries.push({ key: name, edge: "contain", value: child });
2504
+ yield { done: ++ctx.count, path: path.relative(ctx.root, abs).split(path.sep).join("/") };
2505
+ }
2506
+ const node = { kind: "mapping", entries, array: false };
2507
+ return applyMeta(applyBody(dir, node, ctx), meta);
2508
+ }
2509
+ function* childNode(abs, m, ctx) {
2510
+ const stat = fs.statSync(abs);
2511
+ if (stat.isDirectory()) return yield* dirNode(abs, ctx);
2512
+ const ext = path.extname(abs).toLowerCase();
2513
+ const fmt = m?.format ?? EXT_FORMAT[ext] ?? null;
2514
+ if (m?.type === "binary") return blob(abs, fmt ?? "application/octet-stream", ctx);
2515
+ if (fmt && (DOC_FORMATS[fmt] || TEXT_FORMATS.has(fmt))) {
2516
+ if (stat.size > MAX_DOC_BYTES) return blob(abs, fmt, ctx);
2517
+ if (DOC_FORMATS[fmt]) return parsedDoc(abs, DOC_FORMATS[fmt], ctx);
2518
+ return textScalar(abs, fmt, ctx);
2519
+ }
2520
+ if (fmt) return blob(abs, fmt, ctx);
2521
+ if (looksBinary(abs)) return blob(abs, "application/octet-stream", ctx);
2522
+ return parsedScalar(abs, ext, ctx);
2523
+ }
2524
+ function applyMeta(node, meta) {
2525
+ for (const e2 of node.entries ?? []) {
2526
+ if (e2.key == null || isPointer(e2.value)) continue;
2527
+ const m = meta[e2.key];
2528
+ if (!m) continue;
2529
+ if (m.uniqueItems) e2.value = { ...e2.value, meta: { ...e2.value.meta, set: true } };
2530
+ if (e2.value.kind === "blob") continue;
2531
+ if (m.format && !e2.value.meta?.schema) e2.value = { ...e2.value, meta: { ...e2.value.meta, schema: inlineFormat(m.format) } };
2532
+ }
2533
+ return node;
2534
+ }
2535
+ function blob(abs, format, ctx) {
2536
+ const stat = fs.statSync(abs);
2537
+ const rel = path.relative(ctx.root, abs).split(path.sep).join("/");
2538
+ const cached = ctx.opts.cache?.(rel, stat.size, stat.mtimeMs) ?? null;
2539
+ if (cached) {
2540
+ record(ctx, abs, cached, stat.size, stat.mtimeMs);
2541
+ return { kind: "blob", format, contentHash: cached, size: stat.size };
2542
+ }
2543
+ const inlineMax = ctx.opts.hashInlineMax ?? HASH_INLINE_MAX;
2544
+ const contentHash = stat.size <= inlineMax ? hashBytes(fs.readFileSync(abs)) : null;
2545
+ record(ctx, abs, contentHash, stat.size, stat.mtimeMs);
2546
+ return { kind: "blob", format, contentHash, size: stat.size };
2547
+ }
2548
+ function textScalar(abs, format, ctx) {
2549
+ const text = readTracked(ctx, abs).toString("utf8");
2550
+ return { kind: "scalar", value: text, raw: text, meta: { schema: inlineFormat(format) } };
2551
+ }
2552
+ function parsedScalar(abs, ext, ctx) {
2553
+ return parsedDoc(abs, ext === ".json" || ext === ".json5" || ext === ".json5p" ? "json5p" : "yamlover", ctx);
2554
+ }
2555
+ function parsedDoc(abs, lang, ctx) {
2556
+ const text = readTracked(ctx, abs).toString("utf8");
2557
+ try {
2558
+ const doc = lang === "json5p" ? parseJson5p(text, abs) : parseYamlover(text, abs);
2559
+ const root = doc.root;
2560
+ root.meta = { ...root.meta, documentRoot: true };
2561
+ return root;
2562
+ } catch {
2563
+ return { kind: "scalar", value: text, raw: text };
2564
+ }
2565
+ }
2566
+ function findDefsRoot(dir) {
2567
+ let d = path.resolve(dir);
2568
+ for (; ; ) {
2569
+ if (fs.existsSync(path.join(d, "$defs"))) return d;
2570
+ const up = path.dirname(d);
2571
+ if (up === d) return path.resolve(dir);
2572
+ d = up;
2573
+ }
2574
+ }
2575
+ function applySchemas(root, defsRoot) {
2576
+ const cache = /* @__PURE__ */ new Map();
2577
+ const loadDef = (name) => {
2578
+ if (!cache.has(name)) {
2579
+ const defFile = path.join(defsRoot, "$defs", name);
2580
+ try {
2581
+ cache.set(name, parseYamlover(fs.readFileSync(defFile, "utf8"), defFile).root);
2582
+ } catch {
2583
+ cache.set(name, null);
2584
+ }
2585
+ }
2586
+ return cache.get(name);
2587
+ };
2588
+ const field = (n, key) => n.entries?.find((e2) => e2.key === key)?.value ?? null;
2589
+ const str = (n, key) => {
2590
+ const v = field(n, key);
2591
+ return v && !isPointer(v) && v.kind === "scalar" && v.value != null ? String(v.value) : null;
2592
+ };
2593
+ const hasFormat = (inst) => {
2594
+ if (inst.kind === "blob") return true;
2595
+ const s = inst.meta?.schema;
2596
+ return !!s && !isPointer(s) && s.kind === "mapping" && !!field(s, "format");
2597
+ };
2598
+ const apply = (inst, schema, depth) => {
2599
+ if (depth > 64 || isPointer(inst)) return;
2600
+ let name = null;
2601
+ let s;
2602
+ if (isPointer(schema)) {
2603
+ const last = schema.steps[schema.steps.length - 1];
2604
+ name = last?.sel === "key" ? last.name : null;
2605
+ s = name ? loadDef(name) : null;
2606
+ } else s = schema;
2607
+ if (!s || isPointer(s)) return;
2608
+ const fmt = str(s, "format") ?? (name && str(s, "type") === "object" ? `x-yamlover-${name}` : null);
2609
+ if (fmt && !hasFormat(inst)) inst.meta = { ...inst.meta, schema: inlineFormat(fmt) };
2610
+ const stype = str(s, "type");
2611
+ if (stype === "object" || stype === "variant" || stype === "mixed") {
2612
+ const props = field(s, "properties");
2613
+ const addl = field(s, "additionalProperties");
2614
+ for (const e2 of inst.entries ?? []) {
2615
+ if (e2.key == null || isPointer(e2.value)) continue;
2616
+ const declared = props && !isPointer(props) ? field(props, e2.key) : null;
2617
+ const sub = declared ?? addl;
2618
+ if (sub) apply(e2.value, sub, depth + 1);
2619
+ }
2620
+ } else if (stype === "array") {
2621
+ const items = field(s, "items");
2622
+ if (items) {
2623
+ for (const e2 of inst.entries ?? []) if (!isPointer(e2.value)) apply(e2.value, items, depth + 1);
2624
+ }
2625
+ }
2626
+ };
2627
+ const walk = (node) => {
2628
+ if (node.meta?.schema) apply(node, node.meta.schema, 0);
2629
+ for (const e2 of node.entries ?? []) if (!isPointer(e2.value)) walk(e2.value);
2630
+ };
2631
+ walk(root);
2632
+ }
2633
+ function inlineFormat(format) {
2634
+ return { kind: "mapping", entries: [{ key: "format", edge: "contain", value: { kind: "scalar", value: format, raw: format } }], array: false };
2635
+ }
2636
+ function applyBody(dir, node, ctx) {
2637
+ const file = path.join(dir, YAMLOVER_DIR, "body.yamlover");
2638
+ if (!fs.existsSync(file)) return node;
2639
+ const bodyDoc = parseYamlover(readTracked(ctx, file).toString("utf8"), file);
2640
+ const body = bodyDoc.root;
2641
+ if (body.kind !== "mapping" && body.kind !== "scalar" || !body.entries) return node;
2642
+ const meta = { ...node.meta, ...body.meta, documentRoot: true };
2643
+ if (body.kind === "mapping" && (body.array || body.entries.length > 0 && body.entries.every((e2) => e2.key === null))) {
2644
+ const byKey = new Map(node.entries.map((e2) => [e2.key, e2]));
2645
+ const ordered = [];
2646
+ for (const e2 of body.entries) {
2647
+ const targetKey = isPointer(e2.value) ? pointerLeafKey(e2.value) : null;
2648
+ const hit = targetKey != null ? byKey.get(targetKey) : null;
2649
+ if (hit) {
2650
+ ordered.push(hit);
2651
+ byKey.delete(targetKey);
2652
+ } else ordered.push(e2);
2653
+ }
2654
+ for (const e2 of node.entries) if (e2.key != null && byKey.has(e2.key)) ordered.push(e2);
2655
+ return { kind: "mapping", entries: ordered, array: true, meta };
2656
+ }
2657
+ const merged = new Map(node.entries.map((e2) => [e2.key, e2]));
2658
+ const order = node.entries.map((e2) => e2.key);
2659
+ for (const e2 of body.entries) {
2660
+ const existing = merged.get(e2.key);
2661
+ if (!existing) {
2662
+ order.push(e2.key);
2663
+ merged.set(e2.key, e2);
2664
+ } else merged.set(e2.key, augmentEntry(existing, e2));
2665
+ }
2666
+ const entries = order.map((k) => merged.get(k));
2667
+ if (body.kind === "scalar") return { kind: "scalar", value: body.value, raw: body.raw, entries, array: false, meta };
2668
+ return { kind: "mapping", entries, array: false, meta };
2669
+ }
2670
+ function augmentEntry(base, overlay) {
2671
+ if (isPointer(base.value) || isPointer(overlay.value)) return overlay;
2672
+ const b = base.value, o = overlay.value;
2673
+ const value = {
2674
+ ...b,
2675
+ entries: o.entries ?? b.entries,
2676
+ array: o.entries ? o.array : b.array,
2677
+ meta: b.meta || o.meta ? { ...b.meta, ...o.meta } : void 0
2678
+ };
2679
+ return { key: base.key, edge: "contain", value };
2680
+ }
2681
+ function pointerLeafKey(v) {
2682
+ if (!isPointer(v)) return null;
2683
+ const last = v.steps[v.steps.length - 1];
2684
+ if (last?.sel === "key") return last.name;
2685
+ if (v.steps.length === 0 && v.base.scope === "current") return v.raw;
2686
+ return null;
2687
+ }
2688
+ function looksBinary(abs) {
2689
+ try {
2690
+ if (fs.statSync(abs).size > MAX_TEXT_BYTES) return true;
2691
+ const fd = fs.openSync(abs, "r");
2692
+ try {
2693
+ const buf = Buffer.alloc(4096);
2694
+ const n = fs.readSync(fd, buf, 0, buf.length, 0);
2695
+ return buf.subarray(0, n).includes(0);
2696
+ } finally {
2697
+ fs.closeSync(fd);
2698
+ }
2699
+ } catch {
2700
+ return true;
2701
+ }
2702
+ }
2703
+ var EXT_FORMAT = {
2704
+ ".png": "image/png",
2705
+ ".jpg": "image/jpeg",
2706
+ ".jpeg": "image/jpeg",
2707
+ ".gif": "image/gif",
2708
+ ".webp": "image/webp",
2709
+ ".avif": "image/avif",
2710
+ ".bmp": "image/bmp",
2711
+ ".ico": "image/x-icon",
2712
+ ".svg": "image/svg+xml",
2713
+ ".pdf": "application/pdf",
2714
+ ".djvu": "image/vnd.djvu",
2715
+ ".djv": "image/vnd.djvu",
2716
+ ".psd": "image/vnd.adobe.photoshop",
2717
+ ".psb": "image/vnd.adobe.photoshop",
2718
+ ".tif": "image/tiff",
2719
+ ".tiff": "image/tiff",
2720
+ ".heic": "image/heic",
2721
+ ".heif": "image/heic",
2722
+ ".fb2": "application/x-fictionbook+xml",
2723
+ ".epub": "application/epub+zip",
2724
+ ".html": "text/html",
2725
+ ".htm": "text/html",
2726
+ ".md": "text/markdown",
2727
+ ".markdown": "text/markdown",
2728
+ ".adoc": "text/asciidoc",
2729
+ ".asciidoc": "text/asciidoc",
2730
+ ".asc": "text/asciidoc",
2731
+ ".csv": "text/csv",
2732
+ ".tsv": "text/tab-separated-values",
2733
+ ".rtf": "application/rtf",
2734
+ ".doc": "application/msword",
2735
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
2736
+ ".xls": "application/vnd.ms-excel",
2737
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
2738
+ ".kml": "application/vnd.google-earth.kml+xml",
2739
+ ".kmz": "application/vnd.google-earth.kmz",
2740
+ ".puml": "text/x-plantuml",
2741
+ ".plantuml": "text/x-plantuml",
2742
+ ".iuml": "text/x-plantuml",
2743
+ ".pu": "text/x-plantuml",
2744
+ // Plain text kept as RAW BYTES (text/plain is deliberately NOT in TEXT_FORMATS), so the
2745
+ // client decodes it under a chosen encoding (CP866/Win-1251/KOI8-R/UTF-8) — legacy
2746
+ // Cyrillic .txt files are common — rather than the server fixing UTF-8.
2747
+ ".txt": "text/plain",
2748
+ ".text": "text/plain",
2749
+ ".log": "text/plain",
2750
+ ".ini": "text/plain"
2751
+ };
2752
+ var TEXT_FORMATS = /* @__PURE__ */ new Set(["text/markdown", "text/asciidoc", "text/x-plantuml", "text/csv", "text/tab-separated-values"]);
2753
+ var DOC_FORMATS = {
2754
+ "yamlover": "yamlover",
2755
+ "yaml": "yamlover",
2756
+ "yamlover/meta": "yamlover",
2757
+ "yaml/meta": "yamlover",
2758
+ "json": "json5p",
2759
+ "json5": "json5p",
2760
+ "json5p": "json5p",
2761
+ "json/meta": "json5p",
2762
+ "json5p/meta": "json5p",
2763
+ "json/schema": "json5p"
2764
+ };
2765
+
2766
+ // ../engine/ts/src/rewrite.ts
2767
+ import * as path2 from "node:path";
2768
+
2769
+ // ../parser/ts/src/serialize-yamlover.ts
2770
+ function pointerToken(raw) {
2771
+ if (raw !== raw.trim()) return `*'${raw.replace(/'/g, "''")}'`;
2772
+ return "*" + raw;
2773
+ }
2774
+ function anchorToken(body) {
2775
+ if (/^['"]/.test(body) || hasUnquotedSpace(body)) return `&'${body.replace(/'/g, "''")}'`;
2776
+ return "&" + body;
2777
+ }
2778
+ function hasUnquotedSpace(s) {
2779
+ let q = null;
2780
+ for (let i = 0; i < s.length; i++) {
2781
+ const c = s[i];
2782
+ if (q !== null) {
2783
+ if (c === q) q = null;
2784
+ continue;
2785
+ }
2786
+ if (c === "\\") {
2787
+ i++;
2788
+ continue;
2789
+ }
2790
+ if (c === "'" || c === '"') {
2791
+ q = c;
2792
+ continue;
2793
+ }
2794
+ if (c === " " || c === " ") return true;
2795
+ }
2796
+ return false;
2797
+ }
2798
+
2799
+ // ../engine/ts/src/rewrite.ts
2800
+ function under(p, x) {
2801
+ if (x === ":") return true;
2802
+ return p === x || p.startsWith(x + ":") || p.startsWith(x + "[");
2803
+ }
2804
+ function planRewrites(doc, edges, oldStore, newStore, opts = {}) {
2805
+ const mapPath = (p) => under(p, oldStore) ? newStore + p.slice(oldStore.length) : p;
2806
+ const plan = { edits: /* @__PURE__ */ new Map(), rewritten: [], unrewritten: [] };
2807
+ for (const e2 of edges) {
2808
+ if (e2.target.kind !== "node" || !under(e2.target.path, oldStore)) continue;
2809
+ const miss = (reason) => {
2810
+ plan.unrewritten.push({ file: e2.ptr.span?.uri ?? "<unknown>", from: e2.from, raw: e2.raw, reason });
2811
+ };
2812
+ if (e2.anchor) {
2813
+ const span2 = e2.ptr.span;
2814
+ if (!span2) {
2815
+ miss("anchor has no source span");
2816
+ continue;
2817
+ }
2818
+ if (opts.root !== void 0 && path2.relative(opts.root, span2.uri).startsWith("..")) {
2819
+ miss("source file is outside the served root (grafted)");
2820
+ continue;
2821
+ }
2822
+ const holder2 = mapPath(e2.holder);
2823
+ const docRoot2 = mapPath(e2.docRoot);
2824
+ const container = mapPath(e2.target.path);
2825
+ let cRaw = null;
2826
+ switch (e2.ptr.base.scope) {
2827
+ case "link":
2828
+ cRaw = "//" + renderSuffix("/", container).replace(/^\//, "");
2829
+ break;
2830
+ case "document":
2831
+ cRaw = under(container, docRoot2) ? docForm(docRoot2, container) : null;
2832
+ break;
2833
+ case "current": {
2834
+ const p = parentOf(holder2);
2835
+ if (p !== null && under(container, p)) cRaw = renderSuffix(p, container).replace(/^\//, "");
2836
+ else if (under(container, docRoot2)) cRaw = docForm(docRoot2, container);
2837
+ break;
2838
+ }
2839
+ case "parent": {
2840
+ const p1 = parentOf(holder2);
2841
+ const p2 = p1 === null ? null : parentOf(p1);
2842
+ if (p2 !== null && under(container, p2)) cRaw = ".." + suffixAfter(p2, container);
2843
+ else if (under(container, docRoot2)) cRaw = docForm(docRoot2, container);
2844
+ break;
2845
+ }
2846
+ }
2847
+ if (cRaw === null) {
2848
+ miss("anchor container left the holder's document");
2849
+ continue;
2850
+ }
2851
+ let newBody = e2.label != null ? (cRaw === "" ? "" : cRaw === "/" ? "/" : cRaw + "/") + escapeSegment(e2.label) : cRaw + "[]";
2852
+ const tail = newBody.endsWith("[]") ? "[]" : "";
2853
+ newBody = matchStyle(newBody.slice(0, newBody.length - tail.length), e2.raw) + tail;
2854
+ if (newBody === e2.raw.slice(1)) continue;
2855
+ const tok = isJson5pUri(span2.uri) ? json5pAnchorToken(newBody) : anchorToken(newBody);
2856
+ const list2 = plan.edits.get(span2.uri) ?? [];
2857
+ list2.push({ start: span2.start, end: span2.end, text: tok });
2858
+ plan.edits.set(span2.uri, list2);
2859
+ plan.rewritten.push({ file: span2.uri, from: e2.from, oldRaw: e2.raw, newRaw: "&" + newBody });
2860
+ continue;
2861
+ }
2862
+ if (under(e2.holder, oldStore) && (e2.ptr.base.scope === "current" || e2.ptr.base.scope === "document" && under(e2.docRoot, oldStore))) {
2863
+ continue;
2864
+ }
2865
+ const span = e2.ptr.span;
2866
+ if (!span) {
2867
+ miss("pointer has no source span");
2868
+ continue;
2869
+ }
2870
+ if (opts.root !== void 0 && path2.relative(opts.root, span.uri).startsWith("..")) {
2871
+ miss("source file is outside the served root (grafted)");
2872
+ continue;
2873
+ }
2874
+ const holder = mapPath(e2.holder);
2875
+ const docRoot = mapPath(e2.docRoot);
2876
+ const target = mapPath(e2.target.path);
2877
+ let newRaw = null;
2878
+ switch (e2.ptr.base.scope) {
2879
+ case "link":
2880
+ newRaw = "//" + renderSuffix("/", target).replace(/^\//, "");
2881
+ break;
2882
+ case "document":
2883
+ newRaw = under(target, docRoot) ? docForm(docRoot, target) : null;
2884
+ break;
2885
+ case "current":
2886
+ if (under(target, holder) && target !== holder) newRaw = relForm(holder, target);
2887
+ else if (under(target, docRoot)) newRaw = docForm(docRoot, target);
2888
+ break;
2889
+ case "parent": {
2890
+ const p = parentOf(holder);
2891
+ if (p !== null && under(target, p)) newRaw = ".." + suffixAfter(p, target);
2892
+ else if (under(target, docRoot)) newRaw = docForm(docRoot, target);
2893
+ break;
2894
+ }
2895
+ }
2896
+ if (newRaw === null) {
2897
+ miss("target left the holder's document");
2898
+ continue;
2899
+ }
2900
+ newRaw = matchStyle(newRaw, e2.raw);
2901
+ if (newRaw === e2.raw) continue;
2902
+ const token = isJson5pUri(span.uri) ? json5pToken(newRaw) : pointerToken(newRaw);
2903
+ const list = plan.edits.get(span.uri) ?? [];
2904
+ list.push({ start: span.start, end: span.end, text: token });
2905
+ plan.edits.set(span.uri, list);
2906
+ plan.rewritten.push({ file: span.uri, from: e2.from, oldRaw: e2.raw, newRaw });
2907
+ }
2908
+ return plan;
2909
+ }
2910
+ function applyEdits(text, edits) {
2911
+ const sorted = [...edits].sort((a, b) => b.start - a.start);
2912
+ for (let i = 1; i < sorted.length; i++) {
2913
+ if (sorted[i].end > sorted[i - 1].start) {
2914
+ throw new Error(`overlapping edits at ${sorted[i].start}..${sorted[i].end} and ${sorted[i - 1].start}..`);
2915
+ }
2916
+ }
2917
+ let out = text;
2918
+ for (const e2 of sorted) out = out.slice(0, e2.start) + e2.text + out.slice(e2.end);
2919
+ return out;
2920
+ }
2921
+ function nominalPath(doc, e2) {
2922
+ if (e2.anchor) return null;
2923
+ let base;
2924
+ switch (e2.ptr.base.scope) {
2925
+ case "link":
2926
+ base = ":" + e2.ptr.base.authority;
2927
+ break;
2928
+ case "document":
2929
+ base = e2.docRoot;
2930
+ break;
2931
+ case "current":
2932
+ base = e2.holder;
2933
+ break;
2934
+ case "parent":
2935
+ base = parentOf(e2.holder) ?? "/";
2936
+ break;
2937
+ }
2938
+ let p = base === ":" ? "" : base;
2939
+ for (const st of e2.ptr.steps) {
2940
+ if (st.sel === "parent") {
2941
+ const up = parentOf(p === "" ? ":" : p);
2942
+ if (up === null) return null;
2943
+ p = up === ":" ? "" : up;
2944
+ } else if (st.sel === "key") p += ":" + st.name;
2945
+ else p += "[" + st.n + "]";
2946
+ }
2947
+ return p === "" ? ":" : p;
2948
+ }
2949
+ function tokensOf(p) {
2950
+ const out = [];
2951
+ const re = /\[(\d+)\]|:([^:[]*)/g;
2952
+ for (let m = re.exec(p); m !== null; m = re.exec(p)) {
2953
+ if (m[1] !== void 0) out.push({ n: Number(m[1]) });
2954
+ else if (m[2] !== "") out.push({ key: m[2] });
2955
+ }
2956
+ return out;
2957
+ }
2958
+ function parentOf(p) {
2959
+ if (p === ":" || p === "") return null;
2960
+ const toks = tokensOf(p).slice(0, -1);
2961
+ if (toks.length === 0) return ":";
2962
+ return renderPath(toks);
2963
+ }
2964
+ function renderPath(toks) {
2965
+ let s = "";
2966
+ for (const t2 of toks) s += t2.key !== void 0 ? ":" + t2.key : "[" + t2.n + "]";
2967
+ return s === "" ? ":" : s;
2968
+ }
2969
+ function renderSuffix(base, p) {
2970
+ const toks = tokensOf(p).slice(base === ":" ? 0 : tokensOf(base).length);
2971
+ let s = "";
2972
+ for (const t2 of toks) s += t2.key !== void 0 ? "/" + escapeSegment(t2.key) : "[" + t2.n + "]";
2973
+ return s;
2974
+ }
2975
+ function docForm(docRoot, target) {
2976
+ const s = renderSuffix(docRoot, target);
2977
+ return s === "" ? "/" : s.startsWith("/") ? s : "/" + s;
2978
+ }
2979
+ function relForm(holder, target) {
2980
+ return renderSuffix(holder, target).replace(/^\//, "");
2981
+ }
2982
+ function suffixAfter(base, target) {
2983
+ return renderSuffix(base, target);
2984
+ }
2985
+ function isJson5pUri(uri) {
2986
+ return /\.(json|json5|json5p)$/i.test(uri);
2987
+ }
2988
+ function json5pToken(raw) {
2989
+ return `*'${raw.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
2990
+ }
2991
+ function matchStyle(slashRaw, originalRaw) {
2992
+ if (!originalRaw.includes(":")) return slashRaw;
2993
+ try {
2994
+ return renderPointer(parsePointer(slashRaw));
2995
+ } catch {
2996
+ return slashRaw;
2997
+ }
2998
+ }
2999
+ function json5pAnchorToken(body) {
3000
+ return `&'${body.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
3001
+ }
3002
+
3003
+ // ../engine/ts/src/mv.ts
3004
+ import * as fs2 from "node:fs";
3005
+ import * as path3 from "node:path";
3006
+ function mv(absRoot, fromRel, toRel, opts = {}) {
3007
+ const root = path3.resolve(absRoot);
3008
+ const absFrom = path3.resolve(root, fromRel);
3009
+ const absTo = path3.resolve(root, toRel);
3010
+ const relFrom = relInside(root, absFrom, "source");
3011
+ const relTo = relInside(root, absTo, "target");
3012
+ for (const rel of [relFrom, relTo]) {
3013
+ for (const seg of rel.split("/")) {
3014
+ if (seg.startsWith(".")) throw new Error(`mv: hidden/overlay segments are not movable ("${seg}" in "${rel}")`);
3015
+ }
3016
+ }
3017
+ if (!fs2.existsSync(absFrom)) throw new Error(`mv: source does not exist: ${relFrom}`);
3018
+ if (fs2.existsSync(absTo)) throw new Error(`mv: target already exists: ${relTo}`);
3019
+ if (absTo === absFrom || absTo.startsWith(absFrom + path3.sep)) {
3020
+ throw new Error(`mv: cannot move "${relFrom}" into itself`);
3021
+ }
3022
+ const { doc } = walkTree(root, opts);
3023
+ const edges = resolveDocument(doc);
3024
+ const plan = planRewrites(doc, edges, storeOf(relFrom), storeOf(relTo), { root });
3025
+ const editedFiles = [];
3026
+ for (const [uri, edits] of plan.edits) {
3027
+ fs2.writeFileSync(uri, applyEdits(fs2.readFileSync(uri, "utf8"), edits));
3028
+ editedFiles.push(path3.relative(root, uri).split(path3.sep).join("/"));
3029
+ }
3030
+ fs2.mkdirSync(path3.dirname(absTo), { recursive: true });
3031
+ fs2.renameSync(absFrom, absTo);
3032
+ return { from: relFrom, to: relTo, rewritten: plan.rewritten, unrewritten: plan.unrewritten, editedFiles };
3033
+ }
3034
+ function relinkMoved(absRoot, moved, opts = {}) {
3035
+ const root = path3.resolve(absRoot);
3036
+ const { doc } = walkTree(root, opts);
3037
+ const edges = resolveDocument(doc);
3038
+ const rewritten = [];
3039
+ const unrewritten = [];
3040
+ const merged = /* @__PURE__ */ new Map();
3041
+ for (const m of moved) {
3042
+ const oldStore = storeOf(m.from);
3043
+ const stale = edges.filter((e2) => {
3044
+ if (e2.target.kind === "node" && !under(e2.target.path, oldStore)) return false;
3045
+ const nom = nominalPath(doc, e2);
3046
+ return nom !== null && under(nom, oldStore);
3047
+ });
3048
+ const shimmed = stale.map((e2) => ({ ...e2, target: { kind: "node", node: doc.root, path: nominalPath(doc, e2) } }));
3049
+ const plan = planRewrites(doc, shimmed, oldStore, storeOf(m.to), { root });
3050
+ for (const [uri, edits] of plan.edits) merged.set(uri, [...merged.get(uri) ?? [], ...edits]);
3051
+ rewritten.push(...plan.rewritten);
3052
+ unrewritten.push(...plan.unrewritten);
3053
+ }
3054
+ const editedFiles = [];
3055
+ for (const [uri, edits] of merged) {
3056
+ fs2.writeFileSync(uri, applyEdits(fs2.readFileSync(uri, "utf8"), edits));
3057
+ editedFiles.push(path3.relative(root, uri).split(path3.sep).join("/"));
3058
+ }
3059
+ return { rewritten, unrewritten, editedFiles };
3060
+ }
3061
+ function relInside(root, abs, what) {
3062
+ const rel = path3.relative(root, abs);
3063
+ if (rel === "" || rel.startsWith("..") || path3.isAbsolute(rel)) {
3064
+ throw new Error(`mv: ${what} escapes the served root`);
3065
+ }
3066
+ return rel.split(path3.sep).join("/");
3067
+ }
3068
+ function storeOf(rel) {
3069
+ return ":" + rel.split("/").join(":");
3070
+ }
3071
+
3072
+ // ../engine/ts/src/watch.ts
3073
+ import fs3 from "node:fs";
3074
+ import path4 from "node:path";
3075
+ var OVERLAY_FILES = /* @__PURE__ */ new Set(["body.yamlover", "meta.yamlover", "settings.yamlover"]);
3076
+ function watchTree(absRoot, onBatch, opts = {}) {
3077
+ const root = path4.resolve(absRoot);
3078
+ const debounceMs = opts.debounceMs ?? 300;
3079
+ const pending = /* @__PURE__ */ new Set();
3080
+ let timer = null;
3081
+ let running = false;
3082
+ let closed = false;
3083
+ const relevant = (rel) => {
3084
+ const segs = rel.split(path4.sep);
3085
+ for (let i = 0; i < segs.length; i++) {
3086
+ if (!segs[i].startsWith(".")) continue;
3087
+ const isOverlay = segs[i] === ".yamlover" && i === segs.length - 2 && OVERLAY_FILES.has(segs[i + 1]);
3088
+ if (!isOverlay) return false;
3089
+ }
3090
+ return !opts.ignore?.(path4.join(root, rel));
3091
+ };
3092
+ const fire = () => {
3093
+ timer = null;
3094
+ if (running || closed || pending.size === 0) return;
3095
+ const batch = [...pending].map((p) => p.split(path4.sep).join("/"));
3096
+ pending.clear();
3097
+ running = true;
3098
+ try {
3099
+ onBatch(batch);
3100
+ } finally {
3101
+ running = false;
3102
+ if (pending.size > 0) timer = setTimeout(fire, debounceMs);
3103
+ }
3104
+ };
3105
+ const watcher = fs3.watch(root, { recursive: true }, (_event, filename) => {
3106
+ if (closed || filename == null) return;
3107
+ const rel = String(filename);
3108
+ if (!relevant(rel)) return;
3109
+ pending.add(rel);
3110
+ if (timer) clearTimeout(timer);
3111
+ timer = setTimeout(fire, debounceMs);
3112
+ });
3113
+ return () => {
3114
+ closed = true;
3115
+ if (timer) clearTimeout(timer);
3116
+ watcher.close();
3117
+ };
3118
+ }
3119
+
3120
+ // ../engine/ts/src/settings.ts
3121
+ import fs4 from "node:fs";
3122
+ import path5 from "node:path";
3123
+ var DEFAULT_SETTINGS = {
3124
+ annotations: { location: ":annotations" },
3125
+ tags: { location: ":tags" }
3126
+ };
3127
+ function loadSettings(absRoot) {
3128
+ const file = path5.join(absRoot, ".yamlover", "settings.yamlover");
3129
+ if (!fs4.existsSync(file)) return DEFAULT_SETTINGS;
3130
+ let root;
3131
+ try {
3132
+ root = parseYamlover(fs4.readFileSync(file, "utf8"), file).root;
3133
+ } catch {
3134
+ return DEFAULT_SETTINGS;
3135
+ }
3136
+ return {
3137
+ annotations: { location: locationSetting(valueAt(root, "annotations", "location"), DEFAULT_SETTINGS.annotations.location) },
3138
+ tags: { location: locationSetting(valueAt(root, "tags", "location"), DEFAULT_SETTINGS.tags.location) }
3139
+ };
3140
+ }
3141
+ function valueAt(node, ...keys) {
3142
+ let v = node;
3143
+ for (const key of keys) {
3144
+ if (v === void 0 || isPointer(v)) return void 0;
3145
+ v = v.entries?.find((e2) => e2.key === key)?.value;
3146
+ }
3147
+ return v;
3148
+ }
3149
+ function locationSetting(v, dflt) {
3150
+ if (v === void 0) return dflt;
3151
+ if (isPointer(v)) return pointerPath(v) ?? dflt;
3152
+ if (v.kind === "scalar" && typeof v.value === "string") return stringPath(v.value, dflt);
3153
+ return dflt;
3154
+ }
3155
+ function pointerPath(p) {
3156
+ if (p.base.scope !== "current" && p.base.scope !== "document") return null;
3157
+ if (p.steps.length === 0 || !p.steps.every((s) => s.sel === "key")) return null;
3158
+ const segs = p.steps.map((s) => s.name);
3159
+ if (segs.some((seg) => seg === ".." || seg === "")) return null;
3160
+ return ":" + segs.join(":");
3161
+ }
3162
+ function stringPath(v, dflt) {
3163
+ if (v.trim() === "") return dflt;
3164
+ const segs = v.trim().split(/[/:]/).filter((seg) => seg !== "");
3165
+ if (segs.length === 0 || segs.some((seg) => seg === "..")) return dflt;
3166
+ return ":" + segs.join(":");
3167
+ }
3168
+
3169
+ // ../engine/ts/src/query.ts
3170
+ function parseQuery(text) {
3171
+ let rest = text.trim();
3172
+ let base = { scope: "current" };
3173
+ if (rest.startsWith(":::")) {
3174
+ rest = rest.slice(3);
3175
+ base = { scope: "link", authority: "" };
3176
+ } else if (rest.startsWith("::")) {
3177
+ rest = rest.slice(2);
3178
+ base = { scope: "link", authority: "" };
3179
+ } else if (rest.startsWith(":")) {
3180
+ rest = rest.slice(1);
3181
+ base = { scope: "document" };
3182
+ }
3183
+ const raws = splitPortions(rest);
3184
+ if (base.scope === "link") {
3185
+ const auth = raws.shift();
3186
+ if (auth === void 0 || auth === "") throw new SyntaxError(`query: "::" needs an authority in "${text}"`);
3187
+ base = { scope: "link", authority: keyName(auth) };
3188
+ } else if (base.scope === "current" && raws[0] === "..") {
3189
+ base = { scope: "parent" };
3190
+ raws.shift();
3191
+ }
3192
+ const portions = [];
3193
+ for (const r of raws) portions.push(...parsePortion(r));
3194
+ return { base, portions };
3195
+ }
3196
+ function splitPortions(s) {
3197
+ const out = [];
3198
+ let cur = "";
3199
+ let q = null;
3200
+ for (let i = 0; i < s.length; i++) {
3201
+ const c = s[i];
3202
+ if (q !== null) {
3203
+ cur += c;
3204
+ if (c === q) q = null;
3205
+ continue;
3206
+ }
3207
+ if (c === "\\" && i + 1 < s.length) {
3208
+ cur += c + s[i + 1];
3209
+ i++;
3210
+ continue;
3211
+ }
3212
+ if (c === "'" || c === '"') {
3213
+ q = c;
3214
+ cur += c;
3215
+ continue;
3216
+ }
3217
+ if (c === "!" && s.startsWith("!!<", i)) {
3218
+ const close = s.indexOf(">", i);
3219
+ if (close < 0) throw new SyntaxError(`query: unterminated "!!<\u2026>" in "${s}"`);
3220
+ cur += s.slice(i, close + 1);
3221
+ i = close;
3222
+ continue;
3223
+ }
3224
+ if (c === ":") {
3225
+ out.push(cur.trim());
3226
+ cur = "";
3227
+ continue;
3228
+ }
3229
+ cur += c;
3230
+ }
3231
+ out.push(cur.trim());
3232
+ return out.filter((p) => p !== "");
3233
+ }
3234
+ var CMP = /^(>=|<=|!=|=|>|<)/;
3235
+ function parsePortion(r) {
3236
+ if (r === "...") return [{ kind: "descend" }];
3237
+ if (r === "..") return [{ kind: "spine" }];
3238
+ if (r === "?..") return [{ kind: "up", sel: "any" }];
3239
+ if (r === "[]..") return [{ kind: "up", sel: "keyless" }];
3240
+ if (r.startsWith("!!<") && r.endsWith(">")) return [parseMeta(r.slice(3, -1))];
3241
+ {
3242
+ const sp = unquotedSpace(r);
3243
+ if (sp > 0) {
3244
+ const head = parseValTest(r.slice(0, sp));
3245
+ if (head !== null) return [{ kind: "combo", test: head, then: parsePortion(r.slice(sp + 1).trim()) }];
3246
+ throw new SyntaxError(`query: a key containing a space must be quoted ("${r}")`);
3247
+ }
3248
+ }
3249
+ if (r.endsWith("..") && !r.endsWith("\\..") && r.length > 2) {
3250
+ return [{ kind: "up", sel: keyName(r.slice(0, -2)) }];
3251
+ }
3252
+ if (r === "?") return [{ kind: "anykey" }];
3253
+ if (r === "[?]") return [{ kind: "anypos" }];
3254
+ if (r.endsWith("[?]") && !r.endsWith("\\[?]")) {
3255
+ return [...parsePortion(r.slice(0, -3)), { kind: "anypos" }];
3256
+ }
3257
+ const test = parseValTest(r);
3258
+ if (test !== null) return [test];
3259
+ const steps = parsePointer(r).steps;
3260
+ return steps.map((st) => st.sel === "key" ? { kind: "key", name: st.name } : st.sel === "index" ? { kind: "index", n: st.n } : { kind: "spine" });
3261
+ }
3262
+ function parseValTest(r) {
3263
+ const m = CMP.exec(r);
3264
+ if (m) {
3265
+ const lit = literal(
3266
+ r.slice(m[0].length).trim(),
3267
+ /*bareWordsOk*/
3268
+ true
3269
+ );
3270
+ return { kind: "valtest", op: m[0], value: lit };
3271
+ }
3272
+ const t2 = r.trim();
3273
+ if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(t2)) return { kind: "valtest", op: "=", value: Number(t2) };
3274
+ if (t2 === "true") return { kind: "valtest", op: "=", value: true };
3275
+ if (t2 === "false") return { kind: "valtest", op: "=", value: false };
3276
+ if (t2 === "null") return { kind: "valtest", op: "=", value: null };
3277
+ return null;
3278
+ }
3279
+ function literal(t2, bareWordsOk) {
3280
+ if ((t2[0] === "'" || t2[0] === '"') && t2[t2.length - 1] === t2[0]) {
3281
+ return t2.slice(1, -1).replace(t2[0] === "'" ? /''/g : /\\"/g, t2[0]);
3282
+ }
3283
+ if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(t2)) return Number(t2);
3284
+ if (t2 === "true") return true;
3285
+ if (t2 === "false") return false;
3286
+ if (t2 === "null") return null;
3287
+ if (bareWordsOk) return t2;
3288
+ throw new SyntaxError(`query: bad literal "${t2}"`);
3289
+ }
3290
+ function parseMeta(inner) {
3291
+ const t2 = inner.trim();
3292
+ if (t2.startsWith("*")) {
3293
+ const p = parsePointer(t2.slice(1).trim());
3294
+ const last = p.steps[p.steps.length - 1];
3295
+ if (!last || last.sel !== "key") throw new SyntaxError(`query: bad schema matcher "!!<${inner}>"`);
3296
+ return { kind: "meta", schema: last.name };
3297
+ }
3298
+ const root = parseYamlover(t2).root;
3299
+ const out = { kind: "meta" };
3300
+ for (const e2 of root.entries ?? []) {
3301
+ if (isPointer(e2.value) || e2.value.kind !== "scalar") continue;
3302
+ if (e2.key === "type") out.type = String(e2.value.value);
3303
+ if (e2.key === "format") out.format = String(e2.value.value);
3304
+ }
3305
+ if (out.type === void 0 && out.format === void 0) {
3306
+ throw new SyntaxError(`query: a "!!<\u2026>" matcher needs type:/format: or a schema pointer ("${inner}")`);
3307
+ }
3308
+ return out;
3309
+ }
3310
+ function unquotedSpace(s) {
3311
+ let q = null;
3312
+ for (let i = 0; i < s.length; i++) {
3313
+ const c = s[i];
3314
+ if (q !== null) {
3315
+ if (c === q) q = null;
3316
+ continue;
3317
+ }
3318
+ if (c === "\\") {
3319
+ i++;
3320
+ continue;
3321
+ }
3322
+ if (c === "'" || c === '"') {
3323
+ q = c;
3324
+ continue;
3325
+ }
3326
+ if (c === "!" && s.startsWith("!!<", i)) {
3327
+ const close = s.indexOf(">", i);
3328
+ if (close < 0) return -1;
3329
+ i = close;
3330
+ continue;
3331
+ }
3332
+ if (c === " ") return i;
3333
+ }
3334
+ return -1;
3335
+ }
3336
+ function keyName(portion) {
3337
+ const steps = parsePointer(portion).steps;
3338
+ if (steps.length !== 1 || steps[0].sel !== "key") throw new SyntaxError(`query: bad name portion "${portion}"`);
3339
+ return steps[0].name;
3340
+ }
3341
+ function evalQuery(s, text, from = ":") {
3342
+ const q = parseQuery(text);
3343
+ let binds;
3344
+ switch (q.base.scope) {
3345
+ case "current":
3346
+ binds = [from];
3347
+ break;
3348
+ case "parent":
3349
+ binds = compact(spineParent(s, from) === null ? [] : [spineParent(s, from)]);
3350
+ break;
3351
+ case "document":
3352
+ binds = [docRootOf(s, from)];
3353
+ break;
3354
+ case "link": {
3355
+ const hit = childByKey(s, ":", q.base.authority);
3356
+ binds = hit === null ? [] : [hit];
3357
+ break;
3358
+ }
3359
+ }
3360
+ for (const p of q.portions) {
3361
+ binds = dedup(step(s, binds, p));
3362
+ if (binds.length === 0) break;
3363
+ }
3364
+ return dedup(binds);
3365
+ }
3366
+ function step(s, binds, p) {
3367
+ const out = [];
3368
+ for (const b of binds) {
3369
+ switch (p.kind) {
3370
+ case "key": {
3371
+ const hit = childByKey(s, b, p.name);
3372
+ if (hit !== null) out.push(hit);
3373
+ break;
3374
+ }
3375
+ case "index": {
3376
+ const row = s.db.prepare(
3377
+ "SELECT to_path AS t FROM edge WHERE from_path = ? AND kind IN ('contain','ref') AND pos = ? LIMIT 1"
3378
+ ).get(b, p.n);
3379
+ if (row) out.push(row.t);
3380
+ break;
3381
+ }
3382
+ case "anykey": {
3383
+ for (const r of ownEntries(s, b)) if (r.label !== null) out.push(r.to);
3384
+ for (const r of anchorEntries(s, b)) if (r.label !== null) out.push(r.member);
3385
+ break;
3386
+ }
3387
+ case "anypos": {
3388
+ for (const r of ownEntries(s, b)) out.push(r.to);
3389
+ for (const r of anchorEntries(s, b)) out.push(r.member);
3390
+ break;
3391
+ }
3392
+ case "descend":
3393
+ out.push(...descend(s, b));
3394
+ break;
3395
+ case "spine": {
3396
+ const up = spineParent(s, b);
3397
+ if (up !== null) out.push(up);
3398
+ break;
3399
+ }
3400
+ case "up":
3401
+ out.push(...uplinks(s, b, p.sel));
3402
+ break;
3403
+ case "valtest":
3404
+ if (valOk(s.node(b), p)) out.push(b);
3405
+ break;
3406
+ case "meta":
3407
+ if (metaOk(s, b, p)) out.push(b);
3408
+ break;
3409
+ case "combo": {
3410
+ if (!valOk(s.node(b), p.test)) break;
3411
+ let inner = [b];
3412
+ for (const t2 of p.then) inner = step(s, inner, t2);
3413
+ out.push(...inner);
3414
+ break;
3415
+ }
3416
+ }
3417
+ }
3418
+ return out;
3419
+ }
3420
+ function ownEntries(s, p) {
3421
+ return s.db.prepare(
3422
+ "SELECT to_path AS t, label FROM edge WHERE from_path = ? AND kind IN ('contain','ref') ORDER BY pos"
3423
+ ).all(p).map((r) => ({ to: r.t, label: r.label }));
3424
+ }
3425
+ function anchorEntries(s, p) {
3426
+ return s.db.prepare(
3427
+ "SELECT from_path AS f, label FROM edge WHERE to_path = ? AND kind = 'back' ORDER BY from_path"
3428
+ ).all(p).map((r) => ({ member: r.f, label: r.label }));
3429
+ }
3430
+ function childByKey(s, p, name) {
3431
+ const own = s.db.prepare(
3432
+ "SELECT to_path AS t FROM edge WHERE from_path = ? AND kind IN ('contain','ref') AND label = ? LIMIT 1"
3433
+ ).get(p, name);
3434
+ if (own) return own.t;
3435
+ const anchored = s.db.prepare(
3436
+ "SELECT from_path AS f FROM edge WHERE to_path = ? AND kind = 'back' AND label = ? ORDER BY from_path LIMIT 1"
3437
+ ).get(p, name);
3438
+ return anchored ? anchored.f : null;
3439
+ }
3440
+ function spineParent(s, p) {
3441
+ const row = s.db.prepare(
3442
+ "SELECT from_path AS f FROM edge WHERE to_path = ? AND kind = 'contain' LIMIT 1"
3443
+ ).get(p);
3444
+ return row ? row.f : null;
3445
+ }
3446
+ function uplinks(s, p, sel) {
3447
+ const out = [];
3448
+ const label = sel === "any" || sel === "keyless" ? null : sel;
3449
+ const contains = s.db.prepare(
3450
+ sel === "any" ? "SELECT from_path AS x FROM edge WHERE to_path = ? AND kind = 'contain'" : "SELECT from_path AS x FROM edge WHERE to_path = ? AND kind = 'contain' AND label " + (sel === "keyless" ? "IS NULL" : "= ?")
3451
+ );
3452
+ const backs = s.db.prepare(
3453
+ sel === "any" ? "SELECT to_path AS x FROM edge WHERE from_path = ? AND kind = 'back'" : "SELECT to_path AS x FROM edge WHERE from_path = ? AND kind = 'back' AND label " + (sel === "keyless" ? "IS NULL" : "= ?")
3454
+ );
3455
+ const refs = s.db.prepare(
3456
+ sel === "any" ? "SELECT from_path AS x FROM edge WHERE to_path = ? AND kind = 'ref'" : "SELECT from_path AS x FROM edge WHERE to_path = ? AND kind = 'ref' AND label " + (sel === "keyless" ? "IS NULL" : "= ?")
3457
+ );
3458
+ const args = (q) => label === null ? q.all(p) : q.all(p, label);
3459
+ for (const r of args(contains)) out.push(r.x);
3460
+ for (const r of args(backs)) out.push(r.x);
3461
+ for (const r of args(refs)) out.push(r.x);
3462
+ return out;
3463
+ }
3464
+ function descend(s, p) {
3465
+ const out = [p];
3466
+ const kids = s.db.prepare(
3467
+ "SELECT to_path AS t FROM edge WHERE from_path = ? AND kind = 'contain' ORDER BY pos"
3468
+ ).all(p);
3469
+ for (const k of kids) out.push(...descend(s, k.t));
3470
+ return out;
3471
+ }
3472
+ function docRootOf(s, p) {
3473
+ let cur = p;
3474
+ while (cur !== null && cur !== ":") {
3475
+ const row = s.node(cur);
3476
+ if (row?.meta && row.meta.documentRoot === true) return cur;
3477
+ cur = spineParent(s, cur);
3478
+ }
3479
+ return ":";
3480
+ }
3481
+ function valOk(row, t2) {
3482
+ if (!row || row.type !== "scalar") return false;
3483
+ const v = row.value;
3484
+ switch (t2.op) {
3485
+ case "=":
3486
+ return v === t2.value;
3487
+ case "!=":
3488
+ return v !== t2.value;
3489
+ case ">":
3490
+ return typeof v === "number" && typeof t2.value === "number" && v > t2.value;
3491
+ case ">=":
3492
+ return typeof v === "number" && typeof t2.value === "number" && v >= t2.value;
3493
+ case "<":
3494
+ return typeof v === "number" && typeof t2.value === "number" && v < t2.value;
3495
+ case "<=":
3496
+ return typeof v === "number" && typeof t2.value === "number" && v <= t2.value;
3497
+ }
3498
+ }
3499
+ function metaOk(s, p, t2) {
3500
+ const row = s.node(p);
3501
+ if (!row) return false;
3502
+ if (t2.schema !== void 0) return row.format === `x-yamlover-${t2.schema}`;
3503
+ if (t2.format !== void 0 && row.format !== t2.format) return false;
3504
+ if (t2.type !== void 0) {
3505
+ switch (t2.type) {
3506
+ case "binary":
3507
+ return row.type === "blob";
3508
+ case "array":
3509
+ return row.is_array;
3510
+ case "object":
3511
+ return row.type === "mapping" && !row.is_array;
3512
+ case "string":
3513
+ return row.type === "scalar" && typeof row.value === "string";
3514
+ case "integer":
3515
+ return row.type === "scalar" && typeof row.value === "number" && Number.isInteger(row.value);
3516
+ case "number":
3517
+ return row.type === "scalar" && typeof row.value === "number";
3518
+ case "boolean":
3519
+ return row.type === "scalar" && typeof row.value === "boolean";
3520
+ case "variant":
3521
+ return row.type === "scalar" && hasOwnChild(s, p);
3522
+ default:
3523
+ return false;
3524
+ }
3525
+ }
3526
+ return true;
3527
+ }
3528
+ function hasOwnChild(s, p) {
3529
+ return s.db.prepare("SELECT 1 FROM edge WHERE from_path = ? AND kind IN ('contain','ref') LIMIT 1").get(p) !== void 0;
3530
+ }
3531
+ function dedup(paths) {
3532
+ const seen = /* @__PURE__ */ new Set();
3533
+ const out = [];
3534
+ for (const p of paths) if (!seen.has(p)) {
3535
+ seen.add(p);
3536
+ out.push(p);
3537
+ }
3538
+ return out;
3539
+ }
3540
+ function compact(xs) {
3541
+ return xs.filter((x) => x !== null);
3542
+ }
3543
+
3544
+ // src/server/gitignore.ts
3545
+ var import_ignore = __toESM(require_ignore(), 1);
3546
+ import fs5 from "node:fs";
3547
+ import path6 from "node:path";
3548
+ function buildGitIgnore(dataRoot) {
3549
+ const gitRoot = findGitRoot(dataRoot) ?? dataRoot;
3550
+ const cache = /* @__PURE__ */ new Map();
3551
+ const matcherFor = (dir) => {
3552
+ if (!cache.has(dir)) {
3553
+ let ig = null;
3554
+ try {
3555
+ const gi = path6.join(dir, ".gitignore");
3556
+ if (fs5.statSync(gi).isFile()) ig = (0, import_ignore.default)().add(fs5.readFileSync(gi, "utf-8"));
3557
+ } catch {
3558
+ }
3559
+ cache.set(dir, ig);
3560
+ }
3561
+ return cache.get(dir);
3562
+ };
3563
+ return (absPath) => {
3564
+ const fromRoot = path6.relative(gitRoot, absPath);
3565
+ if (fromRoot === ".git" || fromRoot.startsWith(".git" + path6.sep)) return true;
3566
+ let isDir = false;
3567
+ try {
3568
+ isDir = fs5.statSync(absPath).isDirectory();
3569
+ } catch {
3570
+ }
3571
+ const rel = path6.relative(gitRoot, path6.dirname(absPath));
3572
+ const segs = rel === "" ? [] : rel.split(path6.sep);
3573
+ let dir = gitRoot;
3574
+ const dirs = [gitRoot];
3575
+ for (const s of segs) {
3576
+ dir = path6.join(dir, s);
3577
+ dirs.push(dir);
3578
+ }
3579
+ for (const d of dirs) {
3580
+ const ig = matcherFor(d);
3581
+ if (!ig) continue;
3582
+ const relToDir = path6.relative(d, absPath);
3583
+ if (!relToDir) continue;
3584
+ if (ig.ignores(relToDir) || isDir && ig.ignores(relToDir + "/")) return true;
3585
+ }
3586
+ return false;
3587
+ };
3588
+ }
3589
+ function findGitRoot(start) {
3590
+ let dir = path6.resolve(start);
3591
+ try {
3592
+ if (!fs5.statSync(dir).isDirectory()) dir = path6.dirname(dir);
3593
+ } catch {
3594
+ return null;
3595
+ }
3596
+ for (; ; ) {
3597
+ if (fs5.existsSync(path6.join(dir, ".git"))) return dir;
3598
+ const parent = path6.dirname(dir);
3599
+ if (parent === dir) return null;
3600
+ dir = parent;
3601
+ }
3602
+ }
3603
+
3604
+ // src/server/node-kind.ts
3605
+ function ownedEntries(s, p) {
3606
+ return s.entries(p).filter((e2) => e2.kind !== "back");
3607
+ }
3608
+ function displayKind(s, p, row) {
3609
+ const ents = ownedEntries(s, p);
3610
+ if (row.type === "blob") return ents.length ? "omni" : "binary";
3611
+ if (row.type === "scalar") return ents.length ? "omni" : "scalar";
3612
+ if (!ents.length) return row.is_array ? "array" : "object";
3613
+ if (row.is_array) return "array";
3614
+ return ents.some((e2) => e2.label === null) ? "mix" : "object";
3615
+ }
3616
+ function typeName(s, p, row) {
3617
+ const k = displayKind(s, p, row);
3618
+ if (k === "scalar") return scalarType(row.value);
3619
+ if (k === "mix") return "mixed";
3620
+ if (k === "omni") return "variant";
3621
+ return k;
3622
+ }
3623
+ function scalarType(v) {
3624
+ if (v === null) return "null";
3625
+ if (typeof v === "boolean") return "boolean";
3626
+ if (typeof v === "number") return Number.isInteger(v) ? "integer" : "number";
3627
+ return "string";
3628
+ }
3629
+
3630
+ // src/server/tasks.ts
3631
+ var PROGRESS_EMIT_MS = 150;
3632
+ var KEEP_FINISHED_MS = 5e3;
3633
+ var TaskRegistry = class {
3634
+ constructor(emit) {
3635
+ this.emit = emit;
3636
+ }
3637
+ seq = 0;
3638
+ tasks = /* @__PURE__ */ new Map();
3639
+ lastEmit = /* @__PURE__ */ new Map();
3640
+ start(label) {
3641
+ const id = "t" + ++this.seq;
3642
+ const info = { id, label, state: "running", progress: { done: 0 }, startedAt: Date.now() };
3643
+ this.tasks.set(id, info);
3644
+ this.send(info, true);
3645
+ const finish = (state, error) => {
3646
+ if (info.state !== "running") return;
3647
+ info.state = state;
3648
+ info.finishedAt = Date.now();
3649
+ if (error !== void 0) info.error = error;
3650
+ this.send(info, true);
3651
+ };
3652
+ return {
3653
+ id,
3654
+ progress: (done, total, message) => {
3655
+ if (info.state !== "running") return;
3656
+ info.progress = { done, ...total !== void 0 && { total }, ...message !== void 0 && { message } };
3657
+ this.send(info, false);
3658
+ },
3659
+ done: () => finish("done"),
3660
+ fail: (err) => finish("error", String(err?.message ?? err))
3661
+ };
3662
+ }
3663
+ /** Running tasks + ones finished within the last few seconds (pruning the rest). */
3664
+ list() {
3665
+ const now = Date.now();
3666
+ for (const [id, t2] of this.tasks) {
3667
+ if (t2.state !== "running" && now - (t2.finishedAt ?? 0) > KEEP_FINISHED_MS) {
3668
+ this.tasks.delete(id);
3669
+ this.lastEmit.delete(id);
3670
+ }
3671
+ }
3672
+ return [...this.tasks.values()].map((t2) => ({ ...t2, progress: { ...t2.progress } }));
3673
+ }
3674
+ send(t2, always) {
3675
+ const now = Date.now();
3676
+ if (!always && now - (this.lastEmit.get(t2.id) ?? 0) < PROGRESS_EMIT_MS) return;
3677
+ this.lastEmit.set(t2.id, now);
3678
+ this.emit({ ...t2, progress: { ...t2.progress } });
3679
+ }
3680
+ };
3681
+
3682
+ // src/server/engine-api.ts
3683
+ var LINK_KEY = "$yamloverLink";
3684
+ var BINARY_KEY = "$yamloverBinary";
3685
+ var MIXED_KEY = "$yamloverMixed";
3686
+ function createHandlers(dataRoot, opts = {}) {
3687
+ const rootName = path7.basename(path7.resolve(dataRoot)) || "/";
3688
+ const dbPath = path7.join(dataRoot, ".yamlover", "index.db");
3689
+ const settings = loadSettings(dataRoot);
3690
+ const ignore2 = opts.gitignore === false ? void 0 : buildGitIgnore(dataRoot);
3691
+ fs6.mkdirSync(path7.dirname(dbPath), { recursive: true });
3692
+ const store0 = new Store(dbPath);
3693
+ const store = () => store0;
3694
+ const log = opts.log ?? (() => {
3695
+ });
3696
+ let closed = false;
3697
+ const sseClients = /* @__PURE__ */ new Set();
3698
+ const sseWrite = (frame) => {
3699
+ const payload = JSON.stringify(frame);
3700
+ for (const res of sseClients) res.write(`data: ${payload}
3701
+
3702
+ `);
3703
+ };
3704
+ const broadcast = (diff) => {
3705
+ if (diff.added.length + diff.changed.length + diff.removed.length + diff.moved.length === 0) return;
3706
+ const toClient = (rel) => segsToStr(rel.split("/"));
3707
+ sseWrite({
3708
+ type: "diff",
3709
+ added: diff.added.map(toClient),
3710
+ changed: diff.changed.map(toClient),
3711
+ removed: diff.removed.map(toClient),
3712
+ moved: diff.moved.map((m) => ({ from: toClient(m.from), to: toClient(m.to) }))
3713
+ });
3714
+ };
3715
+ const announce = (d) => broadcast({ added: [], changed: [], removed: [], moved: [], ...d });
3716
+ const relFileOf = (clientPath) => strToSegs(clientPath).map(String).join("/");
3717
+ const tasks = new TaskRegistry((t2) => sseWrite({ type: "task", task: t2 }));
3718
+ let chain = Promise.resolve();
3719
+ const enqueue = (fn) => {
3720
+ const p = chain.then(fn);
3721
+ chain = p.catch(() => {
3722
+ });
3723
+ return p;
3724
+ };
3725
+ const gib = (b) => (b / 2 ** 30).toFixed(1);
3726
+ const BIG_FILE_BYTES = 256 * 2 ** 20;
3727
+ let hashing = false;
3728
+ const scheduleHasher = () => {
3729
+ if (hashing || closed) return;
3730
+ if (store0.unhashedFiles(1).length === 0) return;
3731
+ hashing = true;
3732
+ void (async () => {
3733
+ const skip = /* @__PURE__ */ new Set();
3734
+ let done = 0;
3735
+ let lastLog = 0;
3736
+ const t0 = Date.now();
3737
+ let h = null;
3738
+ try {
3739
+ for (; ; ) {
3740
+ if (closed) break;
3741
+ const pending = store0.unhashedFiles().filter((f) => !skip.has(f.path));
3742
+ if (pending.length === 0) break;
3743
+ h ??= tasks.start("hashing large files");
3744
+ const next = pending[0];
3745
+ const total = done + pending.length;
3746
+ h.progress(done, total, next.path);
3747
+ const abs = path7.join(dataRoot, ...next.path.split("/"));
3748
+ let hash = null;
3749
+ try {
3750
+ hash = await hashFileAsync(abs, (bytes) => {
3751
+ if (next.size >= BIG_FILE_BYTES) h?.progress(done, total, `${next.path} \u2014 ${gib(bytes)}/${gib(next.size)} GiB`);
3752
+ });
3753
+ } catch {
3754
+ }
3755
+ const st = hash !== null ? fs6.statSync(abs, { throwIfNoEntry: false }) : void 0;
3756
+ const fresh = st !== void 0 && st.size === next.size && st.mtimeMs === next.mtimeMs;
3757
+ const ok = hash !== null && fresh && !closed ? await enqueue(() => store0.setFileHash(next.path, hash, next.size, next.mtimeMs)) : false;
3758
+ if (!ok) {
3759
+ skip.add(next.path);
3760
+ continue;
3761
+ }
3762
+ done++;
3763
+ const now = Date.now();
3764
+ if (now - lastLog >= 500) {
3765
+ lastLog = now;
3766
+ log(`hashing ${done}/${total} \u2014 ${next.path}`);
3767
+ }
3768
+ }
3769
+ h?.done();
3770
+ if (h) log(`hashing done \u2014 ${done} file(s) in ${((Date.now() - t0) / 1e3).toFixed(1)}s`);
3771
+ } catch (e2) {
3772
+ h?.fail(e2);
3773
+ log(`hashing FAILED \u2014 ${String(e2?.message ?? e2)}`);
3774
+ } finally {
3775
+ hashing = false;
3776
+ }
3777
+ })();
3778
+ };
3779
+ const doReindex = () => reindexAsync(store0, dataRoot, { ignore: ignore2 });
3780
+ const runIndexTask = (label) => enqueue(async () => {
3781
+ const h = tasks.start(label);
3782
+ const t0 = Date.now();
3783
+ let lastLog = 0;
3784
+ log(`${label}\u2026`);
3785
+ try {
3786
+ const diff = await reindexAsync(store0, dataRoot, {
3787
+ ignore: ignore2,
3788
+ onProgress: (p) => {
3789
+ h.progress(p.done, p.total, p.message);
3790
+ const now = Date.now();
3791
+ if (now - lastLog >= 500) {
3792
+ lastLog = now;
3793
+ log(`${label} ${p.done}/${p.total ?? "?"}${p.message ? ` \u2014 ${p.message}` : ""}`);
3794
+ }
3795
+ }
3796
+ });
3797
+ h.done();
3798
+ log(
3799
+ `${label} done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (+${diff.added.length} ~${diff.changed.length} \u2212${diff.removed.length} \u2192${diff.moved.length})`
3800
+ );
3801
+ broadcast(diff);
3802
+ scheduleHasher();
3803
+ return diff;
3804
+ } catch (e2) {
3805
+ h.fail(e2);
3806
+ log(`${label} FAILED \u2014 ${String(e2?.message ?? e2)}`);
3807
+ throw e2;
3808
+ }
3809
+ });
3810
+ const reconcile = () => enqueue(async () => {
3811
+ const h = tasks.start("reconciling");
3812
+ try {
3813
+ const diff = await doReindex();
3814
+ if (diff.moved.length > 0) {
3815
+ const r = relinkMoved(dataRoot, diff.moved, { ignore: ignore2 });
3816
+ if (r.editedFiles.length > 0) {
3817
+ const follow = reindex(store0, dataRoot, { ignore: ignore2 });
3818
+ diff.changed = [.../* @__PURE__ */ new Set([...diff.changed, ...follow.changed])];
3819
+ }
3820
+ }
3821
+ h.done();
3822
+ broadcast(diff);
3823
+ scheduleHasher();
3824
+ return diff;
3825
+ } catch (e2) {
3826
+ h.fail(e2);
3827
+ throw e2;
3828
+ }
3829
+ });
3830
+ const ready = runIndexTask(`indexing ${rootName}`);
3831
+ const stopWatch = opts.watch ? watchTree(dataRoot, () => {
3832
+ reconcile().catch((e2) => log(`reconcile FAILED \u2014 ${String(e2?.message ?? e2)}`));
3833
+ }, { ignore: ignore2 }) : null;
3834
+ const handler = (req, res, url) => {
3835
+ try {
3836
+ const s = store();
3837
+ if (req.method === "GET" && url.pathname === "/api/events") {
3838
+ res.statusCode = 200;
3839
+ res.setHeader("Content-Type", "text/event-stream");
3840
+ res.setHeader("Cache-Control", "no-cache");
3841
+ res.setHeader("Connection", "keep-alive");
3842
+ res.write(": connected\n\n");
3843
+ sseClients.add(res);
3844
+ const ping = setInterval(() => res.write(": ping\n\n"), 3e4);
3845
+ req.on("close", () => {
3846
+ clearInterval(ping);
3847
+ sseClients.delete(res);
3848
+ });
3849
+ return;
3850
+ }
3851
+ if (req.method === "POST" && url.pathname === "/api/reindex") {
3852
+ reconcile().then((diff) => sendJson(res, 200, diff)).catch((e2) => sendJson(res, 500, { error: String(e2.message || e2) }));
3853
+ return;
3854
+ }
3855
+ if (url.pathname === "/api/tasks") {
3856
+ sendJson(res, 200, tasks.list());
3857
+ return;
3858
+ }
3859
+ if (url.pathname === "/api/query") {
3860
+ const q = url.searchParams.get("q") || "";
3861
+ const at = storePath(strToSegs(url.searchParams.get("path") || ":"));
3862
+ try {
3863
+ const results = evalQuery(s, q, at).map((p2) => segsToStr(storePathToSegs(p2)));
3864
+ sendJson(res, 200, { results });
3865
+ } catch (e2) {
3866
+ sendJson(res, 400, { error: String(e2.message || e2) });
3867
+ }
3868
+ return;
3869
+ }
3870
+ if (url.pathname === "/api/dangling") {
3871
+ sendJson(res, 200, s.dangling().map((d) => ({ from: segsToStr(storePathToSegs(d.from)), raw: d.raw, reason: d.reason })));
3872
+ return;
3873
+ }
3874
+ if (req.method === "POST" && url.pathname === "/api/annotate") {
3875
+ readBody(req).then(
3876
+ (data) => (
3877
+ // Queued: an incremental row added while a full walk (whose disk snapshot predates
3878
+ // this annotation) is committing would be silently swapped away.
3879
+ enqueue(() => {
3880
+ const a = data;
3881
+ const tagStore = storePath(strToSegs(a.tag ?? ""));
3882
+ if (!a?.tag || s.node(tagStore)?.format !== TAG_FORMAT) {
3883
+ throw new Error("annotation needs a `tag` that is an x-yamlover-tag node");
3884
+ }
3885
+ const annPath = writeAnnotation(dataRoot, settings.annotations.location, a);
3886
+ const doc = parseYamlover(fs6.readFileSync(path7.join(dataRoot, ...strToSegs(annPath).map(String)), "utf8"), annPath);
3887
+ s.addAnnotation(storePath(strToSegs(annPath)), storePath(strToSegs(a.target)), doc, tagStore);
3888
+ announce({ added: [relFileOf(annPath)] });
3889
+ return { path: annPath };
3890
+ })
3891
+ )
3892
+ ).then((body) => sendJson(res, 201, body)).catch((e2) => sendJson(res, 400, { error: String(e2.message || e2) }));
3893
+ return;
3894
+ }
3895
+ if (req.method === "DELETE" && url.pathname === "/api/annotate") {
3896
+ const annPath = url.searchParams.get("path") || "";
3897
+ enqueue(() => {
3898
+ deleteAnnotation(dataRoot, s, annPath);
3899
+ s.removeAnnotation(storePath(strToSegs(annPath)));
3900
+ announce({ removed: [relFileOf(annPath)] });
3901
+ }).then(() => sendJson(res, 200, { ok: true })).catch((e2) => sendJson(res, 400, { error: String(e2.message || e2) }));
3902
+ return;
3903
+ }
3904
+ if (req.method === "POST" && url.pathname === "/api/tag") {
3905
+ readBody(req).then(
3906
+ (data) => enqueue(async () => {
3907
+ const name = String(data?.name ?? "").trim();
3908
+ if (!name) throw new Error("tag needs a non-empty name");
3909
+ const segs2 = [...strToSegs(settings.tags.location), name];
3910
+ const tagPath = segsToStr(segs2);
3911
+ const existing = s.node(storePath(segs2));
3912
+ if (existing) {
3913
+ if (existing.format !== TAG_FORMAT) throw new Error(`a node already exists at ${tagPath} and is not a tag`);
3914
+ const color = s.node(storePath(segs2) + ":color")?.value;
3915
+ return { path: tagPath, name, color: typeof color === "string" ? color : null, created: false };
3916
+ }
3917
+ const written = writeTag(dataRoot, settings.tags.location, name);
3918
+ s.addTag(storePath(strToSegs(settings.tags.location)), name, written.pos, written.node);
3919
+ if (s.node(storePath(segs2))?.format !== TAG_FORMAT) throw new Error(`the created tag did not index as a tag: ${tagPath}`);
3920
+ announce(written.createdFile ? { added: [written.file] } : { changed: [written.file] });
3921
+ return { path: tagPath, name, color: null, created: true };
3922
+ })
3923
+ ).then((body) => sendJson(res, 201, body)).catch((e2) => sendJson(res, 400, { error: String(e2.message || e2) }));
3924
+ return;
3925
+ }
3926
+ if (req.method === "POST" && url.pathname === "/api/paste") {
3927
+ readBody(req).then(
3928
+ (data) => enqueue(async () => {
3929
+ const result = handlePaste(dataRoot, s, data);
3930
+ broadcast(await doReindex());
3931
+ scheduleHasher();
3932
+ return result;
3933
+ })
3934
+ ).then((result) => sendJson(res, 201, result)).catch((e2) => sendJson(res, 400, { error: String(e2.message || e2) }));
3935
+ return;
3936
+ }
3937
+ if (req.method === "POST" && url.pathname === "/api/mv") {
3938
+ readBody(req).then(
3939
+ (data) => enqueue(async () => {
3940
+ const { from, to } = data;
3941
+ const rel = (p2, what) => {
3942
+ const segs2 = strToSegs(p2);
3943
+ if (segs2.length === 0) throw new Error(`mv: ${what} must name a file or directory`);
3944
+ if (segs2.some((g) => typeof g === "number")) throw new Error(`mv: ${what} must be a file/directory path (no positions)`);
3945
+ return segs2.join("/");
3946
+ };
3947
+ const report = mv(dataRoot, rel(from ?? "", "from"), rel(to ?? "", "to"), { ignore: ignore2 });
3948
+ const diff = await doReindex();
3949
+ broadcast(diff);
3950
+ return { ...report, diff };
3951
+ })
3952
+ ).then((body) => sendJson(res, 200, body)).catch((e2) => sendJson(res, 400, { error: String(e2.message || e2) }));
3953
+ return;
3954
+ }
3955
+ const segs = strToSegs(url.searchParams.get("path") || ":");
3956
+ const p = storePath(segs);
3957
+ const depth = parseDepth(url.searchParams.get("depth"));
3958
+ if (url.pathname === "/api/info") {
3959
+ sendJson(res, 200, { root: rootName });
3960
+ return;
3961
+ }
3962
+ if (url.pathname === "/api/annotations") {
3963
+ sendJson(res, 200, annotationsFor(dataRoot, s, segs));
3964
+ return;
3965
+ }
3966
+ if (url.pathname === "/api/tagged") {
3967
+ const row2 = s.node(p);
3968
+ if (!row2 || row2.format !== TAG_FORMAT) return notFound(res, url);
3969
+ sendJson(res, 200, taggedMaterials(dataRoot, s, p));
3970
+ return;
3971
+ }
3972
+ if (url.pathname === "/api/tree") {
3973
+ const row2 = s.node(p);
3974
+ if (!row2) return notFound(res, url);
3975
+ const label = segs.length === 0 ? rootName : labelFor(s, p, segs[segs.length - 1]);
3976
+ sendJson(res, 200, buildTree(dataRoot, s, segs, label, depth ?? 3));
3977
+ return;
3978
+ }
3979
+ if (url.pathname === "/api/blob") {
3980
+ const file = path7.join(dataRoot, ...segs.map(String));
3981
+ if (!fs6.existsSync(file) || fs6.statSync(file).isDirectory()) return notFound(res, url);
3982
+ res.statusCode = 200;
3983
+ res.setHeader("Content-Type", s.node(p)?.format ?? formatFromExt(file) ?? "application/octet-stream");
3984
+ res.setHeader("Content-Length", String(fs6.statSync(file).size));
3985
+ const stream = fs6.createReadStream(file);
3986
+ stream.on("error", () => res.destroy());
3987
+ stream.pipe(res);
3988
+ return;
3989
+ }
3990
+ const row = s.node(p);
3991
+ if (!row) return notFound(res, url);
3992
+ const viewDepth = depth ?? 1;
3993
+ const kind = displayKind(s, p, row);
3994
+ if (url.pathname === "/api/json") {
3995
+ const wantBytes = kind === "binary" && url.searchParams.get("binary") === "1";
3996
+ sendJson(res, 200, {
3997
+ path: segsToStr(segs),
3998
+ type: tocType(s, p, row),
3999
+ format: row.format ?? null,
4000
+ concrete: concreteOf(dataRoot, segs, row),
4001
+ // dir | yamlover | null (stat-derived; engine tracks no per-node concrete yet)
4002
+ documentPath: documentPath(s, segs),
4003
+ // nearest enclosing document root (for `/…` links)
4004
+ title: titleOf(s, p),
4005
+ description: null,
4006
+ value: wantBytes ? binaryContent(dataRoot, segs, row) : projectValue(dataRoot, s, segs, viewDepth, true),
4007
+ relations: buildRelations(dataRoot, s, segs)
4008
+ });
4009
+ } else if (url.pathname === "/api/schema") {
4010
+ sendJson(res, 200, projectSchema(dataRoot, s, segs, viewDepth, true));
4011
+ } else {
4012
+ notFound(res, url);
4013
+ }
4014
+ } catch (exc) {
4015
+ sendJson(res, 400, { error: exc.message || String(exc) });
4016
+ }
4017
+ };
4018
+ return Object.assign(handler, {
4019
+ ready,
4020
+ close: () => {
4021
+ closed = true;
4022
+ stopWatch?.();
4023
+ for (const r of sseClients) r.end();
4024
+ sseClients.clear();
4025
+ store0.close();
4026
+ }
4027
+ });
4028
+ }
4029
+ function tocType(s, p, row) {
4030
+ return typeName(s, p, row);
4031
+ }
4032
+ function concreteOf(dataRoot, segs, row) {
4033
+ if (row.type !== "mapping") return null;
4034
+ if (segs.some((g) => typeof g === "number")) return null;
4035
+ const abs = path7.resolve(dataRoot, ...segs.map(String));
4036
+ let st;
4037
+ try {
4038
+ st = fs6.statSync(abs);
4039
+ } catch {
4040
+ return null;
4041
+ }
4042
+ if (!st.isDirectory()) return null;
4043
+ return fs6.existsSync(path7.join(abs, ".yamlover")) ? "yamlover" : "dir";
4044
+ }
4045
+ var relKey = (label, other) => `${label ?? ""}\0${other}`;
4046
+ function downstreamEntries(s, p) {
4047
+ const isSet = !!s.node(p)?.meta?.set;
4048
+ let own = s.entries(p).filter((e2) => e2.kind !== "back");
4049
+ const seen = new Set(own.map((e2) => relKey(e2.label, e2.to)));
4050
+ if (isSet) {
4051
+ const kept = /* @__PURE__ */ new Set();
4052
+ own = own.filter((e2) => {
4053
+ const k = relKey(e2.label, e2.to);
4054
+ if (kept.has(k)) return false;
4055
+ kept.add(k);
4056
+ return true;
4057
+ });
4058
+ }
4059
+ const out = [...own];
4060
+ const backs = s.relationships(p).in.filter((e2) => e2.kind === "back" && e2.from).sort((a, b) => a.from < b.from ? -1 : a.from > b.from ? 1 : 0);
4061
+ for (const e2 of backs) {
4062
+ const k = relKey(e2.label, e2.from);
4063
+ if (e2.label != null || isSet) {
4064
+ if (seen.has(k)) continue;
4065
+ seen.add(k);
4066
+ }
4067
+ out.push({ to: e2.from, label: e2.label, pos: null, kind: "ref" });
4068
+ }
4069
+ return out;
4070
+ }
4071
+ function projectValue(dataRoot, s, segs, depth, top) {
4072
+ const p = storePath(segs);
4073
+ const row = s.node(p);
4074
+ const k = displayKind(s, p, row);
4075
+ if (!top && depth <= 0) return linkMarker(dataRoot, s, segs);
4076
+ if (k === "binary" && !top) return linkMarker(dataRoot, s, segs);
4077
+ if (k === "binary") return { size: row.size, format: row.format };
4078
+ const kids = downstreamEntries(s, p);
4079
+ const project = (c) => c.kind === "contain" ? projectValue(dataRoot, s, [...segs, c.label ?? c.pos ?? 0], depth - 1, false) : linkMarker(dataRoot, s, storePathToSegs(c.to));
4080
+ if (k === "array") return kids.map(project);
4081
+ if (k === "omni" || k === "mix") {
4082
+ const entries = kids.map((c) => ({ key: c.label, value: project(c) }));
4083
+ const marker = { kind: k, entries };
4084
+ if (k === "omni") marker.value = row.value;
4085
+ return { [MIXED_KEY]: marker };
4086
+ }
4087
+ if (k === "object") {
4088
+ const out = {};
4089
+ for (const c of kids) out[c.label ?? String(c.pos)] = project(c);
4090
+ return out;
4091
+ }
4092
+ return row.value;
4093
+ }
4094
+ function projectSchema(dataRoot, s, segs, depth, top) {
4095
+ const p = storePath(segs);
4096
+ const row = s.node(p);
4097
+ const k = displayKind(s, p, row);
4098
+ if ((k === "object" || k === "array" || k === "mix" || k === "omni") && depth <= 0) return linkMarker(dataRoot, s, segs);
4099
+ if (k === "binary" && !top) return linkMarker(dataRoot, s, segs);
4100
+ const schema = { type: typeName(s, p, row) };
4101
+ if (row.format) schema.format = row.format;
4102
+ const kids = downstreamEntries(s, p);
4103
+ const sub = (c) => c.kind === "contain" ? projectSchema(dataRoot, s, [...segs, c.label ?? c.pos ?? 0], depth - 1, false) : linkMarker(dataRoot, s, storePathToSegs(c.to));
4104
+ if (k === "object" || k === "mix" || k === "omni") {
4105
+ const props = {};
4106
+ for (const c of kids) props[c.label ?? `[${c.pos}]`] = sub(c);
4107
+ schema.properties = props;
4108
+ if (k === "omni") schema.value = row.value;
4109
+ } else if (k === "array") {
4110
+ schema.prefixItems = kids.map(sub);
4111
+ schema.items = false;
4112
+ } else if (k === "binary") {
4113
+ schema.const = { size: row.size, format: row.format };
4114
+ } else {
4115
+ schema.const = row.value;
4116
+ }
4117
+ const t2 = titleOf(s, p);
4118
+ if (t2) schema.title = t2;
4119
+ return schema;
4120
+ }
4121
+ function linkMarker(dataRoot, s, segs) {
4122
+ const p = storePath(segs);
4123
+ const row = s.node(p);
4124
+ const k = displayKind(s, p, row);
4125
+ const info = { kind: k, type: tocType(s, p, row), path: segsToStr(segs) };
4126
+ if (row.format) info.format = row.format;
4127
+ const concrete = concreteOf(dataRoot, segs, row);
4128
+ if (concrete) info.concrete = concrete;
4129
+ const title = titleOf(s, p);
4130
+ if (title) info.title = title;
4131
+ if (k === "binary") info.size = row.size;
4132
+ else if (k === "scalar") info.value = row.value;
4133
+ else if (k === "omni" || k === "mix") {
4134
+ info.count = ownedEntries(s, p).length;
4135
+ if (k === "omni") info.value = row.value;
4136
+ } else info.count = s.children(p).length;
4137
+ if (row.format === TAG_FORMAT) {
4138
+ const c = s.node(p + ":color")?.value;
4139
+ if (typeof c === "string") info.color = c;
4140
+ }
4141
+ return { [LINK_KEY]: info };
4142
+ }
4143
+ var segsEqual = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
4144
+ function scopedPath(s, src, currentDoc) {
4145
+ if (segsEqual(documentRootSegs(s, src), currentDoc)) return segsToStr(src.slice(currentDoc.length));
4146
+ return "::" + segsToStr(src).slice(1);
4147
+ }
4148
+ function buildRelations(dataRoot, s, segs) {
4149
+ const p = storePath(segs);
4150
+ const out = {};
4151
+ const put = (label, marker) => {
4152
+ let k = label;
4153
+ for (let i = 2; k in out; i++) k = `${label} (${i})`;
4154
+ out[k] = marker;
4155
+ };
4156
+ if (segs.length > 0) put("..", linkMarker(dataRoot, s, segs.slice(0, -1)));
4157
+ const currentDoc = documentRootSegs(s, segs);
4158
+ const { out: outEdges, in: inEdges } = s.relationships(p);
4159
+ const upstream = /* @__PURE__ */ new Map();
4160
+ const addUp = (src, label) => {
4161
+ if (src) upstream.set(relKey(label, src), src);
4162
+ };
4163
+ for (const e2 of inEdges) if (e2.kind === "ref") addUp(e2.from, e2.label);
4164
+ for (const e2 of outEdges) if (e2.kind === "back") addUp(e2.to, e2.label);
4165
+ for (const src of upstream.values()) {
4166
+ const segs2 = storePathToSegs(src);
4167
+ put(scopedPath(s, segs2, currentDoc), linkMarker(dataRoot, s, segs2));
4168
+ }
4169
+ return out;
4170
+ }
4171
+ function binaryContent(dataRoot, segs, row) {
4172
+ const file = path7.join(dataRoot, ...segs.map(String));
4173
+ const bytes = fs6.existsSync(file) ? fs6.readFileSync(file) : Buffer.alloc(0);
4174
+ return { [BINARY_KEY]: { format: row.format ?? null, size: row.size ?? bytes.length, base64: bytes.toString("base64") } };
4175
+ }
4176
+ function buildTree(dataRoot, s, segs, label, depth) {
4177
+ const p = storePath(segs);
4178
+ const row = s.node(p);
4179
+ const node = {
4180
+ path: segsToStr(segs),
4181
+ label,
4182
+ type: tocType(s, p, row),
4183
+ format: row.format ?? null,
4184
+ concrete: concreteOf(dataRoot, segs, row),
4185
+ hasChildren: s.hasChildren(p),
4186
+ children: []
4187
+ };
4188
+ if (s.hasChildren(p) && depth > 0) {
4189
+ for (const c of s.children(p)) {
4190
+ const seg = c.label ?? c.pos ?? 0;
4191
+ node.children.push(buildTree(dataRoot, s, [...segs, seg], labelFor(s, c.to, seg), depth - 1));
4192
+ }
4193
+ }
4194
+ return node;
4195
+ }
4196
+ function labelFor(s, p, keyOrIdx) {
4197
+ const t2 = titleOf(s, p);
4198
+ if (t2) return t2;
4199
+ return typeof keyOrIdx === "number" ? `[${keyOrIdx}]` : keyOrIdx;
4200
+ }
4201
+ var TAG_FORMAT = "x-yamlover-tag";
4202
+ function appliedTag(s, annStorePath) {
4203
+ const e2 = s.relationships(annStorePath).out.find(
4204
+ (t2) => t2.kind === "back" && t2.label === null && s.node(t2.to)?.format === TAG_FORMAT
4205
+ );
4206
+ if (!e2) return null;
4207
+ const segs = storePathToSegs(e2.to);
4208
+ const color = s.node(e2.to + ":color")?.value;
4209
+ return { path: segsToStr(segs), name: String(segs[segs.length - 1] ?? ""), color: typeof color === "string" ? color : null };
4210
+ }
4211
+ function annotationsFor(dataRoot, s, segs) {
4212
+ const p = storePath(segs);
4213
+ const out = [];
4214
+ for (const e2 of s.relationships(p).in) {
4215
+ if (e2.kind !== "ref") continue;
4216
+ const src = s.node(e2.from);
4217
+ if (src?.format !== "x-yamlover-annotation") continue;
4218
+ const aSegs = storePathToSegs(e2.from);
4219
+ out.push({
4220
+ path: segsToStr(aSegs),
4221
+ tag: appliedTag(s, e2.from),
4222
+ ...projectValue(dataRoot, s, aSegs, 6, true)
4223
+ });
4224
+ }
4225
+ return out;
4226
+ }
4227
+ function taggedMaterials(dataRoot, s, tagStorePath) {
4228
+ const seen = /* @__PURE__ */ new Set();
4229
+ const out = [];
4230
+ const backs = s.relationships(tagStorePath).in.filter((e2) => e2.kind === "back" && e2.from).sort((a, b) => a.from < b.from ? -1 : a.from > b.from ? 1 : 0);
4231
+ for (const e2 of backs) {
4232
+ let material = e2.from;
4233
+ if (s.node(e2.from)?.format === "x-yamlover-annotation") {
4234
+ const t2 = s.relationships(e2.from).out.find((o) => o.kind === "ref" && o.label === "target");
4235
+ if (!t2) continue;
4236
+ material = t2.to;
4237
+ }
4238
+ if (seen.has(material) || !s.node(material)) continue;
4239
+ seen.add(material);
4240
+ out.push(linkMarker(dataRoot, s, storePathToSegs(material)));
4241
+ }
4242
+ return out;
4243
+ }
4244
+ function pointerRaw(clientPath) {
4245
+ let out = "";
4246
+ for (const seg of strToSegs(clientPath)) {
4247
+ out += typeof seg === "number" ? `[${seg}]` : (out === "" ? "" : ":") + colonSegment(seg);
4248
+ }
4249
+ return "::" + out;
4250
+ }
4251
+ function yScalar(v) {
4252
+ return typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(String(v ?? ""));
4253
+ }
4254
+ function writeAnnotation(dataRoot, location, a) {
4255
+ if (!a?.target || !a?.tag) throw new Error("annotation needs a target and a tag");
4256
+ const dir = path7.resolve(dataRoot, ...strToSegs(location).map(String));
4257
+ const root = path7.resolve(dataRoot);
4258
+ if (dir !== root && !dir.startsWith(root + path7.sep)) throw new Error("annotation location escapes the data root");
4259
+ fs6.mkdirSync(dir, { recursive: true });
4260
+ const id = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
4261
+ const file = `${id}.yamlover`;
4262
+ const lines = [
4263
+ "!!<*yamlover/$defs/annotation>",
4264
+ `target: ${pointerToken(pointerRaw(a.target))}`,
4265
+ anchorToken(`${pointerRaw(a.tag)}[]`)
4266
+ // the applied tag holds me (ordinal path anchor)
4267
+ ];
4268
+ if (a.selector) lines.push("selector:", ...Object.entries(a.selector).map(([k, v]) => ` ${k}: ${yScalar(v)}`));
4269
+ if (a.description) lines.push(`description: ${yScalar(a.description)}`);
4270
+ lines.push(`created: ${(/* @__PURE__ */ new Date()).toISOString()}`, "");
4271
+ fs6.writeFileSync(path7.join(dir, file), lines.join("\n"));
4272
+ return `${location}:${file}`;
4273
+ }
4274
+ function writeTag(dataRoot, location, name) {
4275
+ if (/[/\\\r\n:]/.test(name)) throw new Error("a tag name cannot contain '/', '\\', ':' or line breaks");
4276
+ const root = path7.resolve(dataRoot);
4277
+ const dir = path7.resolve(dataRoot, ...strToSegs(location).map(String), ".yamlover");
4278
+ if (!dir.startsWith(root + path7.sep)) throw new Error("tags location escapes the data root");
4279
+ const file = path7.join(dir, "body.yamlover");
4280
+ const createdFile = !fs6.existsSync(file);
4281
+ const head = "# Named tags created from the annotation picker (settings.yamlover: tags.location).\n";
4282
+ const existing = createdFile ? head : fs6.readFileSync(file, "utf8");
4283
+ const body = (existing === "" || existing.endsWith("\n") ? existing : existing + "\n") + `${name}: !!<*yamlover/$defs/tag>
4284
+ `;
4285
+ const entries = parseYamlover(body, file).root.entries ?? [];
4286
+ const pos = entries.findIndex((e2) => e2.key === name);
4287
+ const entry = pos >= 0 ? entries[pos] : void 0;
4288
+ if (!entry || isPointer(entry.value) || entry.value.meta?.schema === void 0) {
4289
+ throw new Error(`cannot write a tag named ${JSON.stringify(name)}`);
4290
+ }
4291
+ fs6.mkdirSync(dir, { recursive: true });
4292
+ fs6.writeFileSync(file, body);
4293
+ return { node: entry.value, pos, file: [...strToSegs(location).map(String), ".yamlover", "body.yamlover"].join("/"), createdFile };
4294
+ }
4295
+ function deleteAnnotation(dataRoot, s, annPath) {
4296
+ const segs = strToSegs(annPath);
4297
+ if (!String(segs[segs.length - 1] ?? "").endsWith(".yamlover")) throw new Error("not an annotation file");
4298
+ if (s.node(storePath(segs))?.format !== "x-yamlover-annotation") throw new Error("not an annotation node");
4299
+ const root = path7.resolve(dataRoot);
4300
+ const file = path7.resolve(dataRoot, ...segs.map(String));
4301
+ if (!file.startsWith(root + path7.sep)) throw new Error("outside the served root");
4302
+ if (!fs6.existsSync(file) || fs6.statSync(file).isDirectory()) throw new Error("not an annotation file");
4303
+ fs6.rmSync(file, { force: true });
4304
+ }
4305
+ function handlePaste(dataRoot, s, input) {
4306
+ const segs = strToSegs(input.path || ":");
4307
+ const row = s.node(storePath(segs));
4308
+ if (!row) throw new Error(`no such node: ${input.path}`);
4309
+ if (input.rich != null) {
4310
+ const rich = parseRich(input.rich);
4311
+ if (row.format === "x-yamlover-chapter") return pasteRichIntoChapter(dataRoot, s, segs, rich);
4312
+ return pasteRichAsChapter(dataRoot, segs, rich);
4313
+ }
4314
+ if (typeof input.text === "string") {
4315
+ const text = input.text.replace(/\r\n?/g, "\n");
4316
+ if (text.trim().length === 0) throw new Error("empty paste (no text)");
4317
+ if (row.format === "x-yamlover-chapter") return pasteTextIntoChapter(dataRoot, s, segs, text);
4318
+ return pasteTextAsChapterFile(dataRoot, segs, text);
4319
+ }
4320
+ const bytes = Buffer.from(input.contentBase64 || "", "base64");
4321
+ if (bytes.length === 0) throw new Error("empty paste (no file bytes)");
4322
+ const name = sanitizeName(input.filename ?? "");
4323
+ if (row.format === "x-yamlover-chapter") return pasteIntoChapter(dataRoot, s, segs, name, bytes);
4324
+ const dirSegs = nearestDirSegs(dataRoot, segs);
4325
+ if (!dirSegs) throw new Error("no enclosing directory to paste into");
4326
+ const dir = path7.resolve(dataRoot, ...dirSegs.map(String));
4327
+ const final = uniqueName(dir, name);
4328
+ writeInside(dataRoot, dir, final, bytes);
4329
+ return { path: segsToStr([...dirSegs, final]), dir: segsToStr(dirSegs), open: dirSegs.length !== segs.length };
4330
+ }
4331
+ function nearestDirSegs(dataRoot, segs) {
4332
+ for (let i = segs.length; i >= 0; i--) {
4333
+ const sub = segs.slice(0, i);
4334
+ const abs = path7.resolve(dataRoot, ...sub.map(String));
4335
+ if (fs6.existsSync(abs) && fs6.statSync(abs).isDirectory()) return sub;
4336
+ }
4337
+ return null;
4338
+ }
4339
+ function chapterSource(dataRoot, s, segs) {
4340
+ const docSegs = documentRootSegs(s, segs);
4341
+ const docFs = path7.resolve(dataRoot, ...docSegs.map(String));
4342
+ const dirBacked = fs6.existsSync(docFs) && fs6.statSync(docFs).isDirectory();
4343
+ const bodyFile = dirBacked ? path7.join(docFs, ".yamlover", "body.yamlover") : docFs;
4344
+ if (!bodyFile.endsWith(".yamlover") || !fs6.existsSync(bodyFile)) {
4345
+ throw new Error("unsupported chapter source (need a .yamlover body)");
4346
+ }
4347
+ return { docSegs, bodyFile, dirBacked };
4348
+ }
4349
+ function pasteIntoChapter(dataRoot, s, segs, name, bytes) {
4350
+ const { docSegs, bodyFile, dirBacked } = chapterSource(dataRoot, s, segs);
4351
+ const writeDirSegs = dirBacked ? docSegs : docSegs.slice(0, -1);
4352
+ const writeDir = path7.resolve(dataRoot, ...writeDirSegs.map(String));
4353
+ const final = uniqueName(writeDir, name);
4354
+ writeInside(dataRoot, writeDir, final, bytes);
4355
+ const fileSegs = [...writeDirSegs, final];
4356
+ const pointer = dirBacked ? `*/${final}` : `*/${segsToStr(fileSegs)}`;
4357
+ const within = segs.slice(docSegs.length);
4358
+ const src = fs6.readFileSync(bodyFile, "utf8");
4359
+ fs6.writeFileSync(bodyFile, appendToList(src, within, "chunks", (indent) => [`${" ".repeat(indent)}- ${pointer}`]));
4360
+ return { path: segsToStr(fileSegs), chapter: segsToStr(segs), pointer };
4361
+ }
4362
+ function pasteTextIntoChapter(dataRoot, s, segs, text) {
4363
+ const { docSegs, bodyFile } = chapterSource(dataRoot, s, segs);
4364
+ const within = segs.slice(docSegs.length);
4365
+ const src = fs6.readFileSync(bodyFile, "utf8");
4366
+ fs6.writeFileSync(bodyFile, appendToList(src, within, "chunks", (indent) => textChunkLines(text, indent)));
4367
+ return { path: segsToStr(segs), chapter: segsToStr(segs) };
4368
+ }
4369
+ function pasteTextAsChapterFile(dataRoot, segs, text) {
4370
+ const dirSegs = nearestDirSegs(dataRoot, segs);
4371
+ if (!dirSegs) throw new Error("no enclosing directory to paste into");
4372
+ const dir = path7.resolve(dataRoot, ...dirSegs.map(String));
4373
+ const title = titleFromText(text);
4374
+ const final = uniqueName(dir, chapterFileName(title));
4375
+ const src = ["!!<*yamlover/$defs/chapter>", `title: ${JSON.stringify(title)}`, "chunks:", ...textChunkLines(text, 0), ""].join("\n");
4376
+ writeInside(dataRoot, dir, final, Buffer.from(src, "utf8"));
4377
+ return { path: segsToStr([...dirSegs, final]), dir: segsToStr(dirSegs), open: dirSegs.length !== segs.length };
4378
+ }
4379
+ function parseRich(raw, depth = 0) {
4380
+ if (depth > 8) throw new Error("rich paste: nesting too deep");
4381
+ const r = raw ?? {};
4382
+ const chunks = [];
4383
+ for (const c of Array.isArray(r.chunks) ? r.chunks : []) {
4384
+ if (typeof c?.text === "string") {
4385
+ if (c.text.trim()) chunks.push({ text: c.text.replace(/\r\n?/g, "\n") });
4386
+ continue;
4387
+ }
4388
+ const f = c?.file;
4389
+ if (f && typeof f.name === "string") {
4390
+ const bytes = Buffer.from(String(f.contentBase64 ?? ""), "base64");
4391
+ if (bytes.length === 0) throw new Error("rich paste: empty file chunk");
4392
+ chunks.push({ name: sanitizeName(f.name), bytes });
4393
+ continue;
4394
+ }
4395
+ throw new Error("rich paste: a chunk must be {text} or {file}");
4396
+ }
4397
+ const children = (Array.isArray(r.children) ? r.children : []).map((k) => {
4398
+ const sub = parseRich(k, depth + 1);
4399
+ const title = typeof k?.title === "string" ? String(k.title).trim() : "";
4400
+ return { ...sub, title: title || "Untitled" };
4401
+ });
4402
+ if (depth === 0 && chunks.length === 0 && children.length === 0) throw new Error("empty rich paste");
4403
+ return { title: typeof r.title === "string" && r.title.trim() ? r.title.trim() : null, chunks, children };
4404
+ }
4405
+ function richItemLines(item, indent, pointerFor) {
4406
+ if ("text" in item) return textChunkLines(item.text, indent);
4407
+ return [`${" ".repeat(indent)}- ${pointerFor(item.name, item.bytes)}`];
4408
+ }
4409
+ function richChildLines(node, indent, pointerFor) {
4410
+ const pad = " ".repeat(indent);
4411
+ const lines = [`${pad}- title: ${JSON.stringify(node.title)}`];
4412
+ if (node.chunks.length) lines.push(`${pad} chunks:`, ...node.chunks.flatMap((c) => richItemLines(c, indent + 2, pointerFor)));
4413
+ if (node.children.length) lines.push(`${pad} children:`, ...node.children.flatMap((k) => richChildLines(k, indent + 2, pointerFor)));
4414
+ return lines;
4415
+ }
4416
+ function pasteRichIntoChapter(dataRoot, s, segs, rich) {
4417
+ const { docSegs, bodyFile, dirBacked } = chapterSource(dataRoot, s, segs);
4418
+ const writeDirSegs = dirBacked ? docSegs : docSegs.slice(0, -1);
4419
+ const writeDir = path7.resolve(dataRoot, ...writeDirSegs.map(String));
4420
+ const files = [];
4421
+ const pointerFor = (name, bytes) => {
4422
+ const final = uniqueName(writeDir, name);
4423
+ writeInside(dataRoot, writeDir, final, bytes);
4424
+ files.push(segsToStr([...writeDirSegs, final]));
4425
+ return dirBacked ? `*/${final}` : `*/${segsToStr([...writeDirSegs, final])}`;
4426
+ };
4427
+ const within = segs.slice(docSegs.length);
4428
+ let src = fs6.readFileSync(bodyFile, "utf8");
4429
+ if (rich.chunks.length) src = appendToList(src, within, "chunks", (ind) => rich.chunks.flatMap((c) => richItemLines(c, ind, pointerFor)));
4430
+ if (rich.children.length) src = appendToList(src, within, "children", (ind) => rich.children.flatMap((k) => richChildLines(k, ind, pointerFor)));
4431
+ fs6.writeFileSync(bodyFile, src);
4432
+ return { path: segsToStr(segs), chapter: segsToStr(segs), files };
4433
+ }
4434
+ function pasteRichAsChapter(dataRoot, segs, rich) {
4435
+ const dirSegs = nearestDirSegs(dataRoot, segs);
4436
+ if (!dirSegs) throw new Error("no enclosing directory to paste into");
4437
+ const dir = path7.resolve(dataRoot, ...dirSegs.map(String));
4438
+ if (!rich.title && rich.chunks.length === 0 && rich.children.length === 1) rich = rich.children[0];
4439
+ const firstText = rich.chunks.find((c) => "text" in c);
4440
+ const title = rich.title ?? (firstText ? titleFromText(firstText.text) : rich.children[0]?.title ?? "Pasted content");
4441
+ if (!richHasFiles(rich)) {
4442
+ const final = uniqueName(dir, chapterFileName(title));
4443
+ const src2 = renderChapterSource(title, rich, () => {
4444
+ throw new Error("unreachable: no files");
4445
+ });
4446
+ writeInside(dataRoot, dir, final, Buffer.from(src2, "utf8"));
4447
+ return { path: segsToStr([...dirSegs, final]), dir: segsToStr(dirSegs), open: dirSegs.length !== segs.length };
4448
+ }
4449
+ const name = uniqueName(dir, chapterFileName(title).replace(/\.yamlover$/, ""));
4450
+ const chDir = path7.join(dir, name);
4451
+ if (!path7.resolve(chDir).startsWith(path7.resolve(dataRoot) + path7.sep)) throw new Error("target escapes the data root");
4452
+ fs6.mkdirSync(path7.join(chDir, ".yamlover"), { recursive: true });
4453
+ const pointerFor = (fname, bytes) => {
4454
+ const final = uniqueName(chDir, fname);
4455
+ writeInside(dataRoot, chDir, final, bytes);
4456
+ return `*/${final}`;
4457
+ };
4458
+ const src = renderChapterSource(title, rich, pointerFor);
4459
+ writeInside(dataRoot, path7.join(chDir, ".yamlover"), "body.yamlover", Buffer.from(src, "utf8"));
4460
+ return { path: segsToStr([...dirSegs, name]), dir: segsToStr(dirSegs), open: dirSegs.length !== segs.length };
4461
+ }
4462
+ function renderChapterSource(title, rich, pointerFor) {
4463
+ const lines = ["!!<*yamlover/$defs/chapter>", `title: ${JSON.stringify(title)}`];
4464
+ if (rich.chunks.length) lines.push("chunks:", ...rich.chunks.flatMap((c) => richItemLines(c, 0, pointerFor)));
4465
+ if (rich.children.length) lines.push("children:", ...rich.children.flatMap((k) => richChildLines(k, 0, pointerFor)));
4466
+ return lines.join("\n") + "\n";
4467
+ }
4468
+ function richHasFiles(rich) {
4469
+ return rich.chunks.some((c) => "bytes" in c) || rich.children.some(richHasFiles);
4470
+ }
4471
+ function titleFromText(text) {
4472
+ const first = text.split("\n").find((l) => l.trim().length > 0)?.trim() ?? "";
4473
+ const t2 = first.replace(/^#{1,6}\s+/, "").trim();
4474
+ return (t2.length > 80 ? t2.slice(0, 79).trimEnd() + "\u2026" : t2) || "Pasted text";
4475
+ }
4476
+ function chapterFileName(title) {
4477
+ const base = title.replace(/[^\p{L}\p{N} ._-]+/gu, " ").replace(/\s+/g, " ").trim().slice(0, 60).trim().replace(/^\.+/, "");
4478
+ return `${base || "pasted"}.yamlover`;
4479
+ }
4480
+ function sanitizeName(raw) {
4481
+ const base = path7.basename(String(raw || "")).replace(/[^A-Za-z0-9._-]/g, "_").replace(/^\.+/, "");
4482
+ return base || "pasted";
4483
+ }
4484
+ function uniqueName(dir, name) {
4485
+ if (!fs6.existsSync(path7.join(dir, name))) return name;
4486
+ const ext = path7.extname(name);
4487
+ const stem = name.slice(0, name.length - ext.length);
4488
+ for (let i = 1; ; i++) {
4489
+ const cand = `${stem}-${i}${ext}`;
4490
+ if (!fs6.existsSync(path7.join(dir, cand))) return cand;
4491
+ }
4492
+ }
4493
+ function writeInside(dataRoot, dir, name, bytes) {
4494
+ const root = path7.resolve(dataRoot);
4495
+ const target = path7.resolve(dir, name);
4496
+ if (target !== root && !target.startsWith(root + path7.sep)) throw new Error("target escapes the data root");
4497
+ fs6.writeFileSync(target, bytes);
4498
+ }
4499
+ var indentOf = (line) => {
4500
+ let i = 0;
4501
+ while (line[i] === " ") i++;
4502
+ return i;
4503
+ };
4504
+ var isContentLine = (line) => {
4505
+ const t2 = line.trim();
4506
+ return t2.length > 0 && !t2.startsWith("#");
4507
+ };
4508
+ function textChunkLines(text, indent) {
4509
+ const pad = " ".repeat(indent);
4510
+ const first = text.split("\n").find((l) => l.trim().length > 0);
4511
+ if (!first || /^\s/.test(first)) return [`${pad}- ${JSON.stringify(text)}`];
4512
+ const body = text.endsWith("\n") ? text.slice(0, -1) : text;
4513
+ const head = text.endsWith("\n") ? "|" : "|-";
4514
+ return [`${pad}- ${head}`, ...body.split("\n").map((l) => l.trim().length ? `${pad} ${l}` : "")];
4515
+ }
4516
+ function appendToList(text, chapterPath, key, renderItems) {
4517
+ const lines = text.split("\n");
4518
+ let lo = 0;
4519
+ let hi = lines.length;
4520
+ let indent = firstContentIndent(lines);
4521
+ for (let i = 0; i < chapterPath.length; i += 2) {
4522
+ const idx = Number(chapterPath[i + 1]);
4523
+ const kids = findKeyLine(lines, lo, hi, indent, "children");
4524
+ if (kids < 0) throw new Error(`no 'children:' at indent ${indent}`);
4525
+ const items = seqItems(lines, kids + 1, hi, indent);
4526
+ if (!(idx >= 0 && idx < items.length)) throw new Error(`children[${idx}] out of range (${items.length})`);
4527
+ hi = idx + 1 < items.length ? items[idx + 1] : seqEnd(lines, kids + 1, hi, indent);
4528
+ lo = items[idx] + 1;
4529
+ indent += 2;
4530
+ }
4531
+ const keyLine = findKeyLine(lines, lo, hi, indent, key);
4532
+ if (keyLine < 0) {
4533
+ const end = trimBack(lines, lo - 1, hi);
4534
+ lines.splice(end, 0, `${" ".repeat(indent)}${key}:`, ...renderItems(indent));
4535
+ } else {
4536
+ const end = seqEnd(lines, keyLine + 1, hi, indent);
4537
+ lines.splice(end, 0, ...renderItems(indent));
4538
+ }
4539
+ return lines.join("\n");
4540
+ }
4541
+ function firstContentIndent(lines) {
4542
+ for (const l of lines) if (isContentLine(l)) return indentOf(l);
4543
+ return 0;
4544
+ }
4545
+ function findKeyLine(lines, lo, hi, indent, key) {
4546
+ for (let i = lo; i < hi; i++) {
4547
+ if (!isContentLine(lines[i])) continue;
4548
+ const ind = indentOf(lines[i]);
4549
+ if (ind < indent) return -1;
4550
+ if (ind !== indent) continue;
4551
+ const t2 = lines[i].trim();
4552
+ if (t2 === `${key}:` || t2.startsWith(`${key}:`)) return i;
4553
+ }
4554
+ return -1;
4555
+ }
4556
+ function seqItems(lines, from, hi, indent) {
4557
+ const out = [];
4558
+ for (let i = from; i < hi; i++) {
4559
+ if (!isContentLine(lines[i])) continue;
4560
+ const ind = indentOf(lines[i]);
4561
+ if (ind < indent) break;
4562
+ if (ind !== indent) continue;
4563
+ const t2 = lines[i].trim();
4564
+ if (t2 === "-" || t2.startsWith("- ")) out.push(i);
4565
+ else break;
4566
+ }
4567
+ return out;
4568
+ }
4569
+ function seqEnd(lines, from, hi, indent) {
4570
+ let last = from;
4571
+ for (let i = from; i < hi; i++) {
4572
+ if (!isContentLine(lines[i])) continue;
4573
+ const ind = indentOf(lines[i]);
4574
+ if (ind < indent) return trimBack(lines, last, i);
4575
+ if (ind === indent) {
4576
+ const t2 = lines[i].trim();
4577
+ if (t2 === "-" || t2.startsWith("- ")) {
4578
+ last = i;
4579
+ continue;
4580
+ }
4581
+ return trimBack(lines, last, i);
4582
+ }
4583
+ last = i;
4584
+ }
4585
+ return trimBack(lines, last, hi);
4586
+ }
4587
+ function trimBack(lines, lastItemLine, end) {
4588
+ let e2 = end;
4589
+ while (e2 > lastItemLine + 1 && !isContentLine(lines[e2 - 1])) e2--;
4590
+ return e2;
4591
+ }
4592
+ function readBody(req) {
4593
+ return new Promise((resolve3, reject) => {
4594
+ const chunks = [];
4595
+ req.on("data", (c) => chunks.push(c));
4596
+ req.on("end", () => {
4597
+ try {
4598
+ resolve3(JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"));
4599
+ } catch (e2) {
4600
+ reject(e2);
4601
+ }
4602
+ });
4603
+ req.on("error", reject);
4604
+ });
4605
+ }
4606
+ function documentRootSegs(s, segs) {
4607
+ for (let i = segs.length; i >= 0; i--) {
4608
+ const anc = segs.slice(0, i);
4609
+ if (s.node(storePath(anc))?.meta?.documentRoot) return anc;
4610
+ }
4611
+ return [];
4612
+ }
4613
+ function documentPath(s, segs) {
4614
+ return segsToStr(documentRootSegs(s, segs));
4615
+ }
4616
+ function titleOf(s, p) {
4617
+ const titlePath = (p === ":" ? "" : p) + ":title";
4618
+ const t2 = s.node(titlePath);
4619
+ if (t2 && t2.type === "scalar" && !s.hasChildren(titlePath) && t2.value != null) return String(t2.value);
4620
+ return null;
4621
+ }
4622
+ var PATH_TOKEN = /\[\d+\]|[^:\[\]]+/g;
4623
+ function segsToStr(segs) {
4624
+ return segs.map((seg) => typeof seg === "number" ? `[${seg}]` : `:${encodeURIComponent(seg)}`).join("") || ":";
4625
+ }
4626
+ function strToSegs(str) {
4627
+ const out = [];
4628
+ for (const tok of str.match(PATH_TOKEN) || []) out.push(/^\[\d+\]$/.test(tok) ? Number(tok.slice(1, -1)) : safeDecode(tok));
4629
+ return out;
4630
+ }
4631
+ function storePath(segs) {
4632
+ return segs.map((seg) => typeof seg === "number" ? `[${seg}]` : `:${seg}`).join("") || ":";
4633
+ }
4634
+ function storePathToSegs(p) {
4635
+ const out = [];
4636
+ for (const tok of p.match(PATH_TOKEN) || []) out.push(/^\[\d+\]$/.test(tok) ? Number(tok.slice(1, -1)) : tok);
4637
+ return out;
4638
+ }
4639
+ function safeDecode(s) {
4640
+ try {
4641
+ return decodeURIComponent(s);
4642
+ } catch {
4643
+ return s;
4644
+ }
4645
+ }
4646
+ var EXT_CT = {
4647
+ ".png": "image/png",
4648
+ ".jpg": "image/jpeg",
4649
+ ".jpeg": "image/jpeg",
4650
+ ".gif": "image/gif",
4651
+ ".webp": "image/webp",
4652
+ ".svg": "image/svg+xml",
4653
+ ".bmp": "image/bmp",
4654
+ ".ico": "image/x-icon",
4655
+ ".pdf": "application/pdf",
4656
+ ".tiff": "image/tiff",
4657
+ ".tif": "image/tiff",
4658
+ ".html": "text/html",
4659
+ ".md": "text/markdown",
4660
+ ".csv": "text/csv",
4661
+ ".epub": "application/epub+zip"
4662
+ };
4663
+ function formatFromExt(file) {
4664
+ return EXT_CT[path7.extname(file).toLowerCase()] ?? null;
4665
+ }
4666
+ function parseDepth(raw) {
4667
+ if (raw == null || raw === "") return null;
4668
+ const n = Number(raw);
4669
+ return Number.isInteger(n) && n >= 0 ? n : null;
4670
+ }
4671
+ function sendJson(res, status, body) {
4672
+ res.statusCode = status;
4673
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
4674
+ res.end(JSON.stringify(body, null, 2));
4675
+ }
4676
+ function notFound(res, url) {
4677
+ sendJson(res, 404, { error: `no such node/endpoint: ${url.pathname}?${url.searchParams}` });
4678
+ }
4679
+ export {
4680
+ createHandlers
4681
+ };