// Effect-mode executor. `saveDraftEffect` runs the pipeline over the // request-scoped `ctx.store` (a DataStore): it derives the computed fields, // validates, and writes to `_drafts` — the tenant/audit columns stamped // by the store spine. Its typed failures ride the Effect error channel, so we // `catchTag` them into the result union instead of letting them become a 500. import { Effect } from 'effect' import type { AppContext } from '@voltro/runtime' import { saveDraftEffect } from '@voltro/cms' import { contentTypeByName } from '../content' const execute = (input: { type: string; values: Record }, ctx: AppContext) => Effect.gen(function* () { const ct = contentTypeByName.get(input.type) if (ct === undefined) { return { ok: false as const, violations: [{ field: 'type', rule: 'unknown', message: `unknown content type '${input.type}'` }], } } return yield* saveDraftEffect(ctx.store, ct, input.values).pipe( Effect.map((row) => ({ ok: true as const, id: row['id'] as string, status: row['status'] as string })), // A rule violation → annotate the form; not an error. Effect.catchTag('ContentValidationFailed', (e) => Effect.succeed({ ok: false as const, violations: [...e.violations] }), ), // A caller-pinned id owned by another tenant → refuse, surfaced the same way. Effect.catchTag('ContentIdConflict', (e) => Effect.succeed({ ok: false as const, violations: [{ field: 'id', rule: 'conflict', message: `id '${e.id}' belongs to another tenant` }], }), ), ) }) export default execute