cvx-kit 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/crud.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # `cvx-kit/crud` — the CRUD command factory
2
2
 
3
+ > This factory uses the deprecated Promise command facade and remains available
4
+ > for existing consumers. For new Effect commands, use `createEffectCrud` from
5
+ > `cvx-kit/effect`; see [domain integrations](./domain-operations.md).
6
+
3
7
  `createCrudCommands` generates the three standard operations every entity
4
8
  starts with — create, update, archive — from one zodTable declaration,
5
9
  **fully inside the audited command pipeline**: strict command inputs from
@@ -4,7 +4,7 @@ Declare operations beside their handlers, inject the application's repositories,
4
4
 
5
5
  The complete TypeScript module below is mirrored in `test/domain-operation-guide-types.ts` for strict compilation. All TypeScript in this guide belongs to that one module; there are no standalone handler fragments. It uses public `cvx-kit/*` imports, real Convex builders, an injected document repository, and generated references from the native acceptance fixture. Its document schema and generic builders let the example compile independently; an application imports its own `mutation`, `query`, and internal builders from `./_generated/server`. The integrated rename portion imports the exact registry from `test/fixture-effect/convex/integratedRename.ts` and binds its real generated references. The document create/receipt example remains separate.
6
6
 
7
- The examples use the optional Effect adapter even for ordinary handlers. Consumers without Effect can keep the legacy `cvx-kit/components/foundation` surface and use the neutral contracts, errors, idempotency, workflow, and tool facades. Import `cvx-kit/effect` only when the application supplies its Effect peer.
7
+ The examples use the recommended Effect command API even for ordinary handlers. The older `Foundation.Command` facade is deprecated and retained for existing consumers without Effect; use [the migration guide](./commands.md) when moving those domains. Neutral contracts, errors, idempotency, workflow, and tool facades remain available without Effect. Import `cvx-kit/effect` only when the application supplies its Effect peer.
8
8
 
9
9
  ## Declare a cohesive domain
10
10
 
package/docs/effect.md CHANGED
@@ -5,7 +5,11 @@ runner. Handlers can return ordinary values, Promises, or Effects. Each
5
5
  registry returns a lazy Effect from `exec(operation, input, host)`; the API
6
6
  adapter provides request services and runs it once.
7
7
 
8
- Install Effect explicitly when opting in:
8
+ This is the recommended command API. The older `Foundation.Command` facade
9
+ and `exec({ operation, handler })` executor API are deprecated and retained for
10
+ compatibility. Declare handlers inside `command({...})` for new command code.
11
+
12
+ Install Effect explicitly:
9
13
 
10
14
  ```sh
11
15
  bun add effect@4.0.1
@@ -377,8 +381,15 @@ registry's lazy Effect to a shared API adapter. A normal async handler works
377
381
  in the cohesive declaration without a generator; wrap expected Promise failures
378
382
  only when you want a typed error channel.
379
383
 
380
- The [command migration guide](./commands.md) shows a complete legacy-to-Effect
381
- pair and the existing lifecycle contract. The [authentication guide](./auth.md)
384
+ The [command guide](./commands.md) documents the current lifecycle and migration
385
+ steps; the [deprecated API reference](./commands-legacy.md) retains old examples.
386
+ The [authentication guide](./auth.md)
382
387
  shows how to wrap trusted custom builders with `effectZodApiBuilder`, so auth,
383
388
  tenancy, validators, row-level policies, and triggers run through their
384
389
  existing builder before Effect request services are provisioned.
390
+
391
+ Optional request context can be added through the existing Effect API adapter's
392
+ `context` callback. Bind selected registries with `commands.withContext(value)`
393
+ and consume `ctx.commands.exec(name, input)` through the application's existing
394
+ function helper names. Keep dependencies local to each feature; explicit execution
395
+ and input-only command handlers remain supported. See [optional context injection](./command-context.md).
package/docs/llms.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Paste this file (installed at `node_modules/cvx-kit/docs/`) into an agent's
4
4
  > context when it works on a cvx-kit application. Detailed docs:
5
5
  > `architecture.md`, `conventions.md`, `zod-table.md`, `auth.md`,
6
- > `commands.md`, `triggers.md`, `approvals.md`, `maintainability.md`,
6
+ > `commands.md`, `effect.md`, `domain-operations.md`, `triggers.md`, `approvals.md`, `maintainability.md`,
7
7
  > `migration.md` (same directory).
8
8
  > Structure, file anatomy, and naming rules → `conventions.md` is
9
9
  > authoritative. Restructuring an existing raw project → `migration.md`.
@@ -22,7 +22,13 @@
22
22
  cvx-kit is a reusable Convex application kit: zod table boundaries, auth-aware
23
23
  function constructors, a trigger registry, an audited command protocol
24
24
  (Foundation component), and a declarative approvals component. Peer deps:
25
- `convex ^1.43`, `zod ^4`.
25
+ `convex ^1.45.0`, `zod ^4.5.4`; command implementations also install
26
+ `effect >=4.0.1 <5` and import `cvx-kit/effect`.
27
+
28
+ **Command API selection:** use `createEffectFoundation` and declare handlers
29
+ inside `command({...})`. The older `Foundation.Command` Promise facade is
30
+ deprecated. Its compatibility examples live in `commands-legacy.md`; do not
31
+ copy that API into new implementations.
26
32
 
27
33
  ## Non-negotiable rules
28
34
 
@@ -31,27 +37,27 @@ function constructors, a trigger registry, an audited command protocol
31
37
  2. **Raw `query`/`mutation`/`action`/`internal*` builders appear only in
32
38
  `convex/functions.ts`** (the `createAuthFunctions` call). All other
33
39
  functions use `authQuery/authMutation/authAction`, `roleQuery/...`,
34
- `adminQuery/...` (public) or `systemQuery/systemMutation/systemAction` (internal).
40
+ `adminQuery/...` (public) or `systemQuery/systemMutation/systemAction` (internal),
41
+ wrapped once with `effectZodApiBuilder` when their handlers return Effects.
35
42
  This is what guarantees auth, triggers, and bounded reads.
36
43
  3. **Every public query returns DTOs** via `<table>.toPublicDto(row)` —
37
44
  runtime redaction, not just types.
38
45
  4. **Every read is bounded**: `ctx.include(ctx.db.query('t')).matching(...)
39
46
  .execute(limit)` with `1 ≤ limit ≤ 100`. `.resolve()` falls back to a full
40
47
  table scan — avoid it.
41
- 5. **State changes are commands**: an operations registry with mandatory
42
- `classification` and `audit()` per operation, executed via
43
- `commands.exec({ operation, handler })`. Audit writes happen in the same
44
- transaction. Optional per-operation `permission` (checked via the
45
- Foundation's injected `checkPermission`) and `guard(ctx, command)`
46
- preconditions run BEFORE the handler, plus a registry-wide default guard
47
- (`new Command(operations, { guard })`). Composable middleware
48
- (`Command.middleware(async ({ context, next }) => ...)`) wraps
49
- [guards → handler] via registry `middleware: [...]` and per-operation
50
- `middleware: [...]` — `next({ context })` enriches downstream ctx, and
51
- the chain's output is re-parsed by the result schema. Query takes the
52
- same shape (`Query.middleware`, kernel + per-executor arrays). Order:
53
- permission → registry middleware → operation middleware → default guard
54
- → operation guard → handler → result parse → aggregates → audit.
48
+ 5. **State changes are Effect commands**: declare
49
+ `Command({ context, operations: ({ command }) => ({ ... }), defaults })`.
50
+ Each `command({ input, result, classification, handler, audit })` owns its
51
+ handler; handlers receive `(input, context)`. Execute with
52
+ `commands.exec(operation, input, host)` and return the lazy Effect through a
53
+ shared API builder. Never pass a handler to `exec` or run a nested
54
+ `Effect.runPromise`. Permission uses the injected `checkPermission(host, ...)`;
55
+ guards receive `(context, input)`. Per-operation middleware is one callback,
56
+ `next()` returns an Effect, and `next({ context })` enriches downstream ctx.
57
+ Order: input → context → permission → prepare/replay → middleware → default
58
+ guard → operation guard → handler → result parse → aggregates/audit → completion.
59
+ Audit writes stay in the mutation transaction. Failures must escape the
60
+ mutation for rollback; replay skips middleware, guards, handler, and audit.
55
61
  6. **Never write timestamps by hand.** `createdAt`/`updatedAt` are maintained
56
62
  by the `timestamps` trigger; `archivedAt` is the app-controlled soft-delete
57
63
  marker.
@@ -74,18 +80,19 @@ function constructors, a trigger registry, an audited command protocol
74
80
 
75
81
  | Import | Provides |
76
82
  | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77
- | `cvx-kit` | everything below re-exported (except components' defaults) |
83
+ | `cvx-kit` | Non-Effect helpers and component clients; import the optional Effect facade explicitly from `cvx-kit/effect`. |
78
84
  | `cvx-kit/zod-table` | `zodTable`, `tenantTable`, `createModule`, `paginated`, `zodVariantTable`, `jsonSafeZid`, `TIMESTAMP_FIELDS` |
85
+ | `cvx-kit/effect` | Recommended command/query API: `createEffectFoundation`, `effectApiBuilder`, `effectZodApiBuilder`, `createEffectCrud`, idempotency/workflow adapters and operation tools. |
79
86
  | `cvx-kit/auth` | `createAuthFunctions` (incl. optional `security` RLS config), `createInclude`, `defaultRoleMap` |
80
87
  | `cvx-kit/tenancy` | `createTenantRules`, `composeRules`, `requireTenantReference`, `assertTenantOwned`, `TENANT_FIELD` |
81
- | `cvx-kit/crud` | `createCrudCommands` — create/update/archive from a zodTable, inside the command pipeline; `enrich` REQUIRED for tenantTables |
88
+ | `cvx-kit/crud` | Compatibility CRUD factory using the deprecated Promise command facade; use `createEffectCrud` from `cvx-kit/effect` for new domains. |
82
89
  | `cvx-kit/state-machine` | `createStateMachine` — typed transitions from constants tuples; `assert` drops into command guards |
83
90
  | `cvx-kit/middleware` | `rateLimit` — packaged middleware over an injected rate-limiter instance; keyed by `ctx.tenant`, missing key = config error |
84
91
  | `cvx-kit/webhooks` | `createWebhookBoundary`, `recordWebhookEvent`, `webhookEventsTable` — raw-body verify, natural-key dedup in the mutation |
85
92
  | `cvx-kit/agent-tools` | `createAgentTools` — tool records from table masks; mutation handlers route through command executors |
86
93
  | `cvx-kit/triggers` | `createTriggers`, `timestamps`, `appendOnly`, `noDelete`, `tenantOwnership`, `Triggers` |
87
94
  | `cvx-kit/errors` | `KitError`, `defaultErrors`, `ErrorFactory` |
88
- | `cvx-kit/components/foundation` | `Foundation` — the ONLY runtime export besides the default component config for `app.use`. Everything (`Command`, `Query`, `observability`, `executeResultBoundary`, `projectResult`, `emitSemanticEvent`) destructures from the instance; nothing is importable loose. |
95
+ | `cvx-kit/components/foundation` | Component client and types; `Foundation.Command` is deprecated. The component and other capabilities remain available. New command registries use `cvx-kit/effect`. |
89
96
  | `cvx-kit/components/approvals` | `Approvals` client; default export = component config for `app.use` |
90
97
  | `cvx-kit/test` | `registerFoundation(t)`, `registerApprovals(t)` for convex-test |
91
98
 
@@ -110,7 +117,7 @@ appendOnly(triggers, 'history')
110
117
  // convex/functions.ts
111
118
  export const {
112
119
  authQuery,
113
- authMutation,
120
+ authMutation: baseAuthMutation,
114
121
  authAction,
115
122
  adminQuery,
116
123
  adminMutation,
@@ -141,13 +148,23 @@ export const {
141
148
  },
142
149
  })
143
150
 
151
+ // convex/functions.ts — wrap configured auth constructors once, after auth/RLS/triggers
152
+ // Import Context from effect and effectZodApiBuilder from cvx-kit/effect.
153
+ // Place this after the registry declaration or import the registry from its module.
154
+ export const authMutation = effectZodApiBuilder(baseAuthMutation, {
155
+ context: (ctx) => ({ commands: commands.withContext(ctx) }),
156
+ })
157
+
144
158
  // convex/foundation.ts
145
- export const { Command, Query, observability } = new Foundation(components.foundation, {
159
+ // Import createEffectFoundation from cvx-kit/effect.
160
+ import type { MutationCtx } from './_generated/server'
161
+ export type CommandHost = MutationCtx & { actor: { userId: string } }
162
+ export const { Command, Query, observability } = createEffectFoundation({
146
163
  observability: {
147
164
  enabled: () => process.env.OBS === 'true',
148
165
  classifyError, // → { outcome: 'denied'|'failed', errorCode }
149
- writeAudit: (ctx, entry) => writeAuditEntry(ctx, entry),
150
166
  },
167
+ writeAudit: (ctx: CommandHost, entry) => writeAuditEntry(ctx, entry),
151
168
  })
152
169
 
153
170
  // convex/approvals.ts
@@ -175,32 +192,33 @@ export const documents = zodTable(
175
192
  defineSchema({ documents: documents.table.index('by_owner', ['ownerId']) })
176
193
 
177
194
  // domain/documents/commands.ts
178
- const operations = {
179
- 'documents.rename': Command.operation({
180
- command: documents.commandInput.extend({ id: zid('documents'), actorId: z.string() }),
181
- result: z.object({ ok: z.literal(true) }).strict(),
182
- classification: 'business',
183
- audit: ({ command }) => ({
184
- operation: 'documents.rename',
185
- actorId: command.actorId,
186
- aggregate: { type: 'document', id: command.id },
195
+ export const renameInput = documents.commandInput.extend({ id: zid('documents') })
196
+ export const renameResult = z.object({ ok: z.literal(true) }).strict()
197
+ export const commands = Command({
198
+ context: (host: CommandHost) => host,
199
+ operations: ({ command }) => ({
200
+ 'documents.rename': command({
201
+ input: renameInput,
202
+ result: renameResult,
203
+ classification: 'business',
204
+ handler: async (input, ctx) => {
205
+ await ctx.db.patch(input.id, { title: input.title })
206
+ return { ok: true as const }
207
+ },
208
+ audit: ({ command }, ctx) => ({
209
+ operation: 'documents.rename',
210
+ actorId: ctx.actor.userId,
211
+ aggregate: { type: 'document', id: command.id },
212
+ }),
187
213
  }),
188
214
  }),
189
- } as const
190
- const commands = new Command<MutationCtx, typeof operations>(operations)
191
- export const executeRename = commands.exec({
192
- operation: 'documents.rename',
193
- handler: async (ctx, cmd) => {
194
- await ctx.db.patch(cmd.id, { title: cmd.title })
195
- return { ok: true }
196
- },
197
215
  })
198
216
 
199
217
  // api/documents.ts — thin public adapter
200
218
  export const rename = authMutation({
201
- args: documents.commandInput.extend({ id: zid('documents') }),
202
- returns: z.object({ ok: z.literal(true) }).strict(),
203
- handler: (ctx, args) => executeRename(ctx, { ...args, actorId: ctx.actor.userId }),
219
+ args: renameInput,
220
+ returns: renameResult,
221
+ handler: (ctx, input) => ctx.commands.exec('documents.rename', input),
204
222
  })
205
223
  export const list = authQuery({
206
224
  args: { limit: z.number() },
@@ -261,9 +279,15 @@ points at `runId`.
261
279
  4. Mis-declared `serverFields` silently make server-owned fields
262
280
  client-writable; masks are the security boundary.
263
281
  5. `include(...).resolve()` silently falls back to `fullTableScan()`.
264
- 6. Audit callback ctx is untyped (`never`) — commands must run in mutations;
265
- wiring a query ctx fails at runtime.
282
+ 6. Command audit actors come from trusted host context, never client input.
283
+ Commands with writes must run through mutation builders with wrapped `ctx.db`.
266
284
  7. Changing approval steps without bumping `compatibilityKey` lets old runs
267
285
  be driven by an incompatible shape.
268
286
  8. When composing ctx manually, spread `wrapDB(ctx)` first — later spreads
269
287
  carrying `db` restore the unwrapped database.
288
+
289
+ Optional request context can be added through the existing Effect API adapter's
290
+ `context` callback. Bind selected registries with `commands.withContext(value)`
291
+ and consume `ctx.commands.exec(name, input)` through the application's existing
292
+ function helper names. Keep dependencies local to each feature; explicit execution
293
+ and input-only command handlers remain supported. See [optional context injection](./command-context.md).
package/docs/migration.md CHANGED
@@ -25,7 +25,7 @@ convex/
25
25
  schema.ts # defineSchema(domainTables). Nothing else.
26
26
  functions.ts # the single createAuthFunctions(...) call
27
27
  triggers.ts # the single trigger registry + helper registrations
28
- foundation.ts # the single new Foundation(...) facade
28
+ foundation.ts # the single createEffectFoundation(...) facade
29
29
  <component>.ts # one facade per mounted component (audit.ts, approvals.ts, pool.ts, workflows.ts, ...)
30
30
  auth.ts # identity-provider client wiring
31
31
  auth.config.ts # Convex auth providers config
package/docs/upgrading.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Upgrading to 0.1.0 — the new way of things
2
2
 
3
+ ## Current command API — Effect declarations
4
+
5
+ `Foundation.Command` and its separate `exec({ operation, handler })` API are
6
+ deprecated. For new command code, use `createEffectFoundation` from
7
+ `cvx-kit/effect`, declare handlers inside `command({...})`, and return
8
+ `exec(operation, input, host)` through a shared Effect API builder.
9
+ See [commands.md](./commands.md) for migration steps. Existing consumers remain
10
+ compatible; older release sections below describe their historical APIs.
11
+
3
12
  ## 0.1.6 — stricter architecture checks
4
13
 
5
14
  Enable `project-structure`, `root-wiring-only`, `application-orchestration`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cvx-kit",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Reusable Convex application kit: foundation kernels, approvals component, zod table boundaries, WorkOS-backed auth function constructors, and audited command protocol.",
5
5
  "keywords": [
6
6
  "component",
@@ -26,8 +26,11 @@ const domainGuideImports = new Set([
26
26
  './fixture-effect/convex/_generated/dataModel',
27
27
  ])
28
28
  const extraTypeFixtures = [
29
+ join(root, 'README.md'),
29
30
  join(root, 'src/docs/effect.md'),
30
31
  join(root, 'src/docs/commands.md'),
32
+ join(root, 'src/docs/command-context.md'),
33
+ join(root, 'src/docs/commands-legacy.md'),
31
34
  join(root, 'src/docs/auth.md'),
32
35
  domainGuidePath,
33
36
  ...process.argv.slice(2).map((path) => resolve(path)),