@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.
- package/CHANGELOG.md +30 -0
- package/dist/app-table/index.cjs +1 -1
- package/dist/app-table/index.cjs.map +1 -1
- package/dist/app-table/index.d.cts +3 -3
- package/dist/app-table/index.d.ts +3 -3
- package/dist/app-table/index.js +1 -1
- package/dist/app-table/index.js.map +1 -1
- package/dist/{client-DLIKeBWF.d.cts → client-CesQciPd.d.cts} +1 -1
- package/dist/{client-D1LTezMZ.d.ts → client-TU-nvgcF.d.ts} +1 -1
- package/dist/core/index.cjs +3 -3
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +4 -4
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/core/index.js.map +1 -1
- package/dist/data/index.cjs +1 -1
- package/dist/data/index.cjs.map +1 -1
- package/dist/data/index.d.cts +2 -2
- package/dist/data/index.d.ts +2 -2
- package/dist/data/index.js +1 -1
- package/dist/data/index.js.map +1 -1
- package/dist/{declareTable-02uao-5n.d.cts → declareTable-B9sr2E0h.d.cts} +2 -2
- package/dist/{declareTable-C5FlIS0z.d.ts → declareTable-COXYIOiT.d.ts} +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +3 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +3 -19
- package/dist/react/index.d.ts +3 -19
- package/dist/react/index.js +3 -3
- package/dist/react/index.js.map +1 -1
- package/dist/{table-kind-LodkBhCZ.d.cts → table-kind-B4vIKJVM.d.cts} +3 -19
- package/dist/{table-kind-COmkYYkc.d.ts → table-kind-C5T-DOdc.d.ts} +3 -19
- package/dist/templates/index.cjs +147 -4
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.js +147 -4
- package/dist/templates/index.js.map +1 -1
- 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
|
|
4037
|
-
*
|
|
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
|
|
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
|
|
4037
|
-
*
|
|
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
|
|
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
|
package/dist/templates/index.cjs
CHANGED
|
@@ -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
|
|
4570
|
-
if (!
|
|
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
|
|
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) =>
|
|
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
|
}
|