// The CRDT write. A client produces an encoded update from its local // `crdtText()` handle (`handle.encode()`) and sends it here. The server does // NOT overwrite `body` with it — the runtime folds the incoming update into the // STORED state with `mergeCrdtStates` on the write path (see the `.server.ts` // comment), so two clients that edited the same base CONVERGE. `update` is the // opaque encoded CRDT state/delta (`bytes`); the same wire shape a Yjs update // blob carries. // // `op: 'update'` targets the existing row by its primary key. There is no // tenant field in the input: the `tenant()` mixin auto-scopes the update to the // caller's tenant, so a foreign `id` simply matches no row. import { defineMutation } from '@voltro/protocol' import { Schema } from 'effect' export const setDocumentBody = defineMutation({ name: 'documents.setBody', target: { table: 'documents', op: 'update' }, // Open, and note WHAT the CRDT changes about the risk: the server does not // overwrite `body` with the caller's bytes, it MERGES them // (`mergeCrdtStates`), so a concurrent editor's text is not destroyed by a // conflicting write — the two converge. `tenant()` scopes the update, so an // id from another tenant matches no row at all. // // What remains open is who may join an editing session, and that needs an // identity this template does not ship: with no auth strategy every caller is // an anonymous Subject holding no scopes, so a `guards:` would deny everyone. openAccess: 'merges a caller-supplied CRDT update into a document of the request\'s tenant — a merge, ' + 'not an overwrite, so no concurrent editor\'s text is lost, and an id outside the tenant ' + 'matches no row. Guard who may edit once the app has an identity.', input: Schema.Struct({ id: Schema.NonEmptyString, update: Schema.Uint8ArrayFromBase64, }), output: Schema.Struct({ id: Schema.String, title: Schema.String, // The MERGED body — what every subscriber now converges to. body: Schema.NullOr(Schema.Uint8ArrayFromBase64), tenantId: Schema.String, }), })