cvx-kit 0.1.5 → 0.1.7

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 (124) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +21 -3
  3. package/dist/agent-tools.d.mts +2 -1
  4. package/dist/agent-tools.mjs +1 -1
  5. package/dist/agent-tools.mjs.map +1 -1
  6. package/dist/components/approvals/audit.mjs +1 -1
  7. package/dist/components/foundation/client.d.mts +8 -7
  8. package/dist/components/foundation/client.mjs +1 -1
  9. package/dist/components/foundation/client.mjs.map +1 -1
  10. package/dist/components/foundation/modules/command/command.d.mts +50 -10
  11. package/dist/components/foundation/modules/command/command.mjs +1 -1
  12. package/dist/components/foundation/modules/command/command.mjs.map +1 -1
  13. package/dist/components/foundation/modules/command/lifecycle.d.mts +81 -0
  14. package/dist/components/foundation/modules/command/lifecycle.mjs +2 -0
  15. package/dist/components/foundation/modules/command/lifecycle.mjs.map +1 -0
  16. package/dist/components/foundation/modules/observability/observability.d.mts +8 -0
  17. package/dist/components/foundation/modules/observability/observability.mjs +1 -1
  18. package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
  19. package/dist/components/foundation/operation.d.mts +6 -6
  20. package/dist/components/foundation/operation.mjs.map +1 -1
  21. package/dist/contracts.d.mts +4 -0
  22. package/dist/contracts.mjs +1 -0
  23. package/dist/crud.d.mts +2 -2
  24. package/dist/crud.mjs.map +1 -1
  25. package/dist/effect.d.mts +12 -0
  26. package/dist/effect.mjs +1 -0
  27. package/dist/errors.d.mts +2 -1
  28. package/dist/errors.mjs +1 -1
  29. package/dist/errors.mjs.map +1 -1
  30. package/dist/idempotency.d.mts +2 -0
  31. package/dist/idempotency.mjs +1 -0
  32. package/dist/modules/contracts/contract.d.mts +70 -0
  33. package/dist/modules/contracts/contract.mjs +2 -0
  34. package/dist/modules/contracts/contract.mjs.map +1 -0
  35. package/dist/modules/contracts/convex.d.mts +22 -0
  36. package/dist/modules/contracts/convex.mjs +2 -0
  37. package/dist/modules/contracts/convex.mjs.map +1 -0
  38. package/dist/modules/contracts/errors.d.mts +44 -0
  39. package/dist/modules/contracts/errors.mjs +2 -0
  40. package/dist/modules/contracts/errors.mjs.map +1 -0
  41. package/dist/modules/contracts/exposure.d.mts +105 -0
  42. package/dist/modules/contracts/exposure.mjs +2 -0
  43. package/dist/modules/contracts/exposure.mjs.map +1 -0
  44. package/dist/modules/contracts/idempotency.d.mts +87 -0
  45. package/dist/modules/contracts/idempotency.mjs +2 -0
  46. package/dist/modules/contracts/idempotency.mjs.map +1 -0
  47. package/dist/modules/contracts/standard.d.mts +16 -0
  48. package/dist/modules/contracts/standard.mjs +2 -0
  49. package/dist/modules/contracts/standard.mjs.map +1 -0
  50. package/dist/modules/contracts/workflow.d.mts +167 -0
  51. package/dist/modules/contracts/workflow.mjs +2 -0
  52. package/dist/modules/contracts/workflow.mjs.map +1 -0
  53. package/dist/modules/effect/agent-tools.d.mts +2 -0
  54. package/dist/modules/effect/agent-tools.mjs +1 -0
  55. package/dist/modules/effect/api-runtime.d.mts +28 -0
  56. package/dist/modules/effect/api-runtime.mjs +2 -0
  57. package/dist/modules/effect/api-runtime.mjs.map +1 -0
  58. package/dist/modules/effect/api-zod.d.mts +35 -0
  59. package/dist/modules/effect/api-zod.mjs +2 -0
  60. package/dist/modules/effect/api-zod.mjs.map +1 -0
  61. package/dist/modules/effect/api.d.mts +21 -0
  62. package/dist/modules/effect/api.mjs +2 -0
  63. package/dist/modules/effect/api.mjs.map +1 -0
  64. package/dist/modules/effect/client.d.mts +11 -0
  65. package/dist/modules/effect/client.mjs +1 -0
  66. package/dist/modules/effect/command.d.mts +59 -0
  67. package/dist/modules/effect/command.mjs +2 -0
  68. package/dist/modules/effect/command.mjs.map +1 -0
  69. package/dist/modules/effect/crud.d.mts +785 -0
  70. package/dist/modules/effect/crud.mjs +2 -0
  71. package/dist/modules/effect/crud.mjs.map +1 -0
  72. package/dist/modules/effect/errors.d.mts +8 -0
  73. package/dist/modules/effect/errors.mjs +2 -0
  74. package/dist/modules/effect/errors.mjs.map +1 -0
  75. package/dist/modules/effect/foundation.d.mts +21 -0
  76. package/dist/modules/effect/foundation.mjs +2 -0
  77. package/dist/modules/effect/foundation.mjs.map +1 -0
  78. package/dist/modules/effect/idempotency.d.mts +31 -0
  79. package/dist/modules/effect/idempotency.mjs +2 -0
  80. package/dist/modules/effect/idempotency.mjs.map +1 -0
  81. package/dist/modules/effect/normalize.d.mts +10 -0
  82. package/dist/modules/effect/normalize.mjs +2 -0
  83. package/dist/modules/effect/normalize.mjs.map +1 -0
  84. package/dist/modules/effect/operation.d.mts +141 -0
  85. package/dist/modules/effect/operation.mjs +2 -0
  86. package/dist/modules/effect/operation.mjs.map +1 -0
  87. package/dist/modules/effect/query.d.mts +65 -0
  88. package/dist/modules/effect/query.mjs +2 -0
  89. package/dist/modules/effect/query.mjs.map +1 -0
  90. package/dist/modules/effect/schema.d.mts +33 -0
  91. package/dist/modules/effect/schema.mjs +2 -0
  92. package/dist/modules/effect/schema.mjs.map +1 -0
  93. package/dist/modules/effect/workflow.d.mts +77 -0
  94. package/dist/modules/effect/workflow.mjs +2 -0
  95. package/dist/modules/effect/workflow.mjs.map +1 -0
  96. package/dist/node_modules/@standard-schema/spec/dist/index.d.mts +79 -0
  97. package/dist/oxlint/architecture.d.mts +20 -0
  98. package/dist/oxlint/architecture.mjs +2 -0
  99. package/dist/oxlint/architecture.mjs.map +1 -0
  100. package/dist/oxlint/ast.d.mts +33 -0
  101. package/dist/oxlint/ast.mjs +2 -0
  102. package/dist/oxlint/ast.mjs.map +1 -0
  103. package/dist/oxlint/ownership.d.mts +62 -0
  104. package/dist/oxlint/ownership.mjs +2 -0
  105. package/dist/oxlint/ownership.mjs.map +1 -0
  106. package/dist/oxlint.d.mts +5 -2
  107. package/dist/oxlint.mjs +1 -1
  108. package/dist/oxlint.mjs.map +1 -1
  109. package/dist/workflow.d.mts +2 -0
  110. package/dist/workflow.mjs +1 -0
  111. package/docs/auth.md +65 -0
  112. package/docs/commands.md +118 -0
  113. package/docs/conventions.md +49 -21
  114. package/docs/domain-operations.md +615 -0
  115. package/docs/effect.md +384 -0
  116. package/docs/oxlint.md +74 -26
  117. package/docs/upgrading.md +13 -0
  118. package/package.json +37 -3
  119. package/scripts/convex-smoke-env.mjs +33 -0
  120. package/scripts/smoke-packed-components.mjs +9 -2
  121. package/scripts/smoke-packed-effect.mjs +439 -0
  122. package/scripts/test-packed-effect.mjs +457 -0
  123. package/scripts/test-packed-ids.mjs +1 -0
  124. package/scripts/test-packed-oxlint.mjs +24 -2
package/dist/oxlint.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{z as e}from"zod";import{basename as t,dirname as n,relative as r,resolve as i,sep as a}from"node:path";const o=e.object({convexDir:e.string().min(1).default(`convex`)}),s=l({"root-facade-ownership":{meta:_(`Configure shared kit infrastructure once in its root facade.`,{facade:`Configure {{name}} once in convex/{{file}}; import that facade elsewhere.`}),create(e){if(v(e,e.filename))return{};let t=new Set;function n(n){let r=d(e,n.callee);if(!r)return;let i=null;[`cvx-kit`,`cvx-kit/auth`].includes(r.source)&&r.name===`createAuthFunctions`&&(i=`functions.ts`),[`cvx-kit`,`cvx-kit/triggers`].includes(r.source)&&r.name===`createTriggers`&&(i=`triggers.ts`),[`cvx-kit`,`cvx-kit/components/foundation`].includes(r.source)&&r.name===`Foundation`&&(i=`foundation.ts`),i&&((u(e)!==i||t.has(r.name))&&e.report({node:n,messageId:`facade`,data:{name:r.name,file:i}}),t.add(r.name))}return{CallExpression:n,NewExpression:n}}},"no-raw-builders":{meta:_(`Build host functions through the auth-aware root constructors.`,{builder:`Import raw builders only in convex/functions.ts; use auth*/role*/admin* or system* elsewhere.`}),create(e){return u(e)===`functions.ts`||v(e,e.filename)?{}:{ImportDeclaration(t){if(t.importKind===`type`)return;let n=t.source.value;if(!(!/(?:^|\/)_generated\/server(?:\.[cm]?[jt]s)?$/.test(n)&&n!==`convex/server`))for(let r of t.specifiers){if(r.type===`ImportSpecifier`&&r.importKind===`type`)continue;let t=r.type===`ImportSpecifier`?C(r.imported):null;(t===null&&n!==`convex/server`||t&&/^(?:internal)?(?:Query|Mutation|Action)(?:Generic)?$|^(?:query|mutation|action)(?:Generic)?$/.test(t))&&e.report({node:r,messageId:`builder`})}},CallExpression(t){let n=d(e,t.callee);n?.source===`convex/server`&&/^(?:internal)?(?:Query|Mutation|Action)Generic$|^(?:query|mutation|action)Generic$/.test(n.name)&&t.callee.type===`MemberExpression`&&e.report({node:t,messageId:`builder`})}}}},"no-handwritten-references":{meta:_(`Use generated api/internal references.`,{reference:`Use generated api/internal references instead of makeFunctionReference.`}),create(e){return{CallExpression(t){let n=d(e,t.callee);n?.source===`convex/server`&&n.name===`makeFunctionReference`&&e.report({node:t,messageId:`reference`})}}}},"domain-import-boundaries":{meta:_(`Keep api, application, domain, and shared dependencies directed.`,{boundary:`Follow api → application/domain → shared; sibling modules expose only rules.ts and queries.ts.`}),create(e){let r=u(e);return f(e,(a,o)=>{if(!r||!o.startsWith(`.`))return;let s=u(e,i(n(e.filename),o))?.replace(/\.[cm]?[jt]s$/,``);if(!s)return;let c=r.split(`/`),l=s.split(`/`),d=!1;c[0]===`domain`?(d=[`api`,`application`].includes(l[0]),l[0]===`domain`&&c[1]!==l[1]&&l[1]!==`shared`&&c[1]!==`table.ts`&&(d=c[1]===`shared`||![`rules`,`queries`].includes(l.slice(2).join(`/`)))):c[0]===`api`?d=l[0]===`api`||l[0]===`domain`&&l[1]!==`shared`&&l[1]!==t(r).replace(/\.[cm]?[jt]s$/,``):c[0]===`application`&&(d=l[0]===`api`),d&&e.report({node:a,messageId:`boundary`})})}},"public-functions-in-api":{meta:_(`Expose authenticated public functions only through api/.`,{public:`Declare public auth*/role*/admin* functions in convex/api/<module>.ts.`}),create(e){return u(e)?.startsWith(`api/`)?{}:{CallExpression(t){let n=d(e,t.callee);t.callee.type!==`CallExpression`&&n&&p(e,n.source)&&/^(auth|role|admin)(Query|Mutation|Action)$/.test(n.name)&&e.report({node:t,messageId:`public`})}}}},"thin-api-adapters":{meta:_(`Keep business decisions and writes in domain executors.`,{adapter:`Move business branches, private helpers, and database writes into the owning domain executor.`}),create(e){return u(e)?.startsWith(`api/`)?{IfStatement(t){e.report({node:t,messageId:`adapter`})},SwitchStatement(t){e.report({node:t,messageId:`adapter`})},ConditionalExpression(t){e.report({node:t,messageId:`adapter`})},Program(t){let n=S(t);for(let r of t.body)if(r.type===`FunctionDeclaration`&&r.id&&!n.has(r.id.name)&&e.report({node:r,messageId:`adapter`}),r.type===`VariableDeclaration`)for(let t of r.declarations)t.id.type===`Identifier`&&!n.has(t.id.name)&&[`ArrowFunctionExpression`,`FunctionExpression`].includes(t.init?.type??``)&&e.report({node:t,messageId:`adapter`})},CallExpression(t){h(t)&&e.report({node:t,messageId:`adapter`})}}:{}}},"no-inline-enums":{meta:_(`Keep vocabularies in named constants.ts tuples.`,{vocabulary:`Define a named readonly tuple in constants.ts and pass it to z.enum().`}),create(e){return{CallExpression(t){let n=d(e,t.callee),r=t.arguments[0];for(;r?.type===`TSAsExpression`||r?.type===`TSSatisfiesExpression`;)r=r.expression;n&&/^zod(?:\/v4)?$/.test(n.source)&&n.name===`enum`&&r?.type===`ArrayExpression`&&e.report({node:t,messageId:`vocabulary`})}}}},"no-unbounded-reads":{meta:_(`Bound collection reads through include or pagination.`,{bounded:`Use include(...).execute(limit) or bounded pagination; do not collect() or resolve() an unbounded query.`}),create(e){return v(e,e.filename)?{}:{CallExpression(t){if(t.callee.type!==`MemberExpression`)return;let n=b(t.callee);(n===`collect`&&g(t.callee.object,`query`)||n===`resolve`&&g(t.callee.object,`include`))&&e.report({node:t,messageId:`bounded`})}}}},"no-manual-timestamps":{meta:_(`Let timestamps triggers own createdAt and updatedAt.`,{timestamp:`Remove {{field}} from this write; register the timestamps trigger instead.`}),create(e){return v(e,e.filename)?{}:{CallExpression(t){if(!h(t))return;let n=t.arguments.at(-1);if(n?.type===`ObjectExpression`)for(let t of n.properties){if(t.type!==`Property`)continue;let n=t.computed?y(t.key):C(t.key);(n===`createdAt`||n===`updatedAt`)&&e.report({node:t,messageId:`timestamp`,data:{field:n}})}}}}},"component-boundaries":{meta:_(`Keep portable components isolated and consume their client facades.`,{boundary:`Components may import only their own files and npm dependencies; inject host policy through the client API.`,facade:`Import a component through its client.ts facade, not its private implementation.`}),create(e){let o=v(e,e.filename);return f(e,(s,c)=>{if(!c.startsWith(`.`)){/^cvx-kit\/components\/[^/]+\//.test(c)&&!/^cvx-kit\/components\/[^/]+\/convex\.config(?:\.js)?$/.test(c)&&e.report({node:s,messageId:`facade`});return}let l=i(n(e.filename),c);o&&(r(o,l).startsWith(`..${a}`)||l===n(o))?e.report({node:s,messageId:`boundary`}):!o&&v(e,l)&&!/^client(?:\.[cm]?[jt]s)?$/.test(t(l))&&!(u(e)===`convex.config.ts`&&/^convex\.config(?:\.[cm]?[jt]s)?$/.test(t(l)))&&e.report({node:s,messageId:`facade`})})}},"no-component-env":{meta:_(`Pass environment configuration into portable components.`,{env:`Portable components must receive configuration through arguments; do not read process.env.`}),create(e){if(!v(e,e.filename))return{};function t(t){if(t.type!==`Identifier`)return!1;let n=x(e,t);return t.name===`process`&&!n?.defs.length||n?.defs.some(e=>e.type===`ImportBinding`&&e.parent?.type===`ImportDeclaration`&&[`node:process`,`process`].includes(e.parent.source.value)&&[`ImportDefaultSpecifier`,`ImportNamespaceSpecifier`].includes(e.node.type))}return{ImportDeclaration(t){if([`node:process`,`process`].includes(t.source.value))for(let n of t.specifiers)n.type===`ImportSpecifier`&&C(n.imported)===`env`&&e.report({node:n,messageId:`env`})},MemberExpression(n){if(b(n)!==`env`)return;let r=n.object,i=r.type===`MemberExpression`&&b(r)===`process`&&r.object.type===`Identifier`&&r.object.name===`globalThis`&&!x(e,r.object)?.defs.length;(t(r)||i)&&e.report({node:n,messageId:`env`})},VariableDeclarator(n){if(n.init&&t(n.init)&&n.id.type===`ObjectPattern`)for(let t of n.id.properties)t.type===`Property`&&(t.computed?y(t.key):C(t.key))===`env`&&e.report({node:t,messageId:`env`})}}}},"schema-file-boundaries":{meta:_(`Keep storage shapes and indexes in their documented owners.`,{schema:`{{builder}} belongs in {{file}}.`}),create(e){return{CallExpression(n){let r=d(e,n.callee),i=u(e);if(!i)return;let a=null;r?.source===`convex/server`&&(r.name===`defineSchema`&&i!==`schema.ts`&&!(v(e,e.filename)&&t(i)===`schema.ts`)&&(a=`the root schema.ts (or a component schema.ts)`),r.name===`defineTable`&&!(v(e,e.filename)&&[`schema.ts`,`table.ts`].includes(t(i)))&&(a=`a component schema.ts/table.ts; host tables use zodTable in domain/<module>/schema.ts`)),r&&[`cvx-kit`,`cvx-kit/zod-table`].includes(r.source)&&[`zodTable`,`tenantTable`,`zodVariantTable`].includes(r.name)&&!/^domain\/[^/]+\/schema\.ts$/.test(i)&&(a=`domain/<module>/schema.ts`),a&&e.report({node:n,messageId:`schema`,data:{builder:r?.name??`table`,file:a}}),n.callee.type===`MemberExpression`&&[`index`,`searchIndex`,`vectorIndex`].includes(b(n.callee)??``)&&m(n.callee.object)&&!v(e,e.filename)&&!/^domain\/[^/]+\/table\.ts$/.test(i)&&e.report({node:n,messageId:`schema`,data:{builder:`Table indexes`,file:`domain/<module>/table.ts`}})}}}},"no-internal-reexports":{meta:_(`Import from owning modules directly; consumer apps have no barrels.`,{reexport:`Import from the owning file directly; do not re-export imports inside convex/.`}),create(e){function t(t){if(t.source){e.report({node:t,messageId:`reexport`});return}for(let n of t.type===`ExportNamedDeclaration`?t.specifiers:[])x(e,n.local)?.defs.some(e=>e.type===`ImportBinding`)&&e.report({node:n,messageId:`reexport`})}return{ExportNamedDeclaration:t,ExportAllDeclaration:t,ExportDefaultDeclaration(t){t.declaration.type===`Identifier`&&x(e,t.declaration)?.defs.some(e=>e.type===`ImportBinding`)&&e.report({node:t,messageId:`reexport`})}}}},"public-api-first":{meta:_(`Place private helper implementations after the exported API.`,{order:`Move this private helper below the exported API so the file reads from public purpose to implementation.`}),create(e){return{Program(t){let n=S(t),r=t.body.findLastIndex(e=>e.type.startsWith(`Export`));for(let i of t.body.slice(0,r<0?0:r))i.type===`FunctionDeclaration`&&i.id&&!n.has(i.id.name)&&e.report({node:i,messageId:`order`})}}}},"named-private-helpers":{meta:_(`Use named declarations for top-level private helper functions.`,{helper:`Declare this private helper with function name(...) at the bottom of the file.`}),create(e){return{Program(t){let n=S(t);for(let r of t.body)if(r.type===`VariableDeclaration`)for(let t of r.declarations)t.id.type===`Identifier`&&!n.has(t.id.name)&&(t.init?.type===`ArrowFunctionExpression`||t.init?.type===`FunctionExpression`)&&e.report({node:t,messageId:`helper`})}}}}}),c={meta:{name:`cvx`},rules:s};function l(e){let t={...e},n=Object.keys(e);for(let r of n){let n=e[r];t[r]={meta:{...n.meta,schema:[{type:`object`,properties:{convexDir:{type:`string`,minLength:1}},additionalProperties:!1}]},create(e){let t=u(e);return!t||/(?:^|\/)(?:_generated|__tests__)(?:\/|$)/.test(t)||/\.(?:test|spec)\.[cm]?[jt]s$/.test(t)?{}:n.create(e)}}}return t}function u(e,t=e.filename){let n=i(e.cwd,o.parse(e.options[0]??{}).convexDir),s=r(n,t).split(a).join(`/`);return s===`..`||s.startsWith(`../`)||s.startsWith(`/`)?null:s}function d(e,t){if(t.type===`CallExpression`)return d(e,t.callee);if(t.type===`MemberExpression`){let n=d(e,t.object),r=b(t);return n&&r&&[`*`,`z`,`default`].includes(n.name)?{source:n.source,name:r}:null}if(t.type!==`Identifier`)return null;for(let n of x(e,t)?.defs??[]){if(n.type!==`ImportBinding`||n.parent?.type!==`ImportDeclaration`||n.parent.importKind===`type`)continue;let e=n.node;if(e.type!==`ImportSpecifier`||e.importKind!==`type`)return{source:n.parent.source.value,name:e.type===`ImportSpecifier`?C(e.imported)??``:e.type===`ImportDefaultSpecifier`?`default`:`*`}}return null}function f(e,t){function n(e,n){let r=y(n);r&&t(e,r)}return{ImportDeclaration(e){n(e,e.source)},ExportNamedDeclaration(e){n(e,e.source)},ExportAllDeclaration(e){n(e,e.source)},ImportExpression(e){n(e,e.source)},TSImportType(e){n(e,e.source)},CallExpression(t){t.callee.type===`Identifier`&&t.callee.name===`require`&&!x(e,t.callee)?.defs.length&&n(t,t.arguments[0])}}}function p(e,t){return t.startsWith(`.`)&&u(e,i(n(e.filename),t))?.replace(/\.[cm]?[jt]s$/,``)===`functions`}function m(e){return e.type===`MemberExpression`?b(e)===`table`||m(e.object):e.type===`CallExpression`&&m(e.callee)}function h(e){return e.callee.type===`MemberExpression`&&[`insert`,`patch`,`replace`,`delete`].includes(b(e.callee)??``)&&e.callee.object.type===`MemberExpression`&&b(e.callee.object)===`db`}function g(e,t){return e.type===`CallExpression`?e.callee.type===`MemberExpression`&&b(e.callee)===t&&(t!==`query`||b(e.callee.object)===`db`)||g(e.callee,t):e.type===`MemberExpression`&&g(e.object,t)}function _(e,t){return{type:`problem`,docs:{description:e},schema:[],messages:t}}function v(e,t){let n=i(e.cwd,o.parse(e.options[0]??{}).convexDir),s=r(n,t).split(a).join(`/`).match(/^components\/([^/]+)(?:\/|$)/);return s?i(n,`components`,s[1]):null}function y(e){return e?.type===`Literal`&&`value`in e&&String(e.value)===e.value?String(e.value):e?.type===`TemplateLiteral`&&e.expressions.length===0?e.quasis[0].value.cooked:null}function b(e){return e.type===`MemberExpression`?e.computed?y(e.property):e.property.name:null}function x(e,t){if(t.type!==`Identifier`)return null;let n=e.sourceCode.getScope(t);for(;n;){let e=n.set.get(t.name);if(e)return e;n=n.upper}return null}function S(e){let t=new Set;for(let n of e.body)if(n.type===`ExportNamedDeclaration`&&!n.source)for(let e of n.specifiers)t.add(C(e.local)??``);else n.type===`ExportDefaultDeclaration`&&n.declaration.type===`Identifier`&&t.add(n.declaration.name);return t}function C(e){return e.type===`Identifier`?e.name:y(e)}export{c as default,s as rules};
1
+ import{createResolver as e,inspectArchitecture as t,resolveImport as n}from"./oxlint/architecture.mjs";import{appFile as r,binding as i,componentRoot as a,exportName as o,exportedNames as s,hasCall as c,imported as l,isDbWrite as u,isFunctionsImport as d,isTableChain as f,metadata as p,propertyName as m,sourceVisitors as h,staticString as g}from"./oxlint/ast.mjs";import{ownershipRules as _}from"./oxlint/ownership.mjs";import{basename as v,dirname as y,relative as b,sep as x}from"node:path";function S(e={}){return t(e)}const C=T({..._,"root-facade-ownership":{meta:p(`Configure shared kit infrastructure once in its root facade.`,{facade:`Configure {{name}} once in convex/{{file}}; import that facade elsewhere.`}),create(e){if(a(e,e.filename))return{};let t=new Set;function n(n){let i=l(e,n.callee);if(!i)return;let a=null;[`cvx-kit`,`cvx-kit/auth`].includes(i.source)&&i.name===`createAuthFunctions`&&(a=`functions.ts`),[`cvx-kit`,`cvx-kit/triggers`].includes(i.source)&&i.name===`createTriggers`&&(a=`triggers.ts`),[`cvx-kit`,`cvx-kit/components/foundation`].includes(i.source)&&i.name===`Foundation`&&(a=`foundation.ts`),a&&((r(e)!==a||t.has(i.name))&&e.report({node:n,messageId:`facade`,data:{name:i.name,file:a}}),t.add(i.name))}return{CallExpression:n,NewExpression:n}}},"no-raw-builders":{meta:p(`Build host functions through the auth-aware root constructors.`,{builder:`Import raw builders only in convex/functions.ts; use auth*/role*/admin* or system* elsewhere.`}),create(t){if(r(t)===`functions.ts`||a(t,t.filename))return{};let i=e();return{ImportDeclaration(e){if(e.importKind===`type`)return;let a=e.source.value,s=n(i,t.filename,a),c=s&&r(t,s)?.startsWith(`_generated/`)?r(t,s):a;if(!(!/(?:^|\/)_generated\/server(?:\.[cm]?[jt]s)?$/.test(c)&&c!==`convex/server`))for(let n of e.specifiers){if(n.type===`ImportSpecifier`&&n.importKind===`type`)continue;let e=n.type===`ImportSpecifier`?o(n.imported):null;(e===null&&c!==`convex/server`||e&&/^(?:internal)?(?:Query|Mutation|Action)(?:Generic)?$|^(?:query|mutation|action)(?:Generic)?$/.test(e))&&t.report({node:n,messageId:`builder`})}},CallExpression(e){let n=l(t,e.callee);n?.source===`convex/server`&&/^(?:internal)?(?:Query|Mutation|Action)Generic$|^(?:query|mutation|action)Generic$/.test(n.name)&&e.callee.type===`MemberExpression`&&t.report({node:e,messageId:`builder`})}}}},"no-handwritten-references":{meta:p(`Use generated api/internal references.`,{reference:`Use generated api/internal references instead of makeFunctionReference.`}),create(e){return{CallExpression(t){let n=l(e,t.callee);n?.source===`convex/server`&&n.name===`makeFunctionReference`&&e.report({node:t,messageId:`reference`})}}}},"domain-import-boundaries":{meta:p(`Keep api, application, domain, and shared dependencies directed.`,{boundary:`Follow api → application/domain → shared; sibling modules expose only rules.ts and queries.ts.`}),create(t){let i=r(t),a=e();return h(t,(e,o)=>{if(!i)return;let s=n(a,t.filename,o);if(!s)return;let c=r(t,s)?.replace(/\.[cm]?[jt]s$/,``);if(!c){s.split(x).includes(`node_modules`)||t.report({node:e,messageId:`boundary`});return}let l=i.split(`/`),u=c.split(`/`),d=!1;l[0]===`domain`?(d=[`api`,`application`,`migrations`].includes(u[0]),u[0]===`domain`&&l[1]!==u[1]&&u[1]!==`shared`&&l[1]!==`table.ts`&&(d=l[1]===`shared`||![`rules`,`queries`].includes(u.slice(2).join(`/`)))):l[0]===`api`?d=[`api`,`migrations`].includes(u[0])||u[0]===`domain`&&u[1]!==`shared`&&u[1]!==(l.length>2?l[1]:v(i).replace(/\.[cm]?[jt]s$/,``)):l[0]===`application`&&(d=[`api`,`migrations`].includes(u[0])),u[0]===`migrations`&&l[0]!==`migrations`&&(d=!0),[`api`,`domain`,`application`].includes(l[0])&&u.length>1&&![`api`,`domain`,`application`,`_generated`,`components`,`migrations`].includes(u[0])&&(d=!0),d&&t.report({node:e,messageId:`boundary`})})}},"public-functions-in-api":{meta:p(`Expose authenticated public functions only through api/.`,{public:`Declare public auth*/role*/admin* functions in convex/api/<module>.ts.`}),create(e){return r(e)?.startsWith(`api/`)?{}:{CallExpression(t){let n=l(e,t.callee);t.callee.type!==`CallExpression`&&n&&d(e,n.source)&&/^(auth|role|admin)(Query|Mutation|Action)$/.test(n.name)&&e.report({node:t,messageId:`public`})}}}},"thin-api-adapters":{meta:p(`Keep business decisions and writes in domain executors.`,{adapter:`Move business branches, private helpers, and database writes into the owning domain executor.`}),create(e){return r(e)?.startsWith(`api/`)?{IfStatement(t){e.report({node:t,messageId:`adapter`})},SwitchStatement(t){e.report({node:t,messageId:`adapter`})},ConditionalExpression(t){e.report({node:t,messageId:`adapter`})},Program(t){let n=s(t);for(let r of t.body)if(r.type===`FunctionDeclaration`&&r.id&&!n.has(r.id.name)&&e.report({node:r,messageId:`adapter`}),r.type===`VariableDeclaration`)for(let t of r.declarations)t.id.type===`Identifier`&&!n.has(t.id.name)&&[`ArrowFunctionExpression`,`FunctionExpression`].includes(t.init?.type??``)&&e.report({node:t,messageId:`adapter`})},CallExpression(t){u(t)&&e.report({node:t,messageId:`adapter`})}}:{}}},"no-inline-enums":{meta:p(`Keep vocabularies in named constants.ts tuples.`,{vocabulary:`Define a named readonly tuple in constants.ts and pass it to z.enum().`}),create(e){return{CallExpression(t){let n=l(e,t.callee),r=t.arguments[0];for(;r?.type===`TSAsExpression`||r?.type===`TSSatisfiesExpression`;)r=r.expression;n&&/^zod(?:\/v4)?$/.test(n.source)&&n.name===`enum`&&r?.type===`ArrayExpression`&&e.report({node:t,messageId:`vocabulary`})}}}},"no-unbounded-reads":{meta:p(`Bound collection reads through include or pagination.`,{bounded:`Use include(...).execute(limit) or bounded pagination; do not collect() or resolve() an unbounded query.`}),create(e){return a(e,e.filename)?{}:{CallExpression(t){if(t.callee.type!==`MemberExpression`)return;let n=m(t.callee);(n===`collect`&&c(t.callee.object,`query`)||n===`resolve`&&c(t.callee.object,`include`))&&e.report({node:t,messageId:`bounded`})}}}},"no-manual-timestamps":{meta:p(`Let timestamps triggers own createdAt and updatedAt.`,{timestamp:`Remove {{field}} from this write; register the timestamps trigger instead.`}),create(e){return a(e,e.filename)?{}:{CallExpression(t){if(!u(t))return;let n=t.arguments.at(-1);if(n?.type===`ObjectExpression`)for(let t of n.properties){if(t.type!==`Property`)continue;let n=t.computed?g(t.key):o(t.key);(n===`createdAt`||n===`updatedAt`)&&e.report({node:t,messageId:`timestamp`,data:{field:n}})}}}}},"component-boundaries":{meta:p(`Keep portable components isolated and consume their client facades.`,{boundary:`Components may import only their own files and npm dependencies; inject host policy through the client API.`,facade:`Import a component through its client.ts facade, not its private implementation.`}),create(t){let i=a(t,t.filename),o=e();return h(t,(e,s)=>{s.startsWith(`.`)||/^cvx-kit\/components\/[^/]+\//.test(s)&&!/^cvx-kit\/components\/[^/]+\/convex\.config(?:\.js)?$/.test(s)&&t.report({node:e,messageId:`facade`});let c=n(o,t.filename,s);!c||c.split(x).includes(`node_modules`)||(i&&(b(i,c).startsWith(`..${x}`)||c===y(i))?t.report({node:e,messageId:`boundary`}):!i&&a(t,c)&&!/^client(?:\.[cm]?[jt]s)?$/.test(v(c))&&!(r(t)===`convex.config.ts`&&/^convex\.config(?:\.[cm]?[jt]s)?$/.test(v(c)))&&t.report({node:e,messageId:`facade`}))})}},"no-component-env":{meta:p(`Pass environment configuration into portable components.`,{env:`Portable components must receive configuration through arguments; do not read process.env.`}),create(e){if(!a(e,e.filename))return{};function t(t){if(t.type!==`Identifier`)return!1;let n=i(e,t);return t.name===`process`&&!n?.defs.length||n?.defs.some(e=>e.type===`ImportBinding`&&e.parent?.type===`ImportDeclaration`&&[`node:process`,`process`].includes(e.parent.source.value)&&[`ImportDefaultSpecifier`,`ImportNamespaceSpecifier`].includes(e.node.type))}return{ImportDeclaration(t){if([`node:process`,`process`].includes(t.source.value))for(let n of t.specifiers)n.type===`ImportSpecifier`&&o(n.imported)===`env`&&e.report({node:n,messageId:`env`})},MemberExpression(n){if(m(n)!==`env`)return;let r=n.object,a=r.type===`MemberExpression`&&m(r)===`process`&&r.object.type===`Identifier`&&r.object.name===`globalThis`&&!i(e,r.object)?.defs.length;(t(r)||a)&&e.report({node:n,messageId:`env`})},VariableDeclarator(n){if(n.init&&t(n.init)&&n.id.type===`ObjectPattern`)for(let t of n.id.properties)t.type===`Property`&&(t.computed?g(t.key):o(t.key))===`env`&&e.report({node:t,messageId:`env`})}}}},"schema-file-boundaries":{meta:p(`Keep storage shapes and indexes in their documented owners.`,{schema:`{{builder}} belongs in {{file}}.`}),create(e){return{CallExpression(t){let n=l(e,t.callee),i=r(e);if(!i)return;let o=null;n?.source===`convex/server`&&(n.name===`defineSchema`&&i!==`schema.ts`&&!(a(e,e.filename)&&v(i)===`schema.ts`)&&(o=`the root schema.ts (or a component schema.ts)`),n.name===`defineTable`&&!(a(e,e.filename)&&[`schema.ts`,`table.ts`].includes(v(i)))&&(o=`a component schema.ts/table.ts; host tables use zodTable in domain/<module>/schema.ts`)),n&&[`cvx-kit`,`cvx-kit/zod-table`].includes(n.source)&&[`zodTable`,`tenantTable`,`zodVariantTable`].includes(n.name)&&!/^domain\/[^/]+\/schema\.ts$/.test(i)&&(o=`domain/<module>/schema.ts`),o&&e.report({node:t,messageId:`schema`,data:{builder:n?.name??`table`,file:o}}),t.callee.type===`MemberExpression`&&[`index`,`searchIndex`,`vectorIndex`].includes(m(t.callee)??``)&&f(t.callee.object)&&!a(e,e.filename)&&!/^domain\/[^/]+\/table\.ts$/.test(i)&&e.report({node:t,messageId:`schema`,data:{builder:`Table indexes`,file:`domain/<module>/table.ts`}})}}}},"no-internal-reexports":{meta:p(`Import from owning modules directly; consumer apps have no barrels.`,{reexport:`Import from the owning file directly; do not re-export imports inside convex/.`}),create(e){function t(t){if(t.source){e.report({node:t,messageId:`reexport`});return}for(let n of t.type===`ExportNamedDeclaration`?t.specifiers:[])i(e,n.local)?.defs.some(e=>e.type===`ImportBinding`)&&e.report({node:n,messageId:`reexport`})}return{ExportNamedDeclaration:t,ExportAllDeclaration:t,ExportDefaultDeclaration(t){t.declaration.type===`Identifier`&&i(e,t.declaration)?.defs.some(e=>e.type===`ImportBinding`)&&e.report({node:t,messageId:`reexport`})}}}},"public-api-first":{meta:p(`Place private helper implementations after the exported API.`,{order:`Move this private helper below the exported API so the file reads from public purpose to implementation.`}),create(e){return{Program(t){let n=s(t),r=t.body.findLastIndex(e=>e.type.startsWith(`Export`));for(let i of t.body.slice(0,r<0?0:r))i.type===`FunctionDeclaration`&&i.id&&!n.has(i.id.name)&&e.report({node:i,messageId:`order`})}}}},"named-private-helpers":{meta:p(`Use named declarations for top-level private helper functions.`,{helper:`Declare this private helper with function name(...) at the bottom of the file.`}),create(e){return{Program(t){let n=s(t);for(let r of t.body)if(r.type===`VariableDeclaration`)for(let t of r.declarations)t.id.type===`Identifier`&&!n.has(t.id.name)&&(t.init?.type===`ArrowFunctionExpression`||t.init?.type===`FunctionExpression`)&&e.report({node:t,messageId:`helper`})}}}}}),w={meta:{name:`cvx`},rules:C};function T(e){let t={...e},n=Object.keys(e);for(let i of n){let n=e[i];t[i]={meta:{...n.meta,schema:[{type:`object`,properties:{convexDir:{type:`string`,minLength:1}},additionalProperties:!1}]},create(e){let t=r(e);return!t||/(?:^|\/)(?:_generated|__tests__)(?:\/|$)/.test(t)||/\.(?:test|spec)\.[cm]?[jt]s$/.test(t)?{}:n.create(e)}}}return t}export{S as checkArchitecture,w as default,C as rules};
2
2
  //# sourceMappingURL=oxlint.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"oxlint.mjs","names":[],"sources":["../src/oxlint.ts"],"sourcesContent":["import { basename, dirname, relative, resolve, sep } from 'node:path'\nimport type {\n\tContext,\n\tCreateRule,\n\tESTree,\n\tPlugin,\n\tRule,\n\tRuleMeta,\n\tScope,\n\tVisitor,\n} from '@oxlint/plugins'\nimport { z } from 'zod'\n\nconst options = z.object({ convexDir: z.string().min(1).default('convex') })\n\nexport type RuleName =\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'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\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (node.importKind === 'type') return\n\t\t\t\t\tconst source = node.source.value\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\treturn sourceVisitors(context, (node, source) => {\n\t\t\t\tif (!file || !source.startsWith('.')) return\n\t\t\t\tconst target = appFile(context, resolve(dirname(context.filename), source))?.replace(\n\t\t\t\t\t/\\.[cm]?[jt]s$/,\n\t\t\t\t\t'',\n\t\t\t\t)\n\t\t\t\tif (!target) return\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'].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\tto[0] === 'api' ||\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] !== basename(file).replace(/\\.[cm]?[jt]s$/, ''))\n\t\t\t\t} else if (from[0] === 'application') forbidden = to[0] === 'api'\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\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\treturn\n\t\t\t\t}\n\t\t\t\tconst target = resolve(dirname(context.filename), specifier)\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\nfunction appFile(context: Context, filename = context.filename) {\n\tconst root = resolve(context.cwd, options.parse(context.options[0] ?? {}).convexDir)\n\tconst file = relative(root, filename).split(sep).join('/')\n\treturn file === '..' || file.startsWith('../') || file.startsWith('/') ? null : file\n}\n\nfunction imported(context: Context, node: ESTree.Node): { source: string; name: string } | null {\n\tif (node.type === 'CallExpression') return imported(context, node.callee)\n\tif (node.type === 'MemberExpression') {\n\t\tconst owner = imported(context, node.object)\n\t\tconst name = propertyName(node)\n\t\treturn owner && name && ['*', 'z', 'default'].includes(owner.name)\n\t\t\t? { source: owner.source, name }\n\t\t\t: null\n\t}\n\tif (node.type !== 'Identifier') return null\n\tfor (const def of binding(context, node)?.defs ?? []) {\n\t\tif (\n\t\t\tdef.type !== 'ImportBinding' ||\n\t\t\tdef.parent?.type !== 'ImportDeclaration' ||\n\t\t\tdef.parent.importKind === 'type'\n\t\t)\n\t\t\tcontinue\n\t\tconst specifier = def.node\n\t\tif (specifier.type === 'ImportSpecifier' && specifier.importKind === 'type') continue\n\t\treturn {\n\t\t\tsource: def.parent.source.value,\n\t\t\tname:\n\t\t\t\tspecifier.type === 'ImportSpecifier'\n\t\t\t\t\t? (exportName(specifier.imported) ?? '')\n\t\t\t\t\t: specifier.type === 'ImportDefaultSpecifier'\n\t\t\t\t\t\t? 'default'\n\t\t\t\t\t\t: '*',\n\t\t}\n\t}\n\treturn null\n}\n\nfunction sourceVisitors(\n\tcontext: Context,\n\tcheck: (node: ESTree.Node, source: string) => void,\n): Visitor {\n\tfunction visit(node: ESTree.Node, source: ESTree.Node | null | undefined) {\n\t\tconst text = staticString(source)\n\t\tif (text) check(node, text)\n\t}\n\treturn {\n\t\tImportDeclaration(node) {\n\t\t\tvisit(node, node.source)\n\t\t},\n\t\tExportNamedDeclaration(node) {\n\t\t\tvisit(node, node.source)\n\t\t},\n\t\tExportAllDeclaration(node) {\n\t\t\tvisit(node, node.source)\n\t\t},\n\t\tImportExpression(node) {\n\t\t\tvisit(node, node.source)\n\t\t},\n\t\tTSImportType(node) {\n\t\t\tvisit(node, node.source)\n\t\t},\n\t\tCallExpression(node) {\n\t\t\tif (\n\t\t\t\tnode.callee.type === 'Identifier' &&\n\t\t\t\tnode.callee.name === 'require' &&\n\t\t\t\t!binding(context, node.callee)?.defs.length\n\t\t\t)\n\t\t\t\tvisit(node, node.arguments[0])\n\t\t},\n\t}\n}\n\nfunction isFunctionsImport(context: Context, source: string) {\n\treturn (\n\t\tsource.startsWith('.') &&\n\t\tappFile(context, resolve(dirname(context.filename), source))?.replace(/\\.[cm]?[jt]s$/, '') ===\n\t\t\t'functions'\n\t)\n}\n\nfunction isTableChain(node: ESTree.Node): boolean {\n\tif (node.type === 'MemberExpression')\n\t\treturn propertyName(node) === 'table' || isTableChain(node.object)\n\treturn node.type === 'CallExpression' && isTableChain(node.callee)\n}\n\nfunction isDbWrite(node: ESTree.CallExpression) {\n\treturn (\n\t\tnode.callee.type === 'MemberExpression' &&\n\t\t['insert', 'patch', 'replace', 'delete'].includes(propertyName(node.callee) ?? '') &&\n\t\tnode.callee.object.type === 'MemberExpression' &&\n\t\tpropertyName(node.callee.object) === 'db'\n\t)\n}\n\nfunction hasCall(node: ESTree.Node, name: string): boolean {\n\tif (node.type === 'CallExpression')\n\t\treturn (\n\t\t\t(node.callee.type === 'MemberExpression' &&\n\t\t\t\tpropertyName(node.callee) === name &&\n\t\t\t\t(name !== 'query' || propertyName(node.callee.object) === 'db')) ||\n\t\t\thasCall(node.callee, name)\n\t\t)\n\tif (node.type === 'MemberExpression') return hasCall(node.object, name)\n\treturn false\n}\n\nfunction metadata(description: string, messages: Record<string, string>): RuleMeta {\n\treturn { type: 'problem', docs: { description }, schema: [], messages }\n}\n\nfunction componentRoot(context: Context, filename: string) {\n\tconst root = resolve(context.cwd, options.parse(context.options[0] ?? {}).convexDir)\n\tconst file = relative(root, filename).split(sep).join('/')\n\tconst match = file.match(/^components\\/([^/]+)(?:\\/|$)/)\n\treturn match ? resolve(root, 'components', match[1]) : null\n}\n\nfunction staticString(node: ESTree.Node | null | undefined) {\n\tif (node?.type === 'Literal' && 'value' in node && String(node.value) === node.value)\n\t\treturn String(node.value)\n\tif (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n\t\treturn node.quasis[0].value.cooked\n\treturn null\n}\n\nfunction propertyName(node: ESTree.Node): string | null {\n\tif (node.type !== 'MemberExpression') return null\n\treturn node.computed ? staticString(node.property) : node.property.name\n}\n\nfunction binding(context: Context, node: ESTree.Node) {\n\tif (node.type !== 'Identifier') return null\n\tlet scope: Scope | null = context.sourceCode.getScope(node)\n\twhile (scope) {\n\t\tconst variable = scope.set.get(node.name)\n\t\tif (variable) return variable\n\t\tscope = scope.upper\n\t}\n\treturn null\n}\n\nfunction exportedNames(program: ESTree.Program) {\n\tconst names = new Set<string>()\n\tfor (const statement of program.body) {\n\t\tif (statement.type === 'ExportNamedDeclaration' && !statement.source) {\n\t\t\tfor (const specifier of statement.specifiers) names.add(exportName(specifier.local) ?? '')\n\t\t} else if (\n\t\t\tstatement.type === 'ExportDefaultDeclaration' &&\n\t\t\tstatement.declaration.type === 'Identifier'\n\t\t) {\n\t\t\tnames.add(statement.declaration.name)\n\t\t}\n\t}\n\treturn names\n}\n\nfunction exportName(node: ESTree.Node): string | null {\n\treturn node.type === 'Identifier' ? node.name : staticString(node)\n}\n"],"mappings":"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"}
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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"}
@@ -0,0 +1,2 @@
1
+ import { ReceiptProtectedWorkflowMutation, WorkflowBinding, WorkflowBindingError, WorkflowExecution, WorkflowHostState, WorkflowHostUpdate, WorkflowNativeStatus, WorkflowOutcome, WorkflowReceiptAdapter, WorkflowStepArguments, WorkflowStepOptions, WorkflowStepRunner, WorkflowTransition, receiptWorkflowMutation, runWorkflowStep, terminalWorkflowAttempt, workflowAttempt, workflowBinding, workflowCancel, workflowExecution, workflowReceiptInvocation, workflowRestart, workflowStatusBindings, workflowTransition } from "./modules/contracts/workflow.mjs";
2
+ export { type ReceiptProtectedWorkflowMutation, type WorkflowBinding, WorkflowBindingError, type WorkflowExecution, type WorkflowHostState, type WorkflowHostUpdate, type WorkflowNativeStatus, type WorkflowOutcome, type WorkflowReceiptAdapter, type WorkflowStepArguments, type WorkflowStepOptions, type WorkflowStepRunner, type WorkflowTransition, receiptWorkflowMutation, runWorkflowStep, terminalWorkflowAttempt, workflowAttempt, workflowBinding, workflowCancel, workflowExecution, workflowReceiptInvocation, workflowRestart, workflowStatusBindings, workflowTransition };
@@ -0,0 +1 @@
1
+ import{WorkflowBindingError as e,receiptWorkflowMutation as t,runWorkflowStep as n,terminalWorkflowAttempt as r,workflowAttempt as i,workflowBinding as a,workflowCancel as o,workflowExecution as s,workflowReceiptInvocation as c,workflowRestart as l,workflowStatusBindings as u,workflowTransition as d}from"./modules/contracts/workflow.mjs";export{e as WorkflowBindingError,t as receiptWorkflowMutation,n as runWorkflowStep,r as terminalWorkflowAttempt,i as workflowAttempt,a as workflowBinding,o as workflowCancel,s as workflowExecution,c as workflowReceiptInvocation,l as workflowRestart,u as workflowStatusBindings,d as workflowTransition};
package/docs/auth.md CHANGED
@@ -64,6 +64,71 @@ 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
+ ## Shared Effect execution after authentication
68
+
69
+ Wrap the constructors returned by `createAuthFunctions` with
70
+ `effectZodApiBuilder` from `cvx-kit/effect`. The original constructor resolves
71
+ authentication, authorization, tenant policy, and the wrapped database before
72
+ the service provider runs. Keep that ordering when adding Effect; use the Zod
73
+ adapter for these custom constructors.
74
+
75
+ This factory accepts the configured `authMutation` from the setup above. The
76
+ actor service comes from trusted `ctx.actor`, with no actor or organization
77
+ argument from callers:
78
+
79
+ <!-- packed-effect-example -->
80
+
81
+ ```ts
82
+ import type { GenericDataModel } from 'convex/server'
83
+ import { createAuthFunctions } from 'cvx-kit/auth'
84
+ import { effectZodApiBuilder } from 'cvx-kit/effect'
85
+ import { Context, Effect } from 'effect'
86
+ import { z } from 'zod'
87
+
88
+ class Actor extends Context.Service<
89
+ Actor,
90
+ { userId: string; organizationId: string; role: string }
91
+ >()('app/Actor') {}
92
+
93
+ export function createEffectAccountApi<DataModel extends GenericDataModel>(
94
+ authMutation: ReturnType<typeof createAuthFunctions<DataModel>>['authMutation'],
95
+ ) {
96
+ const effectAuthMutation = effectZodApiBuilder(authMutation, {
97
+ services: (ctx) => Context.make(Actor, ctx.actor),
98
+ })
99
+ const currentActor = effectAuthMutation({
100
+ args: {},
101
+ returns: z.object({ userId: z.string(), organizationId: z.string() }),
102
+ handler: () =>
103
+ Effect.gen(function* () {
104
+ const actor = yield* Actor
105
+ return { userId: actor.userId, organizationId: actor.organizationId }
106
+ }),
107
+ })
108
+ return { effectAuthMutation, currentActor }
109
+ }
110
+ ```
111
+
112
+ Call `createEffectAccountApi(authMutation)` with the constructor exported by
113
+ your `convex/functions.ts`, then export `currentActor` from the account API
114
+ module. The factory preserves the configured auth policy; it does not choose
115
+ an identity provider or membership authority.
116
+
117
+ Declare the provider once per constructor, then reuse the builder across
118
+ endpoints. Database services should capture the provider's `ctx.db`, so
119
+ trigger and RLS enforcement also applies to Effect-driven writes. Services
120
+ are created per invocation; do not cache an authenticated context in a module
121
+ singleton. Query providers receive readers; HTTP integrations belong in
122
+ actions.
123
+
124
+ The adapter runs the composed Effect within an invocation scope and awaits
125
+ finalizers before returning. Domain errors can be mapped with handler pipes
126
+ or shared `mapError`; they remain thrown failures at the native boundary so
127
+ mutation writes roll back. Native argument/result validators remain in place.
128
+ Effect's inferred server-side error and service channels are not a typed
129
+ transport error contract. See [the Effect guide](./effect.md) for complete
130
+ domain registries, error mapping, scoped services, and tracing.
131
+
67
132
  ## The constructor families
68
133
 
69
134
  | Constructor | Visibility | Auth | Extra |
package/docs/commands.md CHANGED
@@ -37,6 +37,124 @@ export const { Command, Query, observability } = new Foundation(components.found
37
37
  Host code consumes the Foundation **only through this facade** — never deep
38
38
  imports of the component's internal modules.
39
39
 
40
+ For new domains, [the optional Effect facade](./effect.md) offers cohesive
41
+ command/query entries with handlers in the declaration and injectable context.
42
+ The complete guide covers ordinary, `Effect.gen`, and `Effect.fn` handlers,
43
+ nested domain integrations, and shared API execution.
44
+
45
+ ## Incremental migration to Effect
46
+
47
+ The existing Foundation keeps separate operation and handler declarations,
48
+ context-first handler arguments, and Promise executors. No existing consumer
49
+ needs to change when another domain adopts `cvx-kit/effect`.
50
+
51
+ This legacy factory accepts the mounted Foundation component and defines all
52
+ of its domain dependencies through the host context:
53
+
54
+ <!-- packed-effect-example -->
55
+
56
+ ```ts
57
+ import { Foundation, type AuditEntryInput } from 'cvx-kit/components/foundation'
58
+ import { z } from 'zod'
59
+
60
+ type Host = {
61
+ actorId: string
62
+ rename: (id: string, title: string) => Promise<void>
63
+ appendAudit: (entry: AuditEntryInput) => Promise<void>
64
+ }
65
+ const input = z.object({ id: z.string(), title: z.string() }).strict()
66
+ const result = z.object({ ok: z.literal(true) }).strict()
67
+
68
+ export function createLegacyRename(component: ConstructorParameters<typeof Foundation>[0]) {
69
+ const { Command } = new Foundation(component, {
70
+ observability: {
71
+ enabled: false,
72
+ classifyError: () => ({ outcome: 'failed', errorCode: 'RENAME_FAILED' }),
73
+ writeAudit: (host: Host, entry) => host.appendAudit(entry),
74
+ },
75
+ })
76
+ const typed = Command.withContext<Host>()
77
+ const operations = {
78
+ rename: typed.operation({
79
+ command: input,
80
+ result,
81
+ classification: 'business',
82
+ audit: ({ command }, host) => ({
83
+ operation: 'documents.rename',
84
+ actorId: host.actorId,
85
+ aggregate: { type: 'document', id: command.id },
86
+ }),
87
+ }),
88
+ }
89
+ const commands = new Command<Host, typeof operations>(operations)
90
+ return commands.exec({
91
+ operation: 'rename',
92
+ handler: async (host, command) => {
93
+ await host.rename(command.id, command.title)
94
+ return { ok: true }
95
+ },
96
+ })
97
+ }
98
+ ```
99
+
100
+ The replacement keeps the handler beside its schemas and audit policy. It
101
+ uses input-first, context-second callbacks and returns a lazy Effect from
102
+ `exec(operation, input, host)`, including when the handler is ordinary async:
103
+
104
+ <!-- packed-effect-example -->
105
+
106
+ ```ts
107
+ import { type AuditEntryInput } from 'cvx-kit/components/foundation'
108
+ import { createEffectFoundation } from 'cvx-kit/effect'
109
+ import { z } from 'zod'
110
+
111
+ type Host = {
112
+ actorId: string
113
+ rename: (id: string, title: string) => Promise<void>
114
+ appendAudit: (entry: AuditEntryInput) => Promise<void>
115
+ }
116
+ const input = z.object({ id: z.string(), title: z.string() }).strict()
117
+ const result = z.object({ ok: z.literal(true) }).strict()
118
+ const foundation = createEffectFoundation({
119
+ observability: {
120
+ enabled: false,
121
+ classifyError: () => ({ outcome: 'failed', errorCode: 'RENAME_FAILED' }),
122
+ },
123
+ writeAudit: (host: Host, entry) => host.appendAudit(entry),
124
+ })
125
+ export const commands = foundation.Command({
126
+ context: (host: Host) => host,
127
+ operations: ({ command }) => ({
128
+ rename: command({
129
+ input,
130
+ result,
131
+ classification: 'business',
132
+ handler: async (input, context) => {
133
+ await context.rename(input.id, input.title)
134
+ return { ok: true as const }
135
+ },
136
+ audit: ({ command }, context) => ({
137
+ operation: 'documents.rename',
138
+ actorId: context.actorId,
139
+ aggregate: { type: 'document', id: command.id },
140
+ }),
141
+ }),
142
+ }),
143
+ })
144
+ ```
145
+
146
+ Pass the resulting Effect to a handler registered through a shared
147
+ `effectApiBuilder` or `effectZodApiBuilder`; see [the complete domain-to-API
148
+ example](./effect.md). Domain composition does not start a separate runner.
149
+ Ordinary throws and rejected Promises become defects; use `Effect.fail` or
150
+ `Effect.tryPromise` for an inferred typed error channel. The native Convex
151
+ reference retains argument/result types, but does not transport the server's
152
+ Effect error and service types.
153
+
154
+ Both facades share command lifecycle ordering, validation, audit, and replay
155
+ rules. Commands that write remain inside a Convex mutation, and failures must
156
+ escape that mutation to roll its writes back.
157
+
40
158
  ## Defining commands — per domain
41
159
 
42
160
  Each domain declares a frozen registry of operations and derives typed
@@ -2,7 +2,7 @@
2
2
 
3
3
  This is the kit's doctrine. Not a style suggestion: the structure below is
4
4
  what the kit's structural guarantees (auth, triggers, bounded reads, audited
5
- commands) assume, and what its recommended architecture tests enforce. Follow
5
+ commands) assume, and what its packaged architecture rules enforce. Follow
6
6
  it as written; deviate only with a documented reason and a matching change to
7
7
  the tests.
8
8
 
@@ -33,10 +33,13 @@ convex/
33
33
  rules.ts shared.ts actions.ts
34
34
  <workflow>.ts <workflow>_functions.ts
35
35
  __tests__/
36
+ application/ (optional cross-module orchestration only)
37
+ migrations/ (optional backfills and migrations)
38
+ _generated/ (generated; exempt)
36
39
  components/ (only if you author portable components)
37
40
  <name>/
38
41
  __tests__/
39
- setup.ts architecture.test.ts facades.test.ts
42
+ setup.ts facades.test.ts
40
43
  ```
41
44
 
42
45
  Three zones, three privileges:
@@ -49,7 +52,8 @@ Three zones, three privileges:
49
52
  helpers, or reads/writes beyond delegation.
50
53
  - **`domain/`** — all business logic. May import `functions.ts`,
51
54
  `foundation.ts`, facades, `domain/shared/`, and its own entity directory.
52
- May NOT import `api/`, sibling entities, `_generated/server` builders, or
55
+ May NOT import `api/`, `application/`, migrations, sibling implementations
56
+ (except the rules/queries contract below), `_generated/server` builders, or
53
57
  component internals.
54
58
 
55
59
  Dependency direction is one-way: `api → domain → shared → kit`. Anything
@@ -75,6 +79,18 @@ Cross-module rules:
75
79
  `application/` layer above `domain/` (`application/<workflow>.ts`); `api/`
76
80
  adapters then import `application/*` and their own module, never a second
77
81
  module.
82
+ - `application/` is not a service or helper layer. Each executable file must
83
+ coordinate at least two domain modules through their `commands.ts`, `queries.ts`,
84
+ or `rules.ts`, or generated `internal.domain` references. No direct database
85
+ access, provider construction, validators, private helpers, or business branches.
86
+ Keep a workflow owned by one module inside that module, including its integrations.
87
+ - The root directory allowlist is exhaustive: `api`, `domain`, `application`,
88
+ `migrations`, `components`, `__tests__`, and `_generated`. Do not add root
89
+ `host`, `internal`, `lib`, `utils`, `services`, or `helpers` directories.
90
+ - A module may contain nested `internal/`, `integrations/`, provider, or workflow
91
+ directories. Its responsibilities stay local. Do not create unused scaffold files.
92
+ - Application code must not import migrations. Migrations can coordinate data
93
+ changes across modules, but remain isolated from ordinary execution.
78
94
 
79
95
  ---
80
96
 
@@ -107,7 +123,7 @@ another file's function to shorten an import.
107
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) |
108
124
  | `foundation.ts` | the single `new Foundation(...)`, destructured exports | command definitions |
109
125
  | `<component>.ts` | `new <Client>(components.<name>)` + minimal admin plumbing | workflow/business definitions |
110
- | `http.ts` | routes: verify signature → parse payload → delegate to a domain function | webhook business logic |
126
+ | `http.ts` | route registration delegating request handling to the owning domain | webhook business logic |
111
127
  | `crons.ts` | `cronJobs()` declarations targeting `internal.domain.<entity>...` | handler logic |
112
128
 
113
129
  ### `api/<entity>.ts` — the adapter file
@@ -202,9 +218,11 @@ it, it moves to `domain/shared/` — never a sibling import.
202
218
 
203
219
  ### `domain/<entity>/actions.ts` — external side effects
204
220
 
205
- Action-side logic using the shared provider clients from
206
- `domain/shared/<provider>.ts`. Domains import the shared client; they never
207
- instantiate their own vendor SDK.
221
+ Action-side logic owns external side effects. Put a provider used by one module
222
+ in that module's `integrations/` or `providers/` directory. Share configured clients
223
+ in `domain/shared/` only when multiple modules need them. Commands and queries
224
+ must not perform network I/O; pure rules receive values instead of context or
225
+ clock/random/environment dependencies.
208
226
 
209
227
  ### `domain/<entity>/<workflow>.ts` + `<workflow>_functions.ts`
210
228
 
@@ -214,21 +232,31 @@ targets built with `system*` constructors. Callbacks re-validate everything:
214
232
 
215
233
  ### `domain/shared/` — cross-entity, and only cross-entity
216
234
 
217
- One configured client per external provider (`<provider>.ts`), plus zod/util
218
- helpers genuinely used by multiple entities. This directory is not a dumping
235
+ Configured provider clients and infrastructure genuinely used by multiple modules.
236
+ Shared infrastructure cannot import business modules. This directory is not a dumping
219
237
  ground: code with one consumer moves back to that consumer.
220
238
 
221
239
  ### Tests
222
240
 
223
- - `domain/<entity>/__tests__/` — behavioral tests for the entity, driven
224
- through the real constructors (`t.withIdentity(...)`).
225
- - `convex/__tests__/architecture.test.ts` — the executable conventions;
226
- every rule proved against a synthetic violating fixture first.
227
- - `convex/__tests__/facades.test.ts` — every `app.use` exactly once; no
228
- private-child access.
229
- - `convex/__tests__/setup.ts` — env stubs for `defineApp({ env })` vars.
230
- - Test files are named `<subject>.test.ts` and live in a `__tests__/`
231
- directory beside the code they test — never in a parallel top-level tree.
241
+ Every directory containing executable application source must have a direct
242
+ `__tests__/` child with at least one `.test.ts` or `.spec.ts` file (JS variants
243
+ also work). The same requirement covers `api/`, nested domain directories,
244
+ `application/`, `migrations/`, and local components. Parent and child suites do
245
+ not substitute for that directory's own tests.
246
+
247
+ Generated code, declaration/type-only files, test fixtures inside `__tests__/`,
248
+ and folders that only group child directories are exempt. `domain/table.ts` is
249
+ an assembly exception; root wiring shares `convex/__tests__/`. Do not create
250
+ empty directories or placeholder tests just to satisfy the checker.
251
+
252
+ The package checks for a recognized, nonempty test callback outside skipped or
253
+ TODO suites. It does not execute tests or prove coverage: run the application's
254
+ behavioral tests in CI. Conditional test registration is not counted as the
255
+ required runnable test. Keep fixtures and setup helpers under `__tests__/`.
256
+
257
+ `cvx-kit/oxlint` supplies the structural checks. Consumers write behavioral tests,
258
+ including meaningful facade/wiring tests where needed; they do not copy or
259
+ maintain an architecture-testing framework.
232
260
 
233
261
  ---
234
262
 
@@ -255,8 +283,8 @@ ground: code with one consumer moves back to that consumer.
255
283
  | Public function path | `api/<entity>:<fn>` — clients never call a root path | — |
256
284
 
257
285
  The two regexes are enforced at runtime: observability silently drops events
258
- whose operation/classification/errorCode don't match. The rest is enforced by
259
- the architecture tests — a naming rule without a test is a wish.
286
+ whose operation/classification/errorCode don't match. Other naming conventions remain review guidance unless a documented package
287
+ lint rule explicitly enforces them.
260
288
 
261
289
  ---
262
290
 
@@ -268,7 +296,7 @@ Allowed, per zone (anything not listed is forbidden):
268
296
  | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
269
297
  | `api/<entity>.ts` | `functions.ts`, `domain/<entity>/*`, `domain/shared/*`, zod |
270
298
  | `domain/<entity>/*` | `functions.ts`, `foundation.ts`, component facades, `domain/shared/*`, own directory, generated `internal`/`api` (references only), kit modules |
271
- | `domain/shared/*` | kit modules, vendor SDKs (this is the ONLY home for vendor clients) |
299
+ | `domain/shared/*` | kit modules and infrastructure shared by multiple modules; module-specific clients stay local |
272
300
  | root facades | kit modules, `components` from generated api |
273
301
  | `functions.ts` | `_generated/server` (the only file allowed to) |
274
302
  | `components/<name>/**` | its own directory + npm deps only — no host imports, no `process.env` |