cvx-kit 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -12,6 +12,37 @@ are this file's matching section.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.1.6] - 2026-09-19
16
+
17
+ ### Added
18
+
19
+ - Add five opt-in consumer Oxlint rules for project structure, root wiring,
20
+ application orchestration, internal function ownership, and domain file
21
+ responsibilities. The packaged plugin now includes 20 rules.
22
+ - Check colocated test presence, runtime import cycles, and shared-code ownership.
23
+ Export `checkArchitecture` for standalone checks without copying architecture
24
+ test suites into applications. These static checks do not replace behavior tests.
25
+
26
+ ### Fixed
27
+
28
+ - Resolve tsconfig aliases when checking consumer import boundaries and reject
29
+ migration dependencies from application code.
30
+ - Enforce documented ownership of domain implementations, table aggregation,
31
+ query side effects, and component boundaries.
32
+
33
+ ## [0.1.5] - 2026-09-18
34
+
35
+ ### Fixed
36
+
37
+ - Correct `cvx-kit/oxlint` to enforce the documented consumer `convex/` layout
38
+ rather than this repository's `src/` layout. Keep the six library-authoring
39
+ rules in repository tooling and publish 15 consumer rules, with coverage for
40
+ domain boundaries, auth builders, thin adapters, facade ownership, table
41
+ placement, vocabularies, bounded read syntax, and trigger-owned timestamps.
42
+ - Replace consumer `entryPoints`/`packageName` options with `convexDir`; document
43
+ migration, allowed exceptions, and the checks that still need runtime tests.
44
+ Validate the packaged plugin against consumer fixtures installed by npm and Bun.
45
+
15
46
  ## [0.1.4] - 2026-09-18
16
47
 
17
48
  ### Added
package/README.md CHANGED
@@ -185,38 +185,59 @@ import approvals from 'cvx-kit/components/approvals/convex.config'
185
185
  app.use(approvals)
186
186
  ```
187
187
 
188
- ## Component authoring conventions
188
+ ## Application lint rules
189
189
 
190
- The TypeScript Oxlint plugin ships as `cvx-kit/oxlint`. Load it in your
191
- `.oxlintrc.json`:
190
+ The TypeScript plugin `cvx-kit/oxlint` checks applications using the kit against
191
+ [`conventions.md`](./src/docs/conventions.md). It targets `convex/`, including
192
+ public adapters, domain modules, root facades, and locally authored components.
193
+ Load it in your application's `.oxlintrc.json` and select rules:
192
194
 
193
195
  ```json
194
196
  {
195
197
  "jsPlugins": [{ "name": "cvx", "specifier": "cvx-kit/oxlint" }],
196
198
  "rules": {
199
+ "cvx/project-structure": "error",
200
+ "cvx/root-wiring-only": "error",
201
+ "cvx/application-orchestration": "error",
202
+ "cvx/internal-function-ownership": "error",
203
+ "cvx/domain-file-responsibilities": "error",
204
+ "cvx/no-raw-builders": "error",
205
+ "cvx/no-handwritten-references": "error",
206
+ "cvx/domain-import-boundaries": "error",
207
+ "cvx/public-functions-in-api": "error",
208
+ "cvx/thin-api-adapters": "error",
209
+ "cvx/root-facade-ownership": "error",
210
+ "cvx/no-inline-enums": "error",
211
+ "cvx/no-unbounded-reads": "error",
212
+ "cvx/no-manual-timestamps": "error",
197
213
  "cvx/component-boundaries": "error",
198
214
  "cvx/no-component-env": "error",
199
215
  "cvx/schema-file-boundaries": "error",
200
- "cvx/no-internal-reexports": ["error", { "entryPoints": ["src/index.ts"] }],
216
+ "cvx/no-internal-reexports": "error",
201
217
  "cvx/public-api-first": "error",
202
218
  "cvx/named-private-helpers": "error"
203
219
  }
204
220
  }
205
221
  ```
206
222
 
207
- With Vite+, put those fields under `lint` in `vite.config.ts`. Component
208
- rules target `src/components/<name>/`; set your public files in `entryPoints`
209
- (paths relative to the linter's working directory). To block imports back
210
- into your own package, configure `component-boundaries` with
211
- `["error", { "packageName": "your-package" }]`. The plugin exports its default
212
- plugin object, named `rules`, and the `RuleName` type. Verified with Oxlint
213
- 1.79.0 and Vite+ 0.3.0.
223
+ With Vite+, put those fields under `lint` in `vite.config.ts`. Each rule accepts
224
+ `{ "convexDir": "path/to/convex" }` when your backend directory differs from
225
+ `convex/`. Generated files and tests are excluded from source-code rules. `project-structure`
226
+ inspects the whole backend for directory ownership, colocated runnable tests, and
227
+ module cycles. Run Oxlint on the whole backend, including `convex.config.ts`;
228
+ changed-file-only lint cannot guarantee a project scan. No application-owned
229
+ architecture test framework is required. The plugin also exports `checkArchitecture()`
230
+ for tools that need project diagnostics directly, named `rules`, and `RuleName`. Verified with
231
+ Oxlint 1.79.0 and Vite+ 0.3.0. See [rule coverage and limitations](./src/docs/oxlint.md)
232
+ and the [migration from the initial 0.1.4 plugin](./src/docs/upgrading.md).
214
233
 
215
234
  Repository checks and the enforced library rules are documented in
216
235
  [`tools/oxlint/README.md`](./tools/oxlint/README.md). Run `bun run lint` for
217
236
  anti-slop and architecture checks, `bun run test:lint` to test the custom
218
237
  rules, and `bun run check` for the full Vite+ lint and formatting check.
219
238
 
239
+ ## Component authoring conventions
240
+
220
241
  This package follows the official Convex component template
221
242
  (`get-convex/templates/template-component`; see
222
243
  https://docs.convex.dev/components/authoring):
@@ -1,2 +1,2 @@
1
- import{components as e}from"./_generated/api.mjs";import{mutation as t,query as n}from"./_generated/server.mjs";import{APPROVAL_AUDIT_RETENTION_CATEGORY as r,MAX_APPROVAL_AUDIT_RETENTION_DAYS as i}from"./constants.mjs";import{z as a}from"zod";import{v as o}from"convex/values";import{AuditLog as s}from"convex-audit-log";const c=new s(e.auditLog,{samplingEnabled:!1}),l=n({args:{resourceType:o.string(),resourceRef:o.string(),limit:o.optional(o.number())},returns:o.array(o.object({action:o.string(),actorRef:o.optional(o.string()),correlationKey:o.string(),transition:o.string()})),handler:async(e,t)=>{let n=Math.max(1,Math.min(Math.trunc(t.limit??100),100)),r=await c.queryByResource(e,{resourceType:t.resourceType,resourceId:t.resourceRef,limit:n}),i=new Map;for(let e of r){let t=h(e);t&&i.set(t.correlationKey,t)}return[...i.values()]}}),u=t({args:{olderThanDays:o.number(),batchSize:o.number()},returns:o.number(),handler:(e,t)=>{let n=p(t.olderThanDays,1,i,`olderThanDays`),a=p(t.batchSize,1,100,`batchSize`);return c.cleanup(e,{olderThanDays:n,batchSize:a,retentionCategory:r})}});function d(e){return{action:e.action,actorId:e.actorRef,resourceType:e.resourceType,resourceId:e.resourceRef,severity:e.severity??`info`,tags:[`approvals`],retentionCategory:r,metadata:{correlationKey:`${e.runId}:${e.transition}`,runId:e.runId,transition:e.transition}}}async function f(e,t){await c.log(e,d(t))}function p(e,t,n,r){if(!Number.isInteger(e)||e<t||e>n)throw Error(`${r} must be an integer between ${t} and ${n}`);return e}const m=a.object({action:a.string(),actorId:a.string().optional().catch(void 0),metadata:a.object({correlationKey:a.string(),transition:a.string()})});function h(e){let t=m.safeParse(e);if(!t.success)return null;let{action:n,actorId:r,metadata:i}=t.data;return{action:n,actorRef:r,...i}}export{c as approvalAudit,d as approvalAuditEvent,u as cleanup,l as history,f as logApprovalTransition};
1
+ import{components as e}from"./_generated/api.mjs";import{mutation as t,query as n}from"./_generated/server.mjs";import{APPROVAL_AUDIT_RETENTION_CATEGORY as r,MAX_APPROVAL_AUDIT_RETENTION_DAYS as i}from"./constants.mjs";import{z as a}from"zod";import{AuditLog as o}from"convex-audit-log";import{v as s}from"convex/values";const c=new o(e.auditLog,{samplingEnabled:!1}),l=n({args:{resourceType:s.string(),resourceRef:s.string(),limit:s.optional(s.number())},returns:s.array(s.object({action:s.string(),actorRef:s.optional(s.string()),correlationKey:s.string(),transition:s.string()})),handler:async(e,t)=>{let n=Math.max(1,Math.min(Math.trunc(t.limit??100),100)),r=await c.queryByResource(e,{resourceType:t.resourceType,resourceId:t.resourceRef,limit:n}),i=new Map;for(let e of r){let t=h(e);t&&i.set(t.correlationKey,t)}return[...i.values()]}}),u=t({args:{olderThanDays:s.number(),batchSize:s.number()},returns:s.number(),handler:(e,t)=>{let n=p(t.olderThanDays,1,i,`olderThanDays`),a=p(t.batchSize,1,100,`batchSize`);return c.cleanup(e,{olderThanDays:n,batchSize:a,retentionCategory:r})}});function d(e){return{action:e.action,actorId:e.actorRef,resourceType:e.resourceType,resourceId:e.resourceRef,severity:e.severity??`info`,tags:[`approvals`],retentionCategory:r,metadata:{correlationKey:`${e.runId}:${e.transition}`,runId:e.runId,transition:e.transition}}}async function f(e,t){await c.log(e,d(t))}function p(e,t,n,r){if(!Number.isInteger(e)||e<t||e>n)throw Error(`${r} must be an integer between ${t} and ${n}`);return e}const m=a.object({action:a.string(),actorId:a.string().optional().catch(void 0),metadata:a.object({correlationKey:a.string(),transition:a.string()})});function h(e){let t=m.safeParse(e);if(!t.success)return null;let{action:n,actorId:r,metadata:i}=t.data;return{action:n,actorRef:r,...i}}export{c as approvalAudit,d as approvalAuditEvent,u as cleanup,l as history,f as logApprovalTransition};
2
2
  //# sourceMappingURL=audit.mjs.map
@@ -0,0 +1,20 @@
1
+ import { ResolverFactory } from "oxc-resolver";
2
+ //#region src/oxlint/architecture.d.ts
3
+ interface ArchitectureOptions {
4
+ cwd?: string;
5
+ convexDir?: string;
6
+ }
7
+ interface ArchitectureDiagnostic {
8
+ code: 'missing-root' | 'root-directory' | 'tests' | 'test-location' | 'parse' | 'cycle' | 'symlink' | 'domain-entry' | 'shared-owner';
9
+ file: string;
10
+ message: string;
11
+ }
12
+ /** Full-project checks, independent of the consumer's test runner. No persistent cache. */
13
+ declare function inspectArchitecture(options?: ArchitectureOptions): ArchitectureDiagnostic[];
14
+ declare function createResolver(): ResolverFactory;
15
+ declare function resolveImport(resolver: ResolverFactory, filename: string, source: string): string | null;
16
+ /** The lint rule runs the project scan on one deterministic source file. */
17
+ declare function architectureAnchor(root: string): string | null;
18
+ //#endregion
19
+ export { ArchitectureDiagnostic, ArchitectureOptions, architectureAnchor, createResolver, inspectArchitecture, resolveImport };
20
+ //# sourceMappingURL=architecture.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{dirname as e,relative as t,resolve as n,sep as r}from"node:path";import{existsSync as i,readFileSync as a,readdirSync as o,realpathSync as s}from"node:fs";import{Visitor as c,parseSync as l}from"oxc-parser";import{ResolverFactory as u}from"oxc-resolver";function d(o={}){let c=n(o.cwd??process.cwd(),o.convexDir??`convex`),u=i(c)?s(c):c,d=[];if(!i(u))return[{code:`missing-root`,file:`.`,message:`Convex directory does not exist: ${u}`}];let m=h(u,d),y=new Set,w=new Set,T=new Map,E=new Map,D=f();for(let i of m){let o=n(u,i),s=l(o,a(o,`utf8`));if(s.errors.length){d.push({code:`parse`,file:i,message:`Cannot validate ${i}: ${s.errors[0].message}`});continue}if(g(i)){i.includes(`__tests__/`)?e(i).endsWith(`__tests__`)&&v(s.program)&&w.add(e(e(i))):d.push({code:`test-location`,file:i,message:`Place tests in a sibling __tests__/ directory.`});continue}if(i.split(`/`).includes(`__tests__`))continue;/^domain\/[^/]+$/.test(i)&&i!==`domain/table.ts`&&d.push({code:`domain-entry`,file:i,message:`Domain source belongs in domain/<module>/ or domain/shared/. Only domain/table.ts assembles module tables.`}),_(s.program)&&i!==`domain/table.ts`&&y.add(e(i));let c=x(i)??``,f=new Set;E.set(i,f);for(let e of b(s.program)){let n=p(D,o,e);if(!n)continue;let i=t(u,n).split(r).join(`/`);f.add(i);let a=x(i);if(c&&a&&c!==a){let e=T.get(c)??new Set;T.set(c,e),e.add(a)}}}for(let e of y)w.has(e)||d.push({code:`tests`,file:e,message:`${e}/__tests__/ must contain a .test or .spec file with a nonempty, non-skipped test. Parent or child suites do not count.`});d.push(...C(E));for(let e of S(T))d.push({code:`cycle`,file:e[0],message:`Module dependency cycle: ${e.join(` → `)}.`});return d.sort((e,t)=>e.file.localeCompare(t.file)||e.code.localeCompare(t.code))}function f(){return new u({tsconfig:`auto`,extensions:[`.ts`,`.tsx`,`.mts`,`.cts`,`.js`,`.jsx`,`.mjs`,`.cjs`,`.json`],extensionAlias:{".js":[`.ts`,`.tsx`,`.js`],".mjs":[`.mts`,`.mjs`],".cjs":[`.cts`,`.cjs`]},conditionNames:[`types`,`import`,`node`,`default`],symlinks:!0})}function p(t,r,i){return t.resolveFileSync(r,i).path||(i.startsWith(`.`)?n(e(r),i):null)}function m(e){if(!i(e))return null;if(i(n(e,`convex.config.ts`)))return s(n(e,`convex.config.ts`));e=s(e);for(let t of h(e,[]))if(!t.includes(`__tests__/`)&&!g(t))return n(e,t);return null}function*h(e,i){let a=new Set([`_generated`,`__tests__`,`api`,`domain`,`application`,`migrations`,`components`]);function*s(c){for(let l of o(c,{withFileTypes:!0}).sort((e,t)=>e.name.localeCompare(t.name))){let o=n(c,l.name),u=t(e,o).split(r).join(`/`);if(l.isSymbolicLink()){i.push({code:`symlink`,file:u,message:`Keep application source inside its owning directory; symlinked files and folders are not allowed.`});continue}l.isDirectory()?(c===e&&!a.has(l.name)&&i.push({code:`root-directory`,file:u,message:`Root folder ${l.name}/ is not allowed. Use api/, domain/, application/, migrations/, components/, __tests__/, or _generated/.`}),l.name!==`_generated`&&l.name!==`node_modules`&&!l.name.startsWith(`.`)&&(yield*s(o))):/\.[cm]?[jt]sx?$/.test(l.name)&&!/\.d\.[cm]?ts$/.test(l.name)&&(yield u)}}yield*s(e)}function g(e){return/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(e)}function _(e){return e.body.some(e=>{let t=e.type===`ExportNamedDeclaration`?e.declaration:e;return t&&`declare`in t&&t.declare||e.type===`ImportDeclaration`||e.type===`EmptyStatement`?!1:e.type===`ExportNamedDeclaration`?e.exportKind===`type`||!e.declaration?!1:![`TSInterfaceDeclaration`,`TSTypeAliasDeclaration`,`TSDeclareFunction`].includes(e.declaration.type):![`TSInterfaceDeclaration`,`TSTypeAliasDeclaration`,`TSDeclareFunction`,`ExportAllDeclaration`].includes(e.type)})}function v(e){let t=new Map([[`test`,`test`],[`it`,`it`],[`describe`,`describe`],[`suite`,`suite`]]);for(let n of e.body){let e=n.type===`ExportNamedDeclaration`?n.declaration:n;if(e?.type===`FunctionDeclaration`&&e.id&&t.delete(e.id.name),e?.type===`VariableDeclaration`)for(let n of e.declarations)n.id.type===`Identifier`&&t.delete(n.id.name);if(n.type===`ImportDeclaration`)for(let e of n.specifiers)t.delete(e.local.name),[`vitest`,`vite-plus/test`,`bun:test`,`node:test`,`@jest/globals`].includes(n.source.value)&&(e.type===`ImportNamespaceSpecifier`?t.set(e.local.name,`*`):e.type===`ImportDefaultSpecifier`?t.set(e.local.name,`test`):e.imported.type===`Identifier`&&t.set(e.local.name,e.imported.name))}let n=[];new c({CallExpression(e){n.push(e)}}).visit(e);let r=n.filter(e=>{let n=y(e.callee,t);return[`test`,`it`,`describe`,`suite`].includes(n[0])&&(n.some(e=>[`skip`,`todo`,`skipIf`,`runIf`].includes(e))||e.arguments.some(e=>e.type===`ObjectExpression`&&e.properties.some(e=>e.type===`Property`&&[`skip`,`todo`].includes(e.key.type===`Identifier`?e.key.name:e.key.type===`Literal`?String(e.key.value):``)&&(e.value.type!==`Literal`||e.value.value!==!1))))});return n.some(e=>{let n=y(e.callee,t);return![`test`,`it`].includes(n[0])||r.some(t=>e.start>=t.start&&e.end<=t.end)?!1:e.arguments.some(e=>(e.type===`ArrowFunctionExpression`||e.type===`FunctionExpression`)&&e.body&&(e.body.type!==`BlockStatement`||e.body.body.some(e=>e.type!==`EmptyStatement`)))})}function y(e,t){if(e.type===`Identifier`){let n=t.get(e.name);return n?[n]:[]}if(e.type===`CallExpression`)return y(e.callee,t);if(e.type===`MemberExpression`){let n=y(e.object,t),r=!e.computed&&e.property.type===`Identifier`?e.property.name:e.property.type===`Literal`?String(e.property.value):``;return!n.length||!r?[]:n[0]===`*`?[r]:[...n,r]}return[]}function b(e){let t=[];return new c({ImportDeclaration(e){e.importKind!==`type`&&(e.specifiers.length===0||e.specifiers.some(e=>e.type!==`ImportSpecifier`||e.importKind!==`type`))&&t.push(e.source.value)},ExportNamedDeclaration(e){e.source&&e.exportKind!==`type`&&e.specifiers.some(e=>e.exportKind!==`type`)&&t.push(e.source.value)},ExportAllDeclaration(e){e.exportKind!==`type`&&t.push(e.source.value)},ImportExpression(e){e.source.type===`Literal`&&String(e.source.value)===e.source.value&&t.push(String(e.source.value))}}).visit(e),t}function x(e){let t=e.split(`/`);return t[0]===`domain`&&t.length>=3&&t[1]!==`__tests__`?`domain/${t[1]}`:null}function S(e){let t=new Set,n=[],r=[];function i(a){let o=n.indexOf(a);if(o>=0){r.push([...n.slice(o),a]);return}if(!t.has(a)){n.push(a);for(let t of e.get(a)??[])i(t);n.pop(),t.add(a)}}for(let t of e.keys())i(t);return r}function C(e){let t=new Map;for(let[n,r]of e){let i=x(n)??``;if(!i||i===`domain/shared`)continue;let a=new Set;function o(n){if(!n.startsWith(`domain/shared/`)||a.has(n))return;a.add(n);let r=t.get(n)??new Set;r.add(i),t.set(n,r);for(let t of e.get(n)??[])o(t)}for(let e of r)o(e)}let n=[];for(let[e,r]of t)r.size===1&&n.push({code:`shared-owner`,file:e,message:`This shared implementation is used only by ${[...r][0]}. Move it into that module until another module needs it.`});return n}export{m as architectureAnchor,f as createResolver,d as inspectArchitecture,p as resolveImport};
2
+ //# sourceMappingURL=architecture.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"architecture.mjs","names":[],"sources":["../../src/oxlint/architecture.ts"],"sourcesContent":["import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs'\nimport { dirname, relative, resolve, sep } from 'node:path'\nimport { parseSync, Visitor, type Program, type Node, type CallExpression } from 'oxc-parser'\nimport { ResolverFactory } from 'oxc-resolver'\n\nexport interface ArchitectureOptions {\n\tcwd?: string\n\tconvexDir?: string\n}\nexport interface ArchitectureDiagnostic {\n\tcode:\n\t\t| 'missing-root'\n\t\t| 'root-directory'\n\t\t| 'tests'\n\t\t| 'test-location'\n\t\t| 'parse'\n\t\t| 'cycle'\n\t\t| 'symlink'\n\t\t| 'domain-entry'\n\t\t| 'shared-owner'\n\tfile: string\n\tmessage: string\n}\n\n/** Full-project checks, independent of the consumer's test runner. No persistent cache. */\nexport function inspectArchitecture(options: ArchitectureOptions = {}): ArchitectureDiagnostic[] {\n\tconst requestedRoot = resolve(options.cwd ?? process.cwd(), options.convexDir ?? 'convex')\n\tconst root = existsSync(requestedRoot) ? realpathSync(requestedRoot) : requestedRoot\n\tconst diagnostics: ArchitectureDiagnostic[] = []\n\tif (!existsSync(root))\n\t\treturn [\n\t\t\t{ code: 'missing-root', file: '.', message: `Convex directory does not exist: ${root}` },\n\t\t]\n\tconst files = sourceFiles(root, diagnostics)\n\tconst required = new Set<string>()\n\tconst tested = new Set<string>()\n\tconst graph = new Map<string, Set<string>>()\n\tconst dependencies = new Map<string, Set<string>>()\n\tconst resolver = createResolver()\n\tfor (const file of files) {\n\t\tconst filename = resolve(root, file)\n\t\tconst parsed = parseSync(filename, readFileSync(filename, 'utf8'))\n\t\tif (parsed.errors.length) {\n\t\t\tdiagnostics.push({\n\t\t\t\tcode: 'parse',\n\t\t\t\tfile,\n\t\t\t\tmessage: `Cannot validate ${file}: ${parsed.errors[0].message}`,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\t\tif (testFile(file)) {\n\t\t\tif (!file.includes('__tests__/'))\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\tcode: 'test-location',\n\t\t\t\t\tfile,\n\t\t\t\t\tmessage: 'Place tests in a sibling __tests__/ directory.',\n\t\t\t\t})\n\t\t\telse if (dirname(file).endsWith('__tests__') && hasRunnableTest(parsed.program))\n\t\t\t\ttested.add(dirname(dirname(file)))\n\t\t\tcontinue\n\t\t}\n\t\tif (file.split('/').includes('__tests__')) continue\n\t\tif (/^domain\\/[^/]+$/.test(file) && file !== 'domain/table.ts')\n\t\t\tdiagnostics.push({\n\t\t\t\tcode: 'domain-entry',\n\t\t\t\tfile,\n\t\t\t\tmessage:\n\t\t\t\t\t'Domain source belongs in domain/<module>/ or domain/shared/. Only domain/table.ts assembles module tables.',\n\t\t\t})\n\t\tif (executable(parsed.program) && file !== 'domain/table.ts') required.add(dirname(file))\n\t\tconst owner = moduleOwner(file) ?? ''\n\t\tconst targets = new Set<string>()\n\t\tdependencies.set(file, targets)\n\t\tfor (const source of runtimeImports(parsed.program)) {\n\t\t\tconst target = resolveImport(resolver, filename, source)\n\t\t\tif (!target) continue\n\t\t\tconst targetFile = relative(root, target).split(sep).join('/')\n\t\t\ttargets.add(targetFile)\n\t\t\tconst targetOwner = moduleOwner(targetFile)\n\t\t\tif (owner && targetOwner && owner !== targetOwner) {\n\t\t\t\tconst edges = graph.get(owner) ?? new Set<string>()\n\t\t\t\tgraph.set(owner, edges)\n\t\t\t\tedges.add(targetOwner)\n\t\t\t}\n\t\t}\n\t}\n\tfor (const directory of required) {\n\t\tif (!tested.has(directory))\n\t\t\tdiagnostics.push({\n\t\t\t\tcode: 'tests',\n\t\t\t\tfile: directory,\n\t\t\t\tmessage: `${directory}/__tests__/ must contain a .test or .spec file with a nonempty, non-skipped test. Parent or child suites do not count.`,\n\t\t\t})\n\t}\n\tdiagnostics.push(...sharedOwnership(dependencies))\n\tfor (const cycle of cycles(graph))\n\t\tdiagnostics.push({\n\t\t\tcode: 'cycle',\n\t\t\tfile: cycle[0],\n\t\t\tmessage: `Module dependency cycle: ${cycle.join(' → ')}.`,\n\t\t})\n\treturn diagnostics.sort((a, b) => a.file.localeCompare(b.file) || a.code.localeCompare(b.code))\n}\n\nexport function createResolver() {\n\treturn new ResolverFactory({\n\t\ttsconfig: 'auto',\n\t\textensions: ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json'],\n\t\textensionAlias: {\n\t\t\t'.js': ['.ts', '.tsx', '.js'],\n\t\t\t'.mjs': ['.mts', '.mjs'],\n\t\t\t'.cjs': ['.cts', '.cjs'],\n\t\t},\n\t\tconditionNames: ['types', 'import', 'node', 'default'],\n\t\tsymlinks: true,\n\t})\n}\n\nexport function resolveImport(resolver: ResolverFactory, filename: string, source: string) {\n\tconst found = resolver.resolveFileSync(filename, source).path\n\tif (found) return found\n\t// Keep path checks useful in unsaved/editor fixtures and before Convex codegen.\n\treturn source.startsWith('.') ? resolve(dirname(filename), source) : null\n}\n\n/** The lint rule runs the project scan on one deterministic source file. */\nexport function architectureAnchor(root: string) {\n\tif (!existsSync(root)) return null\n\tif (existsSync(resolve(root, 'convex.config.ts')))\n\t\treturn realpathSync(resolve(root, 'convex.config.ts'))\n\troot = realpathSync(root)\n\tfor (const file of sourceFiles(root, []))\n\t\tif (!file.includes('__tests__/') && !testFile(file)) return resolve(root, file)\n\treturn null\n}\n\nfunction* sourceFiles(root: string, diagnostics: ArchitectureDiagnostic[]): Generator<string> {\n\tconst allowed = new Set([\n\t\t'_generated',\n\t\t'__tests__',\n\t\t'api',\n\t\t'domain',\n\t\t'application',\n\t\t'migrations',\n\t\t'components',\n\t])\n\tfunction* scan(directory: string): Generator<string> {\n\t\tfor (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) =>\n\t\t\ta.name.localeCompare(b.name),\n\t\t)) {\n\t\t\tconst path = resolve(directory, entry.name)\n\t\t\tconst file = relative(root, path).split(sep).join('/')\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\tcode: 'symlink',\n\t\t\t\t\tfile,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t'Keep application source inside its owning directory; symlinked files and folders are not allowed.',\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tif (directory === root && !allowed.has(entry.name))\n\t\t\t\t\tdiagnostics.push({\n\t\t\t\t\t\tcode: 'root-directory',\n\t\t\t\t\t\tfile,\n\t\t\t\t\t\tmessage: `Root folder ${entry.name}/ is not allowed. Use api/, domain/, application/, migrations/, components/, __tests__/, or _generated/.`,\n\t\t\t\t\t})\n\t\t\t\tif (\n\t\t\t\t\tentry.name !== '_generated' &&\n\t\t\t\t\tentry.name !== 'node_modules' &&\n\t\t\t\t\t!entry.name.startsWith('.')\n\t\t\t\t)\n\t\t\t\t\tyield* scan(path)\n\t\t\t} else if (/\\.[cm]?[jt]sx?$/.test(entry.name) && !/\\.d\\.[cm]?ts$/.test(entry.name)) yield file\n\t\t}\n\t}\n\tyield* scan(root)\n}\n\nfunction testFile(file: string) {\n\treturn /\\.(?:test|spec)\\.[cm]?[jt]sx?$/.test(file)\n}\n\nfunction executable(program: Program) {\n\treturn program.body.some((statement) => {\n\t\tconst declaration =\n\t\t\tstatement.type === 'ExportNamedDeclaration' ? statement.declaration : statement\n\t\tif (declaration && 'declare' in declaration && declaration.declare) return false\n\t\tif (statement.type === 'ImportDeclaration' || statement.type === 'EmptyStatement') return false\n\t\tif (statement.type === 'ExportNamedDeclaration') {\n\t\t\tif (statement.exportKind === 'type' || !statement.declaration) return false\n\t\t\treturn !['TSInterfaceDeclaration', 'TSTypeAliasDeclaration', 'TSDeclareFunction'].includes(\n\t\t\t\tstatement.declaration.type,\n\t\t\t)\n\t\t}\n\t\treturn ![\n\t\t\t'TSInterfaceDeclaration',\n\t\t\t'TSTypeAliasDeclaration',\n\t\t\t'TSDeclareFunction',\n\t\t\t'ExportAllDeclaration',\n\t\t].includes(statement.type)\n\t})\n}\n\nfunction hasRunnableTest(program: Program) {\n\tconst names = new Map([\n\t\t['test', 'test'],\n\t\t['it', 'it'],\n\t\t['describe', 'describe'],\n\t\t['suite', 'suite'],\n\t])\n\tfor (const statement of program.body) {\n\t\tconst declaration =\n\t\t\tstatement.type === 'ExportNamedDeclaration' ? statement.declaration : statement\n\t\tif (declaration?.type === 'FunctionDeclaration' && declaration.id)\n\t\t\tnames.delete(declaration.id.name)\n\t\tif (declaration?.type === 'VariableDeclaration')\n\t\t\tfor (const item of declaration.declarations) {\n\t\t\t\tif (item.id.type === 'Identifier') names.delete(item.id.name)\n\t\t\t}\n\t\tif (statement.type !== 'ImportDeclaration') continue\n\t\tfor (const specifier of statement.specifiers) {\n\t\t\tnames.delete(specifier.local.name)\n\t\t\tif (\n\t\t\t\t!['vitest', 'vite-plus/test', 'bun:test', 'node:test', '@jest/globals'].includes(\n\t\t\t\t\tstatement.source.value,\n\t\t\t\t)\n\t\t\t)\n\t\t\t\tcontinue\n\t\t\tif (specifier.type === 'ImportNamespaceSpecifier') names.set(specifier.local.name, '*')\n\t\t\telse if (specifier.type === 'ImportDefaultSpecifier') names.set(specifier.local.name, 'test')\n\t\t\telse if (specifier.imported.type === 'Identifier')\n\t\t\t\tnames.set(specifier.local.name, specifier.imported.name)\n\t\t}\n\t}\n\tconst calls: CallExpression[] = []\n\tnew Visitor({\n\t\tCallExpression(node) {\n\t\t\tcalls.push(node)\n\t\t},\n\t}).visit(program)\n\tconst skipped = calls.filter((node) => {\n\t\tconst parts = testCallee(node.callee, names)\n\t\treturn (\n\t\t\t['test', 'it', 'describe', 'suite'].includes(parts[0]) &&\n\t\t\t(parts.some((part) => ['skip', 'todo', 'skipIf', 'runIf'].includes(part)) ||\n\t\t\t\tnode.arguments.some(\n\t\t\t\t\t(argument) =>\n\t\t\t\t\t\targument.type === 'ObjectExpression' &&\n\t\t\t\t\t\targument.properties.some(\n\t\t\t\t\t\t\t(property) =>\n\t\t\t\t\t\t\t\tproperty.type === 'Property' &&\n\t\t\t\t\t\t\t\t['skip', 'todo'].includes(\n\t\t\t\t\t\t\t\t\tproperty.key.type === 'Identifier'\n\t\t\t\t\t\t\t\t\t\t? property.key.name\n\t\t\t\t\t\t\t\t\t\t: property.key.type === 'Literal'\n\t\t\t\t\t\t\t\t\t\t\t? String(property.key.value)\n\t\t\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!(property.value.type === 'Literal' && property.value.value === false),\n\t\t\t\t\t\t),\n\t\t\t\t))\n\t\t)\n\t})\n\treturn calls.some((node) => {\n\t\tconst parts = testCallee(node.callee, names)\n\t\tif (\n\t\t\t!['test', 'it'].includes(parts[0]) ||\n\t\t\tskipped.some((skip) => node.start >= skip.start && node.end <= skip.end)\n\t\t)\n\t\t\treturn false\n\t\t// each(...) is a factory; only the outer call has the test callback.\n\t\treturn node.arguments.some(\n\t\t\t(argument) =>\n\t\t\t\t(argument.type === 'ArrowFunctionExpression' || argument.type === 'FunctionExpression') &&\n\t\t\t\targument.body &&\n\t\t\t\t(argument.body.type !== 'BlockStatement' ||\n\t\t\t\t\targument.body.body.some((statement) => statement.type !== 'EmptyStatement')),\n\t\t)\n\t})\n}\n\nfunction testCallee(node: Node, names: Map<string, string>): string[] {\n\tif (node.type === 'Identifier') {\n\t\tconst name = names.get(node.name)\n\t\treturn name ? [name] : []\n\t}\n\tif (node.type === 'CallExpression') return testCallee(node.callee, names)\n\tif (node.type === 'MemberExpression') {\n\t\tconst base = testCallee(node.object, names)\n\t\tconst property =\n\t\t\t!node.computed && node.property.type === 'Identifier'\n\t\t\t\t? node.property.name\n\t\t\t\t: node.property.type === 'Literal'\n\t\t\t\t\t? String(node.property.value)\n\t\t\t\t\t: ''\n\t\tif (!base.length || !property) return []\n\t\treturn base[0] === '*' ? [property] : [...base, property]\n\t}\n\treturn []\n}\n\nfunction runtimeImports(program: Program) {\n\tconst imports: string[] = []\n\tnew Visitor({\n\t\tImportDeclaration(node) {\n\t\t\tif (\n\t\t\t\tnode.importKind !== 'type' &&\n\t\t\t\t(node.specifiers.length === 0 ||\n\t\t\t\t\tnode.specifiers.some((s) => s.type !== 'ImportSpecifier' || s.importKind !== 'type'))\n\t\t\t)\n\t\t\t\timports.push(node.source.value)\n\t\t},\n\t\tExportNamedDeclaration(node) {\n\t\t\tif (\n\t\t\t\tnode.source &&\n\t\t\t\tnode.exportKind !== 'type' &&\n\t\t\t\tnode.specifiers.some((specifier) => specifier.exportKind !== 'type')\n\t\t\t)\n\t\t\t\timports.push(node.source.value)\n\t\t},\n\t\tExportAllDeclaration(node) {\n\t\t\tif (node.exportKind !== 'type') imports.push(node.source.value)\n\t\t},\n\t\tImportExpression(node) {\n\t\t\tif (node.source.type === 'Literal' && String(node.source.value) === node.source.value)\n\t\t\t\timports.push(String(node.source.value))\n\t\t},\n\t}).visit(program)\n\treturn imports\n}\n\nfunction moduleOwner(file: string) {\n\tconst parts = file.split('/')\n\treturn parts[0] === 'domain' && parts.length >= 3 && parts[1] !== '__tests__'\n\t\t? `domain/${parts[1]}`\n\t\t: null\n}\n\nfunction cycles(graph: Map<string, Set<string>>) {\n\tconst completed = new Set<string>()\n\tconst stack: string[] = []\n\tconst found: string[][] = []\n\tfunction visit(owner: string) {\n\t\tconst index = stack.indexOf(owner)\n\t\tif (index >= 0) {\n\t\t\tfound.push([...stack.slice(index), owner])\n\t\t\treturn\n\t\t}\n\t\tif (completed.has(owner)) return\n\t\tstack.push(owner)\n\t\tfor (const dependency of graph.get(owner) ?? []) visit(dependency)\n\t\tstack.pop()\n\t\tcompleted.add(owner)\n\t}\n\tfor (const owner of graph.keys()) visit(owner)\n\treturn found\n}\n\nfunction sharedOwnership(dependencies: Map<string, Set<string>>): ArchitectureDiagnostic[] {\n\tconst consumers = new Map<string, Set<string>>()\n\tfor (const [file, targets] of dependencies) {\n\t\tconst owner = moduleOwner(file) ?? ''\n\t\tif (!owner || owner === 'domain/shared') continue\n\t\tconst visited = new Set<string>()\n\t\tfunction visit(target: string) {\n\t\t\tif (!target.startsWith('domain/shared/') || visited.has(target)) return\n\t\t\tvisited.add(target)\n\t\t\tconst owners = consumers.get(target) ?? new Set<string>()\n\t\t\towners.add(owner)\n\t\t\tconsumers.set(target, owners)\n\t\t\tfor (const next of dependencies.get(target) ?? []) visit(next)\n\t\t}\n\t\tfor (const target of targets) visit(target)\n\t}\n\tconst diagnostics: ArchitectureDiagnostic[] = []\n\tfor (const [file, owners] of consumers) {\n\t\tif (owners.size === 1)\n\t\t\tdiagnostics.push({\n\t\t\t\tcode: 'shared-owner',\n\t\t\t\tfile,\n\t\t\t\tmessage: `This shared implementation is used only by ${[...owners][0]}. Move it into that module until another module needs it.`,\n\t\t\t})\n\t}\n\treturn diagnostics\n}\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { Context, ESTree, RuleMeta, Visitor } from "@oxlint/plugins";
3
+ //#region src/oxlint/ast.d.ts
4
+ declare const consumerOptions: z.ZodObject<{
5
+ convexDir: z.ZodDefault<z.ZodString>;
6
+ }, z.core.$strip>;
7
+ declare function appFile(context: Context, filename?: string): string | null;
8
+ declare function imported(context: Context, node: ESTree.Node): {
9
+ source: string;
10
+ name: string;
11
+ } | null;
12
+ declare function sourceVisitors(context: Context, check: (node: ESTree.Node, source: string) => void): Visitor;
13
+ declare function isFunctionsImport(context: Context, source: string): boolean;
14
+ declare function typeOnlySource(node: ESTree.Node): boolean;
15
+ declare function isDbAccess(node: ESTree.CallExpression): boolean;
16
+ declare function isExternalCall(context: Context, node: ESTree.CallExpression): boolean;
17
+ declare function containsComponentReference(context: Context, node: ESTree.Node): boolean;
18
+ declare function rootMember(node: ESTree.Node): ESTree.Node;
19
+ declare function memberPath(node: ESTree.Node): string[];
20
+ declare function isTableChain(node: ESTree.Node): boolean;
21
+ declare function isDbWrite(node: ESTree.CallExpression): boolean;
22
+ declare function hasCall(node: ESTree.Node, name: string): boolean;
23
+ declare function metadata(description: string, messages: Record<string, string>): RuleMeta;
24
+ declare function componentRoot(context: Context, filename: string): string | null;
25
+ declare function staticString(node: ESTree.Node | null | undefined): string | null;
26
+ declare function propertyName(node: ESTree.Node): string | null;
27
+ declare function binding(context: Context, node: ESTree.Node): import("@oxlint/plugins").Variable | null;
28
+ declare function exportedNames(program: ESTree.Program): Set<string>;
29
+ declare function exportName(node: ESTree.Node): string | null;
30
+ declare function delegatingBody(node: ESTree.Node): boolean;
31
+ //#endregion
32
+ export { appFile, binding, componentRoot, consumerOptions, containsComponentReference, delegatingBody, exportName, exportedNames, hasCall, imported, isDbAccess, isDbWrite, isExternalCall, isFunctionsImport, isTableChain, memberPath, metadata, propertyName, rootMember, sourceVisitors, staticString, typeOnlySource };
33
+ //# sourceMappingURL=ast.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{createResolver as e,resolveImport as t}from"./architecture.mjs";import{z as n}from"zod";import{dirname as r,relative as i,resolve as a,sep as o}from"node:path";import{existsSync as s,realpathSync as c}from"node:fs";const l=n.object({convexDir:n.string().min(1).default(`convex`)});function u(e,t=e.filename){let n=a(e.cwd,l.parse(e.options[0]??{}).convexDir),r=s(n)?c(n):n,u=i(r,s(t)?c(t):t).split(o).join(`/`);return u===`..`||u.startsWith(`../`)||u.startsWith(`/`)?null:u}function d(e,t){if(t.type===`CallExpression`)return d(e,t.callee);if(t.type===`MemberExpression`){let n=d(e,t.object),r=E(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 D(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`?k(e.imported)??``:e.type===`ImportDefaultSpecifier`?`default`:`*`}}return null}function f(e,t){function n(e,n){let r=T(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`&&!D(e,t.callee)?.defs.length&&n(t,t.arguments[0])}}}function p(n,i){let o=i.startsWith(`.`)?a(r(n.filename),i):t(e(),n.filename,i);return o!==null&&u(n,o)?.replace(/\.[cm]?[jt]s$/,``)===`functions`}function m(e){return e.type===`TSImportType`||e.type===`ImportDeclaration`&&(e.importKind===`type`||e.specifiers.length>0&&e.specifiers.every(e=>e.type===`ImportSpecifier`&&e.importKind===`type`))||(e.type===`ExportNamedDeclaration`||e.type===`ExportAllDeclaration`)&&e.exportKind===`type`}function h(e){return e.callee.type===`MemberExpression`&&e.callee.object.type===`MemberExpression`&&E(e.callee.object)===`db`}function g(e,t){let n=d(e,t.callee);return n&&/^(?:node:)?(?:https?|net|tls|fs)(?:\/|$)|^(?:undici|axios)(?:\/|$)/.test(n.source)?!0:t.callee.type===`Identifier`&&t.callee.name===`fetch`&&!D(e,t.callee)?.defs.length||t.callee.type===`MemberExpression`&&E(t.callee)===`fetch`&&t.callee.object.type===`Identifier`&&t.callee.object.name===`globalThis`&&!D(e,t.callee.object)?.defs.length}function _(e,t){if(t.type===`MemberExpression`){let n=d(e,t.object);return n?.name===`components`&&/_generated\/api/.test(n.source)?!0:_(e,t.object)}return t.type===`ObjectExpression`&&t.properties.some(t=>t.type===`Property`&&_(e,t.value))}function v(e){return e.type===`MemberExpression`?v(e.object):e}function y(e){return e.type===`Identifier`?[e.name]:e.type===`MemberExpression`?[...y(e.object),E(e)??``]:[]}function b(e){return e.type===`MemberExpression`?E(e)===`table`||b(e.object):e.type===`CallExpression`&&b(e.callee)}function x(e){return e.callee.type===`MemberExpression`&&[`insert`,`patch`,`replace`,`delete`].includes(E(e.callee)??``)&&e.callee.object.type===`MemberExpression`&&E(e.callee.object)===`db`}function S(e,t){return e.type===`CallExpression`?e.callee.type===`MemberExpression`&&E(e.callee)===t&&(t!==`query`||E(e.callee.object)===`db`)||S(e.callee,t):e.type===`MemberExpression`&&S(e.object,t)}function C(e,t){return{type:`problem`,docs:{description:e},schema:[],messages:t}}function w(e,t){let n=a(e.cwd,l.parse(e.options[0]??{}).convexDir),r=s(n)?c(n):n,u=i(r,s(t)?c(t):t).split(o).join(`/`).match(/^components\/([^/]+)(?:\/|$)/);return u?a(r,`components`,u[1]):null}function T(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 E(e){return e.type===`MemberExpression`?e.computed?T(e.property):e.property.name:null}function D(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 O(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(k(e.local)??``);else n.type===`ExportDefaultDeclaration`&&n.declaration.type===`Identifier`&&t.add(n.declaration.name);return t}function k(e){return e.type===`Identifier`?e.name:T(e)}function A(e){return e.type===`CallExpression`?!0:e.type===`AwaitExpression`?A(e.argument):e.type===`BlockStatement`&&e.body.length>0&&e.body.every(e=>e.type===`ReturnStatement`&&e.argument!==null&&A(e.argument)||e.type===`ExpressionStatement`&&A(e.expression))}export{u as appFile,D as binding,w as componentRoot,l as consumerOptions,_ as containsComponentReference,A as delegatingBody,k as exportName,O as exportedNames,S as hasCall,d as imported,h as isDbAccess,x as isDbWrite,g as isExternalCall,p as isFunctionsImport,b as isTableChain,y as memberPath,C as metadata,E as propertyName,v as rootMember,f as sourceVisitors,T as staticString,m as typeOnlySource};
2
+ //# sourceMappingURL=ast.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.mjs","names":[],"sources":["../../src/oxlint/ast.ts"],"sourcesContent":["import { existsSync, realpathSync } from 'node:fs'\nimport { dirname, relative, resolve, sep } from 'node:path'\nimport type { Context, ESTree, RuleMeta, Scope, Visitor } from '@oxlint/plugins'\nimport { z } from 'zod'\nimport { createResolver, resolveImport } from './architecture'\n\nexport const consumerOptions = z.object({ convexDir: z.string().min(1).default('convex') })\n\nexport function appFile(context: Context, filename = context.filename) {\n\tconst requestedRoot = resolve(\n\t\tcontext.cwd,\n\t\tconsumerOptions.parse(context.options[0] ?? {}).convexDir,\n\t)\n\tconst root = existsSync(requestedRoot) ? realpathSync(requestedRoot) : requestedRoot\n\tconst file = relative(root, existsSync(filename) ? realpathSync(filename) : filename)\n\t\t.split(sep)\n\t\t.join('/')\n\treturn file === '..' || file.startsWith('../') || file.startsWith('/') ? null : file\n}\n\nexport function imported(\n\tcontext: Context,\n\tnode: ESTree.Node,\n): { 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\nexport function 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\nexport function isFunctionsImport(context: Context, source: string) {\n\tconst target = source.startsWith('.')\n\t\t? resolve(dirname(context.filename), source)\n\t\t: resolveImport(createResolver(), context.filename, source)\n\treturn target !== null && appFile(context, target)?.replace(/\\.[cm]?[jt]s$/, '') === 'functions'\n}\n\nexport function typeOnlySource(node: ESTree.Node) {\n\treturn (\n\t\tnode.type === 'TSImportType' ||\n\t\t(node.type === 'ImportDeclaration' &&\n\t\t\t(node.importKind === 'type' ||\n\t\t\t\t(node.specifiers.length > 0 &&\n\t\t\t\t\tnode.specifiers.every(\n\t\t\t\t\t\t(specifier) => specifier.type === 'ImportSpecifier' && specifier.importKind === 'type',\n\t\t\t\t\t)))) ||\n\t\t((node.type === 'ExportNamedDeclaration' || node.type === 'ExportAllDeclaration') &&\n\t\t\tnode.exportKind === 'type')\n\t)\n}\n\nexport function isDbAccess(node: ESTree.CallExpression) {\n\treturn (\n\t\tnode.callee.type === 'MemberExpression' &&\n\t\tnode.callee.object.type === 'MemberExpression' &&\n\t\tpropertyName(node.callee.object) === 'db'\n\t)\n}\n\nexport function isExternalCall(context: Context, node: ESTree.CallExpression) {\n\tconst ref = imported(context, node.callee)\n\tif (ref && /^(?:node:)?(?:https?|net|tls|fs)(?:\\/|$)|^(?:undici|axios)(?:\\/|$)/.test(ref.source))\n\t\treturn true\n\treturn (\n\t\t(node.callee.type === 'Identifier' &&\n\t\t\tnode.callee.name === 'fetch' &&\n\t\t\t!binding(context, node.callee)?.defs.length) ||\n\t\t(node.callee.type === 'MemberExpression' &&\n\t\t\tpropertyName(node.callee) === 'fetch' &&\n\t\t\tnode.callee.object.type === 'Identifier' &&\n\t\t\tnode.callee.object.name === 'globalThis' &&\n\t\t\t!binding(context, node.callee.object)?.defs.length)\n\t)\n}\n\nexport function containsComponentReference(context: Context, node: ESTree.Node): boolean {\n\tif (node.type === 'MemberExpression') {\n\t\tconst owner = imported(context, node.object)\n\t\tif (owner?.name === 'components' && /_generated\\/api/.test(owner.source)) return true\n\t\treturn containsComponentReference(context, node.object)\n\t}\n\tif (node.type === 'ObjectExpression')\n\t\treturn node.properties.some(\n\t\t\t(property) =>\n\t\t\t\tproperty.type === 'Property' && containsComponentReference(context, property.value),\n\t\t)\n\treturn false\n}\n\nexport function rootMember(node: ESTree.Node): ESTree.Node {\n\treturn node.type === 'MemberExpression' ? rootMember(node.object) : node\n}\n\nexport function memberPath(node: ESTree.Node): string[] {\n\tif (node.type === 'Identifier') return [node.name]\n\tif (node.type === 'MemberExpression')\n\t\treturn [...memberPath(node.object), propertyName(node) ?? '']\n\treturn []\n}\n\nexport function 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\nexport function 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\nexport function 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\nexport function metadata(description: string, messages: Record<string, string>): RuleMeta {\n\treturn { type: 'problem', docs: { description }, schema: [], messages }\n}\n\nexport function componentRoot(context: Context, filename: string) {\n\tconst requestedRoot = resolve(\n\t\tcontext.cwd,\n\t\tconsumerOptions.parse(context.options[0] ?? {}).convexDir,\n\t)\n\tconst root = existsSync(requestedRoot) ? realpathSync(requestedRoot) : requestedRoot\n\tconst file = relative(root, existsSync(filename) ? realpathSync(filename) : filename)\n\t\t.split(sep)\n\t\t.join('/')\n\tconst match = file.match(/^components\\/([^/]+)(?:\\/|$)/)\n\treturn match ? resolve(root, 'components', match[1]) : null\n}\n\nexport function 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\nexport function 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\nexport function 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\nexport function 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\nexport function exportName(node: ESTree.Node): string | null {\n\treturn node.type === 'Identifier' ? node.name : staticString(node)\n}\n\nexport function delegatingBody(node: ESTree.Node): boolean {\n\tif (node.type === 'CallExpression') return true\n\tif (node.type === 'AwaitExpression') return delegatingBody(node.argument)\n\tif (node.type === 'BlockStatement')\n\t\treturn (\n\t\t\tnode.body.length > 0 &&\n\t\t\tnode.body.every(\n\t\t\t\t(statement) =>\n\t\t\t\t\t(statement.type === 'ReturnStatement' &&\n\t\t\t\t\t\tstatement.argument !== null &&\n\t\t\t\t\t\tdelegatingBody(statement.argument)) ||\n\t\t\t\t\t(statement.type === 'ExpressionStatement' && delegatingBody(statement.expression)),\n\t\t\t)\n\t\t)\n\treturn false\n}\n"],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { ESTree } from "@oxlint/plugins";
2
+ //#region src/oxlint/ownership.d.ts
3
+ declare const ownershipRules: {
4
+ 'project-structure': {
5
+ meta: import("@oxlint/plugins").RuleMeta;
6
+ create(context: import("@oxlint/plugins").Context): {
7
+ Program?: undefined;
8
+ } | {
9
+ Program(node: ESTree.Program): void;
10
+ };
11
+ };
12
+ 'root-wiring-only': {
13
+ meta: import("@oxlint/plugins").RuleMeta;
14
+ create(context: import("@oxlint/plugins").Context): {
15
+ Program?: undefined;
16
+ NewExpression?: undefined;
17
+ CallExpression?: undefined;
18
+ ArrowFunctionExpression?: undefined;
19
+ FunctionExpression?: undefined;
20
+ IfStatement?: undefined;
21
+ SwitchStatement?: undefined;
22
+ ConditionalExpression?: undefined;
23
+ ForStatement?: undefined;
24
+ ForOfStatement?: undefined;
25
+ WhileStatement?: undefined;
26
+ 'Program:exit'?: undefined;
27
+ } | {
28
+ Program(node: ESTree.Program): void;
29
+ NewExpression(node: ESTree.NewExpression): void;
30
+ CallExpression(node: ESTree.CallExpression): void;
31
+ ArrowFunctionExpression(node: ESTree.ArrowFunctionExpression): void;
32
+ FunctionExpression(node: ESTree.Function): void;
33
+ IfStatement: (node: ESTree.Node) => void;
34
+ SwitchStatement: (node: ESTree.Node) => void;
35
+ ConditionalExpression: (node: ESTree.Node) => void;
36
+ ForStatement: (node: ESTree.Node) => void;
37
+ ForOfStatement: (node: ESTree.Node) => void;
38
+ WhileStatement: (node: ESTree.Node) => void;
39
+ 'Program:exit'(node: ESTree.Program): void;
40
+ };
41
+ };
42
+ 'application-orchestration': {
43
+ meta: import("@oxlint/plugins").RuleMeta;
44
+ create(context: import("@oxlint/plugins").Context): {};
45
+ };
46
+ 'internal-function-ownership': {
47
+ meta: import("@oxlint/plugins").RuleMeta;
48
+ create(context: import("@oxlint/plugins").Context): {
49
+ CallExpression?: undefined;
50
+ } | {
51
+ CallExpression(node: ESTree.CallExpression): void;
52
+ };
53
+ };
54
+ 'domain-file-responsibilities': {
55
+ meta: import("@oxlint/plugins").RuleMeta;
56
+ create(context: import("@oxlint/plugins").Context): {};
57
+ };
58
+ };
59
+ type OwnershipRuleName = keyof typeof ownershipRules;
60
+ //#endregion
61
+ export { OwnershipRuleName, ownershipRules };
62
+ //# sourceMappingURL=ownership.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{architectureAnchor as e,createResolver as t,inspectArchitecture as n,resolveImport as r}from"./architecture.mjs";import{appFile as i,binding as a,componentRoot as o,consumerOptions as s,containsComponentReference as c,delegatingBody as l,exportedNames as u,imported as d,isDbAccess as f,isDbWrite as p,isExternalCall as m,isFunctionsImport as h,memberPath as g,metadata as _,propertyName as v,rootMember as y,sourceVisitors as b,typeOnlySource as x}from"./ast.mjs";import{basename as S,resolve as C}from"node:path";import{existsSync as w,realpathSync as T}from"node:fs";const E={"project-structure":{meta:_(`Validate the application tree, colocated tests, and module cycles.`,{structure:`{{file}}: {{message}}`}),create(t){let r=s.parse(t.options[0]??{}),i=C(t.cwd,r.convexDir);return(w(t.filename)?T(t.filename):C(t.filename))===e(i)?{Program(e){for(let i of n({cwd:t.cwd,convexDir:r.convexDir}))t.report({node:e,messageId:`structure`,data:{file:i.file,message:i.message}})}}:{}}},"root-wiring-only":{meta:_(`Keep root files limited to configuration, assembly, and component facades.`,{ownership:`Root files only wire configuration and components. Move behavior into domain/<module>/: {{reason}}.`}),create(e){let t=i(e);if(!t||t.includes(`/`))return{};let n=null,r=`business implementation`,a=!1,o=/^(?:convex\.config|auth\.config|vite\.config|vitest\.config|auth|schema|functions|triggers|foundation|http|crons)\.[cm]?[jt]s$/.test(t);function s(e){n??=e}return{Program(e){e.body.some(e=>e.type===`FunctionDeclaration`||e.type===`ExportNamedDeclaration`&&e.declaration?.type===`FunctionDeclaration`)&&s(e)},NewExpression(t){d(e,t.callee)&&t.arguments.some(t=>c(e,t))&&(a=!0)},CallExpression(n){d(e,n.callee)&&n.arguments.some(t=>c(e,t))&&(a=!0),(p(n)||m(e,n)||t===`schema.ts`&&d(e,n.callee)?.name!==`defineSchema`)&&s(n)},ArrowFunctionExpression(e){(!e.body||!l(e.body))&&s(e)},FunctionExpression(e){(!e.body||!l(e.body))&&s(e)},IfStatement:s,SwitchStatement:s,ConditionalExpression:s,ForStatement:s,ForOfStatement:s,WhileStatement:s,"Program:exit"(t){!o&&!a&&(n=t,r=`unknown root file; only declared component facades may add root source files`),n&&e.report({node:n,messageId:`ownership`,data:{reason:r}})}}}},"application-orchestration":{meta:_(`Reserve application/ for thin cross-domain coordination.`,{ownership:`application/ only coordinates two or more domains. Move schemas, providers, storage access, helpers, and business decisions into their owning domain.`}),create(e){if(!i(e)?.startsWith(`application/`))return{};let n=new Set,a=t(),o=null;function s(e){o??=e}let c=b(e,(t,o)=>{if(x(t))return;let c=r(a,e.filename,o),l=c&&i(e,c),u=l?.match(/^domain\/([^/]+)\/(?:commands|queries|rules)(?:\.[cm]?[jt]s)?$/);u&&u[1]!==`shared`&&n.add(u[1]),l?.startsWith(`domain/`)&&!u&&!l.startsWith(`domain/shared/`)&&s(t),!l&&![`cvx-kit`,`cvx-kit/components/foundation`,`@convex-dev/workflow`,`convex/values`].includes(o)&&s(t)});return{...c,Program(e){let t=u(e);for(let n of e.body){n.type===`FunctionDeclaration`&&n.id&&!t.has(n.id.name)&&s(n);let e=n.type===`ExportNamedDeclaration`?n.declaration:n;if((e?.type===`TSTypeAliasDeclaration`||e?.type===`TSInterfaceDeclaration`)&&s(n),e?.type===`VariableDeclaration`)for(let r of e.declarations)r.init&&[`ObjectExpression`,`ArrayExpression`,`Literal`].includes(r.init.type)&&s(r),n.type===`VariableDeclaration`&&r.id.type===`Identifier`&&!t.has(r.id.name)&&[`ArrowFunctionExpression`,`FunctionExpression`].includes(r.init?.type??``)&&s(r)}},CallExpression(t){c.CallExpression?.(t),(f(t)||m(e,t))&&s(t);let n=d(e,t.callee);n&&[`zod`,`zod/v4`].includes(n.source)&&s(t)},MemberExpression(t){let r=g(t);if(r.length>=4&&r[1]===`domain`){let i=d(e,y(t));i?.name===`internal`&&/_generated\/api/.test(i.source)&&n.add(r[2])}},NewExpression:s,IfStatement:s,SwitchStatement:s,ConditionalExpression:s,"Program:exit"(t){(n.size<2||o)&&e.report({node:o??t,messageId:`ownership`})}}}},"internal-function-ownership":{meta:_(`Place internal handlers with their owner, never in root, api/, or shared/.`,{ownership:`Declare system/internal handlers in domain/<module>/, cross-domain application/, migrations/, or an isolated component.`}),create(e){let t=i(e)??``;return o(e,e.filename)||/^(?:domain\/(?!shared\/)[^/]+\/|application\/|migrations\/)/.test(t)?{}:{CallExpression(t){if(t.callee.type===`CallExpression`)return;let n=d(e,t.callee);n&&(h(e,n.source)&&/^system(Query|Mutation|Action)$/.test(n.name)||/(?:_generated\/server|convex\/server)/.test(n.source)&&/^internal(Query|Mutation|Action)(Generic)?$/.test(n.name))&&e.report({node:t,messageId:`ownership`})}}}},"domain-file-responsibilities":{meta:_(`Keep invariants pure, reads read-only, shapes declarative, and I/O action-side.`,{ownership:`Respect this domain file responsibility: pure rules, read-only queries, declarative schemas/tables/constants, and external I/O only in actions or integrations.`}),create(e){let n=i(e)??``,o=n===`domain/table.ts`;if(!o&&!/^domain\/[^/]+\//.test(n))return{};let s=S(n).replace(/\.[cm]?[jt]s$/,``),c=s===`rules`,l=s===`queries`,u=[`schema`,`table`,`constants`].includes(s),g=/(?:^|\/)(?:actions|integrations|providers)(?:\/|\.)/.test(n)||/(?:^|_)actions\./.test(n),_=t(),y=null;function C(e){y??=e}let w=b(e,(t,n)=>{if(x(t))return;let a=r(_,e.filename,n),s=a&&i(e,a);o&&n!==`cvx-kit/zod-table`&&!/^domain\/[^/]+\/table(?:\.[cm]?[jt]s)?$/.test(s??``)&&C(t),c&&s&&!/(?:^|\/)(?:rules|constants)(?:\.[cm]?[jt]s)?$/.test(s)&&C(t),(c||l||u)&&!s&&![`zod`,`zod/v4`,`convex/values`,`cvx-kit`,`cvx-kit/zod-table`,`cvx-kit/errors`].includes(n)&&C(t),l&&s&&/(?:^|\/)(?:commands|actions|integrations|providers)(?:[./]|$)/.test(s)&&C(t)});return{...w,Program(e){if(o)for(let t of e.body){if(t.type===`ImportDeclaration`||t.type===`EmptyStatement`)continue;let e=t.type===`ExportNamedDeclaration`?t.declaration:t;(e?.type!==`VariableDeclaration`||e.declarations.some(e=>e.init?.type!==`CallExpression`))&&C(t)}},CallExpression(t){if(w.CallExpression?.(t),o){let n=d(e,t.callee);(n?.source!==`cvx-kit/zod-table`||n.name!==`createModule`)&&C(t)}let n=t.callee.type===`MemberExpression`?v(t.callee):null;(c||u)&&(f(t)||[`runQuery`,`runMutation`,`runAction`,`runAfter`,`runAt`].includes(n??``))&&C(t),l&&(p(t)||[`runMutation`,`runAction`,`runAfter`,`runAt`].includes(n??``))&&C(t);let r=d(e,t.callee);r&&h(e,r.source)&&(c||u||l&&/(?:Mutation|Action)$/.test(r.name))&&C(t),l&&t.callee.type===`MemberExpression`&&v(t.callee.object)===`storage`&&[`store`,`delete`,`generateUploadUrl`].includes(n??``)&&C(t),!g&&m(e,t)&&C(t),c&&t.callee.type===`MemberExpression`&&t.callee.object.type===`Identifier`&&!a(e,t.callee.object)?.defs.length&&(t.callee.object.name===`Date`&&n===`now`||t.callee.object.name===`Math`&&n===`random`)&&C(t)},MemberExpression(e){c&&[`db`,`storage`,`scheduler`,`env`].includes(v(e)??``)&&C(e)},NewExpression(t){(c||l||u)&&(t.callee.type===`Identifier`&&!a(e,t.callee)?.defs.length&&[`Error`,`TypeError`,`RangeError`,`Set`,`Map`,`RegExp`].includes(t.callee.name)||C(t))},"Program:exit"(){y&&e.report({node:y,messageId:`ownership`})}}}}};export{E as ownershipRules};
2
+ //# sourceMappingURL=ownership.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ownership.mjs","names":[],"sources":["../../src/oxlint/ownership.ts"],"sourcesContent":["import { existsSync, realpathSync } from 'node:fs'\nimport { basename, resolve } from 'node:path'\nimport type { CreateRule, ESTree } from '@oxlint/plugins'\nimport {\n\tarchitectureAnchor,\n\tinspectArchitecture,\n\tcreateResolver,\n\tresolveImport,\n} from './architecture'\nimport {\n\tconsumerOptions,\n\tappFile,\n\timported,\n\tsourceVisitors,\n\tisFunctionsImport,\n\ttypeOnlySource,\n\tisDbAccess,\n\tisExternalCall,\n\tcontainsComponentReference,\n\trootMember,\n\tmemberPath,\n\tisDbWrite,\n\tmetadata,\n\tcomponentRoot,\n\tpropertyName,\n\tbinding,\n\texportedNames,\n\tdelegatingBody,\n} from './ast'\n\nexport const ownershipRules = {\n\t'project-structure': {\n\t\tmeta: metadata('Validate the application tree, colocated tests, and module cycles.', {\n\t\t\tstructure: '{{file}}: {{message}}',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst settings = consumerOptions.parse(context.options[0] ?? {})\n\t\t\tconst root = resolve(context.cwd, settings.convexDir)\n\t\t\tif (\n\t\t\t\t(existsSync(context.filename)\n\t\t\t\t\t? realpathSync(context.filename)\n\t\t\t\t\t: resolve(context.filename)) !== architectureAnchor(root)\n\t\t\t)\n\t\t\t\treturn {}\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tfor (const issue of inspectArchitecture({\n\t\t\t\t\t\tcwd: context.cwd,\n\t\t\t\t\t\tconvexDir: settings.convexDir,\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: 'structure',\n\t\t\t\t\t\t\tdata: { file: issue.file, message: issue.message },\n\t\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'root-wiring-only': {\n\t\tmeta: metadata('Keep root files limited to configuration, assembly, and component facades.', {\n\t\t\townership:\n\t\t\t\t'Root files only wire configuration and components. Move behavior into domain/<module>/: {{reason}}.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst file = appFile(context)\n\t\t\tif (!file || file.includes('/')) return {}\n\t\t\tlet issue: ESTree.Node | null = null\n\t\t\tlet reason = 'business implementation'\n\t\t\tlet componentFacade = false\n\t\t\tconst standard =\n\t\t\t\t/^(?:convex\\.config|auth\\.config|vite\\.config|vitest\\.config|auth|schema|functions|triggers|foundation|http|crons)\\.[cm]?[jt]s$/.test(\n\t\t\t\t\tfile,\n\t\t\t\t)\n\t\t\tfunction flag(node: ESTree.Node) {\n\t\t\t\tissue ??= node\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.body.some(\n\t\t\t\t\t\t\t(statement) =>\n\t\t\t\t\t\t\t\tstatement.type === 'FunctionDeclaration' ||\n\t\t\t\t\t\t\t\t(statement.type === 'ExportNamedDeclaration' &&\n\t\t\t\t\t\t\t\t\tstatement.declaration?.type === 'FunctionDeclaration'),\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t\t\tflag(node)\n\t\t\t\t},\n\t\t\t\tNewExpression(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\timported(context, node.callee) &&\n\t\t\t\t\t\tnode.arguments.some((argument) => containsComponentReference(context, argument))\n\t\t\t\t\t)\n\t\t\t\t\t\tcomponentFacade = true\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\timported(context, node.callee) &&\n\t\t\t\t\t\tnode.arguments.some((argument) => containsComponentReference(context, argument))\n\t\t\t\t\t)\n\t\t\t\t\t\tcomponentFacade = true\n\t\t\t\t\tif (\n\t\t\t\t\t\tisDbWrite(node) ||\n\t\t\t\t\t\tisExternalCall(context, node) ||\n\t\t\t\t\t\t(file === 'schema.ts' && !(imported(context, node.callee)?.name === 'defineSchema'))\n\t\t\t\t\t)\n\t\t\t\t\t\tflag(node)\n\t\t\t\t},\n\t\t\t\tArrowFunctionExpression(node) {\n\t\t\t\t\tif (!node.body || !delegatingBody(node.body)) flag(node)\n\t\t\t\t},\n\t\t\t\tFunctionExpression(node) {\n\t\t\t\t\tif (!node.body || !delegatingBody(node.body)) flag(node)\n\t\t\t\t},\n\t\t\t\tIfStatement: flag,\n\t\t\t\tSwitchStatement: flag,\n\t\t\t\tConditionalExpression: flag,\n\t\t\t\tForStatement: flag,\n\t\t\t\tForOfStatement: flag,\n\t\t\t\tWhileStatement: flag,\n\t\t\t\t'Program:exit'(node) {\n\t\t\t\t\tif (!standard && !componentFacade) {\n\t\t\t\t\t\tissue = node\n\t\t\t\t\t\treason = 'unknown root file; only declared component facades may add root source files'\n\t\t\t\t\t}\n\t\t\t\t\tif (issue) context.report({ node: issue, messageId: 'ownership', data: { reason } })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'application-orchestration': {\n\t\tmeta: metadata('Reserve application/ for thin cross-domain coordination.', {\n\t\t\townership:\n\t\t\t\t'application/ only coordinates two or more domains. Move schemas, providers, storage access, helpers, and business decisions into their owning domain.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!appFile(context)?.startsWith('application/')) return {}\n\t\t\tconst owners = new Set<string>()\n\t\t\tconst resolver = createResolver()\n\t\t\tlet issue: ESTree.Node | null = null\n\t\t\tfunction flag(node: ESTree.Node) {\n\t\t\t\tissue ??= node\n\t\t\t}\n\t\t\tconst sources = sourceVisitors(context, (node, source) => {\n\t\t\t\tif (typeOnlySource(node)) return\n\t\t\t\tconst target = resolveImport(resolver, context.filename, source)\n\t\t\t\tconst path = target && appFile(context, target)\n\t\t\t\tconst match = path?.match(/^domain\\/([^/]+)\\/(?:commands|queries|rules)(?:\\.[cm]?[jt]s)?$/)\n\t\t\t\tif (match && match[1] !== 'shared') owners.add(match[1])\n\t\t\t\tif (path?.startsWith('domain/') && !match && !path.startsWith('domain/shared/')) flag(node)\n\t\t\t\tif (\n\t\t\t\t\t!path &&\n\t\t\t\t\t![\n\t\t\t\t\t\t'cvx-kit',\n\t\t\t\t\t\t'cvx-kit/components/foundation',\n\t\t\t\t\t\t'@convex-dev/workflow',\n\t\t\t\t\t\t'convex/values',\n\t\t\t\t\t].includes(source)\n\t\t\t\t)\n\t\t\t\t\tflag(node)\n\t\t\t})\n\t\t\treturn {\n\t\t\t\t...sources,\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst publicNames = 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!publicNames.has(statement.id.name)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tflag(statement)\n\t\t\t\t\t\tconst declaration =\n\t\t\t\t\t\t\tstatement.type === 'ExportNamedDeclaration' ? statement.declaration : statement\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tdeclaration?.type === 'TSTypeAliasDeclaration' ||\n\t\t\t\t\t\t\tdeclaration?.type === 'TSInterfaceDeclaration'\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tflag(statement)\n\t\t\t\t\t\tif (declaration?.type === 'VariableDeclaration')\n\t\t\t\t\t\t\tfor (const item of declaration.declarations) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\titem.init &&\n\t\t\t\t\t\t\t\t\t['ObjectExpression', 'ArrayExpression', 'Literal'].includes(item.init.type)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tflag(item)\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tstatement.type === 'VariableDeclaration' &&\n\t\t\t\t\t\t\t\t\titem.id.type === 'Identifier' &&\n\t\t\t\t\t\t\t\t\t!publicNames.has(item.id.name) &&\n\t\t\t\t\t\t\t\t\t['ArrowFunctionExpression', 'FunctionExpression'].includes(item.init?.type ?? '')\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tflag(item)\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\tsources.CallExpression?.(node)\n\t\t\t\t\tif (isDbAccess(node) || isExternalCall(context, node)) flag(node)\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (ref && ['zod', 'zod/v4'].includes(ref.source)) flag(node)\n\t\t\t\t},\n\t\t\t\tMemberExpression(node) {\n\t\t\t\t\tconst path = memberPath(node)\n\t\t\t\t\tif (path.length >= 4 && path[1] === 'domain') {\n\t\t\t\t\t\tconst ref = imported(context, rootMember(node))\n\t\t\t\t\t\tif (ref?.name === 'internal' && /_generated\\/api/.test(ref.source)) owners.add(path[2])\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tNewExpression: flag,\n\t\t\t\tIfStatement: flag,\n\t\t\t\tSwitchStatement: flag,\n\t\t\t\tConditionalExpression: flag,\n\t\t\t\t'Program:exit'(node) {\n\t\t\t\t\tif (owners.size < 2 || issue)\n\t\t\t\t\t\tcontext.report({ node: issue ?? node, messageId: 'ownership' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'internal-function-ownership': {\n\t\tmeta: metadata('Place internal handlers with their owner, never in root, api/, or shared/.', {\n\t\t\townership:\n\t\t\t\t'Declare system/internal handlers in domain/<module>/, cross-domain application/, migrations/, or an isolated component.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst file = appFile(context) ?? ''\n\t\t\tif (\n\t\t\t\tcomponentRoot(context, context.filename) ||\n\t\t\t\t/^(?:domain\\/(?!shared\\/)[^/]+\\/|application\\/|migrations\\/)/.test(file)\n\t\t\t)\n\t\t\t\treturn {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (node.callee.type === 'CallExpression') return\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\t((isFunctionsImport(context, ref.source) &&\n\t\t\t\t\t\t\t/^system(Query|Mutation|Action)$/.test(ref.name)) ||\n\t\t\t\t\t\t\t(/(?:_generated\\/server|convex\\/server)/.test(ref.source) &&\n\t\t\t\t\t\t\t\t/^internal(Query|Mutation|Action)(Generic)?$/.test(ref.name)))\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'ownership' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'domain-file-responsibilities': {\n\t\tmeta: metadata(\n\t\t\t'Keep invariants pure, reads read-only, shapes declarative, and I/O action-side.',\n\t\t\t{\n\t\t\t\townership:\n\t\t\t\t\t'Respect this domain file responsibility: pure rules, read-only queries, declarative schemas/tables/constants, and external I/O only in actions or integrations.',\n\t\t\t},\n\t\t),\n\t\tcreate(context) {\n\t\t\tconst file = appFile(context) ?? ''\n\t\t\tconst assembly = file === 'domain/table.ts'\n\t\t\tif (!assembly && !/^domain\\/[^/]+\\//.test(file)) return {}\n\t\t\tconst role = basename(file).replace(/\\.[cm]?[jt]s$/, '')\n\t\t\tconst pure = role === 'rules'\n\t\t\tconst read = role === 'queries'\n\t\t\tconst declarative = ['schema', 'table', 'constants'].includes(role)\n\t\t\tconst actionSide =\n\t\t\t\t/(?:^|\\/)(?:actions|integrations|providers)(?:\\/|\\.)/.test(file) ||\n\t\t\t\t/(?:^|_)actions\\./.test(file)\n\t\t\tconst resolver = createResolver()\n\t\t\tlet issue: ESTree.Node | null = null\n\t\t\tfunction flag(node: ESTree.Node) {\n\t\t\t\tissue ??= node\n\t\t\t}\n\t\t\tconst sources = sourceVisitors(context, (node, source) => {\n\t\t\t\tif (typeOnlySource(node)) return\n\t\t\t\tconst target = resolveImport(resolver, context.filename, source)\n\t\t\t\tconst path = target && appFile(context, target)\n\t\t\t\tif (\n\t\t\t\t\tassembly &&\n\t\t\t\t\tsource !== 'cvx-kit/zod-table' &&\n\t\t\t\t\t!/^domain\\/[^/]+\\/table(?:\\.[cm]?[jt]s)?$/.test(path ?? '')\n\t\t\t\t)\n\t\t\t\t\tflag(node)\n\t\t\t\tif (pure && path && !/(?:^|\\/)(?:rules|constants)(?:\\.[cm]?[jt]s)?$/.test(path)) flag(node)\n\t\t\t\tif (\n\t\t\t\t\t(pure || read || declarative) &&\n\t\t\t\t\t!path &&\n\t\t\t\t\t![\n\t\t\t\t\t\t'zod',\n\t\t\t\t\t\t'zod/v4',\n\t\t\t\t\t\t'convex/values',\n\t\t\t\t\t\t'cvx-kit',\n\t\t\t\t\t\t'cvx-kit/zod-table',\n\t\t\t\t\t\t'cvx-kit/errors',\n\t\t\t\t\t].includes(source)\n\t\t\t\t)\n\t\t\t\t\tflag(node)\n\t\t\t\tif (\n\t\t\t\t\tread &&\n\t\t\t\t\tpath &&\n\t\t\t\t\t/(?:^|\\/)(?:commands|actions|integrations|providers)(?:[./]|$)/.test(path)\n\t\t\t\t)\n\t\t\t\t\tflag(node)\n\t\t\t})\n\t\t\treturn {\n\t\t\t\t...sources,\n\t\t\t\tProgram(node) {\n\t\t\t\t\tif (!assembly) return\n\t\t\t\t\tfor (const statement of node.body) {\n\t\t\t\t\t\tif (statement.type === 'ImportDeclaration' || statement.type === 'EmptyStatement')\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\tconst declaration =\n\t\t\t\t\t\t\tstatement.type === 'ExportNamedDeclaration' ? statement.declaration : statement\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tdeclaration?.type !== 'VariableDeclaration' ||\n\t\t\t\t\t\t\tdeclaration.declarations.some((item) => item.init?.type !== 'CallExpression')\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tflag(statement)\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tsources.CallExpression?.(node)\n\t\t\t\t\tif (assembly) {\n\t\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\t\tif (ref?.source !== 'cvx-kit/zod-table' || ref.name !== 'createModule') flag(node)\n\t\t\t\t\t}\n\t\t\t\t\tconst method = node.callee.type === 'MemberExpression' ? propertyName(node.callee) : null\n\t\t\t\t\tif (\n\t\t\t\t\t\t(pure || declarative) &&\n\t\t\t\t\t\t(isDbAccess(node) ||\n\t\t\t\t\t\t\t['runQuery', 'runMutation', 'runAction', 'runAfter', 'runAt'].includes(method ?? ''))\n\t\t\t\t\t)\n\t\t\t\t\t\tflag(node)\n\t\t\t\t\tif (\n\t\t\t\t\t\tread &&\n\t\t\t\t\t\t(isDbWrite(node) ||\n\t\t\t\t\t\t\t['runMutation', 'runAction', 'runAfter', 'runAt'].includes(method ?? ''))\n\t\t\t\t\t)\n\t\t\t\t\t\tflag(node)\n\t\t\t\t\tconst constructor = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tconstructor &&\n\t\t\t\t\t\tisFunctionsImport(context, constructor.source) &&\n\t\t\t\t\t\t(pure || declarative || (read && /(?:Mutation|Action)$/.test(constructor.name)))\n\t\t\t\t\t)\n\t\t\t\t\t\tflag(node)\n\t\t\t\t\tif (\n\t\t\t\t\t\tread &&\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression' &&\n\t\t\t\t\t\tpropertyName(node.callee.object) === 'storage' &&\n\t\t\t\t\t\t['store', 'delete', 'generateUploadUrl'].includes(method ?? '')\n\t\t\t\t\t)\n\t\t\t\t\t\tflag(node)\n\t\t\t\t\tif (!actionSide && isExternalCall(context, node)) flag(node)\n\t\t\t\t\tif (\n\t\t\t\t\t\tpure &&\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression' &&\n\t\t\t\t\t\tnode.callee.object.type === 'Identifier' &&\n\t\t\t\t\t\t!binding(context, node.callee.object)?.defs.length &&\n\t\t\t\t\t\t((node.callee.object.name === 'Date' && method === 'now') ||\n\t\t\t\t\t\t\t(node.callee.object.name === 'Math' && method === 'random'))\n\t\t\t\t\t)\n\t\t\t\t\t\tflag(node)\n\t\t\t\t},\n\t\t\t\tMemberExpression(node) {\n\t\t\t\t\tif (pure && ['db', 'storage', 'scheduler', 'env'].includes(propertyName(node) ?? ''))\n\t\t\t\t\t\tflag(node)\n\t\t\t\t},\n\t\t\t\tNewExpression(node) {\n\t\t\t\t\tif (!(pure || read || declarative)) return\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type === 'Identifier' &&\n\t\t\t\t\t\t!binding(context, node.callee)?.defs.length &&\n\t\t\t\t\t\t['Error', 'TypeError', 'RangeError', 'Set', 'Map', 'RegExp'].includes(node.callee.name)\n\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\tflag(node)\n\t\t\t\t},\n\t\t\t\t'Program:exit'() {\n\t\t\t\t\tif (issue) context.report({ node: issue, messageId: 'ownership' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n} satisfies Record<string, CreateRule>\n\nexport type OwnershipRuleName = keyof typeof ownershipRules\n"],"mappings":"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"}
package/dist/oxlint.d.mts CHANGED
@@ -1,9 +1,11 @@
1
+ import { ArchitectureDiagnostic, ArchitectureOptions } from "./oxlint/architecture.mjs";
2
+ import { OwnershipRuleName } from "./oxlint/ownership.mjs";
1
3
  import { Plugin, Rule } from "@oxlint/plugins";
2
4
  //#region src/oxlint.d.ts
3
- type RuleName = 'component-boundaries' | 'no-component-env' | 'schema-file-boundaries' | 'no-internal-reexports' | 'public-api-first' | 'named-private-helpers';
4
- /** Library rules derived from src/docs/conventions.md and architecture.md. */
5
+ declare function checkArchitecture(options?: ArchitectureOptions): ArchitectureDiagnostic[];
6
+ type RuleName = OwnershipRuleName | 'component-boundaries' | 'no-component-env' | 'schema-file-boundaries' | 'no-internal-reexports' | 'public-api-first' | 'named-private-helpers' | 'no-raw-builders' | 'no-handwritten-references' | 'domain-import-boundaries' | 'public-functions-in-api' | 'thin-api-adapters' | 'no-inline-enums' | 'no-unbounded-reads' | 'no-manual-timestamps' | 'root-facade-ownership';
5
7
  declare const rules: Record<RuleName, Rule>;
6
8
  declare const plugin: Plugin;
7
9
  //#endregion
8
- export { RuleName, plugin as default, rules };
10
+ export { type ArchitectureDiagnostic, type ArchitectureOptions, RuleName, checkArchitecture, plugin as default, rules };
9
11
  //# sourceMappingURL=oxlint.d.mts.map
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({entryPoints:e.array(e.string()).default([`src/index.ts`])}),s=e.object({packageName:e.string().min(1).optional()}),c={"component-boundaries":{meta:{...u(`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.`}),schema:[{type:`object`,properties:{packageName:{type:`string`,minLength:1}},additionalProperties:!1}]},create(e){let o=d(e.filename),{packageName:c}=s.parse(e.options[0]??{});function l(s,l){let u=f(l);if(!u)return;if(!u.startsWith(`.`)){o&&c&&(u===c||u.startsWith(`${c}/`))&&e.report({node:s,messageId:`boundary`});return}let p=i(n(e.filename),u);o&&(r(o,p).startsWith(`..${a}`)||p===n(o))?e.report({node:s,messageId:`boundary`}):!o&&d(p)&&!/^client(?:\.[cm]?[jt]s)?$/.test(t(p))&&e.report({node:s,messageId:`facade`})}return{ImportDeclaration(e){l(e,e.source)},ExportNamedDeclaration(e){l(e,e.source)},ExportAllDeclaration(e){l(e,e.source)},ImportExpression(e){l(e,e.source)},TSImportType(e){l(e,e.source)},CallExpression(t){t.callee.type===`Identifier`&&t.callee.name===`require`&&!m(e,t.callee)?.defs.length&&l(t,t.arguments[0])}}}},"no-component-env":{meta:u(`Pass environment configuration into portable components.`,{env:`Portable components must receive configuration through arguments; do not read process.env.`}),create(e){if(!d(e.filename))return{};function t(t){if(t.type!==`Identifier`)return!1;let n=m(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`&&g(n.imported)===`env`&&e.report({node:n,messageId:`env`})},MemberExpression(n){if(p(n)!==`env`)return;let r=n.object,i=r.type===`MemberExpression`&&p(r)===`process`&&r.object.type===`Identifier`&&r.object.name===`globalThis`&&!m(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?f(t.key):g(t.key))===`env`&&e.report({node:t,messageId:`env`})}}}},"schema-file-boundaries":{meta:u(`Keep component storage definitions in schema.ts or table.ts.`,{schema:`{{builder}} belongs in {{file}}, not a function or client module.`}),create(e){return d(e.filename)?{CallExpression(n){let r=n.callee,i=r.type===`Identifier`?r:r.type===`MemberExpression`?r.object:null;if(i?.type!==`Identifier`)return;let a=m(e,i)?.defs.find(e=>e.type===`ImportBinding`&&e.parent?.type===`ImportDeclaration`&&e.parent.source.value===`convex/server`);if(!a)return;let o=a.node.type===`ImportSpecifier`&&r.type===`Identifier`?g(a.node.imported):a.node.type===`ImportNamespaceSpecifier`?p(r):null,s=t(e.filename);o===`defineSchema`&&s!==`schema.ts`?e.report({node:n,messageId:`schema`,data:{builder:o,file:`schema.ts`}}):o===`defineTable`&&![`schema.ts`,`table.ts`].includes(s)&&e.report({node:n,messageId:`schema`,data:{builder:o,file:`schema.ts or table.ts`}})}}:{}}},"no-internal-reexports":{meta:{...u(`Keep re-exports at package entry points and component client facades.`,{reexport:`Import from the owning file directly; only configured entry points and component client.ts facades may re-export.`}),schema:[{type:`object`,properties:{entryPoints:{type:`array`,items:{type:`string`}}},additionalProperties:!1}]},create(e){let t=e.filename.split(a).join(`/`),{entryPoints:n}=o.parse(e.options[0]??{});if(n.some(t=>i(e.cwd,t)===i(e.filename))||/\/src\/components\/[^/]+\/client\.ts$/.test(t))return{};function r(t){if(t.source){e.report({node:t,messageId:`reexport`});return}for(let n of t.type===`ExportNamedDeclaration`?t.specifiers:[])m(e,n.local)?.defs.some(e=>e.type===`ImportBinding`)&&e.report({node:n,messageId:`reexport`})}return{ExportNamedDeclaration:r,ExportAllDeclaration:r,ExportDefaultDeclaration(t){t.declaration.type===`Identifier`&&m(e,t.declaration)?.defs.some(e=>e.type===`ImportBinding`)&&e.report({node:t,messageId:`reexport`})}}}},"public-api-first":{meta:u(`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=h(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:u(`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=h(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`})}}}}},l={meta:{name:`cvx`},rules:c};function u(e,t){return{type:`problem`,docs:{description:e},schema:[],messages:t}}function d(e){let t=e.split(a).join(`/`).match(/^(.*\/src\/components\/[^/]+)(?:\/|$)/);return t?i(t[1]):null}function f(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 p(e){return e.type===`MemberExpression`?e.computed?f(e.property):e.property.name:null}function m(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 h(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(g(e.local)??``);else n.type===`ExportDefaultDeclaration`&&n.declaration.type===`Identifier`&&t.add(n.declaration.name);return t}function g(e){return e.type===`Identifier`?e.name:f(e)}export{l as default,c 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 { Context, ESTree, Plugin, Rule, RuleMeta, Scope } from '@oxlint/plugins'\nimport { z } from 'zod'\n\nconst entryPointOptions = z.object({ entryPoints: z.array(z.string()).default(['src/index.ts']) })\nconst componentOptions = z.object({ packageName: z.string().min(1).optional() })\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\n/** Library rules derived from src/docs/conventions.md and architecture.md. */\nexport const rules: Record<RuleName, Rule> = {\n\t'component-boundaries': {\n\t\tmeta: {\n\t\t\t...metadata('Keep portable components isolated and consume their client facades.', {\n\t\t\t\tboundary:\n\t\t\t\t\t'Components may import only their own files and npm dependencies; inject host policy through the client API.',\n\t\t\t\tfacade: 'Import a component through its client.ts facade, not its private implementation.',\n\t\t\t}),\n\t\t\tschema: [\n\t\t\t\t{\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: { packageName: { type: 'string', minLength: 1 } },\n\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tcreate(context) {\n\t\t\tconst owner = componentRoot(context.filename)\n\t\t\tconst { packageName } = componentOptions.parse(context.options[0] ?? {})\n\t\t\tfunction check(node: ESTree.Node, source: ESTree.Node | null | undefined) {\n\t\t\t\tconst specifier = staticString(source)\n\t\t\t\tif (!specifier) return\n\t\t\t\tif (!specifier.startsWith('.')) {\n\t\t\t\t\tif (\n\t\t\t\t\t\towner &&\n\t\t\t\t\t\tpackageName &&\n\t\t\t\t\t\t(specifier === packageName || specifier.startsWith(`${packageName}/`))\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\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(target) &&\n\t\t\t\t\t!/^client(?:\\.[cm]?[jt]s)?$/.test(basename(target))\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\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tcheck(node, node.source)\n\t\t\t\t},\n\t\t\t\tExportNamedDeclaration(node) {\n\t\t\t\t\tcheck(node, node.source)\n\t\t\t\t},\n\t\t\t\tExportAllDeclaration(node) {\n\t\t\t\t\tcheck(node, node.source)\n\t\t\t\t},\n\t\t\t\tImportExpression(node) {\n\t\t\t\t\tcheck(node, node.source)\n\t\t\t\t},\n\t\t\t\tTSImportType(node) {\n\t\t\t\t\tcheck(node, node.source)\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type === 'Identifier' &&\n\t\t\t\t\t\tnode.callee.name === 'require' &&\n\t\t\t\t\t\t!binding(context, node.callee)?.defs.length\n\t\t\t\t\t) {\n\t\t\t\t\t\tcheck(node, node.arguments[0])\n\t\t\t\t\t}\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.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 component storage definitions in schema.ts or table.ts.', {\n\t\t\tschema: '{{builder}} belongs in {{file}}, not a function or client module.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!componentRoot(context.filename)) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst callee = node.callee\n\t\t\t\t\tconst identifier =\n\t\t\t\t\t\tcallee.type === 'Identifier'\n\t\t\t\t\t\t\t? callee\n\t\t\t\t\t\t\t: callee.type === 'MemberExpression'\n\t\t\t\t\t\t\t\t? callee.object\n\t\t\t\t\t\t\t\t: null\n\t\t\t\t\tif (identifier?.type !== 'Identifier') return\n\t\t\t\t\tconst definition = binding(context, identifier)?.defs.find(\n\t\t\t\t\t\t(def) =>\n\t\t\t\t\t\t\tdef.type === 'ImportBinding' &&\n\t\t\t\t\t\t\tdef.parent?.type === 'ImportDeclaration' &&\n\t\t\t\t\t\t\tdef.parent.source.value === 'convex/server',\n\t\t\t\t\t)\n\t\t\t\t\tif (!definition) return\n\t\t\t\t\tconst builder =\n\t\t\t\t\t\tdefinition.node.type === 'ImportSpecifier' && callee.type === 'Identifier'\n\t\t\t\t\t\t\t? exportName(definition.node.imported)\n\t\t\t\t\t\t\t: definition.node.type === 'ImportNamespaceSpecifier'\n\t\t\t\t\t\t\t\t? propertyName(callee)\n\t\t\t\t\t\t\t\t: null\n\t\t\t\t\tconst file = basename(context.filename)\n\t\t\t\t\tif (builder === 'defineSchema' && file !== 'schema.ts') {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'schema', data: { builder, file: 'schema.ts' } })\n\t\t\t\t\t} else if (builder === 'defineTable' && !['schema.ts', 'table.ts'].includes(file)) {\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, file: 'schema.ts or 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: {\n\t\t\t...metadata('Keep re-exports at package entry points and component client facades.', {\n\t\t\t\treexport:\n\t\t\t\t\t'Import from the owning file directly; only configured entry points and component client.ts facades may re-export.',\n\t\t\t}),\n\t\t\tschema: [\n\t\t\t\t{\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: { entryPoints: { type: 'array', items: { type: 'string' } } },\n\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tcreate(context) {\n\t\t\tconst file = context.filename.split(sep).join('/')\n\t\t\tconst { entryPoints } = entryPointOptions.parse(context.options[0] ?? {})\n\t\t\tconst isEntryPoint = entryPoints.some(\n\t\t\t\t(entry) => resolve(context.cwd, entry) === resolve(context.filename),\n\t\t\t)\n\t\t\tif (isEntryPoint || /\\/src\\/components\\/[^/]+\\/client\\.ts$/.test(file)) return {}\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\nconst plugin: Plugin = { meta: { name: 'cvx' }, rules }\nexport default plugin\n\nfunction metadata(description: string, messages: Record<string, string>): RuleMeta {\n\treturn { type: 'problem', docs: { description }, schema: [], messages }\n}\n\nfunction componentRoot(filename: string) {\n\tconst match = filename\n\t\t.split(sep)\n\t\t.join('/')\n\t\t.match(/^(.*\\/src\\/components\\/[^/]+)(?:\\/|$)/)\n\treturn match ? resolve(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":"+eA6BA,SAAgB,EAAkB,EAA+B,CAAC,EAA6B,CAC9F,OAAO,EAAoB,CAAO,CACnC,CAuhBA,MAAa,EAAgC,EAAW,CAjgBvD,GAAG,EACH,wBAAyB,CACxB,KAAM,EAAS,+DAAgE,CAC9E,OAAQ,2EACT,CAAC,EACD,OAAO,EAAS,CACf,GAAI,EAAc,EAAS,EAAQ,QAAQ,EAAG,MAAO,CAAC,EACtD,IAAM,EAAO,IAAI,IACjB,SAAS,EAAM,EAAoD,CAClE,IAAM,EAAM,EAAS,EAAS,EAAK,MAAM,EACzC,GAAI,CAAC,EAAK,OACV,IAAI,EAAsB,KACtB,CAAC,UAAW,cAAc,CAAC,CAAC,SAAS,EAAI,MAAM,GAAK,EAAI,OAAS,wBACpE,EAAO,gBACJ,CAAC,UAAW,kBAAkB,CAAC,CAAC,SAAS,EAAI,MAAM,GAAK,EAAI,OAAS,mBACxE,EAAO,eAEP,CAAC,UAAW,+BAA+B,CAAC,CAAC,SAAS,EAAI,MAAM,GAChE,EAAI,OAAS,eAEb,EAAO,iBACH,KACD,EAAQ,CAAO,IAAM,GAAQ,EAAK,IAAI,EAAI,IAAI,IACjD,EAAQ,OAAO,CAAE,OAAM,UAAW,SAAU,KAAM,CAAE,KAAM,EAAI,KAAM,MAAK,CAAE,CAAC,EAC7E,EAAK,IAAI,EAAI,IAAI,EAClB,CACA,MAAO,CAAE,eAAgB,EAAO,cAAe,CAAM,CACtD,CACD,EACA,kBAAmB,CAClB,KAAM,EAAS,iEAAkE,CAChF,QACC,+FACF,CAAC,EACD,OAAO,EAAS,CACf,GAAI,EAAQ,CAAO,IAAM,gBAAkB,EAAc,EAAS,EAAQ,QAAQ,EAAG,MAAO,CAAC,EAC7F,IAAM,EAAW,EAAe,EAChC,MAAO,CACN,kBAAkB,EAAM,CACvB,GAAI,EAAK,aAAe,OAAQ,OAChC,IAAM,EAAiB,EAAK,OAAO,MAC7B,EAAS,EAAc,EAAU,EAAQ,SAAU,CAAc,EACjE,EACL,GAAU,EAAQ,EAAS,CAAM,CAAC,EAAE,WAAW,aAAa,EACzD,EAAQ,EAAS,CAAM,EACvB,EAEH,MAAC,+CAA+C,KAAK,CAAM,GAC3D,IAAW,iBAGZ,IAAK,IAAM,KAAa,EAAK,WAAY,CACxC,GAAI,EAAU,OAAS,mBAAqB,EAAU,aAAe,OAAQ,SAC7E,IAAM,EACL,EAAU,OAAS,kBAAoB,EAAW,EAAU,QAAQ,EAAI,MAEvE,IAAS,MAAQ,IAAW,iBAC5B,GACA,+FAA+F,KAC9F,CACD,IAED,EAAQ,OAAO,CAAE,KAAM,EAAW,UAAW,SAAU,CAAC,CAE1D,CACD,EACA,eAAe,EAAM,CACpB,IAAM,EAAM,EAAS,EAAS,EAAK,MAAM,EAExC,GAAK,SAAW,iBAChB,qFAAqF,KACpF,EAAI,IACL,GACA,EAAK,OAAO,OAAS,oBAErB,EAAQ,OAAO,CAAE,OAAM,UAAW,SAAU,CAAC,CAE/C,CACD,CACD,CACD,EACA,4BAA6B,CAC5B,KAAM,EAAS,yCAA0C,CACxD,UAAW,yEACZ,CAAC,EACD,OAAO,EAAS,CACf,MAAO,CACN,eAAe,EAAM,CACpB,IAAM,EAAM,EAAS,EAAS,EAAK,MAAM,EACrC,GAAK,SAAW,iBAAmB,EAAI,OAAS,yBACnD,EAAQ,OAAO,CAAE,OAAM,UAAW,WAAY,CAAC,CACjD,CACD,CACD,CACD,EACA,2BAA4B,CAC3B,KAAM,EAAS,mEAAoE,CAClF,SACC,gGACF,CAAC,EACD,OAAO,EAAS,CACf,IAAM,EAAO,EAAQ,CAAO,EACtB,EAAW,EAAe,EAChC,OAAO,EAAe,GAAU,EAAM,IAAW,CAChD,GAAI,CAAC,EAAM,OACX,IAAM,EAAW,EAAc,EAAU,EAAQ,SAAU,CAAM,EACjE,GAAI,CAAC,EAAU,OACf,IAAM,EAAS,EAAQ,EAAS,CAAQ,CAAC,EAAE,QAAQ,gBAAiB,EAAE,EACtE,GAAI,CAAC,EAAQ,CACP,EAAS,MAAM,CAAG,CAAC,CAAC,SAAS,cAAc,GAC/C,EAAQ,OAAO,CAAE,OAAM,UAAW,UAAW,CAAC,EAC/C,MACD,CACA,IAAM,EAAO,EAAK,MAAM,GAAG,EACrB,EAAK,EAAO,MAAM,GAAG,EACvB,EAAY,GACZ,EAAK,KAAO,UACf,EAAY,CAAC,MAAO,cAAe,YAAY,CAAC,CAAC,SAAS,EAAG,EAAE,EAE9D,EAAG,KAAO,UACV,EAAK,KAAO,EAAG,IACf,EAAG,KAAO,UACV,EAAK,KAAO,aAEZ,EACC,EAAK,KAAO,UAAY,CAAC,CAAC,QAAS,SAAS,CAAC,CAAC,SAAS,EAAG,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,IAEpE,EAAK,KAAO,MACtB,EACC,CAAC,MAAO,YAAY,CAAC,CAAC,SAAS,EAAG,EAAE,GACnC,EAAG,KAAO,UACV,EAAG,KAAO,UACV,EAAG,MAAQ,EAAK,OAAS,EAAI,EAAK,GAAK,EAAS,CAAI,CAAC,CAAC,QAAQ,gBAAiB,EAAE,GACzE,EAAK,KAAO,gBAAe,EAAY,CAAC,MAAO,YAAY,CAAC,CAAC,SAAS,EAAG,EAAE,GAClF,EAAG,KAAO,cAAgB,EAAK,KAAO,eAAc,EAAY,IAEnE,CAAC,MAAO,SAAU,aAAa,CAAC,CAAC,SAAS,EAAK,EAAE,GACjD,EAAG,OAAS,GACZ,CAAC,CAAC,MAAO,SAAU,cAAe,aAAc,aAAc,YAAY,CAAC,CAAC,SAC3E,EAAG,EACJ,IAEA,EAAY,IACT,GAAW,EAAQ,OAAO,CAAE,OAAM,UAAW,UAAW,CAAC,CAC9D,CAAC,CACF,CACD,EACA,0BAA2B,CAC1B,KAAM,EAAS,2DAA4D,CAC1E,OAAQ,wEACT,CAAC,EACD,OAAO,EAAS,CAEf,OADI,EAAQ,CAAO,CAAC,EAAE,WAAW,MAAM,EAAU,CAAC,EAC3C,CACN,eAAe,EAAM,CACpB,IAAM,EAAM,EAAS,EAAS,EAAK,MAAM,EAExC,EAAK,OAAO,OAAS,kBACrB,GACA,EAAkB,EAAS,EAAI,MAAM,GACrC,6CAA6C,KAAK,EAAI,IAAI,GAE1D,EAAQ,OAAO,CAAE,OAAM,UAAW,QAAS,CAAC,CAC9C,CACD,CACD,CACD,EACA,oBAAqB,CACpB,KAAM,EAAS,0DAA2D,CACzE,QACC,+FACF,CAAC,EACD,OAAO,EAAS,CAEf,OADK,EAAQ,CAAO,CAAC,EAAE,WAAW,MAAM,EACjC,CACN,YAAY,EAAM,CACjB,EAAQ,OAAO,CAAE,OAAM,UAAW,SAAU,CAAC,CAC9C,EACA,gBAAgB,EAAM,CACrB,EAAQ,OAAO,CAAE,OAAM,UAAW,SAAU,CAAC,CAC9C,EACA,sBAAsB,EAAM,CAC3B,EAAQ,OAAO,CAAE,OAAM,UAAW,SAAU,CAAC,CAC9C,EACA,QAAQ,EAAM,CACb,IAAM,EAAW,EAAc,CAAI,EACnC,IAAK,IAAM,KAAa,EAAK,KAO5B,GALC,EAAU,OAAS,uBACnB,EAAU,IACV,CAAC,EAAS,IAAI,EAAU,GAAG,IAAI,GAE/B,EAAQ,OAAO,CAAE,KAAM,EAAW,UAAW,SAAU,CAAC,EACrD,EAAU,OAAS,sBACjB,IAAA,IAAM,KAAe,EAAU,aAElC,EAAY,GAAG,OAAS,cACxB,CAAC,EAAS,IAAI,EAAY,GAAG,IAAI,GACjC,CAAC,0BAA2B,oBAAoB,CAAC,CAAC,SACjD,EAAY,MAAM,MAAQ,EAC3B,GAEA,EAAQ,OAAO,CAAE,KAAM,EAAa,UAAW,SAAU,CAAC,CAG/D,EACA,eAAe,EAAM,CAChB,EAAU,CAAI,GAAG,EAAQ,OAAO,CAAE,OAAM,UAAW,SAAU,CAAC,CACnE,CACD,EApCkD,CAAC,CAqCpD,CACD,EACA,kBAAmB,CAClB,KAAM,EAAS,kDAAmD,CACjE,WAAY,wEACb,CAAC,EACD,OAAO,EAAS,CACf,MAAO,CACN,eAAe,EAAM,CACpB,IAAM,EAAM,EAAS,EAAS,EAAK,MAAM,EACrC,EAAS,EAAK,UAAU,GAC5B,KAAO,GAAQ,OAAS,kBAAoB,GAAQ,OAAS,yBAC5D,EAAS,EAAO,WAEhB,GACA,iBAAiB,KAAK,EAAI,MAAM,GAChC,EAAI,OAAS,QACb,GAAQ,OAAS,mBAEjB,EAAQ,OAAO,CAAE,OAAM,UAAW,YAAa,CAAC,CAClD,CACD,CACD,CACD,EACA,qBAAsB,CACrB,KAAM,EAAS,wDAAyD,CACvE,QACC,0GACF,CAAC,EACD,OAAO,EAAS,CAEf,OADI,EAAc,EAAS,EAAQ,QAAQ,EAAU,CAAC,EAC/C,CACN,eAAe,EAAM,CACpB,GAAI,EAAK,OAAO,OAAS,mBAAoB,OAC7C,IAAM,EAAO,EAAa,EAAK,MAAM,GAEnC,IAAS,WAAa,EAAQ,EAAK,OAAO,OAAQ,OAAO,GACzD,IAAS,WAAa,EAAQ,EAAK,OAAO,OAAQ,SAAS,IAE5D,EAAQ,OAAO,CAAE,OAAM,UAAW,SAAU,CAAC,CAC/C,CACD,CACD,CACD,EACA,uBAAwB,CACvB,KAAM,EAAS,uDAAwD,CACtE,UAAW,4EACZ,CAAC,EACD,OAAO,EAAS,CAEf,OADI,EAAc,EAAS,EAAQ,QAAQ,EAAU,CAAC,EAC/C,CACN,eAAe,EAAM,CACpB,GAAI,CAAC,EAAU,CAAI,EAAG,OACtB,IAAM,EAAO,EAAK,UAAU,GAAG,EAAE,EAC7B,MAAM,OAAS,mBACnB,IAAK,IAAM,KAAY,EAAK,WAAY,CACvC,GAAI,EAAS,OAAS,WAAY,SAClC,IAAM,EAAQ,EAAS,SAAW,EAAa,EAAS,GAAG,EAAI,EAAW,EAAS,GAAG,GAClF,IAAU,aAAe,IAAU,cACtC,EAAQ,OAAO,CAAE,KAAM,EAAU,UAAW,YAAa,KAAM,CAAE,OAAM,CAAE,CAAC,CAC5E,CACD,CACD,CACD,CACD,EACA,uBAAwB,CACvB,KAAM,EAAS,sEAAuE,CACrF,SACC,8GACD,OAAQ,kFACT,CAAC,EACD,OAAO,EAAS,CACf,IAAM,EAAQ,EAAc,EAAS,EAAQ,QAAQ,EAC/C,EAAW,EAAe,EAEhC,OAAO,EAAe,GAAU,EAAM,IAAc,CAC9C,EAAU,WAAW,GAAG,GAE3B,gCAAgC,KAAK,CAAS,GAC9C,CAAC,wDAAwD,KAAK,CAAS,GAEvE,EAAQ,OAAO,CAAE,OAAM,UAAW,QAAS,CAAC,EAG9C,IAAM,EAAS,EAAc,EAAU,EAAQ,SAAU,CAAS,EAC9D,CAAC,GAAU,EAAO,MAAM,CAAG,CAAC,CAAC,SAAS,cAAc,IAEvD,IACC,EAAS,EAAO,CAAM,CAAC,CAAC,WAAW,KAAK,GAAK,GAAK,IAAW,EAAQ,CAAK,GAE3E,EAAQ,OAAO,CAAE,OAAM,UAAW,UAAW,CAAC,EAE9C,CAAC,GACD,EAAc,EAAS,CAAM,GAC7B,CAAC,4BAA4B,KAAK,EAAS,CAAM,CAAC,GAClD,EACC,EAAQ,CAAO,IAAM,oBACrB,oCAAoC,KAAK,EAAS,CAAM,CAAC,IAG1D,EAAQ,OAAO,CAAE,OAAM,UAAW,QAAS,CAAC,EAE9C,CAAC,CACF,CACD,EACA,mBAAoB,CACnB,KAAM,EAAS,2DAA4D,CAC1E,IAAK,4FACN,CAAC,EACD,OAAO,EAAS,CACf,GAAI,CAAC,EAAc,EAAS,EAAQ,QAAQ,EAAG,MAAO,CAAC,EACvD,SAAS,EAAU,EAAmB,CACrC,GAAI,EAAK,OAAS,aAAc,MAAO,GACvC,IAAM,EAAW,EAAQ,EAAS,CAAI,EAEtC,OADI,EAAK,OAAS,WAAa,CAAC,GAAU,KAAK,QACxC,GAAU,KAAK,KACpB,GACA,EAAI,OAAS,iBACb,EAAI,QAAQ,OAAS,qBACrB,CAAC,eAAgB,SAAS,CAAC,CAAC,SAAS,EAAI,OAAO,OAAO,KAAK,GAC5D,CAAC,yBAA0B,0BAA0B,CAAC,CAAC,SAAS,EAAI,KAAK,IAAI,CAC/E,CACD,CACA,MAAO,CACN,kBAAkB,EAAM,CACvB,GAAI,CAAC,eAAgB,SAAS,CAAC,CAAC,SAAS,EAAK,OAAO,KAAK,EACpD,IAAA,IAAM,KAAa,EAAK,WAE3B,EAAU,OAAS,mBACnB,EAAW,EAAU,QAAQ,IAAM,OAEnC,EAAQ,OAAO,CAAE,KAAM,EAAW,UAAW,KAAM,CAAC,CAIxD,EACA,iBAAiB,EAAM,CACtB,GAAI,EAAa,CAAI,IAAM,MAAO,OAClC,IAAM,EAAS,EAAK,OACd,EACL,EAAO,OAAS,oBAChB,EAAa,CAAM,IAAM,WACzB,EAAO,OAAO,OAAS,cACvB,EAAO,OAAO,OAAS,cACvB,CAAC,EAAQ,EAAS,EAAO,MAAM,CAAC,EAAE,KAAK,QACpC,EAAU,CAAM,GAAK,IAAe,EAAQ,OAAO,CAAE,OAAM,UAAW,KAAM,CAAC,CAClF,EACA,mBAAmB,EAAM,CACxB,GAAI,EAAK,MAAQ,EAAU,EAAK,IAAI,GAAK,EAAK,GAAG,OAAS,gBACpD,IAAA,IAAM,KAAY,EAAK,GAAG,WAE7B,EAAS,OAAS,aACjB,EAAS,SAAW,EAAa,EAAS,GAAG,EAAI,EAAW,EAAS,GAAG,KACxE,OAED,EAAQ,OAAO,CAAE,KAAM,EAAU,UAAW,KAAM,CAAC,CAIvD,CACD,CACD,CACD,EACA,yBAA0B,CACzB,KAAM,EAAS,8DAA+D,CAC7E,OAAQ,kCACT,CAAC,EACD,OAAO,EAAS,CACf,MAAO,CACN,eAAe,EAAM,CACpB,IAAM,EAAM,EAAS,EAAS,EAAK,MAAM,EACnC,EAAO,EAAQ,CAAO,EAC5B,GAAI,CAAC,EAAM,OACX,IAAI,EAA0B,KAC1B,GAAK,SAAW,kBAElB,EAAI,OAAS,gBACb,IAAS,aACT,EAAE,EAAc,EAAS,EAAQ,QAAQ,GAAK,EAAS,CAAI,IAAM,eAEjE,EAAW,iDAEX,EAAI,OAAS,eACb,EACC,EAAc,EAAS,EAAQ,QAAQ,GACvC,CAAC,YAAa,UAAU,CAAC,CAAC,SAAS,EAAS,CAAI,CAAC,KAGlD,EACC,0FAGF,GACA,CAAC,UAAW,mBAAmB,CAAC,CAAC,SAAS,EAAI,MAAM,GACpD,CAAC,WAAY,cAAe,iBAAiB,CAAC,CAAC,SAAS,EAAI,IAAI,GAChE,CAAC,8BAA8B,KAAK,CAAI,IAExC,EAAW,6BACR,GACH,EAAQ,OAAO,CACd,OACA,UAAW,SACX,KAAM,CAAE,QAAS,GAAK,MAAQ,QAAS,KAAM,CAAS,CACvD,CAAC,EAED,EAAK,OAAO,OAAS,oBACrB,CAAC,QAAS,cAAe,aAAa,CAAC,CAAC,SAAS,EAAa,EAAK,MAAM,GAAK,EAAE,GAChF,EAAa,EAAK,OAAO,MAAM,GAC/B,CAAC,EAAc,EAAS,EAAQ,QAAQ,GACxC,CAAC,6BAA6B,KAAK,CAAI,GAEvC,EAAQ,OAAO,CACd,OACA,UAAW,SACX,KAAM,CAAE,QAAS,gBAAiB,KAAM,0BAA2B,CACpE,CAAC,CAEH,CACD,CACD,CACD,EACA,wBAAyB,CACxB,KAAM,EAAS,sEAAuE,CACrF,SAAU,gFACX,CAAC,EACD,OAAO,EAAS,CACf,SAAS,EAAM,EAAmE,CACjF,GAAI,EAAK,OAAQ,CAChB,EAAQ,OAAO,CAAE,OAAM,UAAW,UAAW,CAAC,EAC9C,MACD,CACA,IAAK,IAAM,KAAa,EAAK,OAAS,yBAA2B,EAAK,WAAa,CAAC,EAC/E,EAAQ,EAAS,EAAU,KAAK,CAAC,EAAE,KAAK,KAAM,GAAQ,EAAI,OAAS,eAAe,GACrF,EAAQ,OAAO,CAAE,KAAM,EAAW,UAAW,UAAW,CAAC,CAG5D,CACA,MAAO,CACN,uBAAwB,EACxB,qBAAsB,EACtB,yBAAyB,EAAM,CAE7B,EAAK,YAAY,OAAS,cAC1B,EAAQ,EAAS,EAAK,WAAW,CAAC,EAAE,KAAK,KAAM,GAAQ,EAAI,OAAS,eAAe,GAEnF,EAAQ,OAAO,CAAE,OAAM,UAAW,UAAW,CAAC,CAEhD,CACD,CACD,CACD,EACA,mBAAoB,CACnB,KAAM,EAAS,+DAAgE,CAC9E,MACC,0GACF,CAAC,EACD,OAAO,EAAS,CACf,MAAO,CACN,QAAQ,EAAM,CACb,IAAM,EAAU,EAAc,CAAI,EAC5B,EAAa,EAAK,KAAK,cAAe,GAC3C,EAAU,KAAK,WAAW,QAAQ,CACnC,EACA,IAAK,IAAM,KAAa,EAAK,KAAK,MAAM,EAAG,EAAa,EAAI,EAAI,CAAU,EAExE,EAAU,OAAS,uBACnB,EAAU,IACV,CAAC,EAAQ,IAAI,EAAU,GAAG,IAAI,GAE9B,EAAQ,OAAO,CAAE,KAAM,EAAW,UAAW,OAAQ,CAAC,CAGzD,CACD,CACD,CACD,EACA,wBAAyB,CACxB,KAAM,EAAS,iEAAkE,CAChF,OAAQ,gFACT,CAAC,EACD,OAAO,EAAS,CACf,MAAO,CACN,QAAQ,EAAM,CACb,IAAM,EAAU,EAAc,CAAI,EAClC,IAAK,IAAM,KAAa,EAAK,KACxB,KAAU,OAAS,sBACvB,IAAK,IAAM,KAAe,EAAU,aAElC,EAAY,GAAG,OAAS,cACxB,CAAC,EAAQ,IAAI,EAAY,GAAG,IAAI,IAC/B,EAAY,MAAM,OAAS,2BAC3B,EAAY,MAAM,OAAS,uBAE5B,EAAQ,OAAO,CAAE,KAAM,EAAa,UAAW,QAAS,CAAC,CAI7D,CACD,CACD,CACD,CAGuD,CAAgB,EAClE,EAAiB,CAAE,KAAM,CAAE,KAAM,KAAM,EAAG,OAAM,EAGtD,SAAS,EAAW,EAA8D,CACjF,IAAM,EAAS,CAAE,GAAG,CAAO,EAErB,EAAQ,OAAO,KAAK,CAAM,EAChC,IAAK,IAAM,KAAQ,EAAO,CACzB,IAAM,EAAO,EAAO,GACpB,EAAO,GAAQ,CACd,KAAM,CACL,GAAG,EAAK,KACR,OAAQ,CACP,CACC,KAAM,SACN,WAAY,CAAE,UAAW,CAAE,KAAM,SAAU,UAAW,CAAE,CAAE,EAC1D,qBAAsB,EACvB,CACD,CACD,EACA,OAAO,EAAS,CACf,IAAM,EAAO,EAAQ,CAAO,EAO5B,MALC,CAAC,GACD,2CAA2C,KAAK,CAAI,GACpD,+BAA+B,KAAK,CAAI,EAEjC,CAAC,EACF,EAAK,OAAO,CAAO,CAC3B,CACD,CACD,CACA,OAAO,CACR"}
@@ -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` |
package/docs/llms.md CHANGED
@@ -9,6 +9,9 @@
9
9
  > authoritative. Restructuring an existing raw project → `migration.md`.
10
10
  > Upgrading from 0.0.x → `upgrading.md`. New 0.1.0 helpers: `crud.md`,
11
11
  > `webhooks.md`, `agent-tools.md`; pagination in `zod-table.md`/`auth.md`.
12
+ > Consumer lint configuration and coverage → `oxlint.md`. The optional
13
+ > `cvx-kit/oxlint` plugin checks the statically detectable conventions;
14
+ > behavioral guarantees still need architecture and runtime tests.
12
15
 
13
16
  > **Placeholders.** Names like `documents`, `users`, `history`, `title`,
14
17
  > `ownerId`, and `documentPublish` in the examples below are illustrative
package/docs/oxlint.md ADDED
@@ -0,0 +1,146 @@
1
+ # Oxlint rules for cvx-kit applications
2
+
3
+ `cvx-kit/oxlint` is a TypeScript-authored plugin bundled with the package. It
4
+ checks applications using the kit against `conventions.md`, the authoritative
5
+ source for folder structure and file anatomy. The package repository has a
6
+ separate, unpublished plugin for its own `src/` layout.
7
+
8
+ The consumer plugin was introduced in 0.1.5; the initial 0.1.4 plugin targeted
9
+ library authoring. The five architecture rules below were added in 0.1.6. See `upgrading.md` when migrating that configuration.
10
+
11
+ ## Configuration
12
+
13
+ Install Oxlint as a development dependency and load the plugin in
14
+ `.oxlintrc.json`. The root README includes a configuration enabling all rules.
15
+ Loading a plugin does not enable its rules. For example:
16
+
17
+ ```json
18
+ {
19
+ "jsPlugins": [{ "name": "cvx", "specifier": "cvx-kit/oxlint" }],
20
+ "rules": {
21
+ "cvx/no-raw-builders": "error",
22
+ "cvx/domain-import-boundaries": "error",
23
+ "cvx/thin-api-adapters": "error"
24
+ }
25
+ }
26
+ ```
27
+
28
+ With Vite+, place those fields under `lint` in `vite.config.ts`. The plugin
29
+ exports a default plugin object, named `rules`, and the `RuleName` type. It is
30
+ tested with Oxlint 1.79.0 and Vite+ 0.3.0.
31
+
32
+ All rules default to `convex/` relative to the linter's working directory.
33
+ For another location, set `convexDir` on **each enabled rule**:
34
+
35
+ ```json
36
+ { "cvx/no-raw-builders": ["error", { "convexDir": "src/server/convex" }] }
37
+ ```
38
+
39
+ Source rules ignore files outside that directory, `_generated/`, `__tests__/`,
40
+ and test/spec files. `project-structure` reads the whole backend, including tests. They do not
41
+ offer automatic fixes for architectural moves.
42
+
43
+ ## Coverage
44
+
45
+ Every name below has the `cvx/` prefix in lint configuration.
46
+
47
+ | Rule | Detects | Documentation |
48
+ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
49
+ | `project-structure` | Root folder allowlist, domain ownership, colocated runnable tests, misplaced tests, symlinks, parse failures, runtime domain-module cycles, and shared implementations with only one domain consumer. | Folder structure and Tests |
50
+ | `root-wiring-only` | Unknown root files without a component facade, root function declarations, nondelegating callbacks, branches, loops, direct writes/network calls, and inline root schema construction. | Root files |
51
+ | `application-orchestration` | Files without two domain dependencies, provider construction, external runtime dependencies, database access, validator construction, and business branches. | Module grain |
52
+ | `internal-function-ownership` | Internal/system registrations at root, in public API adapters, or in domain/shared. | Folder structure |
53
+ | `domain-file-responsibilities` | Impure rules, query writes/mutations, database access in declarative files, misplaced direct external I/O, and imports that violate these roles. | File anatomy |
54
+ | `root-facade-ownership` | `createAuthFunctions`, `createTriggers`, and `new Foundation` outside their root files, or repeated in one file. | `conventions.md`, Root files |
55
+ | `no-raw-builders` | Runtime imports of raw Convex builders outside `functions.ts`; namespace calls to raw generic builders. | `auth.md`; `llms.md`, Non-negotiable rules |
56
+ | `no-handwritten-references` | Calls to Convex `makeFunctionReference`, including import aliases. | `conventions.md`, Import rules |
57
+ | `domain-import-boundaries` | Reverse dependencies into `api/`, domain imports of `application/`, forbidden sibling imports, and API adapters importing another domain module. | `conventions.md`, Module grain and Import rules |
58
+ | `public-functions-in-api` | Calls to root `auth*`, `role*`, or `admin*` constructors outside `api/`. | `conventions.md`, Folder structure |
59
+ | `thin-api-adapters` | `if`, `switch`, ternary branches, top-level private helpers, and direct `ctx.db` writes in `api/`. | `conventions.md`, The adapter file |
60
+ | `no-inline-enums` | Literal arrays passed to imported Zod `enum`. Use named vocabulary tuples. | `conventions.md`, Vocabularies |
61
+ | `no-unbounded-reads` | Direct `ctx.db.query(...).collect()` chains and `ctx.include(...).resolve()` chains in host code. | `llms.md`, Bounded reads; `zod-table.md` |
62
+ | `no-manual-timestamps` | Explicit `createdAt`/`updatedAt` fields in object literals passed to direct `ctx.db` writes. | `triggers.md` |
63
+ | `component-boundaries` | Local portable components importing host/sibling files; host imports bypassing component client facades; private package component subpaths. | `architecture.md`, Injection, not import |
64
+ | `no-component-env` | Direct `process.env` access, process import aliases, environment destructuring, and named env imports inside local portable components. | `conventions.md`, Import rules |
65
+ | `schema-file-boundaries` | Host `defineTable`, misplaced `defineSchema` or kit table constructors, and misplaced `.table.index/searchIndex/vectorIndex` chains. | `conventions.md`, Shapes and topology |
66
+ | `no-internal-reexports` | Re-exports and import-then-export aliases inside `convex/`. | `conventions.md`, Universal file order |
67
+ | `public-api-first` | Private function declarations before the last export. | `conventions.md`, Universal file order |
68
+ | `named-private-helpers` | Top-level private arrow/function-expression variables. | `conventions.md`, Universal file order |
69
+
70
+ ## Allowed patterns
71
+
72
+ - `functions.ts` imports raw builders to configure `createAuthFunctions`.
73
+ Type-only imports such as `MutationCtx` from `_generated/server` are valid
74
+ elsewhere. `httpAction` remains available for HTTP routing.
75
+ - Domain modules may import sibling `rules.ts` and `queries.ts`, as explicitly
76
+ allowed by the module-grain section. Cross-module writes go through the
77
+ optional `application/` layer. `domain/table.ts` only imports module table maps
78
+ and combines them with `createModule` from `cvx-kit/zod-table`.
79
+ - API adapters may delegate and perform the documented bounded inline query
80
+ and DTO projection. `system*` constructors may live in domain modules.
81
+ - Authored components under `convex/components/<name>/` may use their own raw
82
+ builders and raw tables. Host-specific facade, timestamp, and bounded-read
83
+ rules do not apply there. Configuration imports for mounting components are
84
+ allowed from root `convex.config.ts`.
85
+ - Host schemas use kit table constructors in `domain/<module>/schema.ts` and
86
+ attach indexes in `domain/<module>/table.ts`. `schema.ts` assembles them.
87
+ - `archivedAt` remains application-controlled. Singular `db.get` reads and
88
+ bounded `execute`/`paginate` calls are allowed.
89
+
90
+ ## What still needs review and tests
91
+
92
+ These are syntax and lexical-binding checks, not whole-program validation.
93
+ Import-boundary rules resolve relative imports and tsconfig aliases (including
94
+ extended configs) using the package resolver. Symlinked source is rejected by
95
+ the project scan. Computed import paths are not resolved. Named import aliases and namespaces are recognized where documented;
96
+ arbitrary variable reassignment, wrapper functions, destructured database
97
+ methods, and calls split across variables are not traced. Database checks
98
+ recognize the documented `ctx.db` member shape, not its runtime type.
99
+
100
+ The plugin does not prove DTO redaction, complete command/audit coverage,
101
+ tenant/RLS correctness, unique table ownership across files, required facade
102
+ existence, exactly one component mount, numeric read limits, or vocabulary
103
+ tuple ownership. Run the behavioral tests described in `maintainability.md`. The package owns
104
+ structural enforcement; applications do not need copied architecture tests. A clean lint run is not
105
+ evidence of those runtime guarantees.
106
+
107
+ ## Whole-project validation
108
+
109
+ Enable `cvx/project-structure` and lint the **whole** configured backend. The
110
+ scan reports on `convex.config.ts`, or the first non-test source file if no
111
+ configuration file exists. That anchor must be part of the lint invocation;
112
+ changed-file-only runs and cached lint runs can skip project checks. Use a full,
113
+ uncached lint run in CI. The scan ignores generated code, reads current files on
114
+ every invocation, and does not execute application code.
115
+
116
+ For standalone tooling, including an empty/missing backend, use the same packaged
117
+ checker directly:
118
+
119
+ ```ts
120
+ import { checkArchitecture } from 'cvx-kit/oxlint'
121
+
122
+ const issues = checkArchitecture({ cwd: process.cwd(), convexDir: 'convex' })
123
+ for (const issue of issues) console.error(`${issue.file}: ${issue.message}`)
124
+ if (issues.length) process.exitCode = 1
125
+ ```
126
+
127
+ No fixed module scaffold is required. Type-only modules and grouping folders
128
+ need no test directory. Runtime schema/constants files do; root wiring shares
129
+ root tests. Test files must be directly inside the owning `__tests__/` directory.
130
+ Recognized test entrypoints are `test`/`it` globals and imports from Vitest,
131
+ Vite+, Bun, Node, or Jest, including import aliases and namespaces. Empty,
132
+ TODO, skipped, and conditionally skipped/run tests do not satisfy the minimum.
133
+ A nonempty callback is a presence check, not evidence of meaningful assertions,
134
+ execution, or coverage. Run the tests in CI.
135
+
136
+ Cycle detection operates on runtime ESM imports/re-exports and literal dynamic
137
+ imports between `domain/<module>` owners, including nested files. Type-only
138
+ imports do not create runtime cycle edges. Arbitrary CommonJS wrappers, computed
139
+ imports, implicit generated function-reference calls, and value flow are not
140
+ whole-program analyzed. Rules report recognizable violations; clean lint does
141
+ not prove arbitrary business logic is absent.
142
+
143
+ Shared ownership follows static runtime imports through shared helpers. A shared
144
+ file reached from only one domain is reported for relocation. Unused code,
145
+ type-only consumers, and computed dependencies are not treated as proof of shared
146
+ ownership; their ownership still needs review.
package/docs/upgrading.md CHANGED
@@ -1,11 +1,39 @@
1
1
  # Upgrading to 0.1.0 — the new way of things
2
2
 
3
+ ## 0.1.6 — stricter architecture checks
4
+
5
+ Enable `project-structure`, `root-wiring-only`, `application-orchestration`,
6
+ `internal-function-ownership`, and `domain-file-responsibilities` alongside the
7
+ existing rules. These are opt-in additions; existing enabled boundary rules now
8
+ also resolve tsconfig aliases and reject migration dependencies.
9
+
10
+ Move root implementation folders into their owning domains. Keep `application/`
11
+ limited to multi-domain coordination. Move centralized domain tests beside their
12
+ owners and add real tests for nested executable directories. Do not create empty
13
+ scaffold files. Replace copied structural architecture suites with the packaged
14
+ checks; retain tests that exercise actual application behavior and wiring.
15
+
16
+ ## 0.1.5 — consumer Oxlint rules
17
+
18
+ The initial 0.1.4 plugin incorrectly targeted this package's `src/` layout.
19
+ `cvx-kit/oxlint` now targets consuming applications under `convex/`, following
20
+ `conventions.md`. The six existing rule names remain, with consumer semantics;
21
+ nine additional rules cover builders, generated references, domain imports,
22
+ public adapters, facade ownership, vocabularies, reads, and timestamps.
23
+
24
+ Remove `entryPoints` and `packageName` options from your consumer lint config.
25
+ Applications have no barrel entry points inside `convex/`. Every rule now
26
+ accepts only an optional `convexDir`, relative to the linter's working directory
27
+ (default: `convex`). Enable the new rules explicitly; loading the plugin alone
28
+ does not enable them. See `oxlint.md` for coverage and the root README for a
29
+ complete configuration. Repository authoring checks remain local tooling.
30
+
31
+ No runtime API or storage migration is part of this correction.
32
+
3
33
  ## 0.1.4
4
34
 
5
- The optional Oxlint plugin is available through `cvx-kit/oxlint`; the root
6
- README shows consumer configuration. Loading the plugin does not enable rules
7
- automatically. Select rules in your lint configuration and list your public
8
- entry points in `cvx/no-internal-reexports`.
35
+ This release introduced the optional `cvx-kit/oxlint` export. Its original
36
+ rules targeted library authoring; see the correction above for consumer use.
9
37
 
10
38
  This patch tightens several TypeScript contracts during the anti-slop cleanup:
11
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cvx-kit",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Reusable Convex application kit: foundation kernels, approvals component, zod table boundaries, WorkOS-backed auth function constructors, and audited command protocol.",
5
5
  "keywords": [
6
6
  "component",
@@ -109,7 +109,7 @@
109
109
  "scripts": {
110
110
  "lint": "vp lint",
111
111
  "check": "vp check",
112
- "test:lint": "vp test run tools/oxlint/cvx/index.test.mjs",
112
+ "test:lint": "vp test run tools/oxlint/cvx",
113
113
  "build": "vp pack && node scripts/convex-config-bridge.mjs && publint",
114
114
  "typecheck": "tsc --noEmit",
115
115
  "test": "vp test run --exclude test/package-contents.test.mjs",
@@ -127,7 +127,9 @@
127
127
  "dependencies": {
128
128
  "@convex-dev/workflow": "0.4.6",
129
129
  "@oxlint/plugins": "1.79.0",
130
- "convex-audit-log": "^0.2.0"
130
+ "convex-audit-log": "^0.2.0",
131
+ "oxc-parser": "0.150.0",
132
+ "oxc-resolver": "11.24.2"
131
133
  },
132
134
  "devDependencies": {
133
135
  "@edge-runtime/vm": "^5.0.0",
@@ -9,13 +9,15 @@ const root = join(import.meta.dirname, '..')
9
9
  const temporary = mkdtempSync(join(tmpdir(), 'cvx-kit-oxlint-'))
10
10
  const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
11
11
  const ruleNames = Object.keys(rules)
12
- assert.equal(ruleNames.length, 6)
12
+ assert.equal(ruleNames.length, 20)
13
13
 
14
14
  try {
15
15
  run('bun', ['pm', 'pack', '--destination', temporary, '--ignore-scripts'], root)
16
16
  for (const installer of ['npm', 'bun']) {
17
17
  const fixture = join(temporary, installer)
18
- mkdirSync(join(fixture, 'src/components/example'), { recursive: true })
18
+ mkdirSync(join(fixture, 'convex/components/example'), { recursive: true })
19
+ mkdirSync(join(fixture, 'convex/api'), { recursive: true })
20
+ mkdirSync(join(fixture, 'convex/domain/orders'), { recursive: true })
19
21
  writeFileSync(
20
22
  join(fixture, 'package.json'),
21
23
  JSON.stringify({
@@ -35,24 +37,28 @@ try {
35
37
  join(fixture, '.oxlintrc.json'),
36
38
  JSON.stringify({
37
39
  jsPlugins: [{ name: 'cvx', specifier: 'cvx-kit/oxlint' }],
38
- rules: {
39
- ...Object.fromEntries(ruleNames.map((name) => [`cvx/${name}`, 'error'])),
40
- 'cvx/no-internal-reexports': ['error', { entryPoints: ['src/public.ts'] }],
41
- },
40
+ rules: Object.fromEntries(ruleNames.map((name) => [`cvx/${name}`, 'error'])),
42
41
  }),
43
42
  )
44
43
  writeFileSync(
45
- join(fixture, 'src/public.ts'),
46
- "export { run } from './components/example/client'\n",
44
+ join(fixture, 'convex/api/orders.ts'),
45
+ "import { authMutation } from '../functions'; import { executeSaveOrder } from '../domain/orders/commands'; export const save = authMutation({ handler: (ctx, args) => executeSaveOrder(ctx, args) })\n",
47
46
  )
48
47
  writeFileSync(
49
- join(fixture, 'src/components/example/client.ts'),
48
+ join(fixture, 'convex/components/example/client.ts'),
50
49
  'export function run() { return 1 }\n',
51
50
  )
51
+ for (const directory of ['api', 'components/example']) {
52
+ mkdirSync(join(fixture, 'convex', directory, '__tests__'), { recursive: true })
53
+ writeFileSync(
54
+ join(fixture, 'convex', directory, '__tests__/behavior.test.ts'),
55
+ 'test("behavior", () => { expect(1).toBe(1) })',
56
+ )
57
+ }
52
58
  const oxlint = join(fixture, 'node_modules/.bin/oxlint')
53
- run(oxlint, ['--config', '.oxlintrc.json', 'src'], fixture)
59
+ run(oxlint, ['--config', '.oxlintrc.json', 'convex'], fixture)
54
60
  writeFileSync(
55
- join(fixture, 'src/components/example/bad.ts'),
61
+ join(fixture, 'convex/components/example/bad.ts'),
56
62
  `
57
63
  import { defineTable } from 'convex/server'
58
64
  import { host } from '../../host'
@@ -64,13 +70,47 @@ export const value = privateHelper()
64
70
  `,
65
71
  )
66
72
  writeFileSync(
67
- join(fixture, 'src/host.ts'),
68
- "export const host = 1\nimport './components/example/bad'\n",
73
+ join(fixture, 'convex/host.ts'),
74
+ "import { authMutation } from './functions'; export const save = authMutation({}); export const host = 1; import './components/example/bad'\n",
75
+ )
76
+ writeFileSync(
77
+ join(fixture, 'convex/api/orders.ts'),
78
+ `import { mutation } from '../_generated/server'
79
+ import { makeFunctionReference as ref } from 'convex/server'
80
+ import { z } from 'zod'
81
+ import { createAuthFunctions } from 'cvx-kit/auth'
82
+ import { other } from '../domain/users/commands'
83
+ export const save = mutation({ handler: async (ctx) => {
84
+ await ctx.db.insert('orders', { updatedAt: Date.now() })
85
+ return ctx.db.query('orders').collect()
86
+ }})
87
+ export const constructors = createAuthFunctions({})
88
+ export const state = z.enum(['draft'])
89
+ export const target = ref('orders:save')
90
+ export const value = other
91
+ `,
92
+ )
93
+ mkdirSync(join(fixture, 'convex/application'), { recursive: true })
94
+ writeFileSync(
95
+ join(fixture, 'convex/application/provider.ts'),
96
+ "import Stripe from 'stripe'; export const provider = new Stripe('key')",
97
+ )
98
+ writeFileSync(
99
+ join(fixture, 'convex/domain/orders/queries.ts'),
100
+ "export function save(ctx) { return ctx.db.insert('orders', {}) }",
101
+ )
102
+ writeFileSync(
103
+ join(fixture, 'convex/api/internal.ts'),
104
+ "import { systemMutation } from '../functions'; export const internal = systemMutation({})",
105
+ )
106
+ const invalid = spawnSync(
107
+ oxlint,
108
+ ['--config', '.oxlintrc.json', '--format', 'json', 'convex'],
109
+ {
110
+ cwd: fixture,
111
+ encoding: 'utf8',
112
+ },
69
113
  )
70
- const invalid = spawnSync(oxlint, ['--config', '.oxlintrc.json', '--format', 'json', 'src'], {
71
- cwd: fixture,
72
- encoding: 'utf8',
73
- })
74
114
  assert.ifError(invalid.error)
75
115
  assert.equal(invalid.status, 1, invalid.stderr)
76
116
  const diagnostics = JSON.parse(invalid.stdout).diagnostics
@@ -83,11 +123,13 @@ export const value = privateHelper()
83
123
  writeFileSync(
84
124
  join(fixture, 'plugin-types.ts'),
85
125
  `
86
- import plugin, { rules, type RuleName } from 'cvx-kit/oxlint'
126
+ import plugin, { rules, checkArchitecture, type ArchitectureDiagnostic, type RuleName } from 'cvx-kit/oxlint'
87
127
  import type { Plugin, Rule } from '@oxlint/plugins'
88
128
  const compatible: Plugin = plugin
89
129
  const name: RuleName = 'component-boundaries'
90
130
  const rule: Rule = rules[name]
131
+ const issues: ArchitectureDiagnostic[] = checkArchitecture()
132
+ void issues
91
133
  void compatible
92
134
  void rule
93
135
  `,
@@ -109,7 +151,7 @@ void rule
109
151
  fixture,
110
152
  )
111
153
  console.log(
112
- `${installer}: packed Oxlint plugin loads, all six rules report, and declarations typecheck`,
154
+ `${installer}: packed Oxlint plugin loads, all consumer rules report, and declarations typecheck`,
113
155
  )
114
156
  }
115
157
  } finally {