cvx-kit 0.1.2 → 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 +38 -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.d.mts +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 +27 -28
- 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 +17 -8
- package/dist/components/foundation/modules/command/command.mjs +1 -1
- 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 +74 -0
- package/dist/components/foundation/operation.mjs +2 -0
- package/dist/components/foundation/operation.mjs.map +1 -0
- 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 +47 -91
- 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 +159 -44
- 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 +74 -7
- package/docs/zod-table.md +70 -45
- package/package.json +41 -28
- package/scripts/convex-config-bridge.mjs +7 -18
- package/scripts/extract-changelog.mjs +1 -1
- package/scripts/smoke-packed-components.mjs +35 -75
- package/scripts/test-packed-ids.mjs +149 -0
- package/scripts/test-packed-oxlint.mjs +121 -0
- package/src/test.ts +9 -17
- package/docs/plans/2026-08-14-001-feat-v0.1.0-feature-set-plan.md +0 -461
- package/docs/plans/2026-08-21-0930-feat-resolve-organization-hook-plan.md +0 -178
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,36 +10,58 @@ 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
|
|
|
28
28
|
## What you get back
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
### Shared ID validation (0.1.3)
|
|
31
|
+
|
|
32
|
+
Install one compatible `convex-helpers` peer (`>=0.1.123 <0.2.0`) alongside
|
|
33
|
+
cvx-kit. ID metadata lives in a helper-instance registry: copying schemas
|
|
34
|
+
between separately installed helper instances can silently turn an ID into
|
|
35
|
+
`v.any()`. An exact nested dependency previously caused that failure even
|
|
36
|
+
when both helper versions were compatible. Fresh npm and Bun consumer tests
|
|
37
|
+
now exercise storage, command, DTO, and nested ID conversion with a different
|
|
38
|
+
compatible host version.
|
|
39
|
+
|
|
40
|
+
Prefer the matching kit exports when deriving validators from kit schemas:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { zid, zodToConvex, convexToZod } from 'cvx-kit/zod-table'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Do not force conflicting nested helper copies with overrides or reuse schema
|
|
47
|
+
objects across separate bundles. The upstream converter cannot recognize an
|
|
48
|
+
unregistered foreign ID and does not throw; peer installation prevents the
|
|
49
|
+
supported package graph from creating that ambiguity. Verify critical ID
|
|
50
|
+
validators retain `kind === 'id'` and the expected `tableName` after installation.
|
|
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 |
|
|
43
65
|
|
|
44
66
|
## Redaction is runtime, not just types
|
|
45
67
|
|
|
@@ -50,13 +72,13 @@ row a public query returns:
|
|
|
50
72
|
|
|
51
73
|
```ts
|
|
52
74
|
export const get = authQuery({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
},
|
|
60
82
|
})
|
|
61
83
|
```
|
|
62
84
|
|
|
@@ -69,7 +91,7 @@ Every `zodTable` bakes in three optional, **server-owned** fields:
|
|
|
69
91
|
- `archivedAt` — soft-delete marker, under application control
|
|
70
92
|
|
|
71
93
|
They are always excluded from `insertSchema`, `updateSchema`, and
|
|
72
|
-
`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
|
|
73
95
|
`publicFields` by default; list them explicitly when a DTO needs them
|
|
74
96
|
(`publicFields: ['title', 'createdAt']`).
|
|
75
97
|
|
|
@@ -83,11 +105,11 @@ vocabulary is yours; `_creationTime` remains available for index ordering.
|
|
|
83
105
|
|
|
84
106
|
Think of the options as three independent allowlists over the same shape:
|
|
85
107
|
|
|
86
|
-
- `serverFields` —
|
|
108
|
+
- `serverFields` — _the server assigns these._ Excluded from `insertSchema`
|
|
87
109
|
and `updateSchema`. Example: `reviewState`, denormalized counters.
|
|
88
|
-
- `commandFields` —
|
|
110
|
+
- `commandFields` — _a command may say these._ The command protocol's input
|
|
89
111
|
is exactly this pick, strict. Everything else must be derived by the handler.
|
|
90
|
-
- `publicFields` —
|
|
112
|
+
- `publicFields` — _a client may see these._ The DTO is exactly this pick;
|
|
91
113
|
may include timestamp fields.
|
|
92
114
|
|
|
93
115
|
All derived objects are `.strict()` — unknown keys are rejected, in both
|
|
@@ -124,10 +146,13 @@ keeps the same "one owner per table" convention while accepting any zod type
|
|
|
124
146
|
as storage:
|
|
125
147
|
|
|
126
148
|
```ts
|
|
127
|
-
const events = zodVariantTable(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
)
|
|
131
156
|
// events.table → defineTable, events.insertSchema → the union itself
|
|
132
157
|
```
|
|
133
158
|
|
|
@@ -143,7 +168,7 @@ the type level:
|
|
|
143
168
|
|
|
144
169
|
```ts
|
|
145
170
|
const assignInput = z.object({
|
|
146
|
-
|
|
171
|
+
documentId: jsonSafeZid('documents'), // LLM sees: string, "Convex document id for table \"documents\""
|
|
147
172
|
})
|
|
148
173
|
```
|
|
149
174
|
|
|
@@ -155,9 +180,9 @@ import { defineSchema } from 'convex/server'
|
|
|
155
180
|
import { documents } from './tables/documents'
|
|
156
181
|
|
|
157
182
|
export default defineSchema({
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
183
|
+
documents: documents.table
|
|
184
|
+
.index('by_owner', ['ownerId'])
|
|
185
|
+
.index('by_owner_archived', ['ownerId', 'archivedAt']),
|
|
161
186
|
})
|
|
162
187
|
```
|
|
163
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,13 +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",
|
|
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",
|
|
95
119
|
"test:package:smoke:bun": "bun scripts/smoke-packed-components.mjs bun",
|
|
96
120
|
"test:package:smoke:npm": "node scripts/smoke-packed-components.mjs npm",
|
|
97
121
|
"docs:sync": "rm -f docs/*.md && cp src/docs/*.md docs/",
|
|
@@ -101,36 +125,25 @@
|
|
|
101
125
|
"release:minor": "npm version minor && git push --follow-tags"
|
|
102
126
|
},
|
|
103
127
|
"dependencies": {
|
|
104
|
-
"@convex-dev/workflow": "0.4.
|
|
105
|
-
"
|
|
106
|
-
"convex-
|
|
107
|
-
},
|
|
108
|
-
"peerDependencies": {
|
|
109
|
-
"convex": "^1.43.0",
|
|
110
|
-
"zod": "^4.0.0"
|
|
128
|
+
"@convex-dev/workflow": "0.4.6",
|
|
129
|
+
"@oxlint/plugins": "1.79.0",
|
|
130
|
+
"convex-audit-log": "^0.2.0"
|
|
111
131
|
},
|
|
112
132
|
"devDependencies": {
|
|
113
133
|
"@edge-runtime/vm": "^5.0.0",
|
|
114
|
-
"
|
|
115
|
-
"convex
|
|
116
|
-
"
|
|
134
|
+
"@types/node": "^26.6.1",
|
|
135
|
+
"convex": "1.45.0",
|
|
136
|
+
"convex-helpers": "0.1.124",
|
|
137
|
+
"convex-test": "^0.0.56",
|
|
138
|
+
"oxlint": "1.79.0",
|
|
139
|
+
"publint": "^0.3.24",
|
|
117
140
|
"typescript": "7.0.2",
|
|
118
|
-
"vite-plus": "0.
|
|
119
|
-
"zod": "4.4
|
|
141
|
+
"vite-plus": "0.3.0",
|
|
142
|
+
"zod": "4.5.4"
|
|
120
143
|
},
|
|
121
|
-
"
|
|
122
|
-
"convex",
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"zod"
|
|
126
|
-
],
|
|
127
|
-
"author": "Angel Lopez",
|
|
128
|
-
"repository": {
|
|
129
|
-
"type": "git",
|
|
130
|
-
"url": "git+https://github.com/ImRLopezAG/cvx-kit.git"
|
|
131
|
-
},
|
|
132
|
-
"homepage": "https://github.com/ImRLopezAG/cvx-kit#readme",
|
|
133
|
-
"bugs": {
|
|
134
|
-
"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"
|
|
135
148
|
}
|
|
136
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'))
|
|
@@ -11,7 +11,7 @@ if (!version) {
|
|
|
11
11
|
}
|
|
12
12
|
const changelog = readFileSync(new URL('../CHANGELOG.md', import.meta.url), 'utf8')
|
|
13
13
|
const pattern = new RegExp(
|
|
14
|
-
String.raw`^## \[${version.replace(/\./g, String.raw`\.`)}\][^\n]*\n([\s\S]*?)(?=^## \[
|
|
14
|
+
String.raw`^## \[${version.replace(/\./g, String.raw`\.`)}\][^\n]*\n([\s\S]*?)(?=^## \[|(?![\s\S]))`,
|
|
15
15
|
'm',
|
|
16
16
|
)
|
|
17
17
|
const match = changelog.match(pattern)
|
|
@@ -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,8 @@ 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
|
|
22
|
-
|
|
23
|
-
).version
|
|
14
|
+
const packageManifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
15
|
+
const packageVersion = packageManifest.version
|
|
24
16
|
const tarball = join(temporaryRoot, `cvx-kit-${packageVersion}.tgz`)
|
|
25
17
|
|
|
26
18
|
function run(command, args, cwd = fixture) {
|
|
@@ -41,10 +33,7 @@ function runStreaming(command, args, cwd = fixture) {
|
|
|
41
33
|
}
|
|
42
34
|
|
|
43
35
|
function runConvex(...args) {
|
|
44
|
-
return run('node', [
|
|
45
|
-
join(fixture, 'node_modules', 'convex', 'bin', 'main.js'),
|
|
46
|
-
...args,
|
|
47
|
-
])
|
|
36
|
+
return run('node', [join(fixture, 'node_modules', 'convex', 'bin', 'main.js'), ...args])
|
|
48
37
|
}
|
|
49
38
|
|
|
50
39
|
function write(relativePath, contents) {
|
|
@@ -58,11 +47,7 @@ function step(label, callback) {
|
|
|
58
47
|
}
|
|
59
48
|
|
|
60
49
|
try {
|
|
61
|
-
run(
|
|
62
|
-
'bun',
|
|
63
|
-
['pm', 'pack', '--destination', temporaryRoot, '--ignore-scripts'],
|
|
64
|
-
root,
|
|
65
|
-
)
|
|
50
|
+
run('bun', ['pm', 'pack', '--destination', temporaryRoot, '--ignore-scripts'], root)
|
|
66
51
|
mkdirSync(join(fixture, 'convex'), { recursive: true })
|
|
67
52
|
write(
|
|
68
53
|
'package.json',
|
|
@@ -72,11 +57,12 @@ try {
|
|
|
72
57
|
type: 'module',
|
|
73
58
|
dependencies: {
|
|
74
59
|
'cvx-kit': `file:${tarball}`,
|
|
75
|
-
convex:
|
|
76
|
-
zod:
|
|
60
|
+
convex: packageManifest.devDependencies.convex,
|
|
61
|
+
zod: packageManifest.devDependencies.zod,
|
|
62
|
+
'convex-helpers': packageManifest.devDependencies['convex-helpers'],
|
|
77
63
|
},
|
|
78
64
|
devDependencies: {
|
|
79
|
-
'vite-plus':
|
|
65
|
+
'vite-plus': packageManifest.devDependencies['vite-plus'],
|
|
80
66
|
},
|
|
81
67
|
},
|
|
82
68
|
null,
|
|
@@ -250,13 +236,8 @@ export const status = query({
|
|
|
250
236
|
copyFileSync(join(root, 'bun.lock'), join(fixture, 'bun.lock'))
|
|
251
237
|
}
|
|
252
238
|
if (installer === 'bun')
|
|
253
|
-
step('install with bun', () =>
|
|
254
|
-
|
|
255
|
-
)
|
|
256
|
-
else
|
|
257
|
-
step('install with npm', () =>
|
|
258
|
-
runStreaming('npm', ['install', '--ignore-scripts']),
|
|
259
|
-
)
|
|
239
|
+
step('install with bun', () => runStreaming('bun', ['install', '--ignore-scripts']))
|
|
240
|
+
else step('install with npm', () => runStreaming('npm', ['install', '--ignore-scripts']))
|
|
260
241
|
if (installer === 'bun')
|
|
261
242
|
step('test helper with bun', () =>
|
|
262
243
|
runStreaming('bunx', ['vp', 'test', 'run', 'packed-test-helper.test.ts']),
|
|
@@ -266,30 +247,18 @@ export const status = query({
|
|
|
266
247
|
runStreaming('npx', ['vp', 'test', 'run', 'packed-test-helper.test.ts']),
|
|
267
248
|
)
|
|
268
249
|
|
|
269
|
-
step('deploy Convex fixture', () =>
|
|
270
|
-
runConvex('dev', '--once', '--typecheck=disable'),
|
|
271
|
-
)
|
|
250
|
+
step('deploy Convex fixture', () => runConvex('dev', '--once', '--typecheck=disable'))
|
|
272
251
|
const health = JSON.parse(runConvex('run', 'smoke:health'))
|
|
273
252
|
if (health.status !== 'ready' || health.schemaVersion !== 1) {
|
|
274
|
-
throw new Error(
|
|
275
|
-
`Unexpected approvals health response: ${JSON.stringify(health)}`,
|
|
276
|
-
)
|
|
253
|
+
throw new Error(`Unexpected approvals health response: ${JSON.stringify(health)}`)
|
|
277
254
|
}
|
|
278
|
-
const foundationHealth = JSON.parse(
|
|
279
|
-
runConvex('run', 'smoke:foundationHealth'),
|
|
280
|
-
)
|
|
255
|
+
const foundationHealth = JSON.parse(runConvex('run', 'smoke:foundationHealth'))
|
|
281
256
|
if (foundationHealth !== 'ready') {
|
|
282
|
-
throw new Error(
|
|
283
|
-
`Unexpected foundation health response: ${JSON.stringify(foundationHealth)}`,
|
|
284
|
-
)
|
|
257
|
+
throw new Error(`Unexpected foundation health response: ${JSON.stringify(foundationHealth)}`)
|
|
285
258
|
}
|
|
286
259
|
const { runId } = JSON.parse(runConvex('run', 'smoke:start'))
|
|
287
260
|
const { runId: secondRunId } = JSON.parse(
|
|
288
|
-
runConvex(
|
|
289
|
-
'run',
|
|
290
|
-
'smoke:start',
|
|
291
|
-
JSON.stringify({ resourceRef: 'packed-component-2' }),
|
|
292
|
-
),
|
|
261
|
+
runConvex('run', 'smoke:start', JSON.stringify({ resourceRef: 'packed-component-2' })),
|
|
293
262
|
)
|
|
294
263
|
const firstPage = JSON.parse(
|
|
295
264
|
runConvex(
|
|
@@ -322,46 +291,37 @@ export const status = query({
|
|
|
322
291
|
secondPage.page[0]._id !== runId ||
|
|
323
292
|
secondPage.page[0]._id === firstPage.page[0]._id
|
|
324
293
|
) {
|
|
325
|
-
throw new Error(
|
|
326
|
-
`Unexpected continuation approval page: ${JSON.stringify(secondPage)}`,
|
|
327
|
-
)
|
|
294
|
+
throw new Error(`Unexpected continuation approval page: ${JSON.stringify(secondPage)}`)
|
|
328
295
|
}
|
|
329
296
|
|
|
330
|
-
|
|
331
|
-
let
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const currentStatus = JSON.parse(
|
|
341
|
-
runConvex('run', 'smoke:status', JSON.stringify({ runId })),
|
|
342
|
-
)
|
|
343
|
-
if (currentStatus.state === 'approved') {
|
|
344
|
-
decision = currentStatus
|
|
345
|
-
break
|
|
346
|
-
}
|
|
347
|
-
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
297
|
+
// start schedules the workflow; a successful start is not decision readiness.
|
|
298
|
+
let readyStatus
|
|
299
|
+
const readyDeadline = Date.now() + 60_000
|
|
300
|
+
while (Date.now() < readyDeadline) {
|
|
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
|
+
) {
|
|
306
|
+
throw new Error(`Approval ended before decision readiness: ${JSON.stringify(readyStatus)}`)
|
|
348
307
|
}
|
|
308
|
+
if (readyStatus.run.currentStepKey === 'releaseDecision') break
|
|
309
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
310
|
+
}
|
|
311
|
+
if (readyStatus?.run.currentStepKey !== 'releaseDecision') {
|
|
312
|
+
throw new Error(`Approval did not become decision-ready: ${JSON.stringify(readyStatus)}`)
|
|
349
313
|
}
|
|
350
|
-
|
|
314
|
+
const decision = JSON.parse(runConvex('run', 'smoke:decide', JSON.stringify({ runId })))
|
|
351
315
|
if (decision.state !== 'approved') {
|
|
352
316
|
throw new Error(`Unexpected decision response: ${JSON.stringify(decision)}`)
|
|
353
317
|
}
|
|
354
|
-
const history = JSON.parse(
|
|
355
|
-
runConvex('run', 'smoke:history', JSON.stringify({ runId })),
|
|
356
|
-
)
|
|
318
|
+
const history = JSON.parse(runConvex('run', 'smoke:history', JSON.stringify({ runId })))
|
|
357
319
|
if (history.length !== 1 || history[0].decision !== 'approved') {
|
|
358
320
|
throw new Error(`Unexpected approval history: ${JSON.stringify(history)}`)
|
|
359
321
|
}
|
|
360
322
|
let status
|
|
361
323
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
362
|
-
status = JSON.parse(
|
|
363
|
-
runConvex('run', 'smoke:status', JSON.stringify({ runId })),
|
|
364
|
-
)
|
|
324
|
+
status = JSON.parse(runConvex('run', 'smoke:status', JSON.stringify({ runId })))
|
|
365
325
|
if (status.execution?.type === 'completed') break
|
|
366
326
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
367
327
|
}
|