@ai-matrx/records 0.55.1 → 0.56.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.
package/dist/index.js CHANGED
@@ -225,6 +225,7 @@ function fieldKindFor(field, kernel) {
225
225
  return "text";
226
226
  }
227
227
  }
228
+ var UUID_TEXT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
228
229
  var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
229
230
  var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
230
231
  function coercionLabel(field) {
@@ -316,11 +317,23 @@ function coerceTypedAnswer(field, raw, opts = {}) {
316
317
  // id belongs, and every filter, colour and rollup built on that column
317
318
  // would stop agreeing with it. So it is refused and the person is told
318
319
  // which control does hold it.
320
+ //
321
+ // BUT AN ID IS AN IDENTITY (RELATION-TARGETS, 2026-09-23). A Person or File
322
+ // column holds the kernel Person / File record, and the store itself now
323
+ // turns the id a writer actually holds — a member's user id, an uploaded
324
+ // file's id, or the record's own id — into that record at every write door
325
+ // (`custom.relation_kernel_record`), refusing anything else in its own
326
+ // words ("… names someone who is not a member of …"). So an import column
327
+ // of member ids, or an agent handing over the file it just uploaded, is a
328
+ // value and not a refusal; the store, not this mirror, decides whether the
329
+ // id is somebody here.
319
330
  case "member":
331
+ if (UUID_TEXT.test(text)) return { value: text.toLowerCase() };
320
332
  return {
321
333
  refusal: `${label} names a person in this organization, so it is chosen rather than typed. Open it and pick them.`
322
334
  };
323
335
  case "attachment":
336
+ if (UUID_TEXT.test(text)) return { value: text.toLowerCase() };
324
337
  return {
325
338
  refusal: `${label} holds a file, which cannot arrive as typed-in text. Open it and attach the file.`
326
339
  };
@@ -425,6 +438,80 @@ function stableFilterKey(filter) {
425
438
  return `{${parts.join(",")}}`;
426
439
  }
427
440
 
441
+ // src/grid.ts
442
+ function actionRefusals(error) {
443
+ return Array.isArray(error.detail) ? error.detail : [];
444
+ }
445
+ function exampleSpec(useCase) {
446
+ return {
447
+ business: useCase.business.name,
448
+ tables: useCase.tables.map((t) => ({
449
+ token: t.token,
450
+ name: t.name,
451
+ labelSingular: t.labelSingular,
452
+ labelPlural: t.labelPlural,
453
+ type: t.type,
454
+ display: t.display,
455
+ weight: t.weight,
456
+ ordered: t.ordered,
457
+ titleField: t.titleField,
458
+ describes: t.describes,
459
+ fields: t.fields.map((f) => ({
460
+ key: f.key,
461
+ label: f.label,
462
+ parityType: f.parityType,
463
+ multi: f.multi,
464
+ dated: f.dated,
465
+ required: f.required,
466
+ unit: f.unit ?? void 0,
467
+ rules: f.rules,
468
+ choices: f.choices,
469
+ source: f.source,
470
+ computeOn: f.computeOn,
471
+ relationTarget: f.relationTarget,
472
+ relationMax: f.relationMax,
473
+ sensitivity: f.sensitivity,
474
+ contextPolicy: f.contextPolicy
475
+ })),
476
+ rows: t.rows
477
+ }))
478
+ };
479
+ }
480
+ function exportCellText(value) {
481
+ if (value === null || value === void 0) return "";
482
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
483
+ return JSON.stringify(value);
484
+ }
485
+ async function exportPageXlsx(page, labels = {}, sheetLabel = "Records") {
486
+ const XLSX = await import("xlsx");
487
+ const header = page.columns.map((c) => labels[c] ?? c);
488
+ const body = page.rows.map((row) => page.columns.map((c) => exportCellText(row[c])));
489
+ const workbook = XLSX.utils.book_new();
490
+ const name = sheetLabel.replace(/[\\/?*[\]:]/g, " ").trim().slice(0, 31) || "Records";
491
+ XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([header, ...body]), name);
492
+ const withheld = Object.entries(page.withheld ?? {});
493
+ if (withheld.length > 0) {
494
+ XLSX.utils.book_append_sheet(
495
+ workbook,
496
+ XLSX.utils.aoa_to_sheet([
497
+ ["Column", "Why it is empty in this export"],
498
+ ...withheld.map(([k, v]) => [labels[k] ?? k, typeof v === "string" ? v : JSON.stringify(v)])
499
+ ]),
500
+ "Withheld columns"
501
+ );
502
+ }
503
+ if (page.page?.next !== null && page.page?.next !== void 0) {
504
+ XLSX.utils.book_append_sheet(
505
+ workbook,
506
+ XLSX.utils.aoa_to_sheet([[
507
+ `This file holds the first ${page.page.returned} rows. The table has more; export again from row ${page.page.next + 1}.`
508
+ ]]),
509
+ "More rows"
510
+ );
511
+ }
512
+ return XLSX.write(workbook, { type: "array", bookType: "xlsx", compression: true });
513
+ }
514
+
428
515
  // src/capture-queue.ts
429
516
  var DB_NAME = "matrx-capture";
430
517
  var DB_VERSION = 1;
@@ -2561,6 +2648,7 @@ export {
2561
2648
  TABLE_TYPES,
2562
2649
  VALUE_ALTERNATE_KEYS,
2563
2650
  VALUE_ENVELOPE_KEYS,
2651
+ actionRefusals,
2564
2652
  archivedByLine,
2565
2653
  archivedWhen,
2566
2654
  asPermissionLevel,
@@ -2575,6 +2663,8 @@ export {
2575
2663
  emptyArchiveLine,
2576
2664
  emptyPortalArchiveLine,
2577
2665
  err,
2666
+ exampleSpec,
2667
+ exportPageXlsx,
2578
2668
  fieldKindFor,
2579
2669
  forgetAllOps,
2580
2670
  highestLevel,