@jxsuite/parser 4.2.0 → 4.3.1

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.
@@ -80,6 +80,9 @@ function isRef(value) {
80
80
  function isNodeObject(value) {
81
81
  return isJsonObject(value);
82
82
  }
83
+ function isTemplateString(value) {
84
+ return typeof value === "string" && value.includes("${");
85
+ }
83
86
  function isPlainTagName(tagName) {
84
87
  return typeof tagName === "string";
85
88
  }
@@ -272,6 +275,195 @@ var init_alerts = __esm(() => {
272
275
  LINE_END = /\r?\n/;
273
276
  });
274
277
 
278
+ // ../../packages/schema/src/content-query.ts
279
+ function isPlainObject(value) {
280
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
281
+ }
282
+ function comparable(value) {
283
+ if (value instanceof Date) {
284
+ if (Number.isNaN(value.getTime())) {
285
+ return;
286
+ }
287
+ const iso = value.toISOString();
288
+ return iso.endsWith("T00:00:00.000Z") ? iso.slice(0, 10) : iso.replace(/\.\d{3}Z$/, "Z");
289
+ }
290
+ return value;
291
+ }
292
+ function deepEqual(a, b) {
293
+ if (a === b) {
294
+ return true;
295
+ }
296
+ if (Array.isArray(a) && Array.isArray(b)) {
297
+ return a.length === b.length && a.every((item, i) => deepEqual(comparable(item), comparable(b[i])));
298
+ }
299
+ if (isPlainObject(a) && isPlainObject(b)) {
300
+ const keys = Object.keys(a);
301
+ return keys.length === Object.keys(b).length && keys.every((key) => (key in b) && deepEqual(comparable(a[key]), comparable(b[key])));
302
+ }
303
+ return false;
304
+ }
305
+ function sameValue(actual, expected) {
306
+ const a = comparable(actual);
307
+ if (Array.isArray(expected) || isPlainObject(expected)) {
308
+ return deepEqual(a, expected);
309
+ }
310
+ if (Array.isArray(a)) {
311
+ return a.some((item) => comparable(item) === expected);
312
+ }
313
+ return a === expected;
314
+ }
315
+ function readField(data, key) {
316
+ if (Object.hasOwn(data, key)) {
317
+ return data[key];
318
+ }
319
+ if (!key.includes(".")) {
320
+ return;
321
+ }
322
+ return readPath(data, key.split("."));
323
+ }
324
+ function readPath(holder, [part, ...rest]) {
325
+ if (part === undefined || !isPlainObject(holder) || !Object.hasOwn(holder, part)) {
326
+ return;
327
+ }
328
+ const value = holder[part];
329
+ return rest.length === 0 ? value : readPath(value, rest);
330
+ }
331
+ function order(actual, operand) {
332
+ const a = comparable(actual);
333
+ if (isNumber(a) && isNumber(operand)) {
334
+ return a - operand;
335
+ }
336
+ if (isString(a) && isString(operand)) {
337
+ return a < operand ? -1 : a > operand ? 1 : 0;
338
+ }
339
+ return;
340
+ }
341
+ function isNowOperand(operand) {
342
+ return isPlainObject(operand) && operand.$now === true && Object.keys(operand).length === 1;
343
+ }
344
+ function timeOf(value) {
345
+ const time = value instanceof Date ? value.getTime() : isString(value) ? Date.parse(value) : Number.NaN;
346
+ return Number.isNaN(time) ? undefined : time;
347
+ }
348
+ function operatorTest(where, op, operand, ctx) {
349
+ switch (op) {
350
+ case "$eq": {
351
+ return (actual) => sameValue(actual, operand);
352
+ }
353
+ case "$ne": {
354
+ return (actual) => !sameValue(actual, operand);
355
+ }
356
+ case "$in":
357
+ case "$nin": {
358
+ if (!Array.isArray(operand)) {
359
+ throw new TypeError(`${where}.${op}: expected an array, got ${JSON.stringify(operand)}`);
360
+ }
361
+ const hit = (actual) => operand.some((candidate) => sameValue(actual, candidate));
362
+ return op === "$in" ? hit : (actual) => !hit(actual);
363
+ }
364
+ case "$exists": {
365
+ if (!isBoolean(operand)) {
366
+ throw new TypeError(`${where}.$exists: expected true or false, got ${JSON.stringify(operand)}`);
367
+ }
368
+ return (actual) => (actual !== undefined && actual !== null) === operand;
369
+ }
370
+ default: {
371
+ const test = (diff) => op === "$gt" ? diff > 0 : op === "$gte" ? diff >= 0 : op === "$lt" ? diff < 0 : diff <= 0;
372
+ if (isNowOperand(operand)) {
373
+ return (actual) => {
374
+ if (actual === undefined || actual === null) {
375
+ return false;
376
+ }
377
+ const time = timeOf(actual);
378
+ return time === undefined ? ctx.onBadDate : test(time - ctx.now().getTime());
379
+ };
380
+ }
381
+ if (!isString(operand) && !isNumber(operand)) {
382
+ throw new TypeError(`${where}.${op}: expected a string or number, got ${JSON.stringify(operand)}`);
383
+ }
384
+ return (actual) => {
385
+ const diff = order(actual, operand);
386
+ return diff !== undefined && test(diff);
387
+ };
388
+ }
389
+ }
390
+ }
391
+ function operatorKeys(condition) {
392
+ const keys = Object.keys(condition);
393
+ const dollars = keys.filter((key) => key.startsWith("$"));
394
+ if (dollars.length === 0) {
395
+ return null;
396
+ }
397
+ return dollars.length === keys.length ? keys : [];
398
+ }
399
+ function compileCondition(key, condition, ctx) {
400
+ const where = `${ctx.name}.${key}`;
401
+ if (!isPlainObject(condition)) {
402
+ return (actual) => sameValue(actual, condition);
403
+ }
404
+ const ops = operatorKeys(condition);
405
+ if (ops === null) {
406
+ return (actual) => deepEqual(comparable(actual), condition);
407
+ }
408
+ if (ops.length === 0) {
409
+ throw new TypeError(`${where}: cannot mix $operators with plain keys in one condition`);
410
+ }
411
+ const tests = ops.map((op) => {
412
+ if (!OPERATORS.has(op)) {
413
+ throw new TypeError(`${where}: unknown operator "${op}" (use ${[...OPERATORS].join(", ")})`);
414
+ }
415
+ return operatorTest(where, op, condition[op], ctx);
416
+ });
417
+ return (actual) => tests.every((test) => test(actual));
418
+ }
419
+ function compileFields(query, ctx) {
420
+ return Object.entries(query).map(([key, condition]) => {
421
+ if (key.startsWith("$")) {
422
+ throw new TypeError(`${ctx.name}.${key}: top-level operators are not supported, conditions on different fields already combine with AND`);
423
+ }
424
+ return { key, test: compileCondition(key, condition, ctx) };
425
+ });
426
+ }
427
+ function compileWhere(where, options = {}) {
428
+ if (where === undefined) {
429
+ return Object.assign(() => true, { why: nothing });
430
+ }
431
+ if (!isPlainObject(where)) {
432
+ throw new TypeError(`"where" must be an object mapping frontmatter fields to conditions`);
433
+ }
434
+ const ctx = { name: "where", now: options.now ?? realClock, onBadDate: false };
435
+ const tests = compileFields(where, ctx);
436
+ return Object.assign((data) => tests.every(({ key, test }) => test(readField(data, key))), {
437
+ why: (data) => tests.find(({ key, test }) => !test(readField(data, key)))?.key
438
+ });
439
+ }
440
+ function compileHold(hold, options = {}) {
441
+ if (hold === undefined) {
442
+ return nothing;
443
+ }
444
+ if (!Array.isArray(hold)) {
445
+ throw new TypeError(`"hold" must be an array of { "label", "when" } rules`);
446
+ }
447
+ const now = options.now ?? realClock;
448
+ const rules = hold.map((rule, index) => {
449
+ const at = `hold[${index}]`;
450
+ if (!isPlainObject(rule) || !isString(rule.label) || rule.label.trim() === "") {
451
+ throw new TypeError(`${at}: expected a non-empty "label"`);
452
+ }
453
+ if (!isPlainObject(rule.when) || Object.keys(rule.when).length === 0) {
454
+ throw new TypeError(`${at}: expected "when" to name at least one frontmatter field`);
455
+ }
456
+ const ctx = { name: `${at}.when`, now, onBadDate: true };
457
+ return { label: rule.label, tests: compileFields(rule.when, ctx) };
458
+ });
459
+ return (data) => rules.find(({ tests }) => tests.every(({ key, test }) => test(readField(data, key))))?.label;
460
+ }
461
+ var OPERATORS, nothing = () => {}, realClock = () => new Date;
462
+ var init_content_query = __esm(() => {
463
+ init_guards();
464
+ OPERATORS = new Set(["$eq", "$ne", "$in", "$nin", "$exists", "$gt", "$gte", "$lt", "$lte"]);
465
+ });
466
+
275
467
  // src/content-rules.ts
276
468
  function escapeRegExp(text) {
277
469
  return text.replaceAll(REGEXP_SPECIAL, String.raw`\$&`);
@@ -397,7 +589,7 @@ function mayNameDirectory(pattern) {
397
589
  }
398
590
  function compileExclude(patterns) {
399
591
  if (patterns === undefined) {
400
- return { empty: true, excludedBy: nothing, excludesDir: () => false };
592
+ return { empty: true, excludedBy: nothing2, excludesDir: () => false };
401
593
  }
402
594
  if (!Array.isArray(patterns)) {
403
595
  throw new TypeError(`"exclude" must be an array of glob patterns`);
@@ -437,152 +629,11 @@ function compileExclude(patterns) {
437
629
  excludedBy: (path) => files.find((f) => f.re.test(path))?.pattern
438
630
  };
439
631
  }
440
- function isPlainObject(value) {
441
- return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
442
- }
443
- function comparable(value) {
444
- if (value instanceof Date) {
445
- if (Number.isNaN(value.getTime())) {
446
- return;
447
- }
448
- const iso = value.toISOString();
449
- return iso.endsWith("T00:00:00.000Z") ? iso.slice(0, 10) : iso.replace(/\.\d{3}Z$/, "Z");
450
- }
451
- return value;
452
- }
453
- function deepEqual(a, b) {
454
- if (a === b) {
455
- return true;
456
- }
457
- if (Array.isArray(a) && Array.isArray(b)) {
458
- return a.length === b.length && a.every((item, i) => deepEqual(comparable(item), comparable(b[i])));
459
- }
460
- if (isPlainObject(a) && isPlainObject(b)) {
461
- const keys = Object.keys(a);
462
- return keys.length === Object.keys(b).length && keys.every((key) => (key in b) && deepEqual(comparable(a[key]), comparable(b[key])));
463
- }
464
- return false;
465
- }
466
- function sameValue(actual, expected) {
467
- const a = comparable(actual);
468
- if (Array.isArray(expected) || isPlainObject(expected)) {
469
- return deepEqual(a, expected);
470
- }
471
- if (Array.isArray(a)) {
472
- return a.some((item) => comparable(item) === expected);
473
- }
474
- return a === expected;
475
- }
476
- function readField(data, key) {
477
- if (Object.hasOwn(data, key)) {
478
- return data[key];
479
- }
480
- if (!key.includes(".")) {
481
- return;
482
- }
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;
488
- }
489
- const value = holder[part];
490
- return rest.length === 0 ? value : readPath(value, rest);
491
- }
492
- function order(actual, operand) {
493
- const a = comparable(actual);
494
- if (isNumber(a) && isNumber(operand)) {
495
- return a - operand;
496
- }
497
- if (isString(a) && isString(operand)) {
498
- return a < operand ? -1 : a > operand ? 1 : 0;
499
- }
500
- return;
501
- }
502
- function operatorTest(where, op, operand) {
503
- switch (op) {
504
- case "$eq": {
505
- return (actual) => sameValue(actual, operand);
506
- }
507
- case "$ne": {
508
- return (actual) => !sameValue(actual, operand);
509
- }
510
- case "$in":
511
- case "$nin": {
512
- if (!Array.isArray(operand)) {
513
- throw new TypeError(`${where}.${op}: expected an array, got ${JSON.stringify(operand)}`);
514
- }
515
- const hit = (actual) => operand.some((candidate) => sameValue(actual, candidate));
516
- return op === "$in" ? hit : (actual) => !hit(actual);
517
- }
518
- case "$exists": {
519
- if (!isBoolean(operand)) {
520
- throw new TypeError(`${where}.$exists: expected true or false, got ${JSON.stringify(operand)}`);
521
- }
522
- return (actual) => (actual !== undefined && actual !== null) === operand;
523
- }
524
- default: {
525
- if (!isString(operand) && !isNumber(operand)) {
526
- throw new TypeError(`${where}.${op}: expected a string or number, got ${JSON.stringify(operand)}`);
527
- }
528
- const test = (diff) => op === "$gt" ? diff > 0 : op === "$gte" ? diff >= 0 : op === "$lt" ? diff < 0 : diff <= 0;
529
- return (actual) => {
530
- const diff = order(actual, operand);
531
- return diff !== undefined && test(diff);
532
- };
533
- }
534
- }
535
- }
536
- function operatorKeys(condition) {
537
- const keys = Object.keys(condition);
538
- const dollars = keys.filter((key) => key.startsWith("$"));
539
- if (dollars.length === 0) {
540
- return null;
541
- }
542
- return dollars.length === keys.length ? keys : [];
543
- }
544
- function compileCondition(key, condition) {
545
- const where = `where.${key}`;
546
- if (!isPlainObject(condition)) {
547
- return (actual) => sameValue(actual, condition);
548
- }
549
- const ops = operatorKeys(condition);
550
- if (ops === null) {
551
- return (actual) => deepEqual(comparable(actual), condition);
552
- }
553
- if (ops.length === 0) {
554
- throw new TypeError(`${where}: cannot mix $operators with plain keys in one condition`);
555
- }
556
- const tests = ops.map((op) => {
557
- if (!OPERATORS.has(op)) {
558
- throw new TypeError(`${where}: unknown operator "${op}" (use ${[...OPERATORS].join(", ")})`);
559
- }
560
- return operatorTest(where, op, condition[op]);
561
- });
562
- return (actual) => tests.every((test) => test(actual));
563
- }
564
- function compileWhere(where) {
565
- if (where === undefined) {
566
- return Object.assign(() => true, { why: nothing });
567
- }
568
- if (!isPlainObject(where)) {
569
- throw new TypeError(`"where" must be an object mapping frontmatter fields to conditions`);
570
- }
571
- const tests = Object.entries(where).map(([key, condition]) => {
572
- if (key.startsWith("$")) {
573
- throw new TypeError(`where.${key}: top-level operators are not supported, conditions on different fields already combine with AND`);
574
- }
575
- return { key, test: compileCondition(key, condition) };
576
- });
577
- return Object.assign((data) => tests.every(({ key, test }) => test(readField(data, key))), {
578
- why: (data) => tests.find(({ key, test }) => !test(readField(data, key)))?.key
579
- });
580
- }
581
- var nothing = () => {}, REGEXP_SPECIAL, MAX_STAR_RUNS = 4, OPERATORS;
632
+ var nothing2 = () => {}, REGEXP_SPECIAL, MAX_STAR_RUNS = 4;
582
633
  var init_content_rules = __esm(() => {
583
634
  init_guards();
635
+ init_content_query();
584
636
  REGEXP_SPECIAL = /[$()*+.?[\\\]^{|}]/g;
585
- OPERATORS = new Set(["$eq", "$ne", "$in", "$nin", "$exists", "$gt", "$gte", "$lt", "$lte"]);
586
637
  });
587
638
 
588
639
  // ../../node_modules/bail/index.js
@@ -34647,7 +34698,7 @@ function isNonRelativeRef(value) {
34647
34698
  }
34648
34699
  var DOCUMENT_EXTENSIONS = new Set([".md", ".markdown", ".mdx"]);
34649
34700
  function mountFilterFor(def) {
34650
- if (!def || def.exclude === undefined && def.where === undefined && def.route === undefined) {
34701
+ if (!def || def.exclude === undefined && def.where === undefined && def.hold === undefined && def.route === undefined) {
34651
34702
  return;
34652
34703
  }
34653
34704
  let exclude;
@@ -34799,11 +34850,11 @@ function loadContentConfig(projectRoot, projectConfig) {
34799
34850
  const config = { content: projectConfig?.content ?? {} };
34800
34851
  return { config, contentDir };
34801
34852
  }
34802
- async function loadContentSection(section, root, formats, projectConfig) {
34853
+ async function loadContentSection(section, root, formats, projectConfig, includeHeld) {
34803
34854
  const contentTypes = new Map;
34804
34855
  const mounts = sectionMounts(section, root, projectConfig);
34805
34856
  for (const name of Object.keys(section)) {
34806
- contentTypes.set(name, await loadSectionType(name, { formats, mounts, projectConfig, root, section }));
34857
+ contentTypes.set(name, await loadSectionType(name, { formats, includeHeld, mounts, projectConfig, root, section }));
34807
34858
  }
34808
34859
  return contentTypes;
34809
34860
  }
@@ -34814,7 +34865,7 @@ function sectionMounts(section, root, projectConfig) {
34814
34865
  ]));
34815
34866
  }
34816
34867
  async function loadSectionType(name, source) {
34817
- const { section, root, formats, projectConfig, mounts, onDeferred } = source;
34868
+ const { section, root, formats, projectConfig, mounts, onDeferred, includeHeld } = source;
34818
34869
  const contentTypeDef = section[name];
34819
34870
  if (!contentTypeDef) {
34820
34871
  return [];
@@ -34823,6 +34874,7 @@ async function loadSectionType(name, source) {
34823
34874
  const typeSource = contentTypeDef.source;
34824
34875
  if (!isString(typeSource) || !typeSource.includes(LOCALE_PLACEHOLDER)) {
34825
34876
  return await loadContentType(name, contentTypeDef, root, formats, {
34877
+ includeHeld,
34826
34878
  mount: mounts.get(name),
34827
34879
  projectConfig,
34828
34880
  ...deferral
@@ -34837,6 +34889,7 @@ async function loadSectionType(name, source) {
34837
34889
  for (const locale of locales) {
34838
34890
  const localized = { ...contentTypeDef, source: localeSource(root, typeSource, locale) };
34839
34891
  const entries = await loadContentType(name, localized, root, formats, {
34892
+ includeHeld,
34840
34893
  locale,
34841
34894
  mount: mounts.get(`${name}/${locale}`),
34842
34895
  projectConfig,
@@ -34863,6 +34916,7 @@ var KNOWN_TYPE_KEYS = [
34863
34916
  "alerts",
34864
34917
  "exclude",
34865
34918
  "format",
34919
+ "hold",
34866
34920
  "idField",
34867
34921
  "indexRoute",
34868
34922
  "links",
@@ -34922,7 +34976,8 @@ function compileTypeRules(name, def) {
34922
34976
  idField: def.idField,
34923
34977
  links,
34924
34978
  route: parseRouteConfig(def.route, def.indexRoute, name),
34925
- where: def.where === undefined ? null : compileWhere(def.where)
34979
+ where: def.where === undefined ? null : compileWhere(def.where),
34980
+ hold: def.hold === undefined ? null : compileHold(def.hold)
34926
34981
  };
34927
34982
  } catch (error) {
34928
34983
  const message = errorMessage(error);
@@ -34937,6 +34992,7 @@ function finishEntries(files, ctx) {
34937
34992
  const { name, rules } = ctx;
34938
34993
  const kept = [];
34939
34994
  const unpublished = new Map;
34995
+ const held = [];
34940
34996
  let loadedCount = 0;
34941
34997
  for (const file of files) {
34942
34998
  const keep = [];
@@ -34946,8 +35002,19 @@ function finishEntries(files, ctx) {
34946
35002
  if (file.rel !== null && file.entries.length === 1) {
34947
35003
  unpublished.set(file.rel, `left out by where.${rules.where.why(entry.data) ?? "filter"}`);
34948
35004
  }
34949
- } else {
35005
+ continue;
35006
+ }
35007
+ const hold = rules.hold?.(entry.data);
35008
+ if (hold === undefined) {
35009
+ keep.push(entry);
35010
+ } else if (ctx.includeHeld === true) {
35011
+ entry._meta = { ...entry._meta, hold };
34950
35012
  keep.push(entry);
35013
+ } else {
35014
+ held.push(`${file.rel ?? entry.id} (${hold})`);
35015
+ if (file.rel !== null && file.entries.length === 1) {
35016
+ unpublished.set(file.rel, `held: ${hold}`);
35017
+ }
34951
35018
  }
34952
35019
  }
34953
35020
  if (keep.length > 0) {
@@ -34958,6 +35025,9 @@ function finishEntries(files, ctx) {
34958
35025
  const [first] = files.flatMap((file) => file.entries);
34959
35026
  console.warn(`Content type "${name}": "where" left out all ${loadedCount} entries, so the collection is ` + `empty (it failed on "${first ? rules.where.why(first.data) ?? "?" : "?"}" for the first one). ` + `Check the field names and the value types: YAML \`true\` is a boolean, "true" is text.`);
34960
35027
  }
35028
+ if (held.length > 0) {
35029
+ console.info(`Content type "${name}": holding ${held.length} from this build: ${held.join(", ")}`);
35030
+ }
34961
35031
  for (const file of kept) {
34962
35032
  for (const entry of file.entries) {
34963
35033
  if (file.rel !== null) {
@@ -35225,12 +35295,13 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, opti
35225
35295
  if (!source) {
35226
35296
  return [];
35227
35297
  }
35228
- const { mount, projectConfig, locale, deferBody, onDeferred } = options;
35298
+ const { mount, projectConfig, locale, deferBody, onDeferred, includeHeld } = options;
35229
35299
  const { schema } = contentTypeDef;
35230
35300
  const rules = compileTypeRules(name, contentTypeDef);
35231
35301
  const finishContext = (extra) => ({
35232
35302
  coerceDates: false,
35233
35303
  entryExtensions: new Set,
35304
+ includeHeld,
35234
35305
  locale,
35235
35306
  mount,
35236
35307
  name,
@@ -35519,7 +35590,7 @@ class ContentStore extends Map {
35519
35590
  }
35520
35591
  static async whole(source) {
35521
35592
  const store = new ContentStore(source);
35522
- const loaded = await loadContentSection(source.section, source.root, source.formats, source.projectConfig);
35593
+ const loaded = await loadContentSection(source.section, source.root, source.formats, source.projectConfig, source.includeHeld);
35523
35594
  for (const [name, entries] of loaded) {
35524
35595
  store.#adopt(name, entries);
35525
35596
  store.#loads.set(name, Promise.resolve());
@@ -35594,8 +35665,9 @@ class ContentStore extends Map {
35594
35665
  #loadType(name) {
35595
35666
  let load = this.#loads.get(name);
35596
35667
  if (!load) {
35597
- const { section, root, formats, projectConfig } = this.#source;
35668
+ const { section, root, formats, projectConfig, includeHeld } = this.#source;
35598
35669
  load = loadSectionType(name, {
35670
+ includeHeld,
35599
35671
  formats,
35600
35672
  mounts: this.#mounts,
35601
35673
  onDeferred: (entry, completeBody) => {
@@ -35646,6 +35718,7 @@ class Content {
35646
35718
  static async projectData(sectionValue, ctx) {
35647
35719
  const source = {
35648
35720
  formats: ctx.registry.formats,
35721
+ includeHeld: ctx.includeHeld,
35649
35722
  projectConfig: ctx.projectConfig,
35650
35723
  root: ctx.root,
35651
35724
  section: sectionValue ?? {}
package/dist/content.js CHANGED
@@ -80,6 +80,9 @@ function isRef(value) {
80
80
  function isNodeObject(value) {
81
81
  return isJsonObject(value);
82
82
  }
83
+ function isTemplateString(value) {
84
+ return typeof value === "string" && value.includes("${");
85
+ }
83
86
  function isPlainTagName(tagName) {
84
87
  return typeof tagName === "string";
85
88
  }