cvx-kit 0.1.8 → 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 +32 -0
- package/README.md +28 -28
- package/dist/agent-tools.d.mts +5 -5
- package/dist/agent-tools.mjs.map +1 -1
- package/dist/auth.d.mts +39 -289
- package/dist/auth.mjs.map +1 -1
- package/dist/components/approvals/_generated/api.d.mts +3 -4
- package/dist/components/approvals/_generated/api.mjs.map +1 -1
- package/dist/components/approvals/_generated/component.d.mts +1 -2
- package/dist/components/approvals/_generated/dataModel.d.mts +4 -5
- package/dist/components/approvals/_generated/server.d.mts +12 -13
- package/dist/components/approvals/_generated/server.mjs.map +1 -1
- package/dist/components/approvals/audit.d.mts +8 -8
- package/dist/components/approvals/audit.mjs +1 -1
- package/dist/components/approvals/audit.mjs.map +1 -1
- package/dist/components/approvals/client.d.mts +10 -10
- package/dist/components/approvals/client.mjs.map +1 -1
- package/dist/components/approvals/constants.d.mts +30 -30
- package/dist/components/approvals/constants.mjs.map +1 -1
- package/dist/components/approvals/convex.config.mjs.map +1 -1
- package/dist/components/approvals/decisions.d.mts +6 -6
- package/dist/components/approvals/decisions.mjs.map +1 -1
- package/dist/components/approvals/functions.d.mts +3 -3
- package/dist/components/approvals/functions.mjs.map +1 -1
- package/dist/components/approvals/health.d.mts +2 -2
- package/dist/components/approvals/health.mjs.map +1 -1
- package/dist/components/approvals/requests.d.mts +8 -8
- package/dist/components/approvals/requests.mjs.map +1 -1
- package/dist/components/approvals/validators.d.mts +29 -29
- package/dist/components/approvals/validators.mjs +1 -1
- package/dist/components/approvals/validators.mjs.map +1 -1
- package/dist/components/approvals/workflow.d.mts +3 -3
- package/dist/components/approvals/workflow.mjs.map +1 -1
- package/dist/components/approvals/workflow_steps.d.mts +15 -15
- package/dist/components/approvals/workflow_steps.mjs.map +1 -1
- package/dist/components/foundation/_generated/api.d.mts +3 -4
- package/dist/components/foundation/_generated/api.mjs.map +1 -1
- package/dist/components/foundation/_generated/component.d.mts +1 -2
- package/dist/components/foundation/_generated/dataModel.d.mts +4 -5
- package/dist/components/foundation/_generated/server.d.mts +12 -13
- package/dist/components/foundation/_generated/server.mjs.map +1 -1
- package/dist/components/foundation/client.d.mts +13 -13
- package/dist/components/foundation/client.mjs.map +1 -1
- package/dist/components/foundation/convex.config.mjs.map +1 -1
- package/dist/components/foundation/functions.d.mts +2 -2
- package/dist/components/foundation/functions.mjs.map +1 -1
- package/dist/components/foundation/modules/command/command.d.mts +14 -14
- package/dist/components/foundation/modules/command/command.mjs.map +1 -1
- package/dist/components/foundation/modules/command/lifecycle.d.mts +12 -13
- package/dist/components/foundation/modules/command/lifecycle.mjs.map +1 -1
- package/dist/components/foundation/modules/observability/observability.d.mts +4 -4
- package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
- package/dist/components/foundation/modules/query/query.d.mts +5 -5
- package/dist/components/foundation/modules/query/query.mjs.map +1 -1
- package/dist/components/foundation/operation.d.mts +5 -6
- package/dist/components/foundation/operation.mjs.map +1 -1
- package/dist/components/foundation/result.d.mts +6 -6
- package/dist/components/foundation/result.mjs.map +1 -1
- package/dist/components/foundation/telemetry.d.mts +2 -2
- package/dist/components/foundation/telemetry.mjs.map +1 -1
- package/dist/crud.d.mts +6 -5
- package/dist/crud.mjs.map +1 -1
- package/dist/effect.d.mts +3 -2
- package/dist/effect.mjs +1 -1
- package/dist/errors.d.mts +4 -4
- package/dist/errors.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/middleware.d.mts +2 -3
- package/dist/middleware.mjs.map +1 -1
- package/dist/modules/contracts/contract.d.mts +12 -13
- package/dist/modules/contracts/contract.mjs.map +1 -1
- package/dist/modules/contracts/convex.d.mts +3 -5
- package/dist/modules/contracts/convex.mjs.map +1 -1
- package/dist/modules/contracts/errors.d.mts +8 -9
- package/dist/modules/contracts/errors.mjs +1 -1
- package/dist/modules/contracts/errors.mjs.map +1 -1
- package/dist/modules/contracts/exposure.d.mts +13 -14
- package/dist/modules/contracts/exposure.mjs +1 -1
- package/dist/modules/contracts/exposure.mjs.map +1 -1
- package/dist/modules/contracts/idempotency.d.mts +15 -16
- package/dist/modules/contracts/idempotency.mjs +1 -1
- package/dist/modules/contracts/idempotency.mjs.map +1 -1
- package/dist/modules/contracts/standard.d.mts +2 -3
- package/dist/modules/contracts/standard.mjs.map +1 -1
- package/dist/modules/contracts/workflow.d.mts +26 -27
- package/dist/modules/contracts/workflow.mjs.map +1 -1
- package/dist/modules/effect/api-runtime.d.mts +10 -11
- package/dist/modules/effect/api-runtime.mjs +1 -1
- package/dist/modules/effect/api-runtime.mjs.map +1 -1
- package/dist/modules/effect/api-zod.d.mts +2 -3
- package/dist/modules/effect/api-zod.mjs.map +1 -1
- package/dist/modules/effect/api.d.mts +4 -5
- package/dist/modules/effect/api.mjs.map +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.d.mts +4 -5
- package/dist/modules/effect/command.mjs +1 -1
- package/dist/modules/effect/command.mjs.map +1 -1
- package/dist/modules/effect/crud.d.mts +7 -8
- package/dist/modules/effect/crud.mjs +1 -1
- package/dist/modules/effect/crud.mjs.map +1 -1
- package/dist/modules/effect/errors.d.mts +1 -2
- package/dist/modules/effect/errors.mjs.map +1 -1
- package/dist/modules/effect/foundation.d.mts +3 -4
- package/dist/modules/effect/foundation.mjs.map +1 -1
- package/dist/modules/effect/idempotency.d.mts +2 -3
- package/dist/modules/effect/idempotency.mjs.map +1 -1
- package/dist/modules/effect/normalize.d.mts +2 -3
- package/dist/modules/effect/normalize.mjs.map +1 -1
- package/dist/modules/effect/operation.d.mts +17 -18
- package/dist/modules/effect/operation.mjs.map +1 -1
- package/dist/modules/effect/query.d.mts +3 -4
- package/dist/modules/effect/query.mjs +1 -1
- package/dist/modules/effect/query.mjs.map +1 -1
- package/dist/modules/effect/schema.d.mts +8 -9
- package/dist/modules/effect/schema.mjs.map +1 -1
- package/dist/modules/effect/workflow.d.mts +5 -6
- package/dist/modules/effect/workflow.mjs +1 -1
- package/dist/modules/effect/workflow.mjs.map +1 -1
- package/dist/oxlint/architecture.d.mts +6 -7
- package/dist/oxlint/architecture.mjs +1 -1
- package/dist/oxlint/architecture.mjs.map +1 -1
- package/dist/oxlint/ast.d.mts +22 -23
- package/dist/oxlint/ast.mjs +1 -1
- package/dist/oxlint/ast.mjs.map +1 -1
- package/dist/oxlint/ownership.d.mts +2 -3
- package/dist/oxlint/ownership.mjs +1 -1
- package/dist/oxlint/ownership.mjs.map +1 -1
- package/dist/oxlint.d.mts +4 -4
- package/dist/oxlint.mjs +1 -1
- package/dist/oxlint.mjs.map +1 -1
- package/dist/state-machine.d.mts +1 -2
- package/dist/state-machine.mjs.map +1 -1
- package/dist/tenancy.d.mts +6 -6
- package/dist/tenancy.mjs.map +1 -1
- package/dist/triggers.d.mts +6 -6
- package/dist/triggers.mjs.map +1 -1
- package/dist/webhooks.d.mts +5 -6
- package/dist/webhooks.mjs.map +1 -1
- package/dist/zod-table.d.mts +10 -10
- package/dist/zod-table.mjs.map +1 -1
- package/docs/auth.md +131 -1
- package/docs/effect.md +8 -2
- package/docs/llms.md +41 -20
- package/docs/oxlint.md +1 -1
- package/docs/upgrading.md +9 -1
- package/docs/zod-table.md +1 -1
- package/package.json +21 -15
- package/scripts/test-packed-auth.mjs +72 -0
- package/scripts/test-packed-effect.mjs +2 -1
- package/scripts/test-packed-ids.mjs +2 -2
- package/dist/node_modules/@standard-schema/spec/dist/index.d.mts +0 -79
package/dist/oxlint.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oxlint.mjs","names":[],"sources":["../src/oxlint.ts"],"sourcesContent":["import { basename, dirname, relative, sep } from 'node:path'\nimport type { CreateRule, ESTree, Plugin, Rule } from '@oxlint/plugins'\nimport {\n\tinspectArchitecture,\n\tcreateResolver,\n\tresolveImport,\n\ttype ArchitectureOptions,\n\ttype ArchitectureDiagnostic,\n} from './oxlint/architecture'\nimport { ownershipRules, type OwnershipRuleName } from './oxlint/ownership'\nimport {\n\tappFile,\n\timported,\n\tsourceVisitors,\n\tisFunctionsImport,\n\tisTableChain,\n\tisDbWrite,\n\thasCall,\n\tmetadata,\n\tcomponentRoot,\n\tstaticString,\n\tpropertyName,\n\tbinding,\n\texportedNames,\n\texportName,\n} from './oxlint/ast'\n\nexport type { ArchitectureOptions, ArchitectureDiagnostic } from './oxlint/architecture'\n\nexport function checkArchitecture(options: ArchitectureOptions = {}): ArchitectureDiagnostic[] {\n\treturn inspectArchitecture(options)\n}\n\nexport type RuleName =\n\t| OwnershipRuleName\n\t| 'component-boundaries'\n\t| 'no-component-env'\n\t| 'schema-file-boundaries'\n\t| 'no-internal-reexports'\n\t| 'public-api-first'\n\t| 'named-private-helpers'\n\t| 'no-raw-builders'\n\t| 'no-handwritten-references'\n\t| 'domain-import-boundaries'\n\t| 'public-functions-in-api'\n\t| 'thin-api-adapters'\n\t| 'no-inline-enums'\n\t| 'no-unbounded-reads'\n\t| 'no-manual-timestamps'\n\t| 'root-facade-ownership'\n\n/** Consumer application rules derived from docs/conventions.md. */\nconst applicationRules: Record<RuleName, CreateRule> = {\n\t...ownershipRules,\n\t'root-facade-ownership': {\n\t\tmeta: metadata('Configure shared kit infrastructure once in its root facade.', {\n\t\t\tfacade: 'Configure {{name}} once in convex/{{file}}; import that facade elsewhere.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\tconst seen = new Set<string>()\n\t\t\tfunction check(node: ESTree.CallExpression | ESTree.NewExpression) {\n\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\tif (!ref) return\n\t\t\t\tlet file: string | null = null\n\t\t\t\tif (['cvx-kit', 'cvx-kit/auth'].includes(ref.source) && ref.name === 'createAuthFunctions')\n\t\t\t\t\tfile = 'functions.ts'\n\t\t\t\tif (['cvx-kit', 'cvx-kit/triggers'].includes(ref.source) && ref.name === 'createTriggers')\n\t\t\t\t\tfile = 'triggers.ts'\n\t\t\t\tif (\n\t\t\t\t\t['cvx-kit', 'cvx-kit/components/foundation'].includes(ref.source) &&\n\t\t\t\t\tref.name === 'Foundation'\n\t\t\t\t)\n\t\t\t\t\tfile = 'foundation.ts'\n\t\t\t\tif (!file) return\n\t\t\t\tif (appFile(context) !== file || seen.has(ref.name))\n\t\t\t\t\tcontext.report({ node, messageId: 'facade', data: { name: ref.name, file } })\n\t\t\t\tseen.add(ref.name)\n\t\t\t}\n\t\t\treturn { CallExpression: check, NewExpression: check }\n\t\t},\n\t},\n\t'no-raw-builders': {\n\t\tmeta: metadata('Build host functions through the auth-aware root constructors.', {\n\t\t\tbuilder:\n\t\t\t\t'Import raw builders only in convex/functions.ts; use auth*/role*/admin* or system* elsewhere.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (appFile(context) === 'functions.ts' || componentRoot(context, context.filename)) return {}\n\t\t\tconst resolver = createResolver()\n\t\t\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (node.importKind === 'type') return\n\t\t\t\t\tconst importedSource = node.source.value\n\t\t\t\t\tconst target = resolveImport(resolver, context.filename, importedSource)\n\t\t\t\t\tconst source =\n\t\t\t\t\t\ttarget && appFile(context, target)?.startsWith('_generated/')\n\t\t\t\t\t\t\t? appFile(context, target)!\n\t\t\t\t\t\t\t: importedSource\n\t\t\t\t\tif (\n\t\t\t\t\t\t!/(?:^|\\/)_generated\\/server(?:\\.[cm]?[jt]s)?$/.test(source) &&\n\t\t\t\t\t\tsource !== 'convex/server'\n\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\tfor (const specifier of node.specifiers) {\n\t\t\t\t\t\tif (specifier.type === 'ImportSpecifier' && specifier.importKind === 'type') continue\n\t\t\t\t\t\tconst name =\n\t\t\t\t\t\t\tspecifier.type === 'ImportSpecifier' ? exportName(specifier.imported) : null\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t(name === null && source !== 'convex/server') ||\n\t\t\t\t\t\t\t(name &&\n\t\t\t\t\t\t\t\t/^(?:internal)?(?:Query|Mutation|Action)(?:Generic)?$|^(?:query|mutation|action)(?:Generic)?$/.test(\n\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'builder' })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tref?.source === 'convex/server' &&\n\t\t\t\t\t\t/^(?:internal)?(?:Query|Mutation|Action)Generic$|^(?:query|mutation|action)Generic$/.test(\n\t\t\t\t\t\t\tref.name,\n\t\t\t\t\t\t) &&\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression'\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'builder' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-handwritten-references': {\n\t\tmeta: metadata('Use generated api/internal references.', {\n\t\t\treference: 'Use generated api/internal references instead of makeFunctionReference.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (ref?.source === 'convex/server' && ref.name === 'makeFunctionReference')\n\t\t\t\t\t\tcontext.report({ node, messageId: 'reference' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'domain-import-boundaries': {\n\t\tmeta: metadata('Keep api, application, domain, and shared dependencies directed.', {\n\t\t\tboundary:\n\t\t\t\t'Follow api → application/domain → shared; sibling modules expose only rules.ts and queries.ts.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst file = appFile(context)\n\t\t\tconst resolver = createResolver()\n\t\t\treturn sourceVisitors(context, (node, source) => {\n\t\t\t\tif (!file) return\n\t\t\t\tconst resolved = resolveImport(resolver, context.filename, source)\n\t\t\t\tif (!resolved) return\n\t\t\t\tconst target = appFile(context, resolved)?.replace(/\\.[cm]?[jt]s$/, '')\n\t\t\t\tif (!target) {\n\t\t\t\t\tif (!resolved.split(sep).includes('node_modules'))\n\t\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst from = file.split('/')\n\t\t\t\tconst to = target.split('/')\n\t\t\t\tlet forbidden = false\n\t\t\t\tif (from[0] === 'domain') {\n\t\t\t\t\tforbidden = ['api', 'application', 'migrations'].includes(to[0])\n\t\t\t\t\tif (\n\t\t\t\t\t\tto[0] === 'domain' &&\n\t\t\t\t\t\tfrom[1] !== to[1] &&\n\t\t\t\t\t\tto[1] !== 'shared' &&\n\t\t\t\t\t\tfrom[1] !== 'table.ts'\n\t\t\t\t\t) {\n\t\t\t\t\t\tforbidden =\n\t\t\t\t\t\t\tfrom[1] === 'shared' || !['rules', 'queries'].includes(to.slice(2).join('/'))\n\t\t\t\t\t}\n\t\t\t\t} else if (from[0] === 'api') {\n\t\t\t\t\tforbidden =\n\t\t\t\t\t\t['api', 'migrations'].includes(to[0]) ||\n\t\t\t\t\t\t(to[0] === 'domain' &&\n\t\t\t\t\t\t\tto[1] !== 'shared' &&\n\t\t\t\t\t\t\tto[1] !== (from.length > 2 ? from[1] : basename(file).replace(/\\.[cm]?[jt]s$/, '')))\n\t\t\t\t} else if (from[0] === 'application') forbidden = ['api', 'migrations'].includes(to[0])\n\t\t\t\tif (to[0] === 'migrations' && from[0] !== 'migrations') forbidden = true\n\t\t\t\tif (\n\t\t\t\t\t['api', 'domain', 'application'].includes(from[0]) &&\n\t\t\t\t\tto.length > 1 &&\n\t\t\t\t\t!['api', 'domain', 'application', '_generated', 'components', 'migrations'].includes(\n\t\t\t\t\t\tto[0],\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t\t\tforbidden = true\n\t\t\t\tif (forbidden) context.report({ node, messageId: 'boundary' })\n\t\t\t})\n\t\t},\n\t},\n\t'public-functions-in-api': {\n\t\tmeta: metadata('Expose authenticated public functions only through api/.', {\n\t\t\tpublic: 'Declare public auth*/role*/admin* functions in convex/api/<module>.ts.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (appFile(context)?.startsWith('api/')) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type !== 'CallExpression' &&\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\tisFunctionsImport(context, ref.source) &&\n\t\t\t\t\t\t/^(auth|role|admin)(Query|Mutation|Action)$/.test(ref.name)\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'public' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'thin-api-adapters': {\n\t\tmeta: metadata('Keep business decisions and writes in domain executors.', {\n\t\t\tadapter:\n\t\t\t\t'Move business branches, private helpers, and database writes into the owning domain executor.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!appFile(context)?.startsWith('api/')) return {}\n\t\t\treturn {\n\t\t\t\tIfStatement(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tSwitchStatement(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tConditionalExpression(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exported = exportedNames(node)\n\t\t\t\t\tfor (const statement of node.body) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tstatement.type === 'FunctionDeclaration' &&\n\t\t\t\t\t\t\tstatement.id &&\n\t\t\t\t\t\t\t!exported.has(statement.id.name)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tcontext.report({ node: statement, messageId: 'adapter' })\n\t\t\t\t\t\tif (statement.type === 'VariableDeclaration')\n\t\t\t\t\t\t\tfor (const declaration of statement.declarations) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tdeclaration.id.type === 'Identifier' &&\n\t\t\t\t\t\t\t\t\t!exported.has(declaration.id.name) &&\n\t\t\t\t\t\t\t\t\t['ArrowFunctionExpression', 'FunctionExpression'].includes(\n\t\t\t\t\t\t\t\t\t\tdeclaration.init?.type ?? '',\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tcontext.report({ node: declaration, messageId: 'adapter' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (isDbWrite(node)) context.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-inline-enums': {\n\t\tmeta: metadata('Keep vocabularies in named constants.ts tuples.', {\n\t\t\tvocabulary: 'Define a named readonly tuple in constants.ts and pass it to z.enum().',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tlet values = node.arguments[0]\n\t\t\t\t\twhile (values?.type === 'TSAsExpression' || values?.type === 'TSSatisfiesExpression')\n\t\t\t\t\t\tvalues = values.expression\n\t\t\t\t\tif (\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\t/^zod(?:\\/v4)?$/.test(ref.source) &&\n\t\t\t\t\t\tref.name === 'enum' &&\n\t\t\t\t\t\tvalues?.type === 'ArrayExpression'\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'vocabulary' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-unbounded-reads': {\n\t\tmeta: metadata('Bound collection reads through include or pagination.', {\n\t\t\tbounded:\n\t\t\t\t'Use include(...).execute(limit) or bounded pagination; do not collect() or resolve() an unbounded query.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (node.callee.type !== 'MemberExpression') return\n\t\t\t\t\tconst name = propertyName(node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\t(name === 'collect' && hasCall(node.callee.object, 'query')) ||\n\t\t\t\t\t\t(name === 'resolve' && hasCall(node.callee.object, 'include'))\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'bounded' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-manual-timestamps': {\n\t\tmeta: metadata('Let timestamps triggers own createdAt and updatedAt.', {\n\t\t\ttimestamp: 'Remove {{field}} from this write; register the timestamps trigger instead.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (!isDbWrite(node)) return\n\t\t\t\t\tconst data = node.arguments.at(-1)\n\t\t\t\t\tif (data?.type !== 'ObjectExpression') return\n\t\t\t\t\tfor (const property of data.properties) {\n\t\t\t\t\t\tif (property.type !== 'Property') continue\n\t\t\t\t\t\tconst field = property.computed ? staticString(property.key) : exportName(property.key)\n\t\t\t\t\t\tif (field === 'createdAt' || field === 'updatedAt')\n\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'timestamp', data: { field } })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'component-boundaries': {\n\t\tmeta: metadata('Keep portable components isolated and consume their client facades.', {\n\t\t\tboundary:\n\t\t\t\t'Components may import only their own files and npm dependencies; inject host policy through the client API.',\n\t\t\tfacade: 'Import a component through its client.ts facade, not its private implementation.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst owner = componentRoot(context, context.filename)\n\t\t\tconst resolver = createResolver()\n\n\t\t\treturn sourceVisitors(context, (node, specifier) => {\n\t\t\t\tif (!specifier.startsWith('.')) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t/^cvx-kit\\/components\\/[^/]+\\//.test(specifier) &&\n\t\t\t\t\t\t!/^cvx-kit\\/components\\/[^/]+\\/convex\\.config(?:\\.js)?$/.test(specifier)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'facade' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst target = resolveImport(resolver, context.filename, specifier)\n\t\t\t\tif (!target || target.split(sep).includes('node_modules')) return\n\t\t\t\tif (\n\t\t\t\t\towner &&\n\t\t\t\t\t(relative(owner, target).startsWith(`..${sep}`) || target === dirname(owner))\n\t\t\t\t) {\n\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\n\t\t\t\t} else if (\n\t\t\t\t\t!owner &&\n\t\t\t\t\tcomponentRoot(context, target) &&\n\t\t\t\t\t!/^client(?:\\.[cm]?[jt]s)?$/.test(basename(target)) &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tappFile(context) === 'convex.config.ts' &&\n\t\t\t\t\t\t/^convex\\.config(?:\\.[cm]?[jt]s)?$/.test(basename(target))\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tcontext.report({ node, messageId: 'facade' })\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t},\n\t'no-component-env': {\n\t\tmeta: metadata('Pass environment configuration into portable components.', {\n\t\t\tenv: 'Portable components must receive configuration through arguments; do not read process.env.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!componentRoot(context, context.filename)) return {}\n\t\t\tfunction isProcess(node: ESTree.Node) {\n\t\t\t\tif (node.type !== 'Identifier') return false\n\t\t\t\tconst variable = binding(context, node)\n\t\t\t\tif (node.name === 'process' && !variable?.defs.length) return true\n\t\t\t\treturn variable?.defs.some(\n\t\t\t\t\t(def) =>\n\t\t\t\t\t\tdef.type === 'ImportBinding' &&\n\t\t\t\t\t\tdef.parent?.type === 'ImportDeclaration' &&\n\t\t\t\t\t\t['node:process', 'process'].includes(def.parent.source.value) &&\n\t\t\t\t\t\t['ImportDefaultSpecifier', 'ImportNamespaceSpecifier'].includes(def.node.type),\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (['node:process', 'process'].includes(node.source.value)) {\n\t\t\t\t\t\tfor (const specifier of node.specifiers) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tspecifier.type === 'ImportSpecifier' &&\n\t\t\t\t\t\t\t\texportName(specifier.imported) === 'env'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'env' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tMemberExpression(node) {\n\t\t\t\t\tif (propertyName(node) !== 'env') return\n\t\t\t\t\tconst object = node.object\n\t\t\t\t\tconst globalProcess =\n\t\t\t\t\t\tobject.type === 'MemberExpression' &&\n\t\t\t\t\t\tpropertyName(object) === 'process' &&\n\t\t\t\t\t\tobject.object.type === 'Identifier' &&\n\t\t\t\t\t\tobject.object.name === 'globalThis' &&\n\t\t\t\t\t\t!binding(context, object.object)?.defs.length\n\t\t\t\t\tif (isProcess(object) || globalProcess) context.report({ node, messageId: 'env' })\n\t\t\t\t},\n\t\t\t\tVariableDeclarator(node) {\n\t\t\t\t\tif (node.init && isProcess(node.init) && node.id.type === 'ObjectPattern') {\n\t\t\t\t\t\tfor (const property of node.id.properties) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tproperty.type === 'Property' &&\n\t\t\t\t\t\t\t\t(property.computed ? staticString(property.key) : exportName(property.key)) ===\n\t\t\t\t\t\t\t\t\t'env'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'env' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'schema-file-boundaries': {\n\t\tmeta: metadata('Keep storage shapes and indexes in their documented owners.', {\n\t\t\tschema: '{{builder}} belongs in {{file}}.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tconst file = appFile(context)\n\t\t\t\t\tif (!file) return\n\t\t\t\t\tlet expected: string | null = null\n\t\t\t\t\tif (ref?.source === 'convex/server') {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tref.name === 'defineSchema' &&\n\t\t\t\t\t\t\tfile !== 'schema.ts' &&\n\t\t\t\t\t\t\t!(componentRoot(context, context.filename) && basename(file) === 'schema.ts')\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\texpected = 'the root schema.ts (or a component schema.ts)'\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tref.name === 'defineTable' &&\n\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\tcomponentRoot(context, context.filename) &&\n\t\t\t\t\t\t\t\t['schema.ts', 'table.ts'].includes(basename(file))\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\texpected =\n\t\t\t\t\t\t\t\t'a component schema.ts/table.ts; host tables use zodTable in domain/<module>/schema.ts'\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\t['cvx-kit', 'cvx-kit/zod-table'].includes(ref.source) &&\n\t\t\t\t\t\t['zodTable', 'tenantTable', 'zodVariantTable'].includes(ref.name) &&\n\t\t\t\t\t\t!/^domain\\/[^/]+\\/schema\\.ts$/.test(file)\n\t\t\t\t\t)\n\t\t\t\t\t\texpected = 'domain/<module>/schema.ts'\n\t\t\t\t\tif (expected)\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'schema',\n\t\t\t\t\t\t\tdata: { builder: ref?.name ?? 'table', file: expected },\n\t\t\t\t\t\t})\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression' &&\n\t\t\t\t\t\t['index', 'searchIndex', 'vectorIndex'].includes(propertyName(node.callee) ?? '') &&\n\t\t\t\t\t\tisTableChain(node.callee.object) &&\n\t\t\t\t\t\t!componentRoot(context, context.filename) &&\n\t\t\t\t\t\t!/^domain\\/[^/]+\\/table\\.ts$/.test(file)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'schema',\n\t\t\t\t\t\t\tdata: { builder: 'Table indexes', file: 'domain/<module>/table.ts' },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-internal-reexports': {\n\t\tmeta: metadata('Import from owning modules directly; consumer apps have no barrels.', {\n\t\t\treexport: 'Import from the owning file directly; do not re-export imports inside convex/.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tfunction check(node: ESTree.ExportNamedDeclaration | ESTree.ExportAllDeclaration) {\n\t\t\t\tif (node.source) {\n\t\t\t\t\tcontext.report({ node, messageId: 'reexport' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor (const specifier of node.type === 'ExportNamedDeclaration' ? node.specifiers : []) {\n\t\t\t\t\tif (binding(context, specifier.local)?.defs.some((def) => def.type === 'ImportBinding')) {\n\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'reexport' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tExportNamedDeclaration: check,\n\t\t\t\tExportAllDeclaration: check,\n\t\t\t\tExportDefaultDeclaration(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.declaration.type === 'Identifier' &&\n\t\t\t\t\t\tbinding(context, node.declaration)?.defs.some((def) => def.type === 'ImportBinding')\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'reexport' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'public-api-first': {\n\t\tmeta: metadata('Place private helper implementations after the exported API.', {\n\t\t\torder:\n\t\t\t\t'Move this private helper below the exported API so the file reads from public purpose to implementation.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exports = exportedNames(node)\n\t\t\t\t\tconst lastExport = node.body.findLastIndex((statement) =>\n\t\t\t\t\t\tstatement.type.startsWith('Export'),\n\t\t\t\t\t)\n\t\t\t\t\tfor (const statement of node.body.slice(0, lastExport < 0 ? 0 : lastExport)) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tstatement.type === 'FunctionDeclaration' &&\n\t\t\t\t\t\t\tstatement.id &&\n\t\t\t\t\t\t\t!exports.has(statement.id.name)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontext.report({ node: statement, messageId: 'order' })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'named-private-helpers': {\n\t\tmeta: metadata('Use named declarations for top-level private helper functions.', {\n\t\t\thelper: 'Declare this private helper with function name(...) at the bottom of the file.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exports = exportedNames(node)\n\t\t\t\t\tfor (const statement of node.body) {\n\t\t\t\t\t\tif (statement.type !== 'VariableDeclaration') continue\n\t\t\t\t\t\tfor (const declaration of statement.declarations) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tdeclaration.id.type === 'Identifier' &&\n\t\t\t\t\t\t\t\t!exports.has(declaration.id.name) &&\n\t\t\t\t\t\t\t\t(declaration.init?.type === 'ArrowFunctionExpression' ||\n\t\t\t\t\t\t\t\t\tdeclaration.init?.type === 'FunctionExpression')\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: declaration, messageId: 'helper' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n\nexport const rules: Record<RuleName, Rule> = scopeRules(applicationRules)\nconst plugin: Plugin = { meta: { name: 'cvx' }, rules }\nexport default plugin\n\nfunction scopeRules(source: Record<RuleName, CreateRule>): Record<RuleName, Rule> {\n\tconst scoped = { ...source }\n\t// SAFETY: source is the closed applicationRules record; its own keys are exactly RuleName.\n\tconst names = Object.keys(source) as RuleName[]\n\tfor (const name of names) {\n\t\tconst rule = source[name]\n\t\tscoped[name] = {\n\t\t\tmeta: {\n\t\t\t\t...rule.meta,\n\t\t\t\tschema: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'object',\n\t\t\t\t\t\tproperties: { convexDir: { type: 'string', minLength: 1 } },\n\t\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\tcreate(context) {\n\t\t\t\tconst file = appFile(context)\n\t\t\t\tif (\n\t\t\t\t\t!file ||\n\t\t\t\t\t/(?:^|\\/)(?:_generated|__tests__)(?:\\/|$)/.test(file) ||\n\t\t\t\t\t/\\.(?:test|spec)\\.[cm]?[jt]s$/.test(file)\n\t\t\t\t)\n\t\t\t\t\treturn {}\n\t\t\t\treturn rule.create(context)\n\t\t\t},\n\t\t}\n\t}\n\treturn scoped\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oxlint.mjs","names":["checkArchitecture","inspectArchitecture","options","rules","scopeRules","ownershipRules","metadata","componentRoot","context","seen","check","ref","imported","node","file","appFile","resolver","createResolver","importedSource","target","resolveImport","source","specifier","name","exportName","sourceVisitors","resolved","sep","from","to","forbidden","basename","isFunctionsImport","exported","exportedNames","statement","declaration","isDbWrite","values","propertyName","hasCall","data","property","field","staticString","owner","relative","dirname","isProcess","variable","binding","def","object","globalProcess","expected","isTableChain","exports","lastExport","plugin","scoped","names","rule"],"sources":["../src/oxlint.ts"],"sourcesContent":["import { basename, dirname, relative, sep } from 'node:path'\nimport type { CreateRule, ESTree, Plugin, Rule } from '@oxlint/plugins'\nimport {\n\tinspectArchitecture,\n\tcreateResolver,\n\tresolveImport,\n\ttype ArchitectureOptions,\n\ttype ArchitectureDiagnostic,\n} from './oxlint/architecture'\nimport { ownershipRules, type OwnershipRuleName } from './oxlint/ownership'\nimport {\n\tappFile,\n\timported,\n\tsourceVisitors,\n\tisFunctionsImport,\n\tisTableChain,\n\tisDbWrite,\n\thasCall,\n\tmetadata,\n\tcomponentRoot,\n\tstaticString,\n\tpropertyName,\n\tbinding,\n\texportedNames,\n\texportName,\n} from './oxlint/ast'\n\nexport type { ArchitectureOptions, ArchitectureDiagnostic } from './oxlint/architecture'\n\nexport function checkArchitecture(options: ArchitectureOptions = {}): ArchitectureDiagnostic[] {\n\treturn inspectArchitecture(options)\n}\n\nexport type RuleName =\n\t| OwnershipRuleName\n\t| 'component-boundaries'\n\t| 'no-component-env'\n\t| 'schema-file-boundaries'\n\t| 'no-internal-reexports'\n\t| 'public-api-first'\n\t| 'named-private-helpers'\n\t| 'no-raw-builders'\n\t| 'no-handwritten-references'\n\t| 'domain-import-boundaries'\n\t| 'public-functions-in-api'\n\t| 'thin-api-adapters'\n\t| 'no-inline-enums'\n\t| 'no-unbounded-reads'\n\t| 'no-manual-timestamps'\n\t| 'root-facade-ownership'\n\n/** Consumer application rules derived from docs/conventions.md. */\nconst applicationRules: Record<RuleName, CreateRule> = {\n\t...ownershipRules,\n\t'root-facade-ownership': {\n\t\tmeta: metadata('Configure shared kit infrastructure once in its root facade.', {\n\t\t\tfacade: 'Configure {{name}} once in convex/{{file}}; import that facade elsewhere.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\tconst seen = new Set<string>()\n\t\t\tfunction check(node: ESTree.CallExpression | ESTree.NewExpression) {\n\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\tif (!ref) return\n\t\t\t\tlet file: string | null = null\n\t\t\t\tif (['cvx-kit', 'cvx-kit/auth'].includes(ref.source) && ref.name === 'createAuthFunctions')\n\t\t\t\t\tfile = 'functions.ts'\n\t\t\t\tif (['cvx-kit', 'cvx-kit/triggers'].includes(ref.source) && ref.name === 'createTriggers')\n\t\t\t\t\tfile = 'triggers.ts'\n\t\t\t\tif (\n\t\t\t\t\t['cvx-kit', 'cvx-kit/components/foundation'].includes(ref.source) &&\n\t\t\t\t\tref.name === 'Foundation'\n\t\t\t\t)\n\t\t\t\t\tfile = 'foundation.ts'\n\t\t\t\tif (!file) return\n\t\t\t\tif (appFile(context) !== file || seen.has(ref.name))\n\t\t\t\t\tcontext.report({ node, messageId: 'facade', data: { name: ref.name, file } })\n\t\t\t\tseen.add(ref.name)\n\t\t\t}\n\t\t\treturn { CallExpression: check, NewExpression: check }\n\t\t},\n\t},\n\t'no-raw-builders': {\n\t\tmeta: metadata('Build host functions through the auth-aware root constructors.', {\n\t\t\tbuilder:\n\t\t\t\t'Import raw builders only in convex/functions.ts; use auth*/role*/admin* or system* elsewhere.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (appFile(context) === 'functions.ts' || componentRoot(context, context.filename)) return {}\n\t\t\tconst resolver = createResolver()\n\t\t\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (node.importKind === 'type') return\n\t\t\t\t\tconst importedSource = node.source.value\n\t\t\t\t\tconst target = resolveImport(resolver, context.filename, importedSource)\n\t\t\t\t\tconst source =\n\t\t\t\t\t\ttarget && appFile(context, target)?.startsWith('_generated/')\n\t\t\t\t\t\t\t? appFile(context, target)!\n\t\t\t\t\t\t\t: importedSource\n\t\t\t\t\tif (\n\t\t\t\t\t\t!/(?:^|\\/)_generated\\/server(?:\\.[cm]?[jt]s)?$/.test(source) &&\n\t\t\t\t\t\tsource !== 'convex/server'\n\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\tfor (const specifier of node.specifiers) {\n\t\t\t\t\t\tif (specifier.type === 'ImportSpecifier' && specifier.importKind === 'type') continue\n\t\t\t\t\t\tconst name =\n\t\t\t\t\t\t\tspecifier.type === 'ImportSpecifier' ? exportName(specifier.imported) : null\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t(name === null && source !== 'convex/server') ||\n\t\t\t\t\t\t\t(name &&\n\t\t\t\t\t\t\t\t/^(?:internal)?(?:Query|Mutation|Action)(?:Generic)?$|^(?:query|mutation|action)(?:Generic)?$/.test(\n\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'builder' })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tref?.source === 'convex/server' &&\n\t\t\t\t\t\t/^(?:internal)?(?:Query|Mutation|Action)Generic$|^(?:query|mutation|action)Generic$/.test(\n\t\t\t\t\t\t\tref.name,\n\t\t\t\t\t\t) &&\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression'\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'builder' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-handwritten-references': {\n\t\tmeta: metadata('Use generated api/internal references.', {\n\t\t\treference: 'Use generated api/internal references instead of makeFunctionReference.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (ref?.source === 'convex/server' && ref.name === 'makeFunctionReference')\n\t\t\t\t\t\tcontext.report({ node, messageId: 'reference' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'domain-import-boundaries': {\n\t\tmeta: metadata('Keep api, application, domain, and shared dependencies directed.', {\n\t\t\tboundary:\n\t\t\t\t'Follow api → application/domain → shared; sibling modules expose only rules.ts and queries.ts.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst file = appFile(context)\n\t\t\tconst resolver = createResolver()\n\t\t\treturn sourceVisitors(context, (node, source) => {\n\t\t\t\tif (!file) return\n\t\t\t\tconst resolved = resolveImport(resolver, context.filename, source)\n\t\t\t\tif (!resolved) return\n\t\t\t\tconst target = appFile(context, resolved)?.replace(/\\.[cm]?[jt]s$/, '')\n\t\t\t\tif (!target) {\n\t\t\t\t\tif (!resolved.split(sep).includes('node_modules'))\n\t\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst from = file.split('/')\n\t\t\t\tconst to = target.split('/')\n\t\t\t\tlet forbidden = false\n\t\t\t\tif (from[0] === 'domain') {\n\t\t\t\t\tforbidden = ['api', 'application', 'migrations'].includes(to[0])\n\t\t\t\t\tif (\n\t\t\t\t\t\tto[0] === 'domain' &&\n\t\t\t\t\t\tfrom[1] !== to[1] &&\n\t\t\t\t\t\tto[1] !== 'shared' &&\n\t\t\t\t\t\tfrom[1] !== 'table.ts'\n\t\t\t\t\t) {\n\t\t\t\t\t\tforbidden =\n\t\t\t\t\t\t\tfrom[1] === 'shared' || !['rules', 'queries'].includes(to.slice(2).join('/'))\n\t\t\t\t\t}\n\t\t\t\t} else if (from[0] === 'api') {\n\t\t\t\t\tforbidden =\n\t\t\t\t\t\t['api', 'migrations'].includes(to[0]) ||\n\t\t\t\t\t\t(to[0] === 'domain' &&\n\t\t\t\t\t\t\tto[1] !== 'shared' &&\n\t\t\t\t\t\t\tto[1] !== (from.length > 2 ? from[1] : basename(file).replace(/\\.[cm]?[jt]s$/, '')))\n\t\t\t\t} else if (from[0] === 'application') forbidden = ['api', 'migrations'].includes(to[0])\n\t\t\t\tif (to[0] === 'migrations' && from[0] !== 'migrations') forbidden = true\n\t\t\t\tif (\n\t\t\t\t\t['api', 'domain', 'application'].includes(from[0]) &&\n\t\t\t\t\tto.length > 1 &&\n\t\t\t\t\t!['api', 'domain', 'application', '_generated', 'components', 'migrations'].includes(\n\t\t\t\t\t\tto[0],\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t\t\tforbidden = true\n\t\t\t\tif (forbidden) context.report({ node, messageId: 'boundary' })\n\t\t\t})\n\t\t},\n\t},\n\t'public-functions-in-api': {\n\t\tmeta: metadata('Expose authenticated public functions only through api/.', {\n\t\t\tpublic: 'Declare public auth*/role*/admin* functions in convex/api/<module>.ts.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (appFile(context)?.startsWith('api/')) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type !== 'CallExpression' &&\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\tisFunctionsImport(context, ref.source) &&\n\t\t\t\t\t\t/^(auth|role|admin)(Query|Mutation|Action)$/.test(ref.name)\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'public' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'thin-api-adapters': {\n\t\tmeta: metadata('Keep business decisions and writes in domain executors.', {\n\t\t\tadapter:\n\t\t\t\t'Move business branches, private helpers, and database writes into the owning domain executor.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!appFile(context)?.startsWith('api/')) return {}\n\t\t\treturn {\n\t\t\t\tIfStatement(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tSwitchStatement(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tConditionalExpression(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exported = exportedNames(node)\n\t\t\t\t\tfor (const statement of node.body) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tstatement.type === 'FunctionDeclaration' &&\n\t\t\t\t\t\t\tstatement.id &&\n\t\t\t\t\t\t\t!exported.has(statement.id.name)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tcontext.report({ node: statement, messageId: 'adapter' })\n\t\t\t\t\t\tif (statement.type === 'VariableDeclaration')\n\t\t\t\t\t\t\tfor (const declaration of statement.declarations) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tdeclaration.id.type === 'Identifier' &&\n\t\t\t\t\t\t\t\t\t!exported.has(declaration.id.name) &&\n\t\t\t\t\t\t\t\t\t['ArrowFunctionExpression', 'FunctionExpression'].includes(\n\t\t\t\t\t\t\t\t\t\tdeclaration.init?.type ?? '',\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tcontext.report({ node: declaration, messageId: 'adapter' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (isDbWrite(node)) context.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-inline-enums': {\n\t\tmeta: metadata('Keep vocabularies in named constants.ts tuples.', {\n\t\t\tvocabulary: 'Define a named readonly tuple in constants.ts and pass it to z.enum().',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tlet values = node.arguments[0]\n\t\t\t\t\twhile (values?.type === 'TSAsExpression' || values?.type === 'TSSatisfiesExpression')\n\t\t\t\t\t\tvalues = values.expression\n\t\t\t\t\tif (\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\t/^zod(?:\\/v4)?$/.test(ref.source) &&\n\t\t\t\t\t\tref.name === 'enum' &&\n\t\t\t\t\t\tvalues?.type === 'ArrayExpression'\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'vocabulary' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-unbounded-reads': {\n\t\tmeta: metadata('Bound collection reads through include or pagination.', {\n\t\t\tbounded:\n\t\t\t\t'Use include(...).execute(limit) or bounded pagination; do not collect() or resolve() an unbounded query.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (node.callee.type !== 'MemberExpression') return\n\t\t\t\t\tconst name = propertyName(node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\t(name === 'collect' && hasCall(node.callee.object, 'query')) ||\n\t\t\t\t\t\t(name === 'resolve' && hasCall(node.callee.object, 'include'))\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'bounded' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-manual-timestamps': {\n\t\tmeta: metadata('Let timestamps triggers own createdAt and updatedAt.', {\n\t\t\ttimestamp: 'Remove {{field}} from this write; register the timestamps trigger instead.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (!isDbWrite(node)) return\n\t\t\t\t\tconst data = node.arguments.at(-1)\n\t\t\t\t\tif (data?.type !== 'ObjectExpression') return\n\t\t\t\t\tfor (const property of data.properties) {\n\t\t\t\t\t\tif (property.type !== 'Property') continue\n\t\t\t\t\t\tconst field = property.computed ? staticString(property.key) : exportName(property.key)\n\t\t\t\t\t\tif (field === 'createdAt' || field === 'updatedAt')\n\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'timestamp', data: { field } })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'component-boundaries': {\n\t\tmeta: metadata('Keep portable components isolated and consume their client facades.', {\n\t\t\tboundary:\n\t\t\t\t'Components may import only their own files and npm dependencies; inject host policy through the client API.',\n\t\t\tfacade: 'Import a component through its client.ts facade, not its private implementation.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst owner = componentRoot(context, context.filename)\n\t\t\tconst resolver = createResolver()\n\n\t\t\treturn sourceVisitors(context, (node, specifier) => {\n\t\t\t\tif (!specifier.startsWith('.')) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t/^cvx-kit\\/components\\/[^/]+\\//.test(specifier) &&\n\t\t\t\t\t\t!/^cvx-kit\\/components\\/[^/]+\\/convex\\.config(?:\\.js)?$/.test(specifier)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'facade' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst target = resolveImport(resolver, context.filename, specifier)\n\t\t\t\tif (!target || target.split(sep).includes('node_modules')) return\n\t\t\t\tif (\n\t\t\t\t\towner &&\n\t\t\t\t\t(relative(owner, target).startsWith(`..${sep}`) || target === dirname(owner))\n\t\t\t\t) {\n\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\n\t\t\t\t} else if (\n\t\t\t\t\t!owner &&\n\t\t\t\t\tcomponentRoot(context, target) &&\n\t\t\t\t\t!/^client(?:\\.[cm]?[jt]s)?$/.test(basename(target)) &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tappFile(context) === 'convex.config.ts' &&\n\t\t\t\t\t\t/^convex\\.config(?:\\.[cm]?[jt]s)?$/.test(basename(target))\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tcontext.report({ node, messageId: 'facade' })\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t},\n\t'no-component-env': {\n\t\tmeta: metadata('Pass environment configuration into portable components.', {\n\t\t\tenv: 'Portable components must receive configuration through arguments; do not read process.env.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!componentRoot(context, context.filename)) return {}\n\t\t\tfunction isProcess(node: ESTree.Node) {\n\t\t\t\tif (node.type !== 'Identifier') return false\n\t\t\t\tconst variable = binding(context, node)\n\t\t\t\tif (node.name === 'process' && !variable?.defs.length) return true\n\t\t\t\treturn variable?.defs.some(\n\t\t\t\t\t(def) =>\n\t\t\t\t\t\tdef.type === 'ImportBinding' &&\n\t\t\t\t\t\tdef.parent?.type === 'ImportDeclaration' &&\n\t\t\t\t\t\t['node:process', 'process'].includes(def.parent.source.value) &&\n\t\t\t\t\t\t['ImportDefaultSpecifier', 'ImportNamespaceSpecifier'].includes(def.node.type),\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (['node:process', 'process'].includes(node.source.value)) {\n\t\t\t\t\t\tfor (const specifier of node.specifiers) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tspecifier.type === 'ImportSpecifier' &&\n\t\t\t\t\t\t\t\texportName(specifier.imported) === 'env'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'env' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tMemberExpression(node) {\n\t\t\t\t\tif (propertyName(node) !== 'env') return\n\t\t\t\t\tconst object = node.object\n\t\t\t\t\tconst globalProcess =\n\t\t\t\t\t\tobject.type === 'MemberExpression' &&\n\t\t\t\t\t\tpropertyName(object) === 'process' &&\n\t\t\t\t\t\tobject.object.type === 'Identifier' &&\n\t\t\t\t\t\tobject.object.name === 'globalThis' &&\n\t\t\t\t\t\t!binding(context, object.object)?.defs.length\n\t\t\t\t\tif (isProcess(object) || globalProcess) context.report({ node, messageId: 'env' })\n\t\t\t\t},\n\t\t\t\tVariableDeclarator(node) {\n\t\t\t\t\tif (node.init && isProcess(node.init) && node.id.type === 'ObjectPattern') {\n\t\t\t\t\t\tfor (const property of node.id.properties) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tproperty.type === 'Property' &&\n\t\t\t\t\t\t\t\t(property.computed ? staticString(property.key) : exportName(property.key)) ===\n\t\t\t\t\t\t\t\t\t'env'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'env' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'schema-file-boundaries': {\n\t\tmeta: metadata('Keep storage shapes and indexes in their documented owners.', {\n\t\t\tschema: '{{builder}} belongs in {{file}}.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tconst file = appFile(context)\n\t\t\t\t\tif (!file) return\n\t\t\t\t\tlet expected: string | null = null\n\t\t\t\t\tif (ref?.source === 'convex/server') {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tref.name === 'defineSchema' &&\n\t\t\t\t\t\t\tfile !== 'schema.ts' &&\n\t\t\t\t\t\t\t!(componentRoot(context, context.filename) && basename(file) === 'schema.ts')\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\texpected = 'the root schema.ts (or a component schema.ts)'\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tref.name === 'defineTable' &&\n\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\tcomponentRoot(context, context.filename) &&\n\t\t\t\t\t\t\t\t['schema.ts', 'table.ts'].includes(basename(file))\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\texpected =\n\t\t\t\t\t\t\t\t'a component schema.ts/table.ts; host tables use zodTable in domain/<module>/schema.ts'\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\t['cvx-kit', 'cvx-kit/zod-table'].includes(ref.source) &&\n\t\t\t\t\t\t['zodTable', 'tenantTable', 'zodVariantTable'].includes(ref.name) &&\n\t\t\t\t\t\t!/^domain\\/[^/]+\\/schema\\.ts$/.test(file)\n\t\t\t\t\t)\n\t\t\t\t\t\texpected = 'domain/<module>/schema.ts'\n\t\t\t\t\tif (expected)\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'schema',\n\t\t\t\t\t\t\tdata: { builder: ref?.name ?? 'table', file: expected },\n\t\t\t\t\t\t})\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression' &&\n\t\t\t\t\t\t['index', 'searchIndex', 'vectorIndex'].includes(propertyName(node.callee) ?? '') &&\n\t\t\t\t\t\tisTableChain(node.callee.object) &&\n\t\t\t\t\t\t!componentRoot(context, context.filename) &&\n\t\t\t\t\t\t!/^domain\\/[^/]+\\/table\\.ts$/.test(file)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'schema',\n\t\t\t\t\t\t\tdata: { builder: 'Table indexes', file: 'domain/<module>/table.ts' },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-internal-reexports': {\n\t\tmeta: metadata('Import from owning modules directly; consumer apps have no barrels.', {\n\t\t\treexport: 'Import from the owning file directly; do not re-export imports inside convex/.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tfunction check(node: ESTree.ExportNamedDeclaration | ESTree.ExportAllDeclaration) {\n\t\t\t\tif (node.source) {\n\t\t\t\t\tcontext.report({ node, messageId: 'reexport' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor (const specifier of node.type === 'ExportNamedDeclaration' ? node.specifiers : []) {\n\t\t\t\t\tif (binding(context, specifier.local)?.defs.some((def) => def.type === 'ImportBinding')) {\n\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'reexport' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tExportNamedDeclaration: check,\n\t\t\t\tExportAllDeclaration: check,\n\t\t\t\tExportDefaultDeclaration(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.declaration.type === 'Identifier' &&\n\t\t\t\t\t\tbinding(context, node.declaration)?.defs.some((def) => def.type === 'ImportBinding')\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'reexport' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'public-api-first': {\n\t\tmeta: metadata('Place private helper implementations after the exported API.', {\n\t\t\torder:\n\t\t\t\t'Move this private helper below the exported API so the file reads from public purpose to implementation.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exports = exportedNames(node)\n\t\t\t\t\tconst lastExport = node.body.findLastIndex((statement) =>\n\t\t\t\t\t\tstatement.type.startsWith('Export'),\n\t\t\t\t\t)\n\t\t\t\t\tfor (const statement of node.body.slice(0, lastExport < 0 ? 0 : lastExport)) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tstatement.type === 'FunctionDeclaration' &&\n\t\t\t\t\t\t\tstatement.id &&\n\t\t\t\t\t\t\t!exports.has(statement.id.name)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontext.report({ node: statement, messageId: 'order' })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'named-private-helpers': {\n\t\tmeta: metadata('Use named declarations for top-level private helper functions.', {\n\t\t\thelper: 'Declare this private helper with function name(...) at the bottom of the file.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exports = exportedNames(node)\n\t\t\t\t\tfor (const statement of node.body) {\n\t\t\t\t\t\tif (statement.type !== 'VariableDeclaration') continue\n\t\t\t\t\t\tfor (const declaration of statement.declarations) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tdeclaration.id.type === 'Identifier' &&\n\t\t\t\t\t\t\t\t!exports.has(declaration.id.name) &&\n\t\t\t\t\t\t\t\t(declaration.init?.type === 'ArrowFunctionExpression' ||\n\t\t\t\t\t\t\t\t\tdeclaration.init?.type === 'FunctionExpression')\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: declaration, messageId: 'helper' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n\nexport const rules: Record<RuleName, Rule> = scopeRules(applicationRules)\nconst plugin: Plugin = { meta: { name: 'cvx' }, rules }\nexport default plugin\n\nfunction scopeRules(source: Record<RuleName, CreateRule>): Record<RuleName, Rule> {\n\tconst scoped = { ...source }\n\t// SAFETY: source is the closed applicationRules record; its own keys are exactly RuleName.\n\tconst names = Object.keys(source) as RuleName[]\n\tfor (const name of names) {\n\t\tconst rule = source[name]\n\t\tscoped[name] = {\n\t\t\tmeta: {\n\t\t\t\t...rule.meta,\n\t\t\t\tschema: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'object',\n\t\t\t\t\t\tproperties: { convexDir: { type: 'string', minLength: 1 } },\n\t\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\tcreate(context) {\n\t\t\t\tconst file = appFile(context)\n\t\t\t\tif (\n\t\t\t\t\t!file ||\n\t\t\t\t\t/(?:^|\\/)(?:_generated|__tests__)(?:\\/|$)/.test(file) ||\n\t\t\t\t\t/\\.(?:test|spec)\\.[cm]?[jt]s$/.test(file)\n\t\t\t\t)\n\t\t\t\t\treturn {}\n\t\t\t\treturn rule.create(context)\n\t\t\t},\n\t\t}\n\t}\n\treturn scoped\n}\n"],"mappings":"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"}
|
package/dist/state-machine.d.mts
CHANGED
|
@@ -6,7 +6,7 @@ import { ErrorFactory } from "./errors.mjs";
|
|
|
6
6
|
* invalid literal cannot compile; `assert` throws via the injected
|
|
7
7
|
* ErrorFactory so it drops straight into a command guard.
|
|
8
8
|
*/
|
|
9
|
-
declare function createStateMachine<const State extends string>(states: readonly State[], transitions: Partial<Record<NoInfer<State>, readonly NoInfer<State>[]>>, options?: {
|
|
9
|
+
export declare function createStateMachine<const State extends string>(states: readonly State[], transitions: Partial<Record<NoInfer<State>, readonly NoInfer<State>[]>>, options?: {
|
|
10
10
|
errors?: ErrorFactory;
|
|
11
11
|
}): Readonly<{
|
|
12
12
|
states: readonly State[];
|
|
@@ -14,5 +14,4 @@ declare function createStateMachine<const State extends string>(states: readonly
|
|
|
14
14
|
assert: (from: State, to: State) => void;
|
|
15
15
|
}>;
|
|
16
16
|
//#endregion
|
|
17
|
-
export { createStateMachine };
|
|
18
17
|
//# sourceMappingURL=state-machine.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-machine.mjs","names":[],"sources":["../src/state-machine.ts"],"sourcesContent":["import { defaultErrors, type ErrorFactory } from './errors'\n\n/**\n * Typed transition legality from a constants tuple (the conventions.md\n * vocabulary pattern). The transition map is typed against the tuple, so an\n * invalid literal cannot compile; `assert` throws via the injected\n * ErrorFactory so it drops straight into a command guard.\n */\nexport function createStateMachine<const State extends string>(\n\tstates: readonly State[],\n\t// NoInfer: the vocabulary comes from the tuple; a stray literal in the\n\t// transition map must fail to compile, not widen the state union.\n\ttransitions: Partial<Record<NoInfer<State>, readonly NoInfer<State>[]>>,\n\toptions?: { errors?: ErrorFactory },\n) {\n\tconst errors = options?.errors ?? defaultErrors\n\tconst legal = new Map<State, ReadonlySet<State>>(\n\t\tstates.map((state) => [state, new Set(transitions[state] ?? [])]),\n\t)\n\n\tfunction can(from: State, to: State): boolean {\n\t\treturn legal.get(from)?.has(to) ?? false\n\t}\n\n\tfunction assert(from: State, to: State): void {\n\t\tif (!can(from, to)) {\n\t\t\terrors.throw({\n\t\t\t\tcode: 'INVALID_TRANSITION',\n\t\t\t\tmessage: `Transition \"${from}\" → \"${to}\" is not legal`,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn Object.freeze({ states, can, assert })\n}\n"],"mappings":"6CAQA,
|
|
1
|
+
{"version":3,"file":"state-machine.mjs","names":["createStateMachine","errors","options","defaultErrors","legal","states","state","transitions","can","from","to","assert"],"sources":["../src/state-machine.ts"],"sourcesContent":["import { defaultErrors, type ErrorFactory } from './errors'\n\n/**\n * Typed transition legality from a constants tuple (the conventions.md\n * vocabulary pattern). The transition map is typed against the tuple, so an\n * invalid literal cannot compile; `assert` throws via the injected\n * ErrorFactory so it drops straight into a command guard.\n */\nexport function createStateMachine<const State extends string>(\n\tstates: readonly State[],\n\t// NoInfer: the vocabulary comes from the tuple; a stray literal in the\n\t// transition map must fail to compile, not widen the state union.\n\ttransitions: Partial<Record<NoInfer<State>, readonly NoInfer<State>[]>>,\n\toptions?: { errors?: ErrorFactory },\n) {\n\tconst errors = options?.errors ?? defaultErrors\n\tconst legal = new Map<State, ReadonlySet<State>>(\n\t\tstates.map((state) => [state, new Set(transitions[state] ?? [])]),\n\t)\n\n\tfunction can(from: State, to: State): boolean {\n\t\treturn legal.get(from)?.has(to) ?? false\n\t}\n\n\tfunction assert(from: State, to: State): void {\n\t\tif (!can(from, to)) {\n\t\t\terrors.throw({\n\t\t\t\tcode: 'INVALID_TRANSITION',\n\t\t\t\tmessage: `Transition \"${from}\" → \"${to}\" is not legal`,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn Object.freeze({ states, can, assert })\n}\n"],"mappings":"6CAQA,SAAgBA,EACf,EAGA,EACA,EACC,CACD,IAAMC,EAASC,GAAS,QAAUC,EAC5BC,EAAQ,IAAI,IACjBC,EAAO,IAAK,GAAU,CAACC,EAAO,IAAI,IAAIC,EAAYD,IAAU,CAAC,CAAC,CAAC,CAAC,CACjE,EAEA,SAASE,EAAI,EAAa,EAAoB,CAC7C,OAAOJ,EAAM,IAAIK,CAAI,CAAC,EAAE,IAAIC,CAAE,GAAK,EACpC,CAEA,SAASC,EAAO,EAAa,EAAiB,CACxCH,EAAIC,EAAMC,CAAE,GAChBT,EAAO,MAAM,CACZ,KAAM,qBACN,QAAS,eAAeQ,EAAK,OAAOC,EAAG,eACxC,CAAC,CAEH,CAEA,OAAO,OAAO,OAAO,CAAE,SAAQ,MAAK,QAAO,CAAC,CAC7C"}
|
package/dist/tenancy.d.mts
CHANGED
|
@@ -3,32 +3,32 @@ import { GenericDataModel, TableNamesInDataModel } from "convex/server";
|
|
|
3
3
|
import { RLSConfig, Rules, wrapDatabaseReader, wrapDatabaseWriter } from "convex-helpers/server/rowLevelSecurity";
|
|
4
4
|
//#region src/tenancy.d.ts
|
|
5
5
|
/** The server-owned tenancy field stamped on every tenant table row. */
|
|
6
|
-
declare const TENANT_FIELD: 'tenant';
|
|
6
|
+
export declare const TENANT_FIELD: 'tenant';
|
|
7
7
|
/**
|
|
8
8
|
* Deny-oriented row-level security rules generated mechanically from a table
|
|
9
9
|
* registry: read/insert/modify pass only when the row's tenant matches.
|
|
10
10
|
* Registering a table here is the entire onboarding — there is no second
|
|
11
11
|
* per-table step to forget.
|
|
12
12
|
*/
|
|
13
|
-
declare function createTenantRules<DataModel extends GenericDataModel>(tenant: string, tables: readonly TableNamesInDataModel<DataModel>[]): Rules<unknown, DataModel>;
|
|
13
|
+
export declare function createTenantRules<DataModel extends GenericDataModel>(tenant: string, tables: readonly TableNamesInDataModel<DataModel>[]): Rules<unknown, DataModel>;
|
|
14
14
|
/**
|
|
15
15
|
* AND-composes rule sets: a row passes an operation only when every rule set
|
|
16
16
|
* that defines a rule for it passes. Lets tenant isolation and role-level
|
|
17
17
|
* rules coexist on the same wrapped database.
|
|
18
18
|
*/
|
|
19
|
-
declare function composeRules<Ctx, DataModel extends GenericDataModel>(...sets: readonly Rules<Ctx, DataModel>[]): Rules<Ctx, DataModel>;
|
|
19
|
+
export declare function composeRules<Ctx, DataModel extends GenericDataModel>(...sets: readonly Rules<Ctx, DataModel>[]): Rules<Ctx, DataModel>;
|
|
20
20
|
/**
|
|
21
21
|
* Loads a client-referenced document and verifies tenant ownership without
|
|
22
22
|
* disclosing existence: a missing row and a foreign row fail identically
|
|
23
23
|
* with REFERENCE_NOT_FOUND. Use for every id argument a client supplies.
|
|
24
24
|
*/
|
|
25
|
-
declare function requireTenantReference<Document extends {
|
|
25
|
+
export declare function requireTenantReference<Document extends {
|
|
26
26
|
[TENANT_FIELD]?: unknown;
|
|
27
27
|
}>(tenant: string, load: () => Promise<Document | null>, errors?: ErrorFactory): Promise<Document>;
|
|
28
28
|
/** Asserts a loaded document belongs to the tenant; use on internal paths. */
|
|
29
|
-
declare function assertTenantOwned<Document extends {
|
|
29
|
+
export declare function assertTenantOwned<Document extends {
|
|
30
30
|
[TENANT_FIELD]?: unknown;
|
|
31
31
|
}>(tenant: string, document: Document, errors?: ErrorFactory): Document;
|
|
32
32
|
//#endregion
|
|
33
|
-
export { type RLSConfig, type Rules,
|
|
33
|
+
export { type RLSConfig, type Rules, wrapDatabaseReader, wrapDatabaseWriter };
|
|
34
34
|
//# sourceMappingURL=tenancy.d.mts.map
|
package/dist/tenancy.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenancy.mjs","names":[],"sources":["../src/tenancy.ts"],"sourcesContent":["import {\n\twrapDatabaseReader,\n\twrapDatabaseWriter,\n\ttype RLSConfig,\n\ttype Rules,\n} from 'convex-helpers/server/rowLevelSecurity'\nimport type { GenericDataModel, TableNamesInDataModel } from 'convex/server'\nimport { defaultErrors, type ErrorFactory } from './errors'\n\nexport { wrapDatabaseReader, wrapDatabaseWriter }\nexport type { RLSConfig, Rules }\n\n/** The server-owned tenancy field stamped on every tenant table row. */\nexport const TENANT_FIELD = 'tenant' as const\n\n/**\n * Deny-oriented row-level security rules generated mechanically from a table\n * registry: read/insert/modify pass only when the row's tenant matches.\n * Registering a table here is the entire onboarding — there is no second\n * per-table step to forget.\n */\nexport function createTenantRules<DataModel extends GenericDataModel>(\n\ttenant: string,\n\ttables: readonly TableNamesInDataModel<DataModel>[],\n): Rules<unknown, DataModel> {\n\tconst rules: Rules<unknown, DataModel> = {}\n\tfor (const table of tables) {\n\t\trules[table] = {\n\t\t\tread: async (_context, document) => document[TENANT_FIELD] === tenant,\n\t\t\tinsert: async (_context, document) => document[TENANT_FIELD] === tenant,\n\t\t\tmodify: async (_context, document) => document[TENANT_FIELD] === tenant,\n\t\t}\n\t}\n\treturn rules\n}\n\n/**\n * AND-composes rule sets: a row passes an operation only when every rule set\n * that defines a rule for it passes. Lets tenant isolation and role-level\n * rules coexist on the same wrapped database.\n */\nexport function composeRules<Ctx, DataModel extends GenericDataModel>(\n\t...sets: readonly Rules<Ctx, DataModel>[]\n): Rules<Ctx, DataModel> {\n\t// SAFETY: Rules keys are table names from DataModel; values come only from the supplied rule sets.\n\tconst tables = new Set(sets.flatMap((set) => Object.keys(set))) as Set<\n\t\tTableNamesInDataModel<DataModel>\n\t>\n\tconst composed: Rules<Ctx, DataModel> = {}\n\tfor (const table of tables) {\n\t\tconst reads = sets.flatMap((set) => set[table]?.read ?? [])\n\t\tconst inserts = sets.flatMap((set) => set[table]?.insert ?? [])\n\t\tconst modifies = sets.flatMap((set) => set[table]?.modify ?? [])\n\t\tconst rules: NonNullable<Rules<Ctx, DataModel>[typeof table]> = {}\n\t\tif (reads.length)\n\t\t\trules.read = async (context, document) => {\n\t\t\t\tfor (const rule of reads) if (!(await rule(context, document))) return false\n\t\t\t\treturn true\n\t\t\t}\n\t\tif (inserts.length)\n\t\t\trules.insert = async (context, document) => {\n\t\t\t\tfor (const rule of inserts) if (!(await rule(context, document))) return false\n\t\t\t\treturn true\n\t\t\t}\n\t\tif (modifies.length)\n\t\t\trules.modify = async (context, document) => {\n\t\t\t\tfor (const rule of modifies) if (!(await rule(context, document))) return false\n\t\t\t\treturn true\n\t\t\t}\n\t\tcomposed[table] = rules\n\t}\n\treturn composed\n}\n\n/**\n * Loads a client-referenced document and verifies tenant ownership without\n * disclosing existence: a missing row and a foreign row fail identically\n * with REFERENCE_NOT_FOUND. Use for every id argument a client supplies.\n */\nexport async function requireTenantReference<Document extends { [TENANT_FIELD]?: unknown }>(\n\ttenant: string,\n\tload: () => Promise<Document | null>,\n\terrors: ErrorFactory = defaultErrors,\n): Promise<Document> {\n\tconst document = await load()\n\tif (!document || document[TENANT_FIELD] !== tenant) {\n\t\treturn errors.throw({\n\t\t\tcode: 'REFERENCE_NOT_FOUND',\n\t\t\tmessage: 'The referenced row is unavailable',\n\t\t})\n\t}\n\treturn document\n}\n\n/** Asserts a loaded document belongs to the tenant; use on internal paths. */\nexport function assertTenantOwned<Document extends { [TENANT_FIELD]?: unknown }>(\n\ttenant: string,\n\tdocument: Document,\n\terrors: ErrorFactory = defaultErrors,\n): Document {\n\tif (document[TENANT_FIELD] !== tenant) {\n\t\treturn errors.throw({\n\t\t\tcode: 'CROSS_TENANT_REFERENCE',\n\t\t\tmessage: 'The document belongs to another tenant',\n\t\t})\n\t}\n\treturn document\n}\n"],"mappings":"iJAaA,
|
|
1
|
+
{"version":3,"file":"tenancy.mjs","names":["TENANT_FIELD","createTenantRules","rules","table","tables","document","tenant","composeRules","sets","set","composed","reads","inserts","modifies","rule","context","requireTenantReference","defaultErrors","load","\"tenant\"","errors","assertTenantOwned","wrapDatabaseReader","wrapDatabaseWriter"],"sources":["../src/tenancy.ts"],"sourcesContent":["import {\n\twrapDatabaseReader,\n\twrapDatabaseWriter,\n\ttype RLSConfig,\n\ttype Rules,\n} from 'convex-helpers/server/rowLevelSecurity'\nimport type { GenericDataModel, TableNamesInDataModel } from 'convex/server'\nimport { defaultErrors, type ErrorFactory } from './errors'\n\nexport { wrapDatabaseReader, wrapDatabaseWriter }\nexport type { RLSConfig, Rules }\n\n/** The server-owned tenancy field stamped on every tenant table row. */\nexport const TENANT_FIELD = 'tenant' as const\n\n/**\n * Deny-oriented row-level security rules generated mechanically from a table\n * registry: read/insert/modify pass only when the row's tenant matches.\n * Registering a table here is the entire onboarding — there is no second\n * per-table step to forget.\n */\nexport function createTenantRules<DataModel extends GenericDataModel>(\n\ttenant: string,\n\ttables: readonly TableNamesInDataModel<DataModel>[],\n): Rules<unknown, DataModel> {\n\tconst rules: Rules<unknown, DataModel> = {}\n\tfor (const table of tables) {\n\t\trules[table] = {\n\t\t\tread: async (_context, document) => document[TENANT_FIELD] === tenant,\n\t\t\tinsert: async (_context, document) => document[TENANT_FIELD] === tenant,\n\t\t\tmodify: async (_context, document) => document[TENANT_FIELD] === tenant,\n\t\t}\n\t}\n\treturn rules\n}\n\n/**\n * AND-composes rule sets: a row passes an operation only when every rule set\n * that defines a rule for it passes. Lets tenant isolation and role-level\n * rules coexist on the same wrapped database.\n */\nexport function composeRules<Ctx, DataModel extends GenericDataModel>(\n\t...sets: readonly Rules<Ctx, DataModel>[]\n): Rules<Ctx, DataModel> {\n\t// SAFETY: Rules keys are table names from DataModel; values come only from the supplied rule sets.\n\tconst tables = new Set(sets.flatMap((set) => Object.keys(set))) as Set<\n\t\tTableNamesInDataModel<DataModel>\n\t>\n\tconst composed: Rules<Ctx, DataModel> = {}\n\tfor (const table of tables) {\n\t\tconst reads = sets.flatMap((set) => set[table]?.read ?? [])\n\t\tconst inserts = sets.flatMap((set) => set[table]?.insert ?? [])\n\t\tconst modifies = sets.flatMap((set) => set[table]?.modify ?? [])\n\t\tconst rules: NonNullable<Rules<Ctx, DataModel>[typeof table]> = {}\n\t\tif (reads.length)\n\t\t\trules.read = async (context, document) => {\n\t\t\t\tfor (const rule of reads) if (!(await rule(context, document))) return false\n\t\t\t\treturn true\n\t\t\t}\n\t\tif (inserts.length)\n\t\t\trules.insert = async (context, document) => {\n\t\t\t\tfor (const rule of inserts) if (!(await rule(context, document))) return false\n\t\t\t\treturn true\n\t\t\t}\n\t\tif (modifies.length)\n\t\t\trules.modify = async (context, document) => {\n\t\t\t\tfor (const rule of modifies) if (!(await rule(context, document))) return false\n\t\t\t\treturn true\n\t\t\t}\n\t\tcomposed[table] = rules\n\t}\n\treturn composed\n}\n\n/**\n * Loads a client-referenced document and verifies tenant ownership without\n * disclosing existence: a missing row and a foreign row fail identically\n * with REFERENCE_NOT_FOUND. Use for every id argument a client supplies.\n */\nexport async function requireTenantReference<Document extends { [TENANT_FIELD]?: unknown }>(\n\ttenant: string,\n\tload: () => Promise<Document | null>,\n\terrors: ErrorFactory = defaultErrors,\n): Promise<Document> {\n\tconst document = await load()\n\tif (!document || document[TENANT_FIELD] !== tenant) {\n\t\treturn errors.throw({\n\t\t\tcode: 'REFERENCE_NOT_FOUND',\n\t\t\tmessage: 'The referenced row is unavailable',\n\t\t})\n\t}\n\treturn document\n}\n\n/** Asserts a loaded document belongs to the tenant; use on internal paths. */\nexport function assertTenantOwned<Document extends { [TENANT_FIELD]?: unknown }>(\n\ttenant: string,\n\tdocument: Document,\n\terrors: ErrorFactory = defaultErrors,\n): Document {\n\tif (document[TENANT_FIELD] !== tenant) {\n\t\treturn errors.throw({\n\t\t\tcode: 'CROSS_TENANT_REFERENCE',\n\t\t\tmessage: 'The document belongs to another tenant',\n\t\t})\n\t}\n\treturn document\n}\n"],"mappings":"iJAaA,MAAaA,EAAe,SAQ5B,SAAgBC,EACf,EACA,EAC4B,CAC5B,IAAMC,EAAmC,CAAC,EAC1C,IAAK,IAAMC,KAASC,EACnB,EAAMD,GAAS,CACd,KAAM,MAAO,EAAU,IAAaE,EAASL,KAAkBM,EAC/D,OAAQ,MAAO,EAAU,IAAaD,EAASL,KAAkBM,EACjE,OAAQ,MAAO,EAAU,IAAaD,EAASL,KAAkBM,CAClE,EAED,OAAOJ,CACR,CAOA,SAAgBK,EACf,GAAGC,EACqB,CAExB,IAAMJ,EAAS,IAAI,IAAII,EAAK,QAAS,GAAQ,OAAO,KAAKC,CAAG,CAAC,CAAC,EAGxDC,EAAkC,CAAC,EACzC,IAAK,IAAMP,KAASC,EAAQ,CAC3B,IAAMO,EAAQH,EAAK,QAAS,GAAQC,EAAIN,EAAM,EAAE,MAAQ,CAAC,CAAC,EACpDS,EAAUJ,EAAK,QAAS,GAAQC,EAAIN,EAAM,EAAE,QAAU,CAAC,CAAC,EACxDU,EAAWL,EAAK,QAAS,GAAQC,EAAIN,EAAM,EAAE,QAAU,CAAC,CAAC,EACzDD,EAA0D,CAAC,EAC7DS,EAAM,SACT,EAAM,KAAO,MAAO,EAAS,IAAa,CACzC,IAAK,IAAMG,KAAQH,EAAO,GAAI,CAAE,MAAMG,EAAKC,EAASV,CAAQ,EAAI,MAAO,GACvE,MAAO,EACR,GACGO,EAAQ,SACX,EAAM,OAAS,MAAO,EAAS,IAAa,CAC3C,IAAK,IAAME,KAAQF,EAAS,GAAI,CAAE,MAAME,EAAKC,EAASV,CAAQ,EAAI,MAAO,GACzE,MAAO,EACR,GACGQ,EAAS,SACZ,EAAM,OAAS,MAAO,EAAS,IAAa,CAC3C,IAAK,IAAMC,KAAQD,EAAU,GAAI,CAAE,MAAMC,EAAKC,EAASV,CAAQ,EAAI,MAAO,GAC1E,MAAO,EACR,GACD,EAASF,GAASD,CACnB,CACA,OAAOQ,CACR,CAOA,eAAsBM,EACrB,EACA,EACA,EAAuBC,EACH,CACpB,IAAMZ,EAAW,MAAMa,EAAK,EAO5B,MANI,CAACb,GAAYA,EAAAc,SAA2Bb,EACpCc,EAAO,MAAM,CACnB,KAAM,sBACN,QAAS,mCACV,CAAC,EAEKf,CACR,CAGA,SAAgBgB,EACf,EACA,EACA,EAAuBJ,EACZ,CAOX,OANIZ,EAAAc,SAA2Bb,EAMxBD,EALCe,EAAO,MAAM,CACnB,KAAM,yBACN,QAAS,wCACV,CAAC,CAGH"}
|
package/dist/triggers.d.mts
CHANGED
|
@@ -7,14 +7,14 @@ import { Change, Trigger, Triggers } from "convex-helpers/server/triggers";
|
|
|
7
7
|
* systemMutation runs writes through it — trigger enforcement becomes
|
|
8
8
|
* structural, not a convention.
|
|
9
9
|
*/
|
|
10
|
-
declare function createTriggers<DataModel extends GenericDataModel>(): Triggers<DataModel>;
|
|
10
|
+
export declare function createTriggers<DataModel extends GenericDataModel>(): Triggers<DataModel>;
|
|
11
11
|
/**
|
|
12
12
|
* Marks tables as append-only evidence tables: inserts pass, every update or
|
|
13
13
|
* delete throws. The ontology pattern for votes/history/audit-style tables.
|
|
14
14
|
*/
|
|
15
|
-
declare function appendOnly<DataModel extends GenericDataModel>(triggers: Triggers<DataModel>, ...tables: readonly TableNamesInDataModel<DataModel>[]): void;
|
|
15
|
+
export declare function appendOnly<DataModel extends GenericDataModel>(triggers: Triggers<DataModel>, ...tables: readonly TableNamesInDataModel<DataModel>[]): void;
|
|
16
16
|
/** Forbids hard deletes on the given tables (soft-delete discipline). */
|
|
17
|
-
declare function noDelete<DataModel extends GenericDataModel>(triggers: Triggers<DataModel>, ...tables: readonly TableNamesInDataModel<DataModel>[]): void;
|
|
17
|
+
export declare function noDelete<DataModel extends GenericDataModel>(triggers: Triggers<DataModel>, ...tables: readonly TableNamesInDataModel<DataModel>[]): void;
|
|
18
18
|
/**
|
|
19
19
|
* Post-image tenant invariant for tenantTable rows. Row-level security
|
|
20
20
|
* authorizes a modification against the PRE-image, so ownership must also be
|
|
@@ -22,14 +22,14 @@ declare function noDelete<DataModel extends GenericDataModel>(triggers: Triggers
|
|
|
22
22
|
* may never reassign it. Register for every tenant table (same registry that
|
|
23
23
|
* drives createTenantRules).
|
|
24
24
|
*/
|
|
25
|
-
declare function tenantOwnership<DataModel extends GenericDataModel>(triggers: Triggers<DataModel>, ...tables: readonly TableNamesInDataModel<DataModel>[]): void;
|
|
25
|
+
export declare function tenantOwnership<DataModel extends GenericDataModel>(triggers: Triggers<DataModel>, ...tables: readonly TableNamesInDataModel<DataModel>[]): void;
|
|
26
26
|
/**
|
|
27
27
|
* Maintains zod-table's opinionated lifecycle timestamps: createdAt and
|
|
28
28
|
* updatedAt on insert, updatedAt on every update. archivedAt stays under
|
|
29
29
|
* application control. Register once per table; writes made through the
|
|
30
30
|
* auth constructors keep these fields correct automatically.
|
|
31
31
|
*/
|
|
32
|
-
declare function timestamps<DataModel extends GenericDataModel>(triggers: Triggers<DataModel>, ...tables: readonly TableNamesInDataModel<DataModel>[]): void;
|
|
32
|
+
export declare function timestamps<DataModel extends GenericDataModel>(triggers: Triggers<DataModel>, ...tables: readonly TableNamesInDataModel<DataModel>[]): void;
|
|
33
33
|
//#endregion
|
|
34
|
-
export { type Change, type Trigger, Triggers
|
|
34
|
+
export { type Change, type Trigger, Triggers };
|
|
35
35
|
//# sourceMappingURL=triggers.d.mts.map
|
package/dist/triggers.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"triggers.mjs","names":[],"sources":["../src/triggers.ts"],"sourcesContent":["import { z } from 'zod'\nimport { Triggers, type Change, type Trigger } from 'convex-helpers/server/triggers'\nimport type { GenericDataModel, TableNamesInDataModel } from 'convex/server'\n\nexport { Triggers }\nexport type { Change, Trigger }\n\n/**\n * One application-wide mutation interceptor registry. Pass it to\n * createAuthFunctions({ triggers }) so every authMutation/roleMutation/\n * systemMutation runs writes through it — trigger enforcement becomes\n * structural, not a convention.\n */\nexport function createTriggers<DataModel extends GenericDataModel>(): Triggers<DataModel> {\n\treturn new Triggers<DataModel>()\n}\n\n/**\n * Marks tables as append-only evidence tables: inserts pass, every update or\n * delete throws. The ontology pattern for votes/history/audit-style tables.\n */\nexport function appendOnly<DataModel extends GenericDataModel>(\n\ttriggers: Triggers<DataModel>,\n\t...tables: readonly TableNamesInDataModel<DataModel>[]\n) {\n\tfor (const table of tables) {\n\t\ttriggers.register(table, async (_ctx, change) => {\n\t\t\tif (change.operation !== 'insert') {\n\t\t\t\tthrow new Error(`${table} is append-only`)\n\t\t\t}\n\t\t})\n\t}\n}\n\n/** Forbids hard deletes on the given tables (soft-delete discipline). */\nexport function noDelete<DataModel extends GenericDataModel>(\n\ttriggers: Triggers<DataModel>,\n\t...tables: readonly TableNamesInDataModel<DataModel>[]\n) {\n\tfor (const table of tables) {\n\t\ttriggers.register(table, async (_ctx, change) => {\n\t\t\tif (change.operation === 'delete') {\n\t\t\t\tthrow new Error(`${table} rows cannot be deleted`)\n\t\t\t}\n\t\t})\n\t}\n}\n\n/**\n * Post-image tenant invariant for tenantTable rows. Row-level security\n * authorizes a modification against the PRE-image, so ownership must also be\n * enforced against the post-image: inserts must carry a tenant, and updates\n * may never reassign it. Register for every tenant table (same registry that\n * drives createTenantRules).\n */\nexport function tenantOwnership<DataModel extends GenericDataModel>(\n\ttriggers: Triggers<DataModel>,\n\t...tables: readonly TableNamesInDataModel<DataModel>[]\n) {\n\tfor (const table of tables) {\n\t\ttriggers.register(table, async (_ctx, change) => {\n\t\t\tif (change.operation === 'insert') {\n\t\t\t\tconst ownership = tenantDocument.safeParse(change.newDoc)\n\t\t\t\tif (!ownership.success) {\n\t\t\t\t\tthrow new Error(`${table} rows require a tenant`)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (change.operation === 'update') {\n\t\t\t\tconst previous = change.oldDoc\n\t\t\t\tconst current = change.newDoc\n\t\t\t\tif (previous.tenant !== current.tenant) {\n\t\t\t\t\tthrow new Error(`${table} tenant ownership cannot be reassigned`)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n/**\n * Maintains zod-table's opinionated lifecycle timestamps: createdAt and\n * updatedAt on insert, updatedAt on every update. archivedAt stays under\n * application control. Register once per table; writes made through the\n * auth constructors keep these fields correct automatically.\n */\nexport function timestamps<DataModel extends GenericDataModel>(\n\ttriggers: Triggers<DataModel>,\n\t...tables: readonly TableNamesInDataModel<DataModel>[]\n) {\n\tfor (const table of tables) {\n\t\ttriggers.register(table, async (ctx, change) => {\n\t\t\tif (change.operation === 'delete') return\n\t\t\tconst now = Date.now()\n\t\t\tif (change.operation === 'insert') {\n\t\t\t\tconst document = change.newDoc\n\t\t\t\tif (document.createdAt === undefined) {\n\t\t\t\t\t// SAFETY: registration requires zod-table lifecycle fields; Convex validates this numeric timestamp patch.\n\t\t\t\t\tawait ctx.innerDb.patch(change.id, {\n\t\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t} as never)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst previous = change.oldDoc\n\t\t\tconst current = change.newDoc\n\t\t\t// A write that already moved updatedAt (including our own patch)\n\t\t\t// is left alone — this is the recursion guard.\n\t\t\tif (current.updatedAt === previous.updatedAt) {\n\t\t\t\t// SAFETY: registration requires zod-table lifecycle fields; Convex validates this numeric timestamp patch.\n\t\t\t\tawait ctx.innerDb.patch(change.id, { updatedAt: now } as never)\n\t\t\t}\n\t\t})\n\t}\n}\n\nconst tenantDocument = z.object({ tenant: z.string().min(1) })\n"],"mappings":"kFAaA,
|
|
1
|
+
{"version":3,"file":"triggers.mjs","names":["createTriggers","Triggers","appendOnly","tables","table","triggers","change","noDelete","tenantOwnership","tenantDocument","previous","current","timestamps","now","ctx","z"],"sources":["../src/triggers.ts"],"sourcesContent":["import { z } from 'zod'\nimport { Triggers, type Change, type Trigger } from 'convex-helpers/server/triggers'\nimport type { GenericDataModel, TableNamesInDataModel } from 'convex/server'\n\nexport { Triggers }\nexport type { Change, Trigger }\n\n/**\n * One application-wide mutation interceptor registry. Pass it to\n * createAuthFunctions({ triggers }) so every authMutation/roleMutation/\n * systemMutation runs writes through it — trigger enforcement becomes\n * structural, not a convention.\n */\nexport function createTriggers<DataModel extends GenericDataModel>(): Triggers<DataModel> {\n\treturn new Triggers<DataModel>()\n}\n\n/**\n * Marks tables as append-only evidence tables: inserts pass, every update or\n * delete throws. The ontology pattern for votes/history/audit-style tables.\n */\nexport function appendOnly<DataModel extends GenericDataModel>(\n\ttriggers: Triggers<DataModel>,\n\t...tables: readonly TableNamesInDataModel<DataModel>[]\n) {\n\tfor (const table of tables) {\n\t\ttriggers.register(table, async (_ctx, change) => {\n\t\t\tif (change.operation !== 'insert') {\n\t\t\t\tthrow new Error(`${table} is append-only`)\n\t\t\t}\n\t\t})\n\t}\n}\n\n/** Forbids hard deletes on the given tables (soft-delete discipline). */\nexport function noDelete<DataModel extends GenericDataModel>(\n\ttriggers: Triggers<DataModel>,\n\t...tables: readonly TableNamesInDataModel<DataModel>[]\n) {\n\tfor (const table of tables) {\n\t\ttriggers.register(table, async (_ctx, change) => {\n\t\t\tif (change.operation === 'delete') {\n\t\t\t\tthrow new Error(`${table} rows cannot be deleted`)\n\t\t\t}\n\t\t})\n\t}\n}\n\n/**\n * Post-image tenant invariant for tenantTable rows. Row-level security\n * authorizes a modification against the PRE-image, so ownership must also be\n * enforced against the post-image: inserts must carry a tenant, and updates\n * may never reassign it. Register for every tenant table (same registry that\n * drives createTenantRules).\n */\nexport function tenantOwnership<DataModel extends GenericDataModel>(\n\ttriggers: Triggers<DataModel>,\n\t...tables: readonly TableNamesInDataModel<DataModel>[]\n) {\n\tfor (const table of tables) {\n\t\ttriggers.register(table, async (_ctx, change) => {\n\t\t\tif (change.operation === 'insert') {\n\t\t\t\tconst ownership = tenantDocument.safeParse(change.newDoc)\n\t\t\t\tif (!ownership.success) {\n\t\t\t\t\tthrow new Error(`${table} rows require a tenant`)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (change.operation === 'update') {\n\t\t\t\tconst previous = change.oldDoc\n\t\t\t\tconst current = change.newDoc\n\t\t\t\tif (previous.tenant !== current.tenant) {\n\t\t\t\t\tthrow new Error(`${table} tenant ownership cannot be reassigned`)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n/**\n * Maintains zod-table's opinionated lifecycle timestamps: createdAt and\n * updatedAt on insert, updatedAt on every update. archivedAt stays under\n * application control. Register once per table; writes made through the\n * auth constructors keep these fields correct automatically.\n */\nexport function timestamps<DataModel extends GenericDataModel>(\n\ttriggers: Triggers<DataModel>,\n\t...tables: readonly TableNamesInDataModel<DataModel>[]\n) {\n\tfor (const table of tables) {\n\t\ttriggers.register(table, async (ctx, change) => {\n\t\t\tif (change.operation === 'delete') return\n\t\t\tconst now = Date.now()\n\t\t\tif (change.operation === 'insert') {\n\t\t\t\tconst document = change.newDoc\n\t\t\t\tif (document.createdAt === undefined) {\n\t\t\t\t\t// SAFETY: registration requires zod-table lifecycle fields; Convex validates this numeric timestamp patch.\n\t\t\t\t\tawait ctx.innerDb.patch(change.id, {\n\t\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t} as never)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst previous = change.oldDoc\n\t\t\tconst current = change.newDoc\n\t\t\t// A write that already moved updatedAt (including our own patch)\n\t\t\t// is left alone — this is the recursion guard.\n\t\t\tif (current.updatedAt === previous.updatedAt) {\n\t\t\t\t// SAFETY: registration requires zod-table lifecycle fields; Convex validates this numeric timestamp patch.\n\t\t\t\tawait ctx.innerDb.patch(change.id, { updatedAt: now } as never)\n\t\t\t}\n\t\t})\n\t}\n}\n\nconst tenantDocument = z.object({ tenant: z.string().min(1) })\n"],"mappings":"kFAaA,SAAgBA,GAA0E,CACzF,OAAO,IAAIC,CACZ,CAMA,SAAgBC,EACf,EACA,GAAGC,EACF,CACD,IAAK,IAAMC,KAASD,EACnBE,EAAS,SAASD,EAAO,MAAO,EAAM,IAAW,CAChD,GAAIE,EAAO,YAAc,SACxB,MAAU,MAAM,GAAGF,EAAM,gBAAgB,CAE3C,CAAC,CAEH,CAGA,SAAgBG,EACf,EACA,GAAGJ,EACF,CACD,IAAK,IAAMC,KAASD,EACnBE,EAAS,SAASD,EAAO,MAAO,EAAM,IAAW,CAChD,GAAIE,EAAO,YAAc,SACxB,MAAU,MAAM,GAAGF,EAAM,wBAAwB,CAEnD,CAAC,CAEH,CASA,SAAgBI,EACf,EACA,GAAGL,EACF,CACD,IAAK,IAAMC,KAASD,EACnBE,EAAS,SAASD,EAAO,MAAO,EAAM,IAAW,CAChD,GAAIE,EAAO,YAAc,SAAU,CAElC,GAAI,CADcG,EAAe,UAAUH,EAAO,MACrC,CAAC,CAAC,QACd,MAAU,MAAM,GAAGF,EAAM,uBAAuB,EAEjD,MACD,CACA,GAAIE,EAAO,YAAc,SAAU,CAClC,IAAMI,EAAWJ,EAAO,OAClBK,EAAUL,EAAO,OACvB,GAAII,EAAS,SAAWC,EAAQ,OAC/B,MAAU,MAAM,GAAGP,EAAM,uCAAuC,CAElE,CACD,CAAC,CAEH,CAQA,SAAgBQ,EACf,EACA,GAAGT,EACF,CACD,IAAK,IAAMC,KAASD,EACnBE,EAAS,SAASD,EAAO,MAAO,EAAK,IAAW,CAC/C,GAAIE,EAAO,YAAc,SAAU,OACnC,IAAMO,EAAM,KAAK,IAAI,EACrB,GAAIP,EAAO,YAAc,SAAU,CACjBA,EAAO,OACX,YAAc,IAAA,IAE1B,MAAMQ,EAAI,QAAQ,MAAMR,EAAO,GAAI,CAClC,UAAWO,EACX,UAAWA,CACZ,CAAU,EAEX,MACD,CACA,IAAMH,EAAWJ,EAAO,OACRA,EAAO,OAGX,YAAcI,EAAS,WAElC,MAAMI,EAAI,QAAQ,MAAMR,EAAO,GAAI,CAAE,UAAWO,CAAI,CAAU,CAEhE,CAAC,CAEH,CAEA,MAAMJ,EAAiBM,EAAE,OAAO,CAAE,OAAQA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAE,CAAC"}
|
package/dist/webhooks.d.mts
CHANGED
|
@@ -13,7 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
* longer than the provider's redelivery horizon (a cleanup cron over
|
|
14
14
|
* `receivedAt` — see docs/webhooks.md).
|
|
15
15
|
*/
|
|
16
|
-
declare function webhookEventsTable(): TableBoundary<"webhookEvents", {
|
|
16
|
+
export declare function webhookEventsTable(): TableBoundary<"webhookEvents", {
|
|
17
17
|
eventKey: z.ZodString;
|
|
18
18
|
source: z.ZodString;
|
|
19
19
|
receivedAt: z.ZodNumber;
|
|
@@ -25,14 +25,14 @@ type DedupContext = Pick<GenericMutationCtx<GenericDataModel>, 'db'>;
|
|
|
25
25
|
* mutation (transactional), never in the action. Returns duplicate: true
|
|
26
26
|
* when the natural key was already recorded; the caller then no-ops.
|
|
27
27
|
*/
|
|
28
|
-
declare function recordWebhookEvent(ctx: DedupContext, input: {
|
|
28
|
+
export declare function recordWebhookEvent(ctx: DedupContext, input: {
|
|
29
29
|
key: string;
|
|
30
30
|
source?: string;
|
|
31
31
|
table?: string;
|
|
32
32
|
}): Promise<{
|
|
33
33
|
duplicate: boolean;
|
|
34
34
|
}>;
|
|
35
|
-
type WebhookBoundaryConfig = {
|
|
35
|
+
export type WebhookBoundaryConfig = {
|
|
36
36
|
/**
|
|
37
37
|
* Verifies the provider signature over the RAW body bytes — the helper
|
|
38
38
|
* hands it `request.text()` BEFORE any parsing. Implementations must use
|
|
@@ -60,16 +60,15 @@ type WebhookBoundaryConfig = {
|
|
|
60
60
|
* handler: httpAction((ctx, request) =>
|
|
61
61
|
* boundary.handle(ctx, request, internal.domain.vendor.functions.applyEvent)) })
|
|
62
62
|
*/
|
|
63
|
-
declare function createWebhookBoundary(config: WebhookBoundaryConfig): {
|
|
63
|
+
export declare function createWebhookBoundary(config: WebhookBoundaryConfig): {
|
|
64
64
|
handle: <Result>(ctx: {
|
|
65
65
|
runMutation: (target: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>, args: WebhookDelivery) => Promise<Result>;
|
|
66
66
|
}, request: Request, target: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>) => Promise<Response>;
|
|
67
67
|
};
|
|
68
|
-
type WebhookDelivery = {
|
|
68
|
+
export type WebhookDelivery = {
|
|
69
69
|
eventKey: string;
|
|
70
70
|
payload: string;
|
|
71
71
|
source: string;
|
|
72
72
|
};
|
|
73
73
|
//#endregion
|
|
74
|
-
export { WebhookBoundaryConfig, WebhookDelivery, createWebhookBoundary, recordWebhookEvent, webhookEventsTable };
|
|
75
74
|
//# sourceMappingURL=webhooks.d.mts.map
|
package/dist/webhooks.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.mjs","names":[],"sources":["../src/webhooks.ts"],"sourcesContent":["import type { GenericMutationCtx, GenericDataModel, FunctionReference } from 'convex/server'\nimport { z } from 'zod'\nimport { defaultErrors, type ErrorFactory } from './errors'\nimport { zodTable } from './zod-table'\n\n/**\n * The host-owned dedup table (the kit gains no tables). Register it in the\n * schema with the by_eventKey index:\n *\n * webhookEvents: webhookEventsTable().table.index('by_eventKey', ['eventKey'])\n *\n * Rows are host-owned data: prune them on a retention window comfortably\n * longer than the provider's redelivery horizon (a cleanup cron over\n * `receivedAt` — see docs/webhooks.md).\n */\nexport function webhookEventsTable() {\n\treturn zodTable('webhookEvents', () => ({\n\t\teventKey: z.string(),\n\t\tsource: z.string(),\n\t\treceivedAt: z.number(),\n\t}))\n}\n\ntype DedupContext = Pick<GenericMutationCtx<GenericDataModel>, 'db'>\n\n/**\n * Transactional insert-if-absent dedup guard. Call this FIRST inside the\n * internal mutation the boundary delegates to — dedup must live in the\n * mutation (transactional), never in the action. Returns duplicate: true\n * when the natural key was already recorded; the caller then no-ops.\n */\nexport async function recordWebhookEvent(\n\tctx: DedupContext,\n\tinput: { key: string; source?: string; table?: string },\n): Promise<{ duplicate: boolean }> {\n\tconst table = input.table ?? 'webhookEvents'\n\tconst existing = await ctx.db\n\t\t.query(table)\n\t\t.withIndex('by_eventKey', (builder) => builder.eq('eventKey', input.key))\n\t\t.first()\n\tif (existing) return { duplicate: true }\n\tawait ctx.db.insert(table, {\n\t\teventKey: input.key,\n\t\tsource: input.source ?? 'unknown',\n\t\treceivedAt: Date.now(),\n\t})\n\treturn { duplicate: false }\n}\n\nexport type WebhookBoundaryConfig = {\n\t/**\n\t * Verifies the provider signature over the RAW body bytes — the helper\n\t * hands it `request.text()` BEFORE any parsing. Implementations must use\n\t * a constant-time comparison (e.g. crypto.subtle HMAC verify) and read\n\t * the secret from a Convex environment variable. Return false or throw\n\t * to reject.\n\t */\n\tverify: (raw: string, request: Request) => boolean | Promise<boolean>\n\t/**\n\t * Derives the natural dedup key — the `${event}:${id}:${updatedAt}`\n\t * pattern. Receives the raw body and the request; parse inside if needed.\n\t */\n\teventKey: (raw: string, request: Request) => string | Promise<string>\n\tsource?: string\n\terrors?: ErrorFactory\n}\n\n/**\n * The sanctioned webhook pattern as code: verify (raw body, fail closed) →\n * derive natural key → delegate to a host internal mutation whose FIRST act\n * is recordWebhookEvent (transactional dedup). Use inside the host's\n * httpAction:\n *\n * const boundary = createWebhookBoundary({ verify, eventKey })\n * http.route({ path: '/webhooks/vendor', method: 'POST',\n * handler: httpAction((ctx, request) =>\n * boundary.handle(ctx, request, internal.domain.vendor.functions.applyEvent)) })\n */\nexport function createWebhookBoundary(config: WebhookBoundaryConfig) {\n\tconst errors = config.errors ?? defaultErrors\n\n\tasync function handle<Result>(\n\t\tctx: {\n\t\t\trunMutation: (\n\t\t\t\ttarget: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>,\n\t\t\t\targs: WebhookDelivery,\n\t\t\t) => Promise<Result>\n\t\t},\n\t\trequest: Request,\n\t\ttarget: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>,\n\t): Promise<Response> {\n\t\tconst raw = await request.text()\n\t\tlet verified = false\n\t\ttry {\n\t\t\tverified = await config.verify(raw, request)\n\t\t} catch {\n\t\t\tverified = false\n\t\t}\n\t\tif (!verified) {\n\t\t\terrors.throw({\n\t\t\t\tcode: 'WEBHOOK_SIGNATURE_INVALID',\n\t\t\t\tmessage: 'Webhook signature verification failed',\n\t\t\t})\n\t\t}\n\t\tconst key = await config.eventKey(raw, request)\n\t\tawait ctx.runMutation(target, {\n\t\t\teventKey: key,\n\t\t\tpayload: raw,\n\t\t\tsource: config.source ?? 'unknown',\n\t\t})\n\t\treturn new Response(null, { status: 200 })\n\t}\n\n\treturn { handle }\n}\n\nexport type WebhookDelivery = { eventKey: string; payload: string; source: string }\n"],"mappings":"gHAeA,
|
|
1
|
+
{"version":3,"file":"webhooks.mjs","names":["webhookEventsTable","zodTable","z","recordWebhookEvent","table","input","ctx","builder","createWebhookBoundary","errors","config","defaultErrors","handle","raw","request","verified","key","target"],"sources":["../src/webhooks.ts"],"sourcesContent":["import type { GenericMutationCtx, GenericDataModel, FunctionReference } from 'convex/server'\nimport { z } from 'zod'\nimport { defaultErrors, type ErrorFactory } from './errors'\nimport { zodTable } from './zod-table'\n\n/**\n * The host-owned dedup table (the kit gains no tables). Register it in the\n * schema with the by_eventKey index:\n *\n * webhookEvents: webhookEventsTable().table.index('by_eventKey', ['eventKey'])\n *\n * Rows are host-owned data: prune them on a retention window comfortably\n * longer than the provider's redelivery horizon (a cleanup cron over\n * `receivedAt` — see docs/webhooks.md).\n */\nexport function webhookEventsTable() {\n\treturn zodTable('webhookEvents', () => ({\n\t\teventKey: z.string(),\n\t\tsource: z.string(),\n\t\treceivedAt: z.number(),\n\t}))\n}\n\ntype DedupContext = Pick<GenericMutationCtx<GenericDataModel>, 'db'>\n\n/**\n * Transactional insert-if-absent dedup guard. Call this FIRST inside the\n * internal mutation the boundary delegates to — dedup must live in the\n * mutation (transactional), never in the action. Returns duplicate: true\n * when the natural key was already recorded; the caller then no-ops.\n */\nexport async function recordWebhookEvent(\n\tctx: DedupContext,\n\tinput: { key: string; source?: string; table?: string },\n): Promise<{ duplicate: boolean }> {\n\tconst table = input.table ?? 'webhookEvents'\n\tconst existing = await ctx.db\n\t\t.query(table)\n\t\t.withIndex('by_eventKey', (builder) => builder.eq('eventKey', input.key))\n\t\t.first()\n\tif (existing) return { duplicate: true }\n\tawait ctx.db.insert(table, {\n\t\teventKey: input.key,\n\t\tsource: input.source ?? 'unknown',\n\t\treceivedAt: Date.now(),\n\t})\n\treturn { duplicate: false }\n}\n\nexport type WebhookBoundaryConfig = {\n\t/**\n\t * Verifies the provider signature over the RAW body bytes — the helper\n\t * hands it `request.text()` BEFORE any parsing. Implementations must use\n\t * a constant-time comparison (e.g. crypto.subtle HMAC verify) and read\n\t * the secret from a Convex environment variable. Return false or throw\n\t * to reject.\n\t */\n\tverify: (raw: string, request: Request) => boolean | Promise<boolean>\n\t/**\n\t * Derives the natural dedup key — the `${event}:${id}:${updatedAt}`\n\t * pattern. Receives the raw body and the request; parse inside if needed.\n\t */\n\teventKey: (raw: string, request: Request) => string | Promise<string>\n\tsource?: string\n\terrors?: ErrorFactory\n}\n\n/**\n * The sanctioned webhook pattern as code: verify (raw body, fail closed) →\n * derive natural key → delegate to a host internal mutation whose FIRST act\n * is recordWebhookEvent (transactional dedup). Use inside the host's\n * httpAction:\n *\n * const boundary = createWebhookBoundary({ verify, eventKey })\n * http.route({ path: '/webhooks/vendor', method: 'POST',\n * handler: httpAction((ctx, request) =>\n * boundary.handle(ctx, request, internal.domain.vendor.functions.applyEvent)) })\n */\nexport function createWebhookBoundary(config: WebhookBoundaryConfig) {\n\tconst errors = config.errors ?? defaultErrors\n\n\tasync function handle<Result>(\n\t\tctx: {\n\t\t\trunMutation: (\n\t\t\t\ttarget: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>,\n\t\t\t\targs: WebhookDelivery,\n\t\t\t) => Promise<Result>\n\t\t},\n\t\trequest: Request,\n\t\ttarget: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>,\n\t): Promise<Response> {\n\t\tconst raw = await request.text()\n\t\tlet verified = false\n\t\ttry {\n\t\t\tverified = await config.verify(raw, request)\n\t\t} catch {\n\t\t\tverified = false\n\t\t}\n\t\tif (!verified) {\n\t\t\terrors.throw({\n\t\t\t\tcode: 'WEBHOOK_SIGNATURE_INVALID',\n\t\t\t\tmessage: 'Webhook signature verification failed',\n\t\t\t})\n\t\t}\n\t\tconst key = await config.eventKey(raw, request)\n\t\tawait ctx.runMutation(target, {\n\t\t\teventKey: key,\n\t\t\tpayload: raw,\n\t\t\tsource: config.source ?? 'unknown',\n\t\t})\n\t\treturn new Response(null, { status: 200 })\n\t}\n\n\treturn { handle }\n}\n\nexport type WebhookDelivery = { eventKey: string; payload: string; source: string }\n"],"mappings":"gHAeA,SAAgBA,GAAqB,CACpC,OAAOC,EAAS,qBAAwB,CACvC,SAAUC,EAAE,OAAO,EACnB,OAAQA,EAAE,OAAO,EACjB,WAAYA,EAAE,OAAO,CACtB,EAAE,CACH,CAUA,eAAsBC,EACrB,EACA,EACkC,CAClC,IAAMC,EAAQC,EAAM,OAAS,gBAW7B,OANI,MAJmBC,EAAI,GACzB,MAAMF,CAAK,CAAC,CACZ,UAAU,cAAgB,GAAYG,EAAQ,GAAG,WAAYF,EAAM,GAAG,CAAC,CAAC,CACxE,MAAM,EACa,CAAE,UAAW,EAAK,GACvC,MAAMC,EAAI,GAAG,OAAOF,EAAO,CAC1B,SAAUC,EAAM,IAChB,OAAQA,EAAM,QAAU,UACxB,WAAY,KAAK,IAAI,CACtB,CAAC,EACM,CAAE,UAAW,EAAM,EAC3B,CA+BA,SAAgBG,EAAsB,EAA+B,CACpE,IAAMC,EAASC,EAAO,QAAUC,EAEhC,eAAeC,EACd,EAMA,EACA,EACoB,CACpB,IAAMC,EAAM,MAAMC,EAAQ,KAAK,EAC3BC,EAAW,GACf,GAAI,CACH,EAAW,MAAML,EAAO,OAAOG,EAAKC,CAAO,CAC5C,MAAQ,CACP,EAAW,EACZ,CACKC,GACJN,EAAO,MAAM,CACZ,KAAM,4BACN,QAAS,uCACV,CAAC,EAEF,IAAMO,EAAM,MAAMN,EAAO,SAASG,EAAKC,CAAO,EAM9C,OALA,MAAMR,EAAI,YAAYW,EAAQ,CAC7B,SAAUD,EACV,QAASH,EACT,OAAQH,EAAO,QAAU,SAC1B,CAAC,EACM,IAAI,SAAS,KAAM,CAAE,OAAQ,GAAI,CAAC,CAC1C,CAEA,MAAO,CAAE,QAAO,CACjB"}
|
package/dist/zod-table.d.mts
CHANGED
|
@@ -16,8 +16,8 @@ declare const timestampFields: {
|
|
|
16
16
|
archivedAt: z.ZodOptional<z.ZodNumber>;
|
|
17
17
|
};
|
|
18
18
|
type TimestampFields = typeof timestampFields;
|
|
19
|
-
declare const TIMESTAMP_FIELDS: readonly ['createdAt', 'updatedAt', 'archivedAt'];
|
|
20
|
-
type TableBoundaryOptions<Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = {
|
|
19
|
+
export declare const TIMESTAMP_FIELDS: readonly ['createdAt', 'updatedAt', 'archivedAt'];
|
|
20
|
+
export type TableBoundaryOptions<Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = {
|
|
21
21
|
serverFields?: ServerFields;
|
|
22
22
|
commandFields?: CommandFields;
|
|
23
23
|
publicFields?: PublicFields;
|
|
@@ -26,7 +26,7 @@ type StrictObject<Fields extends FieldSchemas> = z.ZodObject<Fields, z.core.$str
|
|
|
26
26
|
type WriteFields<Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[]> = Omit<Fields & TimestampFields, ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number]>;
|
|
27
27
|
type PartialFields<Fields extends FieldSchemas> = { [Key in keyof Fields]: z.ZodOptional<Fields[Key]>; };
|
|
28
28
|
/** Named schema relationships keep generated declarations bounded and consumers fully typed. */
|
|
29
|
-
type TableBoundary<Table extends string, Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = {
|
|
29
|
+
export type TableBoundary<Table extends string, Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = {
|
|
30
30
|
tableName: Table;
|
|
31
31
|
storage: StrictObject<Fields & TimestampFields>;
|
|
32
32
|
schema: ReturnType<typeof documentSchema<Fields & TimestampFields, Table>>;
|
|
@@ -52,7 +52,7 @@ type TableBoundary<Table extends string, Fields extends FieldSchemas, ServerFiel
|
|
|
52
52
|
};
|
|
53
53
|
};
|
|
54
54
|
/** Defines one table and derives its storage, document, write, and DTO boundaries. */
|
|
55
|
-
declare function zodTable<Table extends string, Fields extends FieldSchemas, const ServerFields extends readonly FieldKey<Fields>[] = readonly [], const CommandFields extends readonly FieldKey<Fields>[] = readonly [], const PublicFields extends readonly FieldKey<Fields & TimestampFields>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields, options?: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields>): TableBoundary<Table, Fields, ServerFields, CommandFields, PublicFields>;
|
|
55
|
+
export declare function zodTable<Table extends string, Fields extends FieldSchemas, const ServerFields extends readonly FieldKey<Fields>[] = readonly [], const CommandFields extends readonly FieldKey<Fields>[] = readonly [], const PublicFields extends readonly FieldKey<Fields & TimestampFields>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields, options?: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields>): TableBoundary<Table, Fields, ServerFields, CommandFields, PublicFields>;
|
|
56
56
|
declare const tenantFields: {
|
|
57
57
|
tenant: z.ZodString;
|
|
58
58
|
};
|
|
@@ -65,7 +65,7 @@ type TenantFields = typeof tenantFields;
|
|
|
65
65
|
* trigger forbids reassigning it. Expose it via publicFields only when a DTO
|
|
66
66
|
* genuinely needs it.
|
|
67
67
|
*/
|
|
68
|
-
declare function tenantTable<Table extends string, Fields extends FieldSchemas, const ServerFields extends readonly FieldKey<Fields>[] = readonly [], const CommandFields extends readonly FieldKey<Fields>[] = readonly [], const PublicFields extends readonly FieldKey<Fields & TenantFields & TimestampFields>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields, options?: TableBoundaryOptions<Fields & TenantFields, readonly (ServerFields[number] | 'tenant')[], CommandFields, PublicFields>): TableBoundary<Table, Fields & {
|
|
68
|
+
export declare function tenantTable<Table extends string, Fields extends FieldSchemas, const ServerFields extends readonly FieldKey<Fields>[] = readonly [], const CommandFields extends readonly FieldKey<Fields>[] = readonly [], const PublicFields extends readonly FieldKey<Fields & TenantFields & TimestampFields>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields, options?: TableBoundaryOptions<Fields & TenantFields, readonly (ServerFields[number] | 'tenant')[], CommandFields, PublicFields>): TableBoundary<Table, Fields & {
|
|
69
69
|
tenant: z.ZodString;
|
|
70
70
|
}, readonly ("tenant" | ServerFields[number])[], CommandFields, PublicFields>;
|
|
71
71
|
/**
|
|
@@ -73,7 +73,7 @@ declare function tenantTable<Table extends string, Fields extends FieldSchemas,
|
|
|
73
73
|
* duplicate table names across modules — the module-registry combinator for
|
|
74
74
|
* domain/table.ts: `defineSchema(createModule(catalogTables, salesTables))`.
|
|
75
75
|
*/
|
|
76
|
-
declare function createModule<const Maps extends readonly Record<string, TableDefinition<any>>[]>(...maps: Maps): UnionToIntersection<Maps[number]>;
|
|
76
|
+
export declare function createModule<const Maps extends readonly Record<string, TableDefinition<any>>[]>(...maps: Maps): UnionToIntersection<Maps[number]>;
|
|
77
77
|
type UnionToIntersection<Union> = (Union extends unknown ? (member: Union) => void : never) extends ((member: infer Intersection) => void) ? Intersection : never;
|
|
78
78
|
/**
|
|
79
79
|
* Cursor-pagination boundary schemas bridged from Convex's own validators:
|
|
@@ -84,7 +84,7 @@ type UnionToIntersection<Union> = (Union extends unknown ? (member: Union) => vo
|
|
|
84
84
|
* Note: under row-level security, pages may be SHORTER than numItems when
|
|
85
85
|
* rules reject rows mid-page; cursors remain correct.
|
|
86
86
|
*/
|
|
87
|
-
declare function paginated<Dto extends z.ZodType>(dto: Dto): {
|
|
87
|
+
export declare function paginated<Dto extends z.ZodType>(dto: Dto): {
|
|
88
88
|
args: {
|
|
89
89
|
paginationOpts: z.ZodObject<{
|
|
90
90
|
numItems: z.ZodNumber;
|
|
@@ -107,7 +107,7 @@ declare function paginated<Dto extends z.ZodType>(dto: Dto): {
|
|
|
107
107
|
}, z.core.$strip>;
|
|
108
108
|
};
|
|
109
109
|
/** Preserves discriminated-union storage while retaining the same table owner. */
|
|
110
|
-
declare function zodVariantTable<Table extends string, Schema extends z.ZodType>(tableName: Table, storage: Schema): {
|
|
110
|
+
export declare function zodVariantTable<Table extends string, Schema extends z.ZodType>(tableName: Table, storage: Schema): {
|
|
111
111
|
tableName: Table;
|
|
112
112
|
storage: Schema;
|
|
113
113
|
insertSchema: Schema;
|
|
@@ -117,7 +117,7 @@ declare function zodVariantTable<Table extends string, Schema extends z.ZodType>
|
|
|
117
117
|
* A zid that presents as a plain string in generated JSON schemas so
|
|
118
118
|
* LLM-tool inputs stay primitive, while keeping the Id type at compile time.
|
|
119
119
|
*/
|
|
120
|
-
declare function jsonSafeZid<Table extends string>(tableName: Table): z.ZodType<GenericId<Table>, string>;
|
|
120
|
+
export declare function jsonSafeZid<Table extends string>(tableName: Table): z.ZodType<GenericId<Table>, string>;
|
|
121
121
|
type ZodObjectOmit<Schema extends z.ZodObject<any, any>, OmitMask extends z.util.Mask<keyof Schema['shape']>> = Schema extends z.ZodObject<infer ObjectFields, infer Config> ? z.ZodObject<z.util.Flatten<Omit<ObjectFields, Extract<keyof ObjectFields, keyof OmitMask>>>, Config> : never;
|
|
122
122
|
declare function documentSchema<Fields extends FieldSchemas, Table extends string>(storage: StrictObject<Fields>, tableName: Table): z.ZodObject<(keyof Fields & ("_creationTime" | "_id") extends never ? Fields & {
|
|
123
123
|
_id: import("convex-helpers/server/zod4").Zid<Table>;
|
|
@@ -127,5 +127,5 @@ declare function documentSchema<Fields extends FieldSchemas, Table extends strin
|
|
|
127
127
|
_creationTime: z.ZodNumber;
|
|
128
128
|
}) extends (infer T) ? { [k in keyof T]: T[k]; } : never, z.core.$strict>;
|
|
129
129
|
//#endregion
|
|
130
|
-
export {
|
|
130
|
+
export { convexToZod, zid, zodToConvex };
|
|
131
131
|
//# sourceMappingURL=zod-table.d.mts.map
|
package/dist/zod-table.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod-table.mjs","names":["zid","zodToConvex","convexToZod"],"sources":["../src/zod-table.ts"],"sourcesContent":["import {\n\tconvexToZod,\n\tzid,\n\tzodToConvex,\n\ttype ConvexValidatorFromZod,\n} from 'convex-helpers/server/zod4'\nimport { defineTable, paginationOptsValidator, type TableDefinition } from 'convex/server'\nimport type { GenericId } from 'convex/values'\nimport { z } from 'zod'\n\n// Use this entry point when deriving validators from kit-owned schemas so\n// the producer and converter consult the same convex-helpers ID registry.\nexport { zid, zodToConvex, convexToZod } from 'convex-helpers/server/zod4'\n\ntype FieldSchemas = Record<string, z.ZodType>\ntype FieldKey<Fields extends FieldSchemas> = Extract<keyof Fields, string>\n\n/**\n * Opinionated lifecycle timestamps — every table has them, server-owned,\n * always excluded from insert/command boundaries. Maintained by the\n * `timestamps` trigger helper (see cvx-kit/triggers).\n */\nconst timestampFields = {\n\tcreatedAt: z.number().optional(),\n\tupdatedAt: z.number().optional(),\n\tarchivedAt: z.number().optional(),\n} satisfies FieldSchemas\n\ntype TimestampFields = typeof timestampFields\n\nexport const TIMESTAMP_FIELDS = ['createdAt', 'updatedAt', 'archivedAt'] as const\n\nexport type TableBoundaryOptions<\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n\tCommandFields extends readonly FieldKey<Fields>[],\n\tPublicFields extends readonly FieldKey<Fields & TimestampFields>[],\n> = {\n\tserverFields?: ServerFields\n\tcommandFields?: CommandFields\n\tpublicFields?: PublicFields\n}\n\ntype StrictObject<Fields extends FieldSchemas> = z.ZodObject<Fields, z.core.$strict>\ntype WriteFields<\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n> = Omit<Fields & TimestampFields, ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number]>\ntype PartialFields<Fields extends FieldSchemas> = {\n\t[Key in keyof Fields]: z.ZodOptional<Fields[Key]>\n}\n\n/** Named schema relationships keep generated declarations bounded and consumers fully typed. */\nexport type TableBoundary<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n\tCommandFields extends readonly FieldKey<Fields>[],\n\tPublicFields extends readonly FieldKey<Fields & TimestampFields>[],\n> = {\n\ttableName: Table\n\tstorage: StrictObject<Fields & TimestampFields>\n\tschema: ReturnType<typeof documentSchema<Fields & TimestampFields, Table>>\n\tinsertSchema: StrictObject<WriteFields<Fields, ServerFields>>\n\tupdateSchema: StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>\n\tcommandInput: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>\n\tpublicDto: StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>\n\ttoPublicDto: (\n\t\trow: z.input<StrictObject<Fields & TimestampFields>>,\n\t) => z.output<StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>>\n\ttable: TableDefinition<ConvexValidatorFromZod<StrictObject<Fields & TimestampFields>, 'required'>>\n\tinsert(): StrictObject<WriteFields<Fields, ServerFields>>\n\tinsert<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(\n\t\tomit: Mask,\n\t): ZodObjectOmit<StrictObject<WriteFields<Fields, ServerFields>>, Mask>\n\tupdate(): StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>\n\tupdate<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(\n\t\tomit: Mask,\n\t): ZodObjectOmit<StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>, Mask>\n\ttools: {\n\t\tinsert: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>\n\t\tupdate: StrictObject<{\n\t\t\tdata: StrictObject<PartialFields<Pick<Fields & TimestampFields, CommandFields[number]>>>\n\t\t\tid: z.ZodType<GenericId<Table>, string>\n\t\t}>\n\t\tid: StrictObject<{ id: z.ZodType<GenericId<Table>, string> }>\n\t}\n}\n\n/** Defines one table and derives its storage, document, write, and DTO boundaries. */\nexport function zodTable<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tconst ServerFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly FieldKey<Fields & TimestampFields>[] = readonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields,\n\toptions: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields> = {},\n): TableBoundary<Table, Fields, ServerFields, CommandFields, PublicFields> {\n\tconst columns: Fields & TimestampFields = { ...fields(zid), ...timestampFields }\n\tconst storage: z.ZodObject<Fields & TimestampFields, z.core.$strict> = z.object(columns).strict()\n\tconst schema = documentSchema(storage, tableName)\n\tconst omittedKeys: readonly (ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number])[] = [\n\t\t...TIMESTAMP_FIELDS,\n\t\t...(options.serverFields ?? []),\n\t]\n\tconst commandKeys: readonly CommandFields[number][] = options.commandFields ?? []\n\tconst publicKeys: readonly PublicFields[number][] = options.publicFields ?? []\n\tconst insertSchema: z.ZodObject<WriteFields<Fields, ServerFields>, z.core.$strict> = z\n\t\t.object(omitFields(columns, omittedKeys))\n\t\t.strict()\n\tconst updateSchema = insertSchema.partial()\n\tconst commandInput: z.ZodObject<\n\t\tPick<Fields & TimestampFields, CommandFields[number]>,\n\t\tz.core.$strict\n\t> = z.object(pickFields(columns, commandKeys)).strict()\n\tconst publicDto: z.ZodObject<\n\t\tPick<Fields & TimestampFields, PublicFields[number]>,\n\t\tz.core.$strict\n\t> = z.object(pickFields(columns, publicKeys)).strict()\n\tconst toPublicDto = (row: z.input<typeof storage>) =>\n\t\tpublicDto.parse(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.keys(publicDto.shape).map((key) => {\n\t\t\t\t\t// SAFETY: publicDto contains only keys selected from the storage fields.\n\t\t\t\t\treturn [key, row[key as keyof typeof row]]\n\t\t\t\t}),\n\t\t\t),\n\t\t)\n\n\ttype InsertSchema = typeof insertSchema\n\ttype UpdateSchema = typeof updateSchema\n\ttype InsertMask = z.util.Mask<keyof InsertSchema['shape']>\n\ttype UpdateMask = z.util.Mask<keyof UpdateSchema['shape']>\n\n\tfunction insert(): InsertSchema\n\tfunction insert<OmitMask extends InsertMask>(\n\t\tomit: OmitMask,\n\t): ZodObjectOmit<InsertSchema, OmitMask>\n\tfunction insert(omit?: InsertMask) {\n\t\t// SAFETY: InsertMask contains only insertSchema keys; the overload retains Zod's exact mask relationship.\n\t\treturn omit ? insertSchema.omit(omit as never) : insertSchema\n\t}\n\n\tfunction update(): UpdateSchema\n\tfunction update<OmitMask extends UpdateMask>(\n\t\tomit: OmitMask,\n\t): ZodObjectOmit<UpdateSchema, OmitMask>\n\tfunction update(omit?: UpdateMask) {\n\t\t// SAFETY: UpdateMask contains only updateSchema keys; the overload retains Zod's exact mask relationship.\n\t\treturn omit ? updateSchema.omit(omit as never) : updateSchema\n\t}\n\n\tconst table: TableDefinition<ConvexValidatorFromZod<typeof storage, 'required'>> = defineTable(\n\t\tzodToConvex(storage),\n\t)\n\treturn {\n\t\ttableName,\n\t\tschema,\n\t\tstorage,\n\t\tinsertSchema,\n\t\tupdateSchema,\n\t\tcommandInput,\n\t\tpublicDto,\n\t\ttoPublicDto,\n\t\ttable,\n\t\tinsert,\n\t\tupdate,\n\t\ttools: {\n\t\t\t// jsonSafeZid (not zid): tool masks feed generated JSON schemas, so\n\t\t\t// ids must present as plain strings while keeping the Id<...> type.\n\t\t\tinsert: commandInput,\n\t\t\tupdate: z.object({ data: commandInput.partial(), id: jsonSafeZid(tableName) }).strict(),\n\t\t\tid: z.object({ id: jsonSafeZid(tableName) }).strict(),\n\t\t},\n\t}\n}\n\nconst tenantFields = {\n\ttenant: z.string().min(1),\n} satisfies FieldSchemas\n\ntype TenantFields = typeof tenantFields\n\n/**\n * A zodTable whose rows are tenant-owned: injects the server-owned `tenant`\n * field (the tenancy boundary — see cvx-kit/tenancy). Like timestamps, it is\n * excluded from insert/update/command boundaries; handlers stamp it from\n * ctx.tenant, row-level security matches on it, and the tenantOwnership\n * trigger forbids reassigning it. Expose it via publicFields only when a DTO\n * genuinely needs it.\n */\nexport function tenantTable<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tconst ServerFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly FieldKey<Fields & TenantFields & TimestampFields>[] =\n\t\treadonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields,\n\toptions: TableBoundaryOptions<\n\t\tFields & TenantFields,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t> = {},\n) {\n\treturn zodTable<\n\t\tTable,\n\t\tFields & TenantFields,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t>(tableName, (id) => ({ ...fields(id), ...tenantFields }), {\n\t\t...options,\n\t\tserverFields: [...(options.serverFields ?? []), 'tenant'],\n\t})\n}\n\n/**\n * Merges per-module table maps into the application schema map, rejecting\n * duplicate table names across modules — the module-registry combinator for\n * domain/table.ts: `defineSchema(createModule(catalogTables, salesTables))`.\n */\nexport function createModule<const Maps extends readonly Record<string, TableDefinition<any>>[]>(\n\t...maps: Maps\n): UnionToIntersection<Maps[number]> {\n\tconst combined: Record<string, TableDefinition<any>> = {}\n\tfor (const map of maps) {\n\t\tfor (const [tableName, definition] of Object.entries(map)) {\n\t\t\tif (tableName in combined) {\n\t\t\t\tthrow new Error(`Table \"${tableName}\" is declared by more than one module`)\n\t\t\t}\n\t\t\tcombined[tableName] = definition\n\t\t}\n\t}\n\t// SAFETY: every input entry is copied, and duplicate keys are rejected above.\n\treturn combined as UnionToIntersection<Maps[number]>\n}\n\ntype UnionToIntersection<Union> = (\n\tUnion extends unknown ? (member: Union) => void : never\n) extends (member: infer Intersection) => void\n\t? Intersection\n\t: never\n\n/**\n * Cursor-pagination boundary schemas bridged from Convex's own validators:\n * `args.paginationOpts` for function args, `result` for the page shape.\n * Pair with `include(...).paginate(opts)` (cvx-kit/auth). The result object\n * is deliberately non-strict — Convex may attach engine fields (splitCursor,\n * pageStatus); the page items themselves are validated against the dto.\n * Note: under row-level security, pages may be SHORTER than numItems when\n * rules reject rows mid-page; cursors remain correct.\n */\nexport function paginated<Dto extends z.ZodType>(dto: Dto) {\n\treturn {\n\t\targs: { paginationOpts: convexToZod(paginationOptsValidator) },\n\t\tresult: z.object({\n\t\t\tpage: z.array(dto),\n\t\t\tisDone: z.boolean(),\n\t\t\tcontinueCursor: z.string(),\n\t\t\tsplitCursor: z.string().nullable().optional(),\n\t\t\tpageStatus: z.enum(['SplitRecommended', 'SplitRequired']).nullable().optional(),\n\t\t}),\n\t}\n}\n\n/** Preserves discriminated-union storage while retaining the same table owner. */\nexport function zodVariantTable<Table extends string, Schema extends z.ZodType>(\n\ttableName: Table,\n\tstorage: Schema,\n) {\n\treturn {\n\t\ttableName,\n\t\tstorage,\n\t\tinsertSchema: storage,\n\t\ttable: defineTable(zodToConvex(storage)),\n\t}\n}\n\n/**\n * A zid that presents as a plain string in generated JSON schemas so\n * LLM-tool inputs stay primitive, while keeping the Id type at compile time.\n */\nexport function jsonSafeZid<Table extends string>(\n\ttableName: Table,\n): z.ZodType<GenericId<Table>, string> {\n\tconst id = zid(tableName)\n\treturn z\n\t\t.string()\n\t\t.refine((value): value is GenericId<Table> => id.safeParse(value).success)\n\t\t.describe(`Convex document id for table \"${tableName}\"; validated against the table by Convex`)\n}\n\ntype ZodObjectOmit<\n\tSchema extends z.ZodObject<any, any>,\n\tOmitMask extends z.util.Mask<keyof Schema['shape']>,\n> =\n\tSchema extends z.ZodObject<infer ObjectFields, infer Config>\n\t\t? z.ZodObject<\n\t\t\t\tz.util.Flatten<Omit<ObjectFields, Extract<keyof ObjectFields, keyof OmitMask>>>,\n\t\t\t\tConfig\n\t\t\t>\n\t\t: never\n\nfunction documentSchema<Fields extends FieldSchemas, Table extends string>(\n\tstorage: StrictObject<Fields>,\n\ttableName: Table,\n) {\n\treturn storage.extend({ _id: zid(tableName), _creationTime: z.number() })\n}\n\nfunction pickFields<Fields extends FieldSchemas, const Keys extends readonly FieldKey<Fields>[]>(\n\tfields: Fields,\n\tkeys: Keys,\n) {\n\t// SAFETY: each entry uses a requested key and its original validator, preserving Pick.\n\treturn Object.fromEntries(keys.map((key) => [key, fields[key]])) as Pick<Fields, Keys[number]>\n}\n\nfunction omitFields<Fields extends FieldSchemas, const Keys extends readonly FieldKey<Fields>[]>(\n\tfields: Fields,\n\tkeys: Keys,\n) {\n\tconst omitted = new Set<string>(keys)\n\t// SAFETY: filtering removes exactly the supplied keys and keeps every remaining validator.\n\treturn Object.fromEntries(Object.entries(fields).filter(([key]) => !omitted.has(key))) as Omit<\n\t\tFields,\n\t\tKeys[number]\n\t>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"zod-table.mjs","names":["zid","zodToConvex","convexToZod","convexToZod$1","zid$1","zodToConvex$1","timestampFields","z","TIMESTAMP_FIELDS","zodTable","columns","fields","storage","schema","documentSchema","tableName","omittedKeys","options","commandKeys","publicKeys","insertSchema","omitFields","updateSchema","commandInput","pickFields","publicDto","toPublicDto","key","row","insert","omit","update","defineTable","jsonSafeZid","tenantFields","tenantTable","id","createModule","maps","combined","map","definition","paginated","paginationOptsValidator","dto","zodVariantTable","value","keys","omitted","convexToZod","zid","zodToConvex"],"sources":["../src/zod-table.ts"],"sourcesContent":["import {\n\tconvexToZod,\n\tzid,\n\tzodToConvex,\n\ttype ConvexValidatorFromZod,\n} from 'convex-helpers/server/zod4'\nimport { defineTable, paginationOptsValidator, type TableDefinition } from 'convex/server'\nimport type { GenericId } from 'convex/values'\nimport { z } from 'zod'\n\n// Use this entry point when deriving validators from kit-owned schemas so\n// the producer and converter consult the same convex-helpers ID registry.\nexport { zid, zodToConvex, convexToZod } from 'convex-helpers/server/zod4'\n\ntype FieldSchemas = Record<string, z.ZodType>\ntype FieldKey<Fields extends FieldSchemas> = Extract<keyof Fields, string>\n\n/**\n * Opinionated lifecycle timestamps — every table has them, server-owned,\n * always excluded from insert/command boundaries. Maintained by the\n * `timestamps` trigger helper (see cvx-kit/triggers).\n */\nconst timestampFields = {\n\tcreatedAt: z.number().optional(),\n\tupdatedAt: z.number().optional(),\n\tarchivedAt: z.number().optional(),\n} satisfies FieldSchemas\n\ntype TimestampFields = typeof timestampFields\n\nexport const TIMESTAMP_FIELDS = ['createdAt', 'updatedAt', 'archivedAt'] as const\n\nexport type TableBoundaryOptions<\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n\tCommandFields extends readonly FieldKey<Fields>[],\n\tPublicFields extends readonly FieldKey<Fields & TimestampFields>[],\n> = {\n\tserverFields?: ServerFields\n\tcommandFields?: CommandFields\n\tpublicFields?: PublicFields\n}\n\ntype StrictObject<Fields extends FieldSchemas> = z.ZodObject<Fields, z.core.$strict>\ntype WriteFields<\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n> = Omit<Fields & TimestampFields, ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number]>\ntype PartialFields<Fields extends FieldSchemas> = {\n\t[Key in keyof Fields]: z.ZodOptional<Fields[Key]>\n}\n\n/** Named schema relationships keep generated declarations bounded and consumers fully typed. */\nexport type TableBoundary<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n\tCommandFields extends readonly FieldKey<Fields>[],\n\tPublicFields extends readonly FieldKey<Fields & TimestampFields>[],\n> = {\n\ttableName: Table\n\tstorage: StrictObject<Fields & TimestampFields>\n\tschema: ReturnType<typeof documentSchema<Fields & TimestampFields, Table>>\n\tinsertSchema: StrictObject<WriteFields<Fields, ServerFields>>\n\tupdateSchema: StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>\n\tcommandInput: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>\n\tpublicDto: StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>\n\ttoPublicDto: (\n\t\trow: z.input<StrictObject<Fields & TimestampFields>>,\n\t) => z.output<StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>>\n\ttable: TableDefinition<ConvexValidatorFromZod<StrictObject<Fields & TimestampFields>, 'required'>>\n\tinsert(): StrictObject<WriteFields<Fields, ServerFields>>\n\tinsert<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(\n\t\tomit: Mask,\n\t): ZodObjectOmit<StrictObject<WriteFields<Fields, ServerFields>>, Mask>\n\tupdate(): StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>\n\tupdate<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(\n\t\tomit: Mask,\n\t): ZodObjectOmit<StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>, Mask>\n\ttools: {\n\t\tinsert: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>\n\t\tupdate: StrictObject<{\n\t\t\tdata: StrictObject<PartialFields<Pick<Fields & TimestampFields, CommandFields[number]>>>\n\t\t\tid: z.ZodType<GenericId<Table>, string>\n\t\t}>\n\t\tid: StrictObject<{ id: z.ZodType<GenericId<Table>, string> }>\n\t}\n}\n\n/** Defines one table and derives its storage, document, write, and DTO boundaries. */\nexport function zodTable<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tconst ServerFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly FieldKey<Fields & TimestampFields>[] = readonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields,\n\toptions: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields> = {},\n): TableBoundary<Table, Fields, ServerFields, CommandFields, PublicFields> {\n\tconst columns: Fields & TimestampFields = { ...fields(zid), ...timestampFields }\n\tconst storage: z.ZodObject<Fields & TimestampFields, z.core.$strict> = z.object(columns).strict()\n\tconst schema = documentSchema(storage, tableName)\n\tconst omittedKeys: readonly (ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number])[] = [\n\t\t...TIMESTAMP_FIELDS,\n\t\t...(options.serverFields ?? []),\n\t]\n\tconst commandKeys: readonly CommandFields[number][] = options.commandFields ?? []\n\tconst publicKeys: readonly PublicFields[number][] = options.publicFields ?? []\n\tconst insertSchema: z.ZodObject<WriteFields<Fields, ServerFields>, z.core.$strict> = z\n\t\t.object(omitFields(columns, omittedKeys))\n\t\t.strict()\n\tconst updateSchema = insertSchema.partial()\n\tconst commandInput: z.ZodObject<\n\t\tPick<Fields & TimestampFields, CommandFields[number]>,\n\t\tz.core.$strict\n\t> = z.object(pickFields(columns, commandKeys)).strict()\n\tconst publicDto: z.ZodObject<\n\t\tPick<Fields & TimestampFields, PublicFields[number]>,\n\t\tz.core.$strict\n\t> = z.object(pickFields(columns, publicKeys)).strict()\n\tconst toPublicDto = (row: z.input<typeof storage>) =>\n\t\tpublicDto.parse(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.keys(publicDto.shape).map((key) => {\n\t\t\t\t\t// SAFETY: publicDto contains only keys selected from the storage fields.\n\t\t\t\t\treturn [key, row[key as keyof typeof row]]\n\t\t\t\t}),\n\t\t\t),\n\t\t)\n\n\ttype InsertSchema = typeof insertSchema\n\ttype UpdateSchema = typeof updateSchema\n\ttype InsertMask = z.util.Mask<keyof InsertSchema['shape']>\n\ttype UpdateMask = z.util.Mask<keyof UpdateSchema['shape']>\n\n\tfunction insert(): InsertSchema\n\tfunction insert<OmitMask extends InsertMask>(\n\t\tomit: OmitMask,\n\t): ZodObjectOmit<InsertSchema, OmitMask>\n\tfunction insert(omit?: InsertMask) {\n\t\t// SAFETY: InsertMask contains only insertSchema keys; the overload retains Zod's exact mask relationship.\n\t\treturn omit ? insertSchema.omit(omit as never) : insertSchema\n\t}\n\n\tfunction update(): UpdateSchema\n\tfunction update<OmitMask extends UpdateMask>(\n\t\tomit: OmitMask,\n\t): ZodObjectOmit<UpdateSchema, OmitMask>\n\tfunction update(omit?: UpdateMask) {\n\t\t// SAFETY: UpdateMask contains only updateSchema keys; the overload retains Zod's exact mask relationship.\n\t\treturn omit ? updateSchema.omit(omit as never) : updateSchema\n\t}\n\n\tconst table: TableDefinition<ConvexValidatorFromZod<typeof storage, 'required'>> = defineTable(\n\t\tzodToConvex(storage),\n\t)\n\treturn {\n\t\ttableName,\n\t\tschema,\n\t\tstorage,\n\t\tinsertSchema,\n\t\tupdateSchema,\n\t\tcommandInput,\n\t\tpublicDto,\n\t\ttoPublicDto,\n\t\ttable,\n\t\tinsert,\n\t\tupdate,\n\t\ttools: {\n\t\t\t// jsonSafeZid (not zid): tool masks feed generated JSON schemas, so\n\t\t\t// ids must present as plain strings while keeping the Id<...> type.\n\t\t\tinsert: commandInput,\n\t\t\tupdate: z.object({ data: commandInput.partial(), id: jsonSafeZid(tableName) }).strict(),\n\t\t\tid: z.object({ id: jsonSafeZid(tableName) }).strict(),\n\t\t},\n\t}\n}\n\nconst tenantFields = {\n\ttenant: z.string().min(1),\n} satisfies FieldSchemas\n\ntype TenantFields = typeof tenantFields\n\n/**\n * A zodTable whose rows are tenant-owned: injects the server-owned `tenant`\n * field (the tenancy boundary — see cvx-kit/tenancy). Like timestamps, it is\n * excluded from insert/update/command boundaries; handlers stamp it from\n * ctx.tenant, row-level security matches on it, and the tenantOwnership\n * trigger forbids reassigning it. Expose it via publicFields only when a DTO\n * genuinely needs it.\n */\nexport function tenantTable<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tconst ServerFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly FieldKey<Fields & TenantFields & TimestampFields>[] =\n\t\treadonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields,\n\toptions: TableBoundaryOptions<\n\t\tFields & TenantFields,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t> = {},\n) {\n\treturn zodTable<\n\t\tTable,\n\t\tFields & TenantFields,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t>(tableName, (id) => ({ ...fields(id), ...tenantFields }), {\n\t\t...options,\n\t\tserverFields: [...(options.serverFields ?? []), 'tenant'],\n\t})\n}\n\n/**\n * Merges per-module table maps into the application schema map, rejecting\n * duplicate table names across modules — the module-registry combinator for\n * domain/table.ts: `defineSchema(createModule(catalogTables, salesTables))`.\n */\nexport function createModule<const Maps extends readonly Record<string, TableDefinition<any>>[]>(\n\t...maps: Maps\n): UnionToIntersection<Maps[number]> {\n\tconst combined: Record<string, TableDefinition<any>> = {}\n\tfor (const map of maps) {\n\t\tfor (const [tableName, definition] of Object.entries(map)) {\n\t\t\tif (tableName in combined) {\n\t\t\t\tthrow new Error(`Table \"${tableName}\" is declared by more than one module`)\n\t\t\t}\n\t\t\tcombined[tableName] = definition\n\t\t}\n\t}\n\t// SAFETY: every input entry is copied, and duplicate keys are rejected above.\n\treturn combined as UnionToIntersection<Maps[number]>\n}\n\ntype UnionToIntersection<Union> = (\n\tUnion extends unknown ? (member: Union) => void : never\n) extends (member: infer Intersection) => void\n\t? Intersection\n\t: never\n\n/**\n * Cursor-pagination boundary schemas bridged from Convex's own validators:\n * `args.paginationOpts` for function args, `result` for the page shape.\n * Pair with `include(...).paginate(opts)` (cvx-kit/auth). The result object\n * is deliberately non-strict — Convex may attach engine fields (splitCursor,\n * pageStatus); the page items themselves are validated against the dto.\n * Note: under row-level security, pages may be SHORTER than numItems when\n * rules reject rows mid-page; cursors remain correct.\n */\nexport function paginated<Dto extends z.ZodType>(dto: Dto) {\n\treturn {\n\t\targs: { paginationOpts: convexToZod(paginationOptsValidator) },\n\t\tresult: z.object({\n\t\t\tpage: z.array(dto),\n\t\t\tisDone: z.boolean(),\n\t\t\tcontinueCursor: z.string(),\n\t\t\tsplitCursor: z.string().nullable().optional(),\n\t\t\tpageStatus: z.enum(['SplitRecommended', 'SplitRequired']).nullable().optional(),\n\t\t}),\n\t}\n}\n\n/** Preserves discriminated-union storage while retaining the same table owner. */\nexport function zodVariantTable<Table extends string, Schema extends z.ZodType>(\n\ttableName: Table,\n\tstorage: Schema,\n) {\n\treturn {\n\t\ttableName,\n\t\tstorage,\n\t\tinsertSchema: storage,\n\t\ttable: defineTable(zodToConvex(storage)),\n\t}\n}\n\n/**\n * A zid that presents as a plain string in generated JSON schemas so\n * LLM-tool inputs stay primitive, while keeping the Id type at compile time.\n */\nexport function jsonSafeZid<Table extends string>(\n\ttableName: Table,\n): z.ZodType<GenericId<Table>, string> {\n\tconst id = zid(tableName)\n\treturn z\n\t\t.string()\n\t\t.refine((value): value is GenericId<Table> => id.safeParse(value).success)\n\t\t.describe(`Convex document id for table \"${tableName}\"; validated against the table by Convex`)\n}\n\ntype ZodObjectOmit<\n\tSchema extends z.ZodObject<any, any>,\n\tOmitMask extends z.util.Mask<keyof Schema['shape']>,\n> =\n\tSchema extends z.ZodObject<infer ObjectFields, infer Config>\n\t\t? z.ZodObject<\n\t\t\t\tz.util.Flatten<Omit<ObjectFields, Extract<keyof ObjectFields, keyof OmitMask>>>,\n\t\t\t\tConfig\n\t\t\t>\n\t\t: never\n\nfunction documentSchema<Fields extends FieldSchemas, Table extends string>(\n\tstorage: StrictObject<Fields>,\n\ttableName: Table,\n) {\n\treturn storage.extend({ _id: zid(tableName), _creationTime: z.number() })\n}\n\nfunction pickFields<Fields extends FieldSchemas, const Keys extends readonly FieldKey<Fields>[]>(\n\tfields: Fields,\n\tkeys: Keys,\n) {\n\t// SAFETY: each entry uses a requested key and its original validator, preserving Pick.\n\treturn Object.fromEntries(keys.map((key) => [key, fields[key]])) as Pick<Fields, Keys[number]>\n}\n\nfunction omitFields<Fields extends FieldSchemas, const Keys extends readonly FieldKey<Fields>[]>(\n\tfields: Fields,\n\tkeys: Keys,\n) {\n\tconst omitted = new Set<string>(keys)\n\t// SAFETY: filtering removes exactly the supplied keys and keeps every remaining validator.\n\treturn Object.fromEntries(Object.entries(fields).filter(([key]) => !omitted.has(key))) as Omit<\n\t\tFields,\n\t\tKeys[number]\n\t>\n}\n"],"mappings":"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"}
|