UNPKG

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
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 };