cvx-kit 0.1.3 → 0.1.4
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 +20 -0
- package/README.md +69 -24
- package/dist/agent-tools.d.mts +28 -12
- package/dist/agent-tools.mjs +1 -1
- package/dist/agent-tools.mjs.map +1 -1
- package/dist/auth.d.mts +4 -2
- package/dist/auth.mjs +1 -1
- package/dist/auth.mjs.map +1 -1
- package/dist/components/approvals/_generated/api.d.mts +1 -1
- package/dist/components/approvals/audit.mjs +1 -1
- package/dist/components/approvals/audit.mjs.map +1 -1
- package/dist/components/approvals/client.d.mts +1 -1
- package/dist/components/approvals/client.mjs +1 -1
- package/dist/components/approvals/client.mjs.map +1 -1
- package/dist/components/approvals/constants.mjs.map +1 -1
- package/dist/components/approvals/decisions.d.mts +6 -7
- package/dist/components/approvals/decisions.mjs +1 -1
- package/dist/components/approvals/decisions.mjs.map +1 -1
- package/dist/components/approvals/functions.mjs +1 -1
- package/dist/components/approvals/functions.mjs.map +1 -1
- package/dist/components/approvals/health.mjs.map +1 -1
- package/dist/components/approvals/requests.mjs.map +1 -1
- package/dist/components/approvals/validators.mjs.map +1 -1
- package/dist/components/approvals/workflow.mjs +1 -1
- package/dist/components/approvals/workflow.mjs.map +1 -1
- package/dist/components/approvals/workflow_steps.d.mts +3 -3
- package/dist/components/approvals/workflow_steps.mjs +1 -1
- package/dist/components/approvals/workflow_steps.mjs.map +1 -1
- package/dist/components/foundation/client.d.mts +17 -24
- package/dist/components/foundation/client.mjs +1 -1
- package/dist/components/foundation/client.mjs.map +1 -1
- package/dist/components/foundation/modules/command/command.d.mts +4 -4
- package/dist/components/foundation/modules/command/command.mjs.map +1 -1
- package/dist/components/foundation/modules/observability/observability.d.mts +1 -1
- package/dist/components/foundation/modules/observability/observability.mjs +1 -1
- package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
- package/dist/components/foundation/modules/query/query.d.mts +8 -8
- package/dist/components/foundation/modules/query/query.mjs +1 -1
- package/dist/components/foundation/modules/query/query.mjs.map +1 -1
- package/dist/components/foundation/operation.d.mts +1 -1
- package/dist/components/foundation/operation.mjs.map +1 -1
- package/dist/components/foundation/result.d.mts +1 -1
- package/dist/components/foundation/result.mjs.map +1 -1
- package/dist/components/foundation/telemetry.mjs +1 -1
- package/dist/components/foundation/telemetry.mjs.map +1 -1
- package/dist/crud.d.mts +177 -16
- package/dist/crud.mjs +1 -1
- package/dist/crud.mjs.map +1 -1
- package/dist/middleware.d.mts +7 -7
- package/dist/middleware.mjs +1 -1
- package/dist/middleware.mjs.map +1 -1
- package/dist/oxlint.d.mts +9 -0
- package/dist/oxlint.mjs +2 -0
- package/dist/oxlint.mjs.map +1 -0
- package/dist/tenancy.mjs +1 -1
- package/dist/tenancy.mjs.map +1 -1
- package/dist/triggers.mjs +1 -1
- package/dist/triggers.mjs.map +1 -1
- package/dist/webhooks.d.mts +17 -106
- package/dist/webhooks.mjs.map +1 -1
- package/dist/zod-table.d.mts +46 -90
- package/dist/zod-table.mjs +1 -1
- package/dist/zod-table.mjs.map +1 -1
- package/docs/approvals.md +57 -56
- package/docs/architecture.md +1 -1
- package/docs/auth.md +57 -33
- package/docs/commands.md +53 -53
- package/docs/conventions.md +40 -40
- package/docs/crud.md +5 -5
- package/docs/llms.md +117 -71
- package/docs/maintainability.md +4 -4
- package/docs/migration.md +33 -33
- package/docs/tenancy.md +5 -8
- package/docs/triggers.md +4 -2
- package/docs/upgrading.md +32 -7
- package/docs/zod-table.md +48 -45
- package/package.json +33 -22
- package/scripts/convex-config-bridge.mjs +7 -18
- package/scripts/smoke-packed-components.mjs +20 -61
- package/scripts/test-packed-ids.mjs +16 -24
- package/scripts/test-packed-oxlint.mjs +121 -0
- package/src/test.ts +8 -17
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
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
53
|
-
|
|
54
|
-
| `table`
|
|
55
|
-
| `storage`
|
|
56
|
-
| `schema`
|
|
57
|
-
| `insertSchema`
|
|
58
|
-
| `updateSchema`
|
|
59
|
-
| `commandInput`
|
|
60
|
-
| `publicDto`
|
|
61
|
-
| `toPublicDto(row)`
|
|
62
|
-
| `insert(omit?)` / `update(omit?)`
|
|
63
|
-
| `tools.insert` / `tools.update` / `tools.id` | ready-made LLM tool input schemas
|
|
64
|
-
| `tableName`
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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` —
|
|
108
|
+
- `serverFields` — _the server assigns these._ Excluded from `insertSchema`
|
|
109
109
|
and `updateSchema`. Example: `reviewState`, denormalized counters.
|
|
110
|
-
- `commandFields` —
|
|
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` —
|
|
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(
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
"version": "0.1.4",
|
|
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
|
+
"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
|
-
"
|
|
124
|
-
"convex",
|
|
125
|
-
"
|
|
126
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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,
|
|
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 {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { strict as assert } from 'node:assert'
|
|
2
|
+
import { execFileSync, spawnSync } from 'node:child_process'
|
|
3
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { rules } from '../dist/oxlint.mjs'
|
|
7
|
+
|
|
8
|
+
const root = join(import.meta.dirname, '..')
|
|
9
|
+
const temporary = mkdtempSync(join(tmpdir(), 'cvx-kit-oxlint-'))
|
|
10
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
11
|
+
const ruleNames = Object.keys(rules)
|
|
12
|
+
assert.equal(ruleNames.length, 6)
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
run('bun', ['pm', 'pack', '--destination', temporary, '--ignore-scripts'], root)
|
|
16
|
+
for (const installer of ['npm', 'bun']) {
|
|
17
|
+
const fixture = join(temporary, installer)
|
|
18
|
+
mkdirSync(join(fixture, 'src/components/example'), { recursive: true })
|
|
19
|
+
writeFileSync(
|
|
20
|
+
join(fixture, 'package.json'),
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
private: true,
|
|
23
|
+
type: 'module',
|
|
24
|
+
dependencies: {
|
|
25
|
+
'cvx-kit': `file:${join(temporary, `cvx-kit-${manifest.version}.tgz`)}`,
|
|
26
|
+
convex: manifest.devDependencies.convex,
|
|
27
|
+
'convex-helpers': manifest.devDependencies['convex-helpers'],
|
|
28
|
+
zod: manifest.devDependencies.zod,
|
|
29
|
+
},
|
|
30
|
+
devDependencies: { oxlint: manifest.devDependencies.oxlint },
|
|
31
|
+
}),
|
|
32
|
+
)
|
|
33
|
+
run(installer, ['install', '--ignore-scripts'], fixture)
|
|
34
|
+
writeFileSync(
|
|
35
|
+
join(fixture, '.oxlintrc.json'),
|
|
36
|
+
JSON.stringify({
|
|
37
|
+
jsPlugins: [{ name: 'cvx', specifier: 'cvx-kit/oxlint' }],
|
|
38
|
+
rules: {
|
|
39
|
+
...Object.fromEntries(ruleNames.map((name) => [`cvx/${name}`, 'error'])),
|
|
40
|
+
'cvx/no-internal-reexports': ['error', { entryPoints: ['src/public.ts'] }],
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
writeFileSync(
|
|
45
|
+
join(fixture, 'src/public.ts'),
|
|
46
|
+
"export { run } from './components/example/client'\n",
|
|
47
|
+
)
|
|
48
|
+
writeFileSync(
|
|
49
|
+
join(fixture, 'src/components/example/client.ts'),
|
|
50
|
+
'export function run() { return 1 }\n',
|
|
51
|
+
)
|
|
52
|
+
const oxlint = join(fixture, 'node_modules/.bin/oxlint')
|
|
53
|
+
run(oxlint, ['--config', '.oxlintrc.json', 'src'], fixture)
|
|
54
|
+
writeFileSync(
|
|
55
|
+
join(fixture, 'src/components/example/bad.ts'),
|
|
56
|
+
`
|
|
57
|
+
import { defineTable } from 'convex/server'
|
|
58
|
+
import { host } from '../../host'
|
|
59
|
+
function helper() { return process.env.SECRET }
|
|
60
|
+
const privateHelper = () => helper()
|
|
61
|
+
export const table = defineTable({})
|
|
62
|
+
export { host }
|
|
63
|
+
export const value = privateHelper()
|
|
64
|
+
`,
|
|
65
|
+
)
|
|
66
|
+
writeFileSync(
|
|
67
|
+
join(fixture, 'src/host.ts'),
|
|
68
|
+
"export const host = 1\nimport './components/example/bad'\n",
|
|
69
|
+
)
|
|
70
|
+
const invalid = spawnSync(oxlint, ['--config', '.oxlintrc.json', '--format', 'json', 'src'], {
|
|
71
|
+
cwd: fixture,
|
|
72
|
+
encoding: 'utf8',
|
|
73
|
+
})
|
|
74
|
+
assert.ifError(invalid.error)
|
|
75
|
+
assert.equal(invalid.status, 1, invalid.stderr)
|
|
76
|
+
const diagnostics = JSON.parse(invalid.stdout).diagnostics
|
|
77
|
+
for (const name of ruleNames) {
|
|
78
|
+
assert.ok(
|
|
79
|
+
diagnostics.some((item) => item.code === `cvx(${name})`),
|
|
80
|
+
`${installer}: missing ${name}: ${invalid.stdout}`,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
writeFileSync(
|
|
84
|
+
join(fixture, 'plugin-types.ts'),
|
|
85
|
+
`
|
|
86
|
+
import plugin, { rules, type RuleName } from 'cvx-kit/oxlint'
|
|
87
|
+
import type { Plugin, Rule } from '@oxlint/plugins'
|
|
88
|
+
const compatible: Plugin = plugin
|
|
89
|
+
const name: RuleName = 'component-boundaries'
|
|
90
|
+
const rule: Rule = rules[name]
|
|
91
|
+
void compatible
|
|
92
|
+
void rule
|
|
93
|
+
`,
|
|
94
|
+
)
|
|
95
|
+
run(
|
|
96
|
+
join(root, 'node_modules/.bin/tsc'),
|
|
97
|
+
[
|
|
98
|
+
'--noEmit',
|
|
99
|
+
'--strict',
|
|
100
|
+
'--skipLibCheck',
|
|
101
|
+
'--target',
|
|
102
|
+
'es2025',
|
|
103
|
+
'--module',
|
|
104
|
+
'esnext',
|
|
105
|
+
'--moduleResolution',
|
|
106
|
+
'bundler',
|
|
107
|
+
'plugin-types.ts',
|
|
108
|
+
],
|
|
109
|
+
fixture,
|
|
110
|
+
)
|
|
111
|
+
console.log(
|
|
112
|
+
`${installer}: packed Oxlint plugin loads, all six rules report, and declarations typecheck`,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
} finally {
|
|
116
|
+
rmSync(temporary, { recursive: true, force: true })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function run(command, args, cwd) {
|
|
120
|
+
return execFileSync(command, args, { cwd, encoding: 'utf8', stdio: 'pipe' })
|
|
121
|
+
}
|