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/upgrading.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Upgrading to 0.1.0 — the new way of things
2
2
 
3
+ ## 0.1.5 — consumer Oxlint rules
4
+
5
+ The initial 0.1.4 plugin incorrectly targeted this package's `src/` layout.
6
+ `cvx-kit/oxlint` now targets consuming applications under `convex/`, following
7
+ `conventions.md`. The six existing rule names remain, with consumer semantics;
8
+ nine additional rules cover builders, generated references, domain imports,
9
+ public adapters, facade ownership, vocabularies, reads, and timestamps.
10
+
11
+ Remove `entryPoints` and `packageName` options from your consumer lint config.
12
+ Applications have no barrel entry points inside `convex/`. Every rule now
13
+ accepts only an optional `convexDir`, relative to the linter's working directory
14
+ (default: `convex`). Enable the new rules explicitly; loading the plugin alone
15
+ does not enable them. See `oxlint.md` for coverage and the root README for a
16
+ complete configuration. Repository authoring checks remain local tooling.
17
+
18
+ No runtime API or storage migration is part of this correction.
19
+
20
+ ## 0.1.4
21
+
22
+ This release introduced the optional `cvx-kit/oxlint` export. Its original
23
+ rules targeted library authoring; see the correction above for consumer use.
24
+
25
+ This patch tightens several TypeScript contracts during the anti-slop cleanup:
26
+
27
+ - Query middleware preserves the handler's result type. Return `next()` or
28
+ throw; middleware that fabricates an unrelated result no longer typechecks.
29
+ - `include().execute` and `.paginate` require a transform callback when
30
+ requesting a different output type through an explicit type argument.
31
+ - Audit metadata values must be serializable Convex `Value` values.
32
+ - Webhook targets must be internal mutation references accepting
33
+ `{ eventKey: string, payload: string, source: string }`.
34
+ - CRUD enrichment derives from the table storage schema. Agent tools retain
35
+ each supplied handler's input and result types.
36
+
37
+ Run your application's typecheck after upgrading and update affected callbacks.
38
+ No storage migration is required.
39
+
3
40
  ## 0.1.3
4
41
 
5
42
  Install `convex-helpers` as a direct peer, using one compatible instance:
@@ -135,8 +172,8 @@ New way:
135
172
  import { createStateMachine } from 'cvx-kit/state-machine'
136
173
 
137
174
  const machine = createStateMachine(<ENTITY>_STATES, {
138
- draft: ['published', 'archived'],
139
- published: ['archived'],
175
+ draft: ['published', 'archived'],
176
+ published: ['archived'],
140
177
  })
141
178
  // in a command guard:
142
179
  guard: async (ctx, command) => machine.assert(row.state, command.to)
@@ -151,7 +188,7 @@ dependency-free — it receives the instance):
151
188
  import { rateLimit } from 'cvx-kit/middleware'
152
189
 
153
190
  const commands = new Command(operations, {
154
- middleware: [rateLimit({ limiter, name: '<entities>.write' })],
191
+ middleware: [rateLimit({ limiter, name: '<entities>.write' })],
155
192
  })
156
193
  ```
157
194
 
@@ -167,8 +204,11 @@ import { createWebhookBoundary, recordWebhookEvent, webhookEventsTable } from 'c
167
204
 
168
205
  // schema: webhookEvents: webhookEventsTable().table.index('by_eventKey', ['eventKey'])
169
206
  const boundary = createWebhookBoundary({
170
- verify: async (raw, request) => timingSafeHmacVerify(raw, request), // RAW body, constant-time, secret from env
171
- eventKey: (raw) => { const e = JSON.parse(raw); return `${e.event}:${e.id}:${e.updatedAt}` },
207
+ verify: async (raw, request) => timingSafeHmacVerify(raw, request), // RAW body, constant-time, secret from env
208
+ eventKey: (raw) => {
209
+ const e = JSON.parse(raw)
210
+ return `${e.event}:${e.id}:${e.updatedAt}`
211
+ },
172
212
  })
173
213
  // http.ts routes to boundary.handle(ctx, request, internal.<module>.functions.applyEvent)
174
214
  // applyEvent (systemMutation) calls recordWebhookEvent FIRST — transactional dedup.
@@ -200,8 +240,8 @@ Shape-compatible, dependency-free; checked against `@convex-dev/agent@0.6.x`
200
240
  New way:
201
241
 
202
242
  ```ts
203
- const withVendor = Command.middleware<Ctx, { vendor: Vendor }>(
204
- async ({ context, next }) => next({ context: { vendor: await load(context) } }),
243
+ const withVendor = Command.middleware<Ctx, { vendor: Vendor }>(async ({ context, next }) =>
244
+ next({ context: { vendor: await load(context) } }),
205
245
  )
206
246
  // registries/operations accept typed middleware without casts
207
247
  ```
package/docs/zod-table.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The core idea: an entity's shape is declared **once** as a zod object, and every
4
4
  boundary that shape crosses — storage, insert, update, command input, public
5
- DTO, LLM tool input — is a *derived mask* of that single declaration. There is
5
+ DTO, LLM tool input — is a _derived mask_ of that single declaration. There is
6
6
  no second source of truth to drift.
7
7
 
8
8
  ```ts
@@ -10,18 +10,18 @@ import { z } from 'zod'
10
10
  import { zodTable } from 'cvx-kit/zod-table'
11
11
 
12
12
  export const documents = zodTable(
13
- 'documents',
14
- (id) => ({
15
- title: z.string(),
16
- ownerId: id('users'), // `id` is convex-helpers' zid — typed Id<'users'>
17
- secretNote: z.string(),
18
- reviewState: z.enum(['draft', 'published']),
19
- }),
20
- {
21
- serverFields: ['reviewState'], // excluded from inserts — server assigns it
22
- commandFields: ['title'], // the ONLY fields a command may carry
23
- publicFields: ['title', 'ownerId'], // the DTO allowlist — everything else is redacted
24
- },
13
+ 'documents',
14
+ (id) => ({
15
+ title: z.string(),
16
+ ownerId: id('users'), // `id` is convex-helpers' zid — typed Id<'users'>
17
+ secretNote: z.string(),
18
+ reviewState: z.enum(['draft', 'published']),
19
+ }),
20
+ {
21
+ serverFields: ['reviewState'], // excluded from inserts — server assigns it
22
+ commandFields: ['title'], // the ONLY fields a command may carry
23
+ publicFields: ['title', 'ownerId'], // the DTO allowlist — everything else is redacted
24
+ },
25
25
  )
26
26
  ```
27
27
 
@@ -49,19 +49,19 @@ unregistered foreign ID and does not throw; peer installation prevents the
49
49
  supported package graph from creating that ambiguity. Verify critical ID
50
50
  validators retain `kind === 'id'` and the expected `tableName` after installation.
51
51
 
52
- | Property | What it is | Where you use it |
53
- |---|---|---|
54
- | `table` | `defineTable(...)` from the storage shape | `convex/schema.ts` |
55
- | `storage` | zod object of the raw row (no `_id`/`_creationTime`) | validating rows |
56
- | `schema` | `storage` + `_id` + `_creationTime` | full-document validation |
57
- | `insertSchema` | storage minus timestamps minus `serverFields` | mutation args |
58
- | `updateSchema` | `insertSchema.partial()` | patch mutation args |
59
- | `commandInput` | strict pick of `commandFields` | command protocol inputs |
60
- | `publicDto` | strict pick of `publicFields` | query return validators |
61
- | `toPublicDto(row)` | projects **and re-parses** a row into the DTO | query handlers |
62
- | `insert(omit?)` / `update(omit?)` | schema with per-callsite field omission | specialized mutations |
63
- | `tools.insert` / `tools.update` / `tools.id` | ready-made LLM tool input schemas | agent tool definitions |
64
- | `tableName` | the literal table name | indexes, triggers registration |
52
+ | Property | What it is | Where you use it |
53
+ | -------------------------------------------- | ---------------------------------------------------- | ------------------------------ |
54
+ | `table` | `defineTable(...)` from the storage shape | `convex/schema.ts` |
55
+ | `storage` | zod object of the raw row (no `_id`/`_creationTime`) | validating rows |
56
+ | `schema` | `storage` + `_id` + `_creationTime` | full-document validation |
57
+ | `insertSchema` | storage minus timestamps minus `serverFields` | mutation args |
58
+ | `updateSchema` | `insertSchema.partial()` | patch mutation args |
59
+ | `commandInput` | strict pick of `commandFields` | command protocol inputs |
60
+ | `publicDto` | strict pick of `publicFields` | query return validators |
61
+ | `toPublicDto(row)` | projects **and re-parses** a row into the DTO | query handlers |
62
+ | `insert(omit?)` / `update(omit?)` | schema with per-callsite field omission | specialized mutations |
63
+ | `tools.insert` / `tools.update` / `tools.id` | ready-made LLM tool input schemas | agent tool definitions |
64
+ | `tableName` | the literal table name | indexes, triggers registration |
65
65
 
66
66
  ## Redaction is runtime, not just types
67
67
 
@@ -72,13 +72,13 @@ row a public query returns:
72
72
 
73
73
  ```ts
74
74
  export const get = authQuery({
75
- args: { id: zid('documents') },
76
- returns: documents.publicDto,
77
- handler: async (ctx, { id }) => {
78
- const row = await ctx.db.get(id)
79
- if (!row) throw new KitError({ code: 'NOT_FOUND' })
80
- return documents.toPublicDto(row) // secretNote can never leak
81
- },
75
+ args: { id: zid('documents') },
76
+ returns: documents.publicDto,
77
+ handler: async (ctx, { id }) => {
78
+ const row = await ctx.db.get(id)
79
+ if (!row) throw new KitError({ code: 'NOT_FOUND' })
80
+ return documents.toPublicDto(row) // secretNote can never leak
81
+ },
82
82
  })
83
83
  ```
84
84
 
@@ -91,7 +91,7 @@ Every `zodTable` bakes in three optional, **server-owned** fields:
91
91
  - `archivedAt` — soft-delete marker, under application control
92
92
 
93
93
  They are always excluded from `insertSchema`, `updateSchema`, and
94
- `commandInput` — a client or command can never write them. They are *not* in
94
+ `commandInput` — a client or command can never write them. They are _not_ in
95
95
  `publicFields` by default; list them explicitly when a DTO needs them
96
96
  (`publicFields: ['title', 'createdAt']`).
97
97
 
@@ -105,11 +105,11 @@ vocabulary is yours; `_creationTime` remains available for index ordering.
105
105
 
106
106
  Think of the options as three independent allowlists over the same shape:
107
107
 
108
- - `serverFields` — *the server assigns these.* Excluded from `insertSchema`
108
+ - `serverFields` — _the server assigns these._ Excluded from `insertSchema`
109
109
  and `updateSchema`. Example: `reviewState`, denormalized counters.
110
- - `commandFields` — *a command may say these.* The command protocol's input
110
+ - `commandFields` — _a command may say these._ The command protocol's input
111
111
  is exactly this pick, strict. Everything else must be derived by the handler.
112
- - `publicFields` — *a client may see these.* The DTO is exactly this pick;
112
+ - `publicFields` — _a client may see these._ The DTO is exactly this pick;
113
113
  may include timestamp fields.
114
114
 
115
115
  All derived objects are `.strict()` — unknown keys are rejected, in both
@@ -146,10 +146,13 @@ keeps the same "one owner per table" convention while accepting any zod type
146
146
  as storage:
147
147
 
148
148
  ```ts
149
- const events = zodVariantTable('events', z.discriminatedUnion('kind', [
150
- z.object({ kind: z.literal('created'), by: zid('users') }),
151
- z.object({ kind: z.literal('archived'), reason: z.string() }),
152
- ]))
149
+ const events = zodVariantTable(
150
+ 'events',
151
+ z.discriminatedUnion('kind', [
152
+ z.object({ kind: z.literal('created'), by: zid('users') }),
153
+ z.object({ kind: z.literal('archived'), reason: z.string() }),
154
+ ]),
155
+ )
153
156
  // events.table → defineTable, events.insertSchema → the union itself
154
157
  ```
155
158
 
@@ -165,7 +168,7 @@ the type level:
165
168
 
166
169
  ```ts
167
170
  const assignInput = z.object({
168
- documentId: jsonSafeZid('documents'), // LLM sees: string, "Convex document id for table \"documents\""
171
+ documentId: jsonSafeZid('documents'), // LLM sees: string, "Convex document id for table \"documents\""
169
172
  })
170
173
  ```
171
174
 
@@ -177,9 +180,9 @@ import { defineSchema } from 'convex/server'
177
180
  import { documents } from './tables/documents'
178
181
 
179
182
  export default defineSchema({
180
- documents: documents.table
181
- .index('by_owner', ['ownerId'])
182
- .index('by_owner_archived', ['ownerId', 'archivedAt']),
183
+ documents: documents.table
184
+ .index('by_owner', ['ownerId'])
185
+ .index('by_owner_archived', ['ownerId', 'archivedAt']),
183
186
  })
184
187
  ```
185
188
 
package/package.json CHANGED
@@ -1,9 +1,24 @@
1
1
  {
2
2
  "name": "cvx-kit",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Reusable Convex application kit: foundation kernels, approvals component, zod table boundaries, WorkOS-backed auth function constructors, and audited command protocol.",
5
- "type": "module",
5
+ "keywords": [
6
+ "component",
7
+ "convex",
8
+ "workos",
9
+ "zod"
10
+ ],
11
+ "homepage": "https://github.com/ImRLopezAG/cvx-kit#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/ImRLopezAG/cvx-kit/issues"
14
+ },
6
15
  "license": "MIT",
16
+ "author": "Angel Lopez",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/ImRLopezAG/cvx-kit.git"
20
+ },
21
+ "type": "module",
7
22
  "sideEffects": false,
8
23
  "exports": {
9
24
  "./package.json": "./package.json",
@@ -85,14 +100,22 @@
85
100
  },
86
101
  "./components/approvals/_generated/component": {
87
102
  "types": "./dist/components/approvals/_generated/component.d.mts"
103
+ },
104
+ "./oxlint": {
105
+ "types": "./dist/oxlint.d.mts",
106
+ "import": "./dist/oxlint.mjs"
88
107
  }
89
108
  },
90
109
  "scripts": {
110
+ "lint": "vp lint",
111
+ "check": "vp check",
112
+ "test:lint": "vp test run tools/oxlint/cvx/index.test.mjs",
91
113
  "build": "vp pack && node scripts/convex-config-bridge.mjs && publint",
92
114
  "typecheck": "tsc --noEmit",
93
115
  "test": "vp test run --exclude test/package-contents.test.mjs",
94
116
  "test:package": "bun run build && vp test run test/package-contents.test.mjs",
95
117
  "test:package:ids": "bun run build && node scripts/test-packed-ids.mjs",
118
+ "test:package:oxlint": "bun run build && node scripts/test-packed-oxlint.mjs",
96
119
  "test:package:smoke:bun": "bun scripts/smoke-packed-components.mjs bun",
97
120
  "test:package:smoke:npm": "node scripts/smoke-packed-components.mjs npm",
98
121
  "docs:sync": "rm -f docs/*.md && cp src/docs/*.md docs/",
@@ -103,36 +126,24 @@
103
126
  },
104
127
  "dependencies": {
105
128
  "@convex-dev/workflow": "0.4.6",
129
+ "@oxlint/plugins": "1.79.0",
106
130
  "convex-audit-log": "^0.2.0"
107
131
  },
108
- "peerDependencies": {
109
- "convex-helpers": ">=0.1.123 <0.2.0",
110
- "convex": "^1.45.0",
111
- "zod": "^4.5.4"
112
- },
113
132
  "devDependencies": {
114
- "convex-helpers": "0.1.124",
115
133
  "@edge-runtime/vm": "^5.0.0",
134
+ "@types/node": "^26.6.1",
116
135
  "convex": "1.45.0",
136
+ "convex-helpers": "0.1.124",
117
137
  "convex-test": "^0.0.56",
138
+ "oxlint": "1.79.0",
118
139
  "publint": "^0.3.24",
119
140
  "typescript": "7.0.2",
120
141
  "vite-plus": "0.3.0",
121
142
  "zod": "4.5.4"
122
143
  },
123
- "keywords": [
124
- "convex",
125
- "component",
126
- "workos",
127
- "zod"
128
- ],
129
- "author": "Angel Lopez",
130
- "repository": {
131
- "type": "git",
132
- "url": "git+https://github.com/ImRLopezAG/cvx-kit.git"
133
- },
134
- "homepage": "https://github.com/ImRLopezAG/cvx-kit#readme",
135
- "bugs": {
136
- "url": "https://github.com/ImRLopezAG/cvx-kit/issues"
144
+ "peerDependencies": {
145
+ "convex": "^1.45.0",
146
+ "convex-helpers": ">=0.1.123 <0.2.0",
147
+ "zod": "^4.5.4"
137
148
  }
138
149
  }
@@ -2,13 +2,7 @@
2
2
  // is literally named `convex.config.js` (see DEFINITION_FILENAME_JS in the
3
3
  // convex CLI). vp pack emits .mjs, so this postbuild step materializes the
4
4
  // compiled definition as convex.config.js in each component's dist directory.
5
- import {
6
- readFileSync,
7
- readdirSync,
8
- renameSync,
9
- rmSync,
10
- writeFileSync,
11
- } from 'node:fs'
5
+ import { readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'
12
6
  import { dirname, join } from 'node:path'
13
7
  import { fileURLToPath } from 'node:url'
14
8
 
@@ -16,8 +10,10 @@ const root = join(dirname(fileURLToPath(import.meta.url)), '..')
16
10
 
17
11
  for (const component of ['foundation', 'approvals']) {
18
12
  const directory = join(root, 'dist', 'components', component)
19
- const config = readFileSync(join(directory, 'convex.config.mjs'), 'utf8')
20
- .replace(/\/\/# sourceMappingURL=.*\n?/, '')
13
+ const config = readFileSync(join(directory, 'convex.config.mjs'), 'utf8').replace(
14
+ /\/\/# sourceMappingURL=.*\n?/,
15
+ '',
16
+ )
21
17
  writeFileSync(join(directory, 'convex.config.js'), config)
22
18
 
23
19
  // Component implementations are discovered from schema.ts/schema.js only.
@@ -36,17 +32,10 @@ for (const component of ['foundation', 'approvals']) {
36
32
  if (bridged !== source) writeFileSync(path, bridged)
37
33
  }
38
34
  renameSync(join(directory, 'schema.d.mts'), join(directory, 'schema.d.ts'))
39
- const dataModelDeclaration = join(
40
- directory,
41
- '_generated',
42
- 'dataModel.d.mts',
43
- )
35
+ const dataModelDeclaration = join(directory, '_generated', 'dataModel.d.mts')
44
36
  writeFileSync(
45
37
  dataModelDeclaration,
46
- readFileSync(dataModelDeclaration, 'utf8').replace(
47
- '../schema.mjs',
48
- '../schema.js',
49
- ),
38
+ readFileSync(dataModelDeclaration, 'utf8').replace('../schema.mjs', '../schema.js'),
50
39
  )
51
40
  rmSync(join(directory, 'schema.mjs'))
52
41
  rmSync(join(directory, 'schema.mjs.map'))
@@ -1,12 +1,5 @@
1
1
  import { execFileSync } from 'node:child_process'
2
- import {
3
- copyFileSync,
4
- mkdirSync,
5
- mkdtempSync,
6
- readFileSync,
7
- rmSync,
8
- writeFileSync,
9
- } from 'node:fs'
2
+ import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
10
3
  import { tmpdir } from 'node:os'
11
4
  import { join } from 'node:path'
12
5
 
@@ -18,9 +11,7 @@ if (installer !== 'bun' && installer !== 'npm') {
18
11
  const root = join(import.meta.dirname, '..')
19
12
  const temporaryRoot = mkdtempSync(join(tmpdir(), 'cvx-kit-smoke-'))
20
13
  const fixture = join(temporaryRoot, 'fixture')
21
- const packageManifest = JSON.parse(
22
- readFileSync(join(root, 'package.json'), 'utf8'),
23
- )
14
+ const packageManifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
24
15
  const packageVersion = packageManifest.version
25
16
  const tarball = join(temporaryRoot, `cvx-kit-${packageVersion}.tgz`)
26
17
 
@@ -42,10 +33,7 @@ function runStreaming(command, args, cwd = fixture) {
42
33
  }
43
34
 
44
35
  function runConvex(...args) {
45
- return run('node', [
46
- join(fixture, 'node_modules', 'convex', 'bin', 'main.js'),
47
- ...args,
48
- ])
36
+ return run('node', [join(fixture, 'node_modules', 'convex', 'bin', 'main.js'), ...args])
49
37
  }
50
38
 
51
39
  function write(relativePath, contents) {
@@ -59,11 +47,7 @@ function step(label, callback) {
59
47
  }
60
48
 
61
49
  try {
62
- run(
63
- 'bun',
64
- ['pm', 'pack', '--destination', temporaryRoot, '--ignore-scripts'],
65
- root,
66
- )
50
+ run('bun', ['pm', 'pack', '--destination', temporaryRoot, '--ignore-scripts'], root)
67
51
  mkdirSync(join(fixture, 'convex'), { recursive: true })
68
52
  write(
69
53
  'package.json',
@@ -252,13 +236,8 @@ export const status = query({
252
236
  copyFileSync(join(root, 'bun.lock'), join(fixture, 'bun.lock'))
253
237
  }
254
238
  if (installer === 'bun')
255
- step('install with bun', () =>
256
- runStreaming('bun', ['install', '--ignore-scripts']),
257
- )
258
- else
259
- step('install with npm', () =>
260
- runStreaming('npm', ['install', '--ignore-scripts']),
261
- )
239
+ step('install with bun', () => runStreaming('bun', ['install', '--ignore-scripts']))
240
+ else step('install with npm', () => runStreaming('npm', ['install', '--ignore-scripts']))
262
241
  if (installer === 'bun')
263
242
  step('test helper with bun', () =>
264
243
  runStreaming('bunx', ['vp', 'test', 'run', 'packed-test-helper.test.ts']),
@@ -268,30 +247,18 @@ export const status = query({
268
247
  runStreaming('npx', ['vp', 'test', 'run', 'packed-test-helper.test.ts']),
269
248
  )
270
249
 
271
- step('deploy Convex fixture', () =>
272
- runConvex('dev', '--once', '--typecheck=disable'),
273
- )
250
+ step('deploy Convex fixture', () => runConvex('dev', '--once', '--typecheck=disable'))
274
251
  const health = JSON.parse(runConvex('run', 'smoke:health'))
275
252
  if (health.status !== 'ready' || health.schemaVersion !== 1) {
276
- throw new Error(
277
- `Unexpected approvals health response: ${JSON.stringify(health)}`,
278
- )
253
+ throw new Error(`Unexpected approvals health response: ${JSON.stringify(health)}`)
279
254
  }
280
- const foundationHealth = JSON.parse(
281
- runConvex('run', 'smoke:foundationHealth'),
282
- )
255
+ const foundationHealth = JSON.parse(runConvex('run', 'smoke:foundationHealth'))
283
256
  if (foundationHealth !== 'ready') {
284
- throw new Error(
285
- `Unexpected foundation health response: ${JSON.stringify(foundationHealth)}`,
286
- )
257
+ throw new Error(`Unexpected foundation health response: ${JSON.stringify(foundationHealth)}`)
287
258
  }
288
259
  const { runId } = JSON.parse(runConvex('run', 'smoke:start'))
289
260
  const { runId: secondRunId } = JSON.parse(
290
- runConvex(
291
- 'run',
292
- 'smoke:start',
293
- JSON.stringify({ resourceRef: 'packed-component-2' }),
294
- ),
261
+ runConvex('run', 'smoke:start', JSON.stringify({ resourceRef: 'packed-component-2' })),
295
262
  )
296
263
  const firstPage = JSON.parse(
297
264
  runConvex(
@@ -324,20 +291,18 @@ export const status = query({
324
291
  secondPage.page[0]._id !== runId ||
325
292
  secondPage.page[0]._id === firstPage.page[0]._id
326
293
  ) {
327
- throw new Error(
328
- `Unexpected continuation approval page: ${JSON.stringify(secondPage)}`,
329
- )
294
+ throw new Error(`Unexpected continuation approval page: ${JSON.stringify(secondPage)}`)
330
295
  }
331
296
 
332
297
  // start schedules the workflow; a successful start is not decision readiness.
333
298
  let readyStatus
334
299
  const readyDeadline = Date.now() + 60_000
335
300
  while (Date.now() < readyDeadline) {
336
- readyStatus = JSON.parse(
337
- runConvex('run', 'smoke:status', JSON.stringify({ runId })),
338
- )
339
- if (readyStatus?.run.state !== 'pending' ||
340
- ['failed', 'canceled', 'completed'].includes(readyStatus?.execution?.type)) {
301
+ readyStatus = JSON.parse(runConvex('run', 'smoke:status', JSON.stringify({ runId })))
302
+ if (
303
+ readyStatus?.run.state !== 'pending' ||
304
+ ['failed', 'canceled', 'completed'].includes(readyStatus?.execution?.type)
305
+ ) {
341
306
  throw new Error(`Approval ended before decision readiness: ${JSON.stringify(readyStatus)}`)
342
307
  }
343
308
  if (readyStatus.run.currentStepKey === 'releaseDecision') break
@@ -346,23 +311,17 @@ export const status = query({
346
311
  if (readyStatus?.run.currentStepKey !== 'releaseDecision') {
347
312
  throw new Error(`Approval did not become decision-ready: ${JSON.stringify(readyStatus)}`)
348
313
  }
349
- const decision = JSON.parse(
350
- runConvex('run', 'smoke:decide', JSON.stringify({ runId })),
351
- )
314
+ const decision = JSON.parse(runConvex('run', 'smoke:decide', JSON.stringify({ runId })))
352
315
  if (decision.state !== 'approved') {
353
316
  throw new Error(`Unexpected decision response: ${JSON.stringify(decision)}`)
354
317
  }
355
- const history = JSON.parse(
356
- runConvex('run', 'smoke:history', JSON.stringify({ runId })),
357
- )
318
+ const history = JSON.parse(runConvex('run', 'smoke:history', JSON.stringify({ runId })))
358
319
  if (history.length !== 1 || history[0].decision !== 'approved') {
359
320
  throw new Error(`Unexpected approval history: ${JSON.stringify(history)}`)
360
321
  }
361
322
  let status
362
323
  for (let attempt = 0; attempt < 20; attempt += 1) {
363
- status = JSON.parse(
364
- runConvex('run', 'smoke:status', JSON.stringify({ runId })),
365
- )
324
+ status = JSON.parse(runConvex('run', 'smoke:status', JSON.stringify({ runId })))
366
325
  if (status.execution?.type === 'completed') break
367
326
  await new Promise((resolve) => setTimeout(resolve, 500))
368
327
  }
@@ -1,19 +1,11 @@
1
1
  import { execFileSync } from 'node:child_process'
2
- import {
3
- mkdtempSync,
4
- mkdirSync,
5
- readFileSync,
6
- rmSync,
7
- writeFileSync,
8
- } from 'node:fs'
2
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
9
3
  import { tmpdir } from 'node:os'
10
4
  import { join } from 'node:path'
11
5
 
12
6
  const root = join(import.meta.dirname, '..')
13
7
  const temporary = mkdtempSync(join(tmpdir(), 'cvx-kit-ids-'))
14
- const manifest = JSON.parse(
15
- readFileSync(join(root, 'package.json'), 'utf8'),
16
- )
8
+ const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
17
9
  function run(command, args, cwd) {
18
10
  return execFileSync(command, args, {
19
11
  cwd,
@@ -22,11 +14,7 @@ function run(command, args, cwd) {
22
14
  })
23
15
  }
24
16
  try {
25
- run(
26
- 'bun',
27
- ['pm', 'pack', '--destination', temporary, '--ignore-scripts'],
28
- root,
29
- )
17
+ run('bun', ['pm', 'pack', '--destination', temporary, '--ignore-scripts'], root)
30
18
  for (const installer of ['npm', 'bun']) {
31
19
  const fixture = join(temporary, installer)
32
20
  mkdirSync(fixture)
@@ -75,21 +63,21 @@ for (const validator of [
75
63
  `,
76
64
  )
77
65
  run('node', ['probe.mjs'], fixture)
78
- for (const testFile of [
79
- 'command.test.ts',
80
- 'command-lifecycle.test.ts',
81
- ]) {
66
+ for (const testFile of ['command.test.ts', 'command-lifecycle.test.ts']) {
82
67
  const source = readFileSync(join(root, 'test', testFile), 'utf8')
83
- .replace(
84
- '../src/components/foundation/client',
85
- 'cvx-kit/components/foundation',
86
- )
68
+ .replace('../src/components/foundation/client', 'cvx-kit/components/foundation')
87
69
  .replace(
88
70
  "const modules = import.meta.glob('./fixture/**/*.ts')",
89
71
  "const modules = { './_generated/server.ts': async () => ({}) }",
90
72
  )
91
73
  writeFileSync(join(fixture, testFile), source)
92
74
  }
75
+ for (const testFile of ['zod-table.test.ts', 'agent-tools.test.ts']) {
76
+ const source = readFileSync(join(root, 'test', testFile), 'utf8')
77
+ .replaceAll('../src/zod-table', 'cvx-kit/zod-table')
78
+ .replaceAll('../src/agent-tools', 'cvx-kit/agent-tools')
79
+ writeFileSync(join(fixture, testFile), source)
80
+ }
93
81
  writeFileSync(
94
82
  join(fixture, 'command-types.ts'),
95
83
  readFileSync(join(root, 'test/command-types.ts'), 'utf8').replace(
@@ -134,6 +122,8 @@ it('registers concrete and generic backends from the packed package', () => {
134
122
  'vite/client',
135
123
  'command-types.ts',
136
124
  'registration.test.ts',
125
+ 'zod-table.test.ts',
126
+ 'agent-tools.test.ts',
137
127
  ],
138
128
  fixture,
139
129
  )
@@ -145,11 +135,13 @@ it('registers concrete and generic backends from the packed package', () => {
145
135
  'registration.test.ts',
146
136
  'command.test.ts',
147
137
  'command-lifecycle.test.ts',
138
+ 'zod-table.test.ts',
139
+ 'agent-tools.test.ts',
148
140
  ],
149
141
  fixture,
150
142
  )
151
143
  console.log(
152
- `${installer}: packed IDs, strict declarations, registration, and command lifecycle pass with host convex-helpers 0.1.123`,
144
+ `${installer}: packed IDs, table/tool types, registration, and command lifecycle pass with host convex-helpers 0.1.123`,
153
145
  )
154
146
  }
155
147
  } finally {