cvx-kit 0.1.3 → 0.1.5

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +81 -24
  3. package/dist/agent-tools.d.mts +28 -12
  4. package/dist/agent-tools.mjs +1 -1
  5. package/dist/agent-tools.mjs.map +1 -1
  6. package/dist/auth.d.mts +4 -2
  7. package/dist/auth.mjs +1 -1
  8. package/dist/auth.mjs.map +1 -1
  9. package/dist/components/approvals/_generated/api.d.mts +1 -1
  10. package/dist/components/approvals/audit.mjs +1 -1
  11. package/dist/components/approvals/audit.mjs.map +1 -1
  12. package/dist/components/approvals/client.d.mts +1 -1
  13. package/dist/components/approvals/client.mjs +1 -1
  14. package/dist/components/approvals/client.mjs.map +1 -1
  15. package/dist/components/approvals/constants.mjs.map +1 -1
  16. package/dist/components/approvals/decisions.d.mts +6 -7
  17. package/dist/components/approvals/decisions.mjs +1 -1
  18. package/dist/components/approvals/decisions.mjs.map +1 -1
  19. package/dist/components/approvals/functions.mjs +1 -1
  20. package/dist/components/approvals/functions.mjs.map +1 -1
  21. package/dist/components/approvals/health.mjs.map +1 -1
  22. package/dist/components/approvals/requests.mjs.map +1 -1
  23. package/dist/components/approvals/validators.mjs.map +1 -1
  24. package/dist/components/approvals/workflow.mjs +1 -1
  25. package/dist/components/approvals/workflow.mjs.map +1 -1
  26. package/dist/components/approvals/workflow_steps.d.mts +3 -3
  27. package/dist/components/approvals/workflow_steps.mjs +1 -1
  28. package/dist/components/approvals/workflow_steps.mjs.map +1 -1
  29. package/dist/components/foundation/client.d.mts +17 -24
  30. package/dist/components/foundation/client.mjs +1 -1
  31. package/dist/components/foundation/client.mjs.map +1 -1
  32. package/dist/components/foundation/modules/command/command.d.mts +4 -4
  33. package/dist/components/foundation/modules/command/command.mjs.map +1 -1
  34. package/dist/components/foundation/modules/observability/observability.d.mts +1 -1
  35. package/dist/components/foundation/modules/observability/observability.mjs +1 -1
  36. package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
  37. package/dist/components/foundation/modules/query/query.d.mts +8 -8
  38. package/dist/components/foundation/modules/query/query.mjs +1 -1
  39. package/dist/components/foundation/modules/query/query.mjs.map +1 -1
  40. package/dist/components/foundation/operation.d.mts +1 -1
  41. package/dist/components/foundation/operation.mjs.map +1 -1
  42. package/dist/components/foundation/result.d.mts +1 -1
  43. package/dist/components/foundation/result.mjs.map +1 -1
  44. package/dist/components/foundation/telemetry.mjs +1 -1
  45. package/dist/components/foundation/telemetry.mjs.map +1 -1
  46. package/dist/crud.d.mts +177 -16
  47. package/dist/crud.mjs +1 -1
  48. package/dist/crud.mjs.map +1 -1
  49. package/dist/middleware.d.mts +7 -7
  50. package/dist/middleware.mjs +1 -1
  51. package/dist/middleware.mjs.map +1 -1
  52. package/dist/oxlint.d.mts +8 -0
  53. package/dist/oxlint.mjs +2 -0
  54. package/dist/oxlint.mjs.map +1 -0
  55. package/dist/tenancy.mjs +1 -1
  56. package/dist/tenancy.mjs.map +1 -1
  57. package/dist/triggers.mjs +1 -1
  58. package/dist/triggers.mjs.map +1 -1
  59. package/dist/webhooks.d.mts +17 -106
  60. package/dist/webhooks.mjs.map +1 -1
  61. package/dist/zod-table.d.mts +46 -90
  62. package/dist/zod-table.mjs +1 -1
  63. package/dist/zod-table.mjs.map +1 -1
  64. package/docs/approvals.md +57 -56
  65. package/docs/architecture.md +1 -1
  66. package/docs/auth.md +57 -33
  67. package/docs/commands.md +53 -53
  68. package/docs/conventions.md +40 -40
  69. package/docs/crud.md +5 -5
  70. package/docs/llms.md +120 -71
  71. package/docs/maintainability.md +4 -4
  72. package/docs/migration.md +33 -33
  73. package/docs/oxlint.md +98 -0
  74. package/docs/tenancy.md +5 -8
  75. package/docs/triggers.md +4 -2
  76. package/docs/upgrading.md +47 -7
  77. package/docs/zod-table.md +48 -45
  78. package/package.json +33 -22
  79. package/scripts/convex-config-bridge.mjs +7 -18
  80. package/scripts/smoke-packed-components.mjs +20 -61
  81. package/scripts/test-packed-ids.mjs +16 -24
  82. package/scripts/test-packed-oxlint.mjs +141 -0
  83. package/src/test.ts +8 -17
package/docs/auth.md CHANGED
@@ -12,30 +12,52 @@ host. The kit provides the structure; the app provides the policy.
12
12
  // convex/functions.ts (the single place constructors are built)
13
13
  import { createAuthFunctions, defaultRoleMap } from 'cvx-kit/auth'
14
14
  import {
15
- action, internalAction, internalMutation, internalQuery, mutation, query,
15
+ action,
16
+ internalAction,
17
+ internalMutation,
18
+ internalQuery,
19
+ mutation,
20
+ query,
16
21
  } from './_generated/server'
17
22
  import type { DataModel } from './_generated/dataModel'
18
23
  import { triggers } from './triggers'
19
24
  import { authKit } from './auth'
20
25
 
21
26
  export const {
22
- authQuery, authMutation, authAction,
23
- roleQuery, roleMutation, roleAction,
24
- adminQuery, adminMutation, adminAction,
25
- systemQuery, systemMutation, systemAction,
26
- include, authenticatedUser,
27
+ authQuery,
28
+ authMutation,
29
+ authAction,
30
+ roleQuery,
31
+ roleMutation,
32
+ roleAction,
33
+ adminQuery,
34
+ adminMutation,
35
+ adminAction,
36
+ systemQuery,
37
+ systemMutation,
38
+ systemAction,
39
+ include,
40
+ authenticatedUser,
27
41
  } = createAuthFunctions<DataModel>({
28
- query, mutation, action, internalQuery, internalMutation, internalAction,
29
- getAuthUser: (ctx) => authKit.getAuthUser(ctx),
30
- mapRole: defaultRoleMap, // or your own vocabulary (see below)
31
- adminRoles: ['admin'],
32
- triggers, // every mutation write runs through wrapDB
33
- verifyMembership: async ({ userId, organizationId }) => {
34
- const memberships = await authKit.workos.userManagement
35
- .listOrganizationMemberships({ organizationId, userId, statuses: ['active'] })
36
- const m = memberships.data.find((c) => c.userId === userId)
37
- return m ? { organizationId: m.organizationId, roleSlug: m.role.slug } : null
38
- },
42
+ query,
43
+ mutation,
44
+ action,
45
+ internalQuery,
46
+ internalMutation,
47
+ internalAction,
48
+ getAuthUser: (ctx) => authKit.getAuthUser(ctx),
49
+ mapRole: defaultRoleMap, // or your own vocabulary (see below)
50
+ adminRoles: ['admin'],
51
+ triggers, // every mutation write runs through wrapDB
52
+ verifyMembership: async ({ userId, organizationId }) => {
53
+ const memberships = await authKit.workos.userManagement.listOrganizationMemberships({
54
+ organizationId,
55
+ userId,
56
+ statuses: ['active'],
57
+ })
58
+ const m = memberships.data.find((c) => c.userId === userId)
59
+ return m ? { organizationId: m.organizationId, roleSlug: m.role.slug } : null
60
+ },
39
61
  })
40
62
  ```
41
63
 
@@ -44,14 +66,14 @@ imports from `_generated/server` appear **only** in this file.
44
66
 
45
67
  ## The constructor families
46
68
 
47
- | Constructor | Visibility | Auth | Extra |
48
- |---|---|---|---|
49
- | `authQuery` / `authMutation` / `authAction` | public | any authenticated org member | actions live-verify membership |
50
- | `roleQuery(...roles)` / `roleMutation(...)` / `roleAction(...)` | public | listed roles only | factory — call with your roles |
51
- | `adminQuery` / `adminMutation` / `adminAction` | public | `config.adminRoles` | pre-built `role*(...adminRoles)` |
52
- | `systemQuery` | internal | none (trusted caller) | include-equipped, RLS-unwrapped |
53
- | `systemMutation` | internal | none (trusted caller) | still trigger-wrapped |
54
- | `systemAction` | internal | none | plain internal action |
69
+ | Constructor | Visibility | Auth | Extra |
70
+ | --------------------------------------------------------------- | ---------- | ---------------------------- | -------------------------------- |
71
+ | `authQuery` / `authMutation` / `authAction` | public | any authenticated org member | actions live-verify membership |
72
+ | `roleQuery(...roles)` / `roleMutation(...)` / `roleAction(...)` | public | listed roles only | factory — call with your roles |
73
+ | `adminQuery` / `adminMutation` / `adminAction` | public | `config.adminRoles` | pre-built `role*(...adminRoles)` |
74
+ | `systemQuery` | internal | none (trusted caller) | include-equipped, RLS-unwrapped |
75
+ | `systemMutation` | internal | none (trusted caller) | still trigger-wrapped |
76
+ | `systemAction` | internal | none | plain internal action |
55
77
 
56
78
  All are `zCustom*` constructors from convex-helpers, so `args` and `returns`
57
79
  take zod schemas directly and compose with `zodTable` masks.
@@ -164,14 +186,16 @@ rejects limits outside `1..maxRows` (default 100, configurable via
164
186
 
165
187
  ```ts
166
188
  export const list = authQuery({
167
- args: { ownerId: zid('users').optional(), limit: z.number() },
168
- returns: z.array(documents.publicDto),
169
- handler: (ctx, args) =>
170
- ctx.include(ctx.db.query('documents'))
171
- .when(args.ownerId, (q, ownerId) =>
172
- q.withIndex('by_owner', (ix) => ix.eq('ownerId', ownerId)))
173
- .otherwise((q) => q.withIndex('by_owner'))
174
- .execute(args.limit, (rows) => rows.map(documents.toPublicDto)),
189
+ args: { ownerId: zid('users').optional(), limit: z.number() },
190
+ returns: z.array(documents.publicDto),
191
+ handler: (ctx, args) =>
192
+ ctx
193
+ .include(ctx.db.query('documents'))
194
+ .when(args.ownerId, (q, ownerId) =>
195
+ q.withIndex('by_owner', (ix) => ix.eq('ownerId', ownerId)),
196
+ )
197
+ .otherwise((q) => q.withIndex('by_owner'))
198
+ .execute(args.limit, (rows) => rows.map(documents.toPublicDto)),
175
199
  })
176
200
  ```
177
201
 
package/docs/commands.md CHANGED
@@ -25,16 +25,13 @@ import { Foundation } from 'cvx-kit/components/foundation'
25
25
  import { components } from './_generated/api'
26
26
  import { writeAuditEntry } from './audit'
27
27
 
28
- export const { Command, Query, observability } = new Foundation(
29
- components.foundation,
30
- {
31
- observability: {
32
- enabled: () => process.env.COMMAND_OBSERVABILITY_ENABLED === 'true',
33
- classifyError: (error) => classify(error), // → { outcome: 'denied' | 'failed', errorCode }
34
- writeAudit: (ctx, entry) => writeAuditEntry(ctx, entry),
35
- },
36
- },
37
- )
28
+ export const { Command, Query, observability } = new Foundation(components.foundation, {
29
+ observability: {
30
+ enabled: () => process.env.COMMAND_OBSERVABILITY_ENABLED === 'true',
31
+ classifyError: (error) => classify(error), // → { outcome: 'denied' | 'failed', errorCode }
32
+ writeAudit: (ctx, entry) => writeAuditEntry(ctx, entry),
33
+ },
34
+ })
38
35
  ```
39
36
 
40
37
  Host code consumes the Foundation **only through this facade** — never deep
@@ -57,27 +54,27 @@ import type { MutationCtx } from '../../_generated/server'
57
54
  const typed = Command.withContext<MutationCtx>()
58
55
 
59
56
  const operations = {
60
- 'documents.rename': typed.operation({
61
- command: documents.commandInput.extend({ id: zid('documents') }),
62
- result: z.object({ ok: z.literal(true) }).strict(),
63
- classification: 'business',
64
- audit: ({ command }) => ({
65
- operation: 'documents.rename',
66
- actorId: command.actorId,
67
- aggregate: { type: 'document', id: command.id },
68
- metadata: { title: command.title },
69
- }),
70
- }),
57
+ 'documents.rename': typed.operation({
58
+ command: documents.commandInput.extend({ id: zid('documents') }),
59
+ result: z.object({ ok: z.literal(true) }).strict(),
60
+ classification: 'business',
61
+ audit: ({ command }) => ({
62
+ operation: 'documents.rename',
63
+ actorId: command.actorId,
64
+ aggregate: { type: 'document', id: command.id },
65
+ metadata: { title: command.title },
66
+ }),
67
+ }),
71
68
  } as const
72
69
 
73
70
  const commands = new Command<MutationCtx, typeof operations>(operations)
74
71
 
75
72
  export const executeRename = commands.exec({
76
- operation: 'documents.rename',
77
- handler: async (ctx, command) => {
78
- await ctx.db.patch(command.id, { title: command.title })
79
- return { ok: true }
80
- },
73
+ operation: 'documents.rename',
74
+ handler: async (ctx, command) => {
75
+ await ctx.db.patch(command.id, { title: command.title })
76
+ return { ok: true }
77
+ },
81
78
  })
82
79
  ```
83
80
 
@@ -119,7 +116,7 @@ The public API layer then wraps the executor in an `authMutation` and passes
119
116
  8. **Complete**: await the closure returned by `prepare`, including when audit
120
117
  returns null. A failure propagates inside the observation boundary.
121
118
  9. **Emit the observation**: `{ operation, classification, outcome,
122
- errorCode?, durationMs }` — completed, denied, or failed per
119
+ errorCode?, durationMs }` — completed, denied, or failed per
123
120
  `classifyError`.
124
121
 
125
122
  ### Guards and permissions
@@ -214,32 +211,35 @@ command and result boundary intentionally remains unknown.
214
211
  ```ts
215
212
  const typed = Command.withContext<Ctx>()
216
213
  const operations = {
217
- rename: typed.operation({
218
- command: z.object({ title: z.string().transform(value => value.length) }),
219
- result: z.object({ ok: z.boolean() }),
220
- classification: 'business',
221
- aggregates: ['document'],
222
- guard: (ctx, command) => requireTitleLength(ctx, command.title), // number
223
- middleware: [async ({ command, next }) => {
224
- const result = await next() // { ok: boolean }, before validation
225
- logLength(command.title)
226
- return result
227
- }],
228
- audit: ({ command, result }, ctx) => ({
229
- operation: 'rename', actorId: ctx.actorId,
230
- aggregate: { type: 'document', id: String(command.title) },
231
- metadata: { ok: result.ok },
232
- }),
233
- }),
214
+ rename: typed.operation({
215
+ command: z.object({ title: z.string().transform((value) => value.length) }),
216
+ result: z.object({ ok: z.boolean() }),
217
+ classification: 'business',
218
+ aggregates: ['document'],
219
+ guard: (ctx, command) => requireTitleLength(ctx, command.title), // number
220
+ middleware: [
221
+ async ({ command, next }) => {
222
+ const result = await next() // { ok: boolean }, before validation
223
+ logLength(command.title)
224
+ return result
225
+ },
226
+ ],
227
+ audit: ({ command, result }, ctx) => ({
228
+ operation: 'rename',
229
+ actorId: ctx.actorId,
230
+ aggregate: { type: 'document', id: String(command.title) },
231
+ metadata: { ok: result.ok },
232
+ }),
233
+ }),
234
234
  }
235
- const middleware = typed.registryMiddleware(operations, async input => {
236
- if (input.operation === 'rename') {
237
- input.command.title.toFixed() // discriminator retains schema correlation
238
- }
239
- return input.next()
235
+ const middleware = typed.registryMiddleware(operations, async (input) => {
236
+ if (input.operation === 'rename') {
237
+ input.command.title.toFixed() // discriminator retains schema correlation
238
+ }
239
+ return input.next()
240
240
  })
241
241
  const commands = new Command<Ctx, typeof operations>(operations, {
242
- middleware: [middleware],
242
+ middleware: [middleware],
243
243
  })
244
244
  ```
245
245
 
@@ -320,9 +320,9 @@ rolled back; these transaction guarantees concern Convex mutation writes.
320
320
 
321
321
  ```ts
322
322
  export const executeAny = commands.exec({
323
- dispatcher: z.object({ kind: z.enum(OPERATION_KINDS) /* from constants.ts */ }),
324
- select: (input) => `documents.${input.kind}` as const,
325
- handler,
323
+ dispatcher: z.object({ kind: z.enum(OPERATION_KINDS) /* from constants.ts */ }),
324
+ select: (input) => `documents.${input.kind}` as const,
325
+ handler,
326
326
  })
327
327
  ```
328
328
 
@@ -99,16 +99,16 @@ another file's function to shorten an import.
99
99
 
100
100
  ### Root files
101
101
 
102
- | File | Contains | Never contains |
103
- |---|---|---|
104
- | `convex.config.ts` | `defineApp()`, one `app.use(...)` per component, `defineApp({ env })` declarations, `export default app` | anything else |
105
- | `schema.ts` | `defineSchema(domainTables)` | table definitions, validators |
106
- | `functions.ts` | the single `createAuthFunctions<DataModel>()` call and its exported constructors | handlers, business policy beyond the injected config |
107
- | `triggers.ts` | `createTriggers()`, `timestamps`/`appendOnly`/`noDelete` registrations, calls to per-entity `register<Entity>Triggers` | trigger *logic* for a specific entity (that lives in the entity) |
108
- | `foundation.ts` | the single `new Foundation(...)`, destructured exports | command definitions |
109
- | `<component>.ts` | `new <Client>(components.<name>)` + minimal admin plumbing | workflow/business definitions |
110
- | `http.ts` | routes: verify signature → parse payload → delegate to a domain function | webhook business logic |
111
- | `crons.ts` | `cronJobs()` declarations targeting `internal.domain.<entity>...` | handler logic |
102
+ | File | Contains | Never contains |
103
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
104
+ | `convex.config.ts` | `defineApp()`, one `app.use(...)` per component, `defineApp({ env })` declarations, `export default app` | anything else |
105
+ | `schema.ts` | `defineSchema(domainTables)` | table definitions, validators |
106
+ | `functions.ts` | the single `createAuthFunctions<DataModel>()` call and its exported constructors | handlers, business policy beyond the injected config |
107
+ | `triggers.ts` | `createTriggers()`, `timestamps`/`appendOnly`/`noDelete` registrations, calls to per-entity `register<Entity>Triggers` | trigger _logic_ for a specific entity (that lives in the entity) |
108
+ | `foundation.ts` | the single `new Foundation(...)`, destructured exports | command definitions |
109
+ | `<component>.ts` | `new <Client>(components.<name>)` + minimal admin plumbing | workflow/business definitions |
110
+ | `http.ts` | routes: verify signature → parse payload → delegate to a domain function | webhook business logic |
111
+ | `crons.ts` | `cronJobs()` declarations targeting `internal.domain.<entity>...` | handler logic |
112
112
 
113
113
  ### `api/<entity>.ts` — the adapter file
114
114
 
@@ -176,7 +176,7 @@ compile. Numeric limits and durations the entity owns also live here.
176
176
  ### `domain/<entity>/commands.ts` — every state change
177
177
 
178
178
  1. The frozen operation registry: `Command.operation({ command, result,
179
- classification, audit })` per operation, keys `'<entity>.<verb>'`.
179
+ classification, audit })` per operation, keys `'<entity>.<verb>'`.
180
180
  2. One `new Command<Ctx, typeof operations>(operations)`.
181
181
  3. Exported executors: `export const execute<Verb><Entity> = commands.exec(...)`.
182
182
  4. Private handler helpers at the bottom.
@@ -197,7 +197,7 @@ Handlers call rules; rules never call handlers.
197
197
 
198
198
  ### `domain/<entity>/shared.ts` — entity-private helpers
199
199
 
200
- Code shared by two or more files *of this entity*. If a second entity needs
200
+ Code shared by two or more files _of this entity_. If a second entity needs
201
201
  it, it moves to `domain/shared/` — never a sibling import.
202
202
 
203
203
  ### `domain/<entity>/actions.ts` — external side effects
@@ -234,25 +234,25 @@ ground: code with one consumer moves back to that consumer.
234
234
 
235
235
  ## 3. Naming grammar
236
236
 
237
- | Thing | Convention | Example shape |
238
- |---|---|---|
239
- | Entity directory | plural camelCase, matches table name | `domain/<entities>/` |
240
- | Table | plural camelCase | `<entities>` |
241
- | Fields | camelCase; foreign keys `<other>Id`; timestamps only via the kit | `ownerId`, `archivedAt` |
242
- | Index | `by_<field>` / `by_<field>_and_<field>`, declaration order, nested paths flattened with `_` | `by_ownerId_and_state_status` |
243
- | Command operation | `<entity>.<verb>` lowercase dotted; must match `/^[A-Za-z][A-Za-z0-9_.-]{0,159}$/` | `<entities>.approve` |
244
- | Error code | `UPPER_SNAKE`, matching `/^[A-Z][A-Z0-9_]{0,95}$/`, defined in one owner | `INVALID_REQUEST_BOUNDARY` |
245
- | Vocabulary tuple | `UPPER_SNAKE_CASE` `as const`; derived type PascalCase singular | `<ENTITY>_STATES` / `<Entity>State` |
246
- | Domain executor | `execute<Verb><Entity>` | `executeApprove<Entity>` |
247
- | Rule predicate | `can<Verb>` / `is<Condition>` | `canApprove`, `isTransitionLegal` |
248
- | Trigger registration | `register<Entity>Triggers(triggers)` | exported from the entity, called in root `triggers.ts` |
249
- | Internal-callback file | `<purpose>_functions.ts`, sibling of `<purpose>.ts` | `approval_functions.ts` |
250
- | zodTable export | camelCase plural, same as table | `export const <entities> = zodTable(...)` |
251
- | DTO schema | `<entity><Purpose>Dto` | `<entity>SummaryDto` |
252
- | Zod schema value | camelCase noun | `commandInput`, `publicDto` |
253
- | Type | PascalCase; no `I`/`T` prefixes | `<Entity>State` |
254
- | Test file | `<subject>.test.ts` in `__tests__/` | `commands.test.ts` |
255
- | Public function path | `api/<entity>:<fn>` — clients never call a root path | — |
237
+ | Thing | Convention | Example shape |
238
+ | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
239
+ | Entity directory | plural camelCase, matches table name | `domain/<entities>/` |
240
+ | Table | plural camelCase | `<entities>` |
241
+ | Fields | camelCase; foreign keys `<other>Id`; timestamps only via the kit | `ownerId`, `archivedAt` |
242
+ | Index | `by_<field>` / `by_<field>_and_<field>`, declaration order, nested paths flattened with `_` | `by_ownerId_and_state_status` |
243
+ | Command operation | `<entity>.<verb>` lowercase dotted; must match `/^[A-Za-z][A-Za-z0-9_.-]{0,159}$/` | `<entities>.approve` |
244
+ | Error code | `UPPER_SNAKE`, matching `/^[A-Z][A-Z0-9_]{0,95}$/`, defined in one owner | `INVALID_REQUEST_BOUNDARY` |
245
+ | Vocabulary tuple | `UPPER_SNAKE_CASE` `as const`; derived type PascalCase singular | `<ENTITY>_STATES` / `<Entity>State` |
246
+ | Domain executor | `execute<Verb><Entity>` | `executeApprove<Entity>` |
247
+ | Rule predicate | `can<Verb>` / `is<Condition>` | `canApprove`, `isTransitionLegal` |
248
+ | Trigger registration | `register<Entity>Triggers(triggers)` | exported from the entity, called in root `triggers.ts` |
249
+ | Internal-callback file | `<purpose>_functions.ts`, sibling of `<purpose>.ts` | `approval_functions.ts` |
250
+ | zodTable export | camelCase plural, same as table | `export const <entities> = zodTable(...)` |
251
+ | DTO schema | `<entity><Purpose>Dto` | `<entity>SummaryDto` |
252
+ | Zod schema value | camelCase noun | `commandInput`, `publicDto` |
253
+ | Type | PascalCase; no `I`/`T` prefixes | `<Entity>State` |
254
+ | Test file | `<subject>.test.ts` in `__tests__/` | `commands.test.ts` |
255
+ | Public function path | `api/<entity>:<fn>` — clients never call a root path | — |
256
256
 
257
257
  The two regexes are enforced at runtime: observability silently drops events
258
258
  whose operation/classification/errorCode don't match. The rest is enforced by
@@ -264,14 +264,14 @@ the architecture tests — a naming rule without a test is a wish.
264
264
 
265
265
  Allowed, per zone (anything not listed is forbidden):
266
266
 
267
- | From | May import |
268
- |---|---|
269
- | `api/<entity>.ts` | `functions.ts`, `domain/<entity>/*`, `domain/shared/*`, zod |
270
- | `domain/<entity>/*` | `functions.ts`, `foundation.ts`, component facades, `domain/shared/*`, own directory, generated `internal`/`api` (references only), kit modules |
271
- | `domain/shared/*` | kit modules, vendor SDKs (this is the ONLY home for vendor clients) |
272
- | root facades | kit modules, `components` from generated api |
273
- | `functions.ts` | `_generated/server` (the only file allowed to) |
274
- | `components/<name>/**` | its own directory + npm deps only — no host imports, no `process.env` |
267
+ | From | May import |
268
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
269
+ | `api/<entity>.ts` | `functions.ts`, `domain/<entity>/*`, `domain/shared/*`, zod |
270
+ | `domain/<entity>/*` | `functions.ts`, `foundation.ts`, component facades, `domain/shared/*`, own directory, generated `internal`/`api` (references only), kit modules |
271
+ | `domain/shared/*` | kit modules, vendor SDKs (this is the ONLY home for vendor clients) |
272
+ | root facades | kit modules, `components` from generated api |
273
+ | `functions.ts` | `_generated/server` (the only file allowed to) |
274
+ | `components/<name>/**` | its own directory + npm deps only — no host imports, no `process.env` |
275
275
 
276
276
  Forbidden everywhere: sibling-entity imports, `api/` from `domain/`,
277
277
  component internals (anything below a component's client facade),
@@ -286,6 +286,6 @@ component internals (anything below a component's client facade),
286
286
  - Zod objects that cross a boundary are `.strict()` — always.
287
287
  - `Object.freeze` for registries and long-lived configuration objects.
288
288
  - Comments state constraints the code can't (`// Optional only for rows
289
- created before <epoch>.`) — never narration of the next line.
289
+ created before <epoch>.`) — never narration of the next line.
290
290
  - One entity concept per file; when a file serves two purposes, split it
291
291
  along the anatomy in §2.
package/docs/crud.md CHANGED
@@ -36,11 +36,11 @@ export const create = authMutation({
36
36
 
37
37
  ## What gets generated
38
38
 
39
- | Operation | Input | Effect | Audit aggregate id |
40
- |---|---|---|---|
41
- | `<table>.create` | `commandInput` (strict) + `enrich` stamps | insert | the new row id |
42
- | `<table>.update` | `{ id, data: commandInput.partial() }` (strict) | patch | the row id |
43
- | `<table>.archive` | `{ id }` | sets `archivedAt` (soft delete ONLY) | the row id |
39
+ | Operation | Input | Effect | Audit aggregate id |
40
+ | ----------------- | ----------------------------------------------- | ------------------------------------ | ------------------ |
41
+ | `<table>.create` | `commandInput` (strict) + `enrich` stamps | insert | the new row id |
42
+ | `<table>.update` | `{ id, data: commandInput.partial() }` (strict) | patch | the row id |
43
+ | `<table>.archive` | `{ id }` | sets `archivedAt` (soft delete ONLY) | the row id |
44
44
 
45
45
  No hard-delete operation is generated — kit doctrine (`noDelete` +
46
46
  `archivedAt`).