cvx-kit 0.1.11 → 0.1.12

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 (46) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -0
  3. package/dist/contracts.d.mts +2 -1
  4. package/dist/contracts.mjs +1 -1
  5. package/dist/index.d.mts +2 -1
  6. package/dist/index.mjs +1 -1
  7. package/dist/modules/contracts/binding.d.mts +14 -0
  8. package/dist/modules/contracts/binding.mjs +2 -0
  9. package/dist/modules/contracts/binding.mjs.map +1 -0
  10. package/dist/modules/contracts/domain.d.mts +59 -0
  11. package/dist/modules/contracts/domain.mjs +2 -0
  12. package/dist/modules/contracts/domain.mjs.map +1 -0
  13. package/dist/modules/effect/command.d.mts +35 -1
  14. package/dist/modules/effect/command.mjs +1 -1
  15. package/dist/modules/effect/command.mjs.map +1 -1
  16. package/dist/modules/effect/crud.d.mts +6 -0
  17. package/dist/modules/effect/domain.d.mts +44 -0
  18. package/dist/modules/effect/domain.mjs +2 -0
  19. package/dist/modules/effect/domain.mjs.map +1 -0
  20. package/dist/modules/effect/operation.d.mts +4 -3
  21. package/dist/modules/effect/operation.mjs.map +1 -1
  22. package/dist/modules/effect/query.d.mts +23 -8
  23. package/dist/modules/effect/query.mjs +1 -1
  24. package/dist/modules/effect/query.mjs.map +1 -1
  25. package/dist/modules/foundation/foundation.d.mts +108 -0
  26. package/dist/modules/foundation/foundation.mjs +2 -0
  27. package/dist/modules/foundation/foundation.mjs.map +1 -0
  28. package/dist/modules/foundation/operation.d.mts +121 -0
  29. package/dist/modules/foundation/operation.mjs +2 -0
  30. package/dist/modules/foundation/operation.mjs.map +1 -0
  31. package/dist/oxlint/ownership.mjs +1 -1
  32. package/dist/oxlint/ownership.mjs.map +1 -1
  33. package/dist/oxlint/reserved-files.d.mts +6 -0
  34. package/dist/oxlint/reserved-files.mjs +2 -0
  35. package/dist/oxlint/reserved-files.mjs.map +1 -0
  36. package/dist/oxlint.d.mts +1 -1
  37. package/dist/oxlint.mjs +1 -1
  38. package/dist/oxlint.mjs.map +1 -1
  39. package/docs/commands-legacy.md +3 -1
  40. package/docs/conventions.md +7 -3
  41. package/docs/domain-contracts.md +115 -0
  42. package/docs/effect.md +2 -0
  43. package/docs/oxlint.md +4 -0
  44. package/package.json +1 -1
  45. package/scripts/test-packed-effect.mjs +12 -0
  46. package/scripts/test-packed-oxlint.mjs +1 -1
@@ -0,0 +1,115 @@
1
+ # Optional domain contracts
2
+
3
+ Declare schemas and command classification once in `contracts.ts`, then attach
4
+ the declaration to a Command or Query registry. Errors belong at the root beside
5
+ commands and queries. Either section and the error contract may be omitted.
6
+
7
+ The regular registry uses `createFoundation` from `cvx-kit`; the Effect registry
8
+ uses `createEffectFoundation` from `cvx-kit/effect`. Both accept the same contract
9
+ and named implementation helpers. Existing inline Effect definitions and the
10
+ older `new Foundation(...)` constructors continue to work.
11
+
12
+ ```ts
13
+ import { z } from 'zod'
14
+ import { createFoundation } from 'cvx-kit'
15
+ import { defineDomainContract } from 'cvx-kit/contracts'
16
+ import { defineErrorContract } from 'cvx-kit/errors'
17
+
18
+ const contract = defineDomainContract({
19
+ errors: defineErrorContract({ Missing: { message: 'Task missing', details: {} } }),
20
+ commands: {
21
+ 'tasks.create': {
22
+ input: z.string().transform(Number),
23
+ result: z.number().transform((count) => ({ count })),
24
+ classification: 'business',
25
+ },
26
+ 'tasks.close': {
27
+ input: z.object({ id: z.string() }),
28
+ result: z.boolean(),
29
+ classification: 'business',
30
+ },
31
+ },
32
+ queries: { 'tasks.get': { input: z.number(), result: z.number() } },
33
+ })
34
+ const { Command, Query } = createFoundation({
35
+ observability: {
36
+ enabled: false,
37
+ classifyError: () => ({ outcome: 'failed', errorCode: 'FAILED' }),
38
+ },
39
+ writeAudit: (_host: { actorId: string }, entry) => {
40
+ console.log(entry.operation)
41
+ },
42
+ })
43
+ const commands = Command({
44
+ contract,
45
+ context: (host: { actorId: string }) => host,
46
+ audit: ({ operation, input, result }, context) => {
47
+ if (operation !== 'tasks.create') return null
48
+ return {
49
+ operation,
50
+ actorId: context.actorId,
51
+ aggregate: { type: 'task', id: String(input) },
52
+ metadata: { count: result.count },
53
+ }
54
+ },
55
+ operations: ({ command }) => ({
56
+ 'tasks.create': command['tasks.create']({ handler: (input) => input + 1 }),
57
+ 'tasks.close': command['tasks.close']({
58
+ handler: (input) => input.id.length > 0,
59
+ audit: () => null,
60
+ }),
61
+ }),
62
+ })
63
+ const queries = Query({
64
+ contract,
65
+ context: (host: { actorId: string }) => host,
66
+ operations: ({ query }) => ({ 'tasks.get': query['tasks.get']({ handler: (input) => input }) }),
67
+ })
68
+ const created = await commands.exec('tasks.create', '2', { actorId: 'actor' })
69
+ const count = await queries.withContext({ actorId: 'actor' }).exec('tasks.get', created.count)
70
+ void count
71
+ ```
72
+
73
+ Each map must implement exactly its declared keys, including maps supplied through
74
+ variables. A helper for one key cannot implement another key. Input, result,
75
+ classification, replayResult, and wire come from the declaration and cannot be
76
+ overridden in the implementation. Construction snapshots operation records and
77
+ map membership without cloning validators or running user callbacks.
78
+
79
+ The handler receives decoded input, returns the result schema's accepted input,
80
+ and execution returns its decoded result. Normal handlers and hooks return values
81
+ or Promises. Effect handlers and hooks may also return Effects; their error and
82
+ service channels remain part of the selected operation's type.
83
+
84
+ For independent wire projections, use the existing `operationContract` helper
85
+ inside a domain declaration. It infers the projector's parameter from the result
86
+ schema; provide both its replayResult and wire fields. A command may instead
87
+ supply only replayResult directly in its domain entry. A query cannot prepare,
88
+ audit, declare aggregates, or declare replayResult.
89
+
90
+ A root audit receives `{ operation, input, result }` and resolved base context.
91
+ Its operation discriminates the corresponding input and result types. An
92
+ operation's audit override receives the existing `{ command, result }` envelope
93
+ and replaces the root audit. Exactly one callback runs after fresh-result
94
+ validation. Returning null skips the write. Replay skips handlers, audits,
95
+ writes, and completion; permission still runs before replay. Each command needs
96
+ a definite audit source. A definite override excludes the root audit's Effect
97
+ channels; an optional override retains channels from both possible callbacks.
98
+
99
+ Inline registries can also use root audit, leaving schemas and classification
100
+ on their operation definitions. The legacy constructor API keeps its existing
101
+ context-first execution signatures; the new regular registry uses input-first
102
+ handlers and `exec(operation, input, host)` like Effect.
103
+
104
+ Pass `contract.errors` explicitly to the existing `createEffectApi({ errors })`
105
+ option. Attaching a contract does not change a globally configured API. The
106
+ original error-contract instance still owns projection: foreign and forged errors
107
+ become UnknownFailure. Wire exposure remains explicit through `.expose(owner, key)`.
108
+
109
+ Enable `cvx/reserved-domain-file-anatomy` together with
110
+ `cvx/domain-file-responsibilities` to enforce reserved filenames. A domain's
111
+ commands.ts and queries.ts each export one genuine bound registry. Contracts are
112
+ declarative; schema.ts may declare several zodTable shapes; table.ts owns their
113
+ topology, and domain/table.ts uses createModule to assemble module maps. The rules
114
+ recognize aliases, namespaces, and the root foundation.ts facade. They do not
115
+ require absent reserved files to be created.
package/docs/effect.md CHANGED
@@ -399,3 +399,5 @@ Optional request context can also be added through the existing Effect API adapt
399
399
  and consume `ctx.commands.exec(name, input)` through the application's existing
400
400
  function helper names. Keep dependencies local to each feature; explicit execution
401
401
  and input-only command handlers remain supported. See [optional context injection](./command-context.md).
402
+
403
+ See [Optional domain contracts](./domain-contracts.md) for shared declarations, named implementations, and registry audit defaults in both Effect and Promise registries.
package/docs/oxlint.md CHANGED
@@ -144,3 +144,7 @@ Shared ownership follows static runtime imports through shared helpers. A shared
144
144
  file reached from only one domain is reported for relocation. Unused code,
145
145
  type-only consumers, and computed dependencies are not treated as proof of shared
146
146
  ownership; their ownership still needs review.
147
+
148
+ ## Reserved domain files
149
+
150
+ Enable `cvx/reserved-domain-file-anatomy` to require exported Command/Query registries, declarative contracts, zodTable shapes, and table topology or createModule assembly. Constructor provenance is traced through imported aliases, namespaces, direct Foundation factories, and the root foundation.ts facade. Pair it with `cvx/domain-file-responsibilities` to reject handlers and database/runtime work in contracts.ts. Backend scope and test/generated exclusions apply; absent files are not required.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cvx-kit",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
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",
@@ -54,6 +54,7 @@ function run(command, args, cwd) {
54
54
  function copyPublicTypeFixture(fixture, filename, publicImport) {
55
55
  const source = readFileSync(join(root, 'test', filename), 'utf8')
56
56
  .replaceAll(publicImport, 'cvx-kit/effect')
57
+ .replaceAll('../src/index', 'cvx-kit')
57
58
  .replaceAll('../src/errors', 'cvx-kit/errors')
58
59
  .replaceAll('../src/contracts', 'cvx-kit/contracts')
59
60
  .replaceAll('../src/idempotency', 'cvx-kit/idempotency')
@@ -126,6 +127,8 @@ function copyExtraTypeFixtures(fixture, paths) {
126
127
  }
127
128
 
128
129
  const legacyRuntime = `
130
+ import { createFoundation } from 'cvx-kit'
131
+ import { defineDomainContract } from 'cvx-kit/contracts'
129
132
  import { strict as assert } from 'node:assert'
130
133
  import { createRequire } from 'node:module'
131
134
  import { Foundation } from 'cvx-kit/components/foundation'
@@ -210,6 +213,10 @@ assert.equal(replay.kind, 'replay')
210
213
  assert.deepEqual(await decodeContract(idempotency.replayResult, replay.result), { value: 'final!' })
211
214
  assert.equal(authorizations, 2)
212
215
  assert.equal(ledger.length, 1)
216
+ const domain = defineDomainContract({ commands: { save: { input: z.string().transform(Number), result: z.number().transform(count => ({ count })), classification: 'business' } } })
217
+ const regular = createFoundation({ observability: { enabled: false, classifyError: () => ({ outcome: 'failed', errorCode: 'FAILED' }) }, writeAudit: () => undefined })
218
+ const domainCommands = regular.Command({ contract: domain, context: host => host, audit: () => null, operations: ({ command }) => ({ save: command.save({ handler: value => value + 1 }) }) })
219
+ assert.deepEqual(await domainCommands.exec('save', '2', {}), { count: 3 })
213
220
  `
214
221
 
215
222
  const legacyTypes = `
@@ -387,6 +394,9 @@ try {
387
394
  writeFileSync(join(fixture, 'legacy-types.ts'), legacyTypes)
388
395
  const typeFiles = [
389
396
  'legacy-types.ts',
397
+ copyPublicTypeFixture(fixture, 'domain-contract-types.ts', '../src/effect'),
398
+ copyPublicTypeFixture(fixture, 'domain-foundation-types.ts', '../src/effect'),
399
+ copyPublicTypeFixture(fixture, 'domain-contract-guide-types.ts', '../src/effect'),
390
400
  copyPublicTypeFixture(fixture, 'operation-neutral-types.ts', '../src/effect'),
391
401
  copyPublicTypeFixture(fixture, 'operation-agent-tools-types.ts', '../src/effect'),
392
402
  ]
@@ -394,6 +404,8 @@ try {
394
404
  if (withEffect) {
395
405
  for (const filename of [
396
406
  'effect-foundation-types.ts',
407
+ 'domain-effect-types.ts',
408
+ 'domain-exactness-types.ts',
397
409
  'effect-api-types.ts',
398
410
  'effect-api-zod-types.ts',
399
411
  'effect-auth-types.ts',
@@ -9,7 +9,7 @@ const root = join(import.meta.dirname, '..')
9
9
  const temporary = mkdtempSync(join(tmpdir(), 'cvx-kit-oxlint-'))
10
10
  const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
11
11
  const ruleNames = Object.keys(rules)
12
- assert.equal(ruleNames.length, 20)
12
+ assert.equal(ruleNames.length, 21)
13
13
 
14
14
  try {
15
15
  run('bun', ['pm', 'pack', '--destination', temporary, '--ignore-scripts'], root)