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.
- package/CHANGELOG.md +19 -0
- package/README.md +1 -0
- package/dist/contracts.d.mts +2 -1
- package/dist/contracts.mjs +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +1 -1
- package/dist/modules/contracts/binding.d.mts +14 -0
- package/dist/modules/contracts/binding.mjs +2 -0
- package/dist/modules/contracts/binding.mjs.map +1 -0
- package/dist/modules/contracts/domain.d.mts +59 -0
- package/dist/modules/contracts/domain.mjs +2 -0
- package/dist/modules/contracts/domain.mjs.map +1 -0
- package/dist/modules/effect/command.d.mts +35 -1
- package/dist/modules/effect/command.mjs +1 -1
- package/dist/modules/effect/command.mjs.map +1 -1
- package/dist/modules/effect/crud.d.mts +6 -0
- package/dist/modules/effect/domain.d.mts +44 -0
- package/dist/modules/effect/domain.mjs +2 -0
- package/dist/modules/effect/domain.mjs.map +1 -0
- package/dist/modules/effect/operation.d.mts +4 -3
- package/dist/modules/effect/operation.mjs.map +1 -1
- package/dist/modules/effect/query.d.mts +23 -8
- package/dist/modules/effect/query.mjs +1 -1
- package/dist/modules/effect/query.mjs.map +1 -1
- package/dist/modules/foundation/foundation.d.mts +108 -0
- package/dist/modules/foundation/foundation.mjs +2 -0
- package/dist/modules/foundation/foundation.mjs.map +1 -0
- package/dist/modules/foundation/operation.d.mts +121 -0
- package/dist/modules/foundation/operation.mjs +2 -0
- package/dist/modules/foundation/operation.mjs.map +1 -0
- package/dist/oxlint/ownership.mjs +1 -1
- package/dist/oxlint/ownership.mjs.map +1 -1
- package/dist/oxlint/reserved-files.d.mts +6 -0
- package/dist/oxlint/reserved-files.mjs +2 -0
- package/dist/oxlint/reserved-files.mjs.map +1 -0
- package/dist/oxlint.d.mts +1 -1
- package/dist/oxlint.mjs +1 -1
- package/dist/oxlint.mjs.map +1 -1
- package/docs/commands-legacy.md +3 -1
- package/docs/conventions.md +7 -3
- package/docs/domain-contracts.md +115 -0
- package/docs/effect.md +2 -0
- package/docs/oxlint.md +4 -0
- package/package.json +1 -1
- package/scripts/test-packed-effect.mjs +12 -0
- 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.
|
|
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,
|
|
12
|
+
assert.equal(ruleNames.length, 21)
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
run('bun', ['pm', 'pack', '--destination', temporary, '--ignore-scripts'], root)
|