@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
@@ -4033,8 +4033,8 @@ interface DrillFinding {
4033
4033
  * The records behind a DEFINER definition's numbers: a registry relation carrying the fact's column
4034
4034
  * names (e.g. `bucket`), read by the door definer with the SAME filter compiler and the SAME lane
4035
4035
  * rule as the number, for a window (required), and never past the number's `as_of` (rows created
4036
- * after it are not listed). The number and its records are EQUAL AS OF THE COUNT; a value that
4037
- * changes on a counted row afterwards (a cost that lands late) is SAID by the page (`settling`).
4036
+ * after it are not listed). The number and its records are the SAME counted rows, equal as of the
4037
+ * count (`as_of`); a value that changes afterwards reaches both at the next count.
4038
4038
  *
4039
4039
  * An INVOKER definition may declare records too (lane DRILL-GAPS): they are its own rows (`fact` is
4040
4040
  * the definition's own fact), read as the person through the table's row security, among the columns
@@ -4131,25 +4131,9 @@ interface DrillRowsPage {
4131
4131
  as_of: string | null;
4132
4132
  /**
4133
4133
  * A definer definition's records, first page only: every Measure that adds up (sum, count, ratio)
4134
- * summed over the SAME filter — what these records add up to NOW (the ledger as it stands).
4134
+ * summed over the SAME filter — what these records add up to, which is the number they open.
4135
4135
  */
4136
4136
  measures?: Record<string, number | string | null>;
4137
- /**
4138
- * First page only: the NUMBER's own sums for the same filter, as counted at `as_of` (the summary).
4139
- * The number and its records are equal as of the count; a cost that lands on a counted row after
4140
- * it makes `measures` differ from `counted`, and the page then carries `settling` and says so.
4141
- */
4142
- counted?: Record<string, number | string | null>;
4143
- /**
4144
- * Present only when the records and the number differ: per Measure, what was counted, what the
4145
- * ledger holds now, and the difference — with `says` in words ("$1.23 more has landed since the
4146
- * count at 06:40 UTC. …"). The next recount (every 10 minutes) brings them back equal.
4147
- */
4148
- settling?: Record<string, {
4149
- counted: number | string | null;
4150
- now: number | string | null;
4151
- difference: number;
4152
- }>;
4153
4137
  }
4154
4138
  /**
4155
4139
  * A DECLARED drill definition (a `*.drill.ts` file): an override of one standard table (its key
@@ -4033,8 +4033,8 @@ interface DrillFinding {
4033
4033
  * The records behind a DEFINER definition's numbers: a registry relation carrying the fact's column
4034
4034
  * names (e.g. `bucket`), read by the door definer with the SAME filter compiler and the SAME lane
4035
4035
  * rule as the number, for a window (required), and never past the number's `as_of` (rows created
4036
- * after it are not listed). The number and its records are EQUAL AS OF THE COUNT; a value that
4037
- * changes on a counted row afterwards (a cost that lands late) is SAID by the page (`settling`).
4036
+ * after it are not listed). The number and its records are the SAME counted rows, equal as of the
4037
+ * count (`as_of`); a value that changes afterwards reaches both at the next count.
4038
4038
  *
4039
4039
  * An INVOKER definition may declare records too (lane DRILL-GAPS): they are its own rows (`fact` is
4040
4040
  * the definition's own fact), read as the person through the table's row security, among the columns
@@ -4131,25 +4131,9 @@ interface DrillRowsPage {
4131
4131
  as_of: string | null;
4132
4132
  /**
4133
4133
  * A definer definition's records, first page only: every Measure that adds up (sum, count, ratio)
4134
- * summed over the SAME filter — what these records add up to NOW (the ledger as it stands).
4134
+ * summed over the SAME filter — what these records add up to, which is the number they open.
4135
4135
  */
4136
4136
  measures?: Record<string, number | string | null>;
4137
- /**
4138
- * First page only: the NUMBER's own sums for the same filter, as counted at `as_of` (the summary).
4139
- * The number and its records are equal as of the count; a cost that lands on a counted row after
4140
- * it makes `measures` differ from `counted`, and the page then carries `settling` and says so.
4141
- */
4142
- counted?: Record<string, number | string | null>;
4143
- /**
4144
- * Present only when the records and the number differ: per Measure, what was counted, what the
4145
- * ledger holds now, and the difference — with `says` in words ("$1.23 more has landed since the
4146
- * count at 06:40 UTC. …"). The next recount (every 10 minutes) brings them back equal.
4147
- */
4148
- settling?: Record<string, {
4149
- counted: number | string | null;
4150
- now: number | string | null;
4151
- difference: number;
4152
- }>;
4153
4137
  }
4154
4138
  /**
4155
4139
  * A DECLARED drill definition (a `*.drill.ts` file): an override of one standard table (its key
@@ -4566,8 +4566,8 @@ function validateTemplate(raw, options = {}) {
4566
4566
  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.`);
4567
4567
  for (const [k, v] of Object.entries(rule.equals ?? {})) {
4568
4568
  const got = val(k) ?? null;
4569
- const same2 = sameJson(got, v) || typeof v === "number" && parseNumeric(storeText(got) ?? "") !== null && qCmp(parseNumeric(storeText(got) ?? ""), parseNumeric(String(v))) === 0;
4570
- if (!same2) say(at, `"${row.key}" is ${st}, so "${fieldOf(table, k)?.label ?? k}" is ${JSON.stringify(v)}; it is ${JSON.stringify(got)}.`);
4569
+ const same3 = sameJson(got, v) || typeof v === "number" && parseNumeric(storeText(got) ?? "") !== null && qCmp(parseNumeric(storeText(got) ?? ""), parseNumeric(String(v))) === 0;
4570
+ if (!same3) say(at, `"${row.key}" is ${st}, so "${fieldOf(table, k)?.label ?? k}" is ${JSON.stringify(v)}; it is ${JSON.stringify(got)}.`);
4571
4571
  }
4572
4572
  }
4573
4573
  };
@@ -5905,7 +5905,7 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
5905
5905
  }
5906
5906
  }
5907
5907
  const rows = t.rows;
5908
- const same2 = (x, y) => {
5908
+ const same3 = (x, y) => {
5909
5909
  const a = storeText(x);
5910
5910
  const b = storeText(y);
5911
5911
  if (a === null || b === null) return false;
@@ -5923,7 +5923,7 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
5923
5923
  };
5924
5924
  for (const sf of t.fields.filter((ff) => !isComputed(ff) && !isRelation(ff) && ff.key !== l.key)) {
5925
5925
  const have = rows.filter((r) => filled(r.values?.[sf.key]));
5926
- if (have.length >= 3 && have.every((r) => same2(r.values?.[sf.key], far(r)))) {
5926
+ if (have.length >= 3 && have.every((r) => same3(r.values?.[sf.key], far(r)))) {
5927
5927
  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.`);
5928
5928
  }
5929
5929
  }
@@ -6819,6 +6819,138 @@ function mergeDescribeParts(plan, tables, doors, views, existingTables = [], not
6819
6819
  return spec;
6820
6820
  }
6821
6821
 
6822
+ // src/templates/describe/computed-shape.ts
6823
+ var VIA_KEYS = ["via", "relation", "relationField", "relation_field", "relationKey", "through", "rollupVia", "lookupVia", "rollupRelation", "lookupRelation", "rollupThrough", "lookupThrough", "from", "link", "linkField"];
6824
+ var PICK_KEYS = ["pick", "field", "targetField", "target_field", "fieldKey", "rollupField", "lookupField", "rollupTarget", "lookupTarget", "target", "of", "column", "sourceField", "source"];
6825
+ var AGG_KEYS = ["agg", "fn", "func", "function", "operation", "aggregation", "aggregate", "op", "aggregator", "calculation", "rollupFn", "rollupFunction", "rollupAgg", "rollupAggregate", "rollupAggregation", "rollupOperation", "rollupOp"];
6826
+ var AGG_WORDS = {
6827
+ sum: "sum",
6828
+ total: "sum",
6829
+ add: "sum",
6830
+ sumof: "sum",
6831
+ summation: "sum",
6832
+ count: "count",
6833
+ countall: "count",
6834
+ countrows: "count",
6835
+ number: "count",
6836
+ numberof: "count",
6837
+ n: "count",
6838
+ tally: "count",
6839
+ avg: "avg",
6840
+ average: "avg",
6841
+ mean: "avg",
6842
+ min: "min",
6843
+ minimum: "min",
6844
+ lowest: "min",
6845
+ smallest: "min",
6846
+ earliest: "min",
6847
+ max: "max",
6848
+ maximum: "max",
6849
+ highest: "max",
6850
+ largest: "max",
6851
+ latest: "max",
6852
+ median: "median",
6853
+ filled: "filled",
6854
+ countfilled: "filled",
6855
+ empty: "empty",
6856
+ countempty: "empty",
6857
+ unique: "unique",
6858
+ distinct: "unique",
6859
+ countunique: "unique",
6860
+ countdistinct: "unique"
6861
+ };
6862
+ var squash = (s) => String(s ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
6863
+ var singular = (s) => s.replace(/ies$/, "y").replace(/s$/, "");
6864
+ var same2 = (a, b) => squash(a) === squash(b) || singular(squash(a)) === singular(squash(b));
6865
+ function aggWord(v) {
6866
+ return typeof v === "string" ? AGG_WORDS[squash(v)] : void 0;
6867
+ }
6868
+ function take(f, nested, keys) {
6869
+ for (const k of keys) if (nested && nested[k] !== void 0 && nested[k] !== null && nested[k] !== "") return { value: nested[k], key: k, from: "nested" };
6870
+ for (const k of keys) if (f[k] !== void 0 && f[k] !== null && f[k] !== "") return { value: f[k], key: k, from: "field" };
6871
+ return void 0;
6872
+ }
6873
+ function normalizeComputedFields(spec, fixes) {
6874
+ const every = allTables(spec);
6875
+ const byToken = new Map(every.map((t) => [t.token, t]));
6876
+ for (const t of spec.tables) {
6877
+ for (const f of t.fields) {
6878
+ const raw = f;
6879
+ const nestedKind = ["rollup", "lookup"].find((k) => raw[k] && typeof raw[k] === "object");
6880
+ const kind = f.parityType === "rollup" || f.parityType === "lookup" ? f.parityType : !f.parityType && nestedKind ? nestedKind : void 0;
6881
+ if (!kind) continue;
6882
+ const nested = nestedKind ? raw[nestedKind] : void 0;
6883
+ const at = `tables.${t.token}.fields.${f.key}`;
6884
+ const consumed = [];
6885
+ let changed = false;
6886
+ const hasVia = typeof f.via === "string" && t.fields.some((x) => x.key === f.via);
6887
+ let via = hasVia ? t.fields.find((x) => x.key === f.via) : void 0;
6888
+ if (!via) {
6889
+ const got = take(raw, nested, VIA_KEYS);
6890
+ if (got && typeof got.value === "string") {
6891
+ const rels = t.fields.filter((x) => x.parityType === "relation" && x.relationTarget && x.key !== f.key);
6892
+ const hits = [
6893
+ rels.filter((x) => x.key === got.value),
6894
+ rels.filter((x) => same2(x.key, got.value)),
6895
+ rels.filter((x) => same2(x.label, got.value)),
6896
+ rels.filter((x) => same2(x.relationTarget, got.value))
6897
+ ].find((h) => h.length === 1);
6898
+ if (hits) {
6899
+ via = hits[0];
6900
+ f.via = via.key;
6901
+ consumed.push([got.from, got.key]);
6902
+ changed = true;
6903
+ }
6904
+ }
6905
+ }
6906
+ if (!via || !via.relationTarget) {
6907
+ if (changed) fixes.push(`${at}: ${kind} written as ${nestedKind ? `{${nestedKind}:\u2026}` : "loose keys"}, read into via/pick${kind === "rollup" ? "/agg" : ""} where unambiguous`);
6908
+ continue;
6909
+ }
6910
+ const far = byToken.get(via.relationTarget);
6911
+ if (kind === "rollup" && !f.agg) {
6912
+ const got = take(raw, nested, AGG_KEYS);
6913
+ const word = got ? aggWord(got.value) : void 0;
6914
+ if (got && word) {
6915
+ f.agg = word;
6916
+ consumed.push([got.from, got.key]);
6917
+ changed = true;
6918
+ }
6919
+ }
6920
+ if (!f.pick || !far?.fields.some((x) => x.key === f.pick)) {
6921
+ const got = take(raw, nested, PICK_KEYS);
6922
+ const named = got && typeof got.value === "string" ? got.value : void 0;
6923
+ 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;
6924
+ if (farField) {
6925
+ f.pick = farField.key;
6926
+ if (got) consumed.push([got.from, got.key]);
6927
+ changed = true;
6928
+ } else if (kind === "rollup" && (f.agg === "count" || !f.agg && !named) && far) {
6929
+ const title = far.titleField;
6930
+ const stand = far.fields.find((x) => x.key === title) ?? far.fields.find((x) => !["relation", "formula", "lookup", "rollup", "autonumber"].includes(x.parityType));
6931
+ if (stand) {
6932
+ f.pick = stand.key;
6933
+ if (got) consumed.push([got.from, got.key]);
6934
+ changed = true;
6935
+ }
6936
+ }
6937
+ }
6938
+ if (kind === "rollup" && changed && via && !via.multi && (via.relationMax ?? 1) <= 1) {
6939
+ via.multi = true;
6940
+ delete via.relationMax;
6941
+ for (const r of spec.relationships ?? []) if (r.fromTable === t.token && r.fromField === via.key) r.cardinality = "many";
6942
+ fixes.push(`${at}.via: "${via.key}" made a list (a rollup adds up many linked records)`);
6943
+ }
6944
+ if (!changed) continue;
6945
+ f.parityType = kind;
6946
+ if (kind === "lookup") delete raw["agg"];
6947
+ for (const [from, k] of consumed) if (from === "field") delete raw[k];
6948
+ if (nestedKind) delete raw[nestedKind];
6949
+ 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)"}"` : ""}`);
6950
+ }
6951
+ }
6952
+ }
6953
+
6822
6954
  // src/templates/describe/autofix.ts
6823
6955
  var clone2 = (v) => JSON.parse(JSON.stringify(v));
6824
6956
  var SENSITIVITY_RANK2 = ["public", "internal", "confidential", "restricted"];
@@ -7140,6 +7272,7 @@ function switchJob(spec, fixes) {
7140
7272
  function autofixDescribeSpec(input, ctx = {}) {
7141
7273
  const spec = describeSpec(clone2(input));
7142
7274
  const autoFixes = [];
7275
+ normalizeComputedFields(spec, autoFixes);
7143
7276
  fixMissingQuestionFields(spec, autoFixes);
7144
7277
  raiseSensitivity(spec, autoFixes);
7145
7278
  flagAskedOfSubject(spec, ctx, autoFixes);
@@ -7363,6 +7496,14 @@ var VIEW_SHAPE_FACTS = {
7363
7496
  startField_endField: "timeline only, required: keys of two datetime fields",
7364
7497
  coverField: "gallery only, required: the key of a file field"
7365
7498
  };
7499
+ var ROLLUP_SHAPE_FACTS = {
7500
+ 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.',
7501
+ example: { key: "total_paid", label: "Total paid", parityType: "rollup", via: "payments", pick: "amount", agg: "sum" }
7502
+ };
7503
+ var LOOKUP_SHAPE_FACTS = {
7504
+ 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.',
7505
+ example: { key: "client_phone", label: "Client phone", parityType: "lookup", via: "client", pick: "phone" }
7506
+ };
7366
7507
  function describeVocabulary() {
7367
7508
  return {
7368
7509
  field_kinds: RESOLVABLE_FIELD_TYPE_WORDS,
@@ -7386,6 +7527,8 @@ function describeVocabulary() {
7386
7527
  booking_window: { weekday: "0 = Sunday \u2026 6 = Saturday", from: "HH:MM", to: "HH:MM later than from" },
7387
7528
  booking_availability: BOOKING_AVAILABILITY_FACTS,
7388
7529
  view_shape: VIEW_SHAPE_FACTS,
7530
+ rollup_shape: ROLLUP_SHAPE_FACTS,
7531
+ lookup_shape: LOOKUP_SHAPE_FACTS,
7389
7532
  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"
7390
7533
  };
7391
7534
  }