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.
Files changed (88) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +69 -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.d.mts +1 -1
  25. package/dist/components/approvals/workflow.mjs +1 -1
  26. package/dist/components/approvals/workflow.mjs.map +1 -1
  27. package/dist/components/approvals/workflow_steps.d.mts +3 -3
  28. package/dist/components/approvals/workflow_steps.mjs +1 -1
  29. package/dist/components/approvals/workflow_steps.mjs.map +1 -1
  30. package/dist/components/foundation/client.d.mts +27 -28
  31. package/dist/components/foundation/client.mjs +1 -1
  32. package/dist/components/foundation/client.mjs.map +1 -1
  33. package/dist/components/foundation/modules/command/command.d.mts +17 -8
  34. package/dist/components/foundation/modules/command/command.mjs +1 -1
  35. package/dist/components/foundation/modules/command/command.mjs.map +1 -1
  36. package/dist/components/foundation/modules/observability/observability.d.mts +1 -1
  37. package/dist/components/foundation/modules/observability/observability.mjs +1 -1
  38. package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
  39. package/dist/components/foundation/modules/query/query.d.mts +8 -8
  40. package/dist/components/foundation/modules/query/query.mjs +1 -1
  41. package/dist/components/foundation/modules/query/query.mjs.map +1 -1
  42. package/dist/components/foundation/operation.d.mts +74 -0
  43. package/dist/components/foundation/operation.mjs +2 -0
  44. package/dist/components/foundation/operation.mjs.map +1 -0
  45. package/dist/components/foundation/result.d.mts +1 -1
  46. package/dist/components/foundation/result.mjs.map +1 -1
  47. package/dist/components/foundation/telemetry.mjs +1 -1
  48. package/dist/components/foundation/telemetry.mjs.map +1 -1
  49. package/dist/crud.d.mts +177 -16
  50. package/dist/crud.mjs +1 -1
  51. package/dist/crud.mjs.map +1 -1
  52. package/dist/middleware.d.mts +7 -7
  53. package/dist/middleware.mjs +1 -1
  54. package/dist/middleware.mjs.map +1 -1
  55. package/dist/oxlint.d.mts +9 -0
  56. package/dist/oxlint.mjs +2 -0
  57. package/dist/oxlint.mjs.map +1 -0
  58. package/dist/tenancy.mjs +1 -1
  59. package/dist/tenancy.mjs.map +1 -1
  60. package/dist/triggers.mjs +1 -1
  61. package/dist/triggers.mjs.map +1 -1
  62. package/dist/webhooks.d.mts +17 -106
  63. package/dist/webhooks.mjs.map +1 -1
  64. package/dist/zod-table.d.mts +47 -91
  65. package/dist/zod-table.mjs +1 -1
  66. package/dist/zod-table.mjs.map +1 -1
  67. package/docs/approvals.md +57 -56
  68. package/docs/architecture.md +1 -1
  69. package/docs/auth.md +57 -33
  70. package/docs/commands.md +159 -44
  71. package/docs/conventions.md +40 -40
  72. package/docs/crud.md +5 -5
  73. package/docs/llms.md +117 -71
  74. package/docs/maintainability.md +4 -4
  75. package/docs/migration.md +33 -33
  76. package/docs/tenancy.md +5 -8
  77. package/docs/triggers.md +4 -2
  78. package/docs/upgrading.md +74 -7
  79. package/docs/zod-table.md +70 -45
  80. package/package.json +41 -28
  81. package/scripts/convex-config-bridge.mjs +7 -18
  82. package/scripts/extract-changelog.mjs +1 -1
  83. package/scripts/smoke-packed-components.mjs +35 -75
  84. package/scripts/test-packed-ids.mjs +149 -0
  85. package/scripts/test-packed-oxlint.mjs +121 -0
  86. package/src/test.ts +9 -17
  87. package/docs/plans/2026-08-14-001-feat-v0.1.0-feature-set-plan.md +0 -461
  88. 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 *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,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
- '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
 
28
28
  ## What you get back
29
29
 
30
- | Property | What it is | Where you use it |
31
- |---|---|---|
32
- | `table` | `defineTable(...)` from the storage shape | `convex/schema.ts` |
33
- | `storage` | zod object of the raw row (no `_id`/`_creationTime`) | validating rows |
34
- | `schema` | `storage` + `_id` + `_creationTime` | full-document validation |
35
- | `insertSchema` | storage minus timestamps minus `serverFields` | mutation args |
36
- | `updateSchema` | `insertSchema.partial()` | patch mutation args |
37
- | `commandInput` | strict pick of `commandFields` | command protocol inputs |
38
- | `publicDto` | strict pick of `publicFields` | query return validators |
39
- | `toPublicDto(row)` | projects **and re-parses** a row into the DTO | query handlers |
40
- | `insert(omit?)` / `update(omit?)` | schema with per-callsite field omission | specialized mutations |
41
- | `tools.insert` / `tools.update` / `tools.id` | ready-made LLM tool input schemas | agent tool definitions |
42
- | `tableName` | the literal table name | indexes, triggers registration |
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
- args: { id: zid('documents') },
54
- returns: documents.publicDto,
55
- handler: async (ctx, { id }) => {
56
- const row = await ctx.db.get(id)
57
- if (!row) throw new KitError({ code: 'NOT_FOUND' })
58
- return documents.toPublicDto(row) // secretNote can never leak
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 *not* in
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` — *the server assigns these.* Excluded from `insertSchema`
108
+ - `serverFields` — _the server assigns these._ Excluded from `insertSchema`
87
109
  and `updateSchema`. Example: `reviewState`, denormalized counters.
88
- - `commandFields` — *a command may say these.* The command protocol's input
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` — *a client may see these.* The DTO is exactly this pick;
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('events', z.discriminatedUnion('kind', [
128
- z.object({ kind: z.literal('created'), by: zid('users') }),
129
- z.object({ kind: z.literal('archived'), reason: z.string() }),
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
- 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\""
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
- documents: documents.table
159
- .index('by_owner', ['ownerId'])
160
- .index('by_owner_archived', ['ownerId', 'archivedAt']),
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.2",
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
- "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,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.4",
105
- "convex-audit-log": "^0.2.0",
106
- "convex-helpers": "0.1.122"
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
- "convex": "1.43.0",
115
- "convex-test": "^0.0.55",
116
- "publint": "^0.3.23",
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.2.8",
119
- "zod": "4.4.3"
141
+ "vite-plus": "0.3.0",
142
+ "zod": "4.5.4"
120
143
  },
121
- "keywords": [
122
- "convex",
123
- "component",
124
- "workos",
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
- .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'))
@@ -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]*?)(?=^## \[|\Z)`,
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 packageVersion = JSON.parse(
22
- readFileSync(join(root, 'package.json'), 'utf8'),
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: '1.43.0',
76
- zod: '4.4.3',
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': '0.2.8',
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
- runStreaming('bun', ['install', '--ignore-scripts']),
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
- let decision
331
- let lastError
332
- for (let attempt = 0; attempt < 10; attempt += 1) {
333
- try {
334
- decision = JSON.parse(
335
- runConvex('run', 'smoke:decide', JSON.stringify({ runId })),
336
- )
337
- break
338
- } catch (error) {
339
- lastError = error
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
- if (!decision) throw lastError
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
  }