// `sync.pull` — DESCRIPTOR (browser-safe: schema + name only; NO // node/database/server imports — the codegen pulls this into the client // rpcGroup bundle). // // Pull the next page of events from the (simulated) upstream. This is an // ACTION, not a mutation/query: it does arbitrary imperative work and returns // a computed summary rather than a single optimistic row or a reactive table // scan. The durable `ctx.kv` cursor + idempotency markers live in the // executor (`.server.ts`). import { defineAction } from '@voltro/protocol' import { Schema } from 'effect' export const syncPull = defineAction({ name: 'sync.pull', // Open: every KV key this touches is namespaced with the request's tenant // (`sync::cursor` / `:seen:*` in the executor) and the rows it // writes go into a `tenant()` table, so one caller's sync state is not // reachable from another's. The "upstream" is a pure local generator — there // is no network call and no credential. // // No auth strategy and no rbac ship in this template, so a // `guards: [{ scope: 'sync:run' }]` would be unsatisfiable: every caller is // an anonymous Subject holding no scopes, and the guard would deny all of // them. Growing a role map here purely to look guarded would teach a pattern // this template is not about. openAccess: 'advances the request tenant\'s own sync watermark; every KV key is namespaced by that ' + 'tenant and the rows land in a `tenant()` table. The upstream is a local pure generator ' + '— no network call, no credential.', input: Schema.Struct({ // How many upstream events to pull this call (default 5, capped in the executor). limit: Schema.optional(Schema.Int.pipe(Schema.greaterThan(0))), }), output: Schema.Struct({ pulled: Schema.Int, // newly ingested (marker written) skipped: Schema.Int, // already seen within the idempotency window cursor: Schema.Int, // the durable watermark after this call }), })