@ai-matrx/records 0.96.0 → 0.96.2
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 +6 -0
- package/dist/memory/index.cjs +23 -2
- package/dist/memory/index.cjs.map +1 -1
- package/dist/memory/index.d.cts +1 -1
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.js +23 -2
- package/dist/memory/index.js.map +1 -1
- package/dist/templates/index.cjs +123 -15
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.d.cts +15 -5
- package/dist/templates/index.d.ts +15 -5
- package/dist/templates/index.js +123 -15
- package/dist/templates/index.js.map +1 -1
- package/dist/{types-SezYoQ5_.d.cts → types-BhGs3f8s.d.cts} +9 -0
- package/dist/{types-Cl0b_Hy8.d.ts → types-DAruHh1G.d.ts} +9 -0
- package/package.json +4 -4
package/dist/templates/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/templates/index.ts
|
|
21
21
|
var templates_exports = {};
|
|
22
22
|
__export(templates_exports, {
|
|
23
|
+
AUTOMATION_EVENTS: () => AUTOMATION_EVENTS,
|
|
23
24
|
BAD_OUTCOME: () => BAD_OUTCOME,
|
|
24
25
|
BOOKING_AVAILABILITY_FACTS: () => BOOKING_AVAILABILITY_FACTS,
|
|
25
26
|
CAPABILITY_WORDS: () => CAPABILITY_WORDS,
|
|
@@ -70,6 +71,7 @@ __export(templates_exports, {
|
|
|
70
71
|
attachmentProblems: () => attachmentProblems,
|
|
71
72
|
autofixDescribeSpec: () => autofixDescribeSpec,
|
|
72
73
|
automationHeld: () => automationHeld,
|
|
74
|
+
automationSpec: () => automationSpec,
|
|
73
75
|
blockTable: () => blockTable,
|
|
74
76
|
bookingArgs: () => bookingArgs,
|
|
75
77
|
bookingColumnsProblem: () => bookingColumnsProblem,
|
|
@@ -2714,7 +2716,21 @@ function checklistSpec(checklist, tables, forms, documents) {
|
|
|
2714
2716
|
};
|
|
2715
2717
|
}
|
|
2716
2718
|
function automationHeld(a) {
|
|
2717
|
-
return `The automation "${a.name}"
|
|
2719
|
+
return `The automation "${a.name}" has a trigger the install cannot set up (${a.when.event}); a template automation runs on created, due or overdue.`;
|
|
2720
|
+
}
|
|
2721
|
+
var AUTOMATION_EVENTS = ["created", "due", "overdue"];
|
|
2722
|
+
function automationSpec(a, fieldIds) {
|
|
2723
|
+
const text2 = (a.message?.trim() || a.then.trim()).slice(0, 600);
|
|
2724
|
+
const actions = [{ do: "notify", to: { author: true }, text: text2 }];
|
|
2725
|
+
const at = a.at && /^([01][0-9]|2[0-3]):[0-5][0-9]$/.test(a.at) ? a.at : "09:00";
|
|
2726
|
+
if (a.when.event === "created") return { name: a.name, trigger: { on: "row_added" }, actions };
|
|
2727
|
+
if (a.when.event === "due" || a.when.event === "overdue") {
|
|
2728
|
+
const field2 = a.when.field ? fieldIds[a.when.field] : void 0;
|
|
2729
|
+
if (!field2) throw new Error(`The automation "${a.name}" waits for "${a.when.field ?? "?"}", which "${a.when.table}" does not declare.`);
|
|
2730
|
+
const offset = a.when.event === "due" ? -Math.max(0, Math.round(a.when.daysBefore ?? 0)) : 1;
|
|
2731
|
+
return { name: a.name, trigger: { on: "date_arrives", field: field2, offset_days: offset, at }, actions };
|
|
2732
|
+
}
|
|
2733
|
+
throw new Error(automationHeld(a));
|
|
2718
2734
|
}
|
|
2719
2735
|
function bookingColumnsProblem(tables, booking) {
|
|
2720
2736
|
const t = tables.find((x) => x.token === booking.table);
|
|
@@ -3216,6 +3232,7 @@ var DOOR_FOR = {
|
|
|
3216
3232
|
document: "doc_template_save",
|
|
3217
3233
|
"document.esign": "doc_sign",
|
|
3218
3234
|
notification: "rule_declare",
|
|
3235
|
+
automation: "automation_declare",
|
|
3219
3236
|
stage_rules: "pipeline_declare",
|
|
3220
3237
|
row_action: "action_declare",
|
|
3221
3238
|
"import.upsert": "io_import_begin",
|
|
@@ -3223,9 +3240,7 @@ var DOOR_FOR = {
|
|
|
3223
3240
|
agent: "agent_context",
|
|
3224
3241
|
"view.kanban.swimlanes": "view_declare"
|
|
3225
3242
|
};
|
|
3226
|
-
var OWED_BY = {
|
|
3227
|
-
automation: "lane 11"
|
|
3228
|
-
};
|
|
3243
|
+
var OWED_BY = {};
|
|
3229
3244
|
var STORE_DOOR_NAMES = new Set(STORE_DOORS.map((d) => d.name));
|
|
3230
3245
|
var STORE_FIELD_KINDS = new Set(BEHAVIOR_OF.keys());
|
|
3231
3246
|
function capabilityStatus(word) {
|
|
@@ -3813,7 +3828,8 @@ function validateTemplate(raw, options = {}) {
|
|
|
3813
3828
|
const e = effective(table, f);
|
|
3814
3829
|
return e.contextPolicy !== "exclude" && e.sensitivity !== "restricted";
|
|
3815
3830
|
};
|
|
3816
|
-
if (own.length
|
|
3831
|
+
if (own.length === 0 || own.length > TABLES_MAX) say("tables", `A template installs ${TABLES_MIN}\u2013${TABLES_MAX} related tables; this one has ${own.length}.`);
|
|
3832
|
+
else if (own.length < TABLES_MIN) sayGallery("tables", `A template installs ${TABLES_MIN}\u2013${TABLES_MAX} related tables; this one has ${own.length}.`);
|
|
3817
3833
|
const main = own[0];
|
|
3818
3834
|
if (main && (main.rows.length < MAIN_TABLE_ROWS_MIN || main.rows.length > MAIN_TABLE_ROWS_MAX)) {
|
|
3819
3835
|
say(`tables[0] (${main.token}).rows`, `The main table carries ${MAIN_TABLE_ROWS_MIN}\u2013${MAIN_TABLE_ROWS_MAX} realistic rows; it has ${main.rows.length}.`);
|
|
@@ -3851,12 +3867,12 @@ function validateTemplate(raw, options = {}) {
|
|
|
3851
3867
|
if (seeds.length) {
|
|
3852
3868
|
const hi = Math.max(...seeds);
|
|
3853
3869
|
const lo = Math.min(...seeds);
|
|
3854
|
-
if (new Set(seeds).size < 2)
|
|
3870
|
+
if (new Set(seeds).size < 2) saySeed(`${at}`, `Every seed row holds the same ${field2.label}; real records vary.`);
|
|
3855
3871
|
for (const rule of field2.rules ?? []) {
|
|
3856
3872
|
const bound = Number(rule.value);
|
|
3857
|
-
if (rule.kind === "max" && hi > 0 && bound > 100 * hi)
|
|
3858
|
-
if (rule.kind === "min" && bound < -1e3 * Math.abs(lo))
|
|
3859
|
-
if (rule.kind === "min" && bound < 0 && lo >= 0)
|
|
3873
|
+
if (rule.kind === "max" && hi > 0 && bound > 100 * hi) saySeed(`${at}.rules`, `The most "${field2.label}" allows (${rule.value}) is over 100\xD7 the largest real value (${hi}); write the real ceiling.`);
|
|
3874
|
+
if (rule.kind === "min" && bound < -1e3 * Math.abs(lo)) saySeed(`${at}.rules`, `The least "${field2.label}" allows (${rule.value}) is far below any real value (${lo}); write the real floor.`);
|
|
3875
|
+
if (rule.kind === "min" && bound < 0 && lo >= 0) saySeed(`${at}.rules`, `"${field2.label}" allows negative values, and no real one is negative; the floor is 0 or above.`);
|
|
3860
3876
|
}
|
|
3861
3877
|
}
|
|
3862
3878
|
}
|
|
@@ -4360,10 +4376,21 @@ function validateTemplate(raw, options = {}) {
|
|
|
4360
4376
|
}
|
|
4361
4377
|
} else if (!extra.prompt?.trim()) say(`${at}.prompt`, "An agent action says what the record chat opens with.");
|
|
4362
4378
|
break;
|
|
4363
|
-
case "automation":
|
|
4364
|
-
|
|
4379
|
+
case "automation": {
|
|
4380
|
+
const ev = extra.when?.event;
|
|
4381
|
+
if (needTable(extra.when?.table, "when.table") && extra.when.field && !fieldOf(extra.when.table, extra.when.field)) say(`${at}.when.field`, `"${extra.when.table}" does not declare a field "${extra.when.field}".`);
|
|
4382
|
+
if (!AUTOMATION_EVENTS.includes(ev)) say(`${at}.when.event`, `An automation runs on ${AUTOMATION_EVENTS.join(", ")}; for "${ev}" use a notification.`);
|
|
4383
|
+
if (ev === "due" || ev === "overdue") {
|
|
4384
|
+
const f = extra.when.field ? fieldOf(extra.when.table, extra.when.field) : void 0;
|
|
4385
|
+
if (!extra.when.field) say(`${at}.when.field`, "A date reminder names the date field it waits for.");
|
|
4386
|
+
else if (f && f.parityType !== "datetime") say(`${at}.when.field`, `"${extra.when.field}" is not a date or datetime field, so nothing arrives to remind about.`);
|
|
4387
|
+
const d = extra.when.daysBefore;
|
|
4388
|
+
if (d != null && (!Number.isInteger(d) || d < 0 || d > 365)) say(`${at}.when.daysBefore`, "Say how many whole days before the date, 0 to 365.");
|
|
4389
|
+
}
|
|
4390
|
+
if (extra.at != null && !/^([01][0-9]|2[0-3]):[0-5][0-9]$/.test(extra.at)) say(`${at}.at`, "Say the time of day as HH:MM on a 24-hour clock, like 09:00.");
|
|
4365
4391
|
if (!extra.then?.trim()) say(`${at}.then`, "Say what happens, in the business's words.");
|
|
4366
4392
|
break;
|
|
4393
|
+
}
|
|
4367
4394
|
default:
|
|
4368
4395
|
say(at, `"${extra.kind}" is not an extra a template can carry.`);
|
|
4369
4396
|
}
|
|
@@ -6044,7 +6071,8 @@ var TEMPLATE_PLAN_DOORS = [
|
|
|
6044
6071
|
"rule_declare",
|
|
6045
6072
|
"checklist_declare",
|
|
6046
6073
|
"portal_declare",
|
|
6047
|
-
"query_table_homes"
|
|
6074
|
+
"query_table_homes",
|
|
6075
|
+
"automation_declare"
|
|
6048
6076
|
];
|
|
6049
6077
|
var ROW_BATCH = 10;
|
|
6050
6078
|
var DERIVED = /* @__PURE__ */ new Set(["lookup", "rollup"]);
|
|
@@ -6224,6 +6252,8 @@ function compileInstallPlan(written) {
|
|
|
6224
6252
|
}
|
|
6225
6253
|
const needIds = /* @__PURE__ */ new Set([
|
|
6226
6254
|
...spec.extras.filter((e) => e.kind === "document" || e.kind === "row_action").map((e) => e.table),
|
|
6255
|
+
// A date reminder names its date Field by id.
|
|
6256
|
+
...spec.extras.filter((e) => e.kind === "automation" && e.when.field).map((e) => e.when.table),
|
|
6227
6257
|
...spec.views.filter((v) => v.anyOf && Object.keys(v.anyOf).length).map((v) => v.table)
|
|
6228
6258
|
]);
|
|
6229
6259
|
const fieldRefs = (token) => {
|
|
@@ -6389,7 +6419,13 @@ function compileInstallPlan(written) {
|
|
|
6389
6419
|
});
|
|
6390
6420
|
break;
|
|
6391
6421
|
case "automation":
|
|
6392
|
-
|
|
6422
|
+
steps.push({
|
|
6423
|
+
label: `automations.${e.token}`,
|
|
6424
|
+
door: "automation_declare",
|
|
6425
|
+
args: { p_organization_id: "${org}", p_table_id: tableRef(e.when.table), p_spec: automationSpec(e, fieldRefs(e.when.table)), p_automation_id: null },
|
|
6426
|
+
save: { [`automations.${e.token}`]: ["automation_id"] }
|
|
6427
|
+
});
|
|
6428
|
+
break;
|
|
6393
6429
|
default:
|
|
6394
6430
|
throw new Error(`The install plan has no door for a ${e.kind} yet ("${e.token ?? "?"}"), so this template cannot be declared until it does.`);
|
|
6395
6431
|
}
|
|
@@ -7550,6 +7586,36 @@ function switchJob(spec, fixes) {
|
|
|
7550
7586
|
fixes.push(`job: ${spec.job} switched to ${to} (linked tables, no realism declarations)`);
|
|
7551
7587
|
spec.job = to;
|
|
7552
7588
|
}
|
|
7589
|
+
var ABSOLUTE_SEED = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
7590
|
+
function fixSeedDates(spec, ctx, fixes) {
|
|
7591
|
+
const today = ctx.today && /^\d{4}-\d{2}-\d{2}$/.test(ctx.today) ? ctx.today : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7592
|
+
const t0 = Date.UTC(+today.slice(0, 4), +today.slice(5, 7) - 1, +today.slice(8, 10));
|
|
7593
|
+
for (const t of spec.tables) {
|
|
7594
|
+
const dates = t.fields.filter((f) => f.parityType === "datetime" && !f.absoluteDates);
|
|
7595
|
+
if (!dates.length || !t.rows?.length) continue;
|
|
7596
|
+
let rewritten = 0;
|
|
7597
|
+
for (const f of dates) {
|
|
7598
|
+
let timed = false;
|
|
7599
|
+
for (const r of t.rows) {
|
|
7600
|
+
const v = r.values?.[f.key];
|
|
7601
|
+
if (typeof v !== "string") continue;
|
|
7602
|
+
const m = ABSOLUTE_SEED.exec(v.trim());
|
|
7603
|
+
if (m) {
|
|
7604
|
+
const days = Math.round((Date.UTC(+m[1], +m[2] - 1, +m[3]) - t0) / 864e5);
|
|
7605
|
+
if (Math.abs(days) > 9999) continue;
|
|
7606
|
+
r.values[f.key] = `@today${days === 0 ? "" : days > 0 ? `+${days}d` : `${days}d`}${m[4] ? ` ${m[4]}:${m[5]}` : ""}`;
|
|
7607
|
+
rewritten++;
|
|
7608
|
+
}
|
|
7609
|
+
if (/ \d{2}:\d{2}$|T\d{2}:\d{2}/.test(String(r.values[f.key]))) timed = true;
|
|
7610
|
+
}
|
|
7611
|
+
if (timed && f.format !== "datetime") {
|
|
7612
|
+
f.format = "datetime";
|
|
7613
|
+
fixes.push(`tables.${t.token}.${f.key}: holds a time of day, so it is a date-and-time column`);
|
|
7614
|
+
}
|
|
7615
|
+
}
|
|
7616
|
+
if (rewritten) fixes.push(`tables.${t.token}.rows: ${rewritten} example date(s) written relative to today`);
|
|
7617
|
+
}
|
|
7618
|
+
}
|
|
7553
7619
|
function autofixDescribeSpec(input, ctx = {}) {
|
|
7554
7620
|
const spec = describeSpec(clone2(input));
|
|
7555
7621
|
const autoFixes = [];
|
|
@@ -7572,14 +7638,51 @@ function autofixDescribeSpec(input, ctx = {}) {
|
|
|
7572
7638
|
fixRelationships(spec, ctx, autoFixes);
|
|
7573
7639
|
askRequired(spec, autoFixes);
|
|
7574
7640
|
switchJob(spec, autoFixes);
|
|
7641
|
+
fixSeedDates(spec, ctx, autoFixes);
|
|
7575
7642
|
return { spec, autoFixes };
|
|
7576
7643
|
}
|
|
7644
|
+
var ROW_AT = /^tables(?:\[\d+\])? \((\w+)\)\.rows/;
|
|
7645
|
+
function rowProblemTables(spec, problems) {
|
|
7646
|
+
const out = /* @__PURE__ */ new Set();
|
|
7647
|
+
for (const p of problems) {
|
|
7648
|
+
const m = ROW_AT.exec(p.at);
|
|
7649
|
+
if (m) out.add(m[1]);
|
|
7650
|
+
else if (/^placeholder data "([^"]+)"/.test(p.says)) {
|
|
7651
|
+
const value = /^placeholder data "([^"]+)"/.exec(p.says)[1];
|
|
7652
|
+
for (const t of spec.tables) if (t.rows?.some((r) => JSON.stringify(r.values ?? {}).includes(value))) out.add(t.token);
|
|
7653
|
+
}
|
|
7654
|
+
}
|
|
7655
|
+
return out;
|
|
7656
|
+
}
|
|
7577
7657
|
function describeCheck(spec, ctx = {}) {
|
|
7578
7658
|
const fixed = autofixDescribeSpec(spec, ctx);
|
|
7579
7659
|
const v = validateTemplate(fixed.spec, { profile: "describe" });
|
|
7580
7660
|
const sameFields = (t) => (ctx.existingTables ?? []).some((e) => e.fields.map((f) => f.key).sort().join("|") === t.fields.map((f) => f.key).sort().join("|"));
|
|
7581
7661
|
const reused = new Set(fixed.spec.tables.filter(sameFields).map((t) => t.token));
|
|
7582
|
-
const
|
|
7662
|
+
const notReused = (ps) => ps.filter((p) => !(/^This table copies the shared /.test(p.says) && reused.has(/\((\w+)\)/.exec(p.at)?.[1] ?? "")));
|
|
7663
|
+
let problems = notReused(v.problems);
|
|
7664
|
+
for (let round = 0; round < 3; round++) {
|
|
7665
|
+
const bad = rowProblemTables(fixed.spec, problems);
|
|
7666
|
+
if (!bad.size) break;
|
|
7667
|
+
let grew = true;
|
|
7668
|
+
while (grew) {
|
|
7669
|
+
grew = false;
|
|
7670
|
+
for (const t of fixed.spec.tables) {
|
|
7671
|
+
if (bad.has(t.token) || !t.rows?.length) continue;
|
|
7672
|
+
if (t.fields.some((f) => f.relationTarget && bad.has(f.relationTarget) && t.rows.some((r) => r.values?.[f.key] != null))) {
|
|
7673
|
+
bad.add(t.token);
|
|
7674
|
+
grew = true;
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
}
|
|
7678
|
+
for (const t of fixed.spec.tables) {
|
|
7679
|
+
if (!bad.has(t.token) || !t.rows?.length) continue;
|
|
7680
|
+
const why = problems.find((p) => ROW_AT.exec(p.at)?.[1] === t.token)?.says ?? "they did not pass the check";
|
|
7681
|
+
fixed.autoFixes.push(`tables.${t.token}.rows: example rows left out (${why.slice(0, 120)})`);
|
|
7682
|
+
t.rows = [];
|
|
7683
|
+
}
|
|
7684
|
+
problems = notReused(validateTemplate(fixed.spec, { profile: "describe" }).problems);
|
|
7685
|
+
}
|
|
7583
7686
|
return { ...fixed, problems: problems.map((p) => ({ at: p.at, says: p.says })) };
|
|
7584
7687
|
}
|
|
7585
7688
|
|
|
@@ -7808,6 +7911,10 @@ var FORMULA_SHAPE_FACTS = {
|
|
|
7808
7911
|
rule: "a formula names every field by its LABEL in braces ({Quantity}), never by key; operators are + - * / % only (no >, <, = or comparisons), functions are IF, ROUND, SUM, DATEDIFF and the other store functions; it never reads a lookup or rollup, nor itself.",
|
|
7809
7912
|
example: { key: "line_total", label: "Line total", parityType: "formula", formula: "{Quantity} * {Unit price}" }
|
|
7810
7913
|
};
|
|
7914
|
+
var AUTOMATION_SHAPE_FACTS = {
|
|
7915
|
+
rule: `an automation extra is {kind:"automation", token, name, when:{table, event, field?, daysBefore?}, then, message?, at?}. event is one of: due (a date field is coming up \u2014 field = the key of a datetime field of that table, daysBefore = whole days ahead 0 to 365), overdue (the day after that date field), created (every new row). then says what happens in the business's words; message is the reminder text the person reads (no merge fields); at is the time of day HH:MM (default 09:00). It always tells the person who set it up. Add one only when the sentence asks for a reminder, a heads-up or a follow-up nudge.`,
|
|
7916
|
+
example: { kind: "automation", token: "remind_before_post", name: "Reminder 2 days before a post", when: { table: "post", event: "due", field: "publish_at", daysBefore: 2 }, then: "remind me two days before a post goes out", message: "A post goes out in 2 days \u2014 check the copy and image.", at: "09:00" }
|
|
7917
|
+
};
|
|
7811
7918
|
var RELATIONSHIP_SHAPE_FACTS = {
|
|
7812
7919
|
rule: "a relationship's inverseKey is the name the OTHER table sees this link under; it must not be a key that table already has as a field. A parent that lists its children owns ONE multi relation field (multi true); the child's single link to the parent is a second field, and their inverseKeys differ from every field key.",
|
|
7813
7920
|
example: { fromTable: "request", fromField: "client", toTable: "client", flavor: "referenced", cardinality: "one", onDelete: "restrict", inverseKey: "requests_from" }
|
|
@@ -7831,7 +7938,7 @@ function describeVocabulary() {
|
|
|
7831
7938
|
template_view_kinds: TEMPLATE_VIEW_KINDS,
|
|
7832
7939
|
template_audiences: TEMPLATE_AUDIENCES,
|
|
7833
7940
|
form_audiences: ["public", "member", "staff"],
|
|
7834
|
-
extra_kinds_for_describe: ["booking"],
|
|
7941
|
+
extra_kinds_for_describe: ["booking", "automation"],
|
|
7835
7942
|
booking_window: { weekday: "0 = Sunday \u2026 6 = Saturday", from: "HH:MM", to: "HH:MM later than from" },
|
|
7836
7943
|
booking_availability: BOOKING_AVAILABILITY_FACTS,
|
|
7837
7944
|
view_shape: VIEW_SHAPE_FACTS,
|
|
@@ -7841,6 +7948,7 @@ function describeVocabulary() {
|
|
|
7841
7948
|
field_shape: FIELD_SHAPE_FACTS,
|
|
7842
7949
|
formula_shape: FORMULA_SHAPE_FACTS,
|
|
7843
7950
|
relationship_shape: RELATIONSHIP_SHAPE_FACTS,
|
|
7951
|
+
automation_shape: AUTOMATION_SHAPE_FACTS,
|
|
7844
7952
|
phone_rule: "seed phone numbers are fictional: 555-0100 to 555-0199 (e.g. (813) 555-0142); never a real-looking number, and never a number from a country the person did not name.",
|
|
7845
7953
|
team_rule: "the team (name, role, work email, mobile, start date) is the shared team_member block; do not declare a table with those fields \u2014 keep a table of your own to just name and role, or point a relation field at team_member.",
|
|
7846
7954
|
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"
|