@jxsuite/parser 2.0.1 → 4.0.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.
@@ -58,6 +58,77 @@ var __esm = (fn, res, err) => () => {
58
58
  };
59
59
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
60
60
 
61
+ // ../../packages/schema/src/guards.ts
62
+ function isString(value) {
63
+ return typeof value === "string";
64
+ }
65
+ function isNumber(value) {
66
+ return typeof value === "number";
67
+ }
68
+ function isBoolean(value) {
69
+ return typeof value === "boolean";
70
+ }
71
+ function isNonNullObject(value) {
72
+ return typeof value === "object" && value !== null;
73
+ }
74
+ function isJsonObject(value) {
75
+ return typeof value === "object" && value !== null && !Array.isArray(value);
76
+ }
77
+ function isRef(value) {
78
+ return isJsonObject(value) && typeof value.$ref === "string";
79
+ }
80
+ function isNodeObject(value) {
81
+ return isJsonObject(value);
82
+ }
83
+ function isPlainTagName(tagName) {
84
+ return typeof tagName === "string";
85
+ }
86
+ function isTagExpression(tagName) {
87
+ if (typeof tagName !== "object" || tagName === null || Array.isArray(tagName) || !isJsonObject(tagName.$expression)) {
88
+ return false;
89
+ }
90
+ const expression = tagName.$expression;
91
+ if (expression.operator === "?:") {
92
+ return typeof expression.value === "string" && typeof expression.initial === "string";
93
+ }
94
+ if (expression.operator === "switch") {
95
+ return isJsonObject(expression.cases) && Object.values(expression.cases).every((tag) => isString(tag)) && typeof expression.default === "string";
96
+ }
97
+ return false;
98
+ }
99
+ function tagNameCandidates(tagName) {
100
+ if (isPlainTagName(tagName)) {
101
+ return [tagName];
102
+ }
103
+ if (!isTagExpression(tagName)) {
104
+ return [];
105
+ }
106
+ const expression = tagName.$expression;
107
+ if (expression.operator === "?:") {
108
+ return [...new Set([expression.value, expression.initial])];
109
+ }
110
+ return [...new Set([...Object.values(expression.cases), expression.default])];
111
+ }
112
+ var SCHEMA_KEYWORDS;
113
+ var init_guards = __esm(() => {
114
+ SCHEMA_KEYWORDS = new Set([
115
+ "type",
116
+ "enum",
117
+ "minimum",
118
+ "maximum",
119
+ "minLength",
120
+ "maxLength",
121
+ "pattern",
122
+ "format",
123
+ "items",
124
+ "properties",
125
+ "required",
126
+ "description",
127
+ "title",
128
+ "$comment"
129
+ ]);
130
+ });
131
+
61
132
  // src/alerts.ts
62
133
  function normalizeAlerts(config, where = "alerts") {
63
134
  if (config === false) {
@@ -68,7 +139,7 @@ function normalizeAlerts(config, where = "alerts") {
68
139
  if (config === undefined || config === true) {
69
140
  return { disabled, types };
70
141
  }
71
- if (typeof config !== "object" || config === null || Array.isArray(config)) {
142
+ if (!isJsonObject(config)) {
72
143
  throw new TypeError(`${where}: expected an object mapping alert types to element names, or false`);
73
144
  }
74
145
  for (const [type, element] of Object.entries(config)) {
@@ -81,7 +152,7 @@ function normalizeAlerts(config, where = "alerts") {
81
152
  disabled.add(key);
82
153
  } else if (element === true) {
83
154
  types.set(key, null);
84
- } else if (typeof element === "string" && TAG_NAME.test(element)) {
155
+ } else if (isString(element) && TAG_NAME.test(element)) {
85
156
  types.set(key, element);
86
157
  } else {
87
158
  throw new TypeError(`${where}.${type}: expected an element name such as "doc-note", true or false, got ${JSON.stringify(element)}`);
@@ -92,7 +163,7 @@ function normalizeAlerts(config, where = "alerts") {
92
163
  function inlineText(nodes) {
93
164
  let out = "";
94
165
  for (const node of nodes) {
95
- if (typeof node.value === "string") {
166
+ if (node.value !== undefined) {
96
167
  out += node.value;
97
168
  } else if (node.children) {
98
169
  out += inlineText(node.children);
@@ -105,7 +176,7 @@ function textNode(value) {
105
176
  }
106
177
  function markerOf(paragraph) {
107
178
  const [first] = paragraph.children ?? [];
108
- if (first?.type !== "text" || typeof first.value !== "string") {
179
+ if (first?.type !== "text" || first.value === undefined) {
109
180
  return null;
110
181
  }
111
182
  const match = MARKER.exec(first.value);
@@ -131,7 +202,7 @@ function splitMarker(paragraph, settings) {
131
202
  const take = (node) => {
132
203
  if (inTitle && node.type === "break") {
133
204
  inTitle = false;
134
- } else if (inTitle && node.type === "text" && typeof node.value === "string") {
205
+ } else if (inTitle && node.type === "text" && node.value !== undefined) {
135
206
  const lineEnd = LINE_END.exec(node.value);
136
207
  if (lineEnd === null) {
137
208
  title.push(node);
@@ -188,6 +259,7 @@ function transformAlerts(tree, settings, onUnknown) {
188
259
  }
189
260
  var DEFAULT_ALERT_TITLES, ALERT_NODE = "jxAlert", TAG_NAME, MARKER, LINE_END;
190
261
  var init_alerts = __esm(() => {
262
+ init_guards();
191
263
  DEFAULT_ALERT_TITLES = {
192
264
  caution: "Caution",
193
265
  important: "Important",
@@ -334,7 +406,7 @@ function compileExclude(patterns) {
334
406
  const dirs = [];
335
407
  let everything = false;
336
408
  for (const pattern of patterns) {
337
- if (typeof pattern !== "string" || pattern.trim() === "") {
409
+ if (!isString(pattern) || pattern.trim() === "") {
338
410
  throw new TypeError(`"exclude" entries must be non-empty strings, got ${JSON.stringify(pattern)}`);
339
411
  }
340
412
  if (pattern.startsWith("!")) {
@@ -408,21 +480,21 @@ function readField(data, key) {
408
480
  if (!key.includes(".")) {
409
481
  return;
410
482
  }
411
- let current = data;
412
- for (const part of key.split(".")) {
413
- if (!isPlainObject(current) || !Object.hasOwn(current, part)) {
414
- return;
415
- }
416
- current = current[part];
483
+ return readPath(data, key.split("."));
484
+ }
485
+ function readPath(holder, [part, ...rest]) {
486
+ if (part === undefined || !isPlainObject(holder) || !Object.hasOwn(holder, part)) {
487
+ return;
417
488
  }
418
- return current;
489
+ const value = holder[part];
490
+ return rest.length === 0 ? value : readPath(value, rest);
419
491
  }
420
492
  function order(actual, operand) {
421
493
  const a = comparable(actual);
422
- if (typeof a === "number" && typeof operand === "number") {
494
+ if (isNumber(a) && isNumber(operand)) {
423
495
  return a - operand;
424
496
  }
425
- if (typeof a === "string" && typeof operand === "string") {
497
+ if (isString(a) && isString(operand)) {
426
498
  return a < operand ? -1 : a > operand ? 1 : 0;
427
499
  }
428
500
  return;
@@ -444,13 +516,13 @@ function operatorTest(where, op, operand) {
444
516
  return op === "$in" ? hit : (actual) => !hit(actual);
445
517
  }
446
518
  case "$exists": {
447
- if (typeof operand !== "boolean") {
519
+ if (!isBoolean(operand)) {
448
520
  throw new TypeError(`${where}.$exists: expected true or false, got ${JSON.stringify(operand)}`);
449
521
  }
450
522
  return (actual) => (actual !== undefined && actual !== null) === operand;
451
523
  }
452
524
  default: {
453
- if (typeof operand !== "string" && typeof operand !== "number") {
525
+ if (!isString(operand) && !isNumber(operand)) {
454
526
  throw new TypeError(`${where}.${op}: expected a string or number, got ${JSON.stringify(operand)}`);
455
527
  }
456
528
  const test = (diff) => op === "$gt" ? diff > 0 : op === "$gte" ? diff >= 0 : op === "$lt" ? diff < 0 : diff <= 0;
@@ -508,6 +580,7 @@ function compileWhere(where) {
508
580
  }
509
581
  var nothing = () => {}, REGEXP_SPECIAL, MAX_STAR_RUNS = 4, OPERATORS;
510
582
  var init_content_rules = __esm(() => {
583
+ init_guards();
511
584
  REGEXP_SPECIAL = /[$()*+.?[\\\]^{|}]/g;
512
585
  OPERATORS = new Set(["$eq", "$ne", "$in", "$nin", "$exists", "$gt", "$gte", "$lt", "$lte"]);
513
586
  });
@@ -25568,14 +25641,14 @@ var init_decode_codepoint = __esm(() => {
25568
25641
  });
25569
25642
 
25570
25643
  // ../../node_modules/parse5/node_modules/entities/dist/esm/decode.js
25571
- function isNumber(code) {
25644
+ function isNumber2(code) {
25572
25645
  return code >= CharCodes.ZERO && code <= CharCodes.NINE;
25573
25646
  }
25574
25647
  function isHexadecimalCharacter(code) {
25575
25648
  return code >= CharCodes.UPPER_A && code <= CharCodes.UPPER_F || code >= CharCodes.LOWER_A && code <= CharCodes.LOWER_F;
25576
25649
  }
25577
25650
  function isAsciiAlphaNumeric(code) {
25578
- return code >= CharCodes.UPPER_A && code <= CharCodes.UPPER_Z || code >= CharCodes.LOWER_A && code <= CharCodes.LOWER_Z || isNumber(code);
25651
+ return code >= CharCodes.UPPER_A && code <= CharCodes.UPPER_Z || code >= CharCodes.LOWER_A && code <= CharCodes.LOWER_Z || isNumber2(code);
25579
25652
  }
25580
25653
  function isEntityInAttributeInvalidEnd(code) {
25581
25654
  return code === CharCodes.EQUALS || isAsciiAlphaNumeric(code);
@@ -25649,7 +25722,7 @@ class EntityDecoder {
25649
25722
  const startIndex = offset;
25650
25723
  while (offset < input.length) {
25651
25724
  const char = input.charCodeAt(offset);
25652
- if (isNumber(char) || isHexadecimalCharacter(char)) {
25725
+ if (isNumber2(char) || isHexadecimalCharacter(char)) {
25653
25726
  offset += 1;
25654
25727
  } else {
25655
25728
  this.addToNumericResult(input, startIndex, offset, 16);
@@ -25663,7 +25736,7 @@ class EntityDecoder {
25663
25736
  const startIndex = offset;
25664
25737
  while (offset < input.length) {
25665
25738
  const char = input.charCodeAt(offset);
25666
- if (isNumber(char)) {
25739
+ if (isNumber2(char)) {
25667
25740
  offset += 1;
25668
25741
  } else {
25669
25742
  this.addToNumericResult(input, startIndex, offset, 10);
@@ -33099,11 +33172,11 @@ function convertHastNode(node) {
33099
33172
  if (whitespace(node)) {
33100
33173
  return null;
33101
33174
  }
33102
- return node.value ?? null;
33175
+ return node.value;
33103
33176
  }
33104
33177
  if (node.type === "element") {
33105
33178
  const el = { tagName: node.tagName };
33106
- if (node.properties && Object.keys(node.properties).length > 0) {
33179
+ if (Object.keys(node.properties).length > 0) {
33107
33180
  const { style, attrs } = hastPropsToJx(node.properties);
33108
33181
  if (Object.keys(attrs).length > 0) {
33109
33182
  el.attributes = attrs;
@@ -33112,8 +33185,8 @@ function convertHastNode(node) {
33112
33185
  el.style = style;
33113
33186
  }
33114
33187
  }
33115
- const kids = node.children ? convertHastChildren(node.children) : [];
33116
- if (kids.length === 1 && typeof kids[0] === "string") {
33188
+ const kids = convertHastChildren(node.children);
33189
+ if (kids.length === 1 && isString(kids[0])) {
33117
33190
  [el.textContent] = kids;
33118
33191
  } else if (kids.length > 0) {
33119
33192
  el.children = kids;
@@ -33131,7 +33204,7 @@ function hastPropsToJx(properties) {
33131
33204
  }
33132
33205
  const info = find(html3, key);
33133
33206
  const name = info.attribute;
33134
- if (name === "style" && typeof value === "string") {
33207
+ if (name === "style" && isString(value)) {
33135
33208
  parseInlineStyle(value, style);
33136
33209
  continue;
33137
33210
  }
@@ -33162,6 +33235,7 @@ var init_html_to_jx = __esm(() => {
33162
33235
  init_hast_util_from_html();
33163
33236
  init_hast_util_whitespace();
33164
33237
  init_property_information();
33238
+ init_guards();
33165
33239
  });
33166
33240
 
33167
33241
  // src/transpile.ts
@@ -33177,21 +33251,24 @@ function jxKey(key) {
33177
33251
  function expandDotPaths(attrs) {
33178
33252
  const result = {};
33179
33253
  for (const [key, value] of Object.entries(attrs)) {
33180
- const dotIndex = key.indexOf(".");
33181
- if (dotIndex === -1) {
33254
+ const lastDot = key.lastIndexOf(".");
33255
+ if (lastDot === -1) {
33182
33256
  result[jxKey(key)] = value;
33183
33257
  continue;
33184
33258
  }
33185
- const segments = key.split(".");
33186
33259
  let target = result;
33187
- for (let i = 0;i < segments.length - 1; i++) {
33188
- const seg = jxKey(segments[i]);
33189
- if (!(seg in target) || typeof target[seg] !== "object") {
33190
- target[seg] = {};
33260
+ for (const segment of key.slice(0, lastDot).split(".")) {
33261
+ const seg = jxKey(segment);
33262
+ const existing = seg in target ? target[seg] : undefined;
33263
+ if (isNonNullObject(existing)) {
33264
+ target = existing;
33265
+ } else {
33266
+ const nested = {};
33267
+ target[seg] = nested;
33268
+ target = nested;
33191
33269
  }
33192
- target = target[seg];
33193
33270
  }
33194
- target[jxKey(segments.at(-1))] = value;
33271
+ target[jxKey(key.slice(lastDot + 1))] = value;
33195
33272
  }
33196
33273
  return result;
33197
33274
  }
@@ -33212,22 +33289,34 @@ function applyStyleKeyMapping(styleObj) {
33212
33289
  }
33213
33290
  function routeAttributes(attrs) {
33214
33291
  const expanded = expandDotPaths(attrs);
33215
- if (expanded.style && typeof expanded.style === "object") {
33216
- expanded.style = applyStyleKeyMapping(expanded.style);
33292
+ const { style } = expanded;
33293
+ if (isNonNullObject(style)) {
33294
+ expanded.style = applyStyleKeyMapping(style);
33217
33295
  }
33218
33296
  const props = {};
33219
33297
  const attributes = {};
33220
33298
  for (const [key, value] of Object.entries(expanded)) {
33221
33299
  if (HTML_ATTR_PATTERN.test(key)) {
33222
- attributes[key] = value;
33300
+ attributes[key] = attributeValue(value);
33223
33301
  } else {
33224
33302
  props[key] = value;
33225
33303
  }
33226
33304
  }
33227
33305
  return { attributes, props };
33228
33306
  }
33307
+ function attributeValue(value) {
33308
+ return value;
33309
+ }
33310
+ function setContent(el, children) {
33311
+ const [only] = children;
33312
+ if (children.length === 1 && isString(only)) {
33313
+ el.textContent = only;
33314
+ } else if (children.length > 0) {
33315
+ el.children = children;
33316
+ }
33317
+ }
33229
33318
  function mdastNodeToJx(node) {
33230
- if (!node || typeof node !== "object") {
33319
+ if (!node) {
33231
33320
  return null;
33232
33321
  }
33233
33322
  if (node.type === "yaml" || node.type === "toml") {
@@ -33248,7 +33337,7 @@ function mdastNodeToJx(node) {
33248
33337
  }
33249
33338
  return null;
33250
33339
  }
33251
- const tagFn = JX_TAG_MAP[node.type];
33340
+ const tagFn = JX_TAG_MAP.get(node.type);
33252
33341
  if (!tagFn) {
33253
33342
  return null;
33254
33343
  }
@@ -33264,12 +33353,7 @@ function mdastNodeToJx(node) {
33264
33353
  case "listItem":
33265
33354
  case "tableRow":
33266
33355
  case "tableCell": {
33267
- const children = convertChildren(node.children ?? []);
33268
- if (children.length === 1 && typeof children[0] === "string") {
33269
- [el.textContent] = children;
33270
- } else if (children.length > 0) {
33271
- el.children = children;
33272
- }
33356
+ setContent(el, convertChildren(node.children ?? []));
33273
33357
  break;
33274
33358
  }
33275
33359
  case "inlineCode": {
@@ -33277,34 +33361,18 @@ function mdastNodeToJx(node) {
33277
33361
  break;
33278
33362
  }
33279
33363
  case "link": {
33280
- {
33281
- const linkAttrs = {
33282
- href: node.url ?? ""
33283
- };
33284
- if (node.title) {
33285
- linkAttrs.title = node.title;
33286
- }
33287
- el.attributes = linkAttrs;
33288
- }
33289
- {
33290
- const children = convertChildren(node.children ?? []);
33291
- if (children.length === 1 && typeof children[0] === "string") {
33292
- [el.textContent] = children;
33293
- } else if (children.length > 0) {
33294
- el.children = children;
33295
- }
33364
+ el.attributes = { href: node.url ?? "" };
33365
+ if (node.title) {
33366
+ el.attributes.title = node.title;
33296
33367
  }
33368
+ setContent(el, convertChildren(node.children ?? []));
33297
33369
  break;
33298
33370
  }
33299
33371
  case "image": {
33300
- const imageAttrs = {
33301
- alt: node.alt ?? "",
33302
- src: node.url ?? ""
33303
- };
33372
+ el.attributes = { alt: node.alt ?? "", src: node.url ?? "" };
33304
33373
  if (node.title) {
33305
- imageAttrs.title = node.title;
33374
+ el.attributes.title = node.title;
33306
33375
  }
33307
- el.attributes = imageAttrs;
33308
33376
  break;
33309
33377
  }
33310
33378
  case "list": {
@@ -33317,13 +33385,11 @@ function mdastNodeToJx(node) {
33317
33385
  break;
33318
33386
  }
33319
33387
  case "code": {
33320
- el.children = [
33321
- {
33322
- tagName: "code",
33323
- textContent: node.value ?? null,
33324
- ...node.lang ? { className: `language-${node.lang}` } : {}
33325
- }
33326
- ];
33388
+ const code = { tagName: "code", textContent: node.value ?? null };
33389
+ if (node.lang) {
33390
+ code.className = `language-${node.lang}`;
33391
+ }
33392
+ el.children = [code];
33327
33393
  break;
33328
33394
  }
33329
33395
  case "thematicBreak":
@@ -33333,16 +33399,21 @@ function mdastNodeToJx(node) {
33333
33399
  case "table": {
33334
33400
  const rows = convertChildren(node.children ?? []);
33335
33401
  const [headerRow] = rows;
33336
- if (headerRow && typeof headerRow === "object" && Array.isArray(headerRow.children)) {
33402
+ if (isNonNullObject(headerRow) && Array.isArray(headerRow.children)) {
33337
33403
  for (const cell of headerRow.children) {
33338
- if (cell && typeof cell === "object" && cell.tagName === "td") {
33404
+ if (isNonNullObject(cell) && cell.tagName === "td") {
33339
33405
  cell.tagName = "th";
33340
33406
  }
33341
33407
  }
33342
33408
  }
33343
- const thead = rows.length > 0 ? { children: [rows[0]], tagName: "thead" } : null;
33344
- const tbody = rows.length > 1 ? { children: rows.slice(1), tagName: "tbody" } : null;
33345
- el.children = [thead, tbody].filter(Boolean);
33409
+ const sections = [];
33410
+ if (headerRow !== undefined) {
33411
+ sections.push({ children: [headerRow], tagName: "thead" });
33412
+ }
33413
+ if (rows.length > 1) {
33414
+ sections.push({ children: rows.slice(1), tagName: "tbody" });
33415
+ }
33416
+ el.children = sections;
33346
33417
  break;
33347
33418
  }
33348
33419
  default: {
@@ -33354,10 +33425,7 @@ function mdastNodeToJx(node) {
33354
33425
  function alertToJx(node) {
33355
33426
  const body = convertChildren(node.children ?? []);
33356
33427
  if (node.element) {
33357
- const attributes = { "data-alert": node.alert };
33358
- if (node.titleNodes) {
33359
- attributes["data-title"] = inlineText(node.titleNodes).trim();
33360
- }
33428
+ const attributes = node.titleNodes ? { "data-alert": node.alert, "data-title": inlineText(node.titleNodes).trim() } : { "data-alert": node.alert };
33361
33429
  const el = { attributes, tagName: node.element };
33362
33430
  if (body.length > 0) {
33363
33431
  el.children = body;
@@ -33367,8 +33435,9 @@ function alertToJx(node) {
33367
33435
  const title = { className: "jx-alert-title", tagName: "p" };
33368
33436
  if (node.titleNodes) {
33369
33437
  const inline = convertChildren(node.titleNodes);
33370
- if (inline.length === 1 && typeof inline[0] === "string") {
33371
- [title.textContent] = inline;
33438
+ const [only] = inline;
33439
+ if (inline.length === 1 && isString(only)) {
33440
+ title.textContent = only;
33372
33441
  } else {
33373
33442
  title.children = inline;
33374
33443
  }
@@ -33383,13 +33452,14 @@ function alertToJx(node) {
33383
33452
  };
33384
33453
  }
33385
33454
  function directiveToJx(node) {
33386
- if (PROTOTYPE_DIRECTIVE_NAMES.has(node.name)) {
33387
- return prototypeDirectiveToJx(node);
33455
+ const name = node.name;
33456
+ if (PROTOTYPE_DIRECTIVE_NAMES.has(name)) {
33457
+ return prototypeDirectiveToJx(node, name);
33388
33458
  }
33389
- const el = { tagName: node.name };
33459
+ const el = { tagName: name };
33390
33460
  if (node.attributes && Object.keys(node.attributes).length > 0) {
33391
33461
  const { props, attributes } = routeAttributes(node.attributes);
33392
- const isCustomElement = node.name.includes("-");
33462
+ const isCustomElement = name.includes("-");
33393
33463
  if (isCustomElement) {
33394
33464
  for (const [key, value] of Object.entries(props)) {
33395
33465
  if (key === "style" || key === "children" || key === "textContent" || key === "innerHTML" || key.startsWith("$")) {
@@ -33398,7 +33468,7 @@ function directiveToJx(node) {
33398
33468
  if (!el.attributes) {
33399
33469
  el.attributes = {};
33400
33470
  }
33401
- el.attributes[key] = value;
33471
+ el.attributes[key] = attributeValue(value);
33402
33472
  }
33403
33473
  }
33404
33474
  } else {
@@ -33409,7 +33479,7 @@ function directiveToJx(node) {
33409
33479
  if (!el.attributes) {
33410
33480
  el.attributes = {};
33411
33481
  }
33412
- el.attributes[key] = value;
33482
+ el.attributes[key] = attributeValue(value);
33413
33483
  }
33414
33484
  }
33415
33485
  }
@@ -33419,12 +33489,7 @@ function directiveToJx(node) {
33419
33489
  }
33420
33490
  if (node.type === "textDirective") {
33421
33491
  if (node.children && node.children.length > 0) {
33422
- const children = convertChildren(node.children ?? []);
33423
- if (children.length === 1 && typeof children[0] === "string") {
33424
- [el.textContent] = children;
33425
- } else if (children.length > 0) {
33426
- el.children = children;
33427
- }
33492
+ setContent(el, convertChildren(node.children));
33428
33493
  }
33429
33494
  return el;
33430
33495
  }
@@ -33433,7 +33498,7 @@ function directiveToJx(node) {
33433
33498
  }
33434
33499
  if (node.children && node.children.length > 0) {
33435
33500
  const jxChildren = [];
33436
- const isPhrasingParent = PHRASING_ELEMENTS.has(node.name);
33501
+ const isPhrasingParent = PHRASING_ELEMENTS.has(name);
33437
33502
  for (const child of node.children) {
33438
33503
  if (isPhrasingParent && child.type === "paragraph") {
33439
33504
  for (const inline of child.children ?? []) {
@@ -33459,16 +33524,14 @@ function directiveToJx(node) {
33459
33524
  }
33460
33525
  }
33461
33526
  }
33462
- if (el.children && typeof el.children === "object" && !Array.isArray(el.children)) {} else if (jxChildren.length === 1 && typeof jxChildren[0] === "string") {
33463
- [el.textContent] = jxChildren;
33464
- } else if (jxChildren.length > 0) {
33465
- el.children = jxChildren;
33527
+ if (!isNonNullObject(el.children) || Array.isArray(el.children)) {
33528
+ setContent(el, jxChildren);
33466
33529
  }
33467
33530
  }
33468
33531
  return el;
33469
33532
  }
33470
- function prototypeDirectiveToJx(node) {
33471
- const el = { $prototype: node.name };
33533
+ function prototypeDirectiveToJx(node, name) {
33534
+ const el = { $prototype: name };
33472
33535
  if (node.attributes && Object.keys(node.attributes).length > 0) {
33473
33536
  const expanded = expandDotPaths(node.attributes);
33474
33537
  for (const [key, value] of Object.entries(expanded)) {
@@ -33479,7 +33542,7 @@ function prototypeDirectiveToJx(node) {
33479
33542
  }
33480
33543
  }
33481
33544
  const children = convertChildren(node.children ?? []);
33482
- const template = children.find((c) => c != null && typeof c === "object");
33545
+ const template = children.find((c) => !isString(c));
33483
33546
  if (template) {
33484
33547
  el.map = template;
33485
33548
  }
@@ -33489,17 +33552,19 @@ function convertChildren(children) {
33489
33552
  if (!children) {
33490
33553
  return [];
33491
33554
  }
33492
- return children.flatMap((n) => mdastNodeToJx(n)).filter((c) => c != null);
33555
+ return children.flatMap((n) => mdastNodeToJx(n) ?? []);
33493
33556
  }
33494
33557
  function transpileJxMarkdown(source) {
33495
33558
  const processor = unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(import_remark_parse_frontmatter.default).use(remarkGfm).use(remarkDirective);
33496
33559
  const tree = processor.parse(source);
33497
33560
  const vfile = { data: {} };
33498
33561
  processor.runSync(tree, vfile);
33499
- const frontmatter = vfile.data?.frontmatter ?? {};
33562
+ const frontmatter = vfile.data.frontmatter ?? {};
33500
33563
  const doc = {};
33501
- for (const [key, value] of Object.entries(frontmatter)) {
33502
- doc[key] = value;
33564
+ if (!isNumber(frontmatter) && !isBoolean(frontmatter)) {
33565
+ for (const [key, value] of Object.entries(frontmatter)) {
33566
+ doc[key] = value;
33567
+ }
33503
33568
  }
33504
33569
  const bodyNodes = tree.children.filter((n) => n.type !== "yaml" && n.type !== "toml");
33505
33570
  const children = [];
@@ -33523,10 +33588,10 @@ function slugifyHeading(text) {
33523
33588
  return text.normalize("NFC").toLowerCase().replaceAll(/[^\p{L}\p{N}_\s-]/gu, "").replaceAll(/\s+/gu, "-").replaceAll(/-+/g, "-").replaceAll(/^-|-$/g, "");
33524
33589
  }
33525
33590
  function jxNodeText(node) {
33526
- if (typeof node === "string") {
33591
+ if (isString(node)) {
33527
33592
  return node;
33528
33593
  }
33529
- let out = typeof node.textContent === "string" ? node.textContent : "";
33594
+ let out = isString(node.textContent) ? node.textContent : "";
33530
33595
  if (Array.isArray(node.children)) {
33531
33596
  for (const child of node.children) {
33532
33597
  out += jxNodeText(child);
@@ -33539,13 +33604,13 @@ function assignHeadingIds(children) {
33539
33604
  const seen = new Map;
33540
33605
  function walk(nodes) {
33541
33606
  for (const node of nodes) {
33542
- if (typeof node !== "object" || node === null) {
33607
+ if (!isNonNullObject(node)) {
33543
33608
  continue;
33544
33609
  }
33545
- const tag = typeof node.tagName === "string" ? node.tagName : "";
33610
+ const tag = isString(node.tagName) ? node.tagName : "";
33546
33611
  if (tag && HEADING_TAGS.has(tag)) {
33547
33612
  const text = jxNodeText(node).trim();
33548
- let id = typeof node.id === "string" && node.id.length > 0 ? node.id : "";
33613
+ let id = isString(node.id) && node.id.length > 0 ? node.id : "";
33549
33614
  if (id) {
33550
33615
  seen.set(id, (seen.get(id) ?? 0) + 1);
33551
33616
  } else {
@@ -33575,6 +33640,7 @@ var init_transpile = __esm(() => {
33575
33640
  init_remark_gfm();
33576
33641
  init_remark_directive();
33577
33642
  init_html_to_jx();
33643
+ init_guards();
33578
33644
  init_alerts();
33579
33645
  init_html_to_jx();
33580
33646
  import_remark_parse_frontmatter = __toESM(require_dist2(), 1);
@@ -33642,25 +33708,25 @@ var init_transpile = __esm(() => {
33642
33708
  "summary",
33643
33709
  "dt"
33644
33710
  ]);
33645
- JX_TAG_MAP = {
33646
- blockquote: () => "blockquote",
33647
- break: () => "br",
33648
- code: () => "pre",
33649
- delete: () => "del",
33650
- emphasis: () => "em",
33651
- heading: (n) => `h${n.depth}`,
33652
- image: () => "img",
33653
- inlineCode: () => "code",
33654
- link: () => "a",
33655
- list: (n) => n.ordered ? "ol" : "ul",
33656
- listItem: () => "li",
33657
- paragraph: () => "p",
33658
- strong: () => "strong",
33659
- table: () => "table",
33660
- tableCell: (n) => n.isHeader ? "th" : "td",
33661
- tableRow: () => "tr",
33662
- thematicBreak: () => "hr"
33663
- };
33711
+ JX_TAG_MAP = new Map([
33712
+ ["blockquote", () => "blockquote"],
33713
+ ["break", () => "br"],
33714
+ ["code", () => "pre"],
33715
+ ["delete", () => "del"],
33716
+ ["emphasis", () => "em"],
33717
+ ["heading", (n) => `h${n.depth}`],
33718
+ ["image", () => "img"],
33719
+ ["inlineCode", () => "code"],
33720
+ ["link", () => "a"],
33721
+ ["list", (n) => n.ordered ? "ol" : "ul"],
33722
+ ["listItem", () => "li"],
33723
+ ["paragraph", () => "p"],
33724
+ ["strong", () => "strong"],
33725
+ ["table", () => "table"],
33726
+ ["tableCell", (n) => n.isHeader ? "th" : "td"],
33727
+ ["tableRow", () => "tr"],
33728
+ ["thematicBreak", () => "hr"]
33729
+ ]);
33664
33730
  HEADING_TAGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
33665
33731
  });
33666
33732
 
@@ -33720,10 +33786,14 @@ function walkFiles(root, accept, exclude) {
33720
33786
  var init_walk = () => {};
33721
33787
 
33722
33788
  // src/content-loader.ts
33723
- import { existsSync, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
33789
+ import { existsSync, readFileSync, readdirSync as readdirSync2, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
33724
33790
  import { basename, dirname, extname, relative, resolve } from "node:path";
33725
33791
 
33726
33792
  // ../../packages/schema/src/asset-paths.ts
33793
+ init_guards();
33794
+ var CONTENT_SECTION_KEY = "content";
33795
+ var SAFE_CONTENT_TYPE_NAME = /^[\w.~-]+$/;
33796
+ var LOCALE_PLACEHOLDER = "{locale}";
33727
33797
  function normalizeDir(value) {
33728
33798
  const forward = value.replaceAll("\\", "/");
33729
33799
  return forward.length > 1 && forward.endsWith("/") ? forward.slice(0, -1) : forward;
@@ -33758,6 +33828,7 @@ function assetUrlFor(mounts, absolutePath) {
33758
33828
  var URL_TERMINATOR = String.raw`"'\s<>()\\`;
33759
33829
 
33760
33830
  // ../../packages/schema/src/locale.ts
33831
+ init_guards();
33761
33832
  var RTL_SCRIPTS = new Set([
33762
33833
  "Adlm",
33763
33834
  "Arab",
@@ -33800,7 +33871,7 @@ var RTL_SCRIPTS = new Set([
33800
33871
  "Yezi"
33801
33872
  ]);
33802
33873
  function canonicalizeLocale(tag) {
33803
- if (typeof tag !== "string" || tag.trim() === "") {
33874
+ if (!isString(tag) || tag.trim() === "") {
33804
33875
  return null;
33805
33876
  }
33806
33877
  try {
@@ -33813,7 +33884,7 @@ var DEFAULT_ROUTING = "prefix-except-default";
33813
33884
  function resolveI18n(projectConfig) {
33814
33885
  const raw = projectConfig.i18n;
33815
33886
  const errors = [];
33816
- if (!raw || typeof raw !== "object") {
33887
+ if (!isNonNullObject(raw)) {
33817
33888
  return { errors, i18n: null };
33818
33889
  }
33819
33890
  const declared = Array.isArray(raw.locales) ? raw.locales : [];
@@ -33857,9 +33928,14 @@ function localeUrlPrefix(locale, i18n) {
33857
33928
  }
33858
33929
 
33859
33930
  // src/content-loader.ts
33931
+ init_guards();
33860
33932
  init_alerts();
33861
33933
 
33934
+ // src/content-links.ts
33935
+ init_guards();
33936
+
33862
33937
  // src/content-routes.ts
33938
+ init_guards();
33863
33939
  init_content_rules();
33864
33940
  function slugifySegment(text) {
33865
33941
  return text.normalize("NFKD").replaceAll(/(?<=\p{Script=Latin})\p{M}+/gu, "").normalize("NFC").replaceAll("&", " and ").replaceAll(/['’]/g, "").toLowerCase().replaceAll(/[^\p{L}\p{M}\p{N}]+/gu, "-").replaceAll(/^-+|-+$/g, "");
@@ -33874,13 +33950,13 @@ var TRANSFORMS = {
33874
33950
  upper: (value) => value.toUpperCase()
33875
33951
  };
33876
33952
  function parseRouteTemplate(source, where) {
33877
- if (typeof source !== "string" || !source.startsWith("/")) {
33953
+ if (!isString(source) || !source.startsWith("/")) {
33878
33954
  throw new TypeError(`${where}: a route template is a site-absolute path starting with "/", got ${JSON.stringify(source)}`);
33879
33955
  }
33880
33956
  const tokens = [];
33881
33957
  let last = 0;
33882
33958
  for (const match of source.matchAll(/\{([^{}]*)\}/g)) {
33883
- const index = match.index ?? 0;
33959
+ const { index } = match;
33884
33960
  if (index > last) {
33885
33961
  tokens.push({ literal: source.slice(last, index) });
33886
33962
  }
@@ -33926,10 +34002,10 @@ function directoryOf(path) {
33926
34002
  return slash === -1 ? "" : path.slice(0, slash);
33927
34003
  }
33928
34004
  function textOf(value) {
33929
- if (typeof value === "string") {
34005
+ if (isString(value)) {
33930
34006
  return value.trim() === "" ? null : value;
33931
34007
  }
33932
- if (typeof value === "number" || typeof value === "boolean") {
34008
+ if (isNumber(value) || isBoolean(value)) {
33933
34009
  return String(value);
33934
34010
  }
33935
34011
  if (value instanceof Date && !Number.isNaN(value.getTime())) {
@@ -34182,10 +34258,10 @@ function contentOf(anchor) {
34182
34258
  if (Array.isArray(anchor.children)) {
34183
34259
  return anchor.children;
34184
34260
  }
34185
- if (typeof anchor.innerHTML === "string") {
34261
+ if (anchor.innerHTML !== undefined) {
34186
34262
  return [{ innerHTML: anchor.innerHTML, tagName: "span" }];
34187
34263
  }
34188
- return typeof anchor.textContent === "string" ? [anchor.textContent] : [];
34264
+ return isString(anchor.textContent) ? [anchor.textContent] : [];
34189
34265
  }
34190
34266
  function rewriteLinks(nodes, from, index, report) {
34191
34267
  for (let i = 0;i < nodes.length; i++) {
@@ -34194,11 +34270,11 @@ function rewriteLinks(nodes, from, index, report) {
34194
34270
  rewriteLinks(node, from, index, report);
34195
34271
  continue;
34196
34272
  }
34197
- if (typeof node !== "object" || node === null) {
34273
+ if (!isNonNullObject(node)) {
34198
34274
  continue;
34199
34275
  }
34200
34276
  const href = node.tagName === "a" ? node.attributes?.href : undefined;
34201
- if (typeof href === "string") {
34277
+ if (isString(href)) {
34202
34278
  const resolution = resolveHref(href, from, index);
34203
34279
  if ("problem" in resolution) {
34204
34280
  report({ href, reason: resolution.problem });
@@ -34222,9 +34298,10 @@ function rewriteLinks(nodes, from, index, report) {
34222
34298
  init_content_rules();
34223
34299
 
34224
34300
  // src/dates.ts
34301
+ init_guards();
34225
34302
  var DATE_FORMATS = ["date", "date-time"];
34226
34303
  function isDateFormat(format) {
34227
- return typeof format === "string" && DATE_FORMATS.includes(format);
34304
+ return DATE_FORMATS.some((candidate) => candidate === format);
34228
34305
  }
34229
34306
  var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
34230
34307
  var RFC_3339 = /^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?([Zz]|[+-]\d{2}:\d{2})?$/;
@@ -34241,7 +34318,7 @@ function parseDateValue(value, format) {
34241
34318
  if (value instanceof Date) {
34242
34319
  return Number.isNaN(value.getTime()) ? null : format === "date" ? toDateOnly(value) : toInstant(value);
34243
34320
  }
34244
- if (typeof value !== "string") {
34321
+ if (!isString(value)) {
34245
34322
  return null;
34246
34323
  }
34247
34324
  const text = value.trim();
@@ -34274,7 +34351,7 @@ function coerceEntryDates(entries, schema, contentTypeName) {
34274
34351
  if (!properties) {
34275
34352
  return warnings;
34276
34353
  }
34277
- const dateFields = Object.entries(properties).map(([field, def]) => [field, def.format]).filter((pair) => isDateFormat(pair[1]));
34354
+ const dateFields = Object.entries(properties).flatMap(([field, def]) => isDateFormat(def.format) ? [[field, def.format]] : []);
34278
34355
  if (dateFields.length === 0) {
34279
34356
  return warnings;
34280
34357
  }
@@ -34305,9 +34382,7 @@ function coerceEntryDates(entries, schema, contentTypeName) {
34305
34382
  return warnings;
34306
34383
  }
34307
34384
  function parseComparable(a, b) {
34308
- const aDate = typeof a === "string" && (DATE_ONLY.test(a) || RFC_3339.test(a));
34309
- const bDate = typeof b === "string" && (DATE_ONLY.test(b) || RFC_3339.test(b));
34310
- if (aDate && bDate) {
34385
+ if (isString(a) && isCoercedDate(a) && isString(b) && isCoercedDate(b)) {
34311
34386
  return [a, b];
34312
34387
  }
34313
34388
  return [Number(a), Number(b)];
@@ -34315,23 +34390,24 @@ function parseComparable(a, b) {
34315
34390
 
34316
34391
  // src/content-loader.ts
34317
34392
  init_walk();
34393
+
34394
+ // src/content-data.ts
34395
+ async function entriesOf(data, name) {
34396
+ return await data?.load?.(name) ?? data?.get(name);
34397
+ }
34398
+
34399
+ // src/content-loader.ts
34318
34400
  function loadJSONEntries(filePath) {
34319
34401
  const raw = JSON.parse(readFileSync(filePath, "utf8"));
34320
34402
  if (Array.isArray(raw)) {
34321
- return raw.map((item, i) => ({
34322
- body: null,
34323
- data: item,
34324
- id: item.id ?? `${basename(filePath, ".json")}-${i}`
34325
- }));
34403
+ return raw.map((item, i) => jsonEntry(item, `${basename(filePath, ".json")}-${i}`));
34326
34404
  }
34327
- const rawObj = raw;
34328
- return [
34329
- {
34330
- body: null,
34331
- data: rawObj,
34332
- id: rawObj.id ?? basename(filePath, ".json")
34333
- }
34334
- ];
34405
+ return [jsonEntry(raw, basename(filePath, ".json"))];
34406
+ }
34407
+ function jsonEntry(item, fallbackId) {
34408
+ const data = item;
34409
+ const id = data.id ?? fallbackId;
34410
+ return { body: null, data, id };
34335
34411
  }
34336
34412
  function discoverJSONFiles(resolvedSource, exclude) {
34337
34413
  if (extname(resolvedSource)) {
@@ -34339,15 +34415,24 @@ function discoverJSONFiles(resolvedSource, exclude) {
34339
34415
  }
34340
34416
  return walkFiles(resolvedSource, (name) => name.toLowerCase().endsWith(".json"), exclude);
34341
34417
  }
34342
- var LOCALE_PLACEHOLDER = "{locale}";
34343
34418
  function localesForExpansion(projectConfig) {
34344
34419
  return resolveI18n(projectConfig ?? {}).i18n?.locales ?? [];
34345
34420
  }
34421
+ function existsWithSpelling(root, path) {
34422
+ const full = resolve(root, path);
34423
+ if (!existsSync(full)) {
34424
+ return false;
34425
+ }
34426
+ const wanted = full.split(/[\\/]/).filter(Boolean);
34427
+ const actual = realpathSync2.native(full).split(/[\\/]/).filter(Boolean);
34428
+ const tail = actual.slice(-wanted.length);
34429
+ return wanted.every((segment, index) => segment === tail[index]);
34430
+ }
34346
34431
  function localeSource(root, source, locale) {
34347
34432
  const candidates = [locale, locale.toLowerCase()];
34348
34433
  for (const spelling of candidates) {
34349
34434
  const path = source.split(LOCALE_PLACEHOLDER).join(spelling);
34350
- if (existsSync(resolve(root, path))) {
34435
+ if (existsWithSpelling(root, path)) {
34351
34436
  return path;
34352
34437
  }
34353
34438
  }
@@ -34360,8 +34445,6 @@ function localeSource(root, source, locale) {
34360
34445
  }
34361
34446
  return source.split(LOCALE_PLACEHOLDER).join(locale);
34362
34447
  }
34363
- var SECTION_KEY = "content";
34364
- var SAFE_TYPE_NAME = /^[\w.~-]+$/;
34365
34448
  var ASSET_KEYS = ["src", "poster"];
34366
34449
  function isNonRelativeRef(value) {
34367
34450
  return value === "" || value.startsWith("/") || value.startsWith("#") || value.includes("${") || /^[a-z][\w+.-]*:/i.test(value);
@@ -34396,28 +34479,31 @@ function contentAssetMounts(section, root, projectConfig) {
34396
34479
  return filter === undefined ? {} : { filter };
34397
34480
  };
34398
34481
  for (const [name, contentTypeDef] of Object.entries(section ?? {})) {
34399
- const source = contentTypeDef?.source;
34482
+ if (!isNonNullObject(contentTypeDef)) {
34483
+ continue;
34484
+ }
34485
+ const { source } = contentTypeDef;
34400
34486
  if (!source || source.startsWith("http://") || source.startsWith("https://")) {
34401
34487
  continue;
34402
34488
  }
34403
34489
  if (source.includes(LOCALE_PLACEHOLDER)) {
34404
34490
  for (const locale of locales) {
34405
- if (!SAFE_TYPE_NAME.test(locale)) {
34491
+ if (!SAFE_CONTENT_TYPE_NAME.test(locale)) {
34406
34492
  continue;
34407
34493
  }
34408
34494
  const dir = resolve(root, localeSource(root, source, locale)).split("\\").join("/");
34409
- if (SAFE_TYPE_NAME.test(name) && existsSync(dir) && statSync2(dir).isDirectory()) {
34495
+ if (SAFE_CONTENT_TYPE_NAME.test(name) && existsSync(dir) && statSync2(dir).isDirectory()) {
34410
34496
  mounts.push({
34411
34497
  dir,
34412
- urlPrefix: `/${SECTION_KEY}/${name}/${locale}`,
34498
+ urlPrefix: `/${CONTENT_SECTION_KEY}/${name}/${locale}`,
34413
34499
  ...filterOf(contentTypeDef)
34414
34500
  });
34415
34501
  }
34416
34502
  }
34417
34503
  continue;
34418
34504
  }
34419
- if (!SAFE_TYPE_NAME.test(name)) {
34420
- console.warn(`Content type "${name}": name is not URL-safe — its source directory is not published ` + `at /${SECTION_KEY}/${name}, so content-relative asset references stay unresolved.`);
34505
+ if (!SAFE_CONTENT_TYPE_NAME.test(name)) {
34506
+ console.warn(`Content type "${name}": name is not URL-safe — its source directory is not published ` + `at /${CONTENT_SECTION_KEY}/${name}, so content-relative asset references stay unresolved.`);
34421
34507
  continue;
34422
34508
  }
34423
34509
  const resolvedSource = resolve(root, source).split("\\").join("/");
@@ -34426,7 +34512,7 @@ function contentAssetMounts(section, root, projectConfig) {
34426
34512
  }
34427
34513
  mounts.push({
34428
34514
  dir: resolvedSource,
34429
- urlPrefix: `/${SECTION_KEY}/${name}`,
34515
+ urlPrefix: `/${CONTENT_SECTION_KEY}/${name}`,
34430
34516
  ...filterOf(contentTypeDef)
34431
34517
  });
34432
34518
  }
@@ -34464,15 +34550,14 @@ function rewriteNodeAssets(node, entryDir, mount, onProblem) {
34464
34550
  }
34465
34551
  return;
34466
34552
  }
34467
- if (!node || typeof node !== "object") {
34553
+ if (!isNonNullObject(node)) {
34468
34554
  return;
34469
34555
  }
34470
- const record = node;
34471
- const attributes = record.attributes;
34556
+ const { attributes } = node;
34472
34557
  for (const key of ASSET_KEYS) {
34473
- for (const holder of [record, attributes]) {
34558
+ for (const holder of [node, attributes]) {
34474
34559
  const value = holder?.[key];
34475
- if (typeof value === "string") {
34560
+ if (holder && isString(value)) {
34476
34561
  const rewritten = rewriteRef(value, entryDir, mount, onProblem);
34477
34562
  if (rewritten) {
34478
34563
  holder[key] = rewritten;
@@ -34480,7 +34565,7 @@ function rewriteNodeAssets(node, entryDir, mount, onProblem) {
34480
34565
  }
34481
34566
  }
34482
34567
  }
34483
- const { children } = record;
34568
+ const { children } = node;
34484
34569
  if (Array.isArray(children)) {
34485
34570
  for (const child of children) {
34486
34571
  rewriteNodeAssets(child, entryDir, mount, onProblem);
@@ -34489,22 +34574,30 @@ function rewriteNodeAssets(node, entryDir, mount, onProblem) {
34489
34574
  }
34490
34575
  function rewriteEntryAssets(entries, filePath, mount, typeName, schema) {
34491
34576
  const entryDir = dirname(filePath);
34492
- const uriFields = Object.entries(schema?.properties ?? {}).filter(([, def]) => def?.format === "uri-reference" || def?.items?.format === "uri-reference").map(([field]) => field);
34577
+ const uriFields = Object.entries(schema?.properties ?? {}).flatMap(([field, def]) => isNonNullObject(def) && (def.format === "uri-reference" || def.items?.format === "uri-reference") ? [field] : []);
34493
34578
  for (const entry of entries) {
34494
- const onProblem = (value, problem) => console.warn(problem === "missing" ? `Content type "${typeName}": entry "${entry.id}" references missing asset "${value}"` : `Content type "${typeName}": entry "${entry.id}" references "${value}", which the ` + `collection does not publish (it is excluded, hidden or a document), so the ` + `reference is left as written`);
34495
- for (const child of entry.$children ?? []) {
34496
- rewriteNodeAssets(child, entryDir, mount, onProblem);
34497
- }
34579
+ const onProblem = assetProblemReporter(typeName, entry);
34580
+ rewriteBodyAssets(entry, filePath, mount, typeName);
34498
34581
  for (const field of uriFields) {
34499
34582
  const value = entry.data[field];
34500
- if (typeof value === "string") {
34583
+ if (isString(value)) {
34501
34584
  entry.data[field] = rewriteRef(value, entryDir, mount, onProblem) ?? value;
34502
34585
  } else if (Array.isArray(value)) {
34503
- entry.data[field] = value.map((item) => typeof item === "string" ? rewriteRef(item, entryDir, mount, onProblem) ?? item : item);
34586
+ entry.data[field] = value.map((item) => isString(item) ? rewriteRef(item, entryDir, mount, onProblem) ?? item : item);
34504
34587
  }
34505
34588
  }
34506
34589
  }
34507
34590
  }
34591
+ function assetProblemReporter(typeName, entry) {
34592
+ return (value, problem) => console.warn(problem === "missing" ? `Content type "${typeName}": entry "${entry.id}" references missing asset "${value}"` : `Content type "${typeName}": entry "${entry.id}" references "${value}", which the ` + `collection does not publish (it is excluded, hidden or a document), so the ` + `reference is left as written`);
34593
+ }
34594
+ function rewriteBodyAssets(entry, filePath, mount, typeName) {
34595
+ const entryDir = dirname(filePath);
34596
+ const onProblem = assetProblemReporter(typeName, entry);
34597
+ for (const child of entry.$children ?? []) {
34598
+ rewriteNodeAssets(child, entryDir, mount, onProblem);
34599
+ }
34600
+ }
34508
34601
  function loadContentConfig(projectRoot, projectConfig) {
34509
34602
  const contentDir = resolve(projectRoot, "content");
34510
34603
  const config = { content: projectConfig?.content ?? {} };
@@ -34512,48 +34605,63 @@ function loadContentConfig(projectRoot, projectConfig) {
34512
34605
  }
34513
34606
  async function loadContentSection(section, root, formats, projectConfig) {
34514
34607
  const contentTypes = new Map;
34515
- const mounts = new Map(contentAssetMounts(section, root, projectConfig).map((mount) => [
34516
- mount.urlPrefix.slice(`/${SECTION_KEY}/`.length),
34608
+ const mounts = sectionMounts(section, root, projectConfig);
34609
+ for (const name of Object.keys(section)) {
34610
+ contentTypes.set(name, await loadSectionType(name, { formats, mounts, projectConfig, root, section }));
34611
+ }
34612
+ return contentTypes;
34613
+ }
34614
+ function sectionMounts(section, root, projectConfig) {
34615
+ return new Map(contentAssetMounts(section, root, projectConfig).map((mount) => [
34616
+ mount.urlPrefix.slice(`/${CONTENT_SECTION_KEY}/`.length),
34517
34617
  mount
34518
34618
  ]));
34619
+ }
34620
+ async function loadSectionType(name, source) {
34621
+ const { section, root, formats, projectConfig, mounts, onDeferred } = source;
34622
+ const contentTypeDef = section[name];
34623
+ if (!contentTypeDef) {
34624
+ return [];
34625
+ }
34626
+ const deferral = onDeferred ? { deferBody: true, onDeferred } : {};
34627
+ const typeSource = contentTypeDef.source;
34628
+ if (!isString(typeSource) || !typeSource.includes(LOCALE_PLACEHOLDER)) {
34629
+ return await loadContentType(name, contentTypeDef, root, formats, {
34630
+ mount: mounts.get(name),
34631
+ projectConfig,
34632
+ ...deferral
34633
+ });
34634
+ }
34519
34635
  const locales = localesForExpansion(projectConfig);
34520
- for (const [name, contentTypeDef] of Object.entries(section)) {
34521
- const source = contentTypeDef?.source;
34522
- if (typeof source !== "string" || !source.includes(LOCALE_PLACEHOLDER)) {
34523
- const entries = await loadContentType(name, contentTypeDef, root, formats, {
34524
- mount: mounts.get(name),
34525
- projectConfig
34526
- });
34527
- contentTypes.set(name, entries);
34528
- continue;
34529
- }
34530
- if (locales.length === 0) {
34531
- console.warn(`Content type "${name}": source "${source}" uses ${LOCALE_PLACEHOLDER}, but the project ` + `declares no i18n locales — nothing to expand it over, so no entries were loaded.`);
34532
- contentTypes.set(name, []);
34533
- continue;
34534
- }
34535
- const all = [];
34536
- for (const locale of locales) {
34537
- const localized = { ...contentTypeDef, source: localeSource(root, source, locale) };
34538
- const entries = await loadContentType(name, localized, root, formats, {
34539
- locale,
34540
- mount: mounts.get(`${name}/${locale}`),
34541
- projectConfig
34542
- });
34543
- for (const entry of entries) {
34544
- entry._meta = { ...entry._meta, locale };
34545
- }
34546
- all.push(...entries);
34636
+ if (locales.length === 0) {
34637
+ console.warn(`Content type "${name}": source "${typeSource}" uses ${LOCALE_PLACEHOLDER}, but the project ` + `declares no i18n locales — nothing to expand it over, so no entries were loaded.`);
34638
+ return [];
34639
+ }
34640
+ const all = [];
34641
+ for (const locale of locales) {
34642
+ const localized = { ...contentTypeDef, source: localeSource(root, typeSource, locale) };
34643
+ const entries = await loadContentType(name, localized, root, formats, {
34644
+ locale,
34645
+ mount: mounts.get(`${name}/${locale}`),
34646
+ projectConfig,
34647
+ ...deferral
34648
+ });
34649
+ for (const entry of entries) {
34650
+ entry._meta = { ...entry._meta, locale };
34547
34651
  }
34548
- contentTypes.set(name, all);
34652
+ all.push(...entries);
34549
34653
  }
34550
- return contentTypes;
34654
+ return all;
34551
34655
  }
34552
34656
  function getContentTypeElements(projectRoot, contentTypeName, projectConfig) {
34553
34657
  const { config } = loadContentConfig(projectRoot, projectConfig);
34554
34658
  const def = config.content[contentTypeName];
34555
34659
  return def?.$elements;
34556
34660
  }
34661
+ var LINK_MODES = ["warn", "error", "ignore"];
34662
+ function isLinkMode(value) {
34663
+ return LINK_MODES.some((mode) => mode === value);
34664
+ }
34557
34665
  var KNOWN_TYPE_KEYS = [
34558
34666
  "$elements",
34559
34667
  "alerts",
@@ -34595,7 +34703,7 @@ function compileTypeRules(name, def) {
34595
34703
  const at = `Content type "${name}"`;
34596
34704
  try {
34597
34705
  for (const key of Object.keys(def)) {
34598
- if (KNOWN_TYPE_KEYS.includes(key) || key === "$comment") {
34706
+ if (KNOWN_TYPE_KEYS.some((known) => known === key) || key === "$comment") {
34599
34707
  continue;
34600
34708
  }
34601
34709
  const suggestion = nearestOption(key);
@@ -34604,11 +34712,11 @@ function compileTypeRules(name, def) {
34604
34712
  }
34605
34713
  console.warn(`${at}: ignores the unknown option "${key}" (the options are ${KNOWN_TYPE_KEYS.join(", ")})`);
34606
34714
  }
34607
- if (def.idField !== undefined && (typeof def.idField !== "string" || def.idField === "")) {
34715
+ if (def.idField !== undefined && (!isString(def.idField) || def.idField === "")) {
34608
34716
  throw new TypeError(`"idField" must be a field name`);
34609
34717
  }
34610
34718
  const links = def.links ?? "warn";
34611
- if (links !== "warn" && links !== "error" && links !== "ignore") {
34719
+ if (!isLinkMode(links)) {
34612
34720
  throw new TypeError(`"links" must be "warn", "error" or "ignore", got ${JSON.stringify(links)}`);
34613
34721
  }
34614
34722
  normalizeAlerts(def.alerts, `"alerts"`);
@@ -34669,7 +34777,7 @@ function finishEntries(files, ctx) {
34669
34777
  if (rules.idField) {
34670
34778
  for (const entry of entries) {
34671
34779
  const value = entry.data[rules.idField];
34672
- if (typeof value === "string" && value !== "" || typeof value === "number") {
34780
+ if (isString(value) && value !== "" || isNumber(value)) {
34673
34781
  const unsafe = traversalReason(String(value));
34674
34782
  if (unsafe === null) {
34675
34783
  entry.id = String(value);
@@ -34700,13 +34808,48 @@ function finishEntries(files, ctx) {
34700
34808
  }
34701
34809
  if (rules.route) {
34702
34810
  assignRoutes(entries, unpublished, ctx, rules.route);
34703
- if (ctx.sourceRoot) {
34704
- resolveEntryLinks(kept, unpublished, ctx);
34705
- }
34811
+ const links = ctx.sourceRoot ? resolveEntryLinks(kept, unpublished, ctx) : undefined;
34812
+ deferBodies(kept, ctx, links);
34706
34813
  return entries.filter((entry) => entry._meta?.route !== undefined);
34707
34814
  }
34815
+ deferBodies(kept, ctx);
34708
34816
  return entries;
34709
34817
  }
34818
+ function deferBodies(kept, ctx, links) {
34819
+ const { reload, onDeferred } = ctx;
34820
+ if (!reload || !onDeferred) {
34821
+ return;
34822
+ }
34823
+ for (const file of kept) {
34824
+ const [entry] = file.entries;
34825
+ if (file.entries.length !== 1 || !entry || entry.$children !== undefined) {
34826
+ continue;
34827
+ }
34828
+ onDeferred(entry, async () => {
34829
+ const [full] = await reload(file.filePath);
34830
+ if (!full) {
34831
+ return;
34832
+ }
34833
+ const completed = {
34834
+ ...full,
34835
+ ...entry,
34836
+ _meta: { ...full._meta, ...entry._meta }
34837
+ };
34838
+ for (const key of Object.keys(entry)) {
34839
+ Reflect.deleteProperty(entry, key);
34840
+ }
34841
+ Object.assign(entry, completed);
34842
+ if (ctx.mount) {
34843
+ rewriteBodyAssets(entry, file.filePath, ctx.mount, ctx.name);
34844
+ }
34845
+ if (links && file.rel !== null) {
34846
+ const broken = [];
34847
+ collectBrokenLinks(entry, file.rel, links, broken, new Set);
34848
+ reportBrokenLinks(broken, ctx.name, ctx.rules.links);
34849
+ }
34850
+ });
34851
+ }
34852
+ }
34710
34853
  function settleIndexIds(name, entries) {
34711
34854
  const indexFolders = new Set;
34712
34855
  for (const entry of entries) {
@@ -34729,7 +34872,7 @@ function settleIndexIds(name, entries) {
34729
34872
  }
34730
34873
  function trailingSlashOf(projectConfig) {
34731
34874
  const value = projectConfig?.build?.trailingSlash;
34732
- return typeof value === "string" ? value : "always";
34875
+ return isString(value) ? value : "always";
34733
34876
  }
34734
34877
  function localePrefixOf(ctx) {
34735
34878
  const { i18n } = resolveI18n(ctx.projectConfig ?? {});
@@ -34777,7 +34920,21 @@ function assignRoutes(entries, unpublished, ctx, spec) {
34777
34920
  }
34778
34921
  var UNROUTED_LISTED = 3;
34779
34922
  function resolveEntryLinks(kept, unpublished, ctx) {
34780
- const { name, rules, sourceRoot } = ctx;
34923
+ const index = buildLinkIndex(kept, unpublished, ctx);
34924
+ const broken = [];
34925
+ const seen = new Set;
34926
+ for (const file of kept) {
34927
+ for (const entry of file.entries) {
34928
+ if (entry.$children && file.rel !== null) {
34929
+ collectBrokenLinks(entry, file.rel, index, broken, seen);
34930
+ }
34931
+ }
34932
+ }
34933
+ reportBrokenLinks(broken, ctx.name, ctx.rules.links);
34934
+ return index;
34935
+ }
34936
+ function buildLinkIndex(kept, unpublished, ctx) {
34937
+ const { rules, sourceRoot } = ctx;
34781
34938
  const files = new Map;
34782
34939
  const indexes = new Map;
34783
34940
  const isReadme = (rel) => /^readme\./i.test(rel.slice(rel.lastIndexOf("/") + 1));
@@ -34808,7 +34965,7 @@ function resolveEntryLinks(kept, unpublished, ctx) {
34808
34965
  }
34809
34966
  }
34810
34967
  const onDisk = (path) => resolve(sourceRoot, path);
34811
- const index = {
34968
+ return {
34812
34969
  asset: (path) => {
34813
34970
  const absolute = onDisk(path);
34814
34971
  if (!ctx.mount || !existsSync(absolute) || statSync2(absolute).isDirectory()) {
@@ -34837,27 +34994,22 @@ function resolveEntryLinks(kept, unpublished, ctx) {
34837
34994
  localePrefix: localePrefixOf(ctx),
34838
34995
  trailingSlash: trailingSlashOf(ctx.projectConfig)
34839
34996
  };
34840
- const broken = [];
34841
- const seen = new Set;
34842
- for (const file of kept) {
34843
- for (const entry of file.entries) {
34844
- if (!entry.$children || file.rel === null) {
34845
- continue;
34846
- }
34847
- rewriteLinks(entry.$children, file.rel, index, (problem) => {
34848
- const key = `${file.rel}\x00${problem.href}`;
34849
- if (!seen.has(key)) {
34850
- seen.add(key);
34851
- broken.push({ from: file.rel, href: problem.href, reason: problem.reason });
34852
- }
34853
- });
34997
+ }
34998
+ function collectBrokenLinks(entry, rel, index, broken, seen) {
34999
+ rewriteLinks(entry.$children ?? [], rel, index, (problem) => {
35000
+ const key = `${rel}\x00${problem.href}`;
35001
+ if (!seen.has(key)) {
35002
+ seen.add(key);
35003
+ broken.push({ from: rel, href: problem.href, reason: problem.reason });
34854
35004
  }
34855
- }
34856
- if (rules.links === "ignore" || broken.length === 0) {
35005
+ });
35006
+ }
35007
+ function reportBrokenLinks(broken, name, mode) {
35008
+ if (mode === "ignore" || broken.length === 0) {
34857
35009
  return;
34858
35010
  }
34859
35011
  const lines = broken.map(({ from, href, reason }) => `"${from}" links to "${href}", which ${reason}`);
34860
- if (rules.links === "error") {
35012
+ if (mode === "error") {
34861
35013
  const plural = broken.length === 1 ? "" : "s";
34862
35014
  const list = lines.map((line) => ` - ${line}`).join(`
34863
35015
  `);
@@ -34877,8 +35029,8 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
34877
35029
  if (!source) {
34878
35030
  return [];
34879
35031
  }
34880
- const { mount, projectConfig, locale } = options;
34881
- const schema = contentTypeDef.schema;
35032
+ const { mount, projectConfig, locale, deferBody, onDeferred } = options;
35033
+ const { schema } = contentTypeDef;
34882
35034
  const rules = compileTypeRules(name, contentTypeDef);
34883
35035
  const finishContext = (extra) => ({
34884
35036
  coerceDates: false,
@@ -34893,7 +35045,7 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
34893
35045
  ...extra
34894
35046
  });
34895
35047
  const directiveOptions = contentTypeDef.$elements?.length ? {
34896
- allowedNames: contentTypeDef.$elements.filter((e) => typeof e === "string" || typeof e === "object" && e?.$ref).map((e) => typeof e === "string" ? e : e.$ref)
35048
+ allowedNames: contentTypeDef.$elements.flatMap((e) => isString(e) ? [e] : isNonNullObject(e) && e.$ref ? [e.$ref] : [])
34897
35049
  } : undefined;
34898
35050
  const formatName = contentTypeDef.format;
34899
35051
  let entry;
@@ -34949,25 +35101,32 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
34949
35101
  if (!entry) {
34950
35102
  throw unknownFormatError(source, ext || `content type "${name}" (directory sources need an explicit "format")`);
34951
35103
  }
34952
- const discovered = entry.capabilities.discover ? await entry.call("discover", source, {
34953
- baseDir: projectRoot,
34954
- ...rules.exclude.empty ? {} : { exclude: contentTypeDef.exclude }
34955
- }) : [resolvedSource];
35104
+ const discoverOptions = { baseDir: projectRoot };
35105
+ if (!rules.exclude.empty) {
35106
+ discoverOptions.exclude = contentTypeDef.exclude;
35107
+ }
35108
+ const discovered = entry.capabilities.discover ? await entry.call("discover", source, discoverOptions) : [resolvedSource];
34956
35109
  const files = discovered.filter((file) => !isExcluded(file));
34957
35110
  const fileExtensions = files.map((file) => extname(file).toLowerCase());
35111
+ const format = entry;
35112
+ const loadFile = (filePath, withoutBody) => {
35113
+ const rel = relOf(filePath);
35114
+ return format.call("load", filePath, {
35115
+ directiveOptions,
35116
+ schema,
35117
+ ...sourceRoot !== undefined && { sourceRoot },
35118
+ ...rules.alerts !== undefined && { alerts: rules.alerts },
35119
+ ...rules.idField !== undefined && { idField: rules.idField },
35120
+ ...withoutBody && { deferBody: true },
35121
+ onUnknownAlert: (type) => console.warn(`Content callouts: "${name}": "${rel ?? filePath}" has a [!${type}] callout, but ` + `"${type.toLowerCase()}" is not an enabled type, so it stays a blockquote with the ` + `marker showing. Add "alerts": { "${type.toUpperCase()}": true } to render it.`)
35122
+ });
35123
+ };
34958
35124
  const loaded = [];
34959
35125
  for (const filePath of files) {
34960
35126
  const rel = relOf(filePath);
34961
35127
  let fileEntries;
34962
35128
  try {
34963
- fileEntries = await entry.call("load", filePath, {
34964
- directiveOptions,
34965
- schema,
34966
- ...sourceRoot !== undefined && { sourceRoot },
34967
- ...rules.alerts !== undefined && { alerts: rules.alerts },
34968
- ...rules.idField !== undefined && { idField: rules.idField },
34969
- onUnknownAlert: (type) => console.warn(`Content callouts: "${name}": "${rel ?? filePath}" has a [!${type}] callout, but ` + `"${type.toLowerCase()}" is not an enabled type, so it stays a blockquote with the ` + `marker showing. Add "alerts": { "${type.toUpperCase()}": true } to render it.`)
34970
- });
35129
+ fileEntries = await loadFile(filePath, deferBody === true);
34971
35130
  } catch (error) {
34972
35131
  throw unreadableFile(name, rel ?? filePath, error);
34973
35132
  }
@@ -34979,17 +35138,30 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
34979
35138
  return finishEntries(loaded, finishContext({
34980
35139
  coerceDates: true,
34981
35140
  entryExtensions: new Set([...entry.extensions, ...fileExtensions]),
34982
- sourceRoot
35141
+ sourceRoot,
35142
+ ...deferBody === true && {
35143
+ onDeferred,
35144
+ reload: async (filePath) => await loadFile(filePath, false)
35145
+ }
34983
35146
  }));
34984
35147
  }
34985
- function unreadableFile(type, path, error) {
34986
- return new Error(`Content type "${type}": cannot read "${path}": ${errorMessage(error)}. Fix the file, or add it ` + `to "exclude" if it is not a document (a templates folder, for example).`, { cause: error });
35148
+ function unreadableFile(type, path, cause) {
35149
+ return new Error(`Content type "${type}": cannot read "${path}": ${errorMessage(cause)}. Fix the file, or add it ` + `to "exclude" if it is not a document (a templates folder, for example).`, { cause });
34987
35150
  }
34988
35151
  function unknownFormatError(path, ext) {
34989
35152
  return new Error(`No format class registered for "${ext}" (${path}). ` + `Enable an extension providing this format in project.json "extensions", ` + `e.g. "@jxsuite/parser".`);
34990
35153
  }
34991
- function errorMessage(error) {
34992
- return error instanceof Error ? error.message : String(error);
35154
+ function errorMessage(cause) {
35155
+ return cause instanceof Error ? cause.message : String(cause);
35156
+ }
35157
+ function jsTypeName(value) {
35158
+ if (isString(value)) {
35159
+ return "string";
35160
+ }
35161
+ if (isNumber(value)) {
35162
+ return "number";
35163
+ }
35164
+ return isBoolean(value) ? "boolean" : "object";
34993
35165
  }
34994
35166
  function validateEntries(entries, schema, contentTypeName) {
34995
35167
  const required = schema.required ?? [];
@@ -35005,16 +35177,16 @@ function validateEntries(entries, schema, contentTypeName) {
35005
35177
  if (value == null) {
35006
35178
  continue;
35007
35179
  }
35008
- if (isDateFormat(def.format) && typeof value === "string" && !isCoercedDate(value)) {
35180
+ if (isDateFormat(def.format) && isString(value) && !isCoercedDate(value)) {
35009
35181
  console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" is declared ` + `${def.format} but "${value}" was not coerced — it will sort and filter as plain text.`);
35010
- } else if (def.type === "string" && typeof value !== "string") {
35011
- console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected string, got ${typeof value}`);
35012
- } else if (def.type === "number" && typeof value !== "number") {
35013
- console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected number, got ${typeof value}`);
35014
- } else if (def.type === "boolean" && typeof value !== "boolean") {
35015
- console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected boolean, got ${typeof value}`);
35182
+ } else if (def.type === "string" && !isString(value)) {
35183
+ console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected string, got ${jsTypeName(value)}`);
35184
+ } else if (def.type === "number" && !isNumber(value)) {
35185
+ console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected number, got ${jsTypeName(value)}`);
35186
+ } else if (def.type === "boolean" && !isBoolean(value)) {
35187
+ console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected boolean, got ${jsTypeName(value)}`);
35016
35188
  } else if (def.type === "array" && !Array.isArray(value)) {
35017
- console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected array, got ${typeof value}`);
35189
+ console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected array, got ${jsTypeName(value)}`);
35018
35190
  }
35019
35191
  }
35020
35192
  }
@@ -35026,9 +35198,19 @@ function refTargetName(ref) {
35026
35198
  }
35027
35199
  return ref.slice(CONTENT_REF_PREFIX.length);
35028
35200
  }
35201
+ function referencedContentTypes(def) {
35202
+ const targets = new Set;
35203
+ for (const field of Object.values(def?.schema?.properties ?? {})) {
35204
+ const target = refTargetName(field.$ref) ?? refTargetName(field.items?.$ref);
35205
+ if (target !== undefined) {
35206
+ targets.add(target);
35207
+ }
35208
+ }
35209
+ return [...targets];
35210
+ }
35029
35211
  function resolveContentTypeRefs(contentTypes, section) {
35030
35212
  for (const [name, contentTypeDef] of Object.entries(section)) {
35031
- const schema = contentTypeDef.schema;
35213
+ const { schema } = contentTypeDef;
35032
35214
  if (!schema?.properties) {
35033
35215
  continue;
35034
35216
  }
@@ -35050,7 +35232,7 @@ function resolveContentTypeRefs(contentTypes, section) {
35050
35232
  }
35051
35233
  for (const entry of entries) {
35052
35234
  const value = entry.data[field];
35053
- if (toOne && typeof value === "string") {
35235
+ if (toOne && isString(value)) {
35054
35236
  const resolved = refEntries.find((e) => e.id === value);
35055
35237
  if (resolved) {
35056
35238
  entry.data[field] = resolved;
@@ -35059,7 +35241,7 @@ function resolveContentTypeRefs(contentTypes, section) {
35059
35241
  }
35060
35242
  } else if (toMany && Array.isArray(value)) {
35061
35243
  entry.data[field] = value.map((id) => {
35062
- if (typeof id !== "string") {
35244
+ if (!isString(id)) {
35063
35245
  return id;
35064
35246
  }
35065
35247
  const resolved = refEntries.find((e) => e.id === id);
@@ -35086,7 +35268,10 @@ function reportUnservedRoutes(contentType, entries, ctx, prefix) {
35086
35268
  return;
35087
35269
  }
35088
35270
  const patterns = ctx.patterns.map((pattern) => withoutLocalePrefix(pattern, prefix));
35089
- const unserved = entries.map((entry) => entry._meta?.route).filter((route) => route !== undefined && !patterns.some((pattern) => routeParams(route, pattern, []) !== null));
35271
+ const unserved = entries.flatMap((entry) => {
35272
+ const route = entry._meta?.route;
35273
+ return route !== undefined && !patterns.some((pattern) => routeParams(route, pattern, []) !== null) ? [route] : [];
35274
+ });
35090
35275
  if (unserved.length === 0) {
35091
35276
  return;
35092
35277
  }
@@ -35109,7 +35294,11 @@ function routedPaths(contentType, entries, ctx, urlPattern) {
35109
35294
  example ??= route;
35110
35295
  const params = routeParams(route, pattern, ctx.params ?? []);
35111
35296
  if (params) {
35112
- paths.push({ ...params, ...entry._meta === undefined ? {} : { _meta: entry._meta } });
35297
+ const pathEntry = { ...params };
35298
+ if (entry._meta !== undefined) {
35299
+ pathEntry._meta = entry._meta;
35300
+ }
35301
+ paths.push(pathEntry);
35113
35302
  }
35114
35303
  }
35115
35304
  if (paths.length === 0 && example !== undefined) {
@@ -35119,18 +35308,159 @@ function routedPaths(contentType, entries, ctx, urlPattern) {
35119
35308
  return paths;
35120
35309
  }
35121
35310
 
35311
+ class ContentStore extends Map {
35312
+ #source;
35313
+ #mounts;
35314
+ #loads = new Map;
35315
+ #linked = new Set;
35316
+ #entries = new WeakSet;
35317
+ #deferred = new WeakMap;
35318
+ #completing = new WeakMap;
35319
+ constructor(source) {
35320
+ super();
35321
+ this.#source = source;
35322
+ this.#mounts = sectionMounts(source.section, source.root, source.projectConfig);
35323
+ }
35324
+ static async whole(source) {
35325
+ const store = new ContentStore(source);
35326
+ const loaded = await loadContentSection(source.section, source.root, source.formats, source.projectConfig);
35327
+ for (const [name, entries] of loaded) {
35328
+ store.#adopt(name, entries);
35329
+ store.#loads.set(name, Promise.resolve());
35330
+ }
35331
+ resolveContentTypeRefs(store, source.section);
35332
+ for (const name of loaded.keys()) {
35333
+ store.#linked.add(name);
35334
+ }
35335
+ return store;
35336
+ }
35337
+ static lazy(source) {
35338
+ return new ContentStore(source);
35339
+ }
35340
+ async load(name) {
35341
+ if (!Object.hasOwn(this.#source.section, name)) {
35342
+ return;
35343
+ }
35344
+ const related = this.#relatedTypes(name);
35345
+ await Promise.all(related.map(async (type) => await this.#loadType(type)));
35346
+ const unlinked = related.filter((type) => !this.#linked.has(type));
35347
+ for (const type of unlinked) {
35348
+ this.#linked.add(type);
35349
+ }
35350
+ if (unlinked.length > 0) {
35351
+ const { section } = this.#source;
35352
+ resolveContentTypeRefs(this, Object.fromEntries(unlinked.flatMap((type) => section[type] ? [[type, section[type]]] : [])));
35353
+ }
35354
+ return this.get(name);
35355
+ }
35356
+ async materialize(entries) {
35357
+ const seen = new Set;
35358
+ const pending = [];
35359
+ const visit = (entry) => {
35360
+ if (seen.has(entry)) {
35361
+ return;
35362
+ }
35363
+ seen.add(entry);
35364
+ pending.push(this.#complete(entry));
35365
+ for (const value of Object.values(entry.data)) {
35366
+ for (const item of Array.isArray(value) ? value : [value]) {
35367
+ if (this.#isEntry(item)) {
35368
+ visit(item);
35369
+ }
35370
+ }
35371
+ }
35372
+ };
35373
+ for (const entry of entries) {
35374
+ if (entry) {
35375
+ visit(entry);
35376
+ }
35377
+ }
35378
+ await Promise.all(pending);
35379
+ }
35380
+ async complete() {
35381
+ const lists = await Promise.all(Object.keys(this.#source.section).map(async (name) => await this.load(name) ?? []));
35382
+ await this.materialize(lists.flat());
35383
+ }
35384
+ #relatedTypes(name) {
35385
+ const { section } = this.#source;
35386
+ const found = new Set([name]);
35387
+ const queue = [name];
35388
+ for (let type = queue.shift();type !== undefined; type = queue.shift()) {
35389
+ for (const target of referencedContentTypes(section[type])) {
35390
+ if (Object.hasOwn(section, target) && !found.has(target)) {
35391
+ found.add(target);
35392
+ queue.push(target);
35393
+ }
35394
+ }
35395
+ }
35396
+ return [...found];
35397
+ }
35398
+ #loadType(name) {
35399
+ let load = this.#loads.get(name);
35400
+ if (!load) {
35401
+ const { section, root, formats, projectConfig } = this.#source;
35402
+ load = loadSectionType(name, {
35403
+ formats,
35404
+ mounts: this.#mounts,
35405
+ onDeferred: (entry, completeBody) => {
35406
+ this.#deferred.set(entry, completeBody);
35407
+ },
35408
+ projectConfig,
35409
+ root,
35410
+ section
35411
+ }).then((entries) => {
35412
+ this.#adopt(name, entries);
35413
+ });
35414
+ load.catch(() => {
35415
+ this.#loads.delete(name);
35416
+ });
35417
+ this.#loads.set(name, load);
35418
+ }
35419
+ return load;
35420
+ }
35421
+ #adopt(name, entries) {
35422
+ for (const entry of entries) {
35423
+ this.#entries.add(entry);
35424
+ }
35425
+ this.set(name, entries);
35426
+ }
35427
+ #isEntry(value) {
35428
+ return isNonNullObject(value) && this.#entries.has(value);
35429
+ }
35430
+ #complete(entry) {
35431
+ let completing = this.#completing.get(entry);
35432
+ if (!completing) {
35433
+ const completeBody = this.#deferred.get(entry);
35434
+ if (!completeBody) {
35435
+ return Promise.resolve();
35436
+ }
35437
+ completing = completeBody().then(() => {
35438
+ this.#deferred.delete(entry);
35439
+ });
35440
+ completing.catch(() => {
35441
+ this.#completing.delete(entry);
35442
+ });
35443
+ this.#completing.set(entry, completing);
35444
+ }
35445
+ return completing;
35446
+ }
35447
+ }
35448
+
35122
35449
  class Content {
35123
35450
  static async projectData(sectionValue, ctx) {
35124
- const section = sectionValue ?? {};
35125
- const data = await loadContentSection(section, ctx.root, ctx.registry.formats, ctx.projectConfig);
35126
- resolveContentTypeRefs(data, section);
35127
- return data;
35451
+ const source = {
35452
+ formats: ctx.registry.formats,
35453
+ projectConfig: ctx.projectConfig,
35454
+ root: ctx.root,
35455
+ section: sectionValue ?? {}
35456
+ };
35457
+ return ctx.lazy === true ? ContentStore.lazy(source) : await ContentStore.whole(source);
35128
35458
  }
35129
35459
  static assets(sectionValue, ctx) {
35130
35460
  return contentAssetMounts(sectionValue ?? {}, ctx.root, ctx.projectConfig);
35131
35461
  }
35132
35462
  static async resolvePaths(pathsDef, ctx) {
35133
- const entries = ctx.data.get(pathsDef.contentType);
35463
+ const entries = await entriesOf(ctx.data, pathsDef.contentType);
35134
35464
  if (!entries || entries.length === 0) {
35135
35465
  console.warn(`Warning: $paths references content type "${pathsDef.contentType}" but it has no entries`);
35136
35466
  return [];
@@ -35139,7 +35469,7 @@ class Content {
35139
35469
  const field = pathsDef.field ?? "id";
35140
35470
  const localized = entries.some((entry) => entry._meta?.locale !== undefined);
35141
35471
  const wanted = ctx.locale;
35142
- const scoped = localized && typeof wanted === "string" ? entries.filter((entry) => entry._meta?.locale === wanted) : entries;
35472
+ const scoped = localized && isString(wanted) ? entries.filter((entry) => entry._meta?.locale === wanted) : entries;
35143
35473
  if (pathsDef.param === undefined && pathsDef.field === undefined && ctx.urlPattern !== undefined && scoped.some((entry) => entry._meta?.route !== undefined)) {
35144
35474
  return routedPaths(pathsDef.contentType, scoped, ctx, ctx.urlPattern);
35145
35475
  }
@@ -35165,13 +35495,17 @@ class Content {
35165
35495
  }
35166
35496
  export {
35167
35497
  Content,
35498
+ ContentStore,
35168
35499
  LOCALE_PLACEHOLDER,
35169
35500
  contentAssetMounts,
35170
35501
  getContentTypeElements,
35171
35502
  loadContentConfig,
35172
35503
  loadContentSection,
35504
+ loadSectionType,
35173
35505
  localeSource,
35174
35506
  localesForExpansion,
35507
+ referencedContentTypes,
35175
35508
  resolveContentTypeRefs,
35176
- rewriteEntryAssets
35509
+ rewriteEntryAssets,
35510
+ sectionMounts
35177
35511
  };