cvx-kit 0.1.0 → 0.1.2

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 (45) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +6 -0
  3. package/dist/auth.d.mts +16 -0
  4. package/dist/auth.mjs +1 -1
  5. package/dist/auth.mjs.map +1 -1
  6. package/dist/components/approvals/_generated/api.d.mts +2 -0
  7. package/dist/components/approvals/_generated/api.mjs.map +1 -1
  8. package/dist/components/approvals/_generated/component.d.mts +7 -0
  9. package/dist/components/approvals/_generated/dataModel.d.mts +1 -1
  10. package/dist/components/approvals/audit.mjs +1 -1
  11. package/dist/components/approvals/client.d.mts +4 -4
  12. package/dist/components/approvals/client.mjs +1 -1
  13. package/dist/components/approvals/client.mjs.map +1 -1
  14. package/dist/components/approvals/constants.d.mts +8 -2
  15. package/dist/components/approvals/constants.mjs +1 -1
  16. package/dist/components/approvals/constants.mjs.map +1 -1
  17. package/dist/components/approvals/decisions.mjs +1 -1
  18. package/dist/components/approvals/decisions.mjs.map +1 -1
  19. package/dist/components/approvals/health.d.mts +15 -0
  20. package/dist/components/approvals/health.mjs +2 -0
  21. package/dist/components/approvals/health.mjs.map +1 -0
  22. package/dist/components/approvals/requests.mjs +1 -1
  23. package/dist/components/approvals/requests.mjs.map +1 -1
  24. package/dist/components/approvals/schema.js +1 -0
  25. package/dist/components/approvals/validators.d.mts +119 -2
  26. package/dist/components/approvals/validators.mjs +1 -1
  27. package/dist/components/approvals/validators.mjs.map +1 -1
  28. package/dist/components/approvals/workflow_steps.d.mts +2 -25
  29. package/dist/components/approvals/workflow_steps.mjs.map +1 -1
  30. package/dist/components/foundation/_generated/dataModel.d.mts +1 -1
  31. package/dist/components/foundation/{schema.mjs → schema.js} +0 -1
  32. package/docs/approvals.md +50 -2
  33. package/docs/auth.md +49 -0
  34. package/docs/llms.md +1 -0
  35. package/docs/plans/2026-08-21-0930-feat-resolve-organization-hook-plan.md +178 -0
  36. package/docs/upgrading.md +23 -0
  37. package/package.json +5 -2
  38. package/scripts/convex-config-bridge.mjs +41 -3
  39. package/scripts/smoke-packed-components.mjs +374 -0
  40. package/src/test.ts +2 -2
  41. package/dist/components/approvals/schema.mjs +0 -2
  42. package/dist/components/approvals/schema.mjs.map +0 -1
  43. package/dist/components/foundation/schema.mjs.map +0 -1
  44. /package/dist/components/approvals/{schema.d.mts → schema.d.ts} +0 -0
  45. /package/dist/components/foundation/{schema.d.mts → schema.d.ts} +0 -0
@@ -0,0 +1,374 @@
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'
10
+ import { tmpdir } from 'node:os'
11
+ import { join } from 'node:path'
12
+
13
+ const installer = process.argv[2] ?? 'bun'
14
+ if (installer !== 'bun' && installer !== 'npm') {
15
+ throw new Error('Usage: smoke-packed-components.mjs [bun|npm]')
16
+ }
17
+
18
+ const root = join(import.meta.dirname, '..')
19
+ const temporaryRoot = mkdtempSync(join(tmpdir(), 'cvx-kit-smoke-'))
20
+ const fixture = join(temporaryRoot, 'fixture')
21
+ const packageVersion = JSON.parse(
22
+ readFileSync(join(root, 'package.json'), 'utf8'),
23
+ ).version
24
+ const tarball = join(temporaryRoot, `cvx-kit-${packageVersion}.tgz`)
25
+
26
+ function run(command, args, cwd = fixture) {
27
+ return execFileSync(command, args, {
28
+ cwd,
29
+ encoding: 'utf8',
30
+ stdio: ['ignore', 'pipe', 'pipe'],
31
+ env: { ...process.env, CONVEX_AGENT_MODE: 'anonymous' },
32
+ })
33
+ }
34
+
35
+ function runStreaming(command, args, cwd = fixture) {
36
+ execFileSync(command, args, {
37
+ cwd,
38
+ stdio: 'inherit',
39
+ env: { ...process.env, CONVEX_AGENT_MODE: 'anonymous' },
40
+ })
41
+ }
42
+
43
+ function runConvex(...args) {
44
+ return run('node', [
45
+ join(fixture, 'node_modules', 'convex', 'bin', 'main.js'),
46
+ ...args,
47
+ ])
48
+ }
49
+
50
+ function write(relativePath, contents) {
51
+ const path = join(fixture, relativePath)
52
+ writeFileSync(path, contents)
53
+ }
54
+
55
+ function step(label, callback) {
56
+ console.log(`smoke step: ${label}`)
57
+ return callback()
58
+ }
59
+
60
+ try {
61
+ run(
62
+ 'bun',
63
+ ['pm', 'pack', '--destination', temporaryRoot, '--ignore-scripts'],
64
+ root,
65
+ )
66
+ mkdirSync(join(fixture, 'convex'), { recursive: true })
67
+ write(
68
+ 'package.json',
69
+ JSON.stringify(
70
+ {
71
+ private: true,
72
+ type: 'module',
73
+ dependencies: {
74
+ 'cvx-kit': `file:${tarball}`,
75
+ convex: '1.43.0',
76
+ zod: '4.4.3',
77
+ },
78
+ devDependencies: {
79
+ 'vite-plus': '0.2.8',
80
+ },
81
+ },
82
+ null,
83
+ 2,
84
+ ),
85
+ )
86
+ write(
87
+ 'packed-test-helper.test.ts',
88
+ `import { describe, expect, it } from 'vite-plus/test'
89
+ import { registerApprovals, registerFoundation } from 'cvx-kit/test'
90
+
91
+ describe('packed cvx-kit test helpers', () => {
92
+ it('registers both compiled component bundles and their discoverable schemas', () => {
93
+ const registrations = []
94
+ const testRuntime = {
95
+ registerComponent(name, schema, modules) {
96
+ registrations.push({ name, schema, modules })
97
+ },
98
+ }
99
+
100
+ registerApprovals(testRuntime)
101
+ registerFoundation(testRuntime)
102
+
103
+ expect(registrations.map(({ name }) => name)).toEqual(['approvals', 'foundation'])
104
+ expect(Object.keys(registrations[0].schema.tables).sort()).toEqual([
105
+ 'approvalDecisions',
106
+ 'approvalRuns',
107
+ ])
108
+ expect(Object.keys(registrations[0].modules)).toContain(
109
+ '../dist/components/approvals/health.mjs',
110
+ )
111
+ })
112
+ })
113
+ `,
114
+ )
115
+ write(
116
+ 'convex/convex.config.ts',
117
+ `import { defineApp } from 'convex/server'
118
+ import approvals from 'cvx-kit/components/approvals/convex.config'
119
+ import foundation from 'cvx-kit/components/foundation/convex.config'
120
+
121
+ const app = defineApp()
122
+ app.use(approvals)
123
+ app.use(foundation)
124
+ export default app
125
+ `,
126
+ )
127
+ write(
128
+ 'convex/smoke.ts',
129
+ `import { approvalCallbackArgs } from 'cvx-kit/components/approvals'
130
+ import { createFunctionHandle, paginationOptsValidator } from 'convex/server'
131
+ import { v } from 'convex/values'
132
+ import { components, internal } from './_generated/api'
133
+ import { internalMutation, mutation, query } from './_generated/server'
134
+
135
+ export const applyDecision = internalMutation({
136
+ args: approvalCallbackArgs,
137
+ returns: v.null(),
138
+ handler: (_ctx, input) => {
139
+ if (input.decision?.evidence[0]?.stepKey !== 'releaseDecision') {
140
+ throw new Error('Missing callback decision step key')
141
+ }
142
+ return null
143
+ },
144
+ })
145
+
146
+ export const rejectDecision = internalMutation({
147
+ args: approvalCallbackArgs,
148
+ returns: v.null(),
149
+ handler: () => null,
150
+ })
151
+
152
+ export const health = query({
153
+ args: {},
154
+ returns: v.any(),
155
+ handler: (ctx) => ctx.runQuery(components.approvals.health.check, {}),
156
+ })
157
+
158
+ export const foundationHealth = query({
159
+ args: {},
160
+ returns: v.literal('ready'),
161
+ handler: (ctx) => ctx.runQuery(components.foundation.functions.status, {}),
162
+ })
163
+
164
+ export const start = mutation({
165
+ args: { resourceRef: v.optional(v.string()) },
166
+ returns: v.object({ runId: v.string() }),
167
+ handler: async (ctx, args) => {
168
+ const approvedHandle = await createFunctionHandle(internal.smoke.applyDecision)
169
+ const rejectedHandle = await createFunctionHandle(internal.smoke.rejectDecision)
170
+ return ctx.runMutation(components.approvals.requests.start, {
171
+ scopeRef: 'smoke',
172
+ resourceType: 'release',
173
+ resourceRef: args.resourceRef ?? 'packed-component',
174
+ requester: { actorRef: 'requester', capabilities: [] },
175
+ workflow: {
176
+ schemaVersion: 1,
177
+ compatibilityKey: 'packedSmoke',
178
+ name: 'packedSmoke',
179
+ steps: [
180
+ {
181
+ kind: 'decision',
182
+ key: 'releaseDecision',
183
+ decisions: ['approved', 'rejected'],
184
+ quorum: { kind: 'count', approvals: 1 },
185
+ makerChecker: true,
186
+ },
187
+ {
188
+ kind: 'branch',
189
+ key: 'routeDecision',
190
+ approvedStepKey: 'applyDecision',
191
+ rejectedStepKey: 'rejectDecision',
192
+ },
193
+ {
194
+ kind: 'mutation',
195
+ key: 'applyDecision',
196
+ callback: { kind: 'mutation', handle: approvedHandle, retry: false },
197
+ },
198
+ {
199
+ kind: 'mutation',
200
+ key: 'rejectDecision',
201
+ callback: { kind: 'mutation', handle: rejectedHandle, retry: false },
202
+ },
203
+ ],
204
+ },
205
+ })
206
+ },
207
+ })
208
+
209
+ export const list = query({
210
+ args: {
211
+ paginationOpts: paginationOptsValidator,
212
+ state: v.optional(v.union(v.literal('pending'), v.literal('approved'))),
213
+ },
214
+ returns: v.any(),
215
+ handler: (ctx, args) => ctx.runQuery(components.approvals.requests.list, {
216
+ scopeRef: 'smoke',
217
+ ...args,
218
+ }),
219
+ })
220
+
221
+ export const decide = mutation({
222
+ args: { runId: v.string() },
223
+ returns: v.any(),
224
+ handler: (ctx, args) => ctx.runMutation(components.approvals.decisions.decide, {
225
+ runId: args.runId,
226
+ decision: 'approved',
227
+ actor: { actorRef: 'approver', capabilities: [] },
228
+ compatibilityKey: 'packedSmoke',
229
+ }),
230
+ })
231
+
232
+ export const history = query({
233
+ args: { runId: v.string() },
234
+ returns: v.any(),
235
+ handler: (ctx, args) => ctx.runQuery(components.approvals.decisions.list, args),
236
+ })
237
+
238
+ export const status = query({
239
+ args: { runId: v.string() },
240
+ returns: v.any(),
241
+ handler: (ctx, args) => ctx.runQuery(components.approvals.requests.status, {
242
+ ...args,
243
+ compatibilityKey: 'packedSmoke',
244
+ }),
245
+ })
246
+ `,
247
+ )
248
+
249
+ if (installer === 'bun') {
250
+ copyFileSync(join(root, 'bun.lock'), join(fixture, 'bun.lock'))
251
+ }
252
+ 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
+ )
260
+ if (installer === 'bun')
261
+ step('test helper with bun', () =>
262
+ runStreaming('bunx', ['vp', 'test', 'run', 'packed-test-helper.test.ts']),
263
+ )
264
+ else
265
+ step('test helper with npm', () =>
266
+ runStreaming('npx', ['vp', 'test', 'run', 'packed-test-helper.test.ts']),
267
+ )
268
+
269
+ step('deploy Convex fixture', () =>
270
+ runConvex('dev', '--once', '--typecheck=disable'),
271
+ )
272
+ const health = JSON.parse(runConvex('run', 'smoke:health'))
273
+ if (health.status !== 'ready' || health.schemaVersion !== 1) {
274
+ throw new Error(
275
+ `Unexpected approvals health response: ${JSON.stringify(health)}`,
276
+ )
277
+ }
278
+ const foundationHealth = JSON.parse(
279
+ runConvex('run', 'smoke:foundationHealth'),
280
+ )
281
+ if (foundationHealth !== 'ready') {
282
+ throw new Error(
283
+ `Unexpected foundation health response: ${JSON.stringify(foundationHealth)}`,
284
+ )
285
+ }
286
+ const { runId } = JSON.parse(runConvex('run', 'smoke:start'))
287
+ const { runId: secondRunId } = JSON.parse(
288
+ runConvex(
289
+ 'run',
290
+ 'smoke:start',
291
+ JSON.stringify({ resourceRef: 'packed-component-2' }),
292
+ ),
293
+ )
294
+ const firstPage = JSON.parse(
295
+ runConvex(
296
+ 'run',
297
+ 'smoke:list',
298
+ JSON.stringify({
299
+ state: 'pending',
300
+ paginationOpts: { cursor: null, numItems: 1 },
301
+ }),
302
+ ),
303
+ )
304
+ if (firstPage.page.length !== 1 || firstPage.page[0]._id !== secondRunId) {
305
+ throw new Error(`Unexpected first approval page: ${JSON.stringify(firstPage)}`)
306
+ }
307
+ const secondPage = JSON.parse(
308
+ runConvex(
309
+ 'run',
310
+ 'smoke:list',
311
+ JSON.stringify({
312
+ state: 'pending',
313
+ paginationOpts: {
314
+ cursor: firstPage.continueCursor,
315
+ numItems: 10,
316
+ },
317
+ }),
318
+ ),
319
+ )
320
+ if (
321
+ secondPage.page.length !== 1 ||
322
+ secondPage.page[0]._id !== runId ||
323
+ secondPage.page[0]._id === firstPage.page[0]._id
324
+ ) {
325
+ throw new Error(
326
+ `Unexpected continuation approval page: ${JSON.stringify(secondPage)}`,
327
+ )
328
+ }
329
+
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))
348
+ }
349
+ }
350
+ if (!decision) throw lastError
351
+ if (decision.state !== 'approved') {
352
+ throw new Error(`Unexpected decision response: ${JSON.stringify(decision)}`)
353
+ }
354
+ const history = JSON.parse(
355
+ runConvex('run', 'smoke:history', JSON.stringify({ runId })),
356
+ )
357
+ if (history.length !== 1 || history[0].decision !== 'approved') {
358
+ throw new Error(`Unexpected approval history: ${JSON.stringify(history)}`)
359
+ }
360
+ let status
361
+ for (let attempt = 0; attempt < 20; attempt += 1) {
362
+ status = JSON.parse(
363
+ runConvex('run', 'smoke:status', JSON.stringify({ runId })),
364
+ )
365
+ if (status.execution?.type === 'completed') break
366
+ await new Promise((resolve) => setTimeout(resolve, 500))
367
+ }
368
+ if (status?.execution?.type !== 'completed') {
369
+ throw new Error(`Approval callback did not complete: ${JSON.stringify(status)}`)
370
+ }
371
+ console.log(`packed component smoke passed with ${installer}`)
372
+ } finally {
373
+ rmSync(temporaryRoot, { recursive: true, force: true })
374
+ }
package/src/test.ts CHANGED
@@ -6,8 +6,8 @@
6
6
  * registers the COMPILED component modules from dist, so dist is the only
7
7
  * code the package ships.
8
8
  */
9
- import approvalsSchema from '../dist/components/approvals/schema.mjs'
10
- import foundationSchema from '../dist/components/foundation/schema.mjs'
9
+ import approvalsSchema from '../dist/components/approvals/schema.js'
10
+ import foundationSchema from '../dist/components/foundation/schema.js'
11
11
 
12
12
  const foundationModules = import.meta.glob(
13
13
  '../dist/components/foundation/**/*.mjs',
@@ -1,2 +0,0 @@
1
- import{approvalDecisionDocument as e,approvalRunDocument as t}from"./validators.mjs";import{zodToConvex as n}from"convex-helpers/server/zod4";import{defineSchema as r,defineTable as i}from"convex/server";var a=r({approvalRuns:i(n(t)).index(`by_resourceType_and_resourceRef`,[`resourceType`,`resourceRef`]).index(`by_scopeRef_and_state`,[`scopeRef`,`state`]).index(`by_state_and_expiresAt`,[`state`,`expiresAt`]),approvalDecisions:i(n(e)).index(`by_runId_and_stepKey_and_actor_actorRef`,[`runId`,`stepKey`,`actor.actorRef`]).index(`by_runId_and_stepKey_and_decidedAt`,[`runId`,`stepKey`,`decidedAt`]).index(`by_runId_and_decidedAt`,[`runId`,`decidedAt`])});export{a as default};
2
- //# sourceMappingURL=schema.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema.mjs","names":[],"sources":["../../../src/components/approvals/schema.ts"],"sourcesContent":["import { zodToConvex } from 'convex-helpers/server/zod4'\nimport { defineSchema, defineTable } from 'convex/server'\n\nimport { approvalDecisionDocument, approvalRunDocument } from './validators'\n\nexport default defineSchema({\n\tapprovalRuns: defineTable(zodToConvex(approvalRunDocument))\n\t\t.index('by_resourceType_and_resourceRef', ['resourceType', 'resourceRef'])\n\t\t.index('by_scopeRef_and_state', ['scopeRef', 'state'])\n\t\t.index('by_state_and_expiresAt', ['state', 'expiresAt']),\n\tapprovalDecisions: defineTable(zodToConvex(approvalDecisionDocument))\n\t\t.index('by_runId_and_stepKey_and_actor_actorRef', [\n\t\t\t'runId',\n\t\t\t'stepKey',\n\t\t\t'actor.actorRef',\n\t\t])\n\t\t.index('by_runId_and_stepKey_and_decidedAt', [\n\t\t\t'runId',\n\t\t\t'stepKey',\n\t\t\t'decidedAt',\n\t\t])\n\t\t.index('by_runId_and_decidedAt', ['runId', 'decidedAt']),\n})\n"],"mappings":"4MAKA,IAAA,EAAe,EAAa,CAC3B,aAAc,EAAY,EAAY,CAAmB,CAAC,CAAC,CACzD,MAAM,kCAAmC,CAAC,eAAgB,aAAa,CAAC,CAAC,CACzE,MAAM,wBAAyB,CAAC,WAAY,OAAO,CAAC,CAAC,CACrD,MAAM,yBAA0B,CAAC,QAAS,WAAW,CAAC,EACxD,kBAAmB,EAAY,EAAY,CAAwB,CAAC,CAAC,CACnE,MAAM,0CAA2C,CACjD,QACA,UACA,gBACD,CAAC,CAAC,CACD,MAAM,qCAAsC,CAC5C,QACA,UACA,WACD,CAAC,CAAC,CACD,MAAM,yBAA0B,CAAC,QAAS,WAAW,CAAC,CACzD,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema.mjs","names":[],"sources":["../../../src/components/foundation/schema.ts"],"sourcesContent":["import { defineSchema } from 'convex/server'\n\n/** Foundation is a capability-free in-process kernel and owns no business data. */\nexport default defineSchema({})\n"],"mappings":"6CAGA,IAAA,EAAe,EAAa,CAAC,CAAC"}