// Create a project — the paywall in ONE handler. The billing plugin auto-merges // its typed `EntitlementExceeded` (+ `BillingError`) into this procedure's // wire-error union, so the client decodes the over-quota failure typed WITHOUT // a manual `error:` declaration here. That typed failure is the paywall signal: // the frontend catches `_tag === 'EntitlementExceeded'` and shows the upgrade CTA. import { defineMutation } from '@voltro/protocol' import { Schema } from 'effect' export const createProject = defineMutation({ name: 'projects.create', target: { table: 'projects', op: 'insert' }, // Members only. Note the ORDER, because it is the whole reason this line // exists next to `requireEntitlement`: the guard runs in the dispatch spine // BEFORE the executor and before the transaction opens, so an anonymous call // is refused without ever consuming a paying tenant's metered quota. A quota // gate alone would let a stranger burn the free plan's three projects. // // Satisfiable as shipped: a matched session gets `projects:write` from // `auth.resolveScopes` (authz.ts); anonymous callers hold no scopes. guards: [{ scope: 'projects:write' }], input: Schema.Struct({ name: Schema.NonEmptyString }), output: Schema.Struct({ id: Schema.String, name: Schema.String, tenantId: Schema.String, }), })