trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
419 lines (412 loc) • 13.9 kB
JavaScript
import {
findSchemaForType
} from "./chunk-LEGH72HW.js";
// src/forms/types.ts
var FORM_MODES = ["create", "edit", "view"];
var FIELD_CONTROLS = [
"text",
"textarea",
"number",
"checkbox",
"select",
"multi_select",
"date",
"relation",
"people",
"files",
"json",
"readonly"
];
// src/forms/derive.ts
function controlFor(spec, readonly) {
if (readonly) return "readonly";
switch (spec.valueType) {
case "title":
return "text";
case "rich_text":
return "textarea";
case "number":
return "number";
case "checkbox":
return "checkbox";
case "select":
case "status":
return "select";
case "multi_select":
return "multi_select";
case "date":
return "date";
case "url":
case "email":
case "phone_number":
return "text";
case "relation":
return "relation";
case "people":
return "people";
case "files":
return "files";
case "json":
return "json";
case "rollup":
case "formula":
case "ai_generated":
return "readonly";
}
}
function inputModeFor(spec) {
switch (spec.valueType) {
case "url":
return "url";
case "email":
return "email";
case "phone_number":
return "tel";
case "number":
return "numeric";
default:
return void 0;
}
}
function humanize(name) {
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().split(" ").filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
}
function optionsFor(spec) {
const raw = spec.selectOptions;
if (!raw || raw.length === 0) return void 0;
return raw.map((value) => ({ value, label: String(value) }));
}
function validationFor(spec) {
const v = {};
if (spec.required) v.required = true;
if (spec.min !== void 0) v.min = spec.min;
if (spec.max !== void 0) v.max = spec.max;
if (spec.pattern !== void 0) v.pattern = spec.pattern;
if (spec.minLength !== void 0) v.minLength = spec.minLength;
if (spec.maxLength !== void 0) v.maxLength = spec.maxLength;
return Object.keys(v).length > 0 ? v : void 0;
}
function modesFor(spec) {
const computed = spec.computed === true || spec.valueType === "rollup" || spec.valueType === "formula" || spec.valueType === "ai_generated" || spec.editable === false;
if (spec.modes && spec.modes.length > 0) {
const modes = spec.modes.slice();
if (computed && !modes.includes("view")) modes.push("view");
return modes;
}
return computed ? ["view"] : ["create", "edit", "view"];
}
function fieldFromSpec(spec, order) {
const readonly = spec.computed === true || spec.valueType === "rollup" || spec.valueType === "formula" || spec.valueType === "ai_generated" || spec.editable === false;
const field = {
name: spec.name,
label: humanize(spec.name),
control: controlFor(spec, readonly),
valueType: spec.valueType,
required: (spec.required ?? false) && !readonly,
readonly,
modes: modesFor(spec),
order,
section: spec.group ?? "properties"
};
if (spec.description) field.description = spec.description;
const options = optionsFor(spec);
if (options) field.options = options;
if (spec.defaultValue !== void 0) field.defaultValue = spec.defaultValue;
if (spec.display) field.display = spec.display;
if (spec.relation) {
field.relation = {
targetSchema: spec.relation.targetSchema,
cardinality: spec.relation.cardinality
};
}
const inputMode = inputModeFor(spec);
if (inputMode) field.inputMode = inputMode;
const validation = validationFor(spec);
if (validation) field.validation = validation;
return field;
}
function orderFields(specs, propertyFieldIds, titleField) {
const sorted = propertyFieldIds && propertyFieldIds.length > 0 ? [
...propertyFieldIds.map((id) => specs.find((s) => s.name === id)).filter(
(s) => s !== void 0
),
...specs.filter((s) => !propertyFieldIds.includes(s.name))
] : [...specs];
if (!titleField) return sorted;
const idx = sorted.findIndex((s) => s.name === titleField);
if (idx > 0) {
const [title] = sorted.splice(idx, 1);
if (title) sorted.unshift(title);
}
return sorted;
}
function sectionTitle(group) {
return humanize(group);
}
function deriveTypeName(schema) {
const id = schema["@id"];
const idx = id.lastIndexOf(":");
return idx >= 0 ? id.slice(idx + 1) : id;
}
function deriveFormDescriptor(schema, opts = {}) {
const mode = opts.mode ?? "create";
const entityType = deriveTypeName(schema);
const titleField = schema.fields.find((f) => f.valueType === "title")?.name;
const specs = orderFields(schema.fields, schema.propertyFieldIds, titleField);
const sections = /* @__PURE__ */ new Map();
for (const spec of specs) {
const group = spec.group ?? "";
const key = group || "properties";
let section = sections.get(key);
if (!section) {
section = {
id: key,
title: group ? sectionTitle(group) : "Properties",
specs: []
};
sections.set(key, section);
}
section.specs.push(spec);
}
const sectionList = [];
const allFields = [];
let order = 0;
for (const [key, section] of sections) {
const fields = section.specs.map((spec) => fieldFromSpec(spec, order++)).filter((f) => f.modes.includes(mode));
if (fields.length === 0) continue;
sectionList.push({ id: key, title: section.title, fields });
allFields.push(...fields);
}
return {
formId: `${schema["@id"]}:${mode}`,
schemaId: schema["@id"],
entityType,
label: schema.label ?? entityType,
...schema.labelPlural ? { labelPlural: schema.labelPlural } : {},
...schema.icon ? { icon: schema.icon } : {},
...titleField ? { titleField } : {},
mode,
schemaVersion: schema.version,
overridden: false,
derived: true,
sections: sectionList,
fields: allFields
};
}
function deriveFormFields(schema) {
const titleField = schema.fields.find((f) => f.valueType === "title")?.name;
return orderFields(schema.fields, schema.propertyFieldIds, titleField).map(
(spec, i) => fieldFromSpec(spec, i)
);
}
// src/forms/overrides.ts
function formMatchesMode(form, mode) {
return form.mode === void 0 || form.mode === mode;
}
function formEntityToOverride(entity) {
return {
id: entity.id,
entityType: entity.entityType,
...entity.mode ? { mode: entity.mode } : {},
...entity.title !== void 0 ? { title: entity.title } : {},
...entity.description !== void 0 ? { description: entity.description } : {},
fields: (entity.fields ?? []).map((f) => ({
fieldName: f.fieldName,
...f.label !== void 0 ? { label: f.label } : {},
...f.control !== void 0 ? { control: f.control } : {},
...f.required !== void 0 ? { required: f.required } : {},
...f.readonly !== void 0 ? { readonly: f.readonly } : {},
...f.hidden !== void 0 ? { hidden: f.hidden } : {},
...f.order !== void 0 ? { order: f.order } : {},
...f.section !== void 0 ? { section: f.section } : {},
...f.options !== void 0 ? { options: f.options } : {},
...f.defaultValue !== void 0 ? { defaultValue: f.defaultValue } : {},
...f.placeholder !== void 0 ? { placeholder: f.placeholder } : {},
...f.description !== void 0 ? { description: f.description } : {}
}))
};
}
function applyFieldOverride(field, ov) {
if (ov.label !== void 0) field.label = ov.label;
if (ov.control !== void 0) field.control = ov.control;
if (ov.required !== void 0) field.required = ov.required;
if (ov.readonly !== void 0) {
field.readonly = ov.readonly;
if (ov.readonly) field.control = "readonly";
}
if (ov.order !== void 0) field.order = ov.order;
if (ov.defaultValue !== void 0) field.defaultValue = ov.defaultValue;
if (ov.placeholder !== void 0) field.placeholder = ov.placeholder;
if (ov.description !== void 0) field.description = ov.description;
if (ov.options !== void 0) {
field.options = ov.options.map((value) => ({ value, label: String(value) }));
}
}
function titleize(section) {
return section.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().split(" ").filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
}
function applyFormOverride(descriptor, override) {
if (!formMatchesMode(override, descriptor.mode)) return descriptor;
if (override.title !== void 0) descriptor.label = override.title;
if (override.description !== void 0) {
descriptor.description = override.description;
}
const hidden = /* @__PURE__ */ new Set();
for (const ov of override.fields) {
const field = descriptor.fields.find((f) => f.name === ov.fieldName);
if (!field) continue;
applyFieldOverride(field, ov);
if (ov.section !== void 0) field.section = ov.section;
if (ov.hidden) hidden.add(field.name);
}
if (hidden.size > 0 || override.fields.some((f) => f.section !== void 0)) {
rebuildSections(descriptor);
}
descriptor.fields = descriptor.fields.filter((f) => !hidden.has(f.name));
descriptor.sections = descriptor.sections.map((s) => ({ ...s, fields: s.fields.filter((f) => !hidden.has(f.name)) })).filter((s) => s.fields.length > 0);
descriptor.overridden = true;
return descriptor;
}
function rebuildSections(descriptor) {
const sections = /* @__PURE__ */ new Map();
for (const field of descriptor.fields) {
let section = sections.get(field.section);
if (!section) {
section = {
id: field.section,
title: field.section === "properties" ? "Properties" : titleize(field.section),
fields: []
};
sections.set(field.section, section);
}
section.fields.push(field);
}
descriptor.sections = Array.from(sections.values());
for (const section of descriptor.sections) {
section.fields.sort((a, b) => a.order - b.order);
}
let order = 0;
for (const section of descriptor.sections) {
for (const field of section.fields) field.order = order++;
}
}
// src/forms/resolve.ts
function toOverride(entry) {
return "type" in entry ? formEntityToOverride(entry) : entry;
}
function resolveFormDescriptor(ontologies, type, opts = {}) {
const mode = opts.mode ?? "create";
const schema = findSchemaForType(ontologies, type);
if (!schema) return void 0;
const descriptor = deriveFormDescriptor(schema, { mode });
const overrides = [
...opts.overrides ?? [],
...(opts.forms ?? []).map(toOverride)
];
const matching = overrides.filter(
(o) => o.entityType === descriptor.entityType && formMatchesMode(o, descriptor.mode)
);
if (matching.length > 0) {
const scoped = matching.filter((o) => o.mode === mode);
for (const ov of scoped.length > 0 ? scoped : matching) {
applyFormOverride(descriptor, ov);
}
}
return descriptor;
}
function listFormableTypes(ontologies) {
return Array.from(ontologies).map((schema) => {
const id = schema["@id"];
const idx = id.lastIndexOf(":");
return {
entityType: idx >= 0 ? id.slice(idx + 1) : id,
schemaId: id,
label: schema.label ?? id
};
});
}
// src/forms/kernel.ts
function attr(entity, name) {
return entity.facts.find((f) => f.a === name)?.v;
}
function fieldFromEntity(entity) {
const field = {
id: entity.id,
type: "FormField",
fieldName: String(attr(entity, "fieldName") ?? "")
};
const s = (n) => attr(entity, n);
const sv = s("label");
if (sv !== void 0) field.label = String(sv);
const control = s("control");
if (control !== void 0) field.control = String(control);
const required = s("required");
if (required !== void 0) field.required = required === true;
const readonly = s("readonly");
if (readonly !== void 0) field.readonly = readonly === true;
const hidden = s("hidden");
if (hidden !== void 0) field.hidden = hidden === true;
const order = s("order");
if (order !== void 0 && typeof order === "number") field.order = order;
const section = s("section");
if (section !== void 0) field.section = String(section);
const options = s("options");
if (options !== void 0 && typeof options === "string") {
try {
const parsed = JSON.parse(options);
if (Array.isArray(parsed)) field.options = parsed;
} catch {
}
}
const defaultValue = s("defaultValue");
if (defaultValue !== void 0) field.defaultValue = defaultValue;
const placeholder = s("placeholder");
if (placeholder !== void 0) field.placeholder = String(placeholder);
const description = s("description");
if (description !== void 0) field.description = String(description);
return field;
}
function readFormOverrides(reader) {
const forms = reader.listEntities("Form");
return forms.map((record) => {
const entityType = attr(record, "entityType");
if (entityType === void 0) return null;
const fieldIds = record.links.filter((l) => l.a === "fields").map((l) => l.e2);
const fields = [];
for (const id of fieldIds) {
const fieldEntity = reader.getEntity(id);
if (fieldEntity && fieldEntity.type === "FormField") {
fields.push(fieldFromEntity(fieldEntity));
}
}
const form = {
id: record.id,
type: "Form",
entityType: String(entityType),
fields
};
const mode = attr(record, "mode");
if (mode !== void 0) {
form.mode = String(mode);
}
const title = attr(record, "title");
if (title !== void 0) form.title = String(title);
const description = attr(record, "description");
if (description !== void 0) form.description = String(description);
return form;
}).filter((f) => f !== null);
}
export {
FORM_MODES,
FIELD_CONTROLS,
humanize,
deriveTypeName,
deriveFormDescriptor,
deriveFormFields,
formMatchesMode,
formEntityToOverride,
applyFormOverride,
resolveFormDescriptor,
listFormableTypes,
readFormOverrides
};