@jxsuite/parser 2.0.1 → 3.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
 
@@ -33724,6 +33790,10 @@ import { existsSync, readFileSync, readdirSync as readdirSync2, statSync as stat
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,7 +34415,6 @@ 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
  }
@@ -34360,8 +34435,6 @@ function localeSource(root, source, locale) {
34360
34435
  }
34361
34436
  return source.split(LOCALE_PLACEHOLDER).join(locale);
34362
34437
  }
34363
- var SECTION_KEY = "content";
34364
- var SAFE_TYPE_NAME = /^[\w.~-]+$/;
34365
34438
  var ASSET_KEYS = ["src", "poster"];
34366
34439
  function isNonRelativeRef(value) {
34367
34440
  return value === "" || value.startsWith("/") || value.startsWith("#") || value.includes("${") || /^[a-z][\w+.-]*:/i.test(value);
@@ -34396,28 +34469,31 @@ function contentAssetMounts(section, root, projectConfig) {
34396
34469
  return filter === undefined ? {} : { filter };
34397
34470
  };
34398
34471
  for (const [name, contentTypeDef] of Object.entries(section ?? {})) {
34399
- const source = contentTypeDef?.source;
34472
+ if (!isNonNullObject(contentTypeDef)) {
34473
+ continue;
34474
+ }
34475
+ const { source } = contentTypeDef;
34400
34476
  if (!source || source.startsWith("http://") || source.startsWith("https://")) {
34401
34477
  continue;
34402
34478
  }
34403
34479
  if (source.includes(LOCALE_PLACEHOLDER)) {
34404
34480
  for (const locale of locales) {
34405
- if (!SAFE_TYPE_NAME.test(locale)) {
34481
+ if (!SAFE_CONTENT_TYPE_NAME.test(locale)) {
34406
34482
  continue;
34407
34483
  }
34408
34484
  const dir = resolve(root, localeSource(root, source, locale)).split("\\").join("/");
34409
- if (SAFE_TYPE_NAME.test(name) && existsSync(dir) && statSync2(dir).isDirectory()) {
34485
+ if (SAFE_CONTENT_TYPE_NAME.test(name) && existsSync(dir) && statSync2(dir).isDirectory()) {
34410
34486
  mounts.push({
34411
34487
  dir,
34412
- urlPrefix: `/${SECTION_KEY}/${name}/${locale}`,
34488
+ urlPrefix: `/${CONTENT_SECTION_KEY}/${name}/${locale}`,
34413
34489
  ...filterOf(contentTypeDef)
34414
34490
  });
34415
34491
  }
34416
34492
  }
34417
34493
  continue;
34418
34494
  }
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.`);
34495
+ if (!SAFE_CONTENT_TYPE_NAME.test(name)) {
34496
+ 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
34497
  continue;
34422
34498
  }
34423
34499
  const resolvedSource = resolve(root, source).split("\\").join("/");
@@ -34426,7 +34502,7 @@ function contentAssetMounts(section, root, projectConfig) {
34426
34502
  }
34427
34503
  mounts.push({
34428
34504
  dir: resolvedSource,
34429
- urlPrefix: `/${SECTION_KEY}/${name}`,
34505
+ urlPrefix: `/${CONTENT_SECTION_KEY}/${name}`,
34430
34506
  ...filterOf(contentTypeDef)
34431
34507
  });
34432
34508
  }
@@ -34464,15 +34540,14 @@ function rewriteNodeAssets(node, entryDir, mount, onProblem) {
34464
34540
  }
34465
34541
  return;
34466
34542
  }
34467
- if (!node || typeof node !== "object") {
34543
+ if (!isNonNullObject(node)) {
34468
34544
  return;
34469
34545
  }
34470
- const record = node;
34471
- const attributes = record.attributes;
34546
+ const { attributes } = node;
34472
34547
  for (const key of ASSET_KEYS) {
34473
- for (const holder of [record, attributes]) {
34548
+ for (const holder of [node, attributes]) {
34474
34549
  const value = holder?.[key];
34475
- if (typeof value === "string") {
34550
+ if (holder && isString(value)) {
34476
34551
  const rewritten = rewriteRef(value, entryDir, mount, onProblem);
34477
34552
  if (rewritten) {
34478
34553
  holder[key] = rewritten;
@@ -34480,7 +34555,7 @@ function rewriteNodeAssets(node, entryDir, mount, onProblem) {
34480
34555
  }
34481
34556
  }
34482
34557
  }
34483
- const { children } = record;
34558
+ const { children } = node;
34484
34559
  if (Array.isArray(children)) {
34485
34560
  for (const child of children) {
34486
34561
  rewriteNodeAssets(child, entryDir, mount, onProblem);
@@ -34489,22 +34564,30 @@ function rewriteNodeAssets(node, entryDir, mount, onProblem) {
34489
34564
  }
34490
34565
  function rewriteEntryAssets(entries, filePath, mount, typeName, schema) {
34491
34566
  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);
34567
+ const uriFields = Object.entries(schema?.properties ?? {}).flatMap(([field, def]) => isNonNullObject(def) && (def.format === "uri-reference" || def.items?.format === "uri-reference") ? [field] : []);
34493
34568
  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
- }
34569
+ const onProblem = assetProblemReporter(typeName, entry);
34570
+ rewriteBodyAssets(entry, filePath, mount, typeName);
34498
34571
  for (const field of uriFields) {
34499
34572
  const value = entry.data[field];
34500
- if (typeof value === "string") {
34573
+ if (isString(value)) {
34501
34574
  entry.data[field] = rewriteRef(value, entryDir, mount, onProblem) ?? value;
34502
34575
  } else if (Array.isArray(value)) {
34503
- entry.data[field] = value.map((item) => typeof item === "string" ? rewriteRef(item, entryDir, mount, onProblem) ?? item : item);
34576
+ entry.data[field] = value.map((item) => isString(item) ? rewriteRef(item, entryDir, mount, onProblem) ?? item : item);
34504
34577
  }
34505
34578
  }
34506
34579
  }
34507
34580
  }
34581
+ function assetProblemReporter(typeName, entry) {
34582
+ 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`);
34583
+ }
34584
+ function rewriteBodyAssets(entry, filePath, mount, typeName) {
34585
+ const entryDir = dirname(filePath);
34586
+ const onProblem = assetProblemReporter(typeName, entry);
34587
+ for (const child of entry.$children ?? []) {
34588
+ rewriteNodeAssets(child, entryDir, mount, onProblem);
34589
+ }
34590
+ }
34508
34591
  function loadContentConfig(projectRoot, projectConfig) {
34509
34592
  const contentDir = resolve(projectRoot, "content");
34510
34593
  const config = { content: projectConfig?.content ?? {} };
@@ -34512,48 +34595,63 @@ function loadContentConfig(projectRoot, projectConfig) {
34512
34595
  }
34513
34596
  async function loadContentSection(section, root, formats, projectConfig) {
34514
34597
  const contentTypes = new Map;
34515
- const mounts = new Map(contentAssetMounts(section, root, projectConfig).map((mount) => [
34516
- mount.urlPrefix.slice(`/${SECTION_KEY}/`.length),
34598
+ const mounts = sectionMounts(section, root, projectConfig);
34599
+ for (const name of Object.keys(section)) {
34600
+ contentTypes.set(name, await loadSectionType(name, { formats, mounts, projectConfig, root, section }));
34601
+ }
34602
+ return contentTypes;
34603
+ }
34604
+ function sectionMounts(section, root, projectConfig) {
34605
+ return new Map(contentAssetMounts(section, root, projectConfig).map((mount) => [
34606
+ mount.urlPrefix.slice(`/${CONTENT_SECTION_KEY}/`.length),
34517
34607
  mount
34518
34608
  ]));
34609
+ }
34610
+ async function loadSectionType(name, source) {
34611
+ const { section, root, formats, projectConfig, mounts, onDeferred } = source;
34612
+ const contentTypeDef = section[name];
34613
+ if (!contentTypeDef) {
34614
+ return [];
34615
+ }
34616
+ const deferral = onDeferred ? { deferBody: true, onDeferred } : {};
34617
+ const typeSource = contentTypeDef.source;
34618
+ if (!isString(typeSource) || !typeSource.includes(LOCALE_PLACEHOLDER)) {
34619
+ return await loadContentType(name, contentTypeDef, root, formats, {
34620
+ mount: mounts.get(name),
34621
+ projectConfig,
34622
+ ...deferral
34623
+ });
34624
+ }
34519
34625
  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);
34626
+ if (locales.length === 0) {
34627
+ 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.`);
34628
+ return [];
34629
+ }
34630
+ const all = [];
34631
+ for (const locale of locales) {
34632
+ const localized = { ...contentTypeDef, source: localeSource(root, typeSource, locale) };
34633
+ const entries = await loadContentType(name, localized, root, formats, {
34634
+ locale,
34635
+ mount: mounts.get(`${name}/${locale}`),
34636
+ projectConfig,
34637
+ ...deferral
34638
+ });
34639
+ for (const entry of entries) {
34640
+ entry._meta = { ...entry._meta, locale };
34547
34641
  }
34548
- contentTypes.set(name, all);
34642
+ all.push(...entries);
34549
34643
  }
34550
- return contentTypes;
34644
+ return all;
34551
34645
  }
34552
34646
  function getContentTypeElements(projectRoot, contentTypeName, projectConfig) {
34553
34647
  const { config } = loadContentConfig(projectRoot, projectConfig);
34554
34648
  const def = config.content[contentTypeName];
34555
34649
  return def?.$elements;
34556
34650
  }
34651
+ var LINK_MODES = ["warn", "error", "ignore"];
34652
+ function isLinkMode(value) {
34653
+ return LINK_MODES.some((mode) => mode === value);
34654
+ }
34557
34655
  var KNOWN_TYPE_KEYS = [
34558
34656
  "$elements",
34559
34657
  "alerts",
@@ -34595,7 +34693,7 @@ function compileTypeRules(name, def) {
34595
34693
  const at = `Content type "${name}"`;
34596
34694
  try {
34597
34695
  for (const key of Object.keys(def)) {
34598
- if (KNOWN_TYPE_KEYS.includes(key) || key === "$comment") {
34696
+ if (KNOWN_TYPE_KEYS.some((known) => known === key) || key === "$comment") {
34599
34697
  continue;
34600
34698
  }
34601
34699
  const suggestion = nearestOption(key);
@@ -34604,11 +34702,11 @@ function compileTypeRules(name, def) {
34604
34702
  }
34605
34703
  console.warn(`${at}: ignores the unknown option "${key}" (the options are ${KNOWN_TYPE_KEYS.join(", ")})`);
34606
34704
  }
34607
- if (def.idField !== undefined && (typeof def.idField !== "string" || def.idField === "")) {
34705
+ if (def.idField !== undefined && (!isString(def.idField) || def.idField === "")) {
34608
34706
  throw new TypeError(`"idField" must be a field name`);
34609
34707
  }
34610
34708
  const links = def.links ?? "warn";
34611
- if (links !== "warn" && links !== "error" && links !== "ignore") {
34709
+ if (!isLinkMode(links)) {
34612
34710
  throw new TypeError(`"links" must be "warn", "error" or "ignore", got ${JSON.stringify(links)}`);
34613
34711
  }
34614
34712
  normalizeAlerts(def.alerts, `"alerts"`);
@@ -34669,7 +34767,7 @@ function finishEntries(files, ctx) {
34669
34767
  if (rules.idField) {
34670
34768
  for (const entry of entries) {
34671
34769
  const value = entry.data[rules.idField];
34672
- if (typeof value === "string" && value !== "" || typeof value === "number") {
34770
+ if (isString(value) && value !== "" || isNumber(value)) {
34673
34771
  const unsafe = traversalReason(String(value));
34674
34772
  if (unsafe === null) {
34675
34773
  entry.id = String(value);
@@ -34700,13 +34798,48 @@ function finishEntries(files, ctx) {
34700
34798
  }
34701
34799
  if (rules.route) {
34702
34800
  assignRoutes(entries, unpublished, ctx, rules.route);
34703
- if (ctx.sourceRoot) {
34704
- resolveEntryLinks(kept, unpublished, ctx);
34705
- }
34801
+ const links = ctx.sourceRoot ? resolveEntryLinks(kept, unpublished, ctx) : undefined;
34802
+ deferBodies(kept, ctx, links);
34706
34803
  return entries.filter((entry) => entry._meta?.route !== undefined);
34707
34804
  }
34805
+ deferBodies(kept, ctx);
34708
34806
  return entries;
34709
34807
  }
34808
+ function deferBodies(kept, ctx, links) {
34809
+ const { reload, onDeferred } = ctx;
34810
+ if (!reload || !onDeferred) {
34811
+ return;
34812
+ }
34813
+ for (const file of kept) {
34814
+ const [entry] = file.entries;
34815
+ if (file.entries.length !== 1 || !entry || entry.$children !== undefined) {
34816
+ continue;
34817
+ }
34818
+ onDeferred(entry, async () => {
34819
+ const [full] = await reload(file.filePath);
34820
+ if (!full) {
34821
+ return;
34822
+ }
34823
+ const completed = {
34824
+ ...full,
34825
+ ...entry,
34826
+ _meta: { ...full._meta, ...entry._meta }
34827
+ };
34828
+ for (const key of Object.keys(entry)) {
34829
+ Reflect.deleteProperty(entry, key);
34830
+ }
34831
+ Object.assign(entry, completed);
34832
+ if (ctx.mount) {
34833
+ rewriteBodyAssets(entry, file.filePath, ctx.mount, ctx.name);
34834
+ }
34835
+ if (links && file.rel !== null) {
34836
+ const broken = [];
34837
+ collectBrokenLinks(entry, file.rel, links, broken, new Set);
34838
+ reportBrokenLinks(broken, ctx.name, ctx.rules.links);
34839
+ }
34840
+ });
34841
+ }
34842
+ }
34710
34843
  function settleIndexIds(name, entries) {
34711
34844
  const indexFolders = new Set;
34712
34845
  for (const entry of entries) {
@@ -34729,7 +34862,7 @@ function settleIndexIds(name, entries) {
34729
34862
  }
34730
34863
  function trailingSlashOf(projectConfig) {
34731
34864
  const value = projectConfig?.build?.trailingSlash;
34732
- return typeof value === "string" ? value : "always";
34865
+ return isString(value) ? value : "always";
34733
34866
  }
34734
34867
  function localePrefixOf(ctx) {
34735
34868
  const { i18n } = resolveI18n(ctx.projectConfig ?? {});
@@ -34777,7 +34910,21 @@ function assignRoutes(entries, unpublished, ctx, spec) {
34777
34910
  }
34778
34911
  var UNROUTED_LISTED = 3;
34779
34912
  function resolveEntryLinks(kept, unpublished, ctx) {
34780
- const { name, rules, sourceRoot } = ctx;
34913
+ const index = buildLinkIndex(kept, unpublished, ctx);
34914
+ const broken = [];
34915
+ const seen = new Set;
34916
+ for (const file of kept) {
34917
+ for (const entry of file.entries) {
34918
+ if (entry.$children && file.rel !== null) {
34919
+ collectBrokenLinks(entry, file.rel, index, broken, seen);
34920
+ }
34921
+ }
34922
+ }
34923
+ reportBrokenLinks(broken, ctx.name, ctx.rules.links);
34924
+ return index;
34925
+ }
34926
+ function buildLinkIndex(kept, unpublished, ctx) {
34927
+ const { rules, sourceRoot } = ctx;
34781
34928
  const files = new Map;
34782
34929
  const indexes = new Map;
34783
34930
  const isReadme = (rel) => /^readme\./i.test(rel.slice(rel.lastIndexOf("/") + 1));
@@ -34808,7 +34955,7 @@ function resolveEntryLinks(kept, unpublished, ctx) {
34808
34955
  }
34809
34956
  }
34810
34957
  const onDisk = (path) => resolve(sourceRoot, path);
34811
- const index = {
34958
+ return {
34812
34959
  asset: (path) => {
34813
34960
  const absolute = onDisk(path);
34814
34961
  if (!ctx.mount || !existsSync(absolute) || statSync2(absolute).isDirectory()) {
@@ -34837,27 +34984,22 @@ function resolveEntryLinks(kept, unpublished, ctx) {
34837
34984
  localePrefix: localePrefixOf(ctx),
34838
34985
  trailingSlash: trailingSlashOf(ctx.projectConfig)
34839
34986
  };
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
- });
34987
+ }
34988
+ function collectBrokenLinks(entry, rel, index, broken, seen) {
34989
+ rewriteLinks(entry.$children ?? [], rel, index, (problem) => {
34990
+ const key = `${rel}\x00${problem.href}`;
34991
+ if (!seen.has(key)) {
34992
+ seen.add(key);
34993
+ broken.push({ from: rel, href: problem.href, reason: problem.reason });
34854
34994
  }
34855
- }
34856
- if (rules.links === "ignore" || broken.length === 0) {
34995
+ });
34996
+ }
34997
+ function reportBrokenLinks(broken, name, mode) {
34998
+ if (mode === "ignore" || broken.length === 0) {
34857
34999
  return;
34858
35000
  }
34859
35001
  const lines = broken.map(({ from, href, reason }) => `"${from}" links to "${href}", which ${reason}`);
34860
- if (rules.links === "error") {
35002
+ if (mode === "error") {
34861
35003
  const plural = broken.length === 1 ? "" : "s";
34862
35004
  const list = lines.map((line) => ` - ${line}`).join(`
34863
35005
  `);
@@ -34877,8 +35019,8 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
34877
35019
  if (!source) {
34878
35020
  return [];
34879
35021
  }
34880
- const { mount, projectConfig, locale } = options;
34881
- const schema = contentTypeDef.schema;
35022
+ const { mount, projectConfig, locale, deferBody, onDeferred } = options;
35023
+ const { schema } = contentTypeDef;
34882
35024
  const rules = compileTypeRules(name, contentTypeDef);
34883
35025
  const finishContext = (extra) => ({
34884
35026
  coerceDates: false,
@@ -34893,7 +35035,7 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
34893
35035
  ...extra
34894
35036
  });
34895
35037
  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)
35038
+ allowedNames: contentTypeDef.$elements.flatMap((e) => isString(e) ? [e] : isNonNullObject(e) && e.$ref ? [e.$ref] : [])
34897
35039
  } : undefined;
34898
35040
  const formatName = contentTypeDef.format;
34899
35041
  let entry;
@@ -34949,25 +35091,32 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
34949
35091
  if (!entry) {
34950
35092
  throw unknownFormatError(source, ext || `content type "${name}" (directory sources need an explicit "format")`);
34951
35093
  }
34952
- const discovered = entry.capabilities.discover ? await entry.call("discover", source, {
34953
- baseDir: projectRoot,
34954
- ...rules.exclude.empty ? {} : { exclude: contentTypeDef.exclude }
34955
- }) : [resolvedSource];
35094
+ const discoverOptions = { baseDir: projectRoot };
35095
+ if (!rules.exclude.empty) {
35096
+ discoverOptions.exclude = contentTypeDef.exclude;
35097
+ }
35098
+ const discovered = entry.capabilities.discover ? await entry.call("discover", source, discoverOptions) : [resolvedSource];
34956
35099
  const files = discovered.filter((file) => !isExcluded(file));
34957
35100
  const fileExtensions = files.map((file) => extname(file).toLowerCase());
35101
+ const format = entry;
35102
+ const loadFile = (filePath, withoutBody) => {
35103
+ const rel = relOf(filePath);
35104
+ return format.call("load", filePath, {
35105
+ directiveOptions,
35106
+ schema,
35107
+ ...sourceRoot !== undefined && { sourceRoot },
35108
+ ...rules.alerts !== undefined && { alerts: rules.alerts },
35109
+ ...rules.idField !== undefined && { idField: rules.idField },
35110
+ ...withoutBody && { deferBody: true },
35111
+ 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.`)
35112
+ });
35113
+ };
34958
35114
  const loaded = [];
34959
35115
  for (const filePath of files) {
34960
35116
  const rel = relOf(filePath);
34961
35117
  let fileEntries;
34962
35118
  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
- });
35119
+ fileEntries = await loadFile(filePath, deferBody === true);
34971
35120
  } catch (error) {
34972
35121
  throw unreadableFile(name, rel ?? filePath, error);
34973
35122
  }
@@ -34979,17 +35128,30 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
34979
35128
  return finishEntries(loaded, finishContext({
34980
35129
  coerceDates: true,
34981
35130
  entryExtensions: new Set([...entry.extensions, ...fileExtensions]),
34982
- sourceRoot
35131
+ sourceRoot,
35132
+ ...deferBody === true && {
35133
+ onDeferred,
35134
+ reload: async (filePath) => await loadFile(filePath, false)
35135
+ }
34983
35136
  }));
34984
35137
  }
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 });
35138
+ function unreadableFile(type, path, cause) {
35139
+ 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
35140
  }
34988
35141
  function unknownFormatError(path, ext) {
34989
35142
  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
35143
  }
34991
- function errorMessage(error) {
34992
- return error instanceof Error ? error.message : String(error);
35144
+ function errorMessage(cause) {
35145
+ return cause instanceof Error ? cause.message : String(cause);
35146
+ }
35147
+ function jsTypeName(value) {
35148
+ if (isString(value)) {
35149
+ return "string";
35150
+ }
35151
+ if (isNumber(value)) {
35152
+ return "number";
35153
+ }
35154
+ return isBoolean(value) ? "boolean" : "object";
34993
35155
  }
34994
35156
  function validateEntries(entries, schema, contentTypeName) {
34995
35157
  const required = schema.required ?? [];
@@ -35005,16 +35167,16 @@ function validateEntries(entries, schema, contentTypeName) {
35005
35167
  if (value == null) {
35006
35168
  continue;
35007
35169
  }
35008
- if (isDateFormat(def.format) && typeof value === "string" && !isCoercedDate(value)) {
35170
+ if (isDateFormat(def.format) && isString(value) && !isCoercedDate(value)) {
35009
35171
  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}`);
35172
+ } else if (def.type === "string" && !isString(value)) {
35173
+ console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected string, got ${jsTypeName(value)}`);
35174
+ } else if (def.type === "number" && !isNumber(value)) {
35175
+ console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected number, got ${jsTypeName(value)}`);
35176
+ } else if (def.type === "boolean" && !isBoolean(value)) {
35177
+ console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected boolean, got ${jsTypeName(value)}`);
35016
35178
  } else if (def.type === "array" && !Array.isArray(value)) {
35017
- console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected array, got ${typeof value}`);
35179
+ console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected array, got ${jsTypeName(value)}`);
35018
35180
  }
35019
35181
  }
35020
35182
  }
@@ -35026,9 +35188,19 @@ function refTargetName(ref) {
35026
35188
  }
35027
35189
  return ref.slice(CONTENT_REF_PREFIX.length);
35028
35190
  }
35191
+ function referencedContentTypes(def) {
35192
+ const targets = new Set;
35193
+ for (const field of Object.values(def?.schema?.properties ?? {})) {
35194
+ const target = refTargetName(field.$ref) ?? refTargetName(field.items?.$ref);
35195
+ if (target !== undefined) {
35196
+ targets.add(target);
35197
+ }
35198
+ }
35199
+ return [...targets];
35200
+ }
35029
35201
  function resolveContentTypeRefs(contentTypes, section) {
35030
35202
  for (const [name, contentTypeDef] of Object.entries(section)) {
35031
- const schema = contentTypeDef.schema;
35203
+ const { schema } = contentTypeDef;
35032
35204
  if (!schema?.properties) {
35033
35205
  continue;
35034
35206
  }
@@ -35050,7 +35222,7 @@ function resolveContentTypeRefs(contentTypes, section) {
35050
35222
  }
35051
35223
  for (const entry of entries) {
35052
35224
  const value = entry.data[field];
35053
- if (toOne && typeof value === "string") {
35225
+ if (toOne && isString(value)) {
35054
35226
  const resolved = refEntries.find((e) => e.id === value);
35055
35227
  if (resolved) {
35056
35228
  entry.data[field] = resolved;
@@ -35059,7 +35231,7 @@ function resolveContentTypeRefs(contentTypes, section) {
35059
35231
  }
35060
35232
  } else if (toMany && Array.isArray(value)) {
35061
35233
  entry.data[field] = value.map((id) => {
35062
- if (typeof id !== "string") {
35234
+ if (!isString(id)) {
35063
35235
  return id;
35064
35236
  }
35065
35237
  const resolved = refEntries.find((e) => e.id === id);
@@ -35086,7 +35258,10 @@ function reportUnservedRoutes(contentType, entries, ctx, prefix) {
35086
35258
  return;
35087
35259
  }
35088
35260
  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));
35261
+ const unserved = entries.flatMap((entry) => {
35262
+ const route = entry._meta?.route;
35263
+ return route !== undefined && !patterns.some((pattern) => routeParams(route, pattern, []) !== null) ? [route] : [];
35264
+ });
35090
35265
  if (unserved.length === 0) {
35091
35266
  return;
35092
35267
  }
@@ -35109,7 +35284,11 @@ function routedPaths(contentType, entries, ctx, urlPattern) {
35109
35284
  example ??= route;
35110
35285
  const params = routeParams(route, pattern, ctx.params ?? []);
35111
35286
  if (params) {
35112
- paths.push({ ...params, ...entry._meta === undefined ? {} : { _meta: entry._meta } });
35287
+ const pathEntry = { ...params };
35288
+ if (entry._meta !== undefined) {
35289
+ pathEntry._meta = entry._meta;
35290
+ }
35291
+ paths.push(pathEntry);
35113
35292
  }
35114
35293
  }
35115
35294
  if (paths.length === 0 && example !== undefined) {
@@ -35119,18 +35298,159 @@ function routedPaths(contentType, entries, ctx, urlPattern) {
35119
35298
  return paths;
35120
35299
  }
35121
35300
 
35301
+ class ContentStore extends Map {
35302
+ #source;
35303
+ #mounts;
35304
+ #loads = new Map;
35305
+ #linked = new Set;
35306
+ #entries = new WeakSet;
35307
+ #deferred = new WeakMap;
35308
+ #completing = new WeakMap;
35309
+ constructor(source) {
35310
+ super();
35311
+ this.#source = source;
35312
+ this.#mounts = sectionMounts(source.section, source.root, source.projectConfig);
35313
+ }
35314
+ static async whole(source) {
35315
+ const store = new ContentStore(source);
35316
+ const loaded = await loadContentSection(source.section, source.root, source.formats, source.projectConfig);
35317
+ for (const [name, entries] of loaded) {
35318
+ store.#adopt(name, entries);
35319
+ store.#loads.set(name, Promise.resolve());
35320
+ }
35321
+ resolveContentTypeRefs(store, source.section);
35322
+ for (const name of loaded.keys()) {
35323
+ store.#linked.add(name);
35324
+ }
35325
+ return store;
35326
+ }
35327
+ static lazy(source) {
35328
+ return new ContentStore(source);
35329
+ }
35330
+ async load(name) {
35331
+ if (!Object.hasOwn(this.#source.section, name)) {
35332
+ return;
35333
+ }
35334
+ const related = this.#relatedTypes(name);
35335
+ await Promise.all(related.map(async (type) => await this.#loadType(type)));
35336
+ const unlinked = related.filter((type) => !this.#linked.has(type));
35337
+ for (const type of unlinked) {
35338
+ this.#linked.add(type);
35339
+ }
35340
+ if (unlinked.length > 0) {
35341
+ const { section } = this.#source;
35342
+ resolveContentTypeRefs(this, Object.fromEntries(unlinked.flatMap((type) => section[type] ? [[type, section[type]]] : [])));
35343
+ }
35344
+ return this.get(name);
35345
+ }
35346
+ async materialize(entries) {
35347
+ const seen = new Set;
35348
+ const pending = [];
35349
+ const visit = (entry) => {
35350
+ if (seen.has(entry)) {
35351
+ return;
35352
+ }
35353
+ seen.add(entry);
35354
+ pending.push(this.#complete(entry));
35355
+ for (const value of Object.values(entry.data)) {
35356
+ for (const item of Array.isArray(value) ? value : [value]) {
35357
+ if (this.#isEntry(item)) {
35358
+ visit(item);
35359
+ }
35360
+ }
35361
+ }
35362
+ };
35363
+ for (const entry of entries) {
35364
+ if (entry) {
35365
+ visit(entry);
35366
+ }
35367
+ }
35368
+ await Promise.all(pending);
35369
+ }
35370
+ async complete() {
35371
+ const lists = await Promise.all(Object.keys(this.#source.section).map(async (name) => await this.load(name) ?? []));
35372
+ await this.materialize(lists.flat());
35373
+ }
35374
+ #relatedTypes(name) {
35375
+ const { section } = this.#source;
35376
+ const found = new Set([name]);
35377
+ const queue = [name];
35378
+ for (let type = queue.shift();type !== undefined; type = queue.shift()) {
35379
+ for (const target of referencedContentTypes(section[type])) {
35380
+ if (Object.hasOwn(section, target) && !found.has(target)) {
35381
+ found.add(target);
35382
+ queue.push(target);
35383
+ }
35384
+ }
35385
+ }
35386
+ return [...found];
35387
+ }
35388
+ #loadType(name) {
35389
+ let load = this.#loads.get(name);
35390
+ if (!load) {
35391
+ const { section, root, formats, projectConfig } = this.#source;
35392
+ load = loadSectionType(name, {
35393
+ formats,
35394
+ mounts: this.#mounts,
35395
+ onDeferred: (entry, completeBody) => {
35396
+ this.#deferred.set(entry, completeBody);
35397
+ },
35398
+ projectConfig,
35399
+ root,
35400
+ section
35401
+ }).then((entries) => {
35402
+ this.#adopt(name, entries);
35403
+ });
35404
+ load.catch(() => {
35405
+ this.#loads.delete(name);
35406
+ });
35407
+ this.#loads.set(name, load);
35408
+ }
35409
+ return load;
35410
+ }
35411
+ #adopt(name, entries) {
35412
+ for (const entry of entries) {
35413
+ this.#entries.add(entry);
35414
+ }
35415
+ this.set(name, entries);
35416
+ }
35417
+ #isEntry(value) {
35418
+ return isNonNullObject(value) && this.#entries.has(value);
35419
+ }
35420
+ #complete(entry) {
35421
+ let completing = this.#completing.get(entry);
35422
+ if (!completing) {
35423
+ const completeBody = this.#deferred.get(entry);
35424
+ if (!completeBody) {
35425
+ return Promise.resolve();
35426
+ }
35427
+ completing = completeBody().then(() => {
35428
+ this.#deferred.delete(entry);
35429
+ });
35430
+ completing.catch(() => {
35431
+ this.#completing.delete(entry);
35432
+ });
35433
+ this.#completing.set(entry, completing);
35434
+ }
35435
+ return completing;
35436
+ }
35437
+ }
35438
+
35122
35439
  class Content {
35123
35440
  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;
35441
+ const source = {
35442
+ formats: ctx.registry.formats,
35443
+ projectConfig: ctx.projectConfig,
35444
+ root: ctx.root,
35445
+ section: sectionValue ?? {}
35446
+ };
35447
+ return ctx.lazy === true ? ContentStore.lazy(source) : await ContentStore.whole(source);
35128
35448
  }
35129
35449
  static assets(sectionValue, ctx) {
35130
35450
  return contentAssetMounts(sectionValue ?? {}, ctx.root, ctx.projectConfig);
35131
35451
  }
35132
35452
  static async resolvePaths(pathsDef, ctx) {
35133
- const entries = ctx.data.get(pathsDef.contentType);
35453
+ const entries = await entriesOf(ctx.data, pathsDef.contentType);
35134
35454
  if (!entries || entries.length === 0) {
35135
35455
  console.warn(`Warning: $paths references content type "${pathsDef.contentType}" but it has no entries`);
35136
35456
  return [];
@@ -35139,7 +35459,7 @@ class Content {
35139
35459
  const field = pathsDef.field ?? "id";
35140
35460
  const localized = entries.some((entry) => entry._meta?.locale !== undefined);
35141
35461
  const wanted = ctx.locale;
35142
- const scoped = localized && typeof wanted === "string" ? entries.filter((entry) => entry._meta?.locale === wanted) : entries;
35462
+ const scoped = localized && isString(wanted) ? entries.filter((entry) => entry._meta?.locale === wanted) : entries;
35143
35463
  if (pathsDef.param === undefined && pathsDef.field === undefined && ctx.urlPattern !== undefined && scoped.some((entry) => entry._meta?.route !== undefined)) {
35144
35464
  return routedPaths(pathsDef.contentType, scoped, ctx, ctx.urlPattern);
35145
35465
  }
@@ -35165,13 +35485,17 @@ class Content {
35165
35485
  }
35166
35486
  export {
35167
35487
  Content,
35488
+ ContentStore,
35168
35489
  LOCALE_PLACEHOLDER,
35169
35490
  contentAssetMounts,
35170
35491
  getContentTypeElements,
35171
35492
  loadContentConfig,
35172
35493
  loadContentSection,
35494
+ loadSectionType,
35173
35495
  localeSource,
35174
35496
  localesForExpansion,
35497
+ referencedContentTypes,
35175
35498
  resolveContentTypeRefs,
35176
- rewriteEntryAssets
35499
+ rewriteEntryAssets,
35500
+ sectionMounts
35177
35501
  };