@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
package/dist/templates/index.js
CHANGED
|
@@ -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
|
|
4405
|
-
if (!
|
|
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
|
|
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) =>
|
|
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
|
}
|