cvx-kit 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +1 -0
  3. package/dist/contracts.d.mts +2 -1
  4. package/dist/contracts.mjs +1 -1
  5. package/dist/effect.d.mts +3 -2
  6. package/dist/effect.mjs +1 -1
  7. package/dist/index.d.mts +2 -1
  8. package/dist/index.mjs +1 -1
  9. package/dist/modules/contracts/binding.d.mts +14 -0
  10. package/dist/modules/contracts/binding.mjs +2 -0
  11. package/dist/modules/contracts/binding.mjs.map +1 -0
  12. package/dist/modules/contracts/domain.d.mts +59 -0
  13. package/dist/modules/contracts/domain.mjs +2 -0
  14. package/dist/modules/contracts/domain.mjs.map +1 -0
  15. package/dist/modules/effect/api-runtime.mjs +1 -1
  16. package/dist/modules/effect/auth.d.mts +43 -0
  17. package/dist/modules/effect/auth.mjs +2 -0
  18. package/dist/modules/effect/auth.mjs.map +1 -0
  19. package/dist/modules/effect/client.d.mts +1 -1
  20. package/dist/modules/effect/client.mjs +1 -1
  21. package/dist/modules/effect/command.d.mts +35 -1
  22. package/dist/modules/effect/command.mjs +1 -1
  23. package/dist/modules/effect/command.mjs.map +1 -1
  24. package/dist/modules/effect/crud.d.mts +6 -0
  25. package/dist/modules/effect/crud.mjs +1 -1
  26. package/dist/modules/effect/domain.d.mts +44 -0
  27. package/dist/modules/effect/domain.mjs +2 -0
  28. package/dist/modules/effect/domain.mjs.map +1 -0
  29. package/dist/modules/effect/operation.d.mts +4 -3
  30. package/dist/modules/effect/operation.mjs.map +1 -1
  31. package/dist/modules/effect/query.d.mts +23 -8
  32. package/dist/modules/effect/query.mjs +1 -1
  33. package/dist/modules/effect/query.mjs.map +1 -1
  34. package/dist/modules/effect/workflow.mjs +1 -1
  35. package/dist/modules/foundation/foundation.d.mts +108 -0
  36. package/dist/modules/foundation/foundation.mjs +2 -0
  37. package/dist/modules/foundation/foundation.mjs.map +1 -0
  38. package/dist/modules/foundation/operation.d.mts +121 -0
  39. package/dist/modules/foundation/operation.mjs +2 -0
  40. package/dist/modules/foundation/operation.mjs.map +1 -0
  41. package/dist/oxlint/ownership.mjs +1 -1
  42. package/dist/oxlint/ownership.mjs.map +1 -1
  43. package/dist/oxlint/reserved-files.d.mts +6 -0
  44. package/dist/oxlint/reserved-files.mjs +2 -0
  45. package/dist/oxlint/reserved-files.mjs.map +1 -0
  46. package/dist/oxlint.d.mts +1 -1
  47. package/dist/oxlint.mjs +1 -1
  48. package/dist/oxlint.mjs.map +1 -1
  49. package/docs/auth.md +131 -1
  50. package/docs/commands-legacy.md +3 -1
  51. package/docs/conventions.md +7 -3
  52. package/docs/domain-contracts.md +115 -0
  53. package/docs/effect.md +10 -2
  54. package/docs/llms.md +41 -20
  55. package/docs/oxlint.md +4 -0
  56. package/package.json +1 -1
  57. package/scripts/test-packed-auth.mjs +5 -1
  58. package/scripts/test-packed-effect.mjs +13 -0
  59. package/scripts/test-packed-oxlint.mjs +1 -1
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"oxlint.mjs","names":["checkArchitecture","inspectArchitecture","options","rules","scopeRules","ownershipRules","reservedDomainFiles","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 { reservedDomainFiles } from './oxlint/reserved-files'\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| 'reserved-domain-file-anatomy'\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'reserved-domain-file-anatomy': reservedDomainFiles,\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 (\n\t\t\t\t\t(ref.source === 'cvx-kit/effect' && ref.name === 'createEffectFoundation') ||\n\t\t\t\t\t(ref.source === 'cvx-kit' && ref.name === 'createFoundation')\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/docs/auth.md CHANGED
@@ -64,9 +64,139 @@ export const {
64
64
  Every function in the app is then built from these — raw `query`/`mutation`
65
65
  imports from `_generated/server` appear **only** in this file.
66
66
 
67
+ ## One shared Effect function foundation
68
+
69
+ Use `createEffectAuthFunctions` from `cvx-kit/effect` in `convex/functions.ts`
70
+ to configure all twelve constructors once. Its first argument is the same
71
+ `AuthFunctionsConfig` used by `createAuthFunctions`; its second argument has
72
+ six explicit policies: `query`, `mutation`, `action`, `systemQuery`,
73
+ `systemMutation`, and `systemAction`. Each accepts `services`, `context`, and
74
+ `mapError`, like `effectZodApiBuilder`. Pass `{}` when no additions are needed.
75
+
76
+ Type the auth config with your generated `DataModel` and role vocabulary,
77
+ then let the factory infer its generics from both arguments. Do not supply
78
+ partial generic arguments, which would fix the remaining inference defaults.
79
+ The following public-package example defines a reusable app factory; the
80
+ host passes its auth, membership, trigger, and RLS policy as `config`.
81
+
82
+ <!-- packed-effect-example -->
83
+
84
+ ```ts
85
+ import {
86
+ defineSchema,
87
+ defineTable,
88
+ makeFunctionReference,
89
+ type DataModelFromSchemaDefinition,
90
+ type GenericQueryCtx,
91
+ type GenericMutationCtx,
92
+ } from 'convex/server'
93
+ import { v } from 'convex/values'
94
+ import { Effect } from 'effect'
95
+ import { z } from 'zod'
96
+ import type { AuthFunctionsConfig, AuthBundle } from 'cvx-kit/auth'
97
+ import { createEffectAuthFunctions, createEffectFoundation } from 'cvx-kit/effect'
98
+
99
+ const schema = defineSchema({ notes: defineTable({ tenant: v.string(), title: v.string() }) })
100
+ type Model = DataModelFromSchemaDefinition<typeof schema>
101
+ type Role = 'owner' | 'viewer'
102
+ const input = z.object({ title: z.string().trim().min(1) })
103
+ const saveRef = makeFunctionReference<'mutation', z.input<typeof input>, null>('notes:save')
104
+
105
+ export function createApplicationFunctions(config: AuthFunctionsConfig<Model, Role>) {
106
+ const foundation = createEffectFoundation({
107
+ observability: {
108
+ enabled: false,
109
+ classifyError: () => ({ outcome: 'failed', errorCode: 'FAILED' }),
110
+ },
111
+ writeAudit: () => undefined, // supply the app's transactional audit writer in production
112
+ })
113
+ const queries = foundation.Query({
114
+ context: (ctx: (GenericQueryCtx<Model> | GenericMutationCtx<Model>) & AuthBundle<Role>) => ctx,
115
+ operations: ({ query }) => ({
116
+ actor: query({
117
+ input: z.object({}),
118
+ result: z.string(),
119
+ handler: (_input, ctx) => ctx.actor.userId,
120
+ }),
121
+ }),
122
+ })
123
+ const commands = foundation.Command({
124
+ context: (ctx: GenericMutationCtx<Model> & AuthBundle<Role>) => ctx,
125
+ operations: ({ command }) => ({
126
+ save: command({
127
+ input,
128
+ result: z.null(),
129
+ classification: 'business',
130
+ audit: () => null,
131
+ handler: (args, ctx) =>
132
+ Effect.promise(async () => {
133
+ await ctx.db.insert('notes', { title: args.title, tenant: ctx.tenant })
134
+ return null
135
+ }),
136
+ }),
137
+ }),
138
+ })
139
+ const functions = createEffectAuthFunctions(config, {
140
+ query: { context: (ctx) => ({ queries: queries.withContext(ctx) }) },
141
+ mutation: {
142
+ context: (ctx) => ({
143
+ queries: queries.withContext(ctx),
144
+ commands: commands.withContext(ctx),
145
+ }),
146
+ },
147
+ action: {
148
+ context: (ctx) => ({
149
+ commands: {
150
+ save: (args: z.input<typeof input>) =>
151
+ Effect.promise(() => ctx.runMutation(saveRef, args)),
152
+ },
153
+ }),
154
+ },
155
+ systemQuery: {},
156
+ systemMutation: {},
157
+ systemAction: {},
158
+ })
159
+ const save = functions.roleMutation('owner')({
160
+ args: input.shape,
161
+ returns: z.null(),
162
+ handler: (ctx, args) => ctx.commands.exec('save', args),
163
+ })
164
+ return { ...functions, save }
165
+ }
166
+ ```
167
+
168
+ Export the returned constructors once from `convex/functions.ts`, and export
169
+ `save` at the registered `notes:save` mutation path. Endpoint modules can then
170
+ use `ctx.commands` or `ctx.queries` directly. The callbacks run per invocation
171
+ after auth, role checks, triggers/RLS wrapping, and live action membership
172
+ refresh. Binding is lazy; do not cache an authenticated `withContext` handle
173
+ at module scope. Denied requests never run the callbacks.
174
+
175
+ | Policy | Available authority and capabilities |
176
+ | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
+ | `query` | Trusted auth bundle, secured DB reader, `include`; bind read registries only. |
178
+ | `mutation` | Trusted auth bundle, secured triggered DB writer, `include`; bind queries and mutation commands. |
179
+ | `action` | Live-verified auth bundle, `runQuery`/`runMutation`; use action-compatible registries or forward to registered functions. No DB or `include`. |
180
+ | `systemQuery` / `systemMutation` / `systemAction` | Existing internal authority, configured separately. No fabricated actor or inherited public handles; system mutations retain triggers and bypass public RLS as before. |
181
+
182
+ Every auth/role/admin constructor of a kind shares that kind's policy. System
183
+ policies are deliberately explicit; choose their trusted capabilities rather
184
+ than synthesizing public credentials. An action forwarding to `runMutation`
185
+ creates a separate registered mutation transaction; it does not turn the
186
+ whole action into a transaction or carry its live role into that mutation's
187
+ JWT-based auth policy.
188
+
189
+ Additions cannot replace native or auth context keys: static checks reject
190
+ known collisions and runtime checks also reject inherited keys before the
191
+ handler executes. Put class-based handles under named properties in the
192
+ returned plain record. Scoped services, required Effect services, Zod raw and
193
+ parsed argument/result types, function references, and error projection retain
194
+ the existing adapter's behavior. Let failures escape registered mutations so
195
+ nested command writes, triggers, and audit writes roll back together.
196
+
67
197
  ## Shared Effect execution after authentication
68
198
 
69
- Wrap the constructors returned by `createAuthFunctions` with
199
+ For an individual constructor, wrap the builder returned by `createAuthFunctions` with
70
200
  `effectZodApiBuilder` from `cvx-kit/effect`. The original constructor resolves
71
201
  authentication, authorization, tenant policy, and the wrapped database before
72
202
  the service provider runs. Keep that ordering when adding Effect; use the Zod
@@ -241,7 +241,7 @@ The public API layer then wraps the executor in an `authMutation` and passes
241
241
  8. **Complete**: await the closure returned by `prepare`, including when audit
242
242
  returns null. A failure propagates inside the observation boundary.
243
243
  9. **Emit the observation**: `{ operation, classification, outcome,
244
- errorCode?, durationMs }` — completed, denied, or failed per
244
+ errorCode?, durationMs }` — completed, denied, or failed per
245
245
  `classifyError`.
246
246
 
247
247
  ### Guards and permissions
@@ -509,3 +509,5 @@ knowing. Metadata defaults are set at construction and merged per-executor.
509
509
  telemetry silently disappears (see regexes above).
510
510
  5. Keep operation registries frozen (`as const`) in the domain's
511
511
  `commands.ts`; vocabulary tuples live in `constants.ts`.
512
+
513
+ See [Optional domain contracts](./domain-contracts.md) for shared declarations, named implementations, and registry audit defaults in both Effect and Promise registries.
@@ -121,7 +121,7 @@ another file's function to shorten an import.
121
121
  | `schema.ts` | `defineSchema(domainTables)` | table definitions, validators |
122
122
  | `functions.ts` | the single `createAuthFunctions<DataModel>()` call and its exported constructors | handlers, business policy beyond the injected config |
123
123
  | `triggers.ts` | `createTriggers()`, `timestamps`/`appendOnly`/`noDelete` registrations, calls to per-entity `register<Entity>Triggers` | trigger _logic_ for a specific entity (that lives in the entity) |
124
- | `foundation.ts` | the single `createEffectFoundation(...)`, bound command/query exports | command definitions |
124
+ | `foundation.ts` | one `createEffectFoundation(...)` or `createFoundation(...)`, bound command/query exports | command definitions |
125
125
  | `<component>.ts` | `new <Client>(components.<name>)` + minimal admin plumbing | workflow/business definitions |
126
126
  | `http.ts` | route registration delegating request handling to the owning domain | webhook business logic |
127
127
  | `crons.ts` | `cronJobs()` declarations targeting `internal.domain.<entity>...` | handler logic |
@@ -155,7 +155,7 @@ moves to the domain.
155
155
 
156
156
  ### `domain/<entity>/schema.ts` — shapes only
157
157
 
158
- - The entity's single `zodTable(...)` call, with `serverFields`,
158
+ - The module's `zodTable(...)` declarations (one per owned table), with `serverFields`,
159
159
  `commandFields`, `publicFields` decided deliberately.
160
160
  - DTO compositions derived from the table's boundaries
161
161
  (`z.object({ ...<entity>.publicDto.shape, ... }).strict()`).
@@ -318,7 +318,7 @@ component internals (anything below a component's client facade),
318
318
  - Zod objects that cross a boundary are `.strict()` — always.
319
319
  - `Object.freeze` for registries and long-lived configuration objects.
320
320
  - Comments state constraints the code can't (`// Optional only for rows
321
- created before <epoch>.`) — never narration of the next line.
321
+ created before <epoch>.`) — never narration of the next line.
322
322
  - One entity concept per file; when a file serves two purposes, split it
323
323
  along the anatomy in §2.
324
324
 
@@ -327,3 +327,7 @@ Optional request context can be added through the existing Effect API adapter's
327
327
  and consume `ctx.commands.exec(name, input)` through the application's existing
328
328
  function helper names. Keep dependencies local to each feature; explicit execution
329
329
  and input-only command handlers remain supported. See [optional context injection](./command-context.md).
330
+
331
+ ### Reserved domain declarations
332
+
333
+ Existing commands.ts and queries.ts each export one kit-backed Command or Query registry. Contracts.ts contains schemas, an optional defineDomainContract declaration, and an optional root error contract; handlers stay in their registries. See [Optional domain contracts](./domain-contracts.md).
@@ -0,0 +1,115 @@
1
+ # Optional domain contracts
2
+
3
+ Declare schemas and command classification once in `contracts.ts`, then attach
4
+ the declaration to a Command or Query registry. Errors belong at the root beside
5
+ commands and queries. Either section and the error contract may be omitted.
6
+
7
+ The regular registry uses `createFoundation` from `cvx-kit`; the Effect registry
8
+ uses `createEffectFoundation` from `cvx-kit/effect`. Both accept the same contract
9
+ and named implementation helpers. Existing inline Effect definitions and the
10
+ older `new Foundation(...)` constructors continue to work.
11
+
12
+ ```ts
13
+ import { z } from 'zod'
14
+ import { createFoundation } from 'cvx-kit'
15
+ import { defineDomainContract } from 'cvx-kit/contracts'
16
+ import { defineErrorContract } from 'cvx-kit/errors'
17
+
18
+ const contract = defineDomainContract({
19
+ errors: defineErrorContract({ Missing: { message: 'Task missing', details: {} } }),
20
+ commands: {
21
+ 'tasks.create': {
22
+ input: z.string().transform(Number),
23
+ result: z.number().transform((count) => ({ count })),
24
+ classification: 'business',
25
+ },
26
+ 'tasks.close': {
27
+ input: z.object({ id: z.string() }),
28
+ result: z.boolean(),
29
+ classification: 'business',
30
+ },
31
+ },
32
+ queries: { 'tasks.get': { input: z.number(), result: z.number() } },
33
+ })
34
+ const { Command, Query } = createFoundation({
35
+ observability: {
36
+ enabled: false,
37
+ classifyError: () => ({ outcome: 'failed', errorCode: 'FAILED' }),
38
+ },
39
+ writeAudit: (_host: { actorId: string }, entry) => {
40
+ console.log(entry.operation)
41
+ },
42
+ })
43
+ const commands = Command({
44
+ contract,
45
+ context: (host: { actorId: string }) => host,
46
+ audit: ({ operation, input, result }, context) => {
47
+ if (operation !== 'tasks.create') return null
48
+ return {
49
+ operation,
50
+ actorId: context.actorId,
51
+ aggregate: { type: 'task', id: String(input) },
52
+ metadata: { count: result.count },
53
+ }
54
+ },
55
+ operations: ({ command }) => ({
56
+ 'tasks.create': command['tasks.create']({ handler: (input) => input + 1 }),
57
+ 'tasks.close': command['tasks.close']({
58
+ handler: (input) => input.id.length > 0,
59
+ audit: () => null,
60
+ }),
61
+ }),
62
+ })
63
+ const queries = Query({
64
+ contract,
65
+ context: (host: { actorId: string }) => host,
66
+ operations: ({ query }) => ({ 'tasks.get': query['tasks.get']({ handler: (input) => input }) }),
67
+ })
68
+ const created = await commands.exec('tasks.create', '2', { actorId: 'actor' })
69
+ const count = await queries.withContext({ actorId: 'actor' }).exec('tasks.get', created.count)
70
+ void count
71
+ ```
72
+
73
+ Each map must implement exactly its declared keys, including maps supplied through
74
+ variables. A helper for one key cannot implement another key. Input, result,
75
+ classification, replayResult, and wire come from the declaration and cannot be
76
+ overridden in the implementation. Construction snapshots operation records and
77
+ map membership without cloning validators or running user callbacks.
78
+
79
+ The handler receives decoded input, returns the result schema's accepted input,
80
+ and execution returns its decoded result. Normal handlers and hooks return values
81
+ or Promises. Effect handlers and hooks may also return Effects; their error and
82
+ service channels remain part of the selected operation's type.
83
+
84
+ For independent wire projections, use the existing `operationContract` helper
85
+ inside a domain declaration. It infers the projector's parameter from the result
86
+ schema; provide both its replayResult and wire fields. A command may instead
87
+ supply only replayResult directly in its domain entry. A query cannot prepare,
88
+ audit, declare aggregates, or declare replayResult.
89
+
90
+ A root audit receives `{ operation, input, result }` and resolved base context.
91
+ Its operation discriminates the corresponding input and result types. An
92
+ operation's audit override receives the existing `{ command, result }` envelope
93
+ and replaces the root audit. Exactly one callback runs after fresh-result
94
+ validation. Returning null skips the write. Replay skips handlers, audits,
95
+ writes, and completion; permission still runs before replay. Each command needs
96
+ a definite audit source. A definite override excludes the root audit's Effect
97
+ channels; an optional override retains channels from both possible callbacks.
98
+
99
+ Inline registries can also use root audit, leaving schemas and classification
100
+ on their operation definitions. The legacy constructor API keeps its existing
101
+ context-first execution signatures; the new regular registry uses input-first
102
+ handlers and `exec(operation, input, host)` like Effect.
103
+
104
+ Pass `contract.errors` explicitly to the existing `createEffectApi({ errors })`
105
+ option. Attaching a contract does not change a globally configured API. The
106
+ original error-contract instance still owns projection: foreign and forged errors
107
+ become UnknownFailure. Wire exposure remains explicit through `.expose(owner, key)`.
108
+
109
+ Enable `cvx/reserved-domain-file-anatomy` together with
110
+ `cvx/domain-file-responsibilities` to enforce reserved filenames. A domain's
111
+ commands.ts and queries.ts each export one genuine bound registry. Contracts are
112
+ declarative; schema.ts may declare several zodTable shapes; table.ts owns their
113
+ topology, and domain/table.ts uses createModule to assemble module maps. The rules
114
+ recognize aliases, namespaces, and the root foundation.ts facade. They do not
115
+ require absent reserved files to be created.
package/docs/effect.md CHANGED
@@ -384,12 +384,20 @@ only when you want a typed error channel.
384
384
  The [command guide](./commands.md) documents the current lifecycle and migration
385
385
  steps; the [deprecated API reference](./commands-legacy.md) retains old examples.
386
386
  The [authentication guide](./auth.md)
387
- shows how to wrap trusted custom builders with `effectZodApiBuilder`, so auth,
387
+ shows the shared `createEffectAuthFunctions(config, policies)` factory and how
388
+ to wrap individual trusted builders with `effectZodApiBuilder`, so auth,
388
389
  tenancy, validators, row-level policies, and triggers run through their
389
390
  existing builder before Effect request services are provisioned.
390
391
 
391
- Optional request context can be added through the existing Effect API adapter's
392
+ For an app-wide foundation, configure `query`, `mutation`, and `action` policies
393
+ once with `createEffectAuthFunctions` and explicit separate system policies.
394
+ Query handles bind readers, mutation handles bind the secured triggered writer,
395
+ and action handles use `runMutation` rather than a mutation database.
396
+
397
+ Optional request context can also be added through the existing Effect API adapter's
392
398
  `context` callback. Bind selected registries with `commands.withContext(value)`
393
399
  and consume `ctx.commands.exec(name, input)` through the application's existing
394
400
  function helper names. Keep dependencies local to each feature; explicit execution
395
401
  and input-only command handlers remain supported. See [optional context injection](./command-context.md).
402
+
403
+ See [Optional domain contracts](./domain-contracts.md) for shared declarations, named implementations, and registry audit defaults in both Effect and Promise registries.
package/docs/llms.md CHANGED
@@ -22,7 +22,7 @@
22
22
  cvx-kit is a reusable Convex application kit: zod table boundaries, auth-aware
23
23
  function constructors, a trigger registry, an audited command protocol
24
24
  (Foundation component), and a declarative approvals component. Peer deps:
25
- `convex ^1.45.0`, `zod ^4.5.4`; command implementations also install
25
+ `convex ^1.46.0`, `zod ^4.5.4`; command implementations also install
26
26
  `effect >=4.0.1 <5` and import `cvx-kit/effect`.
27
27
 
28
28
  **Command API selection:** use `createEffectFoundation` and declare handlers
@@ -38,12 +38,13 @@ copy that API into new implementations.
38
38
  `convex/functions.ts`** (the `createAuthFunctions` call). All other
39
39
  functions use `authQuery/authMutation/authAction`, `roleQuery/...`,
40
40
  `adminQuery/...` (public) or `systemQuery/systemMutation/systemAction` (internal),
41
- wrapped once with `effectZodApiBuilder` when their handlers return Effects.
41
+ built once with `createEffectAuthFunctions(config, policies)` from `cvx-kit/effect`
42
+ for shared Effect handles, or individually wrapped with `effectZodApiBuilder`.
42
43
  This is what guarantees auth, triggers, and bounded reads.
43
44
  3. **Every public query returns DTOs** via `<table>.toPublicDto(row)` —
44
45
  runtime redaction, not just types.
45
46
  4. **Every read is bounded**: `ctx.include(ctx.db.query('t')).matching(...)
46
- .execute(limit)` with `1 ≤ limit ≤ 100`. `.resolve()` falls back to a full
47
+ .execute(limit)` with `1 ≤ limit ≤ 100`. `.resolve()` falls back to a full
47
48
  table scan — avoid it.
48
49
  5. **State changes are Effect commands**: declare
49
50
  `Command({ context, operations: ({ command }) => ({ ... }), defaults })`.
@@ -78,23 +79,23 @@ copy that API into new implementations.
78
79
 
79
80
  ## Exports map
80
81
 
81
- | Import | Provides |
82
- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
83
- | `cvx-kit` | Non-Effect helpers and component clients; import the optional Effect facade explicitly from `cvx-kit/effect`. |
84
- | `cvx-kit/zod-table` | `zodTable`, `tenantTable`, `createModule`, `paginated`, `zodVariantTable`, `jsonSafeZid`, `TIMESTAMP_FIELDS` |
85
- | `cvx-kit/effect` | Recommended command/query API: `createEffectFoundation`, `effectApiBuilder`, `effectZodApiBuilder`, `createEffectCrud`, idempotency/workflow adapters and operation tools. |
86
- | `cvx-kit/auth` | `createAuthFunctions` (incl. optional `security` RLS config), `createInclude`, `defaultRoleMap` |
87
- | `cvx-kit/tenancy` | `createTenantRules`, `composeRules`, `requireTenantReference`, `assertTenantOwned`, `TENANT_FIELD` |
88
- | `cvx-kit/crud` | Compatibility CRUD factory using the deprecated Promise command facade; use `createEffectCrud` from `cvx-kit/effect` for new domains. |
89
- | `cvx-kit/state-machine` | `createStateMachine` — typed transitions from constants tuples; `assert` drops into command guards |
90
- | `cvx-kit/middleware` | `rateLimit` — packaged middleware over an injected rate-limiter instance; keyed by `ctx.tenant`, missing key = config error |
91
- | `cvx-kit/webhooks` | `createWebhookBoundary`, `recordWebhookEvent`, `webhookEventsTable` — raw-body verify, natural-key dedup in the mutation |
92
- | `cvx-kit/agent-tools` | `createAgentTools` — tool records from table masks; mutation handlers route through command executors |
93
- | `cvx-kit/triggers` | `createTriggers`, `timestamps`, `appendOnly`, `noDelete`, `tenantOwnership`, `Triggers` |
94
- | `cvx-kit/errors` | `KitError`, `defaultErrors`, `ErrorFactory` |
95
- | `cvx-kit/components/foundation` | Component client and types; `Foundation.Command` is deprecated. The component and other capabilities remain available. New command registries use `cvx-kit/effect`. |
96
- | `cvx-kit/components/approvals` | `Approvals` client; default export = component config for `app.use` |
97
- | `cvx-kit/test` | `registerFoundation(t)`, `registerApprovals(t)` for convex-test |
82
+ | Import | Provides |
83
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
84
+ | `cvx-kit` | Non-Effect helpers and component clients; import the optional Effect facade explicitly from `cvx-kit/effect`. |
85
+ | `cvx-kit/zod-table` | `zodTable`, `tenantTable`, `createModule`, `paginated`, `zodVariantTable`, `jsonSafeZid`, `TIMESTAMP_FIELDS` |
86
+ | `cvx-kit/effect` | Recommended command/query API: `createEffectFoundation`, `createEffectAuthFunctions`, `effectApiBuilder`, `effectZodApiBuilder`, `createEffectCrud`, idempotency/workflow adapters and operation tools. |
87
+ | `cvx-kit/auth` | `createAuthFunctions` (incl. optional `security` RLS config), `createInclude`, `defaultRoleMap` |
88
+ | `cvx-kit/tenancy` | `createTenantRules`, `composeRules`, `requireTenantReference`, `assertTenantOwned`, `TENANT_FIELD` |
89
+ | `cvx-kit/crud` | Compatibility CRUD factory using the deprecated Promise command facade; use `createEffectCrud` from `cvx-kit/effect` for new domains. |
90
+ | `cvx-kit/state-machine` | `createStateMachine` — typed transitions from constants tuples; `assert` drops into command guards |
91
+ | `cvx-kit/middleware` | `rateLimit` — packaged middleware over an injected rate-limiter instance; keyed by `ctx.tenant`, missing key = config error |
92
+ | `cvx-kit/webhooks` | `createWebhookBoundary`, `recordWebhookEvent`, `webhookEventsTable` — raw-body verify, natural-key dedup in the mutation |
93
+ | `cvx-kit/agent-tools` | `createAgentTools` — tool records from table masks; mutation handlers route through command executors |
94
+ | `cvx-kit/triggers` | `createTriggers`, `timestamps`, `appendOnly`, `noDelete`, `tenantOwnership`, `Triggers` |
95
+ | `cvx-kit/errors` | `KitError`, `defaultErrors`, `ErrorFactory` |
96
+ | `cvx-kit/components/foundation` | Component client and types; `Foundation.Command` is deprecated. The component and other capabilities remain available. New command registries use `cvx-kit/effect`. |
97
+ | `cvx-kit/components/approvals` | `Approvals` client; default export = component config for `app.use` |
98
+ | `cvx-kit/test` | `registerFoundation(t)`, `registerApprovals(t)` for convex-test |
98
99
 
99
100
  ## Minimal app wiring (the five root files)
100
101
 
@@ -291,3 +292,23 @@ Optional request context can be added through the existing Effect API adapter's
291
292
  and consume `ctx.commands.exec(name, input)` through the application's existing
292
293
  function helper names. Keep dependencies local to each feature; explicit execution
293
294
  and input-only command handlers remain supported. See [optional context injection](./command-context.md).
295
+
296
+ ## Shared auth and Effect context
297
+
298
+ Configure `createEffectAuthFunctions` once in `convex/functions.ts` using a typed
299
+ `AuthFunctionsConfig<DataModel, Role>` and inferred factory generics. Supply six
300
+ explicit policies: `query`, `mutation`, `action`, `systemQuery`, `systemMutation`,
301
+ and `systemAction`; use `{}` for no additions. Each supports `services`,
302
+ `context`, and `mapError`. All auth/role/admin constructors share the policy
303
+ for their kind. Services and context initialize only after successful auth and
304
+ role checks, using the secured DB and refreshed live action membership.
305
+
306
+ Bind query registries only in queries. Mutations can bind queries and commands
307
+ with their secured triggered writer. Actions have no DB; bind action-compatible
308
+ registries or delegate with `runMutation`/`runQuery`. Each registered mutation
309
+ has its own transaction and JWT auth; an action is not one transaction. Configure
310
+ internal system authority separately, without fabricated actors or inherited
311
+ public handles. Return plain records with handles under named keys; native/auth
312
+ key collisions are rejected, including inherited keys at runtime. Keep bound
313
+ handles request-local, return lazy Effects, and let failures escape mutations
314
+ for rollback. See `auth.md` for a packed-compiled shared factory example.
package/docs/oxlint.md CHANGED
@@ -144,3 +144,7 @@ Shared ownership follows static runtime imports through shared helpers. A shared
144
144
  file reached from only one domain is reported for relocation. Unused code,
145
145
  type-only consumers, and computed dependencies are not treated as proof of shared
146
146
  ownership; their ownership still needs review.
147
+
148
+ ## Reserved domain files
149
+
150
+ Enable `cvx/reserved-domain-file-anatomy` to require exported Command/Query registries, declarative contracts, zodTable shapes, and table topology or createModule assembly. Constructor provenance is traced through imported aliases, namespaces, direct Foundation factories, and the root foundation.ts facade. Pair it with `cvx/domain-file-responsibilities` to reject handlers and database/runtime work in contracts.ts. Backend scope and test/generated exclusions apply; absent files are not required.