@ai-matrx/records 0.81.2 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/app-table/index.cjs +1 -1
  3. package/dist/app-table/index.cjs.map +1 -1
  4. package/dist/app-table/index.d.cts +3 -3
  5. package/dist/app-table/index.d.ts +3 -3
  6. package/dist/app-table/index.js +1 -1
  7. package/dist/app-table/index.js.map +1 -1
  8. package/dist/{client-DLIKeBWF.d.cts → client-CesQciPd.d.cts} +1 -1
  9. package/dist/{client-D1LTezMZ.d.ts → client-TU-nvgcF.d.ts} +1 -1
  10. package/dist/core/index.cjs +3 -3
  11. package/dist/core/index.cjs.map +1 -1
  12. package/dist/core/index.d.cts +4 -4
  13. package/dist/core/index.d.ts +4 -4
  14. package/dist/core/index.js +3 -3
  15. package/dist/core/index.js.map +1 -1
  16. package/dist/data/index.cjs +1 -1
  17. package/dist/data/index.cjs.map +1 -1
  18. package/dist/data/index.d.cts +2 -2
  19. package/dist/data/index.d.ts +2 -2
  20. package/dist/data/index.js +1 -1
  21. package/dist/data/index.js.map +1 -1
  22. package/dist/{declareTable-02uao-5n.d.cts → declareTable-B9sr2E0h.d.cts} +2 -2
  23. package/dist/{declareTable-C5FlIS0z.d.ts → declareTable-COXYIOiT.d.ts} +2 -2
  24. package/dist/index.cjs +1 -1
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/react/index.cjs +3 -3
  31. package/dist/react/index.cjs.map +1 -1
  32. package/dist/react/index.d.cts +3 -19
  33. package/dist/react/index.d.ts +3 -19
  34. package/dist/react/index.js +3 -3
  35. package/dist/react/index.js.map +1 -1
  36. package/dist/{table-kind-LodkBhCZ.d.cts → table-kind-B4vIKJVM.d.cts} +3 -19
  37. package/dist/{table-kind-COmkYYkc.d.ts → table-kind-C5T-DOdc.d.ts} +3 -19
  38. package/dist/templates/index.cjs +147 -4
  39. package/dist/templates/index.cjs.map +1 -1
  40. package/dist/templates/index.js +147 -4
  41. package/dist/templates/index.js.map +1 -1
  42. package/package.json +4 -4
@@ -4401,8 +4401,8 @@ function validateTemplate(raw, options = {}) {
4401
4401
  for (const k of rule.filled ?? []) if (!filled(val(k))) say(at, `"${row.key}" is ${st}, so "${fieldOf(table, k)?.label ?? k}" is filled; it is empty.`);
4402
4402
  for (const [k, v] of Object.entries(rule.equals ?? {})) {
4403
4403
  const got = val(k) ?? null;
4404
- const same2 = sameJson(got, v) || typeof v === "number" && parseNumeric(storeText(got) ?? "") !== null && qCmp(parseNumeric(storeText(got) ?? ""), parseNumeric(String(v))) === 0;
4405
- if (!same2) say(at, `"${row.key}" is ${st}, so "${fieldOf(table, k)?.label ?? k}" is ${JSON.stringify(v)}; it is ${JSON.stringify(got)}.`);
4404
+ const same3 = sameJson(got, v) || typeof v === "number" && parseNumeric(storeText(got) ?? "") !== null && qCmp(parseNumeric(storeText(got) ?? ""), parseNumeric(String(v))) === 0;
4405
+ if (!same3) say(at, `"${row.key}" is ${st}, so "${fieldOf(table, k)?.label ?? k}" is ${JSON.stringify(v)}; it is ${JSON.stringify(got)}.`);
4406
4406
  }
4407
4407
  }
4408
4408
  };
@@ -5740,7 +5740,7 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
5740
5740
  }
5741
5741
  }
5742
5742
  const rows = t.rows;
5743
- const same2 = (x, y) => {
5743
+ const same3 = (x, y) => {
5744
5744
  const a = storeText(x);
5745
5745
  const b = storeText(y);
5746
5746
  if (a === null || b === null) return false;
@@ -5758,7 +5758,7 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
5758
5758
  };
5759
5759
  for (const sf of t.fields.filter((ff) => !isComputed(ff) && !isRelation(ff) && ff.key !== l.key)) {
5760
5760
  const have = rows.filter((r) => filled(r.values?.[sf.key]));
5761
- if (have.length >= 3 && have.every((r) => same2(r.values?.[sf.key], far(r)))) {
5761
+ if (have.length >= 3 && have.every((r) => same3(r.values?.[sf.key], far(r)))) {
5762
5762
  say(`tables (${t.token}).fields (${sf.key})`, `"${sf.label}" repeats "${l.label}" on every row; drop it and use the lookup, which follows the linked record.`);
5763
5763
  }
5764
5764
  }
@@ -6654,6 +6654,138 @@ function mergeDescribeParts(plan, tables, doors, views, existingTables = [], not
6654
6654
  return spec;
6655
6655
  }
6656
6656
 
6657
+ // src/templates/describe/computed-shape.ts
6658
+ var VIA_KEYS = ["via", "relation", "relationField", "relation_field", "relationKey", "through", "rollupVia", "lookupVia", "rollupRelation", "lookupRelation", "rollupThrough", "lookupThrough", "from", "link", "linkField"];
6659
+ var PICK_KEYS = ["pick", "field", "targetField", "target_field", "fieldKey", "rollupField", "lookupField", "rollupTarget", "lookupTarget", "target", "of", "column", "sourceField", "source"];
6660
+ var AGG_KEYS = ["agg", "fn", "func", "function", "operation", "aggregation", "aggregate", "op", "aggregator", "calculation", "rollupFn", "rollupFunction", "rollupAgg", "rollupAggregate", "rollupAggregation", "rollupOperation", "rollupOp"];
6661
+ var AGG_WORDS = {
6662
+ sum: "sum",
6663
+ total: "sum",
6664
+ add: "sum",
6665
+ sumof: "sum",
6666
+ summation: "sum",
6667
+ count: "count",
6668
+ countall: "count",
6669
+ countrows: "count",
6670
+ number: "count",
6671
+ numberof: "count",
6672
+ n: "count",
6673
+ tally: "count",
6674
+ avg: "avg",
6675
+ average: "avg",
6676
+ mean: "avg",
6677
+ min: "min",
6678
+ minimum: "min",
6679
+ lowest: "min",
6680
+ smallest: "min",
6681
+ earliest: "min",
6682
+ max: "max",
6683
+ maximum: "max",
6684
+ highest: "max",
6685
+ largest: "max",
6686
+ latest: "max",
6687
+ median: "median",
6688
+ filled: "filled",
6689
+ countfilled: "filled",
6690
+ empty: "empty",
6691
+ countempty: "empty",
6692
+ unique: "unique",
6693
+ distinct: "unique",
6694
+ countunique: "unique",
6695
+ countdistinct: "unique"
6696
+ };
6697
+ var squash = (s) => String(s ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
6698
+ var singular = (s) => s.replace(/ies$/, "y").replace(/s$/, "");
6699
+ var same2 = (a, b) => squash(a) === squash(b) || singular(squash(a)) === singular(squash(b));
6700
+ function aggWord(v) {
6701
+ return typeof v === "string" ? AGG_WORDS[squash(v)] : void 0;
6702
+ }
6703
+ function take(f, nested, keys) {
6704
+ for (const k of keys) if (nested && nested[k] !== void 0 && nested[k] !== null && nested[k] !== "") return { value: nested[k], key: k, from: "nested" };
6705
+ for (const k of keys) if (f[k] !== void 0 && f[k] !== null && f[k] !== "") return { value: f[k], key: k, from: "field" };
6706
+ return void 0;
6707
+ }
6708
+ function normalizeComputedFields(spec, fixes) {
6709
+ const every = allTables(spec);
6710
+ const byToken = new Map(every.map((t) => [t.token, t]));
6711
+ for (const t of spec.tables) {
6712
+ for (const f of t.fields) {
6713
+ const raw = f;
6714
+ const nestedKind = ["rollup", "lookup"].find((k) => raw[k] && typeof raw[k] === "object");
6715
+ const kind = f.parityType === "rollup" || f.parityType === "lookup" ? f.parityType : !f.parityType && nestedKind ? nestedKind : void 0;
6716
+ if (!kind) continue;
6717
+ const nested = nestedKind ? raw[nestedKind] : void 0;
6718
+ const at = `tables.${t.token}.fields.${f.key}`;
6719
+ const consumed = [];
6720
+ let changed = false;
6721
+ const hasVia = typeof f.via === "string" && t.fields.some((x) => x.key === f.via);
6722
+ let via = hasVia ? t.fields.find((x) => x.key === f.via) : void 0;
6723
+ if (!via) {
6724
+ const got = take(raw, nested, VIA_KEYS);
6725
+ if (got && typeof got.value === "string") {
6726
+ const rels = t.fields.filter((x) => x.parityType === "relation" && x.relationTarget && x.key !== f.key);
6727
+ const hits = [
6728
+ rels.filter((x) => x.key === got.value),
6729
+ rels.filter((x) => same2(x.key, got.value)),
6730
+ rels.filter((x) => same2(x.label, got.value)),
6731
+ rels.filter((x) => same2(x.relationTarget, got.value))
6732
+ ].find((h) => h.length === 1);
6733
+ if (hits) {
6734
+ via = hits[0];
6735
+ f.via = via.key;
6736
+ consumed.push([got.from, got.key]);
6737
+ changed = true;
6738
+ }
6739
+ }
6740
+ }
6741
+ if (!via || !via.relationTarget) {
6742
+ if (changed) fixes.push(`${at}: ${kind} written as ${nestedKind ? `{${nestedKind}:\u2026}` : "loose keys"}, read into via/pick${kind === "rollup" ? "/agg" : ""} where unambiguous`);
6743
+ continue;
6744
+ }
6745
+ const far = byToken.get(via.relationTarget);
6746
+ if (kind === "rollup" && !f.agg) {
6747
+ const got = take(raw, nested, AGG_KEYS);
6748
+ const word = got ? aggWord(got.value) : void 0;
6749
+ if (got && word) {
6750
+ f.agg = word;
6751
+ consumed.push([got.from, got.key]);
6752
+ changed = true;
6753
+ }
6754
+ }
6755
+ if (!f.pick || !far?.fields.some((x) => x.key === f.pick)) {
6756
+ const got = take(raw, nested, PICK_KEYS);
6757
+ const named = got && typeof got.value === "string" ? got.value : void 0;
6758
+ const farField = named ? far?.fields.find((x) => x.key === named) ?? far?.fields.find((x) => same2(x.key, named)) ?? far?.fields.find((x) => same2(x.label, named)) : void 0;
6759
+ if (farField) {
6760
+ f.pick = farField.key;
6761
+ if (got) consumed.push([got.from, got.key]);
6762
+ changed = true;
6763
+ } else if (kind === "rollup" && (f.agg === "count" || !f.agg && !named) && far) {
6764
+ const title = far.titleField;
6765
+ const stand = far.fields.find((x) => x.key === title) ?? far.fields.find((x) => !["relation", "formula", "lookup", "rollup", "autonumber"].includes(x.parityType));
6766
+ if (stand) {
6767
+ f.pick = stand.key;
6768
+ if (got) consumed.push([got.from, got.key]);
6769
+ changed = true;
6770
+ }
6771
+ }
6772
+ }
6773
+ if (kind === "rollup" && changed && via && !via.multi && (via.relationMax ?? 1) <= 1) {
6774
+ via.multi = true;
6775
+ delete via.relationMax;
6776
+ for (const r of spec.relationships ?? []) if (r.fromTable === t.token && r.fromField === via.key) r.cardinality = "many";
6777
+ fixes.push(`${at}.via: "${via.key}" made a list (a rollup adds up many linked records)`);
6778
+ }
6779
+ if (!changed) continue;
6780
+ f.parityType = kind;
6781
+ if (kind === "lookup") delete raw["agg"];
6782
+ for (const [from, k] of consumed) if (from === "field") delete raw[k];
6783
+ if (nestedKind) delete raw[nestedKind];
6784
+ fixes.push(`${at}: ${kind} written in a shape the check does not read, rewritten as via "${f.via}", pick "${f.pick ?? "(none)"}"${kind === "rollup" ? `, agg "${f.agg ?? "(none)"}"` : ""}`);
6785
+ }
6786
+ }
6787
+ }
6788
+
6657
6789
  // src/templates/describe/autofix.ts
6658
6790
  var clone2 = (v) => JSON.parse(JSON.stringify(v));
6659
6791
  var SENSITIVITY_RANK2 = ["public", "internal", "confidential", "restricted"];
@@ -6975,6 +7107,7 @@ function switchJob(spec, fixes) {
6975
7107
  function autofixDescribeSpec(input, ctx = {}) {
6976
7108
  const spec = describeSpec(clone2(input));
6977
7109
  const autoFixes = [];
7110
+ normalizeComputedFields(spec, autoFixes);
6978
7111
  fixMissingQuestionFields(spec, autoFixes);
6979
7112
  raiseSensitivity(spec, autoFixes);
6980
7113
  flagAskedOfSubject(spec, ctx, autoFixes);
@@ -7198,6 +7331,14 @@ var VIEW_SHAPE_FACTS = {
7198
7331
  startField_endField: "timeline only, required: keys of two datetime fields",
7199
7332
  coverField: "gallery only, required: the key of a file field"
7200
7333
  };
7334
+ var ROLLUP_SHAPE_FACTS = {
7335
+ rule: 'a rollup is a field of the PARENT table that adds up the records linked to it: parityType "rollup", via = the key of a relation field of THIS table (that link must be a list: multi true), pick = the key of a field on the far table, agg = sum | count | avg | min | max; an optional filter picks which linked records. Write exactly these keys on the field itself \u2014 never a nested {rollup:{\u2026}} object, never relation/field/fn.',
7336
+ example: { key: "total_paid", label: "Total paid", parityType: "rollup", via: "payments", pick: "amount", agg: "sum" }
7337
+ };
7338
+ var LOOKUP_SHAPE_FACTS = {
7339
+ rule: 'a lookup is a field that shows one value of the record a relation field points at: parityType "lookup", via = the key of a relation field of THIS table, pick = the key of a field on the far table. Write exactly these keys on the field itself \u2014 never a nested {lookup:{\u2026}} object.',
7340
+ example: { key: "client_phone", label: "Client phone", parityType: "lookup", via: "client", pick: "phone" }
7341
+ };
7201
7342
  function describeVocabulary() {
7202
7343
  return {
7203
7344
  field_kinds: RESOLVABLE_FIELD_TYPE_WORDS,
@@ -7221,6 +7362,8 @@ function describeVocabulary() {
7221
7362
  booking_window: { weekday: "0 = Sunday \u2026 6 = Saturday", from: "HH:MM", to: "HH:MM later than from" },
7222
7363
  booking_availability: BOOKING_AVAILABILITY_FACTS,
7223
7364
  view_shape: VIEW_SHAPE_FACTS,
7365
+ rollup_shape: ROLLUP_SHAPE_FACTS,
7366
+ lookup_shape: LOOKUP_SHAPE_FACTS,
7224
7367
  title_field: "a table's titleField is the key of a plain text-like field of that same table (a name, a title) \u2014 never a link to another table, a formula or a timed date"
7225
7368
  };
7226
7369
  }