cvx-kit 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +69 -24
- package/dist/agent-tools.d.mts +28 -12
- package/dist/agent-tools.mjs +1 -1
- package/dist/agent-tools.mjs.map +1 -1
- package/dist/auth.d.mts +4 -2
- package/dist/auth.mjs +1 -1
- package/dist/auth.mjs.map +1 -1
- package/dist/components/approvals/_generated/api.d.mts +1 -1
- package/dist/components/approvals/audit.mjs +1 -1
- package/dist/components/approvals/audit.mjs.map +1 -1
- package/dist/components/approvals/client.d.mts +1 -1
- package/dist/components/approvals/client.mjs +1 -1
- package/dist/components/approvals/client.mjs.map +1 -1
- package/dist/components/approvals/constants.mjs.map +1 -1
- package/dist/components/approvals/decisions.d.mts +6 -7
- package/dist/components/approvals/decisions.mjs +1 -1
- package/dist/components/approvals/decisions.mjs.map +1 -1
- package/dist/components/approvals/functions.mjs +1 -1
- package/dist/components/approvals/functions.mjs.map +1 -1
- package/dist/components/approvals/health.mjs.map +1 -1
- package/dist/components/approvals/requests.mjs.map +1 -1
- package/dist/components/approvals/validators.mjs.map +1 -1
- package/dist/components/approvals/workflow.mjs +1 -1
- package/dist/components/approvals/workflow.mjs.map +1 -1
- package/dist/components/approvals/workflow_steps.d.mts +3 -3
- package/dist/components/approvals/workflow_steps.mjs +1 -1
- package/dist/components/approvals/workflow_steps.mjs.map +1 -1
- package/dist/components/foundation/client.d.mts +17 -24
- package/dist/components/foundation/client.mjs +1 -1
- package/dist/components/foundation/client.mjs.map +1 -1
- package/dist/components/foundation/modules/command/command.d.mts +4 -4
- package/dist/components/foundation/modules/command/command.mjs.map +1 -1
- package/dist/components/foundation/modules/observability/observability.d.mts +1 -1
- package/dist/components/foundation/modules/observability/observability.mjs +1 -1
- package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
- package/dist/components/foundation/modules/query/query.d.mts +8 -8
- package/dist/components/foundation/modules/query/query.mjs +1 -1
- package/dist/components/foundation/modules/query/query.mjs.map +1 -1
- package/dist/components/foundation/operation.d.mts +1 -1
- package/dist/components/foundation/operation.mjs.map +1 -1
- package/dist/components/foundation/result.d.mts +1 -1
- package/dist/components/foundation/result.mjs.map +1 -1
- package/dist/components/foundation/telemetry.mjs +1 -1
- package/dist/components/foundation/telemetry.mjs.map +1 -1
- package/dist/crud.d.mts +177 -16
- package/dist/crud.mjs +1 -1
- package/dist/crud.mjs.map +1 -1
- package/dist/middleware.d.mts +7 -7
- package/dist/middleware.mjs +1 -1
- package/dist/middleware.mjs.map +1 -1
- package/dist/oxlint.d.mts +9 -0
- package/dist/oxlint.mjs +2 -0
- package/dist/oxlint.mjs.map +1 -0
- package/dist/tenancy.mjs +1 -1
- package/dist/tenancy.mjs.map +1 -1
- package/dist/triggers.mjs +1 -1
- package/dist/triggers.mjs.map +1 -1
- package/dist/webhooks.d.mts +17 -106
- package/dist/webhooks.mjs.map +1 -1
- package/dist/zod-table.d.mts +46 -90
- package/dist/zod-table.mjs +1 -1
- package/dist/zod-table.mjs.map +1 -1
- package/docs/approvals.md +57 -56
- package/docs/architecture.md +1 -1
- package/docs/auth.md +57 -33
- package/docs/commands.md +53 -53
- package/docs/conventions.md +40 -40
- package/docs/crud.md +5 -5
- package/docs/llms.md +117 -71
- package/docs/maintainability.md +4 -4
- package/docs/migration.md +33 -33
- package/docs/tenancy.md +5 -8
- package/docs/triggers.md +4 -2
- package/docs/upgrading.md +32 -7
- package/docs/zod-table.md +48 -45
- package/package.json +33 -22
- package/scripts/convex-config-bridge.mjs +7 -18
- package/scripts/smoke-packed-components.mjs +20 -61
- package/scripts/test-packed-ids.mjs +16 -24
- package/scripts/test-packed-oxlint.mjs +121 -0
- package/src/test.ts +8 -17
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,26 @@ 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
|
+
|
|
15
35
|
## [0.1.3] - 2026-09-06
|
|
16
36
|
|
|
17
37
|
### Fixed
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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):
|
package/dist/agent-tools.d.mts
CHANGED
|
@@ -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:
|
|
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<
|
|
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:
|
|
50
|
+
declare function createAgentTools<Table extends ToolTable, Handlers extends AgentToolHandlers>(table: Table, handlers: Handlers, options?: {
|
|
41
51
|
descriptions?: Partial<Record<keyof AgentToolHandlers, string>>;
|
|
42
|
-
}):
|
|
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
|
package/dist/agent-tools.mjs
CHANGED
|
@@ -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
|
|
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
|
package/dist/agent-tools.mjs.map
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
|
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{
|
|
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(
|
|
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
|
|
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 = [
|
|
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"}
|