cvx-kit 0.1.10 → 0.1.11
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 +17 -0
- package/dist/effect.d.mts +3 -2
- package/dist/effect.mjs +1 -1
- package/dist/modules/effect/api-runtime.mjs +1 -1
- package/dist/modules/effect/auth.d.mts +43 -0
- package/dist/modules/effect/auth.mjs +2 -0
- package/dist/modules/effect/auth.mjs.map +1 -0
- package/dist/modules/effect/client.d.mts +1 -1
- package/dist/modules/effect/client.mjs +1 -1
- package/dist/modules/effect/command.mjs +1 -1
- package/dist/modules/effect/crud.mjs +1 -1
- package/dist/modules/effect/query.mjs +1 -1
- package/dist/modules/effect/workflow.mjs +1 -1
- package/docs/auth.md +131 -1
- package/docs/effect.md +8 -2
- package/docs/llms.md +41 -20
- package/package.json +1 -1
- package/scripts/test-packed-auth.mjs +5 -1
- package/scripts/test-packed-effect.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,23 @@ are this file's matching section.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.1.11] - 2026-10-09
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Configure shared post-auth Effect services and command/query handles once across
|
|
20
|
+
all auth, role, admin, and system constructors with `createEffectAuthFunctions` (#24).
|
|
21
|
+
- Keep query, mutation, action, and internal system capabilities separately typed
|
|
22
|
+
and configured; preserve secured database wrapping and live action membership.
|
|
23
|
+
- Document the shared application factory with packed-compiled examples and
|
|
24
|
+
constructor capability guidance for coding agents.
|
|
25
|
+
|
|
26
|
+
### Verified
|
|
27
|
+
|
|
28
|
+
- Cover denied authentication, roles and membership, request-local scopes, context
|
|
29
|
+
collisions, nested command auditing, and registered mutation rollback.
|
|
30
|
+
- Preserve packed native auth context compatibility on Convex 1.45 and 1.46.
|
|
31
|
+
|
|
15
32
|
## [0.1.10] - 2026-10-09
|
|
16
33
|
|
|
17
34
|
### Fixed
|
package/dist/effect.d.mts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { OperationExecutor, OperationPublicResult, OperationTool, OperationToolConverter, OperationToolDefinition, OperationToolJsonSchema, OperationToolOutcome, SelectedOperation, bindOperationExecutor, createOperationTools, operationToolDialect, selectOperation } from "./modules/contracts/exposure.mjs";
|
|
2
2
|
import { ContractDecoderError, ContractDecoderRequirements, ContractValidationError, EffectDecoder, decodeEffectContract, effectContract, effectSchema, effectStandardSchema } from "./modules/effect/schema.mjs";
|
|
3
3
|
import { CallbackError, CallbackRequirements, EffectMiddlewareInput, EffectOperationArgument, EffectOperationError, EffectOperationHandlerResult, EffectOperationInput, EffectOperationRequirements, EffectOperationResult, EffectPreparation, EffectValue, effectOperationFactory } from "./modules/effect/operation.mjs";
|
|
4
|
+
import { EffectZodApiBuilder, effectZodApiBuilder } from "./modules/effect/api-zod.mjs";
|
|
5
|
+
import { EffectAuthFunctions, createEffectAuthFunctions } from "./modules/effect/auth.mjs";
|
|
4
6
|
import { EffectFoundationOptions, createEffectFoundation } from "./modules/effect/foundation.mjs";
|
|
5
7
|
import { EffectIdempotencyPreparation, effectTransactionalIdempotency } from "./modules/effect/idempotency.mjs";
|
|
6
8
|
import { EffectCrudConfig, EffectCrudContext, EffectCrudPage, EffectCrudPagination, EffectCrudRepository, EffectCrudTable, createEffectCrud } from "./modules/effect/crud.mjs";
|
|
7
9
|
import { EffectApiBuilder, effectApiBuilder } from "./modules/effect/api.mjs";
|
|
8
|
-
import { EffectZodApiBuilder, effectZodApiBuilder } from "./modules/effect/api-zod.mjs";
|
|
9
10
|
import { projectErrorCause } from "./modules/effect/errors.mjs";
|
|
10
11
|
import { EffectReceiptProtectedWorkflowMutation, effectReceiptWorkflowMutation, effectTerminalWorkflowAttempt, effectWorkflowAttempt, effectWorkflowStatusBindings } from "./modules/effect/workflow.mjs";
|
|
11
12
|
import "./modules/effect/client.mjs";
|
|
12
|
-
export { type CallbackError, type CallbackRequirements, type ContractDecoderError, type ContractDecoderRequirements, ContractValidationError, type EffectApiBuilder, type EffectCrudConfig, type EffectCrudContext, type EffectCrudPage, type EffectCrudPagination, type EffectCrudRepository, type EffectCrudTable, type EffectDecoder, type EffectFoundationOptions, type EffectIdempotencyPreparation, type EffectMiddlewareInput, type EffectOperationArgument, type EffectOperationError, type EffectOperationHandlerResult, type EffectOperationInput, type EffectOperationRequirements, type EffectOperationResult, type EffectPreparation, type EffectReceiptProtectedWorkflowMutation, type EffectValue, type EffectZodApiBuilder, type OperationExecutor, type OperationPublicResult, type OperationTool, type OperationToolConverter, type OperationToolDefinition, type OperationToolJsonSchema, type OperationToolOutcome, type SelectedOperation, bindOperationExecutor, createEffectCrud, createEffectFoundation, createOperationTools, decodeEffectContract, effectApiBuilder, effectContract, effectOperationFactory, effectReceiptWorkflowMutation, effectSchema, effectStandardSchema, effectTerminalWorkflowAttempt, effectTransactionalIdempotency, effectWorkflowAttempt, effectWorkflowStatusBindings, effectZodApiBuilder, operationToolDialect, projectErrorCause, selectOperation };
|
|
13
|
+
export { type CallbackError, type CallbackRequirements, type ContractDecoderError, type ContractDecoderRequirements, ContractValidationError, type EffectApiBuilder, type EffectAuthFunctions, type EffectCrudConfig, type EffectCrudContext, type EffectCrudPage, type EffectCrudPagination, type EffectCrudRepository, type EffectCrudTable, type EffectDecoder, type EffectFoundationOptions, type EffectIdempotencyPreparation, type EffectMiddlewareInput, type EffectOperationArgument, type EffectOperationError, type EffectOperationHandlerResult, type EffectOperationInput, type EffectOperationRequirements, type EffectOperationResult, type EffectPreparation, type EffectReceiptProtectedWorkflowMutation, type EffectValue, type EffectZodApiBuilder, type OperationExecutor, type OperationPublicResult, type OperationTool, type OperationToolConverter, type OperationToolDefinition, type OperationToolJsonSchema, type OperationToolOutcome, type SelectedOperation, bindOperationExecutor, createEffectAuthFunctions, createEffectCrud, createEffectFoundation, createOperationTools, decodeEffectContract, effectApiBuilder, effectContract, effectOperationFactory, effectReceiptWorkflowMutation, effectSchema, effectStandardSchema, effectTerminalWorkflowAttempt, effectTransactionalIdempotency, effectWorkflowAttempt, effectWorkflowStatusBindings, effectZodApiBuilder, operationToolDialect, projectErrorCause, selectOperation };
|
package/dist/effect.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{bindOperationExecutor as e,createOperationTools as t,operationToolDialect as n,selectOperation as r}from"./modules/contracts/exposure.mjs";import{
|
|
1
|
+
import{bindOperationExecutor as e,createOperationTools as t,operationToolDialect as n,selectOperation as r}from"./modules/contracts/exposure.mjs";import{projectErrorCause as i}from"./modules/effect/errors.mjs";import{effectZodApiBuilder as a}from"./modules/effect/api-zod.mjs";import{createEffectAuthFunctions as o}from"./modules/effect/auth.mjs";import{effectOperationFactory as s}from"./modules/effect/operation.mjs";import{ContractValidationError as c,decodeEffectContract as l,effectContract as u,effectSchema as d,effectStandardSchema as f}from"./modules/effect/schema.mjs";import{createEffectFoundation as p}from"./modules/effect/foundation.mjs";import{effectTransactionalIdempotency as m}from"./modules/effect/idempotency.mjs";import{createEffectCrud as h}from"./modules/effect/crud.mjs";import{effectApiBuilder as g}from"./modules/effect/api.mjs";import{effectReceiptWorkflowMutation as _,effectTerminalWorkflowAttempt as v,effectWorkflowAttempt as y,effectWorkflowStatusBindings as b}from"./modules/effect/workflow.mjs";export{c as ContractValidationError,e as bindOperationExecutor,o as createEffectAuthFunctions,h as createEffectCrud,p as createEffectFoundation,t as createOperationTools,l as decodeEffectContract,g as effectApiBuilder,u as effectContract,s as effectOperationFactory,_ as effectReceiptWorkflowMutation,d as effectSchema,f as effectStandardSchema,v as effectTerminalWorkflowAttempt,m as effectTransactionalIdempotency,y as effectWorkflowAttempt,b as effectWorkflowStatusBindings,a as effectZodApiBuilder,n as operationToolDialect,i as projectErrorCause,r as selectOperation};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{projectErrorCause as e}from"./errors.mjs";import{normalizeEffect as t}from"./normalize.mjs";import{ConvexError as n}from"convex/values";import{Cause as r,Context as i,Effect as a,Exit as o}from"effect";function s(s,c){return l=>{let u=typeof l==`function`?l:l.handler,d=async(s,...l)=>{let d=t(()=>c.services?c.services(s):i.empty()).pipe(a.flatMap(e=>t(()=>c.context?c.context(s):{}).pipe(a.flatMap(e=>a.sync(()=>{if(typeof e!=`object`||!e)throw Error(`Effect API context must be an object`);let t=Object.getPrototypeOf(e);if(t!==Object.prototype&&t!==null)throw Error(`Effect API context additions must be a plain record`);let n=s;for(let t of Reflect.ownKeys(e))if(t in n)throw Error(`Effect API context cannot replace ${String(t)}`);return c.context?Object.create(Object.getPrototypeOf(n),{...Object.getOwnPropertyDescriptors(n),...Object.getOwnPropertyDescriptors(e)}):s})),a.flatMap(e=>t(()=>u(e,...l))),a.provideContext(e)))),f=await a.runPromiseExit(a.scoped(d));if(o.isSuccess(f))return f.value;if(c.errors)throw new n(e(c.errors,f.cause));let p=f.cause.reasons.length===1?f.cause.reasons[0]:void 0;throw p&&r.isFailReason(p)?c.mapError?c.mapError(p.error):p.error:p&&r.isDieReason(p)?p.defect:Error(`Effect API execution failed`,{cause:f.cause})};return s(typeof l==`function`?d:{...l,handler:d})}}export{s as wrapEffectBuilder};
|
|
2
2
|
//# sourceMappingURL=api-runtime.mjs.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AuthBundle, AuthFunctions, AuthFunctionsConfig, DefaultRole, Include } from "../../auth.mjs";
|
|
2
|
+
import { EffectValue } from "./operation.mjs";
|
|
3
|
+
import { EffectApiContext, EffectApiOptions, EffectApiServices, ProvidedServices } from "./api-runtime.mjs";
|
|
4
|
+
import { EffectZodApiBuilder } from "./api-zod.mjs";
|
|
5
|
+
import { CustomBuilder } from "convex-helpers/server/zod4";
|
|
6
|
+
import { GenericActionCtx, GenericDataModel, GenericMutationCtx, GenericQueryCtx } from "convex/server";
|
|
7
|
+
import { Context } from "effect";
|
|
8
|
+
//#region src/modules/effect/auth.d.ts
|
|
9
|
+
type Adapted<Builder, Services, Added> = Builder extends CustomBuilder<infer Kind, infer Args, infer Ctx, infer Made, infer Input, infer Visibility, infer Extra> ? EffectZodApiBuilder<Kind, Args, Ctx, Made, Input, Visibility, Extra, ProvidedServices<Services>, EffectValue<Added>, Added> : never;
|
|
10
|
+
/** Shared post-auth composition; internal/system policies are configured separately. */
|
|
11
|
+
export type EffectAuthFunctions<DataModel extends GenericDataModel, Role extends string = DefaultRole, QueryServices extends EffectApiServices = Context.Context<never>, QueryAdded extends EffectApiContext = {}, MutationServices extends EffectApiServices = Context.Context<never>, MutationAdded extends EffectApiContext = {}, ActionServices extends EffectApiServices = Context.Context<never>, ActionAdded extends EffectApiContext = {}, SystemQueryServices extends EffectApiServices = Context.Context<never>, SystemQueryAdded extends EffectApiContext = {}, SystemMutationServices extends EffectApiServices = Context.Context<never>, SystemMutationAdded extends EffectApiContext = {}, SystemActionServices extends EffectApiServices = Context.Context<never>, SystemActionAdded extends EffectApiContext = {}> = Pick<AuthFunctions<DataModel, Role>, 'include' | 'authenticatedUser'> & {
|
|
12
|
+
authQuery: Adapted<AuthFunctions<DataModel, Role>['authQuery'], QueryServices, QueryAdded>;
|
|
13
|
+
roleQuery: (...allowed: readonly Role[]) => Adapted<AuthFunctions<DataModel, Role>['authQuery'], QueryServices, QueryAdded>;
|
|
14
|
+
adminQuery: Adapted<AuthFunctions<DataModel, Role>['authQuery'], QueryServices, QueryAdded>;
|
|
15
|
+
authMutation: Adapted<AuthFunctions<DataModel, Role>['authMutation'], MutationServices, MutationAdded>;
|
|
16
|
+
roleMutation: (...allowed: readonly Role[]) => Adapted<AuthFunctions<DataModel, Role>['authMutation'], MutationServices, MutationAdded>;
|
|
17
|
+
adminMutation: Adapted<AuthFunctions<DataModel, Role>['authMutation'], MutationServices, MutationAdded>;
|
|
18
|
+
authAction: Adapted<AuthFunctions<DataModel, Role>['authAction'], ActionServices, ActionAdded>;
|
|
19
|
+
roleAction: (...allowed: readonly Role[]) => Adapted<AuthFunctions<DataModel, Role>['authAction'], ActionServices, ActionAdded>;
|
|
20
|
+
adminAction: Adapted<AuthFunctions<DataModel, Role>['authAction'], ActionServices, ActionAdded>;
|
|
21
|
+
systemQuery: Adapted<AuthFunctions<DataModel, Role>['systemQuery'], SystemQueryServices, SystemQueryAdded>;
|
|
22
|
+
systemMutation: Adapted<AuthFunctions<DataModel, Role>['systemMutation'], SystemMutationServices, SystemMutationAdded>;
|
|
23
|
+
systemAction: Adapted<AuthFunctions<DataModel, Role>['systemAction'], SystemActionServices, SystemActionAdded>;
|
|
24
|
+
};
|
|
25
|
+
/** Providers and handles run once per invocation after security and live role checks. */
|
|
26
|
+
export declare function createEffectAuthFunctions<DataModel extends GenericDataModel, const Role extends string = DefaultRole, QueryServices extends EffectApiServices = Context.Context<never>, QueryAdded extends EffectApiContext = {}, MutationServices extends EffectApiServices = Context.Context<never>, MutationAdded extends EffectApiContext = {}, ActionServices extends EffectApiServices = Context.Context<never>, ActionAdded extends EffectApiContext = {}, SystemQueryServices extends EffectApiServices = Context.Context<never>, SystemQueryAdded extends EffectApiContext = {}, SystemMutationServices extends EffectApiServices = Context.Context<never>, SystemMutationAdded extends EffectApiContext = {}, SystemActionServices extends EffectApiServices = Context.Context<never>, SystemActionAdded extends EffectApiContext = {}>(config: AuthFunctionsConfig<DataModel, Role>, options: {
|
|
27
|
+
query: EffectApiOptions<GenericQueryCtx<DataModel> & AuthBundle<Role> & {
|
|
28
|
+
include: Include;
|
|
29
|
+
}, QueryServices, QueryAdded>;
|
|
30
|
+
mutation: EffectApiOptions<GenericMutationCtx<DataModel> & AuthBundle<Role> & {
|
|
31
|
+
include: Include;
|
|
32
|
+
}, MutationServices, MutationAdded>;
|
|
33
|
+
action: EffectApiOptions<GenericActionCtx<DataModel> & AuthBundle<Role>, ActionServices, ActionAdded>;
|
|
34
|
+
systemQuery: EffectApiOptions<GenericQueryCtx<DataModel> & {
|
|
35
|
+
include: Include;
|
|
36
|
+
}, SystemQueryServices, SystemQueryAdded>;
|
|
37
|
+
systemMutation: EffectApiOptions<GenericMutationCtx<DataModel> & {
|
|
38
|
+
include: Include;
|
|
39
|
+
}, SystemMutationServices, SystemMutationAdded>;
|
|
40
|
+
systemAction: EffectApiOptions<GenericActionCtx<DataModel>, SystemActionServices, SystemActionAdded>;
|
|
41
|
+
}): EffectAuthFunctions<DataModel, Role, QueryServices, QueryAdded, MutationServices, MutationAdded, ActionServices, ActionAdded, SystemQueryServices, SystemQueryAdded, SystemMutationServices, SystemMutationAdded, SystemActionServices, SystemActionAdded>;
|
|
42
|
+
//#endregion
|
|
43
|
+
//# sourceMappingURL=auth.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createAuthFunctions as e}from"../../auth.mjs";import{effectZodApiBuilder as t}from"./api-zod.mjs";function n(n,r){let i=e(n);return{include:i.include,authenticatedUser:i.authenticatedUser,authQuery:t(i.authQuery,r.query),roleQuery:(...e)=>t(i.roleQuery(...e),r.query),adminQuery:t(i.adminQuery,r.query),authMutation:t(i.authMutation,r.mutation),roleMutation:(...e)=>t(i.roleMutation(...e),r.mutation),adminMutation:t(i.adminMutation,r.mutation),authAction:t(i.authAction,r.action),roleAction:(...e)=>t(i.roleAction(...e),r.action),adminAction:t(i.adminAction,r.action),systemQuery:t(i.systemQuery,r.systemQuery),systemMutation:t(i.systemMutation,r.systemMutation),systemAction:t(i.systemAction,r.systemAction)}}export{n as createEffectAuthFunctions};
|
|
2
|
+
//# sourceMappingURL=auth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.mjs","names":["createEffectAuthFunctions","auth","createAuthFunctions","config","effectZodApiBuilder","options","allowed"],"sources":["../../../src/modules/effect/auth.ts"],"sourcesContent":["import type { Context } from 'effect'\nimport type {\n\tGenericDataModel,\n\tGenericQueryCtx,\n\tGenericMutationCtx,\n\tGenericActionCtx,\n} from 'convex/server'\nimport type { CustomBuilder } from 'convex-helpers/server/zod4'\nimport {\n\tcreateAuthFunctions,\n\ttype AuthFunctions,\n\ttype AuthFunctionsConfig,\n\ttype AuthBundle,\n\ttype DefaultRole,\n\ttype Include,\n} from '../../auth'\nimport { effectZodApiBuilder, type EffectZodApiBuilder } from './api-zod'\nimport type {\n\tEffectApiOptions,\n\tEffectApiServices,\n\tEffectApiContext,\n\tProvidedServices,\n} from './api-runtime'\nimport type { EffectValue } from './operation'\n\n// Derive the adapter from the symbolic auth contract, never from inferred native spreads.\ntype Adapted<Builder, Services, Added> =\n\tBuilder extends CustomBuilder<\n\t\tinfer Kind,\n\t\tinfer Args,\n\t\tinfer Ctx,\n\t\tinfer Made,\n\t\tinfer Input,\n\t\tinfer Visibility,\n\t\tinfer Extra\n\t>\n\t\t? EffectZodApiBuilder<\n\t\t\t\tKind,\n\t\t\t\tArgs,\n\t\t\t\tCtx,\n\t\t\t\tMade,\n\t\t\t\tInput,\n\t\t\t\tVisibility,\n\t\t\t\tExtra,\n\t\t\t\tProvidedServices<Services>,\n\t\t\t\tEffectValue<Added>,\n\t\t\t\tAdded\n\t\t\t>\n\t\t: never\n\n/** Shared post-auth composition; internal/system policies are configured separately. */\nexport type EffectAuthFunctions<\n\tDataModel extends GenericDataModel,\n\tRole extends string = DefaultRole,\n\tQueryServices extends EffectApiServices = Context.Context<never>,\n\tQueryAdded extends EffectApiContext = {},\n\tMutationServices extends EffectApiServices = Context.Context<never>,\n\tMutationAdded extends EffectApiContext = {},\n\tActionServices extends EffectApiServices = Context.Context<never>,\n\tActionAdded extends EffectApiContext = {},\n\tSystemQueryServices extends EffectApiServices = Context.Context<never>,\n\tSystemQueryAdded extends EffectApiContext = {},\n\tSystemMutationServices extends EffectApiServices = Context.Context<never>,\n\tSystemMutationAdded extends EffectApiContext = {},\n\tSystemActionServices extends EffectApiServices = Context.Context<never>,\n\tSystemActionAdded extends EffectApiContext = {},\n> = Pick<AuthFunctions<DataModel, Role>, 'include' | 'authenticatedUser'> & {\n\tauthQuery: Adapted<AuthFunctions<DataModel, Role>['authQuery'], QueryServices, QueryAdded>\n\troleQuery: (\n\t\t...allowed: readonly Role[]\n\t) => Adapted<AuthFunctions<DataModel, Role>['authQuery'], QueryServices, QueryAdded>\n\tadminQuery: Adapted<AuthFunctions<DataModel, Role>['authQuery'], QueryServices, QueryAdded>\n\tauthMutation: Adapted<\n\t\tAuthFunctions<DataModel, Role>['authMutation'],\n\t\tMutationServices,\n\t\tMutationAdded\n\t>\n\troleMutation: (\n\t\t...allowed: readonly Role[]\n\t) => Adapted<AuthFunctions<DataModel, Role>['authMutation'], MutationServices, MutationAdded>\n\tadminMutation: Adapted<\n\t\tAuthFunctions<DataModel, Role>['authMutation'],\n\t\tMutationServices,\n\t\tMutationAdded\n\t>\n\tauthAction: Adapted<AuthFunctions<DataModel, Role>['authAction'], ActionServices, ActionAdded>\n\troleAction: (\n\t\t...allowed: readonly Role[]\n\t) => Adapted<AuthFunctions<DataModel, Role>['authAction'], ActionServices, ActionAdded>\n\tadminAction: Adapted<AuthFunctions<DataModel, Role>['authAction'], ActionServices, ActionAdded>\n\tsystemQuery: Adapted<\n\t\tAuthFunctions<DataModel, Role>['systemQuery'],\n\t\tSystemQueryServices,\n\t\tSystemQueryAdded\n\t>\n\tsystemMutation: Adapted<\n\t\tAuthFunctions<DataModel, Role>['systemMutation'],\n\t\tSystemMutationServices,\n\t\tSystemMutationAdded\n\t>\n\tsystemAction: Adapted<\n\t\tAuthFunctions<DataModel, Role>['systemAction'],\n\t\tSystemActionServices,\n\t\tSystemActionAdded\n\t>\n}\n\n/** Providers and handles run once per invocation after security and live role checks. */\nexport function createEffectAuthFunctions<\n\tDataModel extends GenericDataModel,\n\tconst Role extends string = DefaultRole,\n\tQueryServices extends EffectApiServices = Context.Context<never>,\n\tQueryAdded extends EffectApiContext = {},\n\tMutationServices extends EffectApiServices = Context.Context<never>,\n\tMutationAdded extends EffectApiContext = {},\n\tActionServices extends EffectApiServices = Context.Context<never>,\n\tActionAdded extends EffectApiContext = {},\n\tSystemQueryServices extends EffectApiServices = Context.Context<never>,\n\tSystemQueryAdded extends EffectApiContext = {},\n\tSystemMutationServices extends EffectApiServices = Context.Context<never>,\n\tSystemMutationAdded extends EffectApiContext = {},\n\tSystemActionServices extends EffectApiServices = Context.Context<never>,\n\tSystemActionAdded extends EffectApiContext = {},\n>(\n\tconfig: AuthFunctionsConfig<DataModel, Role>,\n\toptions: {\n\t\tquery: EffectApiOptions<\n\t\t\tGenericQueryCtx<DataModel> & AuthBundle<Role> & { include: Include },\n\t\t\tQueryServices,\n\t\t\tQueryAdded\n\t\t>\n\t\tmutation: EffectApiOptions<\n\t\t\tGenericMutationCtx<DataModel> & AuthBundle<Role> & { include: Include },\n\t\t\tMutationServices,\n\t\t\tMutationAdded\n\t\t>\n\t\taction: EffectApiOptions<\n\t\t\tGenericActionCtx<DataModel> & AuthBundle<Role>,\n\t\t\tActionServices,\n\t\t\tActionAdded\n\t\t>\n\t\tsystemQuery: EffectApiOptions<\n\t\t\tGenericQueryCtx<DataModel> & { include: Include },\n\t\t\tSystemQueryServices,\n\t\t\tSystemQueryAdded\n\t\t>\n\t\tsystemMutation: EffectApiOptions<\n\t\t\tGenericMutationCtx<DataModel> & { include: Include },\n\t\t\tSystemMutationServices,\n\t\t\tSystemMutationAdded\n\t\t>\n\t\tsystemAction: EffectApiOptions<\n\t\t\tGenericActionCtx<DataModel>,\n\t\t\tSystemActionServices,\n\t\t\tSystemActionAdded\n\t\t>\n\t},\n): EffectAuthFunctions<\n\tDataModel,\n\tRole,\n\tQueryServices,\n\tQueryAdded,\n\tMutationServices,\n\tMutationAdded,\n\tActionServices,\n\tActionAdded,\n\tSystemQueryServices,\n\tSystemQueryAdded,\n\tSystemMutationServices,\n\tSystemMutationAdded,\n\tSystemActionServices,\n\tSystemActionAdded\n> {\n\tconst auth = createAuthFunctions(config)\n\treturn {\n\t\tinclude: auth.include,\n\t\tauthenticatedUser: auth.authenticatedUser,\n\t\tauthQuery: effectZodApiBuilder(auth.authQuery, options.query),\n\t\troleQuery: (...allowed) => effectZodApiBuilder(auth.roleQuery(...allowed), options.query),\n\t\tadminQuery: effectZodApiBuilder(auth.adminQuery, options.query),\n\t\tauthMutation: effectZodApiBuilder(auth.authMutation, options.mutation),\n\t\troleMutation: (...allowed) =>\n\t\t\teffectZodApiBuilder(auth.roleMutation(...allowed), options.mutation),\n\t\tadminMutation: effectZodApiBuilder(auth.adminMutation, options.mutation),\n\t\tauthAction: effectZodApiBuilder(auth.authAction, options.action),\n\t\troleAction: (...allowed) => effectZodApiBuilder(auth.roleAction(...allowed), options.action),\n\t\tadminAction: effectZodApiBuilder(auth.adminAction, options.action),\n\t\tsystemQuery: effectZodApiBuilder(auth.systemQuery, options.systemQuery),\n\t\tsystemMutation: effectZodApiBuilder(auth.systemMutation, options.systemMutation),\n\t\tsystemAction: effectZodApiBuilder(auth.systemAction, options.systemAction),\n\t}\n}\n"],"mappings":"yGA4GA,SAAgBA,EAgBf,EACA,EA+CC,CACD,IAAMC,EAAOC,EAAoBC,CAAM,EACvC,MAAO,CACN,QAASF,EAAK,QACd,kBAAmBA,EAAK,kBACxB,UAAWG,EAAoBH,EAAK,UAAWI,EAAQ,KAAK,EAC5D,WAAY,GAAGC,IAAYF,EAAoBH,EAAK,UAAU,GAAGK,CAAO,EAAGD,EAAQ,KAAK,EACxF,WAAYD,EAAoBH,EAAK,WAAYI,EAAQ,KAAK,EAC9D,aAAcD,EAAoBH,EAAK,aAAcI,EAAQ,QAAQ,EACrE,cAAe,GAAGC,IACjBF,EAAoBH,EAAK,aAAa,GAAGK,CAAO,EAAGD,EAAQ,QAAQ,EACpE,cAAeD,EAAoBH,EAAK,cAAeI,EAAQ,QAAQ,EACvE,WAAYD,EAAoBH,EAAK,WAAYI,EAAQ,MAAM,EAC/D,YAAa,GAAGC,IAAYF,EAAoBH,EAAK,WAAW,GAAGK,CAAO,EAAGD,EAAQ,MAAM,EAC3F,YAAaD,EAAoBH,EAAK,YAAaI,EAAQ,MAAM,EACjE,YAAaD,EAAoBH,EAAK,YAAaI,EAAQ,WAAW,EACtE,eAAgBD,EAAoBH,EAAK,eAAgBI,EAAQ,cAAc,EAC/E,aAAcD,EAAoBH,EAAK,aAAcI,EAAQ,YAAY,CAC1E,CACD"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { OperationExecutor, OperationPublicResult, OperationTool, OperationToolConverter, OperationToolDefinition, OperationToolJsonSchema, OperationToolOutcome, SelectedOperation, bindOperationExecutor, createOperationTools, operationToolDialect, selectOperation } from "../contracts/exposure.mjs";
|
|
2
2
|
import { ContractDecoderError, ContractDecoderRequirements, ContractValidationError, EffectDecoder, decodeEffectContract, effectContract, effectSchema, effectStandardSchema } from "./schema.mjs";
|
|
3
3
|
import { CallbackError, CallbackRequirements, EffectMiddlewareInput, EffectOperationArgument, EffectOperationError, EffectOperationHandlerResult, EffectOperationInput, EffectOperationRequirements, EffectOperationResult, EffectPreparation, EffectValue, effectOperationFactory } from "./operation.mjs";
|
|
4
|
+
import { EffectZodApiBuilder, effectZodApiBuilder } from "./api-zod.mjs";
|
|
4
5
|
import { EffectFoundationOptions, createEffectFoundation } from "./foundation.mjs";
|
|
5
6
|
import { EffectIdempotencyPreparation, effectTransactionalIdempotency } from "./idempotency.mjs";
|
|
6
7
|
import { EffectCrudConfig, EffectCrudContext, EffectCrudPage, EffectCrudPagination, EffectCrudRepository, EffectCrudTable, createEffectCrud } from "./crud.mjs";
|
|
7
8
|
import { EffectApiBuilder, effectApiBuilder } from "./api.mjs";
|
|
8
|
-
import { EffectZodApiBuilder, effectZodApiBuilder } from "./api-zod.mjs";
|
|
9
9
|
import { projectErrorCause } from "./errors.mjs";
|
|
10
10
|
import { EffectReceiptProtectedWorkflowMutation, effectReceiptWorkflowMutation, effectTerminalWorkflowAttempt, effectWorkflowAttempt, effectWorkflowStatusBindings } from "./workflow.mjs";
|
|
11
11
|
export { type CallbackError, type CallbackRequirements, type ContractDecoderError, type ContractDecoderRequirements, ContractValidationError, type EffectApiBuilder, type EffectCrudConfig, type EffectCrudContext, type EffectCrudPage, type EffectCrudPagination, type EffectCrudRepository, type EffectCrudTable, type EffectDecoder, type EffectFoundationOptions, type EffectIdempotencyPreparation, type EffectMiddlewareInput, type EffectOperationArgument, type EffectOperationError, type EffectOperationHandlerResult, type EffectOperationInput, type EffectOperationRequirements, type EffectOperationResult, type EffectPreparation, type EffectReceiptProtectedWorkflowMutation, type EffectValue, type EffectZodApiBuilder, type OperationExecutor, type OperationPublicResult, type OperationTool, type OperationToolConverter, type OperationToolDefinition, type OperationToolJsonSchema, type OperationToolOutcome, type SelectedOperation, bindOperationExecutor, createEffectCrud, createEffectFoundation, createOperationTools, decodeEffectContract, effectApiBuilder, effectContract, effectOperationFactory, effectReceiptWorkflowMutation, effectSchema, effectStandardSchema, effectTerminalWorkflowAttempt, effectTransactionalIdempotency, effectWorkflowAttempt, effectWorkflowStatusBindings, effectZodApiBuilder, operationToolDialect, projectErrorCause, selectOperation };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{bindOperationExecutor as e,createOperationTools as t,operationToolDialect as n,selectOperation as r}from"../contracts/exposure.mjs";import{
|
|
1
|
+
import{bindOperationExecutor as e,createOperationTools as t,operationToolDialect as n,selectOperation as r}from"../contracts/exposure.mjs";import{projectErrorCause as i}from"./errors.mjs";import{effectZodApiBuilder as a}from"./api-zod.mjs";import{effectOperationFactory as o}from"./operation.mjs";import{ContractValidationError as s,decodeEffectContract as c,effectContract as l,effectSchema as u,effectStandardSchema as d}from"./schema.mjs";import{createEffectFoundation as f}from"./foundation.mjs";import{effectTransactionalIdempotency as p}from"./idempotency.mjs";import{createEffectCrud as m}from"./crud.mjs";import{effectApiBuilder as h}from"./api.mjs";import{effectReceiptWorkflowMutation as g,effectTerminalWorkflowAttempt as _,effectWorkflowAttempt as v,effectWorkflowStatusBindings as y}from"./workflow.mjs";export{s as ContractValidationError,e as bindOperationExecutor,m as createEffectCrud,f as createEffectFoundation,t as createOperationTools,c as decodeEffectContract,h as effectApiBuilder,l as effectContract,o as effectOperationFactory,g as effectReceiptWorkflowMutation,u as effectSchema,d as effectStandardSchema,_ as effectTerminalWorkflowAttempt,p as effectTransactionalIdempotency,v as effectWorkflowAttempt,y as effectWorkflowStatusBindings,a as effectZodApiBuilder,n as operationToolDialect,i as projectErrorCause,r as selectOperation};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{selectOperation as e}from"../contracts/exposure.mjs";import{
|
|
1
|
+
import{selectOperation as e}from"../contracts/exposure.mjs";import{normalizeEffect as t,observationFailure as n}from"./normalize.mjs";import{effectOperationFactory as r}from"./operation.mjs";import{decodeEffectContract as i}from"./schema.mjs";import{CommandPermissionError as a,executeCommandLifecycle as o}from"../../components/foundation/modules/command/lifecycle.mjs";import{Effect as s,Exit as c}from"effect";const l={succeed:s.succeed,suspend:s.suspend,flatMap:s.flatMap};function u(u){let d=u.operations(r()),p=(e,r,p)=>s.suspend(()=>{if(!Object.prototype.hasOwnProperty.call(d,e))throw Error(`The selected command operation is not configured`);let m=d[e];return s.flatMap(i(m.input,r),r=>{let d=r,h=u.observability.start({operation:e,classification:m.classification}),g=u.context;return t(()=>g(p)).pipe(s.flatMap(n=>o(l,{operation:e,classification:m.classification,context:n,command:d,permission:m.permission===void 0?void 0:()=>t(()=>{if(!u.checkPermission)throw new a(e);return u.checkPermission(p,{permission:m.permission,operation:e})}).pipe(s.asVoid),prepare:()=>t(()=>m.prepare?.(n,d)).pipe(s.map(e=>e?.kind===`execute`?{kind:`execute`,complete:e.complete?n=>t(()=>e.complete(n)).pipe(s.asVoid):void 0}:e)),decodeReplay:e=>i(m.replayResult??m.result,e),middleware:m.middleware?[e=>f(()=>m.middleware({...e,input:d}))]:[],defaultGuard:e=>t(()=>u.defaults?.guard?.(e)).pipe(s.asVoid),guard:e=>t(()=>m.guard?.(e,d)).pipe(s.asVoid),run:e=>f(()=>m.handler(d,e)),decodeResult:e=>i(m.result,e),audit:e=>t(()=>m.audit({command:d,result:e},n)),writeAudit:e=>t(()=>u.writeAudit(p,e)),aggregates:m.aggregates})),s.onExit(e=>s.sync(()=>{c.isSuccess(e)?h.completed():h.failed(n(e.cause))})))})});return{withContext(e){return{exec:(t,n)=>p(t,n,e)}},expose(t,n){if(!Object.hasOwn(d,n))throw Error(`The selected command operation is not configured`);return e(t,`mutation`,n,d[n])},exec:p}}function d(e){return function(t){return u({...t,...e})}}function f(e){return t(e)}export{d as bindEffectCommand,u as createEffectCommand};
|
|
2
2
|
//# sourceMappingURL=command.mjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{defaultErrors as e}from"../../errors.mjs";import{
|
|
1
|
+
import{defaultErrors as e}from"../../errors.mjs";import{normalizeEffect as t}from"./normalize.mjs";import{effectOperationFactory as n}from"./operation.mjs";import{paginated as r,zid as i}from"../../zod-table.mjs";import{z as a}from"zod";import{Effect as o}from"effect";function s(s){let{table:p}=s;if(`tenant`in p.storage.shape&&!s.enrich)return e.throw({code:`CRUD_ENRICH_REQUIRED`,message:`Table "${p.tableName}" requires an enrich callback`});if(!Number.isSafeInteger(s.read.maxPageSize)||s.read.maxPageSize<1)throw Error(`CRUD maxPageSize must be a positive safe integer`);let m=s.read.maximumRowsRead??s.read.maxPageSize;if(!Number.isSafeInteger(m)||m<1)throw Error(`CRUD maximumRowsRead must be a positive safe integer`);let h=p.tableName,g=i(h),_=d(p.commandInput),v=d(p.tools.update),y=d(p.tools.id),b=p.publicDto,x=d(a.object({id:g}).strict()),S=a.object({ok:a.literal(!0)}).strict(),C=a.object({numItems:a.number().int().min(1).max(s.read.maxPageSize),cursor:a.string().nullable(),endCursor:a.string().nullable().optional(),id:a.number().optional(),maximumRowsRead:a.number().int().positive().max(2**53-1).optional(),maximumBytesRead:a.number().int().positive().max(2**53-1).optional()}).strict(),w=r(b).result,T=s.classification??`business`,E=e=>({type:s.aggregateType,id:e}),D=`${h}.create`,O=`${h}.update`,k=`${h}.archive`,A=`${h}.get`,j=`${h}.list`;function M(t,n){return u(()=>s.checkId?s.checkId(t,n):t.db.normalizeId(p.tableName,n)!==null).pipe(o.map(t=>t===!0?n:e.throw({code:`CRUD_INVALID_ID`,message:`Invalid ID for table "${p.tableName}"`})))}function N(e,n){if(s.repository)return t(()=>s.repository.insert(e,n));let r=e;return t(()=>r.db.insert(p.tableName,n))}function P(e,n,r){if(s.repository)return t(()=>s.repository.patch(e,n,r));let i=e;return t(()=>i.db.patch(n,r))}let F=l(s.overrides?.create,(e,t)=>o.gen(function*(){let n=yield*u(()=>s.enrich?.(t,e));return{id:yield*N(t,Object.fromEntries(Object.entries({...e,...n}).filter(([,e])=>e!==void 0)))}})),I=l(s.overrides?.update,(e,t)=>P(t,e.id,e.data).pipe(o.as({ok:!0}))),L=(e,n)=>M(n,e.id).pipe(o.flatMap(()=>t(()=>I(e,n)))),R=l(s.overrides?.archive,(e,t)=>P(t,e.id,f(Date.now())).pipe(o.as({ok:!0}))),z=(e,n)=>M(n,e.id).pipe(o.flatMap(()=>t(()=>R(e,n)))),B=n(),V=Object.assign({},c(D,B.command({input:_,result:x,classification:T,aggregates:[s.aggregateType],permission:s.policy?.permissions?.create,guard:s.guards?.create,handler:F,audit:({result:e},t)=>u(()=>s.actor(t)).pipe(o.map(t=>({operation:D,actorId:t,aggregate:E(e.id)})))})),c(O,B.command({input:v,result:S,classification:T,aggregates:[s.aggregateType],permission:s.policy?.permissions?.update,guard:s.guards?.update,handler:L,audit:({command:e},t)=>u(()=>s.actor(t)).pipe(o.map(t=>({operation:O,actorId:t,aggregate:E(e.id)})))})),c(k,B.command({input:y,result:S,classification:T,aggregates:[s.aggregateType],permission:s.policy?.permissions?.archive,guard:s.guards?.archive,handler:z,audit:({command:e},t)=>u(()=>s.actor(t)).pipe(o.map(t=>({operation:k,actorId:t,aggregate:E(e.id)})))}))),H={context:s.context,defaults:{guard:s.policy?.guard},operations:()=>V},U=s.foundation.Command(H),W=d(b.nullable()),G=Object.assign({},c(A,B.query({input:y,result:W,permission:s.policy?.permissions?.get,guard:s.guards?.get,handler:(e,t)=>M(t,e.id).pipe(o.flatMap(e=>u(()=>s.read.get(t,e))),o.flatMap(e=>e===null?o.succeed(null):u(()=>s.read.project(e))))})),c(j,B.query({input:C,result:w,permission:s.policy?.permissions?.list,guard:s.guards?.list,handler:(e,t)=>u(()=>s.read.list(t,{...e,maximumRowsRead:Math.min(e.maximumRowsRead??m,m)})).pipe(o.flatMap(e=>o.forEach(e.page,e=>u(()=>s.read.project(e))).pipe(o.map(t=>({...e,page:t})))))}))),K={context:s.context,defaults:{guard:s.policy?.guard},operations:()=>G};return{commands:U,queries:s.foundation.Query(K),operations:V,queryOperations:G}}function c(e,t){return{[e]:t}}function l(e,t){return e??t}function u(e){return t(e)}function d(e){return{"~standard":e[`~standard`],parse:t=>e.parse(t),parseAsync:t=>e.parseAsync(t)}}function f(e){return Object.fromEntries([[`archivedAt`,e]])}export{s as createEffectCrud};
|
|
2
2
|
//# sourceMappingURL=crud.mjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{selectOperation as e}from"../contracts/exposure.mjs";import{
|
|
1
|
+
import{selectOperation as e}from"../contracts/exposure.mjs";import{normalizeEffect as t,observationFailure as n}from"./normalize.mjs";import{effectOperationFactory as r}from"./operation.mjs";import{Observability as i}from"../../components/foundation/modules/observability/observability.mjs";import{decodeEffectContract as a}from"./schema.mjs";import{Effect as o,Exit as s}from"effect";var c=class extends Error{code=`QUERY_MIDDLEWARE_NEXT_REUSED`;name=`QueryMiddlewareError`;constructor(){super(`A query middleware called next() more than once`)}},l=class extends Error{code=`QUERY_OPERATION_NOT_CONFIGURED`;name=`QueryConfigurationError`;constructor(){super(`The selected query operation is not configured`)}},u=class extends Error{code=`QUERY_PERMISSION_CHECKER_MISSING`;name=`QueryPermissionError`;constructor(){super(`Query permission requires an injected permission checker`)}};function d(d){let f=r(),p=d.operations(f),m=d.observability instanceof i?d.observability:new i(d.observability??{enabled:!1,classifyError:()=>({outcome:`failed`,errorCode:`UNEXPECTED`})});function h(e,r,i){return o.suspend(()=>{if(!Object.prototype.hasOwnProperty.call(p,e))return o.die(new l);let f=p[e];return o.flatMap(a(f.input,r),r=>{let l=m.start({operation:e,classification:f.classification??`query`});return o.gen(function*(){if(f.permission!==void 0){let n=d.checkPermission;if(!n)return yield*o.die(new u);yield*t(()=>n(i,{permission:f.permission,operation:e}))}let n=yield*t(()=>d.context(i)),s={...d.defaults?.metadata,...f.metadata},l=e=>o.gen(function*(){let n=d.defaults?.guard;return n&&(yield*t(()=>n(e))),f.guard&&(yield*t(()=>f.guard(e,r))),yield*t(()=>f.handler(r,e))}),p=!1,m=f.middleware,h=m?yield*t(()=>m({operation:e,input:r,command:r,context:n,metadata:s,next:e=>o.suspend(()=>p?o.die(new c):(p=!0,l(e?.context?Object.assign({},n,e.context):n)))})):yield*l(n);return yield*a(f.result,h)}).pipe(o.onExit(e=>o.sync(()=>{s.isSuccess(e)?l.completed():l.failed(n(e.cause))})))})})}function g(t,n){if(!Object.hasOwn(p,n))throw new l;return e(t,`query`,n,p[n])}return{exec:h,expose:g,withContext(e){return{exec:(t,n)=>h(t,n,e)}}}}function f(e){return function(t){let n=e.checkPermission;return d({...t,observability:e.observability,checkPermission:e.checkPermission?n:void 0})}}export{f as bindEffectQuery,d as createEffectQuery};
|
|
2
2
|
//# sourceMappingURL=query.mjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{normalizeEffect as e}from"./normalize.mjs";import{decodeEffectContract as t}from"./schema.mjs";import{WorkflowBindingError as n,workflowCancel as r,workflowExecution as i,workflowRestart as a,workflowTransition as o}from"../contracts/workflow.mjs";import{Effect as s}from"effect";function c(t){return n=>s.gen(function*(){let r=yield*f(n),i=yield*e(()=>t.authorize(r));return yield*e(()=>t.execute(r,i))})}function l(n){return r=>s.gen(function*(){let i=yield*f(r),a=yield*e(()=>n.authorize(i)),o=yield*s.tryPromise({try:()=>n.invokeMutation(i,a),catch:e=>e}).pipe(s.map(e=>({kind:`success`,value:e})),s.catch(e=>s.succeed({kind:`rejected`,error:e})));if(o.kind===`rejected`){let r=yield*e(()=>n.terminal(o.error));if(r===void 0)return yield*s.die(o.error);let a=yield*t(n.failure,r);return yield*f({...i,args:a}),{version:1,kind:`failed`,error:a}}return yield*t(n.result,o.value),yield*f({...i,args:o.value}),{version:1,kind:`succeeded`,value:o.value}})}function u(n){return{handler:r=>s.gen(function*(){let i=yield*f(r),a=yield*e(()=>n.authorize(i)),o=n.receipt(a),s=yield*e(()=>o.prepare(n.invocation(i,a)));if(s.kind===`replay`)return yield*t(o.replayResult,s.result);let c=yield*e(()=>n.execute(i,a)),l=s.complete;return yield*e(()=>l(c)),c})}}function d(t){function n(){return e(()=>t.read()).pipe(s.map(e=>e))}function i(r,i,a){return s.gen(function*(){let s=yield*n(),c={generation:r,nativeStatus:i},l=yield*p(()=>o(s,a===void 0?c:{...c,outcome:a}));return l.kind===`applied`&&(yield*e(()=>t.write(r,l.state))),l})}return{callback(n,r,a){return e(()=>t.authorize()).pipe(s.flatMap(()=>i(n,r,a)))},reconcile(n){return s.gen(function*(){return yield*e(()=>t.authorize()),yield*i(n,yield*e(()=>t.nativeStatus()),yield*e(()=>t.receiptOutcome()))})},cancel(i){return s.gen(function*(){yield*e(()=>t.authorize());let a=yield*n(),o=yield*p(()=>r(a,i));return o.kind===`applied`&&a.status===`running`&&(yield*e(()=>t.cancelNative()),yield*e(()=>t.write(i,o.state))),o})},restart(r){return s.gen(function*(){yield*e(()=>t.authorize());let i=yield*n();if(i.generation!==r)return{kind:`ignored`,state:i};let o=yield*p(()=>a(i));return yield*e(()=>t.restartNative()),yield*e(()=>t.write(r,o)),{kind:`applied`,state:o}})}}}function f(e){return p(()=>i(e))}function p(e){return s.try({try:e,catch:e=>{if(e instanceof n)return e;throw e}})}export{u as effectReceiptWorkflowMutation,l as effectTerminalWorkflowAttempt,c as effectWorkflowAttempt,d as effectWorkflowStatusBindings};
|
|
2
2
|
//# sourceMappingURL=workflow.mjs.map
|
package/docs/auth.md
CHANGED
|
@@ -64,9 +64,139 @@ export const {
|
|
|
64
64
|
Every function in the app is then built from these — raw `query`/`mutation`
|
|
65
65
|
imports from `_generated/server` appear **only** in this file.
|
|
66
66
|
|
|
67
|
+
## One shared Effect function foundation
|
|
68
|
+
|
|
69
|
+
Use `createEffectAuthFunctions` from `cvx-kit/effect` in `convex/functions.ts`
|
|
70
|
+
to configure all twelve constructors once. Its first argument is the same
|
|
71
|
+
`AuthFunctionsConfig` used by `createAuthFunctions`; its second argument has
|
|
72
|
+
six explicit policies: `query`, `mutation`, `action`, `systemQuery`,
|
|
73
|
+
`systemMutation`, and `systemAction`. Each accepts `services`, `context`, and
|
|
74
|
+
`mapError`, like `effectZodApiBuilder`. Pass `{}` when no additions are needed.
|
|
75
|
+
|
|
76
|
+
Type the auth config with your generated `DataModel` and role vocabulary,
|
|
77
|
+
then let the factory infer its generics from both arguments. Do not supply
|
|
78
|
+
partial generic arguments, which would fix the remaining inference defaults.
|
|
79
|
+
The following public-package example defines a reusable app factory; the
|
|
80
|
+
host passes its auth, membership, trigger, and RLS policy as `config`.
|
|
81
|
+
|
|
82
|
+
<!-- packed-effect-example -->
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import {
|
|
86
|
+
defineSchema,
|
|
87
|
+
defineTable,
|
|
88
|
+
makeFunctionReference,
|
|
89
|
+
type DataModelFromSchemaDefinition,
|
|
90
|
+
type GenericQueryCtx,
|
|
91
|
+
type GenericMutationCtx,
|
|
92
|
+
} from 'convex/server'
|
|
93
|
+
import { v } from 'convex/values'
|
|
94
|
+
import { Effect } from 'effect'
|
|
95
|
+
import { z } from 'zod'
|
|
96
|
+
import type { AuthFunctionsConfig, AuthBundle } from 'cvx-kit/auth'
|
|
97
|
+
import { createEffectAuthFunctions, createEffectFoundation } from 'cvx-kit/effect'
|
|
98
|
+
|
|
99
|
+
const schema = defineSchema({ notes: defineTable({ tenant: v.string(), title: v.string() }) })
|
|
100
|
+
type Model = DataModelFromSchemaDefinition<typeof schema>
|
|
101
|
+
type Role = 'owner' | 'viewer'
|
|
102
|
+
const input = z.object({ title: z.string().trim().min(1) })
|
|
103
|
+
const saveRef = makeFunctionReference<'mutation', z.input<typeof input>, null>('notes:save')
|
|
104
|
+
|
|
105
|
+
export function createApplicationFunctions(config: AuthFunctionsConfig<Model, Role>) {
|
|
106
|
+
const foundation = createEffectFoundation({
|
|
107
|
+
observability: {
|
|
108
|
+
enabled: false,
|
|
109
|
+
classifyError: () => ({ outcome: 'failed', errorCode: 'FAILED' }),
|
|
110
|
+
},
|
|
111
|
+
writeAudit: () => undefined, // supply the app's transactional audit writer in production
|
|
112
|
+
})
|
|
113
|
+
const queries = foundation.Query({
|
|
114
|
+
context: (ctx: (GenericQueryCtx<Model> | GenericMutationCtx<Model>) & AuthBundle<Role>) => ctx,
|
|
115
|
+
operations: ({ query }) => ({
|
|
116
|
+
actor: query({
|
|
117
|
+
input: z.object({}),
|
|
118
|
+
result: z.string(),
|
|
119
|
+
handler: (_input, ctx) => ctx.actor.userId,
|
|
120
|
+
}),
|
|
121
|
+
}),
|
|
122
|
+
})
|
|
123
|
+
const commands = foundation.Command({
|
|
124
|
+
context: (ctx: GenericMutationCtx<Model> & AuthBundle<Role>) => ctx,
|
|
125
|
+
operations: ({ command }) => ({
|
|
126
|
+
save: command({
|
|
127
|
+
input,
|
|
128
|
+
result: z.null(),
|
|
129
|
+
classification: 'business',
|
|
130
|
+
audit: () => null,
|
|
131
|
+
handler: (args, ctx) =>
|
|
132
|
+
Effect.promise(async () => {
|
|
133
|
+
await ctx.db.insert('notes', { title: args.title, tenant: ctx.tenant })
|
|
134
|
+
return null
|
|
135
|
+
}),
|
|
136
|
+
}),
|
|
137
|
+
}),
|
|
138
|
+
})
|
|
139
|
+
const functions = createEffectAuthFunctions(config, {
|
|
140
|
+
query: { context: (ctx) => ({ queries: queries.withContext(ctx) }) },
|
|
141
|
+
mutation: {
|
|
142
|
+
context: (ctx) => ({
|
|
143
|
+
queries: queries.withContext(ctx),
|
|
144
|
+
commands: commands.withContext(ctx),
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
action: {
|
|
148
|
+
context: (ctx) => ({
|
|
149
|
+
commands: {
|
|
150
|
+
save: (args: z.input<typeof input>) =>
|
|
151
|
+
Effect.promise(() => ctx.runMutation(saveRef, args)),
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
154
|
+
},
|
|
155
|
+
systemQuery: {},
|
|
156
|
+
systemMutation: {},
|
|
157
|
+
systemAction: {},
|
|
158
|
+
})
|
|
159
|
+
const save = functions.roleMutation('owner')({
|
|
160
|
+
args: input.shape,
|
|
161
|
+
returns: z.null(),
|
|
162
|
+
handler: (ctx, args) => ctx.commands.exec('save', args),
|
|
163
|
+
})
|
|
164
|
+
return { ...functions, save }
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Export the returned constructors once from `convex/functions.ts`, and export
|
|
169
|
+
`save` at the registered `notes:save` mutation path. Endpoint modules can then
|
|
170
|
+
use `ctx.commands` or `ctx.queries` directly. The callbacks run per invocation
|
|
171
|
+
after auth, role checks, triggers/RLS wrapping, and live action membership
|
|
172
|
+
refresh. Binding is lazy; do not cache an authenticated `withContext` handle
|
|
173
|
+
at module scope. Denied requests never run the callbacks.
|
|
174
|
+
|
|
175
|
+
| Policy | Available authority and capabilities |
|
|
176
|
+
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| `query` | Trusted auth bundle, secured DB reader, `include`; bind read registries only. |
|
|
178
|
+
| `mutation` | Trusted auth bundle, secured triggered DB writer, `include`; bind queries and mutation commands. |
|
|
179
|
+
| `action` | Live-verified auth bundle, `runQuery`/`runMutation`; use action-compatible registries or forward to registered functions. No DB or `include`. |
|
|
180
|
+
| `systemQuery` / `systemMutation` / `systemAction` | Existing internal authority, configured separately. No fabricated actor or inherited public handles; system mutations retain triggers and bypass public RLS as before. |
|
|
181
|
+
|
|
182
|
+
Every auth/role/admin constructor of a kind shares that kind's policy. System
|
|
183
|
+
policies are deliberately explicit; choose their trusted capabilities rather
|
|
184
|
+
than synthesizing public credentials. An action forwarding to `runMutation`
|
|
185
|
+
creates a separate registered mutation transaction; it does not turn the
|
|
186
|
+
whole action into a transaction or carry its live role into that mutation's
|
|
187
|
+
JWT-based auth policy.
|
|
188
|
+
|
|
189
|
+
Additions cannot replace native or auth context keys: static checks reject
|
|
190
|
+
known collisions and runtime checks also reject inherited keys before the
|
|
191
|
+
handler executes. Put class-based handles under named properties in the
|
|
192
|
+
returned plain record. Scoped services, required Effect services, Zod raw and
|
|
193
|
+
parsed argument/result types, function references, and error projection retain
|
|
194
|
+
the existing adapter's behavior. Let failures escape registered mutations so
|
|
195
|
+
nested command writes, triggers, and audit writes roll back together.
|
|
196
|
+
|
|
67
197
|
## Shared Effect execution after authentication
|
|
68
198
|
|
|
69
|
-
|
|
199
|
+
For an individual constructor, wrap the builder returned by `createAuthFunctions` with
|
|
70
200
|
`effectZodApiBuilder` from `cvx-kit/effect`. The original constructor resolves
|
|
71
201
|
authentication, authorization, tenant policy, and the wrapped database before
|
|
72
202
|
the service provider runs. Keep that ordering when adding Effect; use the Zod
|
package/docs/effect.md
CHANGED
|
@@ -384,11 +384,17 @@ only when you want a typed error channel.
|
|
|
384
384
|
The [command guide](./commands.md) documents the current lifecycle and migration
|
|
385
385
|
steps; the [deprecated API reference](./commands-legacy.md) retains old examples.
|
|
386
386
|
The [authentication guide](./auth.md)
|
|
387
|
-
shows
|
|
387
|
+
shows the shared `createEffectAuthFunctions(config, policies)` factory and how
|
|
388
|
+
to wrap individual trusted builders with `effectZodApiBuilder`, so auth,
|
|
388
389
|
tenancy, validators, row-level policies, and triggers run through their
|
|
389
390
|
existing builder before Effect request services are provisioned.
|
|
390
391
|
|
|
391
|
-
|
|
392
|
+
For an app-wide foundation, configure `query`, `mutation`, and `action` policies
|
|
393
|
+
once with `createEffectAuthFunctions` and explicit separate system policies.
|
|
394
|
+
Query handles bind readers, mutation handles bind the secured triggered writer,
|
|
395
|
+
and action handles use `runMutation` rather than a mutation database.
|
|
396
|
+
|
|
397
|
+
Optional request context can also be added through the existing Effect API adapter's
|
|
392
398
|
`context` callback. Bind selected registries with `commands.withContext(value)`
|
|
393
399
|
and consume `ctx.commands.exec(name, input)` through the application's existing
|
|
394
400
|
function helper names. Keep dependencies local to each feature; explicit execution
|
package/docs/llms.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
cvx-kit is a reusable Convex application kit: zod table boundaries, auth-aware
|
|
23
23
|
function constructors, a trigger registry, an audited command protocol
|
|
24
24
|
(Foundation component), and a declarative approvals component. Peer deps:
|
|
25
|
-
`convex ^1.
|
|
25
|
+
`convex ^1.46.0`, `zod ^4.5.4`; command implementations also install
|
|
26
26
|
`effect >=4.0.1 <5` and import `cvx-kit/effect`.
|
|
27
27
|
|
|
28
28
|
**Command API selection:** use `createEffectFoundation` and declare handlers
|
|
@@ -38,12 +38,13 @@ copy that API into new implementations.
|
|
|
38
38
|
`convex/functions.ts`** (the `createAuthFunctions` call). All other
|
|
39
39
|
functions use `authQuery/authMutation/authAction`, `roleQuery/...`,
|
|
40
40
|
`adminQuery/...` (public) or `systemQuery/systemMutation/systemAction` (internal),
|
|
41
|
-
|
|
41
|
+
built once with `createEffectAuthFunctions(config, policies)` from `cvx-kit/effect`
|
|
42
|
+
for shared Effect handles, or individually wrapped with `effectZodApiBuilder`.
|
|
42
43
|
This is what guarantees auth, triggers, and bounded reads.
|
|
43
44
|
3. **Every public query returns DTOs** via `<table>.toPublicDto(row)` —
|
|
44
45
|
runtime redaction, not just types.
|
|
45
46
|
4. **Every read is bounded**: `ctx.include(ctx.db.query('t')).matching(...)
|
|
46
|
-
.execute(limit)` with `1 ≤ limit ≤ 100`. `.resolve()` falls back to a full
|
|
47
|
+
.execute(limit)` with `1 ≤ limit ≤ 100`. `.resolve()` falls back to a full
|
|
47
48
|
table scan — avoid it.
|
|
48
49
|
5. **State changes are Effect commands**: declare
|
|
49
50
|
`Command({ context, operations: ({ command }) => ({ ... }), defaults })`.
|
|
@@ -78,23 +79,23 @@ copy that API into new implementations.
|
|
|
78
79
|
|
|
79
80
|
## Exports map
|
|
80
81
|
|
|
81
|
-
| Import | Provides
|
|
82
|
-
| ------------------------------- |
|
|
83
|
-
| `cvx-kit` | Non-Effect helpers and component clients; import the optional Effect facade explicitly from `cvx-kit/effect`.
|
|
84
|
-
| `cvx-kit/zod-table` | `zodTable`, `tenantTable`, `createModule`, `paginated`, `zodVariantTable`, `jsonSafeZid`, `TIMESTAMP_FIELDS`
|
|
85
|
-
| `cvx-kit/effect` | Recommended command/query API: `createEffectFoundation`, `effectApiBuilder`, `effectZodApiBuilder`, `createEffectCrud`, idempotency/workflow adapters and operation tools. |
|
|
86
|
-
| `cvx-kit/auth` | `createAuthFunctions` (incl. optional `security` RLS config), `createInclude`, `defaultRoleMap`
|
|
87
|
-
| `cvx-kit/tenancy` | `createTenantRules`, `composeRules`, `requireTenantReference`, `assertTenantOwned`, `TENANT_FIELD`
|
|
88
|
-
| `cvx-kit/crud` | Compatibility CRUD factory using the deprecated Promise command facade; use `createEffectCrud` from `cvx-kit/effect` for new domains.
|
|
89
|
-
| `cvx-kit/state-machine` | `createStateMachine` — typed transitions from constants tuples; `assert` drops into command guards
|
|
90
|
-
| `cvx-kit/middleware` | `rateLimit` — packaged middleware over an injected rate-limiter instance; keyed by `ctx.tenant`, missing key = config error
|
|
91
|
-
| `cvx-kit/webhooks` | `createWebhookBoundary`, `recordWebhookEvent`, `webhookEventsTable` — raw-body verify, natural-key dedup in the mutation
|
|
92
|
-
| `cvx-kit/agent-tools` | `createAgentTools` — tool records from table masks; mutation handlers route through command executors
|
|
93
|
-
| `cvx-kit/triggers` | `createTriggers`, `timestamps`, `appendOnly`, `noDelete`, `tenantOwnership`, `Triggers`
|
|
94
|
-
| `cvx-kit/errors` | `KitError`, `defaultErrors`, `ErrorFactory`
|
|
95
|
-
| `cvx-kit/components/foundation` | Component client and types; `Foundation.Command` is deprecated. The component and other capabilities remain available. New command registries use `cvx-kit/effect`.
|
|
96
|
-
| `cvx-kit/components/approvals` | `Approvals` client; default export = component config for `app.use`
|
|
97
|
-
| `cvx-kit/test` | `registerFoundation(t)`, `registerApprovals(t)` for convex-test
|
|
82
|
+
| Import | Provides |
|
|
83
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `cvx-kit` | Non-Effect helpers and component clients; import the optional Effect facade explicitly from `cvx-kit/effect`. |
|
|
85
|
+
| `cvx-kit/zod-table` | `zodTable`, `tenantTable`, `createModule`, `paginated`, `zodVariantTable`, `jsonSafeZid`, `TIMESTAMP_FIELDS` |
|
|
86
|
+
| `cvx-kit/effect` | Recommended command/query API: `createEffectFoundation`, `createEffectAuthFunctions`, `effectApiBuilder`, `effectZodApiBuilder`, `createEffectCrud`, idempotency/workflow adapters and operation tools. |
|
|
87
|
+
| `cvx-kit/auth` | `createAuthFunctions` (incl. optional `security` RLS config), `createInclude`, `defaultRoleMap` |
|
|
88
|
+
| `cvx-kit/tenancy` | `createTenantRules`, `composeRules`, `requireTenantReference`, `assertTenantOwned`, `TENANT_FIELD` |
|
|
89
|
+
| `cvx-kit/crud` | Compatibility CRUD factory using the deprecated Promise command facade; use `createEffectCrud` from `cvx-kit/effect` for new domains. |
|
|
90
|
+
| `cvx-kit/state-machine` | `createStateMachine` — typed transitions from constants tuples; `assert` drops into command guards |
|
|
91
|
+
| `cvx-kit/middleware` | `rateLimit` — packaged middleware over an injected rate-limiter instance; keyed by `ctx.tenant`, missing key = config error |
|
|
92
|
+
| `cvx-kit/webhooks` | `createWebhookBoundary`, `recordWebhookEvent`, `webhookEventsTable` — raw-body verify, natural-key dedup in the mutation |
|
|
93
|
+
| `cvx-kit/agent-tools` | `createAgentTools` — tool records from table masks; mutation handlers route through command executors |
|
|
94
|
+
| `cvx-kit/triggers` | `createTriggers`, `timestamps`, `appendOnly`, `noDelete`, `tenantOwnership`, `Triggers` |
|
|
95
|
+
| `cvx-kit/errors` | `KitError`, `defaultErrors`, `ErrorFactory` |
|
|
96
|
+
| `cvx-kit/components/foundation` | Component client and types; `Foundation.Command` is deprecated. The component and other capabilities remain available. New command registries use `cvx-kit/effect`. |
|
|
97
|
+
| `cvx-kit/components/approvals` | `Approvals` client; default export = component config for `app.use` |
|
|
98
|
+
| `cvx-kit/test` | `registerFoundation(t)`, `registerApprovals(t)` for convex-test |
|
|
98
99
|
|
|
99
100
|
## Minimal app wiring (the five root files)
|
|
100
101
|
|
|
@@ -291,3 +292,23 @@ Optional request context can be added through the existing Effect API adapter's
|
|
|
291
292
|
and consume `ctx.commands.exec(name, input)` through the application's existing
|
|
292
293
|
function helper names. Keep dependencies local to each feature; explicit execution
|
|
293
294
|
and input-only command handlers remain supported. See [optional context injection](./command-context.md).
|
|
295
|
+
|
|
296
|
+
## Shared auth and Effect context
|
|
297
|
+
|
|
298
|
+
Configure `createEffectAuthFunctions` once in `convex/functions.ts` using a typed
|
|
299
|
+
`AuthFunctionsConfig<DataModel, Role>` and inferred factory generics. Supply six
|
|
300
|
+
explicit policies: `query`, `mutation`, `action`, `systemQuery`, `systemMutation`,
|
|
301
|
+
and `systemAction`; use `{}` for no additions. Each supports `services`,
|
|
302
|
+
`context`, and `mapError`. All auth/role/admin constructors share the policy
|
|
303
|
+
for their kind. Services and context initialize only after successful auth and
|
|
304
|
+
role checks, using the secured DB and refreshed live action membership.
|
|
305
|
+
|
|
306
|
+
Bind query registries only in queries. Mutations can bind queries and commands
|
|
307
|
+
with their secured triggered writer. Actions have no DB; bind action-compatible
|
|
308
|
+
registries or delegate with `runMutation`/`runQuery`. Each registered mutation
|
|
309
|
+
has its own transaction and JWT auth; an action is not one transaction. Configure
|
|
310
|
+
internal system authority separately, without fabricated actors or inherited
|
|
311
|
+
public handles. Return plain records with handles under named keys; native/auth
|
|
312
|
+
key collisions are rejected, including inherited keys at runtime. Keep bound
|
|
313
|
+
handles request-local, return lazy Effects, and let failures escape mutations
|
|
314
|
+
for rollback. See `auth.md` for a packed-compiled shared factory example.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cvx-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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
|
"keywords": [
|
|
6
6
|
"component",
|
|
@@ -45,6 +45,10 @@ try {
|
|
|
45
45
|
join(fixture, 'auth.ts'),
|
|
46
46
|
readFileSync(join(root, 'test/auth-native-context-types.ts')),
|
|
47
47
|
)
|
|
48
|
+
writeFileSync(
|
|
49
|
+
join(fixture, 'shared-auth.ts'),
|
|
50
|
+
readFileSync(join(root, 'test/effect-auth-types.ts')),
|
|
51
|
+
)
|
|
48
52
|
writeFileSync(
|
|
49
53
|
join(fixture, 'tsconfig.json'),
|
|
50
54
|
JSON.stringify({
|
|
@@ -56,7 +60,7 @@ try {
|
|
|
56
60
|
module: 'ESNext',
|
|
57
61
|
moduleResolution: 'bundler',
|
|
58
62
|
},
|
|
59
|
-
include: ['auth.ts'],
|
|
63
|
+
include: ['auth.ts', 'shared-auth.ts'],
|
|
60
64
|
}),
|
|
61
65
|
)
|
|
62
66
|
run('bun', ['install', '--ignore-scripts'], fixture)
|