// `content.types` — the registered content-type descriptors, in a WIRE-SAFE // shape. The editor calls this once and renders a `` per field // purely from `editorHint` + the field metadata — no content-type file is // imported into the browser bundle, so adding a type on the server surfaces in // the editor with zero client changes. // // A field's `derived.transform` is a FUNCTION (not serialisable) and the editor // doesn't need it — the transform runs server-side on save — so the executor // projects each field down to the data the form widgets read. import { defineAction } from '@voltro/protocol' import { Schema } from 'effect' // The browser-facing view of one field: enough to pick + configure a widget. const FieldWire = Schema.Struct({ name: Schema.String, kind: Schema.String, editorHint: Schema.String, isOptional: Schema.Boolean, maxLength: Schema.optional(Schema.Number), literals: Schema.optional(Schema.Array(Schema.String)), referenceType: Schema.optional(Schema.String), }) export const contentTypes = defineAction({ name: 'content.types', // Editors only. It carries no row data at all — but it IS a map of your // content model (every type, every field, every constraint), which is // reconnaissance you have no reason to hand an anonymous caller. Guarded with // the same `content:read` the editor already needs, so this costs a signed-in // editor nothing and tells a stranger nothing. guards: [{ scope: 'content:read' }], input: Schema.Struct({}), output: Schema.Struct({ types: Schema.Array( Schema.Struct({ name: Schema.String, displayName: Schema.String, pluralName: Schema.String, // An ORDERED list (not a Record) so the editor renders fields in // declaration order. fields: Schema.Array(FieldWire), columns: Schema.Array(Schema.String), }), ), }), })