// Creates an article. On commit, the search plugin's ChangeEvent tap mirrors // the new row into the index — so it's searchable immediately, no extra call. // `assertOwnTenant` rejects cross-tenant spoofing (typed `TenantMismatch`). import { defineMutation } from '@voltro/protocol' import { TenantMismatch } from '@voltro/plugin-multitenancy/guard' import { Schema } from 'effect' export const createArticle = defineMutation({ name: 'articles.create', // Open — with one consequence worth naming because it reaches OUTSIDE the // database: on commit the search plugin's ChangeEvent tap mirrors the row // into the external index (Typesense / Meilisearch / Algolia). So an // unguarded write here is an unguarded write there too, and the index is // usually not tenant-partitioned by the same machinery your tables are. // // `assertOwnTenant` still rejects a `tenantId` that does not match the // resolved subject, and no auth strategy or rbac ships in this template, so a // `guards: [{ scope: 'articles:write' }]` would deny every caller. Add an // identity, then that guard — before you point `search` at a real cluster. openAccess: 'inserts an article carrying only caller-supplied fields into the caller\'s own tenant ' + '(`assertOwnTenant` rejects a mismatch). On commit it also mirrors the row into the ' + 'external search index, so guard it before pointing the plugin at a real cluster.', target: { table: 'articles', op: 'insert', shape: (input: { tenantId: string; title: string; body: string; tag: string }) => ({ title: input.title, body: input.body, tag: input.tag, tenantId: input.tenantId, }), }, input: Schema.Struct({ tenantId: Schema.String, title: Schema.NonEmptyString, body: Schema.String, tag: Schema.String, }), output: Schema.Struct({ id: Schema.String, title: Schema.String, body: Schema.String, tag: Schema.String, tenantId: Schema.String, }), error: TenantMismatch, })