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/CHANGELOG.md CHANGED
@@ -12,6 +12,44 @@ are this file's matching section.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.1.4] - 2026-09-18
16
+
17
+ ### Added
18
+
19
+ - Publish the TypeScript Oxlint plugin as `cvx-kit/oxlint`, with six rules for
20
+ component isolation, environment access, schema placement, public facades,
21
+ API ordering, and named private helpers. Consumer entry points and package
22
+ self-import boundaries are configurable.
23
+ - Enforce all 15 generic anti-slop rules in the repository and verify the
24
+ published plugin through fresh npm and Bun installations in CI.
25
+
26
+ ### Changed
27
+
28
+ - Replace cast-heavy command, CRUD, tenancy, auth, webhook, and tool boundaries
29
+ with schema-derived types and validation. Preserve distinct agent-tool result
30
+ types and validate the emitted declarations in packed consumers.
31
+ - Tighten TypeScript contracts for query middleware, include transforms, audit
32
+ metadata, and webhook targets. See `docs/upgrading.md` for migration details.
33
+ - Format owned source, tests, scripts, and documentation consistently.
34
+
35
+ ## [0.1.3] - 2026-09-06
36
+
37
+ ### Fixed
38
+
39
+ - Fix Convex ID validation across package boundaries by sharing `convex-helpers`
40
+ as a peer dependency. Export `zid`, `zodToConvex`, and `convexToZod` from
41
+ `cvx-kit/zod-table`; verify differing compatible host versions under npm and Bun (#10).
42
+ - Add `Command.withContext<Context, Extension>()` for schema-inferred guards,
43
+ audit callbacks, operation middleware, and correlated registry middleware (#11).
44
+ - Add invocation-local `prepare` hooks with authorized, validated replay and
45
+ completion after audit, inside observation. Durable storage remains host-owned (#12).
46
+ - Accept concrete and generic `convex-test` backends directly in registration helpers (#13).
47
+ - Parse command results once after middleware; handler and middleware result types
48
+ describe schema inputs, while audit, completion, and executor results describe outputs.
49
+ - Accept raw command schema inputs at executor call sites while inferring parsed
50
+ outputs for callbacks; reject incompatible typed host contexts and registry middleware.
51
+ - See `docs/upgrading.md` for helper peer installation and transformed-result migration.
52
+
15
53
  ## [0.1.2] - 2026-08-21
16
54
 
17
55
  ### Added
package/README.md CHANGED
@@ -57,16 +57,16 @@ One zod shape per entity, masked into every boundary it crosses:
57
57
  import { zodTable } from 'cvx-kit/zod-table'
58
58
 
59
59
  export const documents = zodTable(
60
- 'documents',
61
- (id) => ({
62
- title: z.string(),
63
- ownerId: id('users'),
64
- secretNote: z.string(),
65
- }),
66
- {
67
- commandFields: ['title'], // what a command may say
68
- publicFields: ['title', 'ownerId'], // the DTO allowlist
69
- },
60
+ 'documents',
61
+ (id) => ({
62
+ title: z.string(),
63
+ ownerId: id('users'),
64
+ secretNote: z.string(),
65
+ }),
66
+ {
67
+ commandFields: ['title'], // what a command may say
68
+ publicFields: ['title', 'ownerId'], // the DTO allowlist
69
+ },
70
70
  )
71
71
  // documents.table → defineTable(...) for schema.ts
72
72
  // documents.toPublicDto(row) → projects AND re-parses (runtime redaction)
@@ -90,20 +90,35 @@ policy — nothing in the kit imports your app's singletons:
90
90
  import { createAuthFunctions } from 'cvx-kit/auth'
91
91
  import { action, internalAction, internalMutation, mutation, query } from './_generated/server'
92
92
 
93
- export const { authQuery, authMutation, authAction, adminQuery, adminMutation,
94
- adminAction, systemMutation, systemAction, include } =
95
- createAuthFunctions<DataModel>({
96
- query, mutation, action, internalMutation, internalAction,
97
- getAuthUser: (ctx) => authKit.getAuthUser(ctx),
98
- verifyMembership: async ({ userId, organizationId }) => {
99
- const memberships = await authKit.workos.userManagement
100
- .listOrganizationMemberships({ organizationId, userId, statuses: ['active'] })
101
- const m = memberships.data.find((c) => c.userId === userId)
102
- return m ? { organizationId: m.organizationId, roleSlug: m.role.slug } : null
103
- },
104
- // resolveOrganization: async ({ ctx, user }) => { /* org+role from app tables */ },
105
- wrapDB: (ctx) => triggers.wrapDB(ctx),
106
- })
93
+ export const {
94
+ authQuery,
95
+ authMutation,
96
+ authAction,
97
+ adminQuery,
98
+ adminMutation,
99
+ adminAction,
100
+ systemMutation,
101
+ systemAction,
102
+ include,
103
+ } = createAuthFunctions<DataModel>({
104
+ query,
105
+ mutation,
106
+ action,
107
+ internalMutation,
108
+ internalAction,
109
+ getAuthUser: (ctx) => authKit.getAuthUser(ctx),
110
+ verifyMembership: async ({ userId, organizationId }) => {
111
+ const memberships = await authKit.workos.userManagement.listOrganizationMemberships({
112
+ organizationId,
113
+ userId,
114
+ statuses: ['active'],
115
+ })
116
+ const m = memberships.data.find((c) => c.userId === userId)
117
+ return m ? { organizationId: m.organizationId, roleSlug: m.role.slug } : null
118
+ },
119
+ // resolveOrganization: async ({ ctx, user }) => { /* org+role from app tables */ },
120
+ wrapDB: (ctx) => triggers.wrapDB(ctx),
121
+ })
107
122
  ```
108
123
 
109
124
  Actions re-verify membership live and fail closed; mutations get the trigger
@@ -172,6 +187,36 @@ app.use(approvals)
172
187
 
173
188
  ## Component authoring conventions
174
189
 
190
+ The TypeScript Oxlint plugin ships as `cvx-kit/oxlint`. Load it in your
191
+ `.oxlintrc.json`:
192
+
193
+ ```json
194
+ {
195
+ "jsPlugins": [{ "name": "cvx", "specifier": "cvx-kit/oxlint" }],
196
+ "rules": {
197
+ "cvx/component-boundaries": "error",
198
+ "cvx/no-component-env": "error",
199
+ "cvx/schema-file-boundaries": "error",
200
+ "cvx/no-internal-reexports": ["error", { "entryPoints": ["src/index.ts"] }],
201
+ "cvx/public-api-first": "error",
202
+ "cvx/named-private-helpers": "error"
203
+ }
204
+ }
205
+ ```
206
+
207
+ With Vite+, put those fields under `lint` in `vite.config.ts`. Component
208
+ rules target `src/components/<name>/`; set your public files in `entryPoints`
209
+ (paths relative to the linter's working directory). To block imports back
210
+ into your own package, configure `component-boundaries` with
211
+ `["error", { "packageName": "your-package" }]`. The plugin exports its default
212
+ plugin object, named `rules`, and the `RuleName` type. Verified with Oxlint
213
+ 1.79.0 and Vite+ 0.3.0.
214
+
215
+ Repository checks and the enforced library rules are documented in
216
+ [`tools/oxlint/README.md`](./tools/oxlint/README.md). Run `bun run lint` for
217
+ anti-slop and architecture checks, `bun run test:lint` to test the custom
218
+ rules, and `bun run check` for the full Vite+ lint and formatting check.
219
+
175
220
  This package follows the official Convex component template
176
221
  (`get-convex/templates/template-component`; see
177
222
  https://docs.convex.dev/components/authoring):
@@ -6,11 +6,11 @@ import { z } from "zod";
6
6
  * adaptable to ai-sdk tool(). The kit imports neither framework — checked
7
7
  * against registry snapshots on 2026-08-14; re-verify at install time.
8
8
  */
9
- type AgentToolRecord = {
9
+ type AgentToolRecord<Result = unknown, Context = never, Input = never> = {
10
10
  name: string;
11
11
  description: string;
12
12
  args: z.ZodType;
13
- handler: (ctx: never, input: never) => Promise<unknown>;
13
+ handler: (ctx: Context, input: Input) => Promise<Result>;
14
14
  };
15
15
  type ToolTable = {
16
16
  tableName: string;
@@ -20,16 +20,26 @@ type ToolTable = {
20
20
  id: z.ZodType;
21
21
  };
22
22
  };
23
- type ToolHandler = (ctx: never, input: never) => Promise<unknown>;
24
- type AgentToolHandlers = {
23
+ type ToolHandler<Result> = (ctx: never, input: never) => Promise<Result>;
24
+ type AgentToolHandlers<Result = unknown> = {
25
25
  /** Mutations: route through command executors — agents get audited commands. */
26
- create?: ToolHandler;
27
- update?: ToolHandler;
28
- archive?: ToolHandler;
26
+ create?: ToolHandler<Result>;
27
+ update?: ToolHandler<Result>;
28
+ archive?: ToolHandler<Result>;
29
29
  /** Reads: caller-supplied query handlers (reads do not route through commands). */
30
- get?: ToolHandler;
31
- list?: ToolHandler;
30
+ get?: ToolHandler<Result>;
31
+ list?: ToolHandler<Result>;
32
32
  };
33
+ declare const listArgs: z.ZodObject<{
34
+ paginationOpts: z.ZodObject<{
35
+ numItems: z.ZodNumber;
36
+ cursor: z.ZodUnion<readonly [z.ZodString, z.ZodNull]>;
37
+ endCursor: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNull]>>;
38
+ id: z.ZodOptional<z.ZodNumber>;
39
+ maximumRowsRead: z.ZodOptional<z.ZodNumber>;
40
+ maximumBytesRead: z.ZodOptional<z.ZodNumber>;
41
+ }, z.core.$strict>;
42
+ }, z.core.$strict>;
33
43
  /**
34
44
  * Emits agent tool definitions from the existing table tool masks: args are
35
45
  * the masks (jsonSafeZid keeps ids primitive in generated JSON schemas),
@@ -37,9 +47,15 @@ type AgentToolHandlers = {
37
47
  * are emitted. Wire mutation handlers to createCrudCommands executors (or
38
48
  * your own) so every agent action is an audited command.
39
49
  */
40
- declare function createAgentTools(table: ToolTable, handlers: AgentToolHandlers, options?: {
50
+ declare function createAgentTools<Table extends ToolTable, Handlers extends AgentToolHandlers>(table: Table, handlers: Handlers, options?: {
41
51
  descriptions?: Partial<Record<keyof AgentToolHandlers, string>>;
42
- }): Record<string, AgentToolRecord>;
52
+ }): AgentTools<Table, Handlers>;
53
+ type AgentTools<Table extends ToolTable, Handlers extends AgentToolHandlers> = Partial<{ [Verb in keyof Handlers & keyof AgentToolHandlers as `${Table['tableName']}_${Verb}`]: {
54
+ name: string;
55
+ description: string;
56
+ args: Verb extends 'create' ? Table['tools']['insert'] : Verb extends 'update' ? Table['tools']['update'] : Verb extends 'list' ? typeof listArgs : Table['tools']['id'];
57
+ handler: NonNullable<Handlers[Verb]>;
58
+ }; }>;
43
59
  //#endregion
44
- export { AgentToolHandlers, AgentToolRecord, createAgentTools };
60
+ export { AgentToolHandlers, AgentToolRecord, AgentTools, createAgentTools };
45
61
  //# sourceMappingURL=agent-tools.d.mts.map
@@ -1,2 +1,2 @@
1
- import{convexToZod as e}from"convex-helpers/server/zod4";import{paginationOptsValidator as t}from"convex/server";import{z as n}from"zod";const r=n.object({paginationOpts:e(t)}).strict();function i(e,t,n){let i=e.tableName,a={create:e.tools.insert,update:e.tools.update,archive:e.tools.id,get:e.tools.id,list:r},o={create:`Create a ${i} record`,update:`Update fields of an existing ${i} record`,archive:`Archive (soft-delete) a ${i} record`,get:`Fetch one ${i} record by id`,list:`List ${i} records, paginated`},s={};for(let e of Object.keys(a)){let r=t[e];r&&(s[`${i}_${e}`]={name:`${i}_${e}`,description:n?.descriptions?.[e]??o[e],args:a[e],handler:r})}return s}export{i as createAgentTools};
1
+ import{convexToZod as e}from"convex-helpers/server/zod4";import{paginationOptsValidator as t}from"convex/server";import{z as n}from"zod";const r=n.object({paginationOpts:e(t)}).strict();function i(e,t,n){let i=e.tableName,a={create:e.tools.insert,update:e.tools.update,archive:e.tools.id,get:e.tools.id,list:r},o={create:`Create a ${i} record`,update:`Update fields of an existing ${i} record`,archive:`Archive (soft-delete) a ${i} record`,get:`Fetch one ${i} record by id`,list:`List ${i} records, paginated`},s={};for(let e of[`create`,`update`,`archive`,`get`,`list`]){let r=t[e];r&&(s[`${i}_${e}`]={name:`${i}_${e}`,description:n?.descriptions?.[e]??o[e],args:a[e],handler:r})}return s}export{i as createAgentTools};
2
2
  //# sourceMappingURL=agent-tools.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-tools.mjs","names":[],"sources":["../src/agent-tools.ts"],"sourcesContent":["import { convexToZod } from 'convex-helpers/server/zod4'\nimport { paginationOptsValidator } from 'convex/server'\nimport { z } from 'zod'\n\n/**\n * A framework-agnostic tool record: directly spreadable into\n * @convex-dev/agent's createTool ({ description, args, handler }) and\n * adaptable to ai-sdk tool(). The kit imports neither framework — checked\n * against registry snapshots on 2026-08-14; re-verify at install time.\n */\nexport type AgentToolRecord = {\n\tname: string\n\tdescription: string\n\targs: z.ZodType\n\thandler: (ctx: never, input: never) => Promise<unknown>\n}\n\ntype ToolTable = {\n\ttableName: string\n\ttools: {\n\t\tinsert: z.ZodType\n\t\tupdate: z.ZodType\n\t\tid: z.ZodType\n\t}\n}\n\ntype ToolHandler = (ctx: never, input: never) => Promise<unknown>\n\nexport type AgentToolHandlers = {\n\t/** Mutations: route through command executors — agents get audited commands. */\n\tcreate?: ToolHandler\n\tupdate?: ToolHandler\n\tarchive?: ToolHandler\n\t/** Reads: caller-supplied query handlers (reads do not route through commands). */\n\tget?: ToolHandler\n\tlist?: ToolHandler\n}\n\nconst listArgs = z\n\t.object({ paginationOpts: convexToZod(paginationOptsValidator) })\n\t.strict()\n\n/**\n * Emits agent tool definitions from the existing table tool masks: args are\n * the masks (jsonSafeZid keeps ids primitive in generated JSON schemas),\n * names are `<tableName>_<verb>`, and only the kinds you supply handlers for\n * are emitted. Wire mutation handlers to createCrudCommands executors (or\n * your own) so every agent action is an audited command.\n */\nexport function createAgentTools(\n\ttable: ToolTable,\n\thandlers: AgentToolHandlers,\n\toptions?: { descriptions?: Partial<Record<keyof AgentToolHandlers, string>> },\n): Record<string, AgentToolRecord> {\n\tconst name = table.tableName\n\tconst masks: Record<keyof AgentToolHandlers, z.ZodType> = {\n\t\tcreate: table.tools.insert,\n\t\tupdate: table.tools.update,\n\t\tarchive: table.tools.id,\n\t\tget: table.tools.id,\n\t\tlist: listArgs,\n\t}\n\tconst defaults: Record<keyof AgentToolHandlers, string> = {\n\t\tcreate: `Create a ${name} record`,\n\t\tupdate: `Update fields of an existing ${name} record`,\n\t\tarchive: `Archive (soft-delete) a ${name} record`,\n\t\tget: `Fetch one ${name} record by id`,\n\t\tlist: `List ${name} records, paginated`,\n\t}\n\tconst tools: Record<string, AgentToolRecord> = {}\n\tfor (const verb of Object.keys(masks) as (keyof AgentToolHandlers)[]) {\n\t\tconst handler = handlers[verb]\n\t\tif (!handler) continue\n\t\ttools[`${name}_${verb}`] = {\n\t\t\tname: `${name}_${verb}`,\n\t\t\tdescription: options?.descriptions?.[verb] ?? defaults[verb],\n\t\t\targs: masks[verb],\n\t\t\thandler,\n\t\t}\n\t}\n\treturn tools\n}\n"],"mappings":"yIAsCA,MAAM,EAAW,EACf,OAAO,CAAE,eAAgB,EAAY,CAAuB,CAAE,CAAC,CAAC,CAChE,OAAO,EAST,SAAgB,EACf,EACA,EACA,EACkC,CAClC,IAAM,EAAO,EAAM,UACb,EAAoD,CACzD,OAAQ,EAAM,MAAM,OACpB,OAAQ,EAAM,MAAM,OACpB,QAAS,EAAM,MAAM,GACrB,IAAK,EAAM,MAAM,GACjB,KAAM,CACP,EACM,EAAoD,CACzD,OAAQ,YAAY,EAAK,SACzB,OAAQ,gCAAgC,EAAK,SAC7C,QAAS,2BAA2B,EAAK,SACzC,IAAK,aAAa,EAAK,eACvB,KAAM,QAAQ,EAAK,oBACpB,EACM,EAAyC,CAAC,EAChD,IAAK,IAAM,KAAQ,OAAO,KAAK,CAAK,EAAkC,CACrE,IAAM,EAAU,EAAS,GACpB,IACL,EAAM,GAAG,EAAK,GAAG,KAAU,CAC1B,KAAM,GAAG,EAAK,GAAG,IACjB,YAAa,GAAS,eAAe,IAAS,EAAS,GACvD,KAAM,EAAM,GACZ,SACD,EACD,CACA,OAAO,CACR"}
1
+ {"version":3,"file":"agent-tools.mjs","names":[],"sources":["../src/agent-tools.ts"],"sourcesContent":["import { convexToZod } from 'convex-helpers/server/zod4'\nimport { paginationOptsValidator } from 'convex/server'\nimport { z } from 'zod'\n\n/**\n * A framework-agnostic tool record: directly spreadable into\n * @convex-dev/agent's createTool ({ description, args, handler }) and\n * adaptable to ai-sdk tool(). The kit imports neither framework — checked\n * against registry snapshots on 2026-08-14; re-verify at install time.\n */\nexport type AgentToolRecord<Result = unknown, Context = never, Input = never> = {\n\tname: string\n\tdescription: string\n\targs: z.ZodType\n\thandler: (ctx: Context, input: Input) => Promise<Result>\n}\n\ntype ToolTable = {\n\ttableName: string\n\ttools: {\n\t\tinsert: z.ZodType\n\t\tupdate: z.ZodType\n\t\tid: z.ZodType\n\t}\n}\n\ntype ToolHandler<Result> = (ctx: never, input: never) => Promise<Result>\n\nexport type AgentToolHandlers<Result = unknown> = {\n\t/** Mutations: route through command executors — agents get audited commands. */\n\tcreate?: ToolHandler<Result>\n\tupdate?: ToolHandler<Result>\n\tarchive?: ToolHandler<Result>\n\t/** Reads: caller-supplied query handlers (reads do not route through commands). */\n\tget?: ToolHandler<Result>\n\tlist?: ToolHandler<Result>\n}\n\nconst listArgs = z.object({ paginationOpts: convexToZod(paginationOptsValidator) }).strict()\n\n/**\n * Emits agent tool definitions from the existing table tool masks: args are\n * the masks (jsonSafeZid keeps ids primitive in generated JSON schemas),\n * names are `<tableName>_<verb>`, and only the kinds you supply handlers for\n * are emitted. Wire mutation handlers to createCrudCommands executors (or\n * your own) so every agent action is an audited command.\n */\nexport function createAgentTools<Table extends ToolTable, Handlers extends AgentToolHandlers>(\n\ttable: Table,\n\thandlers: Handlers,\n\toptions?: { descriptions?: Partial<Record<keyof AgentToolHandlers, string>> },\n) {\n\tconst name = table.tableName\n\tconst masks: Record<keyof AgentToolHandlers, z.ZodType> = {\n\t\tcreate: table.tools.insert,\n\t\tupdate: table.tools.update,\n\t\tarchive: table.tools.id,\n\t\tget: table.tools.id,\n\t\tlist: listArgs,\n\t}\n\tconst defaults: Record<keyof AgentToolHandlers, string> = {\n\t\tcreate: `Create a ${name} record`,\n\t\tupdate: `Update fields of an existing ${name} record`,\n\t\tarchive: `Archive (soft-delete) a ${name} record`,\n\t\tget: `Fetch one ${name} record by id`,\n\t\tlist: `List ${name} records, paginated`,\n\t}\n\tconst tools: Record<string, AgentToolRecord> = {}\n\tfor (const verb of ['create', 'update', 'archive', 'get', 'list'] as const) {\n\t\tconst handler = handlers[verb]\n\t\tif (!handler) continue\n\t\ttools[`${name}_${verb}`] = {\n\t\t\tname: `${name}_${verb}`,\n\t\t\tdescription: options?.descriptions?.[verb] ?? defaults[verb],\n\t\t\targs: masks[verb],\n\t\t\thandler,\n\t\t}\n\t}\n\t// SAFETY: each emitted key combines this table name with a supplied verb; its handler and schema come from those same inputs.\n\treturn tools as AgentTools<Table, Handlers>\n}\n\nexport type AgentTools<Table extends ToolTable, Handlers extends AgentToolHandlers> = Partial<{\n\t[Verb in keyof Handlers & keyof AgentToolHandlers as `${Table['tableName']}_${Verb}`]: {\n\t\tname: string\n\t\tdescription: string\n\t\targs: Verb extends 'create'\n\t\t\t? Table['tools']['insert']\n\t\t\t: Verb extends 'update'\n\t\t\t\t? Table['tools']['update']\n\t\t\t\t: Verb extends 'list'\n\t\t\t\t\t? typeof listArgs\n\t\t\t\t\t: Table['tools']['id']\n\t\thandler: NonNullable<Handlers[Verb]>\n\t}\n}>\n"],"mappings":"yIAsCA,MAAM,EAAW,EAAE,OAAO,CAAE,eAAgB,EAAY,CAAuB,CAAE,CAAC,CAAC,CAAC,OAAO,EAS3F,SAAgB,EACf,EACA,EACA,EACC,CACD,IAAM,EAAO,EAAM,UACb,EAAoD,CACzD,OAAQ,EAAM,MAAM,OACpB,OAAQ,EAAM,MAAM,OACpB,QAAS,EAAM,MAAM,GACrB,IAAK,EAAM,MAAM,GACjB,KAAM,CACP,EACM,EAAoD,CACzD,OAAQ,YAAY,EAAK,SACzB,OAAQ,gCAAgC,EAAK,SAC7C,QAAS,2BAA2B,EAAK,SACzC,IAAK,aAAa,EAAK,eACvB,KAAM,QAAQ,EAAK,oBACpB,EACM,EAAyC,CAAC,EAChD,IAAK,IAAM,IAAQ,CAAC,SAAU,SAAU,UAAW,MAAO,MAAM,EAAY,CAC3E,IAAM,EAAU,EAAS,GACpB,IACL,EAAM,GAAG,EAAK,GAAG,KAAU,CAC1B,KAAM,GAAG,EAAK,GAAG,IACjB,YAAa,GAAS,eAAe,IAAS,EAAS,GACvD,KAAM,EAAM,GACZ,SACD,EACD,CAEA,OAAO,CACR"}
package/dist/auth.d.mts CHANGED
@@ -127,7 +127,8 @@ type IncludedQuery<Table extends GenericTableInfo> = {
127
127
  matching<IndexName extends IndexNames<Table>>(indexName: IndexName, indexRange?: (query: IndexRangeBuilder<DocumentByInfo<Table>, NamedIndex<Table, IndexName>>) => IndexRange, shouldMatch?: boolean): IncludedQuery<Table>;
128
128
  otherwise(select: (query: QueryInitializer<Table>) => Query<Table>): Query<Table>;
129
129
  resolve(): Query<Table>;
130
- execute<Result = Awaited<ReturnType<Query<Table>['take']>>>(limit: number, transform?: (data: Awaited<ReturnType<Query<Table>['take']>>) => Result | Promise<Result>): Promise<Result>;
130
+ execute(limit: number): Promise<DocumentByInfo<Table>[]>;
131
+ execute<Result>(limit: number, transform: (data: Awaited<ReturnType<Query<Table>['take']>>) => Result | Promise<Result>): Promise<Result>;
131
132
  /**
132
133
  * Cursor pagination over the selected indexed query. numItems is bounded
133
134
  * by the same maxRows policy as execute(). Under row-level security a
@@ -135,7 +136,8 @@ type IncludedQuery<Table extends GenericTableInfo> = {
135
136
  * pagination); cursors remain correct. Pair with paginated(dto) from
136
137
  * cvx-kit/zod-table for the boundary schemas.
137
138
  */
138
- paginate<Item = DocumentByInfo<Table>>(opts: PaginationOptions, transform?: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>): Promise<PaginationResult<Item>>;
139
+ paginate(opts: PaginationOptions): Promise<PaginationResult<DocumentByInfo<Table>>>;
140
+ paginate<Item>(opts: PaginationOptions, transform: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>): Promise<PaginationResult<Item>>;
139
141
  };
140
142
  type Include = <Table extends GenericTableInfo>(query: QueryInitializer<Table>) => IncludedQuery<Table>;
141
143
  /** Builds an include() that selects the first matching indexed query and bounds the read. */
package/dist/auth.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{defaultErrors as e}from"./errors.mjs";import{composeRules as t,createTenantRules as n,wrapDatabaseReader as r,wrapDatabaseWriter as i}from"./tenancy.mjs";import{zCustomAction as a,zCustomMutation as o,zCustomQuery as s}from"convex-helpers/server/zod4";function c(e){let t=e?.trim().toLowerCase();return t===`member`?`writer`:t===`reader`||t===`writer`||t===`admin`?t:null}function l(t){let n=t?.errors??e,r=t?.maxRows??100;function i(e,t){return{when:(n,r)=>t||n==null?i(e,t):i(e,r(e,n)),matching:(n,r,a=!0)=>t||!a?i(e,t):i(e,e.withIndex(n,r)),otherwise:n=>t??n(e),resolve:()=>t??e.fullTableScan(),execute:async(i,a)=>{if(!Number.isSafeInteger(i)||i<1||i>r)return n.throw({code:`INVALID_REQUEST_BOUNDARY`,message:`A bounded query limit must be from 1 to ${r}`});let o=await(t??e.fullTableScan()).take(i);return a?a(o):o},paginate:async(i,a)=>{if(!Number.isSafeInteger(i.numItems)||i.numItems<1||i.numItems>r)return n.throw({code:`INVALID_REQUEST_BOUNDARY`,message:`A page size must be from 1 to ${r}`});let o=await(t??e.fullTableScan()).paginate(i);return a?{...o,page:await a(o.page)}:o}}}return e=>i(e)}function u(c){let u=c.errors??e,d=c.triggers?e=>c.triggers.wrapDB(e):c.wrapDB??(e=>e),f=l({errors:u,maxRows:c.maxIncludedQueryRows});async function p(e){let t=await e.auth.getUserIdentity(),n=t?await c.getAuthUser(e):null;if(!t||!n)return u.throw({code:`UNAUTHENTICATED`});let r,i;if(c.resolveOrganization){let a=null;try{a=await c.resolveOrganization({ctx:e,identity:t,user:n})}catch{}if(!a)return u.throw({code:`FORBIDDEN`});r=a.organizationId,i=a.roleSlug}else{let e=t.organization,n=e&&typeof e==`object`&&`organizationId`in e&&typeof e.organizationId==`string`?e.organizationId:typeof t.org_id==`string`?t.org_id:null;if(!n)return u.throw({code:`UNAUTHENTICATED`});r=n,i=e&&typeof e==`object`&&`role`in e&&typeof e.role==`string`?e.role:typeof t.role==`string`?t.role:void 0}let a=c.mapRole(i);if(!a)return u.throw({code:`FORBIDDEN`});let o={identity:t,user:Object.freeze({id:n.id}),org:Object.freeze({organizationId:r,role:a}),role:a,actor:Object.freeze({userId:n.id,organizationId:r,role:a})};return Object.freeze({...o,tenant:c.security?.tenancy?.resolve?.(o)??r})}let m=c.security?{defaultPolicy:c.security.defaultPolicy??(c.security.tenancy?`deny`:`allow`)}:void 0;function h(e){let r=c.security;if(!r)return;let i=[];return r.tenancy&&i.push(n(e.tenant,r.tenancy.tables)),r.rules&&i.push(r.rules(e)),i.length===1?i[0]:t(...i)}function g(e,t){let n=h(t);return n?{...e,db:r({},e.db,n,m)}:e}function _(e,t){let n=h(t);return n?{...e,db:i({},e.db,n,m)}:e}async function v(e,t){if(!c.verifyMembership)return null;try{let n=await c.verifyMembership({userId:e,organizationId:t});if(n&&n.organizationId===t)return n}catch{}return u.throw({code:`FORBIDDEN`})}function y(e,t){if(!t.includes(e))return u.throw({code:`FORBIDDEN`})}async function b(e){let t=await p(e);return{ctx:{...g(e,t),include:f,...t},args:{}}}async function x(e){let t=await p(e);return{ctx:{..._(d(e),t),include:f,...t},args:{}}}async function S(e){let t=await p(e),n=await v(t.user.id,t.org.organizationId);if(!n)return{ctx:{...e,...t},args:{}};let r=c.mapRole(n.roleSlug);if(!r)return u.throw({code:`FORBIDDEN`});let i={identity:t.identity,user:t.user,org:Object.freeze({organizationId:n.organizationId,role:r}),role:r,actor:Object.freeze({userId:t.user.id,organizationId:n.organizationId,role:r})};return{ctx:{...e,...i,tenant:c.security?.tenancy?.resolve?.(i)??n.organizationId},args:{}}}function C(...e){return s(c.query,{args:{},input:async t=>{let n=await b(t);return y(n.ctx.actor.role,e),n}})}function w(...e){return o(c.mutation,{args:{},input:async t=>{let n=await x(t);return y(n.ctx.actor.role,e),n}})}function T(...e){return a(c.action,{args:{},input:async t=>{let n=await S(t);return y(n.ctx.actor.role,e),n}})}return{include:f,authenticatedUser:p,roleQuery:C,roleMutation:w,roleAction:T,authQuery:s(c.query,{args:{},input:b}),authMutation:o(c.mutation,{args:{},input:x}),authAction:a(c.action,{args:{},input:S}),adminQuery:C(...c.adminRoles),adminMutation:w(...c.adminRoles),adminAction:T(...c.adminRoles),systemQuery:s(c.internalQuery,{args:{},input:async e=>({ctx:{...e,include:f},args:{}})}),systemMutation:o(c.internalMutation,{args:{},input:async e=>({ctx:{...d(e),include:f},args:{}})}),systemAction:a(c.internalAction,{args:{},input:async e=>({ctx:e,args:{}})})}}export{u as createAuthFunctions,l as createInclude,c as defaultRoleMap};
1
+ import{defaultErrors as e}from"./errors.mjs";import{composeRules as t,createTenantRules as n,wrapDatabaseReader as r,wrapDatabaseWriter as i}from"./tenancy.mjs";import{zCustomAction as a,zCustomMutation as o,zCustomQuery as s}from"convex-helpers/server/zod4";import{z as c}from"zod";function l(e){let t=e?.trim().toLowerCase();return t===`member`?`writer`:t===`reader`||t===`writer`||t===`admin`?t:null}function u(t){let n=t?.errors??e,r=t?.maxRows??100;function i(e,t){async function a(i,a){if(!Number.isSafeInteger(i)||i<1||i>r)return n.throw({code:`INVALID_REQUEST_BOUNDARY`,message:`A bounded query limit must be from 1 to ${r}`});let o=await(t??e.fullTableScan()).take(i);return a?a(o):o}async function o(i,a){if(!Number.isSafeInteger(i.numItems)||i.numItems<1||i.numItems>r)return n.throw({code:`INVALID_REQUEST_BOUNDARY`,message:`A page size must be from 1 to ${r}`});let o=await(t??e.fullTableScan()).paginate(i);return a?{...o,page:await a(o.page)}:o}return{when:(n,r)=>t||n==null?i(e,t):i(e,r(e,n)),matching:(n,r,a=!0)=>t||!a?i(e,t):i(e,e.withIndex(n,r)),otherwise:n=>t??n(e),resolve:()=>t??e.fullTableScan(),execute:a,paginate:o}}return e=>i(e)}function d(c){let l=c.errors??e,d=c.triggers,p=d?e=>d.wrapDB(e):c.wrapDB??(e=>e),m=u({errors:l,maxRows:c.maxIncludedQueryRows});async function h(e){let t=await e.auth.getUserIdentity(),n=t?await c.getAuthUser(e):null;if(!t||!n)return l.throw({code:`UNAUTHENTICATED`});let r,i;if(c.resolveOrganization){let a=null;try{a=await c.resolveOrganization({ctx:e,identity:t,user:n})}catch{}if(!a)return l.throw({code:`FORBIDDEN`});r=a.organizationId,i=a.roleSlug}else{let e=f.parse(t),n=e.organization?.organizationId??e.org_id;if(!n)return l.throw({code:`UNAUTHENTICATED`});r=n,i=e.organization?.role??e.role}let a=c.mapRole(i);if(!a)return l.throw({code:`FORBIDDEN`});let o={identity:t,user:Object.freeze({id:n.id}),org:Object.freeze({organizationId:r,role:a}),role:a,actor:Object.freeze({userId:n.id,organizationId:r,role:a})};return Object.freeze({...o,tenant:c.security?.tenancy?.resolve?.(o)??r})}let g=c.security?{defaultPolicy:c.security.defaultPolicy??(c.security.tenancy?`deny`:`allow`)}:void 0;function _(e){let r=c.security;if(!r)return;let i=[];return r.tenancy&&i.push(n(e.tenant,r.tenancy.tables)),r.rules&&i.push(r.rules(e)),i.length===1?i[0]:t(...i)}function v(e,t){let n=_(t);return n?{...e,db:r({},e.db,n,g)}:e}function y(e,t){let n=_(t);return n?{...e,db:i({},e.db,n,g)}:e}async function b(e,t){if(!c.verifyMembership)return null;try{let n=await c.verifyMembership({userId:e,organizationId:t});if(n&&n.organizationId===t)return n}catch{}return l.throw({code:`FORBIDDEN`})}function x(e,t){if(!t.includes(e))return l.throw({code:`FORBIDDEN`})}async function S(e){let t=await h(e);return{ctx:{...v(e,t),include:m,...t},args:{}}}async function C(e){let t=await h(e);return{ctx:{...y(p(e),t),include:m,...t},args:{}}}async function w(e){let t=await h(e),n=await b(t.user.id,t.org.organizationId);if(!n)return{ctx:{...e,...t},args:{}};let r=c.mapRole(n.roleSlug);if(!r)return l.throw({code:`FORBIDDEN`});let i={identity:t.identity,user:t.user,org:Object.freeze({organizationId:n.organizationId,role:r}),role:r,actor:Object.freeze({userId:t.user.id,organizationId:n.organizationId,role:r})};return{ctx:{...e,...i,tenant:c.security?.tenancy?.resolve?.(i)??n.organizationId},args:{}}}function T(...e){return s(c.query,{args:{},input:async t=>{let n=await S(t);return x(n.ctx.actor.role,e),n}})}function E(...e){return o(c.mutation,{args:{},input:async t=>{let n=await C(t);return x(n.ctx.actor.role,e),n}})}function D(...e){return a(c.action,{args:{},input:async t=>{let n=await w(t);return x(n.ctx.actor.role,e),n}})}return{include:m,authenticatedUser:h,roleQuery:T,roleMutation:E,roleAction:D,authQuery:s(c.query,{args:{},input:S}),authMutation:o(c.mutation,{args:{},input:C}),authAction:a(c.action,{args:{},input:w}),adminQuery:T(...c.adminRoles),adminMutation:E(...c.adminRoles),adminAction:D(...c.adminRoles),systemQuery:s(c.internalQuery,{args:{},input:async e=>({ctx:{...e,include:m},args:{}})}),systemMutation:o(c.internalMutation,{args:{},input:async e=>({ctx:{...p(e),include:m},args:{}})}),systemAction:a(c.internalAction,{args:{},input:async e=>({ctx:e,args:{}})})}}const f=c.object({organization:c.object({organizationId:c.string().optional().catch(void 0),role:c.string().optional().catch(void 0)}).optional().catch(void 0),org_id:c.string().optional().catch(void 0),role:c.string().optional().catch(void 0)});export{d as createAuthFunctions,u as createInclude,l as defaultRoleMap};
2
2
  //# sourceMappingURL=auth.mjs.map
package/dist/auth.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["import {\n\tzCustomAction,\n\tzCustomMutation,\n\tzCustomQuery,\n} from 'convex-helpers/server/zod4'\nimport type {\n\tActionBuilder,\n\tDocumentByInfo,\n\tPaginationOptions,\n\tPaginationResult,\n\tGenericActionCtx,\n\tGenericDataModel,\n\tGenericMutationCtx,\n\tGenericQueryCtx,\n\tGenericTableInfo,\n\tIndexNames,\n\tIndexRange,\n\tIndexRangeBuilder,\n\tMutationBuilder,\n\tNamedIndex,\n\tQuery,\n\tQueryBuilder,\n\tQueryInitializer,\n\tUserIdentity,\n} from 'convex/server'\nimport type { Triggers } from 'convex-helpers/server/triggers'\nimport type { TableNamesInDataModel } from 'convex/server'\nimport { defaultErrors, type ErrorFactory } from './errors'\nimport {\n\tcomposeRules,\n\tcreateTenantRules,\n\twrapDatabaseReader,\n\twrapDatabaseWriter,\n\ttype RLSConfig,\n\ttype Rules,\n} from './tenancy'\n\n/** The default vocabulary; every app can supply its own via the Role generic. */\nexport type DefaultRole = 'reader' | 'writer' | 'admin'\n\nexport type Actor<Role extends string> = Readonly<{\n\tuserId: string\n\torganizationId: string\n\trole: Role\n}>\n\nexport type AuthBundle<Role extends string> = Readonly<{\n\tidentity: UserIdentity\n\tuser: Readonly<{ id: string }>\n\torg: Readonly<{ organizationId: string; role: Role }>\n\trole: Role\n\tactor: Actor<Role>\n\t/** The resolved tenant — security.tenancy.resolve, or the organization id. */\n\ttenant: string\n}>\n\n/**\n * Row-level security configuration. Tenant isolation and role-level rules\n * compose (AND) onto the same wrapped database: queries get a wrapped\n * reader, mutations get triggers first, then a wrapped writer.\n */\nexport type SecurityConfig<\n\tDataModel extends GenericDataModel,\n\tRole extends string,\n> = {\n\t/**\n\t * Tenant isolation. `tables` is THE registry: every listed table gets\n\t * read/insert/modify gated on row.tenant === ctx.tenant. Pair with\n\t * tenantTable (schema) and tenantOwnership (triggers) on the same list.\n\t */\n\ttenancy?: {\n\t\ttables: readonly TableNamesInDataModel<DataModel>[]\n\t\t/** Maps the auth bundle to the tenant; defaults to org.organizationId. */\n\t\tresolve?: (bundle: Omit<AuthBundle<Role>, 'tenant'>) => string\n\t}\n\t/**\n\t * Role-level (or any policy) rules derived from the authenticated bundle,\n\t * composed AND-wise with tenant isolation. E.g. gate `modify` on a table\n\t * to admin roles.\n\t */\n\trules?: (bundle: AuthBundle<Role>) => Rules<unknown, DataModel>\n\t/**\n\t * Policy for tables with no rule. Defaults to 'deny' when tenancy is\n\t * configured (unlisted tables are unreachable — fail closed), 'allow'\n\t * otherwise.\n\t */\n\tdefaultPolicy?: RLSConfig['defaultPolicy']\n}\n\ntype AnyAuthContext<DataModel extends GenericDataModel> =\n\t| GenericQueryCtx<DataModel>\n\t| GenericMutationCtx<DataModel>\n\t| GenericActionCtx<DataModel>\n\nexport type AuthFunctionsConfig<\n\tDataModel extends GenericDataModel,\n\tRole extends string,\n> = {\n\t/** The consumer's generated function builders (from ./_generated/server). */\n\tquery: QueryBuilder<DataModel, 'public'>\n\tmutation: MutationBuilder<DataModel, 'public'>\n\taction: ActionBuilder<DataModel, 'public'>\n\tinternalQuery: QueryBuilder<DataModel, 'internal'>\n\tinternalMutation: MutationBuilder<DataModel, 'internal'>\n\tinternalAction: ActionBuilder<DataModel, 'internal'>\n\t/** Resolves the synchronized principal, e.g. (ctx) => authKit.getAuthUser(ctx). */\n\tgetAuthUser: (\n\t\tctx: AnyAuthContext<DataModel>,\n\t) => Promise<{ id: string } | null>\n\t/**\n\t * Maps an identity role slug onto the app's role vocabulary. Returning\n\t * null rejects the caller with FORBIDDEN — unknown roles never pass.\n\t */\n\tmapRole: (roleSlug?: string) => Role | null\n\t/** Roles allowed through the admin* constructors. */\n\tadminRoles: readonly NoInfer<Role>[]\n\t/**\n\t * Live membership check for actions (queries/mutations trust the JWT).\n\t * Authentication fails closed with FORBIDDEN when verification errors.\n\t * Omit to let actions trust the JWT like queries do.\n\t */\n\tverifyMembership?: (input: {\n\t\tuserId: string\n\t\torganizationId: string\n\t}) => Promise<{ organizationId: string; roleSlug: string } | null>\n\t/**\n\t * Resolves organization authority from the app's own data for identity\n\t * providers whose tokens carry no org claims. When configured, its result\n\t * overrides claim-based resolution; null or an error rejects with FORBIDDEN\n\t * (fail closed). In actions the ctx has no db — resolve via ctx.runQuery.\n\t */\n\tresolveOrganization?: (input: {\n\t\tctx: AnyAuthContext<DataModel>\n\t\tidentity: UserIdentity\n\t\tuser: { id: string }\n\t}) => Promise<{ organizationId: string; roleSlug: string } | null>\n\t/** Host error policy; defaults to throwing KitError. */\n\terrors?: ErrorFactory\n\t/**\n\t * Application trigger registry. When set, every authMutation,\n\t * roleMutation, and systemMutation write runs through triggers.wrapDB —\n\t * evaluations registered on tables fire structurally.\n\t */\n\ttriggers?: Triggers<DataModel>\n\t/** Lower-level alternative to `triggers`: a custom ctx wrapper. */\n\twrapDB?: (\n\t\tctx: GenericMutationCtx<DataModel>,\n\t) => GenericMutationCtx<DataModel>\n\t/** Upper bound for include() reads. */\n\tmaxIncludedQueryRows?: number\n\t/**\n\t * Row-level security: tenant isolation (from a table registry) and\n\t * role-level rules, applied structurally inside every auth*, role*, and\n\t * admin* constructor. system* constructors stay unwrapped (trusted).\n\t */\n\tsecurity?: SecurityConfig<DataModel, Role>\n}\n\n/** WorkOS-style default: `member` writes; reader/writer/admin pass through. */\nexport function defaultRoleMap(roleSlug?: string): DefaultRole | null {\n\tconst normalized = roleSlug?.trim().toLowerCase()\n\tif (normalized === 'member') return 'writer'\n\treturn normalized === 'reader' ||\n\t\tnormalized === 'writer' ||\n\t\tnormalized === 'admin'\n\t\t? normalized\n\t\t: null\n}\n\nexport type IncludedQuery<Table extends GenericTableInfo> = {\n\twhen<Value>(\n\t\tvalue: Value | null | undefined,\n\t\tselect: (query: QueryInitializer<Table>, value: Value) => Query<Table>,\n\t): IncludedQuery<Table>\n\tmatching<IndexName extends IndexNames<Table>>(\n\t\tindexName: IndexName,\n\t\tindexRange?: (\n\t\t\tquery: IndexRangeBuilder<\n\t\t\t\tDocumentByInfo<Table>,\n\t\t\t\tNamedIndex<Table, IndexName>\n\t\t\t>,\n\t\t) => IndexRange,\n\t\tshouldMatch?: boolean,\n\t): IncludedQuery<Table>\n\totherwise(\n\t\tselect: (query: QueryInitializer<Table>) => Query<Table>,\n\t): Query<Table>\n\tresolve(): Query<Table>\n\texecute<Result = Awaited<ReturnType<Query<Table>['take']>>>(\n\t\tlimit: number,\n\t\ttransform?: (\n\t\t\tdata: Awaited<ReturnType<Query<Table>['take']>>,\n\t\t) => Result | Promise<Result>,\n\t): Promise<Result>\n\t/**\n\t * Cursor pagination over the selected indexed query. numItems is bounded\n\t * by the same maxRows policy as execute(). Under row-level security a\n\t * page may be shorter than numItems (rejected rows are filtered after\n\t * pagination); cursors remain correct. Pair with paginated(dto) from\n\t * cvx-kit/zod-table for the boundary schemas.\n\t */\n\tpaginate<Item = DocumentByInfo<Table>>(\n\t\topts: PaginationOptions,\n\t\ttransform?: (\n\t\t\tpage: DocumentByInfo<Table>[],\n\t\t) => Item[] | Promise<Item[]>,\n\t): Promise<PaginationResult<Item>>\n}\n\nexport type Include = <Table extends GenericTableInfo>(\n\tquery: QueryInitializer<Table>,\n) => IncludedQuery<Table>\n\n/** Builds an include() that selects the first matching indexed query and bounds the read. */\nexport function createInclude(options?: {\n\terrors?: ErrorFactory\n\tmaxRows?: number\n}): Include {\n\tconst errors = options?.errors ?? defaultErrors\n\tconst maxRows = options?.maxRows ?? 100\n\n\tfunction attach<Table extends GenericTableInfo>(\n\t\tquery: QueryInitializer<Table>,\n\t\tselected?: Query<Table>,\n\t): IncludedQuery<Table> {\n\t\treturn {\n\t\t\twhen: (value, select) =>\n\t\t\t\tselected || value === undefined || value === null\n\t\t\t\t\t? attach(query, selected)\n\t\t\t\t\t: attach(query, select(query, value)),\n\t\t\tmatching: (indexName, indexRange, shouldMatch = true) =>\n\t\t\t\tselected || !shouldMatch\n\t\t\t\t\t? attach(query, selected)\n\t\t\t\t\t: attach(query, query.withIndex(indexName, indexRange)),\n\t\t\totherwise: (select) => selected ?? select(query),\n\t\t\tresolve: () => selected ?? query.fullTableScan(),\n\t\t\texecute: async (limit, transform) => {\n\t\t\t\tif (!Number.isSafeInteger(limit) || limit < 1 || limit > maxRows) {\n\t\t\t\t\treturn errors.throw({\n\t\t\t\t\t\tcode: 'INVALID_REQUEST_BOUNDARY',\n\t\t\t\t\t\tmessage: `A bounded query limit must be from 1 to ${maxRows}`,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tconst data = await (selected ?? query.fullTableScan()).take(limit)\n\t\t\t\treturn transform ? transform(data) : (data as never)\n\t\t\t},\n\t\t\tpaginate: async (opts, transform) => {\n\t\t\t\tif (\n\t\t\t\t\t!Number.isSafeInteger(opts.numItems) ||\n\t\t\t\t\topts.numItems < 1 ||\n\t\t\t\t\topts.numItems > maxRows\n\t\t\t\t) {\n\t\t\t\t\treturn errors.throw({\n\t\t\t\t\t\tcode: 'INVALID_REQUEST_BOUNDARY',\n\t\t\t\t\t\tmessage: `A page size must be from 1 to ${maxRows}`,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tconst result = await (selected ?? query.fullTableScan()).paginate(opts)\n\t\t\t\tif (!transform) return result as never\n\t\t\t\treturn { ...result, page: await transform(result.page) } as never\n\t\t\t},\n\t\t}\n\t}\n\n\treturn (query) => attach(query)\n}\n\n/**\n * Builds the auth-aware function constructors from the host's injected\n * policy: identity resolution, the app's own role vocabulary, live\n * membership verification for actions, trigger wrapping for mutations,\n * and errors. Role-based access is first-class — every constructor family\n * has a role-gated variant (roleQuery/roleMutation/roleAction), and the\n * admin* constructors are just that gate applied to config.adminRoles.\n */\nexport function createAuthFunctions<\n\tDataModel extends GenericDataModel,\n\tconst Role extends string = DefaultRole,\n>(config: AuthFunctionsConfig<DataModel, Role>) {\n\tconst errors = config.errors ?? defaultErrors\n\tconst wrapDB = config.triggers\n\t\t? (ctx: GenericMutationCtx<DataModel>) =>\n\t\t\t\t(config.triggers as Triggers<DataModel>).wrapDB(ctx)\n\t\t: (config.wrapDB ?? ((ctx) => ctx))\n\tconst include = createInclude({\n\t\terrors,\n\t\tmaxRows: config.maxIncludedQueryRows,\n\t})\n\n\tasync function authenticatedUser(\n\t\tctx: AnyAuthContext<DataModel>,\n\t): Promise<AuthBundle<Role>> {\n\t\tconst identity = await ctx.auth.getUserIdentity()\n\t\tconst user = identity ? await config.getAuthUser(ctx) : null\n\t\tif (!identity || !user) return errors.throw({ code: 'UNAUTHENTICATED' })\n\t\tlet organizationId: string\n\t\tlet organizationRole: string | undefined\n\t\tif (config.resolveOrganization) {\n\t\t\t// The hook is the organization authority — claims are skipped\n\t\t\t// entirely. Authentication fails closed when it errors or abstains.\n\t\t\tlet resolved: { organizationId: string; roleSlug: string } | null = null\n\t\t\ttry {\n\t\t\t\tresolved = await config.resolveOrganization({ ctx, identity, user })\n\t\t\t} catch {\n\t\t\t\t// Authentication fails closed when the hook cannot resolve.\n\t\t\t}\n\t\t\tif (!resolved) return errors.throw({ code: 'FORBIDDEN' })\n\t\t\torganizationId = resolved.organizationId\n\t\t\torganizationRole = resolved.roleSlug\n\t\t} else {\n\t\t\tconst organization = identity.organization\n\t\t\tconst claimedOrganizationId =\n\t\t\t\torganization &&\n\t\t\t\ttypeof organization === 'object' &&\n\t\t\t\t'organizationId' in organization &&\n\t\t\t\ttypeof organization.organizationId === 'string'\n\t\t\t\t\t? organization.organizationId\n\t\t\t\t\t: typeof identity.org_id === 'string'\n\t\t\t\t\t\t? identity.org_id\n\t\t\t\t\t\t: null\n\t\t\tif (!claimedOrganizationId) {\n\t\t\t\treturn errors.throw({ code: 'UNAUTHENTICATED' })\n\t\t\t}\n\t\t\torganizationId = claimedOrganizationId\n\t\t\torganizationRole =\n\t\t\t\torganization &&\n\t\t\t\ttypeof organization === 'object' &&\n\t\t\t\t'role' in organization &&\n\t\t\t\ttypeof organization.role === 'string'\n\t\t\t\t\t? organization.role\n\t\t\t\t\t: typeof identity.role === 'string'\n\t\t\t\t\t\t? identity.role\n\t\t\t\t\t\t: undefined\n\t\t}\n\t\tconst role = config.mapRole(organizationRole)\n\t\tif (!role) return errors.throw({ code: 'FORBIDDEN' })\n\t\tconst base = {\n\t\t\tidentity,\n\t\t\tuser: Object.freeze({ id: user.id }),\n\t\t\torg: Object.freeze({ organizationId, role }),\n\t\t\trole,\n\t\t\tactor: Object.freeze({ userId: user.id, organizationId, role }),\n\t\t}\n\t\treturn Object.freeze({\n\t\t\t...base,\n\t\t\ttenant: config.security?.tenancy?.resolve?.(base) ?? organizationId,\n\t\t})\n\t}\n\n\tconst securityConfig: RLSConfig | undefined = config.security\n\t\t? {\n\t\t\t\tdefaultPolicy:\n\t\t\t\t\tconfig.security.defaultPolicy ??\n\t\t\t\t\t(config.security.tenancy ? 'deny' : 'allow'),\n\t\t\t}\n\t\t: undefined\n\n\tfunction securityRules(\n\t\tbundle: AuthBundle<Role>,\n\t): Rules<unknown, DataModel> | undefined {\n\t\tconst security = config.security\n\t\tif (!security) return undefined\n\t\tconst sets: Rules<unknown, DataModel>[] = []\n\t\tif (security.tenancy) {\n\t\t\tsets.push(\n\t\t\t\tcreateTenantRules<DataModel>(bundle.tenant, security.tenancy.tables),\n\t\t\t)\n\t\t}\n\t\tif (security.rules) sets.push(security.rules(bundle))\n\t\treturn sets.length === 1 ? sets[0] : composeRules(...sets)\n\t}\n\n\tfunction secureReader(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\tbundle: AuthBundle<Role>,\n\t): GenericQueryCtx<DataModel> {\n\t\tconst rules = securityRules(bundle)\n\t\tif (!rules) return ctx\n\t\treturn {\n\t\t\t...ctx,\n\t\t\tdb: wrapDatabaseReader({}, ctx.db, rules as never, securityConfig),\n\t\t}\n\t}\n\n\tfunction secureWriter(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tbundle: AuthBundle<Role>,\n\t): GenericMutationCtx<DataModel> {\n\t\tconst rules = securityRules(bundle)\n\t\tif (!rules) return ctx\n\t\t// Triggers wrap first (ctx already trigger-wrapped by the caller),\n\t\t// then row-level security wraps the triggered db — order matters.\n\t\treturn {\n\t\t\t...ctx,\n\t\t\tdb: wrapDatabaseWriter({}, ctx.db, rules as never, securityConfig),\n\t\t}\n\t}\n\n\tasync function verifiedMembership(userId: string, organizationId: string) {\n\t\tif (!config.verifyMembership) return null\n\t\ttry {\n\t\t\tconst membership = await config.verifyMembership({\n\t\t\t\tuserId,\n\t\t\t\torganizationId,\n\t\t\t})\n\t\t\tif (membership && membership.organizationId === organizationId) {\n\t\t\t\treturn membership\n\t\t\t}\n\t\t} catch {\n\t\t\t// Authentication fails closed when membership cannot be verified.\n\t\t}\n\t\treturn errors.throw({ code: 'FORBIDDEN' })\n\t}\n\n\tfunction requireRole(role: Role, allowed: readonly Role[]) {\n\t\tif (!allowed.includes(role)) return errors.throw({ code: 'FORBIDDEN' })\n\t}\n\n\tasync function queryInput(ctx: GenericQueryCtx<DataModel>) {\n\t\tconst bundle = await authenticatedUser(ctx)\n\t\treturn {\n\t\t\tctx: { ...secureReader(ctx, bundle), include, ...bundle },\n\t\t\targs: {},\n\t\t}\n\t}\n\n\tasync function mutationInput(ctx: GenericMutationCtx<DataModel>) {\n\t\tconst bundle = await authenticatedUser(ctx)\n\t\treturn {\n\t\t\tctx: { ...secureWriter(wrapDB(ctx), bundle), include, ...bundle },\n\t\t\targs: {},\n\t\t}\n\t}\n\n\tasync function actionInput(ctx: GenericActionCtx<DataModel>) {\n\t\tconst authenticated = await authenticatedUser(ctx)\n\t\tconst membership = await verifiedMembership(\n\t\t\tauthenticated.user.id,\n\t\t\tauthenticated.org.organizationId,\n\t\t)\n\t\tif (!membership) return { ctx: { ...ctx, ...authenticated }, args: {} }\n\t\tconst role = config.mapRole(membership.roleSlug)\n\t\tif (!role) return errors.throw({ code: 'FORBIDDEN' })\n\t\tconst verified = {\n\t\t\tidentity: authenticated.identity,\n\t\t\tuser: authenticated.user,\n\t\t\torg: Object.freeze({\n\t\t\t\torganizationId: membership.organizationId,\n\t\t\t\trole,\n\t\t\t}),\n\t\t\trole,\n\t\t\tactor: Object.freeze({\n\t\t\t\tuserId: authenticated.user.id,\n\t\t\t\torganizationId: membership.organizationId,\n\t\t\t\trole,\n\t\t\t}),\n\t\t}\n\t\treturn {\n\t\t\tctx: {\n\t\t\t\t...ctx,\n\t\t\t\t...verified,\n\t\t\t\t// Tenant re-derives from the live-verified organization.\n\t\t\t\ttenant:\n\t\t\t\t\tconfig.security?.tenancy?.resolve?.(verified) ??\n\t\t\t\t\tmembership.organizationId,\n\t\t\t},\n\t\t\targs: {},\n\t\t}\n\t}\n\n\t/** Query constructor allowing only the given roles. */\n\tfunction roleQuery(...allowed: readonly Role[]) {\n\t\treturn zCustomQuery(config.query, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await queryInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\t/** Mutation constructor allowing only the given roles. */\n\tfunction roleMutation(...allowed: readonly Role[]) {\n\t\treturn zCustomMutation(config.mutation, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await mutationInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\t/** Action constructor allowing only the given roles (post live verification). */\n\tfunction roleAction(...allowed: readonly Role[]) {\n\t\treturn zCustomAction(config.action, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await actionInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\treturn {\n\t\tinclude,\n\t\tauthenticatedUser,\n\t\troleQuery,\n\t\troleMutation,\n\t\troleAction,\n\t\tauthQuery: zCustomQuery(config.query, { args: {}, input: queryInput }),\n\t\tauthMutation: zCustomMutation(config.mutation, {\n\t\t\targs: {},\n\t\t\tinput: mutationInput,\n\t\t}),\n\t\tauthAction: zCustomAction(config.action, {\n\t\t\targs: {},\n\t\t\tinput: actionInput,\n\t\t}),\n\t\tadminQuery: roleQuery(...config.adminRoles),\n\t\tadminMutation: roleMutation(...config.adminRoles),\n\t\tadminAction: roleAction(...config.adminRoles),\n\t\tsystemQuery: zCustomQuery(config.internalQuery, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx: { ...ctx, include }, args: {} }),\n\t\t}),\n\t\tsystemMutation: zCustomMutation(config.internalMutation, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx: { ...wrapDB(ctx), include }, args: {} }),\n\t\t}),\n\t\tsystemAction: zCustomAction(config.internalAction, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx, args: {} }),\n\t\t}),\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["import { z } from 'zod'\nimport { zCustomAction, zCustomMutation, zCustomQuery } from 'convex-helpers/server/zod4'\nimport type {\n\tActionBuilder,\n\tDocumentByInfo,\n\tPaginationOptions,\n\tPaginationResult,\n\tGenericActionCtx,\n\tGenericDataModel,\n\tGenericMutationCtx,\n\tGenericQueryCtx,\n\tGenericTableInfo,\n\tIndexNames,\n\tIndexRange,\n\tIndexRangeBuilder,\n\tMutationBuilder,\n\tNamedIndex,\n\tQuery,\n\tQueryBuilder,\n\tQueryInitializer,\n\tUserIdentity,\n} from 'convex/server'\nimport type { Triggers } from 'convex-helpers/server/triggers'\nimport type { TableNamesInDataModel } from 'convex/server'\nimport { defaultErrors, type ErrorFactory } from './errors'\nimport {\n\tcomposeRules,\n\tcreateTenantRules,\n\twrapDatabaseReader,\n\twrapDatabaseWriter,\n\ttype RLSConfig,\n\ttype Rules,\n} from './tenancy'\n\n/** The default vocabulary; every app can supply its own via the Role generic. */\nexport type DefaultRole = 'reader' | 'writer' | 'admin'\n\nexport type Actor<Role extends string> = Readonly<{\n\tuserId: string\n\torganizationId: string\n\trole: Role\n}>\n\nexport type AuthBundle<Role extends string> = Readonly<{\n\tidentity: UserIdentity\n\tuser: Readonly<{ id: string }>\n\torg: Readonly<{ organizationId: string; role: Role }>\n\trole: Role\n\tactor: Actor<Role>\n\t/** The resolved tenant — security.tenancy.resolve, or the organization id. */\n\ttenant: string\n}>\n\n/**\n * Row-level security configuration. Tenant isolation and role-level rules\n * compose (AND) onto the same wrapped database: queries get a wrapped\n * reader, mutations get triggers first, then a wrapped writer.\n */\nexport type SecurityConfig<DataModel extends GenericDataModel, Role extends string> = {\n\t/**\n\t * Tenant isolation. `tables` is THE registry: every listed table gets\n\t * read/insert/modify gated on row.tenant === ctx.tenant. Pair with\n\t * tenantTable (schema) and tenantOwnership (triggers) on the same list.\n\t */\n\ttenancy?: {\n\t\ttables: readonly TableNamesInDataModel<DataModel>[]\n\t\t/** Maps the auth bundle to the tenant; defaults to org.organizationId. */\n\t\tresolve?: (bundle: Omit<AuthBundle<Role>, 'tenant'>) => string\n\t}\n\t/**\n\t * Role-level (or any policy) rules derived from the authenticated bundle,\n\t * composed AND-wise with tenant isolation. E.g. gate `modify` on a table\n\t * to admin roles.\n\t */\n\trules?: (bundle: AuthBundle<Role>) => Rules<unknown, DataModel>\n\t/**\n\t * Policy for tables with no rule. Defaults to 'deny' when tenancy is\n\t * configured (unlisted tables are unreachable — fail closed), 'allow'\n\t * otherwise.\n\t */\n\tdefaultPolicy?: RLSConfig['defaultPolicy']\n}\n\ntype AnyAuthContext<DataModel extends GenericDataModel> =\n\t| GenericQueryCtx<DataModel>\n\t| GenericMutationCtx<DataModel>\n\t| GenericActionCtx<DataModel>\n\nexport type AuthFunctionsConfig<DataModel extends GenericDataModel, Role extends string> = {\n\t/** The consumer's generated function builders (from ./_generated/server). */\n\tquery: QueryBuilder<DataModel, 'public'>\n\tmutation: MutationBuilder<DataModel, 'public'>\n\taction: ActionBuilder<DataModel, 'public'>\n\tinternalQuery: QueryBuilder<DataModel, 'internal'>\n\tinternalMutation: MutationBuilder<DataModel, 'internal'>\n\tinternalAction: ActionBuilder<DataModel, 'internal'>\n\t/** Resolves the synchronized principal, e.g. (ctx) => authKit.getAuthUser(ctx). */\n\tgetAuthUser: (ctx: AnyAuthContext<DataModel>) => Promise<{ id: string } | null>\n\t/**\n\t * Maps an identity role slug onto the app's role vocabulary. Returning\n\t * null rejects the caller with FORBIDDEN — unknown roles never pass.\n\t */\n\tmapRole: (roleSlug?: string) => Role | null\n\t/** Roles allowed through the admin* constructors. */\n\tadminRoles: readonly NoInfer<Role>[]\n\t/**\n\t * Live membership check for actions (queries/mutations trust the JWT).\n\t * Authentication fails closed with FORBIDDEN when verification errors.\n\t * Omit to let actions trust the JWT like queries do.\n\t */\n\tverifyMembership?: (input: {\n\t\tuserId: string\n\t\torganizationId: string\n\t}) => Promise<{ organizationId: string; roleSlug: string } | null>\n\t/**\n\t * Resolves organization authority from the app's own data for identity\n\t * providers whose tokens carry no org claims. When configured, its result\n\t * overrides claim-based resolution; null or an error rejects with FORBIDDEN\n\t * (fail closed). In actions the ctx has no db — resolve via ctx.runQuery.\n\t */\n\tresolveOrganization?: (input: {\n\t\tctx: AnyAuthContext<DataModel>\n\t\tidentity: UserIdentity\n\t\tuser: { id: string }\n\t}) => Promise<{ organizationId: string; roleSlug: string } | null>\n\t/** Host error policy; defaults to throwing KitError. */\n\terrors?: ErrorFactory\n\t/**\n\t * Application trigger registry. When set, every authMutation,\n\t * roleMutation, and systemMutation write runs through triggers.wrapDB —\n\t * evaluations registered on tables fire structurally.\n\t */\n\ttriggers?: Triggers<DataModel>\n\t/** Lower-level alternative to `triggers`: a custom ctx wrapper. */\n\twrapDB?: (ctx: GenericMutationCtx<DataModel>) => GenericMutationCtx<DataModel>\n\t/** Upper bound for include() reads. */\n\tmaxIncludedQueryRows?: number\n\t/**\n\t * Row-level security: tenant isolation (from a table registry) and\n\t * role-level rules, applied structurally inside every auth*, role*, and\n\t * admin* constructor. system* constructors stay unwrapped (trusted).\n\t */\n\tsecurity?: SecurityConfig<DataModel, Role>\n}\n\n/** WorkOS-style default: `member` writes; reader/writer/admin pass through. */\nexport function defaultRoleMap(roleSlug?: string): DefaultRole | null {\n\tconst normalized = roleSlug?.trim().toLowerCase()\n\tif (normalized === 'member') return 'writer'\n\treturn normalized === 'reader' || normalized === 'writer' || normalized === 'admin'\n\t\t? normalized\n\t\t: null\n}\n\nexport type IncludedQuery<Table extends GenericTableInfo> = {\n\twhen<Value>(\n\t\tvalue: Value | null | undefined,\n\t\tselect: (query: QueryInitializer<Table>, value: Value) => Query<Table>,\n\t): IncludedQuery<Table>\n\tmatching<IndexName extends IndexNames<Table>>(\n\t\tindexName: IndexName,\n\t\tindexRange?: (\n\t\t\tquery: IndexRangeBuilder<DocumentByInfo<Table>, NamedIndex<Table, IndexName>>,\n\t\t) => IndexRange,\n\t\tshouldMatch?: boolean,\n\t): IncludedQuery<Table>\n\totherwise(select: (query: QueryInitializer<Table>) => Query<Table>): Query<Table>\n\tresolve(): Query<Table>\n\texecute(limit: number): Promise<DocumentByInfo<Table>[]>\n\texecute<Result>(\n\t\tlimit: number,\n\t\ttransform: (data: Awaited<ReturnType<Query<Table>['take']>>) => Result | Promise<Result>,\n\t): Promise<Result>\n\t/**\n\t * Cursor pagination over the selected indexed query. numItems is bounded\n\t * by the same maxRows policy as execute(). Under row-level security a\n\t * page may be shorter than numItems (rejected rows are filtered after\n\t * pagination); cursors remain correct. Pair with paginated(dto) from\n\t * cvx-kit/zod-table for the boundary schemas.\n\t */\n\tpaginate(opts: PaginationOptions): Promise<PaginationResult<DocumentByInfo<Table>>>\n\tpaginate<Item>(\n\t\topts: PaginationOptions,\n\t\ttransform: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t): Promise<PaginationResult<Item>>\n}\n\nexport type Include = <Table extends GenericTableInfo>(\n\tquery: QueryInitializer<Table>,\n) => IncludedQuery<Table>\n\n/** Builds an include() that selects the first matching indexed query and bounds the read. */\nexport function createInclude(options?: { errors?: ErrorFactory; maxRows?: number }): Include {\n\tconst errors = options?.errors ?? defaultErrors\n\tconst maxRows = options?.maxRows ?? 100\n\n\tfunction attach<Table extends GenericTableInfo>(\n\t\tquery: QueryInitializer<Table>,\n\t\tselected?: Query<Table>,\n\t): IncludedQuery<Table> {\n\t\tfunction execute(limit: number): Promise<DocumentByInfo<Table>[]>\n\t\tfunction execute<Result>(\n\t\t\tlimit: number,\n\t\t\ttransform: (data: DocumentByInfo<Table>[]) => Result | Promise<Result>,\n\t\t): Promise<Result>\n\t\tasync function execute<Result>(\n\t\t\tlimit: number,\n\t\t\ttransform?: (data: DocumentByInfo<Table>[]) => Result | Promise<Result>,\n\t\t) {\n\t\t\tif (!Number.isSafeInteger(limit) || limit < 1 || limit > maxRows) {\n\t\t\t\treturn errors.throw({\n\t\t\t\t\tcode: 'INVALID_REQUEST_BOUNDARY',\n\t\t\t\t\tmessage: `A bounded query limit must be from 1 to ${maxRows}`,\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst data = await (selected ?? query.fullTableScan()).take(limit)\n\t\t\treturn transform ? transform(data) : data\n\t\t}\n\t\tfunction paginate(opts: PaginationOptions): Promise<PaginationResult<DocumentByInfo<Table>>>\n\t\tfunction paginate<Item>(\n\t\t\topts: PaginationOptions,\n\t\t\ttransform: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t\t): Promise<PaginationResult<Item>>\n\t\tasync function paginate<Item>(\n\t\t\topts: PaginationOptions,\n\t\t\ttransform?: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t\t) {\n\t\t\tif (!Number.isSafeInteger(opts.numItems) || opts.numItems < 1 || opts.numItems > maxRows) {\n\t\t\t\treturn errors.throw({\n\t\t\t\t\tcode: 'INVALID_REQUEST_BOUNDARY',\n\t\t\t\t\tmessage: `A page size must be from 1 to ${maxRows}`,\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst result = await (selected ?? query.fullTableScan()).paginate(opts)\n\t\t\treturn transform ? { ...result, page: await transform(result.page) } : result\n\t\t}\n\t\treturn {\n\t\t\twhen: (value, select) =>\n\t\t\t\tselected || value === undefined || value === null\n\t\t\t\t\t? attach(query, selected)\n\t\t\t\t\t: attach(query, select(query, value)),\n\t\t\tmatching: (indexName, indexRange, shouldMatch = true) =>\n\t\t\t\tselected || !shouldMatch\n\t\t\t\t\t? attach(query, selected)\n\t\t\t\t\t: attach(query, query.withIndex(indexName, indexRange)),\n\t\t\totherwise: (select) => selected ?? select(query),\n\t\t\tresolve: () => selected ?? query.fullTableScan(),\n\t\t\texecute,\n\t\t\tpaginate,\n\t\t}\n\t}\n\n\treturn (query) => attach(query)\n}\n\n/**\n * Builds the auth-aware function constructors from the host's injected\n * policy: identity resolution, the app's own role vocabulary, live\n * membership verification for actions, trigger wrapping for mutations,\n * and errors. Role-based access is first-class — every constructor family\n * has a role-gated variant (roleQuery/roleMutation/roleAction), and the\n * admin* constructors are just that gate applied to config.adminRoles.\n */\nexport function createAuthFunctions<\n\tDataModel extends GenericDataModel,\n\tconst Role extends string = DefaultRole,\n>(config: AuthFunctionsConfig<DataModel, Role>) {\n\tconst errors = config.errors ?? defaultErrors\n\tconst triggers = config.triggers\n\tconst wrapDB = triggers\n\t\t? (ctx: GenericMutationCtx<DataModel>) => triggers.wrapDB(ctx)\n\t\t: (config.wrapDB ?? ((ctx) => ctx))\n\tconst include = createInclude({\n\t\terrors,\n\t\tmaxRows: config.maxIncludedQueryRows,\n\t})\n\n\tasync function authenticatedUser(ctx: AnyAuthContext<DataModel>): Promise<AuthBundle<Role>> {\n\t\tconst identity = await ctx.auth.getUserIdentity()\n\t\tconst user = identity ? await config.getAuthUser(ctx) : null\n\t\tif (!identity || !user) return errors.throw({ code: 'UNAUTHENTICATED' })\n\t\tlet organizationId: string\n\t\tlet organizationRole: string | undefined\n\t\tif (config.resolveOrganization) {\n\t\t\t// The hook is the organization authority — claims are skipped\n\t\t\t// entirely. Authentication fails closed when it errors or abstains.\n\t\t\tlet resolved: { organizationId: string; roleSlug: string } | null = null\n\t\t\ttry {\n\t\t\t\tresolved = await config.resolveOrganization({ ctx, identity, user })\n\t\t\t} catch {\n\t\t\t\t// Authentication fails closed when the hook cannot resolve.\n\t\t\t}\n\t\t\tif (!resolved) return errors.throw({ code: 'FORBIDDEN' })\n\t\t\torganizationId = resolved.organizationId\n\t\t\torganizationRole = resolved.roleSlug\n\t\t} else {\n\t\t\tconst claims = organizationClaims.parse(identity)\n\t\t\tconst claimedOrganizationId = claims.organization?.organizationId ?? claims.org_id\n\t\t\tif (!claimedOrganizationId) return errors.throw({ code: 'UNAUTHENTICATED' })\n\t\t\torganizationId = claimedOrganizationId\n\t\t\torganizationRole = claims.organization?.role ?? claims.role\n\t\t}\n\t\tconst role = config.mapRole(organizationRole)\n\t\tif (!role) return errors.throw({ code: 'FORBIDDEN' })\n\t\tconst base = {\n\t\t\tidentity,\n\t\t\tuser: Object.freeze({ id: user.id }),\n\t\t\torg: Object.freeze({ organizationId, role }),\n\t\t\trole,\n\t\t\tactor: Object.freeze({ userId: user.id, organizationId, role }),\n\t\t}\n\t\treturn Object.freeze({\n\t\t\t...base,\n\t\t\ttenant: config.security?.tenancy?.resolve?.(base) ?? organizationId,\n\t\t})\n\t}\n\n\tconst securityConfig: RLSConfig | undefined = config.security\n\t\t? {\n\t\t\t\tdefaultPolicy:\n\t\t\t\t\tconfig.security.defaultPolicy ?? (config.security.tenancy ? 'deny' : 'allow'),\n\t\t\t}\n\t\t: undefined\n\n\tfunction securityRules(bundle: AuthBundle<Role>): Rules<unknown, DataModel> | undefined {\n\t\tconst security = config.security\n\t\tif (!security) return undefined\n\t\tconst sets: Rules<unknown, DataModel>[] = []\n\t\tif (security.tenancy) {\n\t\t\tsets.push(createTenantRules<DataModel>(bundle.tenant, security.tenancy.tables))\n\t\t}\n\t\tif (security.rules) sets.push(security.rules(bundle))\n\t\treturn sets.length === 1 ? sets[0] : composeRules(...sets)\n\t}\n\n\tfunction secureReader(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\tbundle: AuthBundle<Role>,\n\t): GenericQueryCtx<DataModel> {\n\t\tconst rules = securityRules(bundle)\n\t\tif (!rules) return ctx\n\t\treturn {\n\t\t\t...ctx,\n\t\t\tdb: wrapDatabaseReader({}, ctx.db, rules, securityConfig),\n\t\t}\n\t}\n\n\tfunction secureWriter(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tbundle: AuthBundle<Role>,\n\t): GenericMutationCtx<DataModel> {\n\t\tconst rules = securityRules(bundle)\n\t\tif (!rules) return ctx\n\t\t// Triggers wrap first (ctx already trigger-wrapped by the caller),\n\t\t// then row-level security wraps the triggered db — order matters.\n\t\treturn {\n\t\t\t...ctx,\n\t\t\tdb: wrapDatabaseWriter({}, ctx.db, rules, securityConfig),\n\t\t}\n\t}\n\n\tasync function verifiedMembership(userId: string, organizationId: string) {\n\t\tif (!config.verifyMembership) return null\n\t\ttry {\n\t\t\tconst membership = await config.verifyMembership({\n\t\t\t\tuserId,\n\t\t\t\torganizationId,\n\t\t\t})\n\t\t\tif (membership && membership.organizationId === organizationId) {\n\t\t\t\treturn membership\n\t\t\t}\n\t\t} catch {\n\t\t\t// Authentication fails closed when membership cannot be verified.\n\t\t}\n\t\treturn errors.throw({ code: 'FORBIDDEN' })\n\t}\n\n\tfunction requireRole(role: Role, allowed: readonly Role[]) {\n\t\tif (!allowed.includes(role)) return errors.throw({ code: 'FORBIDDEN' })\n\t}\n\n\tasync function queryInput(ctx: GenericQueryCtx<DataModel>) {\n\t\tconst bundle = await authenticatedUser(ctx)\n\t\treturn {\n\t\t\tctx: { ...secureReader(ctx, bundle), include, ...bundle },\n\t\t\targs: {},\n\t\t}\n\t}\n\n\tasync function mutationInput(ctx: GenericMutationCtx<DataModel>) {\n\t\tconst bundle = await authenticatedUser(ctx)\n\t\treturn {\n\t\t\tctx: { ...secureWriter(wrapDB(ctx), bundle), include, ...bundle },\n\t\t\targs: {},\n\t\t}\n\t}\n\n\tasync function actionInput(ctx: GenericActionCtx<DataModel>) {\n\t\tconst authenticated = await authenticatedUser(ctx)\n\t\tconst membership = await verifiedMembership(\n\t\t\tauthenticated.user.id,\n\t\t\tauthenticated.org.organizationId,\n\t\t)\n\t\tif (!membership) return { ctx: { ...ctx, ...authenticated }, args: {} }\n\t\tconst role = config.mapRole(membership.roleSlug)\n\t\tif (!role) return errors.throw({ code: 'FORBIDDEN' })\n\t\tconst verified = {\n\t\t\tidentity: authenticated.identity,\n\t\t\tuser: authenticated.user,\n\t\t\torg: Object.freeze({\n\t\t\t\torganizationId: membership.organizationId,\n\t\t\t\trole,\n\t\t\t}),\n\t\t\trole,\n\t\t\tactor: Object.freeze({\n\t\t\t\tuserId: authenticated.user.id,\n\t\t\t\torganizationId: membership.organizationId,\n\t\t\t\trole,\n\t\t\t}),\n\t\t}\n\t\treturn {\n\t\t\tctx: {\n\t\t\t\t...ctx,\n\t\t\t\t...verified,\n\t\t\t\t// Tenant re-derives from the live-verified organization.\n\t\t\t\ttenant: config.security?.tenancy?.resolve?.(verified) ?? membership.organizationId,\n\t\t\t},\n\t\t\targs: {},\n\t\t}\n\t}\n\n\t/** Query constructor allowing only the given roles. */\n\tfunction roleQuery(...allowed: readonly Role[]) {\n\t\treturn zCustomQuery(config.query, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await queryInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\t/** Mutation constructor allowing only the given roles. */\n\tfunction roleMutation(...allowed: readonly Role[]) {\n\t\treturn zCustomMutation(config.mutation, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await mutationInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\t/** Action constructor allowing only the given roles (post live verification). */\n\tfunction roleAction(...allowed: readonly Role[]) {\n\t\treturn zCustomAction(config.action, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await actionInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\treturn {\n\t\tinclude,\n\t\tauthenticatedUser,\n\t\troleQuery,\n\t\troleMutation,\n\t\troleAction,\n\t\tauthQuery: zCustomQuery(config.query, { args: {}, input: queryInput }),\n\t\tauthMutation: zCustomMutation(config.mutation, {\n\t\t\targs: {},\n\t\t\tinput: mutationInput,\n\t\t}),\n\t\tauthAction: zCustomAction(config.action, {\n\t\t\targs: {},\n\t\t\tinput: actionInput,\n\t\t}),\n\t\tadminQuery: roleQuery(...config.adminRoles),\n\t\tadminMutation: roleMutation(...config.adminRoles),\n\t\tadminAction: roleAction(...config.adminRoles),\n\t\tsystemQuery: zCustomQuery(config.internalQuery, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx: { ...ctx, include }, args: {} }),\n\t\t}),\n\t\tsystemMutation: zCustomMutation(config.internalMutation, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx: { ...wrapDB(ctx), include }, args: {} }),\n\t\t}),\n\t\tsystemAction: zCustomAction(config.internalAction, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx, args: {} }),\n\t\t}),\n\t}\n}\n\nconst organizationClaims = z.object({\n\torganization: z\n\t\t.object({\n\t\t\torganizationId: z.string().optional().catch(undefined),\n\t\t\trole: z.string().optional().catch(undefined),\n\t\t})\n\t\t.optional()\n\t\t.catch(undefined),\n\torg_id: z.string().optional().catch(undefined),\n\trole: z.string().optional().catch(undefined),\n})\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { audit_d_exports } from "../audit.mjs";
2
- import { constants_d_exports } from "../constants.mjs";
3
2
  import { validators_d_exports } from "../validators.mjs";
3
+ import { constants_d_exports } from "../constants.mjs";
4
4
  import { workflow_steps_d_exports } from "../workflow_steps.mjs";
5
5
  import { client_d_exports } from "../client.mjs";
6
6
  import { decisions_d_exports } from "../decisions.mjs";
@@ -1,2 +1,2 @@
1
- import{components as e}from"./_generated/api.mjs";import{mutation as t,query as n}from"./_generated/server.mjs";import{APPROVAL_AUDIT_RETENTION_CATEGORY as r,MAX_APPROVAL_AUDIT_RETENTION_DAYS as i}from"./constants.mjs";import{v as a}from"convex/values";import{AuditLog as o}from"convex-audit-log";const s=new o(e.auditLog,{samplingEnabled:!1}),c=n({args:{resourceType:a.string(),resourceRef:a.string(),limit:a.optional(a.number())},returns:a.array(a.object({action:a.string(),actorRef:a.optional(a.string()),correlationKey:a.string(),transition:a.string()})),handler:async(e,t)=>{let n=Math.max(1,Math.min(Math.trunc(t.limit??100),100)),r=await s.queryByResource(e,{resourceType:t.resourceType,resourceId:t.resourceRef,limit:n}),i=new Map;for(let e of r){let t=p(e);t&&i.set(t.correlationKey,t)}return[...i.values()]}}),l=t({args:{olderThanDays:a.number(),batchSize:a.number()},returns:a.number(),handler:(e,t)=>{let n=f(t.olderThanDays,1,i,`olderThanDays`),a=f(t.batchSize,1,100,`batchSize`);return s.cleanup(e,{olderThanDays:n,batchSize:a,retentionCategory:r})}});function u(e){return{action:e.action,...e.actorRef===void 0?{}:{actorId:e.actorRef},resourceType:e.resourceType,resourceId:e.resourceRef,severity:e.severity??`info`,tags:[`approvals`],retentionCategory:r,metadata:{correlationKey:`${e.runId}:${e.transition}`,runId:e.runId,transition:e.transition}}}async function d(e,t){await s.log(e,u(t))}function f(e,t,n,r){if(!Number.isInteger(e)||e<t||e>n)throw Error(`${r} must be an integer between ${t} and ${n}`);return e}function p(e){if(!e||typeof e!=`object`)return null;let t=e,n=t.metadata;if(!n||typeof n!=`object`)return null;let r=n;return typeof t.action!=`string`||typeof r.correlationKey!=`string`||typeof r.transition!=`string`?null:{action:t.action,...typeof t.actorId==`string`?{actorRef:t.actorId}:{},correlationKey:r.correlationKey,transition:r.transition}}export{s as approvalAudit,u as approvalAuditEvent,l as cleanup,c as history,d as logApprovalTransition};
1
+ import{components as e}from"./_generated/api.mjs";import{mutation as t,query as n}from"./_generated/server.mjs";import{APPROVAL_AUDIT_RETENTION_CATEGORY as r,MAX_APPROVAL_AUDIT_RETENTION_DAYS as i}from"./constants.mjs";import{z as a}from"zod";import{v as o}from"convex/values";import{AuditLog as s}from"convex-audit-log";const c=new s(e.auditLog,{samplingEnabled:!1}),l=n({args:{resourceType:o.string(),resourceRef:o.string(),limit:o.optional(o.number())},returns:o.array(o.object({action:o.string(),actorRef:o.optional(o.string()),correlationKey:o.string(),transition:o.string()})),handler:async(e,t)=>{let n=Math.max(1,Math.min(Math.trunc(t.limit??100),100)),r=await c.queryByResource(e,{resourceType:t.resourceType,resourceId:t.resourceRef,limit:n}),i=new Map;for(let e of r){let t=h(e);t&&i.set(t.correlationKey,t)}return[...i.values()]}}),u=t({args:{olderThanDays:o.number(),batchSize:o.number()},returns:o.number(),handler:(e,t)=>{let n=p(t.olderThanDays,1,i,`olderThanDays`),a=p(t.batchSize,1,100,`batchSize`);return c.cleanup(e,{olderThanDays:n,batchSize:a,retentionCategory:r})}});function d(e){return{action:e.action,actorId:e.actorRef,resourceType:e.resourceType,resourceId:e.resourceRef,severity:e.severity??`info`,tags:[`approvals`],retentionCategory:r,metadata:{correlationKey:`${e.runId}:${e.transition}`,runId:e.runId,transition:e.transition}}}async function f(e,t){await c.log(e,d(t))}function p(e,t,n,r){if(!Number.isInteger(e)||e<t||e>n)throw Error(`${r} must be an integer between ${t} and ${n}`);return e}const m=a.object({action:a.string(),actorId:a.string().optional().catch(void 0),metadata:a.object({correlationKey:a.string(),transition:a.string()})});function h(e){let t=m.safeParse(e);if(!t.success)return null;let{action:n,actorId:r,metadata:i}=t.data;return{action:n,actorRef:r,...i}}export{c as approvalAudit,d as approvalAuditEvent,u as cleanup,l as history,f as logApprovalTransition};
2
2
  //# sourceMappingURL=audit.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit.mjs","names":[],"sources":["../../../src/components/approvals/audit.ts"],"sourcesContent":["import { AuditLog, type AuditEventInput } from 'convex-audit-log'\nimport { v } from 'convex/values'\n\nimport { components } from './_generated/api'\nimport { mutation, query, type MutationCtx } from './_generated/server'\nimport {\n\tAPPROVAL_AUDIT_RETENTION_CATEGORY,\n\tMAX_APPROVAL_AUDIT_CLEANUP_BATCH_SIZE,\n\tMAX_APPROVAL_AUDIT_RETENTION_DAYS,\n\tMIN_APPROVAL_AUDIT_RETENTION_DAYS,\n} from './constants'\n\nexport type ApprovalAuditTransition = Readonly<{\n\taction: string\n\tactorRef?: string\n\trunId: string\n\tresourceType: string\n\tresourceRef: string\n\ttransition: string\n\tseverity?: AuditEventInput['severity']\n}>\n\nexport type ApprovalAuditProjection = Readonly<{\n\taction: string\n\tactorRef?: string\n\tcorrelationKey: string\n\ttransition: string\n}>\n\nexport const approvalAudit = new AuditLog(components.auditLog, {\n\tsamplingEnabled: false,\n})\n\nexport const history = query({\n\targs: {\n\t\tresourceType: v.string(),\n\t\tresourceRef: v.string(),\n\t\tlimit: v.optional(v.number()),\n\t},\n\treturns: v.array(\n\t\tv.object({\n\t\t\taction: v.string(),\n\t\t\tactorRef: v.optional(v.string()),\n\t\t\tcorrelationKey: v.string(),\n\t\t\ttransition: v.string(),\n\t\t}),\n\t),\n\thandler: async (ctx, input) => {\n\t\tconst limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 100), 100))\n\t\tconst events = await approvalAudit.queryByResource(ctx, {\n\t\t\tresourceType: input.resourceType,\n\t\t\tresourceId: input.resourceRef,\n\t\t\tlimit,\n\t\t})\n\t\tconst unique = new Map<string, ApprovalAuditProjection>()\n\t\tfor (const event of events) {\n\t\t\tconst projection = auditProjection(event)\n\t\t\tif (projection) unique.set(projection.correlationKey, projection)\n\t\t}\n\t\treturn [...unique.values()]\n\t},\n})\n\nexport const cleanup = mutation({\n\targs: {\n\t\tolderThanDays: v.number(),\n\t\tbatchSize: v.number(),\n\t},\n\treturns: v.number(),\n\thandler: (ctx, input) => {\n\t\tconst olderThanDays = boundedInteger(\n\t\t\tinput.olderThanDays,\n\t\t\tMIN_APPROVAL_AUDIT_RETENTION_DAYS,\n\t\t\tMAX_APPROVAL_AUDIT_RETENTION_DAYS,\n\t\t\t'olderThanDays',\n\t\t)\n\t\tconst batchSize = boundedInteger(\n\t\t\tinput.batchSize,\n\t\t\t1,\n\t\t\tMAX_APPROVAL_AUDIT_CLEANUP_BATCH_SIZE,\n\t\t\t'batchSize',\n\t\t)\n\t\treturn approvalAudit.cleanup(ctx, {\n\t\t\tolderThanDays,\n\t\t\tbatchSize,\n\t\t\tretentionCategory: APPROVAL_AUDIT_RETENTION_CATEGORY,\n\t\t})\n\t},\n})\n\nexport function approvalAuditEvent(\n\tinput: ApprovalAuditTransition,\n): AuditEventInput {\n\treturn {\n\t\taction: input.action,\n\t\t...(input.actorRef === undefined ? {} : { actorId: input.actorRef }),\n\t\tresourceType: input.resourceType,\n\t\tresourceId: input.resourceRef,\n\t\tseverity: input.severity ?? 'info',\n\t\ttags: ['approvals'],\n\t\tretentionCategory: APPROVAL_AUDIT_RETENTION_CATEGORY,\n\t\tmetadata: {\n\t\t\tcorrelationKey: `${input.runId}:${input.transition}`,\n\t\t\trunId: input.runId,\n\t\t\ttransition: input.transition,\n\t\t},\n\t}\n}\n\nexport async function logApprovalTransition(\n\tctx: MutationCtx,\n\tinput: ApprovalAuditTransition,\n): Promise<void> {\n\tawait approvalAudit.log(ctx, approvalAuditEvent(input))\n}\n\nfunction boundedInteger(\n\tvalue: number,\n\tminimum: number,\n\tmaximum: number,\n\tname: string,\n): number {\n\tif (!Number.isInteger(value) || value < minimum || value > maximum)\n\t\tthrow new Error(\n\t\t\t`${name} must be an integer between ${minimum} and ${maximum}`,\n\t\t)\n\treturn value\n}\n\nfunction auditProjection(event: unknown): ApprovalAuditProjection | null {\n\tif (!event || typeof event !== 'object') return null\n\tconst value = event as Record<string, unknown>\n\tconst metadata = value.metadata\n\tif (!metadata || typeof metadata !== 'object') return null\n\tconst details = metadata as Record<string, unknown>\n\tif (\n\t\ttypeof value.action !== 'string' ||\n\t\ttypeof details.correlationKey !== 'string' ||\n\t\ttypeof details.transition !== 'string'\n\t)\n\t\treturn null\n\treturn {\n\t\taction: value.action,\n\t\t...(typeof value.actorId === 'string' ? { actorRef: value.actorId } : {}),\n\t\tcorrelationKey: details.correlationKey,\n\t\ttransition: details.transition,\n\t}\n}\n"],"mappings":"ySA6BA,MAAa,EAAgB,IAAI,EAAS,EAAW,SAAU,CAC9D,gBAAiB,EAClB,CAAC,EAEY,EAAU,EAAM,CAC5B,KAAM,CACL,aAAc,EAAE,OAAO,EACvB,YAAa,EAAE,OAAO,EACtB,MAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAC7B,EACA,QAAS,EAAE,MACV,EAAE,OAAO,CACR,OAAQ,EAAE,OAAO,EACjB,SAAU,EAAE,SAAS,EAAE,OAAO,CAAC,EAC/B,eAAgB,EAAE,OAAO,EACzB,WAAY,EAAE,OAAO,CACtB,CAAC,CACF,EACA,QAAS,MAAO,EAAK,IAAU,CAC9B,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,MAAM,EAAM,OAAS,GAAG,EAAG,GAAG,CAAC,EACjE,EAAS,MAAM,EAAc,gBAAgB,EAAK,CACvD,aAAc,EAAM,aACpB,WAAY,EAAM,YAClB,OACD,CAAC,EACK,EAAS,IAAI,IACnB,IAAK,IAAM,KAAS,EAAQ,CAC3B,IAAM,EAAa,EAAgB,CAAK,EACpC,GAAY,EAAO,IAAI,EAAW,eAAgB,CAAU,CACjE,CACA,MAAO,CAAC,GAAG,EAAO,OAAO,CAAC,CAC3B,CACD,CAAC,EAEY,EAAU,EAAS,CAC/B,KAAM,CACL,cAAe,EAAE,OAAO,EACxB,UAAW,EAAE,OAAO,CACrB,EACA,QAAS,EAAE,OAAO,EAClB,SAAU,EAAK,IAAU,CACxB,IAAM,EAAgB,EACrB,EAAM,cAAA,EAEN,EACA,eACD,EACM,EAAY,EACjB,EAAM,UACN,EAAA,IAEA,WACD,EACA,OAAO,EAAc,QAAQ,EAAK,CACjC,gBACA,YACA,kBAAmB,CACpB,CAAC,CACF,CACD,CAAC,EAED,SAAgB,EACf,EACkB,CAClB,MAAO,CACN,OAAQ,EAAM,OACd,GAAI,EAAM,WAAa,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAM,QAAS,EAClE,aAAc,EAAM,aACpB,WAAY,EAAM,YAClB,SAAU,EAAM,UAAY,OAC5B,KAAM,CAAC,WAAW,EAClB,kBAAmB,EACnB,SAAU,CACT,eAAgB,GAAG,EAAM,MAAM,GAAG,EAAM,aACxC,MAAO,EAAM,MACb,WAAY,EAAM,UACnB,CACD,CACD,CAEA,eAAsB,EACrB,EACA,EACgB,CAChB,MAAM,EAAc,IAAI,EAAK,EAAmB,CAAK,CAAC,CACvD,CAEA,SAAS,EACR,EACA,EACA,EACA,EACS,CACT,GAAI,CAAC,OAAO,UAAU,CAAK,GAAK,EAAQ,GAAW,EAAQ,EAC1D,MAAU,MACT,GAAG,EAAK,8BAA8B,EAAQ,OAAO,GACtD,EACD,OAAO,CACR,CAEA,SAAS,EAAgB,EAAgD,CACxE,GAAI,CAAC,GAAS,OAAO,GAAU,SAAU,OAAO,KAChD,IAAM,EAAQ,EACR,EAAW,EAAM,SACvB,GAAI,CAAC,GAAY,OAAO,GAAa,SAAU,OAAO,KACtD,IAAM,EAAU,EAOhB,OALC,OAAO,EAAM,QAAW,UACxB,OAAO,EAAQ,gBAAmB,UAClC,OAAO,EAAQ,YAAe,SAEvB,KACD,CACN,OAAQ,EAAM,OACd,GAAI,OAAO,EAAM,SAAY,SAAW,CAAE,SAAU,EAAM,OAAQ,EAAI,CAAC,EACvE,eAAgB,EAAQ,eACxB,WAAY,EAAQ,UACrB,CACD"}
1
+ {"version":3,"file":"audit.mjs","names":[],"sources":["../../../src/components/approvals/audit.ts"],"sourcesContent":["import { z } from 'zod'\nimport { AuditLog, type AuditEventInput } from 'convex-audit-log'\nimport { v } from 'convex/values'\n\nimport { components } from './_generated/api'\nimport { mutation, query, type MutationCtx } from './_generated/server'\nimport {\n\tAPPROVAL_AUDIT_RETENTION_CATEGORY,\n\tMAX_APPROVAL_AUDIT_CLEANUP_BATCH_SIZE,\n\tMAX_APPROVAL_AUDIT_RETENTION_DAYS,\n\tMIN_APPROVAL_AUDIT_RETENTION_DAYS,\n} from './constants'\n\nexport type ApprovalAuditTransition = Readonly<{\n\taction: string\n\tactorRef?: string\n\trunId: string\n\tresourceType: string\n\tresourceRef: string\n\ttransition: string\n\tseverity?: AuditEventInput['severity']\n}>\n\nexport type ApprovalAuditProjection = Readonly<{\n\taction: string\n\tactorRef?: string\n\tcorrelationKey: string\n\ttransition: string\n}>\n\nexport const approvalAudit = new AuditLog(components.auditLog, {\n\tsamplingEnabled: false,\n})\n\nexport const history = query({\n\targs: {\n\t\tresourceType: v.string(),\n\t\tresourceRef: v.string(),\n\t\tlimit: v.optional(v.number()),\n\t},\n\treturns: v.array(\n\t\tv.object({\n\t\t\taction: v.string(),\n\t\t\tactorRef: v.optional(v.string()),\n\t\t\tcorrelationKey: v.string(),\n\t\t\ttransition: v.string(),\n\t\t}),\n\t),\n\thandler: async (ctx, input) => {\n\t\tconst limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 100), 100))\n\t\tconst events = await approvalAudit.queryByResource(ctx, {\n\t\t\tresourceType: input.resourceType,\n\t\t\tresourceId: input.resourceRef,\n\t\t\tlimit,\n\t\t})\n\t\tconst unique = new Map<string, ApprovalAuditProjection>()\n\t\tfor (const event of events) {\n\t\t\tconst projection = auditProjection(event)\n\t\t\tif (projection) unique.set(projection.correlationKey, projection)\n\t\t}\n\t\treturn [...unique.values()]\n\t},\n})\n\nexport const cleanup = mutation({\n\targs: {\n\t\tolderThanDays: v.number(),\n\t\tbatchSize: v.number(),\n\t},\n\treturns: v.number(),\n\thandler: (ctx, input) => {\n\t\tconst olderThanDays = boundedInteger(\n\t\t\tinput.olderThanDays,\n\t\t\tMIN_APPROVAL_AUDIT_RETENTION_DAYS,\n\t\t\tMAX_APPROVAL_AUDIT_RETENTION_DAYS,\n\t\t\t'olderThanDays',\n\t\t)\n\t\tconst batchSize = boundedInteger(\n\t\t\tinput.batchSize,\n\t\t\t1,\n\t\t\tMAX_APPROVAL_AUDIT_CLEANUP_BATCH_SIZE,\n\t\t\t'batchSize',\n\t\t)\n\t\treturn approvalAudit.cleanup(ctx, {\n\t\t\tolderThanDays,\n\t\t\tbatchSize,\n\t\t\tretentionCategory: APPROVAL_AUDIT_RETENTION_CATEGORY,\n\t\t})\n\t},\n})\n\nexport function approvalAuditEvent(input: ApprovalAuditTransition): AuditEventInput {\n\treturn {\n\t\taction: input.action,\n\t\tactorId: input.actorRef,\n\t\tresourceType: input.resourceType,\n\t\tresourceId: input.resourceRef,\n\t\tseverity: input.severity ?? 'info',\n\t\ttags: ['approvals'],\n\t\tretentionCategory: APPROVAL_AUDIT_RETENTION_CATEGORY,\n\t\tmetadata: {\n\t\t\tcorrelationKey: `${input.runId}:${input.transition}`,\n\t\t\trunId: input.runId,\n\t\t\ttransition: input.transition,\n\t\t},\n\t}\n}\n\nexport async function logApprovalTransition(\n\tctx: MutationCtx,\n\tinput: ApprovalAuditTransition,\n): Promise<void> {\n\tawait approvalAudit.log(ctx, approvalAuditEvent(input))\n}\n\nfunction boundedInteger(value: number, minimum: number, maximum: number, name: string): number {\n\tif (!Number.isInteger(value) || value < minimum || value > maximum)\n\t\tthrow new Error(`${name} must be an integer between ${minimum} and ${maximum}`)\n\treturn value\n}\n\nconst auditEventProjection = z.object({\n\taction: z.string(),\n\tactorId: z.string().optional().catch(undefined),\n\tmetadata: z.object({ correlationKey: z.string(), transition: z.string() }),\n})\n\nfunction auditProjection(\n\tevent: Awaited<ReturnType<typeof approvalAudit.queryByResource>>[number],\n): ApprovalAuditProjection | null {\n\tconst parsed = auditEventProjection.safeParse(event)\n\tif (!parsed.success) return null\n\tconst { action, actorId, metadata } = parsed.data\n\treturn { action, actorRef: actorId, ...metadata }\n}\n"],"mappings":"iUA8BA,MAAa,EAAgB,IAAI,EAAS,EAAW,SAAU,CAC9D,gBAAiB,EAClB,CAAC,EAEY,EAAU,EAAM,CAC5B,KAAM,CACL,aAAc,EAAE,OAAO,EACvB,YAAa,EAAE,OAAO,EACtB,MAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAC7B,EACA,QAAS,EAAE,MACV,EAAE,OAAO,CACR,OAAQ,EAAE,OAAO,EACjB,SAAU,EAAE,SAAS,EAAE,OAAO,CAAC,EAC/B,eAAgB,EAAE,OAAO,EACzB,WAAY,EAAE,OAAO,CACtB,CAAC,CACF,EACA,QAAS,MAAO,EAAK,IAAU,CAC9B,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,MAAM,EAAM,OAAS,GAAG,EAAG,GAAG,CAAC,EACjE,EAAS,MAAM,EAAc,gBAAgB,EAAK,CACvD,aAAc,EAAM,aACpB,WAAY,EAAM,YAClB,OACD,CAAC,EACK,EAAS,IAAI,IACnB,IAAK,IAAM,KAAS,EAAQ,CAC3B,IAAM,EAAa,EAAgB,CAAK,EACpC,GAAY,EAAO,IAAI,EAAW,eAAgB,CAAU,CACjE,CACA,MAAO,CAAC,GAAG,EAAO,OAAO,CAAC,CAC3B,CACD,CAAC,EAEY,EAAU,EAAS,CAC/B,KAAM,CACL,cAAe,EAAE,OAAO,EACxB,UAAW,EAAE,OAAO,CACrB,EACA,QAAS,EAAE,OAAO,EAClB,SAAU,EAAK,IAAU,CACxB,IAAM,EAAgB,EACrB,EAAM,cAAA,EAEN,EACA,eACD,EACM,EAAY,EACjB,EAAM,UACN,EAAA,IAEA,WACD,EACA,OAAO,EAAc,QAAQ,EAAK,CACjC,gBACA,YACA,kBAAmB,CACpB,CAAC,CACF,CACD,CAAC,EAED,SAAgB,EAAmB,EAAiD,CACnF,MAAO,CACN,OAAQ,EAAM,OACd,QAAS,EAAM,SACf,aAAc,EAAM,aACpB,WAAY,EAAM,YAClB,SAAU,EAAM,UAAY,OAC5B,KAAM,CAAC,WAAW,EAClB,kBAAmB,EACnB,SAAU,CACT,eAAgB,GAAG,EAAM,MAAM,GAAG,EAAM,aACxC,MAAO,EAAM,MACb,WAAY,EAAM,UACnB,CACD,CACD,CAEA,eAAsB,EACrB,EACA,EACgB,CAChB,MAAM,EAAc,IAAI,EAAK,EAAmB,CAAK,CAAC,CACvD,CAEA,SAAS,EAAe,EAAe,EAAiB,EAAiB,EAAsB,CAC9F,GAAI,CAAC,OAAO,UAAU,CAAK,GAAK,EAAQ,GAAW,EAAQ,EAC1D,MAAU,MAAM,GAAG,EAAK,8BAA8B,EAAQ,OAAO,GAAS,EAC/E,OAAO,CACR,CAEA,MAAM,EAAuB,EAAE,OAAO,CACrC,OAAQ,EAAE,OAAO,EACjB,QAAS,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,IAAA,EAAS,EAC9C,SAAU,EAAE,OAAO,CAAE,eAAgB,EAAE,OAAO,EAAG,WAAY,EAAE,OAAO,CAAE,CAAC,CAC1E,CAAC,EAED,SAAS,EACR,EACiC,CACjC,IAAM,EAAS,EAAqB,UAAU,CAAK,EACnD,GAAI,CAAC,EAAO,QAAS,OAAO,KAC5B,GAAM,CAAE,SAAQ,UAAS,YAAa,EAAO,KAC7C,MAAO,CAAE,SAAQ,SAAU,EAAS,GAAG,CAAS,CACjD"}
@@ -1,6 +1,6 @@
1
+ import { ApprovalActor, ApprovalCallbackInput, ApprovalMetadata, ApprovalWorkflowDescriptor, approvalCallbackArgs, approvalCallbackInput } from "./validators.mjs";
1
2
  import { ComponentApi } from "./_generated/component.mjs";
2
3
  import { ApprovalDecision } from "./constants.mjs";
3
- import { ApprovalActor, ApprovalCallbackInput, ApprovalMetadata, ApprovalWorkflowDescriptor, approvalCallbackArgs, approvalCallbackInput } from "./validators.mjs";
4
4
  import { ApprovalActionStep, ApprovalBranchStep, ApprovalDecisionStep, ApprovalDefinition, ApprovalMutationStep, ApprovalNotifyStep, ApprovalWorkflowStep, CreateApprovalHandle } from "./workflow_steps.mjs";
5
5
  import { FunctionArgs, FunctionReference, FunctionReturnType, GenericDataModel, GenericMutationCtx, GenericQueryCtx } from "convex/server";
6
6
  declare namespace client_d_exports {
@@ -1,2 +1,2 @@
1
- import{approvalCallbackArgs as e,approvalCallbackInput as t}from"./validators.mjs";import{compileApprovalDescriptor as n}from"./workflow_steps.mjs";var r=class{component;createHandle;constructor(e,t={}){this.component=e,this.createHandle=t.createHandle}cleanupAudit(e,t){return e.runMutation(this.component.audit.cleanup,t)}mutation(e,t){return Object.freeze({kind:`mutation`,key:e,handler:t.handler,retry:t.retry??!1})}action(e,t){return Object.freeze({kind:`action`,key:e,handler:t.handler,retry:t.retry??!1})}notify(e,t){return Object.freeze({kind:`notify`,key:e,handler:t.handler,retry:t.retry??!1})}decision(e,t){return Object.freeze({kind:`decision`,key:e,decisions:Object.freeze([...t.decisions]),quorum:Object.freeze({...t.quorum}),makerChecker:t.makerChecker??!1,...t.expiresAfterMs===void 0?{}:{expiresAfterMs:t.expiresAfterMs}})}branch(e,t){return Object.freeze({kind:`branch`,key:e,...t})}define(e){let t=a({name:e.name,compatibilityKey:e.compatibilityKey??e.name,steps:e.steps});return new i(this.component,t,this.createHandle)}},i=class{name;compatibilityKey;steps;component;definition;createHandle;constructor(e,t,n){this.component=e,this.definition=t,this.name=t.name,this.compatibilityKey=t.compatibilityKey,this.steps=t.steps,this.createHandle=n,Object.freeze(this)}descriptor(){return n(this.definition,this.createHandle)}async start(e,t){return e.runMutation(this.component.requests.start,{...t,workflow:await this.descriptor()})}decide(e,t,n){return e.runMutation(this.component.decisions.decide,{...t,actor:n,compatibilityKey:this.compatibilityKey})}evidence(e,t){return e.runQuery(this.component.decisions.list,{runId:t})}auditHistory(e,t){return e.runQuery(this.component.audit.history,t)}cancel(e,t,n){return e.runMutation(this.component.requests.cancel,{runId:t,actor:n,compatibilityKey:this.compatibilityKey})}status(e,t){return e.runQuery(this.component.requests.status,{runId:t,compatibilityKey:this.compatibilityKey})}list(e,t){return e.runQuery(this.component.requests.list,t)}restart(e,t){return e.runMutation(this.component.requests.restart,{runId:t,compatibilityKey:this.compatibilityKey})}};function a(e){return Object.freeze({...e,steps:Object.freeze(e.steps.map(e=>e.kind===`decision`?Object.freeze({...e,decisions:Object.freeze([...e.decisions]),quorum:Object.freeze({...e.quorum})}):Object.freeze({...e})))})}export{r as Approvals,i as ConfiguredApprovalWorkflow,e as approvalCallbackArgs,t as approvalCallbackInput};
1
+ import{approvalCallbackArgs as e,approvalCallbackInput as t}from"./validators.mjs";import{compileApprovalDescriptor as n}from"./workflow_steps.mjs";var r=class{component;createHandle;constructor(e,t={}){this.component=e,this.createHandle=t.createHandle}cleanupAudit(e,t){return e.runMutation(this.component.audit.cleanup,t)}mutation(e,t){return Object.freeze({kind:`mutation`,key:e,handler:t.handler,retry:t.retry??!1})}action(e,t){return Object.freeze({kind:`action`,key:e,handler:t.handler,retry:t.retry??!1})}notify(e,t){return Object.freeze({kind:`notify`,key:e,handler:t.handler,retry:t.retry??!1})}decision(e,t){return Object.freeze({kind:`decision`,key:e,decisions:Object.freeze([...t.decisions]),quorum:Object.freeze({...t.quorum}),makerChecker:t.makerChecker??!1,expiresAfterMs:t.expiresAfterMs})}branch(e,t){return Object.freeze({kind:`branch`,key:e,...t})}define(e){let t=a({name:e.name,compatibilityKey:e.compatibilityKey??e.name,steps:e.steps});return new i(this.component,t,this.createHandle)}},i=class{name;compatibilityKey;steps;component;definition;createHandle;constructor(e,t,n){this.component=e,this.definition=t,this.name=t.name,this.compatibilityKey=t.compatibilityKey,this.steps=t.steps,this.createHandle=n,Object.freeze(this)}descriptor(){return n(this.definition,this.createHandle)}async start(e,t){return e.runMutation(this.component.requests.start,{...t,workflow:await this.descriptor()})}decide(e,t,n){return e.runMutation(this.component.decisions.decide,{...t,actor:n,compatibilityKey:this.compatibilityKey})}evidence(e,t){return e.runQuery(this.component.decisions.list,{runId:t})}auditHistory(e,t){return e.runQuery(this.component.audit.history,t)}cancel(e,t,n){return e.runMutation(this.component.requests.cancel,{runId:t,actor:n,compatibilityKey:this.compatibilityKey})}status(e,t){return e.runQuery(this.component.requests.status,{runId:t,compatibilityKey:this.compatibilityKey})}list(e,t){return e.runQuery(this.component.requests.list,t)}restart(e,t){return e.runMutation(this.component.requests.restart,{runId:t,compatibilityKey:this.compatibilityKey})}};function a(e){return Object.freeze({...e,steps:Object.freeze(e.steps.map(e=>e.kind===`decision`?Object.freeze({...e,decisions:Object.freeze([...e.decisions]),quorum:Object.freeze({...e.quorum})}):Object.freeze({...e})))})}export{r as Approvals,i as ConfiguredApprovalWorkflow,e as approvalCallbackArgs,t as approvalCallbackInput};
2
2
  //# sourceMappingURL=client.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../../../src/components/approvals/client.ts"],"sourcesContent":["import type {\n\tFunctionArgs,\n\tFunctionReference,\n\tFunctionReturnType,\n\tGenericDataModel,\n\tGenericMutationCtx,\n\tGenericQueryCtx,\n} from 'convex/server'\n\nimport type { ComponentApi } from './_generated/component'\nimport type { ApprovalDecision } from './constants'\nimport type {\n\tApprovalActor,\n\tApprovalMetadata,\n\tApprovalWorkflowDescriptor,\n} from './validators'\nimport {\n\tcompileApprovalDescriptor,\n\ttype ApprovalActionStep,\n\ttype ApprovalBranchStep,\n\ttype ApprovalDecisionStep,\n\ttype ApprovalDefinition,\n\ttype ApprovalMutationStep,\n\ttype ApprovalNotifyStep,\n\ttype ApprovalWorkflowStep,\n\ttype ApprovalCallbackInput,\n\ttype CreateApprovalHandle,\n} from './workflow_steps'\n\nexport { approvalCallbackArgs, approvalCallbackInput } from './validators'\nexport type { ApprovalCallbackInput } from './validators'\n\nexport type ApprovalsOptions = Readonly<{\n\tcreateHandle?: CreateApprovalHandle\n}>\n\nexport type ApprovalStartInput = Readonly<{\n\tscopeRef: string\n\tresourceType: string\n\tresourceRef: string\n\trequester: ApprovalActor\n\tmetadata?: ApprovalMetadata\n}>\n\nexport type ApprovalDecisionInput = Readonly<{\n\trunId: string\n\tdecision: ApprovalDecision\n\treason?: string\n}>\n\nexport type ApprovalEvidenceItem = Readonly<{\n\tstepKey: string\n\tactorRef: string\n\tdecision: ApprovalDecision\n\treason?: string\n\tdecidedAt: number\n}>\n\nexport type ApprovalAuditHistoryInput = Readonly<{\n\tresourceType: string\n\tresourceRef: string\n\tlimit?: number\n}>\n\nexport type ApprovalAuditCleanupInput = Readonly<{\n\tolderThanDays: number\n\tbatchSize: number\n}>\n\nexport type ApprovalStatus = FunctionReturnType<\n\tComponentApi['requests']['status']\n>\n\nexport class Approvals<Component extends ComponentApi = ComponentApi> {\n\tprivate readonly component: Component\n\tprivate readonly createHandle: CreateApprovalHandle | undefined\n\n\tconstructor(component: Component, options: ApprovalsOptions = {}) {\n\t\tthis.component = component\n\t\tthis.createHandle = options.createHandle\n\t}\n\n\tcleanupAudit<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tinput: ApprovalAuditCleanupInput,\n\t): Promise<FunctionReturnType<Component['audit']['cleanup']>> {\n\t\treturn ctx.runMutation(this.component.audit.cleanup, input)\n\t}\n\n\tmutation(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\thandler: FunctionReference<\n\t\t\t\t'mutation',\n\t\t\t\t'internal',\n\t\t\t\tApprovalCallbackInput,\n\t\t\t\tunknown\n\t\t\t>\n\t\t\tretry?: boolean\n\t\t}>,\n\t): ApprovalMutationStep {\n\t\treturn Object.freeze({\n\t\t\tkind: 'mutation',\n\t\t\tkey,\n\t\t\thandler: options.handler,\n\t\t\tretry: options.retry ?? false,\n\t\t})\n\t}\n\n\taction(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\thandler: FunctionReference<\n\t\t\t\t'action',\n\t\t\t\t'internal',\n\t\t\t\tApprovalCallbackInput,\n\t\t\t\tunknown\n\t\t\t>\n\t\t\tretry?: boolean\n\t\t}>,\n\t): ApprovalActionStep {\n\t\treturn Object.freeze({\n\t\t\tkind: 'action',\n\t\t\tkey,\n\t\t\thandler: options.handler,\n\t\t\tretry: options.retry ?? false,\n\t\t})\n\t}\n\n\tnotify(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\thandler: FunctionReference<\n\t\t\t\t'action',\n\t\t\t\t'internal',\n\t\t\t\tApprovalCallbackInput,\n\t\t\t\tunknown\n\t\t\t>\n\t\t\tretry?: boolean\n\t\t}>,\n\t): ApprovalNotifyStep {\n\t\treturn Object.freeze({\n\t\t\tkind: 'notify',\n\t\t\tkey,\n\t\t\thandler: options.handler,\n\t\t\tretry: options.retry ?? false,\n\t\t})\n\t}\n\n\tdecision(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\tdecisions: readonly ApprovalDecision[]\n\t\t\tquorum: Readonly<{ kind: 'count'; approvals: number }>\n\t\t\tmakerChecker?: boolean\n\t\t\texpiresAfterMs?: number\n\t\t}>,\n\t): ApprovalDecisionStep {\n\t\treturn Object.freeze({\n\t\t\tkind: 'decision',\n\t\t\tkey,\n\t\t\tdecisions: Object.freeze([...options.decisions]),\n\t\t\tquorum: Object.freeze({ ...options.quorum }),\n\t\t\tmakerChecker: options.makerChecker ?? false,\n\t\t\t...(options.expiresAfterMs === undefined\n\t\t\t\t? {}\n\t\t\t\t: { expiresAfterMs: options.expiresAfterMs }),\n\t\t})\n\t}\n\n\tbranch(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\tapprovedStepKey: string\n\t\t\trejectedStepKey: string\n\t\t}>,\n\t): ApprovalBranchStep {\n\t\treturn Object.freeze({ kind: 'branch', key, ...options })\n\t}\n\n\tdefine(\n\t\tinput: Readonly<{\n\t\t\tname: string\n\t\t\tcompatibilityKey?: string\n\t\t\tsteps: readonly ApprovalWorkflowStep[]\n\t\t}>,\n\t): ConfiguredApprovalWorkflow<Component> {\n\t\tconst definition = freezeDefinition({\n\t\t\tname: input.name,\n\t\t\tcompatibilityKey: input.compatibilityKey ?? input.name,\n\t\t\tsteps: input.steps,\n\t\t})\n\t\treturn new ConfiguredApprovalWorkflow(\n\t\t\tthis.component,\n\t\t\tdefinition,\n\t\t\tthis.createHandle,\n\t\t)\n\t}\n}\n\nexport class ConfiguredApprovalWorkflow<\n\tComponent extends ComponentApi = ComponentApi,\n> {\n\treadonly name: string\n\treadonly compatibilityKey: string\n\treadonly steps: readonly ApprovalWorkflowStep[]\n\n\tprivate readonly component: Component\n\tprivate readonly definition: ApprovalDefinition\n\tprivate readonly createHandle: CreateApprovalHandle | undefined\n\n\tconstructor(\n\t\tcomponent: Component,\n\t\tdefinition: ApprovalDefinition,\n\t\tcreateHandle?: CreateApprovalHandle,\n\t) {\n\t\tthis.component = component\n\t\tthis.definition = definition\n\t\tthis.name = definition.name\n\t\tthis.compatibilityKey = definition.compatibilityKey\n\t\tthis.steps = definition.steps\n\t\tthis.createHandle = createHandle\n\t\tObject.freeze(this)\n\t}\n\n\tdescriptor(): Promise<Readonly<ApprovalWorkflowDescriptor>> {\n\t\treturn compileApprovalDescriptor(this.definition, this.createHandle)\n\t}\n\n\tasync start<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tinput: ApprovalStartInput,\n\t): Promise<FunctionReturnType<Component['requests']['start']>> {\n\t\treturn ctx.runMutation(this.component.requests.start, {\n\t\t\t...input,\n\t\t\tworkflow: await this.descriptor(),\n\t\t})\n\t}\n\n\tdecide<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tinput: ApprovalDecisionInput,\n\t\tactor: ApprovalActor,\n\t): Promise<FunctionReturnType<Component['decisions']['decide']>> {\n\t\treturn ctx.runMutation(this.component.decisions.decide, {\n\t\t\t...input,\n\t\t\tactor,\n\t\t\tcompatibilityKey: this.compatibilityKey,\n\t\t})\n\t}\n\n\tevidence<DataModel extends GenericDataModel>(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\trunId: string,\n\t): Promise<FunctionReturnType<Component['decisions']['list']>> {\n\t\treturn ctx.runQuery(this.component.decisions.list, { runId })\n\t}\n\n\tauditHistory<DataModel extends GenericDataModel>(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\tinput: ApprovalAuditHistoryInput,\n\t): Promise<FunctionReturnType<Component['audit']['history']>> {\n\t\treturn ctx.runQuery(this.component.audit.history, input)\n\t}\n\n\tcancel<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\trunId: string,\n\t\tactor: ApprovalActor,\n\t): Promise<FunctionReturnType<Component['requests']['cancel']>> {\n\t\treturn ctx.runMutation(this.component.requests.cancel, {\n\t\t\trunId,\n\t\t\tactor,\n\t\t\tcompatibilityKey: this.compatibilityKey,\n\t\t})\n\t}\n\n\tstatus<DataModel extends GenericDataModel>(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\trunId: string,\n\t): Promise<ApprovalStatus | null> {\n\t\treturn ctx.runQuery(this.component.requests.status, {\n\t\t\trunId,\n\t\t\tcompatibilityKey: this.compatibilityKey,\n\t\t})\n\t}\n\n\tlist<DataModel extends GenericDataModel>(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\tinput: FunctionArgs<Component['requests']['list']>,\n\t): Promise<FunctionReturnType<Component['requests']['list']>> {\n\t\treturn ctx.runQuery(this.component.requests.list, input)\n\t}\n\n\trestart<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\trunId: string,\n\t): Promise<FunctionReturnType<Component['requests']['restart']>> {\n\t\treturn ctx.runMutation(this.component.requests.restart, {\n\t\t\trunId,\n\t\t\tcompatibilityKey: this.compatibilityKey,\n\t\t})\n\t}\n}\n\nfunction freezeDefinition(definition: ApprovalDefinition): ApprovalDefinition {\n\treturn Object.freeze({\n\t\t...definition,\n\t\tsteps: Object.freeze(\n\t\t\tdefinition.steps.map((step) => {\n\t\t\t\tif (step.kind === 'decision')\n\t\t\t\t\treturn Object.freeze({\n\t\t\t\t\t\t...step,\n\t\t\t\t\t\tdecisions: Object.freeze([...step.decisions]),\n\t\t\t\t\t\tquorum: Object.freeze({ ...step.quorum }),\n\t\t\t\t\t})\n\t\t\t\treturn Object.freeze({ ...step })\n\t\t\t}),\n\t\t),\n\t})\n}\n\n// The component definition is deliberately NOT re-exported here: Convex's\n// CLI only discovers a component whose resolved file is convex.config.js,\n// so hosts must mount via the dedicated subpath:\n// import approvals from 'cvx-kit/components/approvals/convex.config'\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../../../src/components/approvals/client.ts"],"sourcesContent":["import type { ApprovalCallbackInput } from './validators'\nimport type {\n\tFunctionArgs,\n\tFunctionReference,\n\tFunctionReturnType,\n\tGenericDataModel,\n\tGenericMutationCtx,\n\tGenericQueryCtx,\n} from 'convex/server'\n\nimport type { ComponentApi } from './_generated/component'\nimport type { ApprovalDecision } from './constants'\nimport type { ApprovalActor, ApprovalMetadata, ApprovalWorkflowDescriptor } from './validators'\nimport {\n\tcompileApprovalDescriptor,\n\ttype ApprovalActionStep,\n\ttype ApprovalBranchStep,\n\ttype ApprovalDecisionStep,\n\ttype ApprovalDefinition,\n\ttype ApprovalMutationStep,\n\ttype ApprovalNotifyStep,\n\ttype ApprovalWorkflowStep,\n\ttype CreateApprovalHandle,\n} from './workflow_steps'\n\nexport { approvalCallbackArgs, approvalCallbackInput } from './validators'\nexport type { ApprovalCallbackInput } from './validators'\n\nexport type ApprovalsOptions = Readonly<{\n\tcreateHandle?: CreateApprovalHandle\n}>\n\nexport type ApprovalStartInput = Readonly<{\n\tscopeRef: string\n\tresourceType: string\n\tresourceRef: string\n\trequester: ApprovalActor\n\tmetadata?: ApprovalMetadata\n}>\n\nexport type ApprovalDecisionInput = Readonly<{\n\trunId: string\n\tdecision: ApprovalDecision\n\treason?: string\n}>\n\nexport type ApprovalEvidenceItem = Readonly<{\n\tstepKey: string\n\tactorRef: string\n\tdecision: ApprovalDecision\n\treason?: string\n\tdecidedAt: number\n}>\n\nexport type ApprovalAuditHistoryInput = Readonly<{\n\tresourceType: string\n\tresourceRef: string\n\tlimit?: number\n}>\n\nexport type ApprovalAuditCleanupInput = Readonly<{\n\tolderThanDays: number\n\tbatchSize: number\n}>\n\nexport type ApprovalStatus = FunctionReturnType<ComponentApi['requests']['status']>\n\nexport class Approvals<Component extends ComponentApi = ComponentApi> {\n\tprivate readonly component: Component\n\tprivate readonly createHandle: CreateApprovalHandle | undefined\n\n\tconstructor(component: Component, options: ApprovalsOptions = {}) {\n\t\tthis.component = component\n\t\tthis.createHandle = options.createHandle\n\t}\n\n\tcleanupAudit<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tinput: ApprovalAuditCleanupInput,\n\t): Promise<FunctionReturnType<Component['audit']['cleanup']>> {\n\t\treturn ctx.runMutation(this.component.audit.cleanup, input)\n\t}\n\n\tmutation(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\thandler: FunctionReference<'mutation', 'internal', ApprovalCallbackInput, unknown>\n\t\t\tretry?: boolean\n\t\t}>,\n\t): ApprovalMutationStep {\n\t\treturn Object.freeze({\n\t\t\tkind: 'mutation',\n\t\t\tkey,\n\t\t\thandler: options.handler,\n\t\t\tretry: options.retry ?? false,\n\t\t})\n\t}\n\n\taction(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\thandler: FunctionReference<'action', 'internal', ApprovalCallbackInput, unknown>\n\t\t\tretry?: boolean\n\t\t}>,\n\t): ApprovalActionStep {\n\t\treturn Object.freeze({\n\t\t\tkind: 'action',\n\t\t\tkey,\n\t\t\thandler: options.handler,\n\t\t\tretry: options.retry ?? false,\n\t\t})\n\t}\n\n\tnotify(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\thandler: FunctionReference<'action', 'internal', ApprovalCallbackInput, unknown>\n\t\t\tretry?: boolean\n\t\t}>,\n\t): ApprovalNotifyStep {\n\t\treturn Object.freeze({\n\t\t\tkind: 'notify',\n\t\t\tkey,\n\t\t\thandler: options.handler,\n\t\t\tretry: options.retry ?? false,\n\t\t})\n\t}\n\n\tdecision(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\tdecisions: readonly ApprovalDecision[]\n\t\t\tquorum: Readonly<{ kind: 'count'; approvals: number }>\n\t\t\tmakerChecker?: boolean\n\t\t\texpiresAfterMs?: number\n\t\t}>,\n\t): ApprovalDecisionStep {\n\t\treturn Object.freeze({\n\t\t\tkind: 'decision',\n\t\t\tkey,\n\t\t\tdecisions: Object.freeze([...options.decisions]),\n\t\t\tquorum: Object.freeze({ ...options.quorum }),\n\t\t\tmakerChecker: options.makerChecker ?? false,\n\t\t\texpiresAfterMs: options.expiresAfterMs,\n\t\t})\n\t}\n\n\tbranch(\n\t\tkey: string,\n\t\toptions: Readonly<{\n\t\t\tapprovedStepKey: string\n\t\t\trejectedStepKey: string\n\t\t}>,\n\t): ApprovalBranchStep {\n\t\treturn Object.freeze({ kind: 'branch', key, ...options })\n\t}\n\n\tdefine(\n\t\tinput: Readonly<{\n\t\t\tname: string\n\t\t\tcompatibilityKey?: string\n\t\t\tsteps: readonly ApprovalWorkflowStep[]\n\t\t}>,\n\t): ConfiguredApprovalWorkflow<Component> {\n\t\tconst definition = freezeDefinition({\n\t\t\tname: input.name,\n\t\t\tcompatibilityKey: input.compatibilityKey ?? input.name,\n\t\t\tsteps: input.steps,\n\t\t})\n\t\treturn new ConfiguredApprovalWorkflow(this.component, definition, this.createHandle)\n\t}\n}\n\nexport class ConfiguredApprovalWorkflow<Component extends ComponentApi = ComponentApi> {\n\treadonly name: string\n\treadonly compatibilityKey: string\n\treadonly steps: readonly ApprovalWorkflowStep[]\n\n\tprivate readonly component: Component\n\tprivate readonly definition: ApprovalDefinition\n\tprivate readonly createHandle: CreateApprovalHandle | undefined\n\n\tconstructor(\n\t\tcomponent: Component,\n\t\tdefinition: ApprovalDefinition,\n\t\tcreateHandle?: CreateApprovalHandle,\n\t) {\n\t\tthis.component = component\n\t\tthis.definition = definition\n\t\tthis.name = definition.name\n\t\tthis.compatibilityKey = definition.compatibilityKey\n\t\tthis.steps = definition.steps\n\t\tthis.createHandle = createHandle\n\t\tObject.freeze(this)\n\t}\n\n\tdescriptor(): Promise<Readonly<ApprovalWorkflowDescriptor>> {\n\t\treturn compileApprovalDescriptor(this.definition, this.createHandle)\n\t}\n\n\tasync start<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tinput: ApprovalStartInput,\n\t): Promise<FunctionReturnType<Component['requests']['start']>> {\n\t\treturn ctx.runMutation(this.component.requests.start, {\n\t\t\t...input,\n\t\t\tworkflow: await this.descriptor(),\n\t\t})\n\t}\n\n\tdecide<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tinput: ApprovalDecisionInput,\n\t\tactor: ApprovalActor,\n\t): Promise<FunctionReturnType<Component['decisions']['decide']>> {\n\t\treturn ctx.runMutation(this.component.decisions.decide, {\n\t\t\t...input,\n\t\t\tactor,\n\t\t\tcompatibilityKey: this.compatibilityKey,\n\t\t})\n\t}\n\n\tevidence<DataModel extends GenericDataModel>(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\trunId: string,\n\t): Promise<FunctionReturnType<Component['decisions']['list']>> {\n\t\treturn ctx.runQuery(this.component.decisions.list, { runId })\n\t}\n\n\tauditHistory<DataModel extends GenericDataModel>(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\tinput: ApprovalAuditHistoryInput,\n\t): Promise<FunctionReturnType<Component['audit']['history']>> {\n\t\treturn ctx.runQuery(this.component.audit.history, input)\n\t}\n\n\tcancel<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\trunId: string,\n\t\tactor: ApprovalActor,\n\t): Promise<FunctionReturnType<Component['requests']['cancel']>> {\n\t\treturn ctx.runMutation(this.component.requests.cancel, {\n\t\t\trunId,\n\t\t\tactor,\n\t\t\tcompatibilityKey: this.compatibilityKey,\n\t\t})\n\t}\n\n\tstatus<DataModel extends GenericDataModel>(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\trunId: string,\n\t): Promise<ApprovalStatus | null> {\n\t\treturn ctx.runQuery(this.component.requests.status, {\n\t\t\trunId,\n\t\t\tcompatibilityKey: this.compatibilityKey,\n\t\t})\n\t}\n\n\tlist<DataModel extends GenericDataModel>(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\tinput: FunctionArgs<Component['requests']['list']>,\n\t): Promise<FunctionReturnType<Component['requests']['list']>> {\n\t\treturn ctx.runQuery(this.component.requests.list, input)\n\t}\n\n\trestart<DataModel extends GenericDataModel>(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\trunId: string,\n\t): Promise<FunctionReturnType<Component['requests']['restart']>> {\n\t\treturn ctx.runMutation(this.component.requests.restart, {\n\t\t\trunId,\n\t\t\tcompatibilityKey: this.compatibilityKey,\n\t\t})\n\t}\n}\n\nfunction freezeDefinition(definition: ApprovalDefinition): ApprovalDefinition {\n\treturn Object.freeze({\n\t\t...definition,\n\t\tsteps: Object.freeze(\n\t\t\tdefinition.steps.map((step) => {\n\t\t\t\tif (step.kind === 'decision')\n\t\t\t\t\treturn Object.freeze({\n\t\t\t\t\t\t...step,\n\t\t\t\t\t\tdecisions: Object.freeze([...step.decisions]),\n\t\t\t\t\t\tquorum: Object.freeze({ ...step.quorum }),\n\t\t\t\t\t})\n\t\t\t\treturn Object.freeze({ ...step })\n\t\t\t}),\n\t\t),\n\t})\n}\n\n// The component definition is deliberately NOT re-exported here: Convex's\n// CLI only discovers a component whose resolved file is convex.config.js,\n// so hosts must mount via the dedicated subpath:\n// import approvals from 'cvx-kit/components/approvals/convex.config'\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"constants.mjs","names":[],"sources":["../../../src/components/approvals/constants.ts"],"sourcesContent":["export const APPROVAL_RUN_STATES = [\n\t'pending',\n\t'approved',\n\t'rejected',\n\t'expired',\n\t'canceled',\n] as const\n\nexport const APPROVAL_DECISIONS = ['approved', 'rejected'] as const\n\nexport const APPROVAL_EXECUTION_STATES = [\n\t'inProgress',\n\t'completed',\n\t'canceled',\n\t'failed',\n] as const\n\nexport const APPROVAL_STEP_KINDS = [\n\t'mutation',\n\t'action',\n\t'notify',\n\t'decision',\n\t'branch',\n] as const\n\nexport const APPROVAL_QUORUM_KINDS = ['count'] as const\n\nexport const APPROVAL_CALLBACK_KINDS = ['mutation', 'action'] as const\n\nexport const APPROVAL_DESCRIPTOR_SCHEMA_VERSION = 1 as const\n\n/** Version of the component-owned tables and indexes deployed by cvx-kit. */\nexport const APPROVAL_COMPONENT_SCHEMA_VERSION = 1 as const\n\n/** Indexes required by approval evidence reads and decision writes. */\nexport const APPROVAL_DECISION_HISTORY_INDEX =\n\t'by_runId_and_decidedAt' as const\nexport const APPROVAL_ACTOR_DECISION_INDEX =\n\t'by_runId_and_stepKey_and_actor_actorRef' as const\nexport const APPROVAL_REQUIRED_DECISION_INDEXES = [\n\tAPPROVAL_DECISION_HISTORY_INDEX,\n\tAPPROVAL_ACTOR_DECISION_INDEX,\n] as const\n\nexport const APPROVAL_AUDIT_RETENTION_CATEGORY = 'approval-protocol'\nexport const MIN_APPROVAL_AUDIT_RETENTION_DAYS = 1\nexport const MAX_APPROVAL_AUDIT_RETENTION_DAYS = 3_650\nexport const MAX_APPROVAL_AUDIT_CLEANUP_BATCH_SIZE = 100\n\nexport const MAX_APPROVAL_REFERENCE_LENGTH = 256\nexport const MAX_APPROVAL_NAME_LENGTH = 96\nexport const MAX_APPROVAL_REASON_LENGTH = 500\nexport const MAX_APPROVAL_CAPABILITIES = 32\nexport const MAX_APPROVAL_METADATA_ENTRIES = 32\nexport const MAX_APPROVAL_METADATA_KEY_LENGTH = 64\nexport const MAX_APPROVAL_METADATA_VALUE_LENGTH = 512\nexport const MAX_APPROVAL_WORKFLOW_STEPS = 32\nexport const MAX_APPROVAL_DECISIONS_PER_STEP = 8\n\nexport type ApprovalRunState = (typeof APPROVAL_RUN_STATES)[number]\nexport type ApprovalDecision = (typeof APPROVAL_DECISIONS)[number]\nexport type ApprovalExecutionState = (typeof APPROVAL_EXECUTION_STATES)[number]\nexport type ApprovalStepKind = (typeof APPROVAL_STEP_KINDS)[number]\nexport type ApprovalCallbackKind = (typeof APPROVAL_CALLBACK_KINDS)[number]\n"],"mappings":"AAAA,MAAa,EAAsB,CAClC,UACA,WACA,WACA,UACA,UACD,EAEa,EAAqB,CAAC,WAAY,UAAU,EAE5C,EAA4B,CACxC,aACA,YACA,WACA,QACD,EAEa,EAAsB,CAClC,WACA,SACA,SACA,WACA,QACD,EAEa,EAAwB,CAAC,OAAO,EAEhC,EAA0B,CAAC,WAAY,QAAQ,EAE/C,EAAqC,EAGrC,EAAoC,EAGpC,EACZ,yBACY,EACZ,0CACY,EAAqC,CACjD,EACA,CACD,EAEa,EAAoC,oBACpC,EAAoC,EACpC,EAAoC,KACpC,EAAwC,IAExC,EAAgC,IAChC,EAA2B,GAC3B,EAA6B,IAC7B,EAA4B,GAC5B,EAAgC,GAChC,EAAmC,GACnC,EAAqC,IACrC,EAA8B,GAC9B,EAAkC"}
1
+ {"version":3,"file":"constants.mjs","names":[],"sources":["../../../src/components/approvals/constants.ts"],"sourcesContent":["export const APPROVAL_RUN_STATES = [\n\t'pending',\n\t'approved',\n\t'rejected',\n\t'expired',\n\t'canceled',\n] as const\n\nexport const APPROVAL_DECISIONS = ['approved', 'rejected'] as const\n\nexport const APPROVAL_EXECUTION_STATES = ['inProgress', 'completed', 'canceled', 'failed'] as const\n\nexport const APPROVAL_STEP_KINDS = ['mutation', 'action', 'notify', 'decision', 'branch'] as const\n\nexport const APPROVAL_QUORUM_KINDS = ['count'] as const\n\nexport const APPROVAL_CALLBACK_KINDS = ['mutation', 'action'] as const\n\nexport const APPROVAL_DESCRIPTOR_SCHEMA_VERSION = 1 as const\n\n/** Version of the component-owned tables and indexes deployed by cvx-kit. */\nexport const APPROVAL_COMPONENT_SCHEMA_VERSION = 1 as const\n\n/** Indexes required by approval evidence reads and decision writes. */\nexport const APPROVAL_DECISION_HISTORY_INDEX = 'by_runId_and_decidedAt' as const\nexport const APPROVAL_ACTOR_DECISION_INDEX = 'by_runId_and_stepKey_and_actor_actorRef' as const\nexport const APPROVAL_REQUIRED_DECISION_INDEXES = [\n\tAPPROVAL_DECISION_HISTORY_INDEX,\n\tAPPROVAL_ACTOR_DECISION_INDEX,\n] as const\n\nexport const APPROVAL_AUDIT_RETENTION_CATEGORY = 'approval-protocol'\nexport const MIN_APPROVAL_AUDIT_RETENTION_DAYS = 1\nexport const MAX_APPROVAL_AUDIT_RETENTION_DAYS = 3_650\nexport const MAX_APPROVAL_AUDIT_CLEANUP_BATCH_SIZE = 100\n\nexport const MAX_APPROVAL_REFERENCE_LENGTH = 256\nexport const MAX_APPROVAL_NAME_LENGTH = 96\nexport const MAX_APPROVAL_REASON_LENGTH = 500\nexport const MAX_APPROVAL_CAPABILITIES = 32\nexport const MAX_APPROVAL_METADATA_ENTRIES = 32\nexport const MAX_APPROVAL_METADATA_KEY_LENGTH = 64\nexport const MAX_APPROVAL_METADATA_VALUE_LENGTH = 512\nexport const MAX_APPROVAL_WORKFLOW_STEPS = 32\nexport const MAX_APPROVAL_DECISIONS_PER_STEP = 8\n\nexport type ApprovalRunState = (typeof APPROVAL_RUN_STATES)[number]\nexport type ApprovalDecision = (typeof APPROVAL_DECISIONS)[number]\nexport type ApprovalExecutionState = (typeof APPROVAL_EXECUTION_STATES)[number]\nexport type ApprovalStepKind = (typeof APPROVAL_STEP_KINDS)[number]\nexport type ApprovalCallbackKind = (typeof APPROVAL_CALLBACK_KINDS)[number]\n"],"mappings":"AAAA,MAAa,EAAsB,CAClC,UACA,WACA,WACA,UACA,UACD,EAEa,EAAqB,CAAC,WAAY,UAAU,EAE5C,EAA4B,CAAC,aAAc,YAAa,WAAY,QAAQ,EAE5E,EAAsB,CAAC,WAAY,SAAU,SAAU,WAAY,QAAQ,EAE3E,EAAwB,CAAC,OAAO,EAEhC,EAA0B,CAAC,WAAY,QAAQ,EAE/C,EAAqC,EAGrC,EAAoC,EAGpC,EAAkC,yBAClC,EAAgC,0CAChC,EAAqC,CACjD,EACA,CACD,EAEa,EAAoC,oBACpC,EAAoC,EACpC,EAAoC,KACpC,EAAwC,IAExC,EAAgC,IAChC,EAA2B,GAC3B,EAA6B,IAC7B,EAA4B,GAC5B,EAAgC,GAChC,EAAmC,GACnC,EAAqC,IACrC,EAA8B,GAC9B,EAAkC"}