cvx-kit 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/components/approvals/audit.mjs +1 -1
  2. package/package.json +2 -8
  3. package/src/test.ts +7 -5
  4. package/src/auth.ts +0 -473
  5. package/src/components/approvals/_generated/api.ts +0 -69
  6. package/src/components/approvals/_generated/component.ts +0 -335
  7. package/src/components/approvals/_generated/dataModel.ts +0 -60
  8. package/src/components/approvals/_generated/server.ts +0 -156
  9. package/src/components/approvals/audit.ts +0 -148
  10. package/src/components/approvals/client.ts +0 -321
  11. package/src/components/approvals/constants.ts +0 -51
  12. package/src/components/approvals/convex.config.ts +0 -10
  13. package/src/components/approvals/decisions.ts +0 -376
  14. package/src/components/approvals/functions.ts +0 -27
  15. package/src/components/approvals/requests.ts +0 -240
  16. package/src/components/approvals/schema.ts +0 -23
  17. package/src/components/approvals/validators.ts +0 -272
  18. package/src/components/approvals/workflow.ts +0 -204
  19. package/src/components/approvals/workflow_steps.ts +0 -189
  20. package/src/components/foundation/_generated/api.ts +0 -62
  21. package/src/components/foundation/_generated/component.ts +0 -29
  22. package/src/components/foundation/_generated/dataModel.ts +0 -60
  23. package/src/components/foundation/_generated/server.ts +0 -156
  24. package/src/components/foundation/client.ts +0 -301
  25. package/src/components/foundation/convex.config.ts +0 -3
  26. package/src/components/foundation/functions.ts +0 -10
  27. package/src/components/foundation/modules/command/__tests__/command.test.ts +0 -106
  28. package/src/components/foundation/modules/command/command.ts +0 -154
  29. package/src/components/foundation/modules/observability/__tests__/observability.test.ts +0 -79
  30. package/src/components/foundation/modules/observability/observability.ts +0 -95
  31. package/src/components/foundation/modules/query/__tests__/query.test.ts +0 -25
  32. package/src/components/foundation/modules/query/query.ts +0 -47
  33. package/src/components/foundation/result.ts +0 -77
  34. package/src/components/foundation/schema.ts +0 -4
  35. package/src/components/foundation/telemetry.ts +0 -23
  36. package/src/errors.ts +0 -25
  37. package/src/index.ts +0 -56
  38. package/src/tenancy.ts +0 -110
  39. package/src/triggers.ts +0 -124
  40. package/src/zod-table.ts +0 -283
@@ -1,2 +1,2 @@
1
- import{components as e}from"./_generated/api.mjs";import{mutation as t,query as n}from"./_generated/server.mjs";import{APPROVAL_AUDIT_RETENTION_CATEGORY as r,MAX_APPROVAL_AUDIT_RETENTION_DAYS as i}from"./constants.mjs";import{v as a}from"convex/values";import{AuditLog as o}from"convex-audit-log";const s=new o(e.auditLog,{samplingEnabled:!1}),c=n({args:{resourceType:a.string(),resourceRef:a.string(),limit:a.optional(a.number())},returns:a.array(a.object({action:a.string(),actorRef:a.optional(a.string()),correlationKey:a.string(),transition:a.string()})),handler:async(e,t)=>{let n=Math.max(1,Math.min(Math.trunc(t.limit??100),100)),r=await s.queryByResource(e,{resourceType:t.resourceType,resourceId:t.resourceRef,limit:n}),i=new Map;for(let e of r){let t=p(e);t&&i.set(t.correlationKey,t)}return[...i.values()]}}),l=t({args:{olderThanDays:a.number(),batchSize:a.number()},returns:a.number(),handler:(e,t)=>{let n=f(t.olderThanDays,1,i,`olderThanDays`),a=f(t.batchSize,1,100,`batchSize`);return s.cleanup(e,{olderThanDays:n,batchSize:a,retentionCategory:r})}});function u(e){return{action:e.action,...e.actorRef===void 0?{}:{actorId:e.actorRef},resourceType:e.resourceType,resourceId:e.resourceRef,severity:e.severity??`info`,tags:[`approvals`],retentionCategory:r,metadata:{correlationKey:`${e.runId}:${e.transition}`,runId:e.runId,transition:e.transition}}}async function d(e,t){await s.log(e,u(t))}function f(e,t,n,r){if(!Number.isInteger(e)||e<t||e>n)throw Error(`${r} must be an integer between ${t} and ${n}`);return e}function p(e){if(!e||typeof e!=`object`)return null;let t=e,n=t.metadata;if(!n||typeof n!=`object`)return null;let r=n;return typeof t.action!=`string`||typeof r.correlationKey!=`string`||typeof r.transition!=`string`?null:{action:t.action,...typeof t.actorId==`string`?{actorRef:t.actorId}:{},correlationKey:r.correlationKey,transition:r.transition}}export{s as approvalAudit,u as approvalAuditEvent,l as cleanup,c as history,d as logApprovalTransition};
1
+ import{components as e}from"./_generated/api.mjs";import{mutation as t,query as n}from"./_generated/server.mjs";import{APPROVAL_AUDIT_RETENTION_CATEGORY as r,MAX_APPROVAL_AUDIT_RETENTION_DAYS as i}from"./constants.mjs";import{AuditLog as a}from"convex-audit-log";import{v as o}from"convex/values";const s=new a(e.auditLog,{samplingEnabled:!1}),c=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 s.queryByResource(e,{resourceType:t.resourceType,resourceId:t.resourceRef,limit:n}),i=new Map;for(let e of r){let t=p(e);t&&i.set(t.correlationKey,t)}return[...i.values()]}}),l=t({args:{olderThanDays:o.number(),batchSize:o.number()},returns:o.number(),handler:(e,t)=>{let n=f(t.olderThanDays,1,i,`olderThanDays`),a=f(t.batchSize,1,100,`batchSize`);return s.cleanup(e,{olderThanDays:n,batchSize:a,retentionCategory:r})}});function u(e){return{action:e.action,...e.actorRef===void 0?{}:{actorId:e.actorRef},resourceType:e.resourceType,resourceId:e.resourceRef,severity:e.severity??`info`,tags:[`approvals`],retentionCategory:r,metadata:{correlationKey:`${e.runId}:${e.transition}`,runId:e.runId,transition:e.transition}}}async function d(e,t){await s.log(e,u(t))}function f(e,t,n,r){if(!Number.isInteger(e)||e<t||e>n)throw Error(`${r} must be an integer between ${t} and ${n}`);return e}function p(e){if(!e||typeof e!=`object`)return null;let t=e,n=t.metadata;if(!n||typeof n!=`object`)return null;let r=n;return typeof t.action!=`string`||typeof r.correlationKey!=`string`||typeof r.transition!=`string`?null:{action:t.action,...typeof t.actorId==`string`?{actorRef:t.actorId}:{},correlationKey:r.correlationKey,transition:r.transition}}export{s as approvalAudit,u as approvalAuditEvent,l as cleanup,c as history,d as logApprovalTransition};
2
2
  //# sourceMappingURL=audit.mjs.map
package/package.json CHANGED
@@ -1,16 +1,10 @@
1
1
  {
2
2
  "name": "cvx-kit",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Reusable Convex application kit: foundation kernels, approvals component, zod table boundaries, WorkOS-backed auth function constructors, and audited command protocol.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
8
- "files": [
9
- "dist",
10
- "src",
11
- "!src/docs",
12
- "docs"
13
- ],
14
8
  "exports": {
15
9
  "./package.json": "./package.json",
16
10
  ".": {
@@ -74,7 +68,7 @@
74
68
  "typecheck": "tsc --noEmit",
75
69
  "test": "vp test run",
76
70
  "docs:sync": "rm -f docs/*.md && cp src/docs/*.md docs/",
77
- "prepublishOnly": "bun run typecheck && bun run test && bun run build && bun run docs:sync",
71
+ "prepublishOnly": "bun run build && bun run typecheck && bun run test && bun run docs:sync",
78
72
  "build:codegen": "convex codegen --component-dir ./src/components/foundation && convex codegen --component-dir ./src/components/approvals && vp pack",
79
73
  "release": "npm version patch && git push --follow-tags",
80
74
  "release:minor": "npm version minor && git push --follow-tags"
package/src/test.ts CHANGED
@@ -2,16 +2,18 @@
2
2
  * Test helpers for consumers, following the official component template:
3
3
  * register the kit's components on a convex-test instance so host-app tests
4
4
  * run against them in-memory. Requires a vitest-compatible runner
5
- * (import.meta.glob), which is why this export ships as source.
5
+ * (import.meta.glob), which is why this file ships as source — but it
6
+ * registers the COMPILED component modules from dist, so dist is the only
7
+ * code the package ships.
6
8
  */
7
- import approvalsSchema from './components/approvals/schema'
8
- import foundationSchema from './components/foundation/schema'
9
+ import approvalsSchema from '../dist/components/approvals/schema.mjs'
10
+ import foundationSchema from '../dist/components/foundation/schema.mjs'
9
11
 
10
12
  const foundationModules = import.meta.glob(
11
- './components/foundation/**/!(*.test).ts',
13
+ '../dist/components/foundation/**/*.mjs',
12
14
  )
13
15
  const approvalsModules = import.meta.glob(
14
- './components/approvals/**/!(*.test).ts',
16
+ '../dist/components/approvals/**/*.mjs',
15
17
  )
16
18
 
17
19
  type RegistersComponents = {
package/src/auth.ts DELETED
@@ -1,473 +0,0 @@
1
- import {
2
- zCustomAction,
3
- zCustomMutation,
4
- zCustomQuery,
5
- } from 'convex-helpers/server/zod4'
6
- import type {
7
- ActionBuilder,
8
- DocumentByInfo,
9
- GenericActionCtx,
10
- GenericDataModel,
11
- GenericMutationCtx,
12
- GenericQueryCtx,
13
- GenericTableInfo,
14
- IndexNames,
15
- IndexRange,
16
- IndexRangeBuilder,
17
- MutationBuilder,
18
- NamedIndex,
19
- Query,
20
- QueryBuilder,
21
- QueryInitializer,
22
- UserIdentity,
23
- } from 'convex/server'
24
- import type { Triggers } from 'convex-helpers/server/triggers'
25
- import type { TableNamesInDataModel } from 'convex/server'
26
- import { defaultErrors, type ErrorFactory } from './errors'
27
- import {
28
- composeRules,
29
- createTenantRules,
30
- wrapDatabaseReader,
31
- wrapDatabaseWriter,
32
- type RLSConfig,
33
- type Rules,
34
- } from './tenancy'
35
-
36
- /** The default vocabulary; every app can supply its own via the Role generic. */
37
- export type DefaultRole = 'reader' | 'writer' | 'admin'
38
-
39
- export type Actor<Role extends string> = Readonly<{
40
- userId: string
41
- organizationId: string
42
- role: Role
43
- }>
44
-
45
- export type AuthBundle<Role extends string> = Readonly<{
46
- identity: UserIdentity
47
- user: Readonly<{ id: string }>
48
- org: Readonly<{ organizationId: string; role: Role }>
49
- role: Role
50
- actor: Actor<Role>
51
- /** The resolved tenant — security.tenancy.resolve, or the organization id. */
52
- tenant: string
53
- }>
54
-
55
- /**
56
- * Row-level security configuration. Tenant isolation and role-level rules
57
- * compose (AND) onto the same wrapped database: queries get a wrapped
58
- * reader, mutations get triggers first, then a wrapped writer.
59
- */
60
- export type SecurityConfig<
61
- DataModel extends GenericDataModel,
62
- Role extends string,
63
- > = {
64
- /**
65
- * Tenant isolation. `tables` is THE registry: every listed table gets
66
- * read/insert/modify gated on row.tenant === ctx.tenant. Pair with
67
- * tenantTable (schema) and tenantOwnership (triggers) on the same list.
68
- */
69
- tenancy?: {
70
- tables: readonly TableNamesInDataModel<DataModel>[]
71
- /** Maps the auth bundle to the tenant; defaults to org.organizationId. */
72
- resolve?: (bundle: Omit<AuthBundle<Role>, 'tenant'>) => string
73
- }
74
- /**
75
- * Role-level (or any policy) rules derived from the authenticated bundle,
76
- * composed AND-wise with tenant isolation. E.g. gate `modify` on a table
77
- * to admin roles.
78
- */
79
- rules?: (bundle: AuthBundle<Role>) => Rules<unknown, DataModel>
80
- /**
81
- * Policy for tables with no rule. Defaults to 'deny' when tenancy is
82
- * configured (unlisted tables are unreachable — fail closed), 'allow'
83
- * otherwise.
84
- */
85
- defaultPolicy?: RLSConfig['defaultPolicy']
86
- }
87
-
88
- type AnyAuthContext<DataModel extends GenericDataModel> =
89
- | GenericQueryCtx<DataModel>
90
- | GenericMutationCtx<DataModel>
91
- | GenericActionCtx<DataModel>
92
-
93
- export type AuthFunctionsConfig<
94
- DataModel extends GenericDataModel,
95
- Role extends string,
96
- > = {
97
- /** The consumer's generated function builders (from ./_generated/server). */
98
- query: QueryBuilder<DataModel, 'public'>
99
- mutation: MutationBuilder<DataModel, 'public'>
100
- action: ActionBuilder<DataModel, 'public'>
101
- internalMutation: MutationBuilder<DataModel, 'internal'>
102
- internalAction: ActionBuilder<DataModel, 'internal'>
103
- /** Resolves the synchronized principal, e.g. (ctx) => authKit.getAuthUser(ctx). */
104
- getAuthUser: (
105
- ctx: AnyAuthContext<DataModel>,
106
- ) => Promise<{ id: string } | null>
107
- /**
108
- * Maps an identity role slug onto the app's role vocabulary. Returning
109
- * null rejects the caller with FORBIDDEN — unknown roles never pass.
110
- */
111
- mapRole: (roleSlug?: string) => Role | null
112
- /** Roles allowed through the admin* constructors. */
113
- adminRoles: readonly NoInfer<Role>[]
114
- /**
115
- * Live membership check for actions (queries/mutations trust the JWT).
116
- * Authentication fails closed with FORBIDDEN when verification errors.
117
- * Omit to let actions trust the JWT like queries do.
118
- */
119
- verifyMembership?: (input: {
120
- userId: string
121
- organizationId: string
122
- }) => Promise<{ organizationId: string; roleSlug: string } | null>
123
- /** Host error policy; defaults to throwing KitError. */
124
- errors?: ErrorFactory
125
- /**
126
- * Application trigger registry. When set, every authMutation,
127
- * roleMutation, and systemMutation write runs through triggers.wrapDB —
128
- * evaluations registered on tables fire structurally.
129
- */
130
- triggers?: Triggers<DataModel>
131
- /** Lower-level alternative to `triggers`: a custom ctx wrapper. */
132
- wrapDB?: (
133
- ctx: GenericMutationCtx<DataModel>,
134
- ) => GenericMutationCtx<DataModel>
135
- /** Upper bound for include() reads. */
136
- maxIncludedQueryRows?: number
137
- /**
138
- * Row-level security: tenant isolation (from a table registry) and
139
- * role-level rules, applied structurally inside every auth*, role*, and
140
- * admin* constructor. system* constructors stay unwrapped (trusted).
141
- */
142
- security?: SecurityConfig<DataModel, Role>
143
- }
144
-
145
- /** WorkOS-style default: `member` writes; reader/writer/admin pass through. */
146
- export function defaultRoleMap(roleSlug?: string): DefaultRole | null {
147
- const normalized = roleSlug?.trim().toLowerCase()
148
- if (normalized === 'member') return 'writer'
149
- return normalized === 'reader' ||
150
- normalized === 'writer' ||
151
- normalized === 'admin'
152
- ? normalized
153
- : null
154
- }
155
-
156
- export type IncludedQuery<Table extends GenericTableInfo> = {
157
- when<Value>(
158
- value: Value | null | undefined,
159
- select: (query: QueryInitializer<Table>, value: Value) => Query<Table>,
160
- ): IncludedQuery<Table>
161
- matching<IndexName extends IndexNames<Table>>(
162
- indexName: IndexName,
163
- indexRange?: (
164
- query: IndexRangeBuilder<
165
- DocumentByInfo<Table>,
166
- NamedIndex<Table, IndexName>
167
- >,
168
- ) => IndexRange,
169
- shouldMatch?: boolean,
170
- ): IncludedQuery<Table>
171
- otherwise(
172
- select: (query: QueryInitializer<Table>) => Query<Table>,
173
- ): Query<Table>
174
- resolve(): Query<Table>
175
- execute<Result = Awaited<ReturnType<Query<Table>['take']>>>(
176
- limit: number,
177
- transform?: (
178
- data: Awaited<ReturnType<Query<Table>['take']>>,
179
- ) => Result | Promise<Result>,
180
- ): Promise<Result>
181
- }
182
-
183
- export type Include = <Table extends GenericTableInfo>(
184
- query: QueryInitializer<Table>,
185
- ) => IncludedQuery<Table>
186
-
187
- /** Builds an include() that selects the first matching indexed query and bounds the read. */
188
- export function createInclude(options?: {
189
- errors?: ErrorFactory
190
- maxRows?: number
191
- }): Include {
192
- const errors = options?.errors ?? defaultErrors
193
- const maxRows = options?.maxRows ?? 100
194
-
195
- function attach<Table extends GenericTableInfo>(
196
- query: QueryInitializer<Table>,
197
- selected?: Query<Table>,
198
- ): IncludedQuery<Table> {
199
- return {
200
- when: (value, select) =>
201
- selected || value === undefined || value === null
202
- ? attach(query, selected)
203
- : attach(query, select(query, value)),
204
- matching: (indexName, indexRange, shouldMatch = true) =>
205
- selected || !shouldMatch
206
- ? attach(query, selected)
207
- : attach(query, query.withIndex(indexName, indexRange)),
208
- otherwise: (select) => selected ?? select(query),
209
- resolve: () => selected ?? query.fullTableScan(),
210
- execute: async (limit, transform) => {
211
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > maxRows) {
212
- return errors.throw({
213
- code: 'INVALID_REQUEST_BOUNDARY',
214
- message: `A bounded query limit must be from 1 to ${maxRows}`,
215
- })
216
- }
217
- const data = await (selected ?? query.fullTableScan()).take(limit)
218
- return transform ? transform(data) : (data as never)
219
- },
220
- }
221
- }
222
-
223
- return (query) => attach(query)
224
- }
225
-
226
- /**
227
- * Builds the auth-aware function constructors from the host's injected
228
- * policy: identity resolution, the app's own role vocabulary, live
229
- * membership verification for actions, trigger wrapping for mutations,
230
- * and errors. Role-based access is first-class — every constructor family
231
- * has a role-gated variant (roleQuery/roleMutation/roleAction), and the
232
- * admin* constructors are just that gate applied to config.adminRoles.
233
- */
234
- export function createAuthFunctions<
235
- DataModel extends GenericDataModel,
236
- const Role extends string = DefaultRole,
237
- >(config: AuthFunctionsConfig<DataModel, Role>) {
238
- const errors = config.errors ?? defaultErrors
239
- const wrapDB = config.triggers
240
- ? (ctx: GenericMutationCtx<DataModel>) =>
241
- (config.triggers as Triggers<DataModel>).wrapDB(ctx)
242
- : (config.wrapDB ?? ((ctx) => ctx))
243
- const include = createInclude({
244
- errors,
245
- maxRows: config.maxIncludedQueryRows,
246
- })
247
-
248
- async function authenticatedUser(
249
- ctx: AnyAuthContext<DataModel>,
250
- ): Promise<AuthBundle<Role>> {
251
- const identity = await ctx.auth.getUserIdentity()
252
- const user = identity ? await config.getAuthUser(ctx) : null
253
- if (!identity || !user) return errors.throw({ code: 'UNAUTHENTICATED' })
254
- const organization = identity.organization
255
- const organizationId =
256
- organization &&
257
- typeof organization === 'object' &&
258
- 'organizationId' in organization &&
259
- typeof organization.organizationId === 'string'
260
- ? organization.organizationId
261
- : typeof identity.org_id === 'string'
262
- ? identity.org_id
263
- : null
264
- if (!organizationId) return errors.throw({ code: 'UNAUTHENTICATED' })
265
- const organizationRole =
266
- organization &&
267
- typeof organization === 'object' &&
268
- 'role' in organization &&
269
- typeof organization.role === 'string'
270
- ? organization.role
271
- : typeof identity.role === 'string'
272
- ? identity.role
273
- : undefined
274
- const role = config.mapRole(organizationRole)
275
- if (!role) return errors.throw({ code: 'FORBIDDEN' })
276
- const base = {
277
- identity,
278
- user: Object.freeze({ id: user.id }),
279
- org: Object.freeze({ organizationId, role }),
280
- role,
281
- actor: Object.freeze({ userId: user.id, organizationId, role }),
282
- }
283
- return Object.freeze({
284
- ...base,
285
- tenant: config.security?.tenancy?.resolve?.(base) ?? organizationId,
286
- })
287
- }
288
-
289
- const securityConfig: RLSConfig | undefined = config.security
290
- ? {
291
- defaultPolicy:
292
- config.security.defaultPolicy ??
293
- (config.security.tenancy ? 'deny' : 'allow'),
294
- }
295
- : undefined
296
-
297
- function securityRules(
298
- bundle: AuthBundle<Role>,
299
- ): Rules<unknown, DataModel> | undefined {
300
- const security = config.security
301
- if (!security) return undefined
302
- const sets: Rules<unknown, DataModel>[] = []
303
- if (security.tenancy) {
304
- sets.push(
305
- createTenantRules<DataModel>(bundle.tenant, security.tenancy.tables),
306
- )
307
- }
308
- if (security.rules) sets.push(security.rules(bundle))
309
- return sets.length === 1 ? sets[0] : composeRules(...sets)
310
- }
311
-
312
- function secureReader(
313
- ctx: GenericQueryCtx<DataModel>,
314
- bundle: AuthBundle<Role>,
315
- ): GenericQueryCtx<DataModel> {
316
- const rules = securityRules(bundle)
317
- if (!rules) return ctx
318
- return {
319
- ...ctx,
320
- db: wrapDatabaseReader({}, ctx.db, rules as never, securityConfig),
321
- }
322
- }
323
-
324
- function secureWriter(
325
- ctx: GenericMutationCtx<DataModel>,
326
- bundle: AuthBundle<Role>,
327
- ): GenericMutationCtx<DataModel> {
328
- const rules = securityRules(bundle)
329
- if (!rules) return ctx
330
- // Triggers wrap first (ctx already trigger-wrapped by the caller),
331
- // then row-level security wraps the triggered db — order matters.
332
- return {
333
- ...ctx,
334
- db: wrapDatabaseWriter({}, ctx.db, rules as never, securityConfig),
335
- }
336
- }
337
-
338
- async function verifiedMembership(userId: string, organizationId: string) {
339
- if (!config.verifyMembership) return null
340
- try {
341
- const membership = await config.verifyMembership({
342
- userId,
343
- organizationId,
344
- })
345
- if (membership && membership.organizationId === organizationId) {
346
- return membership
347
- }
348
- } catch {
349
- // Authentication fails closed when membership cannot be verified.
350
- }
351
- return errors.throw({ code: 'FORBIDDEN' })
352
- }
353
-
354
- function requireRole(role: Role, allowed: readonly Role[]) {
355
- if (!allowed.includes(role)) return errors.throw({ code: 'FORBIDDEN' })
356
- }
357
-
358
- async function queryInput(ctx: GenericQueryCtx<DataModel>) {
359
- const bundle = await authenticatedUser(ctx)
360
- return {
361
- ctx: { ...secureReader(ctx, bundle), include, ...bundle },
362
- args: {},
363
- }
364
- }
365
-
366
- async function mutationInput(ctx: GenericMutationCtx<DataModel>) {
367
- const bundle = await authenticatedUser(ctx)
368
- return {
369
- ctx: { ...secureWriter(wrapDB(ctx), bundle), include, ...bundle },
370
- args: {},
371
- }
372
- }
373
-
374
- async function actionInput(ctx: GenericActionCtx<DataModel>) {
375
- const authenticated = await authenticatedUser(ctx)
376
- const membership = await verifiedMembership(
377
- authenticated.user.id,
378
- authenticated.org.organizationId,
379
- )
380
- if (!membership) return { ctx: { ...ctx, ...authenticated }, args: {} }
381
- const role = config.mapRole(membership.roleSlug)
382
- if (!role) return errors.throw({ code: 'FORBIDDEN' })
383
- const verified = {
384
- identity: authenticated.identity,
385
- user: authenticated.user,
386
- org: Object.freeze({
387
- organizationId: membership.organizationId,
388
- role,
389
- }),
390
- role,
391
- actor: Object.freeze({
392
- userId: authenticated.user.id,
393
- organizationId: membership.organizationId,
394
- role,
395
- }),
396
- }
397
- return {
398
- ctx: {
399
- ...ctx,
400
- ...verified,
401
- // Tenant re-derives from the live-verified organization.
402
- tenant:
403
- config.security?.tenancy?.resolve?.(verified) ??
404
- membership.organizationId,
405
- },
406
- args: {},
407
- }
408
- }
409
-
410
- /** Query constructor allowing only the given roles. */
411
- function roleQuery(...allowed: readonly Role[]) {
412
- return zCustomQuery(config.query, {
413
- args: {},
414
- input: async (ctx) => {
415
- const authenticated = await queryInput(ctx)
416
- requireRole(authenticated.ctx.actor.role, allowed)
417
- return authenticated
418
- },
419
- })
420
- }
421
-
422
- /** Mutation constructor allowing only the given roles. */
423
- function roleMutation(...allowed: readonly Role[]) {
424
- return zCustomMutation(config.mutation, {
425
- args: {},
426
- input: async (ctx) => {
427
- const authenticated = await mutationInput(ctx)
428
- requireRole(authenticated.ctx.actor.role, allowed)
429
- return authenticated
430
- },
431
- })
432
- }
433
-
434
- /** Action constructor allowing only the given roles (post live verification). */
435
- function roleAction(...allowed: readonly Role[]) {
436
- return zCustomAction(config.action, {
437
- args: {},
438
- input: async (ctx) => {
439
- const authenticated = await actionInput(ctx)
440
- requireRole(authenticated.ctx.actor.role, allowed)
441
- return authenticated
442
- },
443
- })
444
- }
445
-
446
- return {
447
- include,
448
- authenticatedUser,
449
- roleQuery,
450
- roleMutation,
451
- roleAction,
452
- authQuery: zCustomQuery(config.query, { args: {}, input: queryInput }),
453
- authMutation: zCustomMutation(config.mutation, {
454
- args: {},
455
- input: mutationInput,
456
- }),
457
- authAction: zCustomAction(config.action, {
458
- args: {},
459
- input: actionInput,
460
- }),
461
- adminQuery: roleQuery(...config.adminRoles),
462
- adminMutation: roleMutation(...config.adminRoles),
463
- adminAction: roleAction(...config.adminRoles),
464
- systemMutation: zCustomMutation(config.internalMutation, {
465
- args: {},
466
- input: async (ctx) => ({ ctx: { ...wrapDB(ctx), include }, args: {} }),
467
- }),
468
- systemAction: zCustomAction(config.internalAction, {
469
- args: {},
470
- input: async (ctx) => ({ ctx, args: {} }),
471
- }),
472
- }
473
- }
@@ -1,69 +0,0 @@
1
- /* eslint-disable */
2
- /**
3
- * Generated `api` utility.
4
- *
5
- * THIS CODE IS AUTOMATICALLY GENERATED.
6
- *
7
- * To regenerate, run `npx convex dev`.
8
- * @module
9
- */
10
-
11
- import type * as audit from "../audit.js";
12
- import type * as client from "../client.js";
13
- import type * as constants from "../constants.js";
14
- import type * as decisions from "../decisions.js";
15
- import type * as functions from "../functions.js";
16
- import type * as requests from "../requests.js";
17
- import type * as validators from "../validators.js";
18
- import type * as workflow from "../workflow.js";
19
- import type * as workflow_steps from "../workflow_steps.js";
20
-
21
- import type {
22
- ApiFromModules,
23
- FilterApi,
24
- FunctionReference,
25
- } from "convex/server";
26
- import { anyApi, componentsGeneric } from "convex/server";
27
-
28
- const fullApi: ApiFromModules<{
29
- audit: typeof audit;
30
- client: typeof client;
31
- constants: typeof constants;
32
- decisions: typeof decisions;
33
- functions: typeof functions;
34
- requests: typeof requests;
35
- validators: typeof validators;
36
- workflow: typeof workflow;
37
- workflow_steps: typeof workflow_steps;
38
- }> = anyApi as any;
39
-
40
- /**
41
- * A utility for referencing Convex functions in your app's public API.
42
- *
43
- * Usage:
44
- * ```js
45
- * const myFunctionReference = api.myModule.myFunction;
46
- * ```
47
- */
48
- export const api: FilterApi<
49
- typeof fullApi,
50
- FunctionReference<any, "public">
51
- > = anyApi as any;
52
-
53
- /**
54
- * A utility for referencing Convex functions in your app's internal API.
55
- *
56
- * Usage:
57
- * ```js
58
- * const myFunctionReference = internal.myModule.myFunction;
59
- * ```
60
- */
61
- export const internal: FilterApi<
62
- typeof fullApi,
63
- FunctionReference<any, "internal">
64
- > = anyApi as any;
65
-
66
- export const components = componentsGeneric() as unknown as {
67
- workflow: import("@convex-dev/workflow/_generated/component.js").ComponentApi<"workflow">;
68
- auditLog: import("convex-audit-log/_generated/component.js").ComponentApi<"auditLog">;
69
- };