cvx-kit 0.1.13 → 0.1.14
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/AGENTS.md +47 -0
- package/CHANGELOG.md +20 -0
- package/README.md +144 -0
- package/dist/auth.d.mts +3 -1
- package/dist/auth.mjs +1 -1
- package/dist/auth.mjs.map +1 -1
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +1 -1
- package/dist/modules/effect/api-zod.d.mts +1 -1
- package/dist/oxlint/architecture.d.mts +2 -0
- package/dist/oxlint/architecture.mjs +1 -1
- package/dist/oxlint/architecture.mjs.map +1 -1
- package/dist/oxlint/ast.d.mts +10 -1
- package/dist/oxlint/ast.mjs +1 -1
- package/dist/oxlint/ast.mjs.map +1 -1
- package/dist/oxlint.d.mts +1 -1
- package/dist/oxlint.mjs +1 -1
- package/dist/oxlint.mjs.map +1 -1
- package/dist/querying/cursors.d.mts +45 -0
- package/dist/querying/cursors.mjs +2 -0
- package/dist/querying/cursors.mjs.map +1 -0
- package/dist/querying/execution.d.mts +30 -0
- package/dist/querying/execution.mjs +2 -0
- package/dist/querying/execution.mjs.map +1 -0
- package/dist/querying/filters.d.mts +136 -0
- package/dist/querying/filters.mjs +2 -0
- package/dist/querying/filters.mjs.map +1 -0
- package/dist/querying/planner.d.mts +50 -0
- package/dist/querying/planner.mjs +2 -0
- package/dist/querying/planner.mjs.map +1 -0
- package/dist/querying/relationships.d.mts +41 -0
- package/dist/querying/relationships.mjs +2 -0
- package/dist/querying/relationships.mjs.map +1 -0
- package/dist/querying/security.d.mts +107 -0
- package/dist/querying/security.mjs +2 -0
- package/dist/querying/security.mjs.map +1 -0
- package/dist/querying.d.mts +39 -0
- package/dist/querying.mjs +2 -0
- package/dist/querying.mjs.map +1 -0
- package/dist/relations-public.d.mts +2 -0
- package/dist/relations-public.mjs +1 -0
- package/dist/relations.d.mts +155 -0
- package/dist/relations.mjs +2 -0
- package/dist/relations.mjs.map +1 -0
- package/dist/zod-convex.d.mts +10 -0
- package/dist/zod-convex.mjs +2 -0
- package/dist/zod-convex.mjs.map +1 -0
- package/dist/zod-registration.d.mts +10 -0
- package/dist/zod-registration.mjs +2 -0
- package/dist/zod-registration.mjs.map +1 -0
- package/dist/zod-table.d.mts +63 -8
- package/dist/zod-table.mjs +1 -1
- package/dist/zod-table.mjs.map +1 -1
- package/docs/conventions.md +12 -0
- package/docs/llms.md +15 -5
- package/docs/oxlint.md +129 -25
- package/docs/querying.md +293 -0
- package/docs/zod-table.md +93 -13
- package/package.json +11 -1
- package/scripts/owned-process.mjs +140 -0
- package/scripts/owned-process.test.mjs +107 -0
- package/scripts/smoke-packed-querying.mjs +296 -0
- package/scripts/test-packed-effect.mjs +8 -1
- package/scripts/test-packed-oxlint.mjs +5 -1
- package/scripts/test-packed-querying.mjs +185 -0
package/dist/oxlint.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oxlint.mjs","names":["checkArchitecture","inspectArchitecture","options","rules","scopeRules","ownershipRules","reservedDomainFiles","metadata","componentRoot","context","seen","check","ref","imported","node","file","appFile","resolver","createResolver","importedSource","target","resolveImport","source","specifier","name","exportName","sourceVisitors","resolved","sep","from","to","forbidden","basename","isFunctionsImport","exported","exportedNames","statement","declaration","isDbWrite","values","propertyName","hasCall","data","property","field","staticString","owner","relative","dirname","isProcess","variable","binding","def","object","globalProcess","expected","isTableChain","exports","lastExport","plugin","scoped","names","rule"],"sources":["../src/oxlint.ts"],"sourcesContent":["import { basename, dirname, relative, sep } from 'node:path'\nimport type { CreateRule, ESTree, Plugin, Rule } from '@oxlint/plugins'\nimport {\n\tinspectArchitecture,\n\tcreateResolver,\n\tresolveImport,\n\ttype ArchitectureOptions,\n\ttype ArchitectureDiagnostic,\n} from './oxlint/architecture'\nimport { reservedDomainFiles } from './oxlint/reserved-files'\nimport { ownershipRules, type OwnershipRuleName } from './oxlint/ownership'\nimport {\n\tappFile,\n\timported,\n\tsourceVisitors,\n\tisFunctionsImport,\n\tisTableChain,\n\tisDbWrite,\n\thasCall,\n\tmetadata,\n\tcomponentRoot,\n\tstaticString,\n\tpropertyName,\n\tbinding,\n\texportedNames,\n\texportName,\n} from './oxlint/ast'\n\nexport type { ArchitectureOptions, ArchitectureDiagnostic } from './oxlint/architecture'\n\nexport function checkArchitecture(options: ArchitectureOptions = {}): ArchitectureDiagnostic[] {\n\treturn inspectArchitecture(options)\n}\n\nexport type RuleName =\n\t| OwnershipRuleName\n\t| 'component-boundaries'\n\t| 'no-component-env'\n\t| 'schema-file-boundaries'\n\t| 'no-internal-reexports'\n\t| 'public-api-first'\n\t| 'named-private-helpers'\n\t| 'no-raw-builders'\n\t| 'no-handwritten-references'\n\t| 'domain-import-boundaries'\n\t| 'public-functions-in-api'\n\t| 'thin-api-adapters'\n\t| 'no-inline-enums'\n\t| 'no-unbounded-reads'\n\t| 'no-manual-timestamps'\n\t| 'reserved-domain-file-anatomy'\n\t| 'root-facade-ownership'\n\n/** Consumer application rules derived from docs/conventions.md. */\nconst applicationRules: Record<RuleName, CreateRule> = {\n\t...ownershipRules,\n\t'reserved-domain-file-anatomy': reservedDomainFiles,\n\t'root-facade-ownership': {\n\t\tmeta: metadata('Configure shared kit infrastructure once in its root facade.', {\n\t\t\tfacade: 'Configure {{name}} once in convex/{{file}}; import that facade elsewhere.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\tconst seen = new Set<string>()\n\t\t\tfunction check(node: ESTree.CallExpression | ESTree.NewExpression) {\n\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\tif (!ref) return\n\t\t\t\tlet file: string | null = null\n\t\t\t\tif (['cvx-kit', 'cvx-kit/auth'].includes(ref.source) && ref.name === 'createAuthFunctions')\n\t\t\t\t\tfile = 'functions.ts'\n\t\t\t\tif (['cvx-kit', 'cvx-kit/triggers'].includes(ref.source) && ref.name === 'createTriggers')\n\t\t\t\t\tfile = 'triggers.ts'\n\t\t\t\tif (\n\t\t\t\t\t['cvx-kit', 'cvx-kit/components/foundation'].includes(ref.source) &&\n\t\t\t\t\tref.name === 'Foundation'\n\t\t\t\t)\n\t\t\t\t\tfile = 'foundation.ts'\n\t\t\t\tif (\n\t\t\t\t\t(ref.source === 'cvx-kit/effect' && ref.name === 'createEffectFoundation') ||\n\t\t\t\t\t(ref.source === 'cvx-kit' && ref.name === 'createFoundation')\n\t\t\t\t)\n\t\t\t\t\tfile = 'foundation.ts'\n\t\t\t\tif (!file) return\n\t\t\t\tif (appFile(context) !== file || seen.has(ref.name))\n\t\t\t\t\tcontext.report({ node, messageId: 'facade', data: { name: ref.name, file } })\n\t\t\t\tseen.add(ref.name)\n\t\t\t}\n\t\t\treturn { CallExpression: check, NewExpression: check }\n\t\t},\n\t},\n\t'no-raw-builders': {\n\t\tmeta: metadata('Build host functions through the auth-aware root constructors.', {\n\t\t\tbuilder:\n\t\t\t\t'Import raw builders only in convex/functions.ts; use auth*/role*/admin* or system* elsewhere.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (appFile(context) === 'functions.ts' || componentRoot(context, context.filename)) return {}\n\t\t\tconst resolver = createResolver()\n\t\t\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (node.importKind === 'type') return\n\t\t\t\t\tconst importedSource = node.source.value\n\t\t\t\t\tconst target = resolveImport(resolver, context.filename, importedSource)\n\t\t\t\t\tconst source =\n\t\t\t\t\t\ttarget && appFile(context, target)?.startsWith('_generated/')\n\t\t\t\t\t\t\t? appFile(context, target)!\n\t\t\t\t\t\t\t: importedSource\n\t\t\t\t\tif (\n\t\t\t\t\t\t!/(?:^|\\/)_generated\\/server(?:\\.[cm]?[jt]s)?$/.test(source) &&\n\t\t\t\t\t\tsource !== 'convex/server'\n\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\tfor (const specifier of node.specifiers) {\n\t\t\t\t\t\tif (specifier.type === 'ImportSpecifier' && specifier.importKind === 'type') continue\n\t\t\t\t\t\tconst name =\n\t\t\t\t\t\t\tspecifier.type === 'ImportSpecifier' ? exportName(specifier.imported) : null\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t(name === null && source !== 'convex/server') ||\n\t\t\t\t\t\t\t(name &&\n\t\t\t\t\t\t\t\t/^(?:internal)?(?:Query|Mutation|Action)(?:Generic)?$|^(?:query|mutation|action)(?:Generic)?$/.test(\n\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'builder' })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tref?.source === 'convex/server' &&\n\t\t\t\t\t\t/^(?:internal)?(?:Query|Mutation|Action)Generic$|^(?:query|mutation|action)Generic$/.test(\n\t\t\t\t\t\t\tref.name,\n\t\t\t\t\t\t) &&\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression'\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'builder' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-handwritten-references': {\n\t\tmeta: metadata('Use generated api/internal references.', {\n\t\t\treference: 'Use generated api/internal references instead of makeFunctionReference.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (ref?.source === 'convex/server' && ref.name === 'makeFunctionReference')\n\t\t\t\t\t\tcontext.report({ node, messageId: 'reference' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'domain-import-boundaries': {\n\t\tmeta: metadata('Keep api, application, domain, and shared dependencies directed.', {\n\t\t\tboundary:\n\t\t\t\t'Follow api → application/domain → shared; sibling modules expose only rules.ts and queries.ts.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst file = appFile(context)\n\t\t\tconst resolver = createResolver()\n\t\t\treturn sourceVisitors(context, (node, source) => {\n\t\t\t\tif (!file) return\n\t\t\t\tconst resolved = resolveImport(resolver, context.filename, source)\n\t\t\t\tif (!resolved) return\n\t\t\t\tconst target = appFile(context, resolved)?.replace(/\\.[cm]?[jt]s$/, '')\n\t\t\t\tif (!target) {\n\t\t\t\t\tif (!resolved.split(sep).includes('node_modules'))\n\t\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst from = file.split('/')\n\t\t\t\tconst to = target.split('/')\n\t\t\t\tlet forbidden = false\n\t\t\t\tif (from[0] === 'domain') {\n\t\t\t\t\tforbidden = ['api', 'application', 'migrations'].includes(to[0])\n\t\t\t\t\tif (\n\t\t\t\t\t\tto[0] === 'domain' &&\n\t\t\t\t\t\tfrom[1] !== to[1] &&\n\t\t\t\t\t\tto[1] !== 'shared' &&\n\t\t\t\t\t\tfrom[1] !== 'table.ts'\n\t\t\t\t\t) {\n\t\t\t\t\t\tforbidden =\n\t\t\t\t\t\t\tfrom[1] === 'shared' || !['rules', 'queries'].includes(to.slice(2).join('/'))\n\t\t\t\t\t}\n\t\t\t\t} else if (from[0] === 'api') {\n\t\t\t\t\tforbidden =\n\t\t\t\t\t\t['api', 'migrations'].includes(to[0]) ||\n\t\t\t\t\t\t(to[0] === 'domain' &&\n\t\t\t\t\t\t\tto[1] !== 'shared' &&\n\t\t\t\t\t\t\tto[1] !== (from.length > 2 ? from[1] : basename(file).replace(/\\.[cm]?[jt]s$/, '')))\n\t\t\t\t} else if (from[0] === 'application') forbidden = ['api', 'migrations'].includes(to[0])\n\t\t\t\tif (to[0] === 'migrations' && from[0] !== 'migrations') forbidden = true\n\t\t\t\tif (\n\t\t\t\t\t['api', 'domain', 'application'].includes(from[0]) &&\n\t\t\t\t\tto.length > 1 &&\n\t\t\t\t\t!['api', 'domain', 'application', '_generated', 'components', 'migrations'].includes(\n\t\t\t\t\t\tto[0],\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t\t\tforbidden = true\n\t\t\t\tif (forbidden) context.report({ node, messageId: 'boundary' })\n\t\t\t})\n\t\t},\n\t},\n\t'public-functions-in-api': {\n\t\tmeta: metadata('Expose authenticated public functions only through api/.', {\n\t\t\tpublic: 'Declare public auth*/role*/admin* functions in convex/api/<module>.ts.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (appFile(context)?.startsWith('api/')) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type !== 'CallExpression' &&\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\tisFunctionsImport(context, ref.source) &&\n\t\t\t\t\t\t/^(auth|role|admin)(Query|Mutation|Action)$/.test(ref.name)\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'public' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'thin-api-adapters': {\n\t\tmeta: metadata('Keep business decisions and writes in domain executors.', {\n\t\t\tadapter:\n\t\t\t\t'Move business branches, private helpers, and database writes into the owning domain executor.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!appFile(context)?.startsWith('api/')) return {}\n\t\t\treturn {\n\t\t\t\tIfStatement(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tSwitchStatement(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tConditionalExpression(node) {\n\t\t\t\t\tcontext.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exported = exportedNames(node)\n\t\t\t\t\tfor (const statement of node.body) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tstatement.type === 'FunctionDeclaration' &&\n\t\t\t\t\t\t\tstatement.id &&\n\t\t\t\t\t\t\t!exported.has(statement.id.name)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tcontext.report({ node: statement, messageId: 'adapter' })\n\t\t\t\t\t\tif (statement.type === 'VariableDeclaration')\n\t\t\t\t\t\t\tfor (const declaration of statement.declarations) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tdeclaration.id.type === 'Identifier' &&\n\t\t\t\t\t\t\t\t\t!exported.has(declaration.id.name) &&\n\t\t\t\t\t\t\t\t\t['ArrowFunctionExpression', 'FunctionExpression'].includes(\n\t\t\t\t\t\t\t\t\t\tdeclaration.init?.type ?? '',\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tcontext.report({ node: declaration, messageId: 'adapter' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (isDbWrite(node)) context.report({ node, messageId: 'adapter' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-inline-enums': {\n\t\tmeta: metadata('Keep vocabularies in named constants.ts tuples.', {\n\t\t\tvocabulary: 'Define a named readonly tuple in constants.ts and pass it to z.enum().',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tlet values = node.arguments[0]\n\t\t\t\t\twhile (values?.type === 'TSAsExpression' || values?.type === 'TSSatisfiesExpression')\n\t\t\t\t\t\tvalues = values.expression\n\t\t\t\t\tif (\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\t/^zod(?:\\/v4)?$/.test(ref.source) &&\n\t\t\t\t\t\tref.name === 'enum' &&\n\t\t\t\t\t\tvalues?.type === 'ArrayExpression'\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'vocabulary' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-unbounded-reads': {\n\t\tmeta: metadata('Bound collection reads through include or pagination.', {\n\t\t\tbounded:\n\t\t\t\t'Use include(...).execute(limit) or bounded pagination; do not collect() or resolve() an unbounded query.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (node.callee.type !== 'MemberExpression') return\n\t\t\t\t\tconst name = propertyName(node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\t(name === 'collect' && hasCall(node.callee.object, 'query')) ||\n\t\t\t\t\t\t(name === 'resolve' && hasCall(node.callee.object, 'include'))\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'bounded' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-manual-timestamps': {\n\t\tmeta: metadata('Let timestamps triggers own createdAt and updatedAt.', {\n\t\t\ttimestamp: 'Remove {{field}} from this write; register the timestamps trigger instead.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (!isDbWrite(node)) return\n\t\t\t\t\tconst data = node.arguments.at(-1)\n\t\t\t\t\tif (data?.type !== 'ObjectExpression') return\n\t\t\t\t\tfor (const property of data.properties) {\n\t\t\t\t\t\tif (property.type !== 'Property') continue\n\t\t\t\t\t\tconst field = property.computed ? staticString(property.key) : exportName(property.key)\n\t\t\t\t\t\tif (field === 'createdAt' || field === 'updatedAt')\n\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'timestamp', data: { field } })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'component-boundaries': {\n\t\tmeta: metadata('Keep portable components isolated and consume their client facades.', {\n\t\t\tboundary:\n\t\t\t\t'Components may import only their own files and npm dependencies; inject host policy through the client API.',\n\t\t\tfacade: 'Import a component through its client.ts facade, not its private implementation.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst owner = componentRoot(context, context.filename)\n\t\t\tconst resolver = createResolver()\n\n\t\t\treturn sourceVisitors(context, (node, specifier) => {\n\t\t\t\tif (!specifier.startsWith('.')) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t/^cvx-kit\\/components\\/[^/]+\\//.test(specifier) &&\n\t\t\t\t\t\t!/^cvx-kit\\/components\\/[^/]+\\/convex\\.config(?:\\.js)?$/.test(specifier)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'facade' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst target = resolveImport(resolver, context.filename, specifier)\n\t\t\t\tif (!target || target.split(sep).includes('node_modules')) return\n\t\t\t\tif (\n\t\t\t\t\towner &&\n\t\t\t\t\t(relative(owner, target).startsWith(`..${sep}`) || target === dirname(owner))\n\t\t\t\t) {\n\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\n\t\t\t\t} else if (\n\t\t\t\t\t!owner &&\n\t\t\t\t\tcomponentRoot(context, target) &&\n\t\t\t\t\t!/^client(?:\\.[cm]?[jt]s)?$/.test(basename(target)) &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tappFile(context) === 'convex.config.ts' &&\n\t\t\t\t\t\t/^convex\\.config(?:\\.[cm]?[jt]s)?$/.test(basename(target))\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tcontext.report({ node, messageId: 'facade' })\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t},\n\t'no-component-env': {\n\t\tmeta: metadata('Pass environment configuration into portable components.', {\n\t\t\tenv: 'Portable components must receive configuration through arguments; do not read process.env.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!componentRoot(context, context.filename)) return {}\n\t\t\tfunction isProcess(node: ESTree.Node) {\n\t\t\t\tif (node.type !== 'Identifier') return false\n\t\t\t\tconst variable = binding(context, node)\n\t\t\t\tif (node.name === 'process' && !variable?.defs.length) return true\n\t\t\t\treturn variable?.defs.some(\n\t\t\t\t\t(def) =>\n\t\t\t\t\t\tdef.type === 'ImportBinding' &&\n\t\t\t\t\t\tdef.parent?.type === 'ImportDeclaration' &&\n\t\t\t\t\t\t['node:process', 'process'].includes(def.parent.source.value) &&\n\t\t\t\t\t\t['ImportDefaultSpecifier', 'ImportNamespaceSpecifier'].includes(def.node.type),\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (['node:process', 'process'].includes(node.source.value)) {\n\t\t\t\t\t\tfor (const specifier of node.specifiers) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tspecifier.type === 'ImportSpecifier' &&\n\t\t\t\t\t\t\t\texportName(specifier.imported) === 'env'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'env' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tMemberExpression(node) {\n\t\t\t\t\tif (propertyName(node) !== 'env') return\n\t\t\t\t\tconst object = node.object\n\t\t\t\t\tconst globalProcess =\n\t\t\t\t\t\tobject.type === 'MemberExpression' &&\n\t\t\t\t\t\tpropertyName(object) === 'process' &&\n\t\t\t\t\t\tobject.object.type === 'Identifier' &&\n\t\t\t\t\t\tobject.object.name === 'globalThis' &&\n\t\t\t\t\t\t!binding(context, object.object)?.defs.length\n\t\t\t\t\tif (isProcess(object) || globalProcess) context.report({ node, messageId: 'env' })\n\t\t\t\t},\n\t\t\t\tVariableDeclarator(node) {\n\t\t\t\t\tif (node.init && isProcess(node.init) && node.id.type === 'ObjectPattern') {\n\t\t\t\t\t\tfor (const property of node.id.properties) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tproperty.type === 'Property' &&\n\t\t\t\t\t\t\t\t(property.computed ? staticString(property.key) : exportName(property.key)) ===\n\t\t\t\t\t\t\t\t\t'env'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'env' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'schema-file-boundaries': {\n\t\tmeta: metadata('Keep storage shapes and indexes in their documented owners.', {\n\t\t\tschema: '{{builder}} belongs in {{file}}.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tconst file = appFile(context)\n\t\t\t\t\tif (!file) return\n\t\t\t\t\tlet expected: string | null = null\n\t\t\t\t\tif (ref?.source === 'convex/server') {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tref.name === 'defineSchema' &&\n\t\t\t\t\t\t\tfile !== 'schema.ts' &&\n\t\t\t\t\t\t\t!(componentRoot(context, context.filename) && basename(file) === 'schema.ts')\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\texpected = 'the root schema.ts (or a component schema.ts)'\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tref.name === 'defineTable' &&\n\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\tcomponentRoot(context, context.filename) &&\n\t\t\t\t\t\t\t\t['schema.ts', 'table.ts'].includes(basename(file))\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\texpected =\n\t\t\t\t\t\t\t\t'a component schema.ts/table.ts; host tables use zodTable in domain/<module>/schema.ts'\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\t['cvx-kit', 'cvx-kit/zod-table'].includes(ref.source) &&\n\t\t\t\t\t\t['zodTable', 'tenantTable', 'zodVariantTable'].includes(ref.name) &&\n\t\t\t\t\t\t!/^domain\\/[^/]+\\/schema\\.ts$/.test(file)\n\t\t\t\t\t)\n\t\t\t\t\t\texpected = 'domain/<module>/schema.ts'\n\t\t\t\t\tif (expected)\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'schema',\n\t\t\t\t\t\t\tdata: { builder: ref?.name ?? 'table', file: expected },\n\t\t\t\t\t\t})\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression' &&\n\t\t\t\t\t\t['index', 'searchIndex', 'vectorIndex'].includes(propertyName(node.callee) ?? '') &&\n\t\t\t\t\t\tisTableChain(node.callee.object) &&\n\t\t\t\t\t\t!componentRoot(context, context.filename) &&\n\t\t\t\t\t\t!/^domain\\/[^/]+\\/table\\.ts$/.test(file)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'schema',\n\t\t\t\t\t\t\tdata: { builder: 'Table indexes', file: 'domain/<module>/table.ts' },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-internal-reexports': {\n\t\tmeta: metadata('Import from owning modules directly; consumer apps have no barrels.', {\n\t\t\treexport: 'Import from the owning file directly; do not re-export imports inside convex/.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tfunction check(node: ESTree.ExportNamedDeclaration | ESTree.ExportAllDeclaration) {\n\t\t\t\tif (node.source) {\n\t\t\t\t\tcontext.report({ node, messageId: 'reexport' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor (const specifier of node.type === 'ExportNamedDeclaration' ? node.specifiers : []) {\n\t\t\t\t\tif (binding(context, specifier.local)?.defs.some((def) => def.type === 'ImportBinding')) {\n\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'reexport' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tExportNamedDeclaration: check,\n\t\t\t\tExportAllDeclaration: check,\n\t\t\t\tExportDefaultDeclaration(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.declaration.type === 'Identifier' &&\n\t\t\t\t\t\tbinding(context, node.declaration)?.defs.some((def) => def.type === 'ImportBinding')\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'reexport' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'public-api-first': {\n\t\tmeta: metadata('Place private helper implementations after the exported API.', {\n\t\t\torder:\n\t\t\t\t'Move this private helper below the exported API so the file reads from public purpose to implementation.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exports = exportedNames(node)\n\t\t\t\t\tconst lastExport = node.body.findLastIndex((statement) =>\n\t\t\t\t\t\tstatement.type.startsWith('Export'),\n\t\t\t\t\t)\n\t\t\t\t\tfor (const statement of node.body.slice(0, lastExport < 0 ? 0 : lastExport)) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tstatement.type === 'FunctionDeclaration' &&\n\t\t\t\t\t\t\tstatement.id &&\n\t\t\t\t\t\t\t!exports.has(statement.id.name)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontext.report({ node: statement, messageId: 'order' })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'named-private-helpers': {\n\t\tmeta: metadata('Use named declarations for top-level private helper functions.', {\n\t\t\thelper: 'Declare this private helper with function name(...) at the bottom of the file.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exports = exportedNames(node)\n\t\t\t\t\tfor (const statement of node.body) {\n\t\t\t\t\t\tif (statement.type !== 'VariableDeclaration') continue\n\t\t\t\t\t\tfor (const declaration of statement.declarations) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tdeclaration.id.type === 'Identifier' &&\n\t\t\t\t\t\t\t\t!exports.has(declaration.id.name) &&\n\t\t\t\t\t\t\t\t(declaration.init?.type === 'ArrowFunctionExpression' ||\n\t\t\t\t\t\t\t\t\tdeclaration.init?.type === 'FunctionExpression')\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: declaration, messageId: 'helper' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n\nexport const rules: Record<RuleName, Rule> = scopeRules(applicationRules)\nconst plugin: Plugin = { meta: { name: 'cvx' }, rules }\nexport default plugin\n\nfunction scopeRules(source: Record<RuleName, CreateRule>): Record<RuleName, Rule> {\n\tconst scoped = { ...source }\n\t// SAFETY: source is the closed applicationRules record; its own keys are exactly RuleName.\n\tconst names = Object.keys(source) as RuleName[]\n\tfor (const name of names) {\n\t\tconst rule = source[name]\n\t\tscoped[name] = {\n\t\t\tmeta: {\n\t\t\t\t...rule.meta,\n\t\t\t\tschema: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'object',\n\t\t\t\t\t\tproperties: { convexDir: { type: 'string', minLength: 1 } },\n\t\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\tcreate(context) {\n\t\t\t\tconst file = appFile(context)\n\t\t\t\tif (\n\t\t\t\t\t!file ||\n\t\t\t\t\t/(?:^|\\/)(?:_generated|__tests__)(?:\\/|$)/.test(file) ||\n\t\t\t\t\t/\\.(?:test|spec)\\.[cm]?[jt]s$/.test(file)\n\t\t\t\t)\n\t\t\t\t\treturn {}\n\t\t\t\treturn rule.create(context)\n\t\t\t},\n\t\t}\n\t}\n\treturn scoped\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oxlint.mjs","names":["checkArchitecture","inspectArchitecture","options","rules","scopeRules","ownershipRules","file","appFile","context","narrowRootDeclaration","reservedDomainFiles","metadata","componentRoot","seen","check","ref","imported","node","resolver","createResolver","importedSource","target","resolveImport","source","specifier","name","exportName","sourceVisitors","resolved","sep","from","to","forbidden","basename","isFunctionsImport","exported","exportedNames","statement","declaration","isDbWrite","values","propertyName","origin","readProvenance","property","staticString","argument","fieldsNode","localInitializer","fields","field","data","owner","relative","dirname","isProcess","variable","binding","def","object","globalProcess","expected","isTableChain","exports","lastExport","plugin","scoped","names","rule"],"sources":["../src/oxlint.ts"],"sourcesContent":["import { basename, dirname, relative, sep } from 'node:path'\nimport type { CreateRule, ESTree, Plugin, Rule } from '@oxlint/plugins'\nimport {\n\tinspectArchitecture,\n\tcreateResolver,\n\tresolveImport,\n\tnarrowRootDeclaration,\n\ttype ArchitectureOptions,\n\ttype ArchitectureDiagnostic,\n} from './oxlint/architecture'\nimport { reservedDomainFiles } from './oxlint/reserved-files'\nimport { ownershipRules, type OwnershipRuleName } from './oxlint/ownership'\nimport {\n\tappFile,\n\timported,\n\tsourceVisitors,\n\tisFunctionsImport,\n\tisTableChain,\n\tisDbWrite,\n\tmetadata,\n\tcomponentRoot,\n\tstaticString,\n\tpropertyName,\n\tbinding,\n\treadProvenance,\n\tlocalInitializer,\n\texportedNames,\n\texportName,\n} from './oxlint/ast'\n\nexport type { ArchitectureOptions, ArchitectureDiagnostic } from './oxlint/architecture'\n\nexport function checkArchitecture(options: ArchitectureOptions = {}): ArchitectureDiagnostic[] {\n\treturn inspectArchitecture(options)\n}\n\nexport type RuleName =\n\t| OwnershipRuleName\n\t| 'component-boundaries'\n\t| 'no-component-env'\n\t| 'schema-file-boundaries'\n\t| 'no-internal-reexports'\n\t| 'public-api-first'\n\t| 'named-private-helpers'\n\t| 'no-raw-builders'\n\t| 'no-handwritten-references'\n\t| 'domain-import-boundaries'\n\t| 'public-functions-in-api'\n\t| 'thin-api-adapters'\n\t| 'no-inline-enums'\n\t| 'no-unbounded-reads'\n\t| 'no-manual-timestamps'\n\t| 'reserved-domain-file-anatomy'\n\t| 'root-facade-ownership'\n\t| 'no-manual-id-projection'\n\t| 'no-copied-pagination'\n\n/** Consumer application rules derived from docs/conventions.md. */\nconst applicationRules: Record<RuleName, CreateRule> = {\n\t...ownershipRules,\n\t'root-wiring-only': {\n\t\t...ownershipRules['root-wiring-only'],\n\t\tcreate(context) {\n\t\t\tconst file = appFile(context) ?? ''\n\t\t\tif (['relations.ts', 'querying.ts'].includes(file)) {\n\t\t\t\tif (narrowRootDeclaration(file, context.sourceCode.text)) return {}\n\t\t\t\treturn {\n\t\t\t\t\tProgram(node) {\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'ownership',\n\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\treason: `${file} must export one kit factory declaration and contain only declarative configuration`,\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\treturn ownershipRules['root-wiring-only'].create(context)\n\t\t},\n\t},\n\t'reserved-domain-file-anatomy': reservedDomainFiles,\n\t'root-facade-ownership': {\n\t\tmeta: metadata('Configure shared kit infrastructure once in its root facade.', {\n\t\t\tfacade: 'Configure {{name}} once in convex/{{file}}; import that facade elsewhere.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\tconst seen = new Set<string>()\n\t\t\tfunction check(node: ESTree.CallExpression | ESTree.NewExpression) {\n\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\tif (!ref) return\n\t\t\t\tlet file: string | null = null\n\t\t\t\tif (\n\t\t\t\t\t['cvx-kit', 'cvx-kit/querying'].includes(ref.source) &&\n\t\t\t\t\tref.name === 'createQueryEngine'\n\t\t\t\t)\n\t\t\t\t\tfile = 'querying.ts'\n\t\t\t\tif (['cvx-kit', 'cvx-kit/relations'].includes(ref.source) && ref.name === 'defineRelations')\n\t\t\t\t\tfile = 'relations.ts'\n\t\t\t\tif (['cvx-kit', 'cvx-kit/auth'].includes(ref.source) && ref.name === 'createAuthFunctions')\n\t\t\t\t\tfile = 'functions.ts'\n\t\t\t\tif (['cvx-kit', 'cvx-kit/triggers'].includes(ref.source) && ref.name === 'createTriggers')\n\t\t\t\t\tfile = 'triggers.ts'\n\t\t\t\tif (\n\t\t\t\t\t['cvx-kit', 'cvx-kit/components/foundation'].includes(ref.source) &&\n\t\t\t\t\tref.name === 'Foundation'\n\t\t\t\t)\n\t\t\t\t\tfile = 'foundation.ts'\n\t\t\t\tif (\n\t\t\t\t\t(ref.source === 'cvx-kit/effect' && ref.name === 'createEffectFoundation') ||\n\t\t\t\t\t(ref.source === 'cvx-kit' && ref.name === 'createFoundation')\n\t\t\t\t)\n\t\t\t\t\tfile = 'foundation.ts'\n\t\t\t\tif (!file) return\n\t\t\t\tif (appFile(context) !== file || seen.has(ref.name))\n\t\t\t\t\tcontext.report({ node, messageId: 'facade', data: { name: ref.name, file } })\n\t\t\t\tseen.add(ref.name)\n\t\t\t}\n\t\t\treturn { CallExpression: check, NewExpression: check }\n\t\t},\n\t},\n\t'no-raw-builders': {\n\t\tmeta: metadata('Build host functions through the auth-aware root constructors.', {\n\t\t\tbuilder:\n\t\t\t\t'Import raw builders only in convex/functions.ts; use auth*/role*/admin* or system* elsewhere.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (appFile(context) === 'functions.ts' || componentRoot(context, context.filename)) return {}\n\t\t\tconst resolver = createResolver()\n\t\t\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (node.importKind === 'type') return\n\t\t\t\t\tconst importedSource = node.source.value\n\t\t\t\t\tconst target = resolveImport(resolver, context.filename, importedSource)\n\t\t\t\t\tconst source =\n\t\t\t\t\t\ttarget && appFile(context, target)?.startsWith('_generated/')\n\t\t\t\t\t\t\t? appFile(context, target)!\n\t\t\t\t\t\t\t: importedSource\n\t\t\t\t\tif (\n\t\t\t\t\t\t!/(?:^|\\/)_generated\\/server(?:\\.[cm]?[jt]s)?$/.test(source) &&\n\t\t\t\t\t\tsource !== 'convex/server'\n\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\tfor (const specifier of node.specifiers) {\n\t\t\t\t\t\tif (specifier.type === 'ImportSpecifier' && specifier.importKind === 'type') continue\n\t\t\t\t\t\tconst name =\n\t\t\t\t\t\t\tspecifier.type === 'ImportSpecifier' ? exportName(specifier.imported) : null\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t(name === null && source !== 'convex/server') ||\n\t\t\t\t\t\t\t(name &&\n\t\t\t\t\t\t\t\t/^(?:internal)?(?:Query|Mutation|Action)(?:Generic)?$|^(?:query|mutation|action)(?:Generic)?$/.test(\n\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'builder' })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (\n\t\t\t\t\t\tref?.source === 'convex/server' &&\n\t\t\t\t\t\t/^(?:internal)?(?:Query|Mutation|Action)Generic$|^(?:query|mutation|action)Generic$/.test(\n\t\t\t\t\t\t\tref.name,\n\t\t\t\t\t\t) &&\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression'\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'builder' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-handwritten-references': {\n\t\tmeta: metadata('Use generated api/internal references.', {\n\t\t\treference: 'Use generated api/internal references instead of makeFunctionReference.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tif (ref?.source === 'convex/server' && ref.name === 'makeFunctionReference')\n\t\t\t\t\t\tcontext.report({ node, messageId: 'reference' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'domain-import-boundaries': {\n\t\tmeta: metadata('Keep api, application, domain, and shared dependencies directed.', {\n\t\t\tboundary:\n\t\t\t\t'Follow api → application/domain → shared; sibling modules expose only rules.ts and queries.ts.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst file = appFile(context)\n\t\t\tconst resolver = createResolver()\n\t\t\treturn sourceVisitors(context, (node, source) => {\n\t\t\t\tif (!file) return\n\t\t\t\tconst resolved = resolveImport(resolver, context.filename, source)\n\t\t\t\tif (!resolved) return\n\t\t\t\tconst target = appFile(context, resolved)?.replace(/\\.[cm]?[jt]s$/, '')\n\t\t\t\tif (!target) {\n\t\t\t\t\tif (!resolved.split(sep).includes('node_modules'))\n\t\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst from = file.split('/')\n\t\t\t\tconst to = target.split('/')\n\t\t\t\tlet forbidden = false\n\t\t\t\tif (from[0] === 'domain') {\n\t\t\t\t\tforbidden = ['api', 'application', 'migrations'].includes(to[0])\n\t\t\t\t\tif (\n\t\t\t\t\t\tto[0] === 'domain' &&\n\t\t\t\t\t\tfrom[1] !== to[1] &&\n\t\t\t\t\t\tto[1] !== 'shared' &&\n\t\t\t\t\t\tfrom[1] !== 'table.ts'\n\t\t\t\t\t) {\n\t\t\t\t\t\tforbidden =\n\t\t\t\t\t\t\tfrom[1] === 'shared' ||\n\t\t\t\t\t\t\t!['rules', 'queries', 'contracts'].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\telse if (['relations.ts', 'querying.ts'].includes(file) && to[0] === 'domain') {\n\t\t\t\t\tconst allowed = file === 'relations.ts' ? ['schema', 'table'] : ['queries', 'contracts']\n\t\t\t\t\tforbidden =\n\t\t\t\t\t\t!allowed.includes(to.slice(2).join('/')) &&\n\t\t\t\t\t\t!(file === 'relations.ts' && target === 'domain/table')\n\t\t\t\t}\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\torder:\n\t\t\t\t'Apply eligibility filters in the indexed query before take(), execute(), or paginate(); filtering a bounded result can omit eligible rows. Do not move the predicate automatically.',\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\tconst origin = readProvenance(context, node.callee.object)\n\t\t\t\t\tif (name === 'filter' && origin === 'bounded')\n\t\t\t\t\t\tcontext.report({ node, messageId: 'order' })\n\t\t\t\t\tif (\n\t\t\t\t\t\t(name === 'collect' && (origin === 'stream' || origin === 'relationship')) ||\n\t\t\t\t\t\t(name === 'resolve' && origin === 'stream')\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-id-projection': {\n\t\tmeta: metadata('Use the table DTO boundary to project public IDs.', {\n\t\t\tprojection:\n\t\t\t\t'Use toPublicDto(row, true) to include _id; do not add IDs manually after a table DTO parse or projection.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tObjectExpression(node) {\n\t\t\t\t\tconst dto = node.properties.some(\n\t\t\t\t\t\t(property) =>\n\t\t\t\t\t\t\tproperty.type === 'SpreadElement' &&\n\t\t\t\t\t\t\treadProvenance(context, property.argument) === 'dto',\n\t\t\t\t\t)\n\t\t\t\t\tif (!dto) return\n\t\t\t\t\tfor (const property of node.properties) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tproperty.type === 'Property' &&\n\t\t\t\t\t\t\t['id', '_id'].includes(\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)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'projection' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tAssignmentExpression(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.left.type === 'MemberExpression' &&\n\t\t\t\t\t\t['id', '_id'].includes(propertyName(node.left) ?? '') &&\n\t\t\t\t\t\treadProvenance(context, node.left.object) === 'dto'\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'projection' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-copied-pagination': {\n\t\tmeta: metadata('Use native or table pagination schemas instead of copying their shape.', {\n\t\t\tpagination:\n\t\t\t\t'Use the native paginationOptsValidator (or paginated(table.dto())) for native pagination, or table.paginationOpts(selection) for table queries; do not copy pagination fields.',\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?.name === 'object' && ['zod', 'zod/v4', 'convex/values'].includes(ref.source)))\n\t\t\t\t\t\treturn\n\t\t\t\t\tconst argument = node.arguments[0]\n\t\t\t\t\tconst fieldsNode = argument && (localInitializer(context, argument) ?? argument)\n\t\t\t\t\tif (fieldsNode?.type !== 'ObjectExpression') return\n\t\t\t\t\tconst fields = new Set(\n\t\t\t\t\t\tfieldsNode.properties.flatMap((property) =>\n\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: [],\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\tif (\n\t\t\t\t\t\t(fields.has('numItems') && fields.has('cursor')) ||\n\t\t\t\t\t\t['page', 'isDone', 'continueCursor'].every((field) => fields.has(field))\n\t\t\t\t\t)\n\t\t\t\t\t\tcontext.report({ node, messageId: 'pagination' })\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-manual-timestamps': {\n\t\tmeta: metadata('Let timestamps triggers own createdAt and updatedAt.', {\n\t\t\ttimestamp: 'Remove {{field}} from this write; register the timestamps trigger instead.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (componentRoot(context, context.filename)) return {}\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tif (!isDbWrite(node)) return\n\t\t\t\t\tconst data = node.arguments.at(-1)\n\t\t\t\t\tif (data?.type !== 'ObjectExpression') return\n\t\t\t\t\tfor (const property of data.properties) {\n\t\t\t\t\t\tif (property.type !== 'Property') continue\n\t\t\t\t\t\tconst field = property.computed ? staticString(property.key) : exportName(property.key)\n\t\t\t\t\t\tif (field === 'createdAt' || field === 'updatedAt')\n\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'timestamp', data: { field } })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'component-boundaries': {\n\t\tmeta: metadata('Keep portable components isolated and consume their client facades.', {\n\t\t\tboundary:\n\t\t\t\t'Components may import only their own files and npm dependencies; inject host policy through the client API.',\n\t\t\tfacade: 'Import a component through its client.ts facade, not its private implementation.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tconst owner = componentRoot(context, context.filename)\n\t\t\tconst resolver = createResolver()\n\n\t\t\treturn sourceVisitors(context, (node, specifier) => {\n\t\t\t\tif (!specifier.startsWith('.')) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t/^cvx-kit\\/components\\/[^/]+\\//.test(specifier) &&\n\t\t\t\t\t\t!/^cvx-kit\\/components\\/[^/]+\\/convex\\.config(?:\\.js)?$/.test(specifier)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'facade' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst target = resolveImport(resolver, context.filename, specifier)\n\t\t\t\tif (!target || target.split(sep).includes('node_modules')) return\n\t\t\t\tif (\n\t\t\t\t\towner &&\n\t\t\t\t\t(relative(owner, target).startsWith(`..${sep}`) || target === dirname(owner))\n\t\t\t\t) {\n\t\t\t\t\tcontext.report({ node, messageId: 'boundary' })\n\t\t\t\t} else if (\n\t\t\t\t\t!owner &&\n\t\t\t\t\tcomponentRoot(context, target) &&\n\t\t\t\t\t!/^client(?:\\.[cm]?[jt]s)?$/.test(basename(target)) &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tappFile(context) === 'convex.config.ts' &&\n\t\t\t\t\t\t/^convex\\.config(?:\\.[cm]?[jt]s)?$/.test(basename(target))\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tcontext.report({ node, messageId: 'facade' })\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t},\n\t'no-component-env': {\n\t\tmeta: metadata('Pass environment configuration into portable components.', {\n\t\t\tenv: 'Portable components must receive configuration through arguments; do not read process.env.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tif (!componentRoot(context, context.filename)) return {}\n\t\t\tfunction isProcess(node: ESTree.Node) {\n\t\t\t\tif (node.type !== 'Identifier') return false\n\t\t\t\tconst variable = binding(context, node)\n\t\t\t\tif (node.name === 'process' && !variable?.defs.length) return true\n\t\t\t\treturn variable?.defs.some(\n\t\t\t\t\t(def) =>\n\t\t\t\t\t\tdef.type === 'ImportBinding' &&\n\t\t\t\t\t\tdef.parent?.type === 'ImportDeclaration' &&\n\t\t\t\t\t\t['node:process', 'process'].includes(def.parent.source.value) &&\n\t\t\t\t\t\t['ImportDefaultSpecifier', 'ImportNamespaceSpecifier'].includes(def.node.type),\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tImportDeclaration(node) {\n\t\t\t\t\tif (['node:process', 'process'].includes(node.source.value)) {\n\t\t\t\t\t\tfor (const specifier of node.specifiers) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tspecifier.type === 'ImportSpecifier' &&\n\t\t\t\t\t\t\t\texportName(specifier.imported) === 'env'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'env' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tMemberExpression(node) {\n\t\t\t\t\tif (propertyName(node) !== 'env') return\n\t\t\t\t\tconst object = node.object\n\t\t\t\t\tconst globalProcess =\n\t\t\t\t\t\tobject.type === 'MemberExpression' &&\n\t\t\t\t\t\tpropertyName(object) === 'process' &&\n\t\t\t\t\t\tobject.object.type === 'Identifier' &&\n\t\t\t\t\t\tobject.object.name === 'globalThis' &&\n\t\t\t\t\t\t!binding(context, object.object)?.defs.length\n\t\t\t\t\tif (isProcess(object) || globalProcess) context.report({ node, messageId: 'env' })\n\t\t\t\t},\n\t\t\t\tVariableDeclarator(node) {\n\t\t\t\t\tif (node.init && isProcess(node.init) && node.id.type === 'ObjectPattern') {\n\t\t\t\t\t\tfor (const property of node.id.properties) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tproperty.type === 'Property' &&\n\t\t\t\t\t\t\t\t(property.computed ? staticString(property.key) : exportName(property.key)) ===\n\t\t\t\t\t\t\t\t\t'env'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: property, messageId: 'env' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'schema-file-boundaries': {\n\t\tmeta: metadata('Keep storage shapes and indexes in their documented owners.', {\n\t\t\tschema: '{{builder}} belongs in {{file}}.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tCallExpression(node) {\n\t\t\t\t\tconst ref = imported(context, node.callee)\n\t\t\t\t\tconst file = appFile(context)\n\t\t\t\t\tif (!file) return\n\t\t\t\t\tlet expected: string | null = null\n\t\t\t\t\tif (ref?.source === 'convex/server') {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tref.name === 'defineSchema' &&\n\t\t\t\t\t\t\tfile !== 'schema.ts' &&\n\t\t\t\t\t\t\t!(componentRoot(context, context.filename) && basename(file) === 'schema.ts')\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\texpected = 'the root schema.ts (or a component schema.ts)'\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tref.name === 'defineTable' &&\n\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\tcomponentRoot(context, context.filename) &&\n\t\t\t\t\t\t\t\t['schema.ts', 'table.ts'].includes(basename(file))\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\texpected =\n\t\t\t\t\t\t\t\t'a component schema.ts/table.ts; host tables use zodTable in domain/<module>/schema.ts'\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\tref &&\n\t\t\t\t\t\t['cvx-kit', 'cvx-kit/zod-table'].includes(ref.source) &&\n\t\t\t\t\t\t['zodTable', 'tenantTable', 'zodVariantTable'].includes(ref.name) &&\n\t\t\t\t\t\t!/^domain\\/[^/]+\\/schema\\.ts$/.test(file)\n\t\t\t\t\t)\n\t\t\t\t\t\texpected = 'domain/<module>/schema.ts'\n\t\t\t\t\tif (expected)\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'schema',\n\t\t\t\t\t\t\tdata: { builder: ref?.name ?? 'table', file: expected },\n\t\t\t\t\t\t})\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.callee.type === 'MemberExpression' &&\n\t\t\t\t\t\t['index', 'searchIndex', 'vectorIndex'].includes(propertyName(node.callee) ?? '') &&\n\t\t\t\t\t\tisTableChain(node.callee.object) &&\n\t\t\t\t\t\t!componentRoot(context, context.filename) &&\n\t\t\t\t\t\t!/^domain\\/[^/]+\\/table\\.ts$/.test(file)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tmessageId: 'schema',\n\t\t\t\t\t\t\tdata: { builder: 'Table indexes', file: 'domain/<module>/table.ts' },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'no-internal-reexports': {\n\t\tmeta: metadata('Import from owning modules directly; consumer apps have no barrels.', {\n\t\t\treexport: 'Import from the owning file directly; do not re-export imports inside convex/.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\tfunction check(node: ESTree.ExportNamedDeclaration | ESTree.ExportAllDeclaration) {\n\t\t\t\tif (node.source) {\n\t\t\t\t\tcontext.report({ node, messageId: 'reexport' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor (const specifier of node.type === 'ExportNamedDeclaration' ? node.specifiers : []) {\n\t\t\t\t\tif (binding(context, specifier.local)?.defs.some((def) => def.type === 'ImportBinding')) {\n\t\t\t\t\t\tcontext.report({ node: specifier, messageId: 'reexport' })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tExportNamedDeclaration: check,\n\t\t\t\tExportAllDeclaration: check,\n\t\t\t\tExportDefaultDeclaration(node) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tnode.declaration.type === 'Identifier' &&\n\t\t\t\t\t\tbinding(context, node.declaration)?.defs.some((def) => def.type === 'ImportBinding')\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontext.report({ node, messageId: 'reexport' })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'public-api-first': {\n\t\tmeta: metadata('Place private helper implementations after the exported API.', {\n\t\t\torder:\n\t\t\t\t'Move this private helper below the exported API so the file reads from public purpose to implementation.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exports = exportedNames(node)\n\t\t\t\t\tconst lastExport = node.body.findLastIndex((statement) =>\n\t\t\t\t\t\tstatement.type.startsWith('Export'),\n\t\t\t\t\t)\n\t\t\t\t\tfor (const statement of node.body.slice(0, lastExport < 0 ? 0 : lastExport)) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tstatement.type === 'FunctionDeclaration' &&\n\t\t\t\t\t\t\tstatement.id &&\n\t\t\t\t\t\t\t!exports.has(statement.id.name)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontext.report({ node: statement, messageId: 'order' })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n\t'named-private-helpers': {\n\t\tmeta: metadata('Use named declarations for top-level private helper functions.', {\n\t\t\thelper: 'Declare this private helper with function name(...) at the bottom of the file.',\n\t\t}),\n\t\tcreate(context) {\n\t\t\treturn {\n\t\t\t\tProgram(node) {\n\t\t\t\t\tconst exports = exportedNames(node)\n\t\t\t\t\tfor (const statement of node.body) {\n\t\t\t\t\t\tif (statement.type !== 'VariableDeclaration') continue\n\t\t\t\t\t\tfor (const declaration of statement.declarations) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tdeclaration.id.type === 'Identifier' &&\n\t\t\t\t\t\t\t\t!exports.has(declaration.id.name) &&\n\t\t\t\t\t\t\t\t(declaration.init?.type === 'ArrowFunctionExpression' ||\n\t\t\t\t\t\t\t\t\tdeclaration.init?.type === 'FunctionExpression')\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontext.report({ node: declaration, messageId: 'helper' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t},\n}\n\nexport const rules: Record<RuleName, Rule> = scopeRules(applicationRules)\nconst plugin: Plugin = { meta: { name: 'cvx' }, rules }\nexport default plugin\n\nfunction scopeRules(source: Record<RuleName, CreateRule>): Record<RuleName, Rule> {\n\tconst scoped = { ...source }\n\t// SAFETY: source is the closed applicationRules record; its own keys are exactly RuleName.\n\tconst names = Object.keys(source) as RuleName[]\n\tfor (const name of names) {\n\t\tconst rule = source[name]\n\t\tscoped[name] = {\n\t\t\tmeta: {\n\t\t\t\t...rule.meta,\n\t\t\t\tschema: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'object',\n\t\t\t\t\t\tproperties: { convexDir: { type: 'string', minLength: 1 } },\n\t\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\tcreate(context) {\n\t\t\t\tconst file = appFile(context)\n\t\t\t\tif (\n\t\t\t\t\t!file ||\n\t\t\t\t\t/(?:^|\\/)(?:_generated|__tests__)(?:\\/|$)/.test(file) ||\n\t\t\t\t\t/\\.(?:test|spec)\\.[cm]?[jt]s$/.test(file)\n\t\t\t\t)\n\t\t\t\t\treturn {}\n\t\t\t\treturn rule.create(context)\n\t\t\t},\n\t\t}\n\t}\n\treturn scoped\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Value } from "convex/values";
|
|
2
|
+
//#region src/querying/cursors.d.ts
|
|
3
|
+
/** Private engine contract. Bindings must come from trusted normalized engine state, never query args. */
|
|
4
|
+
export interface CursorBinding {
|
|
5
|
+
readonly table: string;
|
|
6
|
+
readonly index?: string | null;
|
|
7
|
+
readonly order: 'asc' | 'desc';
|
|
8
|
+
readonly clientFilters: Value;
|
|
9
|
+
readonly serverFilters: Value;
|
|
10
|
+
readonly relationPath: readonly string[];
|
|
11
|
+
readonly parent?: string | null;
|
|
12
|
+
readonly principal: string;
|
|
13
|
+
readonly tenant?: string | null;
|
|
14
|
+
readonly policyVersion: string;
|
|
15
|
+
readonly engineVersion: string;
|
|
16
|
+
readonly registryVersion: string;
|
|
17
|
+
/** Deliberately excluded from authenticated identity. */
|
|
18
|
+
readonly pageSize?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface CursorCodecConfig {
|
|
21
|
+
readonly version: string;
|
|
22
|
+
readonly currentKeyId: string;
|
|
23
|
+
readonly keys: Readonly<Record<string, Uint8Array>>;
|
|
24
|
+
}
|
|
25
|
+
export declare class CursorError extends Error {
|
|
26
|
+
readonly _tag = "CursorError";
|
|
27
|
+
readonly code = "invalid-cursor";
|
|
28
|
+
constructor();
|
|
29
|
+
}
|
|
30
|
+
export declare class CursorConfigurationError extends Error {
|
|
31
|
+
readonly _tag = "CursorConfigurationError";
|
|
32
|
+
constructor();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Deterministic RFC 5297 AES-SIV: equal plaintext and binding leak equality.
|
|
36
|
+
* One navigation-boundary family permits native split/continue tokens in start/end
|
|
37
|
+
* slots. Relation identity remains in the binding. No nonce/randomness or plaintext fallback. Opening is not authorization: the engine
|
|
38
|
+
* must recheck current policy on every future request, including nested continuations.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createCursorCodec(config: CursorCodecConfig): Readonly<{
|
|
41
|
+
seal(raw: string, binding: CursorBinding): string;
|
|
42
|
+
open(token: string, binding: CursorBinding): string;
|
|
43
|
+
}>;
|
|
44
|
+
//#endregion
|
|
45
|
+
//# sourceMappingURL=cursors.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{canonicalConvexBytes as e}from"../modules/contracts/idempotency.mjs";import{aessiv as t}from"@noble/ciphers/aes.js";import{bytesToHex as n,hexToBytes as r}from"@noble/ciphers/utils.js";var i=class extends Error{_tag=`CursorError`;code=`invalid-cursor`;constructor(){super(`Invalid cursor`),this.name=`CursorError`}},a=class extends Error{_tag=`CursorConfigurationError`;constructor(){super(`Invalid cursor server configuration`),this.name=`CursorConfigurationError`}};const o=Object.freeze({maxDepth:24,maxNodes:2048,maxBytes:16384,maxArrayLength:512,maxObjectFields:128}),s=8192,c=new TextEncoder,l=new TextDecoder(`utf-8`,{fatal:!0}),u=/^[A-Za-z0-9_-]{1,32}$/;function d(e){let o,d,f=/* @__PURE__ */ new Map;try{if(o=e.version,d=e.currentKeyId,typeof o!=`string`||typeof d!=`string`||!u.test(o)||!u.test(d))throw new a;let t=Object.entries(e.keys);if(!t.length)throw new a;for(let[e,n]of t){if(!u.test(e)||!(n instanceof Uint8Array)||n.byteLength!==64)throw new a;f.set(e,Uint8Array.from(n))}if(!f.has(d))throw new a}catch{throw new a}return Object.freeze({seal(e,r){try{if(typeof e!=`string`||e.length>s||c.encode(e).length>s)throw new i;let a=c.encode(JSON.stringify(e)),l=t(f.get(d),p(o,d,r));return`pc.${o}.${d}.${n(l.encrypt(a))}`}catch{throw new i}},open(e,n){try{if(typeof e!=`string`||e.length>98410)throw new i;let a=e.split(`.`);if(a.length!==4||a[0]!==`pc`||a[1]!==o||!u.test(a[2]))throw new i;let d=a[2],m=a[3],h=f.get(d);if(!h||m.length<36||m.length>98340||m.length%2||!/^[0-9a-f]+$/.test(m))throw new i;let g=t(h,p(o,d,n)).decrypt(r(m));if(g.length>49154)throw new i;let _=l.decode(g),v=JSON.parse(_);if(typeof v!=`string`||v.length>s||c.encode(v).length>s||JSON.stringify(v)!==_)throw new i;return v}catch{throw new i}}})}function f(e){let t=/* @__PURE__ */ new Set,n=0;function r(e,a){if(++n>o.maxNodes||a>o.maxDepth)throw new i;if(typeof e!=`object`||!e||e instanceof ArrayBuffer)return;if(t.has(e))throw new i;let s=Array.isArray(e);if(s&&Object.getPrototypeOf(e)!==Array.prototype||!s&&Object.getPrototypeOf(e)!==Object.prototype&&Object.getPrototypeOf(e)!==null||Object.getOwnPropertySymbols(e).length)throw new i;let c=Object.getOwnPropertyNames(e);if(c.length>(s?o.maxArrayLength+1:o.maxObjectFields))throw new i;t.add(e);try{for(let t of c){let n=Object.getOwnPropertyDescriptor(e,t);if(!(`value`in n))throw new i;if(s&&t===`length`){if(n.value>o.maxArrayLength)throw new i;continue}if(!n.enumerable||s&&(!/^(0|[1-9][0-9]*)$/.test(t)||Number(t)>=e.length))throw new i;r(n.value,a+1)}}finally{t.delete(e)}}r(e,0)}function p(t,n,r){if(f(r),r.clientFilters===void 0||r.serverFilters===void 0)throw new i;let{table:a,order:s,principal:l,policyVersion:u,engineVersion:d,registryVersion:p}=r;for(let e of[a,l,u,d,p])if(typeof e!=`string`||!e.length)throw new i;if(s!==`asc`&&s!==`desc`)throw new i;for(let e of[r.index,r.parent,r.tenant])if(e!=null&&typeof e!=`string`)throw new i;if(!Array.isArray(r.relationPath)||r.relationPath.some(e=>typeof e!=`string`))throw new i;return c.encode(e({domain:`cvx-query-cursor`,version:t,keyId:n,table:a,index:r.index??null,order:s,clientFilters:r.clientFilters,serverFilters:r.serverFilters,relationPath:r.relationPath,parent:r.parent??null,principal:l,tenant:r.tenant??null,policyVersion:u,engineVersion:d,registryVersion:p},o))}export{a as CursorConfigurationError,i as CursorError,d as createCursorCodec};
|
|
2
|
+
//# sourceMappingURL=cursors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursors.mjs","names":["CursorError","CursorConfigurationError","bounds","maxPlaintextBytes","encoder","decoder","identifier","createCursorCodec","version","currentKeyId","keys","config","entries","id","key","raw","payload","cipher","aessiv","aad","binding","bytesToHex","token","parts","keyId","encoded","plaintext","hexToBytes","json","checkDescriptors","ancestors","nodes","visit","depth","current","array","names","name","descriptor","value","field","table","principal","policyVersion","engineVersion","registryVersion","order","part","canonicalConvexBytes"],"sources":["../../src/querying/cursors.ts"],"sourcesContent":["/* oxlint-disable anti-slop/no-runtime-typeof, anti-slop/no-unknown-parameters -- Cursor transport and canonical values are explicit validation boundaries. */\nimport { aessiv } from '@noble/ciphers/aes.js'\nimport { bytesToHex, hexToBytes } from '@noble/ciphers/utils.js'\nimport type { Value } from 'convex/values'\nimport { canonicalConvexBytes } from '../modules/contracts/idempotency'\n\n/** Private engine contract. Bindings must come from trusted normalized engine state, never query args. */\nexport interface CursorBinding {\n\treadonly table: string\n\treadonly index?: string | null\n\treadonly order: 'asc' | 'desc'\n\treadonly clientFilters: Value\n\treadonly serverFilters: Value\n\treadonly relationPath: readonly string[]\n\treadonly parent?: string | null\n\treadonly principal: string\n\treadonly tenant?: string | null\n\treadonly policyVersion: string\n\treadonly engineVersion: string\n\treadonly registryVersion: string\n\t/** Deliberately excluded from authenticated identity. */\n\treadonly pageSize?: number\n}\nexport interface CursorCodecConfig {\n\treadonly version: string\n\treadonly currentKeyId: string\n\treadonly keys: Readonly<Record<string, Uint8Array>>\n}\nexport class CursorError extends Error {\n\treadonly _tag = 'CursorError'\n\treadonly code = 'invalid-cursor'\n\tconstructor() {\n\t\tsuper('Invalid cursor')\n\t\tthis.name = 'CursorError'\n\t}\n}\nexport class CursorConfigurationError extends Error {\n\treadonly _tag = 'CursorConfigurationError'\n\tconstructor() {\n\t\tsuper('Invalid cursor server configuration')\n\t\tthis.name = 'CursorConfigurationError'\n\t}\n}\n\nconst bounds = Object.freeze({\n\tmaxDepth: 24,\n\tmaxNodes: 2048,\n\tmaxBytes: 16384,\n\tmaxArrayLength: 512,\n\tmaxObjectFields: 128,\n})\nconst maxPlaintextBytes = 8192\n// JSON string encoding preserves even lone UTF-16 surrogates without lossy UTF-8 replacement.\nconst maxPayloadBytes = maxPlaintextBytes * 6 + 2\nconst maxCiphertextBytes = maxPayloadBytes + 16\nconst maxTokenLength = maxCiphertextBytes * 2 + 70\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder('utf-8', { fatal: true })\nconst identifier = /^[A-Za-z0-9_-]{1,32}$/\n\n/**\n * Deterministic RFC 5297 AES-SIV: equal plaintext and binding leak equality.\n * One navigation-boundary family permits native split/continue tokens in start/end\n * slots. Relation identity remains in the binding. No nonce/randomness or plaintext fallback. Opening is not authorization: the engine\n * must recheck current policy on every future request, including nested continuations.\n */\nexport function createCursorCodec(config: CursorCodecConfig) {\n\tlet version: string, currentKeyId: string\n\tconst keys = new Map<string, Uint8Array>()\n\ttry {\n\t\tversion = config.version\n\t\tcurrentKeyId = config.currentKeyId\n\t\tif (\n\t\t\ttypeof version !== 'string' ||\n\t\t\ttypeof currentKeyId !== 'string' ||\n\t\t\t!identifier.test(version) ||\n\t\t\t!identifier.test(currentKeyId)\n\t\t)\n\t\t\tthrow new CursorConfigurationError()\n\t\tconst entries = Object.entries(config.keys)\n\t\tif (!entries.length) throw new CursorConfigurationError()\n\t\tfor (const [id, key] of entries) {\n\t\t\tif (!identifier.test(id) || !(key instanceof Uint8Array) || key.byteLength !== 64)\n\t\t\t\tthrow new CursorConfigurationError()\n\t\t\tkeys.set(id, Uint8Array.from(key))\n\t\t}\n\t\tif (!keys.has(currentKeyId)) throw new CursorConfigurationError()\n\t} catch {\n\t\tthrow new CursorConfigurationError()\n\t}\n\treturn Object.freeze({\n\t\tseal(raw: string, binding: CursorBinding): string {\n\t\t\ttry {\n\t\t\t\tif (\n\t\t\t\t\ttypeof raw !== 'string' ||\n\t\t\t\t\traw.length > maxPlaintextBytes ||\n\t\t\t\t\tencoder.encode(raw).length > maxPlaintextBytes\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst payload = encoder.encode(JSON.stringify(raw))\n\t\t\t\tconst cipher = aessiv(keys.get(currentKeyId)!, aad(version, currentKeyId, binding))\n\t\t\t\treturn `pc.${version}.${currentKeyId}.${bytesToHex(cipher.encrypt(payload))}`\n\t\t\t} catch {\n\t\t\t\tthrow new CursorError()\n\t\t\t}\n\t\t},\n\t\topen(token: string, binding: CursorBinding): string {\n\t\t\ttry {\n\t\t\t\tif (typeof token !== 'string' || token.length > maxTokenLength) throw new CursorError()\n\t\t\t\tconst parts = token.split('.')\n\t\t\t\tif (\n\t\t\t\t\tparts.length !== 4 ||\n\t\t\t\t\tparts[0] !== 'pc' ||\n\t\t\t\t\tparts[1] !== version ||\n\t\t\t\t\t!identifier.test(parts[2]!)\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst keyId = parts[2]!,\n\t\t\t\t\tencoded = parts[3]!,\n\t\t\t\t\tkey = keys.get(keyId)\n\t\t\t\tif (\n\t\t\t\t\t!key ||\n\t\t\t\t\tencoded.length < 36 ||\n\t\t\t\t\tencoded.length > maxCiphertextBytes * 2 ||\n\t\t\t\t\tencoded.length % 2 ||\n\t\t\t\t\t!/^[0-9a-f]+$/.test(encoded)\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst cipher = aessiv(key, aad(version, keyId, binding))\n\t\t\t\tconst plaintext = cipher.decrypt(hexToBytes(encoded))\n\t\t\t\tif (plaintext.length > maxPayloadBytes) throw new CursorError()\n\t\t\t\tconst json = decoder.decode(plaintext)\n\t\t\t\tconst raw: unknown = JSON.parse(json)\n\t\t\t\tif (\n\t\t\t\t\ttypeof raw !== 'string' ||\n\t\t\t\t\traw.length > maxPlaintextBytes ||\n\t\t\t\t\tencoder.encode(raw).length > maxPlaintextBytes ||\n\t\t\t\t\tJSON.stringify(raw) !== json\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\treturn raw\n\t\t\t} catch {\n\t\t\t\tthrow new CursorError()\n\t\t\t}\n\t\t},\n\t})\n}\n\n/** Reject accessor-bearing arrays/dictionaries before the shared canonical encoder reads values. */\nfunction checkDescriptors(value: unknown): void {\n\tconst ancestors = new Set<object>()\n\tlet nodes = 0\n\tfunction visit(current: unknown, depth: number): void {\n\t\tif (++nodes > bounds.maxNodes || depth > bounds.maxDepth) throw new CursorError()\n\t\tif (current === null || typeof current !== 'object' || current instanceof ArrayBuffer) return\n\t\tif (ancestors.has(current)) throw new CursorError()\n\t\tconst array = Array.isArray(current)\n\t\tif (array && Object.getPrototypeOf(current) !== Array.prototype) throw new CursorError()\n\t\tif (\n\t\t\t!array &&\n\t\t\tObject.getPrototypeOf(current) !== Object.prototype &&\n\t\t\tObject.getPrototypeOf(current) !== null\n\t\t)\n\t\t\tthrow new CursorError()\n\t\tif (Object.getOwnPropertySymbols(current).length) throw new CursorError()\n\t\tconst names = Object.getOwnPropertyNames(current)\n\t\tif (names.length > (array ? bounds.maxArrayLength + 1 : bounds.maxObjectFields))\n\t\t\tthrow new CursorError()\n\t\tancestors.add(current)\n\t\ttry {\n\t\t\tfor (const name of names) {\n\t\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(current, name)!\n\t\t\t\tif (!('value' in descriptor)) throw new CursorError()\n\t\t\t\tif (array && name === 'length') {\n\t\t\t\t\tif (descriptor.value > bounds.maxArrayLength) throw new CursorError()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\t!descriptor.enumerable ||\n\t\t\t\t\t(array && (!/^(0|[1-9][0-9]*)$/.test(name) || Number(name) >= current.length))\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tvisit(descriptor.value, depth + 1)\n\t\t\t}\n\t\t} finally {\n\t\t\tancestors.delete(current)\n\t\t}\n\t}\n\tvisit(value, 0)\n}\nfunction aad(version: string, keyId: string, binding: CursorBinding): Uint8Array {\n\tcheckDescriptors(binding)\n\tif (binding.clientFilters === undefined || binding.serverFilters === undefined)\n\t\tthrow new CursorError()\n\tconst { table, order, principal, policyVersion, engineVersion, registryVersion } = binding\n\tfor (const field of [table, principal, policyVersion, engineVersion, registryVersion])\n\t\tif (typeof field !== 'string' || !field.length) throw new CursorError()\n\tif (order !== 'asc' && order !== 'desc') throw new CursorError()\n\tfor (const field of [binding.index, binding.parent, binding.tenant])\n\t\tif (field != null && typeof field !== 'string') throw new CursorError()\n\tif (\n\t\t!Array.isArray(binding.relationPath) ||\n\t\tbinding.relationPath.some((part) => typeof part !== 'string')\n\t)\n\t\tthrow new CursorError()\n\treturn encoder.encode(\n\t\tcanonicalConvexBytes(\n\t\t\t{\n\t\t\t\tdomain: 'cvx-query-cursor',\n\t\t\t\tversion,\n\t\t\t\tkeyId,\n\t\t\t\ttable,\n\t\t\t\tindex: binding.index ?? null,\n\t\t\t\torder,\n\t\t\t\tclientFilters: binding.clientFilters,\n\t\t\t\tserverFilters: binding.serverFilters,\n\t\t\t\trelationPath: binding.relationPath,\n\t\t\t\tparent: binding.parent ?? null,\n\t\t\t\tprincipal,\n\t\t\t\ttenant: binding.tenant ?? null,\n\t\t\t\tpolicyVersion,\n\t\t\t\tengineVersion,\n\t\t\t\tregistryVersion,\n\t\t\t},\n\t\t\tbounds,\n\t\t),\n\t)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ManagedReadRequest } from "./security.mjs";
|
|
2
|
+
import { NativePagination } from "./filters.mjs";
|
|
3
|
+
import { CursorBinding, createCursorCodec } from "./cursors.mjs";
|
|
4
|
+
import { createPlanner } from "./planner.mjs";
|
|
5
|
+
import { GenericDataModel, GenericDatabaseReader, GenericSchema, SchemaDefinition } from "convex/server";
|
|
6
|
+
import { Value } from "convex/values";
|
|
7
|
+
import { QueryStream } from "convex-helpers/server/stream";
|
|
8
|
+
//#region src/querying/execution.d.ts
|
|
9
|
+
type Plan = ReturnType<ReturnType<typeof createPlanner>['plan']>;
|
|
10
|
+
type Codec = ReturnType<typeof createCursorCodec>;
|
|
11
|
+
export type ExecutionRequest = ManagedReadRequest<GenericDataModel>;
|
|
12
|
+
type Request = Omit<ExecutionRequest, 'db'>;
|
|
13
|
+
/** Private shared boundary: only the established fixed typed errors are public. */
|
|
14
|
+
export declare function rethrowQueryError(error: unknown): never;
|
|
15
|
+
export declare function executeQuery(schema: SchemaDefinition<GenericSchema, boolean>, request: Request, table: string, plan: Plan, planner: ReturnType<typeof createPlanner>, codec: Codec, binding: CursorBinding, complete?: (document: Record<string, Value>, permitted: boolean) => Promise<Record<string, Value> | null>): Promise<{
|
|
16
|
+
page: Record<string, Value>[];
|
|
17
|
+
isDone: boolean;
|
|
18
|
+
splitCursor?: import("convex/server").Cursor | null;
|
|
19
|
+
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
20
|
+
continueCursor: string;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function executeStream(schema: SchemaDefinition<GenericSchema, boolean>, request: Request, table: string, plan: Plan, planner: ReturnType<typeof createPlanner>, codec: Codec, binding: CursorBinding, args: NativePagination, complete?: (document: Record<string, Value>, permitted: boolean) => Promise<Record<string, Value> | null>, seal?: (raw: string) => string, transform?: (source: QueryStream<Record<string, Value>>, db: GenericDatabaseReader<any>) => QueryStream<Record<string, Value>>, authorizationTable?: string): Promise<{
|
|
23
|
+
page: Record<string, Value>[];
|
|
24
|
+
isDone: boolean;
|
|
25
|
+
splitCursor?: import("convex/server").Cursor | null;
|
|
26
|
+
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
27
|
+
continueCursor: string;
|
|
28
|
+
}>;
|
|
29
|
+
//#endregion
|
|
30
|
+
//# sourceMappingURL=execution.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{ManagedReadError as e}from"./security.mjs";import{CursorError as t}from"./cursors.mjs";import{RelationshipError as n}from"./relationships.mjs";import{EmptyStream as r,QueryStream as i,stream as a}from"convex-helpers/server/stream";function o(r){throw r instanceof e||r instanceof t||r instanceof n?r:Error(`Query execution failed`)}async function s(e,t,n,r,i,a,o,s){return c(e,t,n,r,i,a,o,{...r.args,cursor:r.args.cursor===null?null:a.open(r.args.cursor,o),endCursor:r.args.endCursor==null?r.args.endCursor:a.open(r.args.endCursor,o)},s)}async function c(e,t,n,i,s,c,u,d,p,m=e=>c.seal(e,u),h,g=n){let _=l(t,s);try{let o=a(_.db,e).query(n),c=(i.index?o.withIndex(i.index.name,e=>{let t=e;for(let{field:e,value:n}of i.prefix)t=t.eq(e,n);return t}):o).order(i.args.order??`asc`),l=h?h(c,_.db):c,u=i.unsatisfiable?await new r(l.getOrder(),l.getIndexFields(),l.getEqualityIndexFilter()).paginate(d):await new f(l,t,g,i,s,{completed:[],stopped:!1},p).paginate(d),v={...u,continueCursor:m(u.continueCursor)};return u.splitCursor!=null&&(v.splitCursor=m(u.splitCursor)),v}catch(e){o(e)}finally{await _.close()}}function l(e,t){let n=/* @__PURE__ */ new Set;function r(e,i){return new Proxy({},{get(a,o){let s=i;if(o===`withIndex`&&`withIndex`in s)return(...t)=>{let n=s.withIndex;return r(e,n.apply(s,t))};if(o===`order`&&`order`in s)return(...t)=>{let n=s.order;return r(e,n.apply(s,t))};if(o===Symbol.asyncIterator)return()=>{let r=s[Symbol.asyncIterator]();return n.add(r),{next:()=>(t.beforeLeaf(e),r.next())}};throw Error(`Unsupported private query operation`)}})}return{db:{query:n=>(t.beforeLeaf(n),r(n,e.leafQuery(n)))},async close(){let e=!1;for(let t of n)try{await t.return?.()}catch{e=!0}if(n.clear(),e)throw Error(`Query execution failed`)}}}function u(e){let t=e.counters,n=e.budgets;return t.readOperations<n.readOperations&&t.rawCandidates<n.rawCandidates&&t.documentBytesObserved+t.documentBytesReserved+1048576<=n.documentBytes}var d=class extends i{entry;source;constructor(e,t){super(),this.entry=e,this.source=t}getOrder(){return this.source.getOrder()}getIndexFields(){return this.source.getIndexFields()}getEqualityIndexFilter(){return this.source.getEqualityIndexFilter()}narrow(e){if(e.lowerBound.length||e.upperBound.length||!e.lowerBoundInclusive||!e.upperBoundInclusive)throw Error(`Invalid cursor encoder range`);return this}async*iterWithKeys(){yield[{},this.entry[1],this.entry[2]]}},f=class t extends i{source;request;table;plan;planner;progress;complete;constructor(e,t,n,r,i,a,o){super(),this.source=e,this.request=t,this.table=n,this.plan=r,this.planner=i,this.progress=a,this.complete=o}getOrder(){return this.source.getOrder()}getIndexFields(){return this.source.getIndexFields()}getEqualityIndexFilter(){return this.source.getEqualityIndexFilter()}narrow(e){return new t(this.source.narrow(e),this.request,this.table,this.plan,this.planner,this.progress,this.complete)}async*iterWithKeys(){let t=this.source.iterWithKeys(!0)[Symbol.asyncIterator]();try{for(;;){if(!u(this.request)){if(!this.progress.completed.length)throw new e(`MANAGED_READ_BUDGET`);this.progress.stopped=!0;return}let n,r=this.request.counters.rawCandidates;try{n=await t.next(),this.planner.assertUnchanged()}catch(t){if(t instanceof e&&t.code===`MANAGED_READ_BUDGET`&&this.progress.completed.length&&this.request.counters.rawCandidates===r){this.progress.stopped=!0;return}throw t}if(n.done)return;let[i,a,o]=n.value,s=null;if(i){let e=await this.request.authorize(this.table,i);this.planner.assertUnchanged(),s=this.complete?await this.complete(i,e):this.planner.complete(this.table,i,e,this.plan.residual)}let c=[s,a,o];this.progress.completed.push(c),u(this.request)||(this.progress.stopped=!0),yield c}}finally{await t.return?.()}}async paginate(t){let n=await super.paginate(t);if(!this.progress.stopped)return n;let r=this.progress.completed;if(!r.length)throw new e(`MANAGED_READ_BUDGET`);let i=async e=>(await new d(e,this.source).paginate({numItems:1,cursor:null})).continueCursor;return{...n,isDone:!1,pageStatus:`SplitRequired`,continueCursor:await i(r.at(-1)),splitCursor:await i(r[Math.floor((r.length-1)/2)])}}};export{s as executeQuery,c as executeStream,o as rethrowQueryError};
|
|
2
|
+
//# sourceMappingURL=execution.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution.mjs","names":["rethrowQueryError","error","ManagedReadError","CursorError","RelationshipError","executeQuery","executeStream","schema","request","table","plan","planner","codec","binding","complete","raw","leaves","leafReader","base","stream","ordered","range","q","field","value","source","transform","result","EmptyStream","args","EligibleStream","authorizationTable","protectedResult","seal","active","wrap","target","key","withIndex","order","iterator","failed","canFetch","c","b","CapturedKey","QueryStream","entry","bounds","progress","next","candidatesBefore","document","bytes","output","permitted","completed","encode"],"sources":["../../src/querying/execution.ts"],"sourcesContent":["import {\n\tEmptyStream,\n\tQueryStream,\n\tstream,\n\ttype IndexBounds,\n\ttype IndexKey,\n} from 'convex-helpers/server/stream'\nimport type {\n\tGenericDatabaseReader,\n\tGenericDataModel,\n\tGenericDocument,\n\tQueryInitializer,\n\tSchemaDefinition,\n\tGenericSchema,\n\tPaginationResult,\n\tTableDefinition,\n\tIndexRangeBuilder,\n} from 'convex/server'\nimport type { Value } from 'convex/values'\nimport type { createPlanner } from './planner'\nimport { MAX_DOCUMENT_BYTES, ManagedReadError, type ManagedReadRequest } from './security'\nimport { CursorError, type CursorBinding, type createCursorCodec } from './cursors'\nimport type { NativePagination } from './filters'\nimport { RelationshipError } from './relationships'\n\ntype Entry = [Record<string, Value> | null, IndexKey, number]\ntype Plan = ReturnType<ReturnType<typeof createPlanner>['plan']>\ntype Codec = ReturnType<typeof createCursorCodec>\nexport type ExecutionRequest = ManagedReadRequest<GenericDataModel>\ntype Request = Omit<ExecutionRequest, 'db'>\ntype ExecutionSchema = SchemaDefinition<\n\tRecord<string, TableDefinition<any, Record<string, string[]>>>,\n\tboolean\n>\n\n/** Private shared boundary: only the established fixed typed errors are public. */\n// oxlint-disable-next-line anti-slop/no-unknown-parameters -- Thrown values enter this error boundary untrusted; the class checks below admit only the fixed public error contracts.\nexport function rethrowQueryError(error: unknown): never {\n\tif (\n\t\terror instanceof ManagedReadError ||\n\t\terror instanceof CursorError ||\n\t\terror instanceof RelationshipError\n\t)\n\t\tthrow error\n\t// Native/policy errors may contain traversal values or structured private data.\n\tthrow new Error('Query execution failed')\n}\n\nexport async function executeQuery(\n\tschema: SchemaDefinition<GenericSchema, boolean>,\n\trequest: Request,\n\ttable: string,\n\tplan: Plan,\n\tplanner: ReturnType<typeof createPlanner>,\n\tcodec: Codec,\n\tbinding: CursorBinding,\n\tcomplete?: (\n\t\tdocument: Record<string, Value>,\n\t\tpermitted: boolean,\n\t) => Promise<Record<string, Value> | null>,\n) {\n\t// Authenticate BOTH navigation slots before constructing or consuming native queries.\n\tconst args = {\n\t\t...plan.args,\n\t\tcursor: plan.args.cursor === null ? null : codec.open(plan.args.cursor, binding),\n\t\tendCursor:\n\t\t\tplan.args.endCursor == null ? plan.args.endCursor : codec.open(plan.args.endCursor, binding),\n\t}\n\treturn executeStream(schema, request, table, plan, planner, codec, binding, args, complete)\n}\n\nexport async function executeStream(\n\tschema: SchemaDefinition<GenericSchema, boolean>,\n\trequest: Request,\n\ttable: string,\n\tplan: Plan,\n\tplanner: ReturnType<typeof createPlanner>,\n\tcodec: Codec,\n\tbinding: CursorBinding,\n\targs: NativePagination,\n\tcomplete?: (\n\t\tdocument: Record<string, Value>,\n\t\tpermitted: boolean,\n\t) => Promise<Record<string, Value> | null>,\n\tseal: (raw: string) => string = (raw) => codec.seal(raw, binding),\n\ttransform?: (\n\t\tsource: QueryStream<Record<string, Value>>,\n\t\tdb: GenericDatabaseReader<any>,\n\t) => QueryStream<Record<string, Value>>,\n\tauthorizationTable = table,\n) {\n\tconst leaves = leafReader(request, planner)\n\ttry {\n\t\t// SAFETY: runtime ownership/index validation precedes this dynamic table\n\t\t// adapter. Public generics retain the original application's schema.\n\t\tconst base = stream(leaves.db, schema as ExecutionSchema).query(table)\n\t\tconst selected = plan.index\n\t\t\t? base.withIndex(plan.index.name, (q) => {\n\t\t\t\t\t// SAFETY: every equality is a validated consecutive native prefix.\n\t\t\t\t\tlet range = q as IndexRangeBuilder<any, string[], number>\n\t\t\t\t\tfor (const { field, value } of plan.prefix) {\n\t\t\t\t\t\t// SAFETY: native eq returns the next builder; runtime prefix length\n\t\t\t\t\t\t// is validated even though this dynamic adapter erases tuple lengths.\n\t\t\t\t\t\trange = range.eq(field, value) as IndexRangeBuilder<any, string[], number>\n\t\t\t\t\t}\n\t\t\t\t\treturn range\n\t\t\t\t})\n\t\t\t: base\n\t\tconst ordered = selected.order(plan.args.order ?? 'asc')\n\t\tconst source = transform ? transform(ordered, leaves.db) : ordered\n\t\tconst result = plan.unsatisfiable\n\t\t\t? await new EmptyStream<Record<string, Value>>(\n\t\t\t\t\tsource.getOrder(),\n\t\t\t\t\tsource.getIndexFields(),\n\t\t\t\t\tsource.getEqualityIndexFilter(),\n\t\t\t\t).paginate(args)\n\t\t\t: await new EligibleStream(\n\t\t\t\t\tsource,\n\t\t\t\t\trequest,\n\t\t\t\t\tauthorizationTable,\n\t\t\t\t\tplan,\n\t\t\t\t\tplanner,\n\t\t\t\t\t{\n\t\t\t\t\t\tcompleted: [],\n\t\t\t\t\t\tstopped: false,\n\t\t\t\t\t},\n\t\t\t\t\tcomplete,\n\t\t\t\t).paginate(args)\n\t\tconst protectedResult = {\n\t\t\t...result,\n\t\t\tcontinueCursor: seal(result.continueCursor),\n\t\t}\n\t\tif (result.splitCursor != null) protectedResult.splitCursor = seal(result.splitCursor)\n\t\treturn protectedResult\n\t} catch (error) {\n\t\trethrowQueryError(error)\n\t} finally {\n\t\tawait leaves.close()\n\t}\n}\n\n/** Retain the actual metered leaf, since the helper's public key iterator does\n * not forward return(). This private reader only constructs raw metered queries.\n */\nfunction leafReader(request: Request, planner: ReturnType<typeof createPlanner>) {\n\tconst active = new Set<AsyncIterator<GenericDocument>>()\n\tfunction wrap<Source extends AsyncIterable<GenericDocument>>(\n\t\ttable: string,\n\t\tsource: Source,\n\t): Source {\n\t\t// SAFETY: the empty proxy shell is private; supported methods forward to the real native source below.\n\t\treturn new Proxy({} as Source, {\n\t\t\tget(_target, key) {\n\t\t\t\tconst target = source\n\t\t\t\tif (key === 'withIndex' && 'withIndex' in target)\n\t\t\t\t\treturn (...args: Parameters<QueryInitializer<any>['withIndex']>) => {\n\t\t\t\t\t\t// SAFETY: the private facade forwards an existing native index constructor only.\n\t\t\t\t\t\tconst withIndex = target.withIndex as QueryInitializer<any>['withIndex']\n\t\t\t\t\t\treturn wrap(table, withIndex.apply(target, args))\n\t\t\t\t\t}\n\t\t\t\tif (key === 'order' && 'order' in target)\n\t\t\t\t\treturn (...args: Parameters<QueryInitializer<any>['order']>) => {\n\t\t\t\t\t\t// SAFETY: the private facade forwards the existing native order method only.\n\t\t\t\t\t\tconst order = target.order as QueryInitializer<any>['order']\n\t\t\t\t\t\treturn wrap(table, order.apply(target, args))\n\t\t\t\t\t}\n\t\t\t\tif (key === Symbol.asyncIterator)\n\t\t\t\t\treturn () => {\n\t\t\t\t\t\tconst iterator = target[Symbol.asyncIterator]()\n\t\t\t\t\t\tactive.add(iterator)\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tnext: () => {\n\t\t\t\t\t\t\t\tplanner.beforeLeaf(table)\n\t\t\t\t\t\t\t\treturn iterator.next()\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\tthrow new Error('Unsupported private query operation')\n\t\t\t},\n\t\t})\n\t}\n\n\t// SAFETY: stream() uses only query to construct the indexed stream. The\n\t// request's authorized reader handles policy gets separately; never publish this facade.\n\tconst db = {\n\t\tquery: (table: string) => {\n\t\t\tplanner.beforeLeaf(table)\n\t\t\treturn wrap(table, request.leafQuery(table))\n\t\t},\n\t} as GenericDatabaseReader<any>\n\treturn {\n\t\tdb,\n\t\tasync close() {\n\t\t\tlet failed = false\n\t\t\tfor (const iterator of active) {\n\t\t\t\ttry {\n\t\t\t\t\tawait iterator.return?.()\n\t\t\t\t} catch {\n\t\t\t\t\tfailed = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tactive.clear()\n\t\t\tif (failed) throw new Error('Query execution failed')\n\t\t},\n\t}\n}\n\nfunction canFetch(request: Request): boolean {\n\tconst c = request.counters,\n\t\tb = request.budgets\n\treturn (\n\t\tc.readOperations < b.readOperations &&\n\t\tc.rawCandidates < b.rawCandidates &&\n\t\tc.documentBytesObserved + c.documentBytesReserved + MAX_DOCUMENT_BYTES <= b.documentBytes\n\t)\n}\n\n/** Encoder-only use of public native pagination; no private cursor wire format,\n * artificial bandwidth, lookahead, or database read.\n */\nclass CapturedKey extends QueryStream<object> {\n\tconstructor(\n\t\tprivate readonly entry: Entry,\n\t\tprivate readonly source: QueryStream<Record<string, Value>>,\n\t) {\n\t\tsuper()\n\t}\n\tgetOrder() {\n\t\treturn this.source.getOrder()\n\t}\n\tgetIndexFields() {\n\t\treturn this.source.getIndexFields()\n\t}\n\tgetEqualityIndexFilter() {\n\t\treturn this.source.getEqualityIndexFilter()\n\t}\n\tnarrow(bounds: IndexBounds) {\n\t\tif (\n\t\t\tbounds.lowerBound.length ||\n\t\t\tbounds.upperBound.length ||\n\t\t\t!bounds.lowerBoundInclusive ||\n\t\t\t!bounds.upperBoundInclusive\n\t\t)\n\t\t\tthrow new Error('Invalid cursor encoder range')\n\t\treturn this\n\t}\n\tasync *iterWithKeys(): AsyncGenerator<[object, IndexKey, number], undefined> {\n\t\tyield [{}, this.entry[1], this.entry[2]]\n\t}\n}\n\ninterface Progress {\n\tcompleted: Entry[]\n\tstopped: boolean\n}\nclass EligibleStream extends QueryStream<Record<string, Value>> {\n\tconstructor(\n\t\tprivate readonly source: QueryStream<Record<string, Value>>,\n\t\tprivate readonly request: Request,\n\t\tprivate readonly table: string,\n\t\tprivate readonly plan: Plan,\n\t\tprivate readonly planner: ReturnType<typeof createPlanner>,\n\t\tprivate readonly progress: Progress,\n\t\tprivate readonly complete?: (\n\t\t\tdocument: Record<string, Value>,\n\t\t\tpermitted: boolean,\n\t\t) => Promise<Record<string, Value> | null>,\n\t) {\n\t\tsuper()\n\t}\n\tgetOrder() {\n\t\treturn this.source.getOrder()\n\t}\n\tgetIndexFields() {\n\t\treturn this.source.getIndexFields()\n\t}\n\tgetEqualityIndexFilter() {\n\t\treturn this.source.getEqualityIndexFilter()\n\t}\n\tnarrow(bounds: IndexBounds): QueryStream<Record<string, Value>> {\n\t\treturn new EligibleStream(\n\t\t\tthis.source.narrow(bounds),\n\t\t\tthis.request,\n\t\t\tthis.table,\n\t\t\tthis.plan,\n\t\t\tthis.planner,\n\t\t\tthis.progress,\n\t\t\tthis.complete,\n\t\t)\n\t}\n\tasync *iterWithKeys(): AsyncGenerator<Entry, undefined> {\n\t\t// Always request actual root bandwidth, even without a native byte option.\n\t\tconst iterator = this.source.iterWithKeys(true)[Symbol.asyncIterator]()\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\tif (!canFetch(this.request)) {\n\t\t\t\t\tif (!this.progress.completed.length) throw new ManagedReadError('MANAGED_READ_BUDGET')\n\t\t\t\t\tthis.progress.stopped = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet next: IteratorResult<Entry>\n\t\t\t\tconst candidatesBefore = this.request.counters.rawCandidates\n\t\t\t\ttry {\n\t\t\t\t\tnext = await iterator.next()\n\t\t\t\t\tthis.planner.assertUnchanged()\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// A reservation failure BEFORE the next root is safe to stop at\n\t\t\t\t\t// completed progress. Authorization errors below are never partialized.\n\t\t\t\t\tif (\n\t\t\t\t\t\terror instanceof ManagedReadError &&\n\t\t\t\t\t\terror.code === 'MANAGED_READ_BUDGET' &&\n\t\t\t\t\t\tthis.progress.completed.length &&\n\t\t\t\t\t\tthis.request.counters.rawCandidates === candidatesBefore\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.progress.stopped = true\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t\tif (next.done) return\n\t\t\t\tconst [document, key, bytes] = next.value\n\t\t\t\tlet output: Record<string, Value> | null = null\n\t\t\t\tif (document) {\n\t\t\t\t\t// SAFETY: metered native stream documents contain their system IDs.\n\t\t\t\t\tconst permitted = await this.request.authorize(this.table, document as GenericDocument)\n\t\t\t\t\tthis.planner.assertUnchanged()\n\t\t\t\t\t// One live whole-registry guard covers the synchronous residual\n\t\t\t\t\t// and strict projection step AFTER the asynchronous policy finishes.\n\t\t\t\t\t// SAFETY: the registry DTO validates declared public native values only.\n\t\t\t\t\toutput = this.complete\n\t\t\t\t\t\t? await this.complete(document, permitted)\n\t\t\t\t\t\t: (this.planner.complete(this.table, document, permitted, this.plan.residual) as Record<\n\t\t\t\t\t\t\t\tstring,\n\t\t\t\t\t\t\t\tValue\n\t\t\t\t\t\t\t> | null)\n\t\t\t\t}\n\t\t\t\t// Complete policy, residual, and required DTO before recording the key.\n\t\t\t\tconst entry: Entry = [output, key, bytes]\n\t\t\t\tthis.progress.completed.push(entry)\n\t\t\t\tif (!canFetch(this.request)) this.progress.stopped = true\n\t\t\t\tyield entry\n\t\t\t}\n\t\t} finally {\n\t\t\tawait iterator.return?.()\n\t\t}\n\t}\n\toverride async paginate(\n\t\targs: NativePagination,\n\t): Promise<PaginationResult<Record<string, Value>>> {\n\t\tconst result = await super.paginate(args)\n\t\tif (!this.progress.stopped) return result\n\t\tconst completed = this.progress.completed\n\t\tif (!completed.length) throw new ManagedReadError('MANAGED_READ_BUDGET')\n\t\tconst encode = async (entry: Entry) =>\n\t\t\t(await new CapturedKey(entry, this.source).paginate({ numItems: 1, cursor: null }))\n\t\t\t\t.continueCursor\n\t\treturn {\n\t\t\t...result,\n\t\t\tisDone: false,\n\t\t\tpageStatus: 'SplitRequired',\n\t\t\tcontinueCursor: await encode(completed.at(-1)!),\n\t\t\tsplitCursor: await encode(completed[Math.floor((completed.length - 1) / 2)]),\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { ConvexValidatorFromZod } from "convex-helpers/server/zod4";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { GenericId, GenericValidator } from "convex/values";
|
|
4
|
+
//#region src/querying/filters.d.ts
|
|
5
|
+
/** Shared hard ceilings; the execution engine may impose lower budgets. */
|
|
6
|
+
export declare const MAX_PAGE_SIZE = 100;
|
|
7
|
+
export declare const MAX_FILTER_DEPTH = 4;
|
|
8
|
+
export declare const MAX_FILTER_NODES = 256;
|
|
9
|
+
export declare const MAX_FILTER_IN_VALUES = 100;
|
|
10
|
+
export type FieldSchemas = Record<string, z.ZodType>;
|
|
11
|
+
type NativeField<Schema extends z.ZodType> = ConvexValidatorFromZod<Schema, 'required'>;
|
|
12
|
+
type ScalarKind = 'id' | 'string' | 'float64' | 'int64' | 'boolean' | 'null' | 'literal';
|
|
13
|
+
type SupportedNative<Validator extends GenericValidator> = Validator extends {
|
|
14
|
+
kind: ScalarKind;
|
|
15
|
+
} ? true : Validator extends {
|
|
16
|
+
kind: 'union';
|
|
17
|
+
members: infer Members extends GenericValidator[];
|
|
18
|
+
} ? false extends SupportedNative<Members[number]> ? false : true : false;
|
|
19
|
+
export type ScalarSelection<Fields extends FieldSchemas> = { [Key in keyof Fields as false extends SupportedNative<NativeField<Fields[Key]>> ? never : Key]?: true; };
|
|
20
|
+
export type ExactScalarSelection<Fields extends FieldSchemas, Selection> = Selection & { [Key in keyof Selection]: Key extends keyof ScalarSelection<Fields> ? Selection[Key] extends true ? true : never : never; };
|
|
21
|
+
type NativeValue<Schema extends z.ZodType> = Exclude<NativeField<Schema>['type'], undefined>;
|
|
22
|
+
type FilterInputValue<Value> = Value extends GenericId<string> ? string : Value;
|
|
23
|
+
type FilterValue<Schema extends z.ZodType, Side extends 'input' | 'output'> = Side extends 'input' ? FilterInputValue<NativeValue<Schema>> : NativeValue<Schema>;
|
|
24
|
+
type RangeKind = 'string' | 'float64' | 'int64' | 'literal';
|
|
25
|
+
type OrderedNative<Validator extends GenericValidator> = Validator extends {
|
|
26
|
+
kind: RangeKind;
|
|
27
|
+
} ? Validator['type'] extends string | number | bigint | undefined ? true : false : Validator extends {
|
|
28
|
+
kind: 'union';
|
|
29
|
+
members: infer Members extends GenericValidator[];
|
|
30
|
+
} ? false extends OrderedNative<Members[number]> ? false : true : false;
|
|
31
|
+
type OrderedValue<Schema extends z.ZodType> = OrderedNative<NativeField<Schema>> extends true ? NativeValue<Schema> : never;
|
|
32
|
+
export type ScalarPredicate<Schema extends z.ZodType, Side extends 'input' | 'output'> = FilterValue<Schema, Side> | ({
|
|
33
|
+
eq?: FilterValue<Schema, Side>;
|
|
34
|
+
in?: FilterValue<Schema, Side>[];
|
|
35
|
+
} & ([OrderedValue<Schema>] extends [never] ? {} : {
|
|
36
|
+
gt?: OrderedValue<Schema>;
|
|
37
|
+
gte?: OrderedValue<Schema>;
|
|
38
|
+
lt?: OrderedValue<Schema>;
|
|
39
|
+
lte?: OrderedValue<Schema>;
|
|
40
|
+
}) & (NativeField<Schema>['isOptional'] extends 'optional' ? {
|
|
41
|
+
exists?: boolean;
|
|
42
|
+
} : {}));
|
|
43
|
+
type SelectedFieldFilters<Fields extends FieldSchemas, Selection, Side extends 'input' | 'output'> = { [Key in Extract<keyof Selection, keyof Fields>]?: ScalarPredicate<Fields[Key], Side>; };
|
|
44
|
+
/** Direct shorthand and explicit fields combine with AND. The fields namespace
|
|
45
|
+
* accepts every selected scalar name, including boolean-control/namespace names.
|
|
46
|
+
*/
|
|
47
|
+
type FilterDepth = readonly [1, 1, 1, 1];
|
|
48
|
+
type Tail<Depth extends readonly 1[]> = Depth extends readonly [1, ...infer Rest extends 1[]] ? Rest : [];
|
|
49
|
+
export type ScalarFilters<Fields extends FieldSchemas, Selection, Side extends 'input' | 'output' = 'output', Depth extends readonly 1[] = FilterDepth> = Omit<SelectedFieldFilters<Fields, Selection, Side>, 'and' | 'or' | 'not' | 'fields'> & {
|
|
50
|
+
fields?: SelectedFieldFilters<Fields, Selection, Side>;
|
|
51
|
+
} & (Depth extends readonly [] ? {} : {
|
|
52
|
+
and?: ScalarFilters<Fields, Selection, Side, Tail<Depth>>[];
|
|
53
|
+
or?: ScalarFilters<Fields, Selection, Side, Tail<Depth>>[];
|
|
54
|
+
not?: ScalarFilters<Fields, Selection, Side, Tail<Depth>>;
|
|
55
|
+
});
|
|
56
|
+
export declare const nativePagination: z.ZodObject<{
|
|
57
|
+
cursor: z.ZodUnion<readonly [z.ZodString, z.ZodNull]>;
|
|
58
|
+
endCursor: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNull]>>;
|
|
59
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
numItems: z.ZodNumber;
|
|
61
|
+
maximumRowsRead: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
maximumBytesRead: z.ZodOptional<z.ZodNumber>;
|
|
63
|
+
}, z.core.$strict>;
|
|
64
|
+
export type NativePagination = z.output<typeof nativePagination>;
|
|
65
|
+
/** Preserve the object boundary so pagination options can be registered directly as query args. */
|
|
66
|
+
export type PaginationOptionsSchema<Filters, InputFilters = Filters> = z.ZodObject<(typeof nativePagination)['shape'] & {
|
|
67
|
+
filters: z.ZodOptional<z.ZodType<Filters, InputFilters>>;
|
|
68
|
+
order: z.ZodOptional<z.ZodEnum<{
|
|
69
|
+
asc: 'asc';
|
|
70
|
+
desc: 'desc';
|
|
71
|
+
}>>;
|
|
72
|
+
}, z.core.$strict>;
|
|
73
|
+
export type ScalarPaginationSchema<Fields extends FieldSchemas, Selection> = PaginationOptionsSchema<ScalarFilters<Fields, Selection>, ScalarFilters<Fields, Selection, 'input'>>;
|
|
74
|
+
/** Scalar-only schema constructor shared by table and future registry boundaries.
|
|
75
|
+
* Convex metadata describes stored INPUT, so storage refinements/transforms never run here.
|
|
76
|
+
*/
|
|
77
|
+
export declare function scalarPaginationSchema<Fields extends FieldSchemas, const Selection extends ScalarSelection<Fields>>(validators: Record<string, GenericValidator>, selection: ExactScalarSelection<Fields, Selection>): ScalarPaginationSchema<Fields, Selection>;
|
|
78
|
+
export declare function scalarPredicateSchema(validator: GenericValidator): z.ZodType;
|
|
79
|
+
/** Each boolean child and relation target expression consumes one of four edges.
|
|
80
|
+
* some/none wrappers and fields namespaces consume nodes, but no extra depth.
|
|
81
|
+
*/
|
|
82
|
+
export interface FilterGrammar {
|
|
83
|
+
fields: FieldSchemas;
|
|
84
|
+
relations: Record<string, {
|
|
85
|
+
cardinality: 'one' | 'many';
|
|
86
|
+
target: FilterGrammar;
|
|
87
|
+
}>;
|
|
88
|
+
}
|
|
89
|
+
export declare function relationFilterSchema(grammar: FilterGrammar): z.ZodType;
|
|
90
|
+
export type FilterScalar = string | number | bigint | boolean | null;
|
|
91
|
+
export type FilterOperator = 'eq' | 'in' | 'gt' | 'gte' | 'lt' | 'lte' | 'exists';
|
|
92
|
+
export type FilterAst = {
|
|
93
|
+
kind: 'and' | 'or';
|
|
94
|
+
children: FilterAst[];
|
|
95
|
+
} | {
|
|
96
|
+
kind: 'not';
|
|
97
|
+
child: FilterAst;
|
|
98
|
+
} | {
|
|
99
|
+
kind: 'scalar';
|
|
100
|
+
field: string;
|
|
101
|
+
operator: FilterOperator;
|
|
102
|
+
value: FilterScalar | FilterScalar[];
|
|
103
|
+
} | {
|
|
104
|
+
kind: 'relation';
|
|
105
|
+
name: string;
|
|
106
|
+
mode: 'one' | 'some' | 'none';
|
|
107
|
+
predicate: FilterAst | null;
|
|
108
|
+
};
|
|
109
|
+
export interface QueryIndex {
|
|
110
|
+
name: string;
|
|
111
|
+
fields: readonly string[];
|
|
112
|
+
}
|
|
113
|
+
export interface IndexCondition {
|
|
114
|
+
value: FilterScalar | undefined;
|
|
115
|
+
index: string;
|
|
116
|
+
}
|
|
117
|
+
export interface CompiledFilters {
|
|
118
|
+
residual: FilterAst;
|
|
119
|
+
bindings: ReadonlyMap<string, FilterScalar>;
|
|
120
|
+
unsatisfiable: boolean;
|
|
121
|
+
index: QueryIndex | null;
|
|
122
|
+
prefix: readonly {
|
|
123
|
+
field: string;
|
|
124
|
+
value: FilterScalar;
|
|
125
|
+
}[];
|
|
126
|
+
}
|
|
127
|
+
type FilterSpec<Schema extends z.ZodType> = z.input<Schema> extends {
|
|
128
|
+
filters?: infer Spec;
|
|
129
|
+
} ? Spec : never;
|
|
130
|
+
export type ServerFilter<Fields extends FieldSchemas> = ScalarFilters<Fields, ScalarSelection<Fields>>;
|
|
131
|
+
/** Validate first; where is independently validated and ANDed with client predicates.
|
|
132
|
+
* OR remains a residual expression over one traversal, never multiple streams.
|
|
133
|
+
*/
|
|
134
|
+
export declare function compileFilters<Schema extends z.ZodType>(schema: Schema, input: z.input<Schema>, indexes: readonly QueryIndex[], conditions?: readonly IndexCondition[], where?: FilterSpec<Schema>): CompiledFilters;
|
|
135
|
+
//#endregion
|
|
136
|
+
//# sourceMappingURL=filters.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{convexToZod as e}from"convex-helpers/server/zod4";import{paginationOptsValidator as t}from"convex/server";import{z as n}from"zod";import{compareValues as r}from"convex/values";const i=100,a=4,o=256,s=100,c=e(t),l=c.strict().extend({numItems:c.shape.numItems.int().positive().max(100),maximumRowsRead:c.shape.maximumRowsRead.unwrap().int().positive().optional(),maximumBytesRead:c.shape.maximumBytesRead.unwrap().int().positive().optional()});function u(e,t){let r={};for(let[n,i]of Object.entries(t)){if(i!==!0||!Object.hasOwn(e,n))throw Error(`Invalid scalar filter selection "${n}"`);r[n]=d(e[n]).optional()}let i=T(r);return l.extend({filters:i.optional(),order:n.enum([`asc`,`desc`]).optional()})}function d(t){C(t);let r=e(t).nonoptional(),i=n.object({eq:r.optional(),in:n.array(r).min(1).max(100).optional()}),a=w(t)?i.extend({gt:r.optional(),gte:r.optional(),lt:r.optional(),lte:r.optional()}):i,o=(t.isOptional===`optional`?a.extend({exists:n.boolean().optional()}):a).strict().refine(e=>Object.values(e).some(e=>e!==void 0),`At least one operator is required`);return n.union([r,o])}const f=/* @__PURE__ */ new WeakMap,p=/* @__PURE__ */ new WeakMap,m=n.core.$constructor(`BoundedFilterObject`,(e,t)=>{n.ZodObject.init(e,t);let r=e._zod.parse,i=t.shape.fields,a=i instanceof n.ZodOptional?i.unwrap():void 0,o=a instanceof n.ZodObject?Object.keys(a.shape):Object.keys(t.shape),s={fields:Object.fromEntries(o.map(e=>[e,t.shape[e]])),relations:Object.fromEntries(Object.entries(t.shape).flatMap(([e,t])=>{let n=f.get(t);return n?[[e,n]]:[]}))};e._zod.parse=(e,t)=>D(e.value,s)?r(e,t):(e.issues.push({code:`custom`,input:e.value,message:`Unsupported filter layout or depth/node budget exceeded`}),e)});function h(e){let t=new m(E(e,0)._zod.def);return p.set(t,e),t}function g(e,t,n,r=[],i){let a=e.parse(t),o=i===void 0?void 0:e.parse({...a,filters:i}),s=_(e),c={kind:`and`,children:[v(a.filters,s),v(o?.filters,s)]},l=x(c),u=[...n].sort((e,t)=>e.name.localeCompare(t.name)||e.fields.join(`\0`).localeCompare(t.fields.join(`\0`))),d=r.find(e=>e.value!==null&&e.value!==void 0),f=null;if(d){if(f=u.find(e=>e.name===d.index)??null,!f||S(f,l.values).length===0)throw Error(`Explicit index "${d.index}" is incompatible with guaranteed filters`)}else{let e=0;for(let t of u){let n=S(t,l.values).length;n>e&&(f=t,e=n)}}return{residual:c,bindings:l.values,unsatisfiable:l.impossible,index:f,prefix:f?S(f,l.values):[]}}function _(e){if(!(e instanceof n.ZodObject))return;let t=e.shape.filters;if(!(t instanceof n.ZodOptional))return;let r=t.unwrap(),i=p.get(r);if(i)return i;if(r instanceof n.ZodObject)return{fields:r.shape,relations:Object.fromEntries(Object.entries(r.shape).flatMap(([e,t])=>{let n=f.get(t);return n?[[e,n]]:[]}))}}function v(e,t){let n=[];if(!e)return{kind:`and`,children:n};let r=e;for(let[e,i]of Object.entries(r))if(i!==void 0){if(e===`and`||e===`or`)n.push({kind:e,children:i.map(e=>v(e,t))});else if(e===`not`)n.push({kind:`not`,child:v(i,t)});else if(e===`fields`)for(let[e,t]of Object.entries(i))n.push(...y(e,t));else if(t?.relations[e]){let r=t.relations[e];if(r.cardinality===`one`)n.push({kind:`relation`,name:e,mode:`one`,predicate:i===null?null:v(i,r.target)});else{let t=i;n.push({kind:`relation`,name:e,mode:t.some===void 0?`none`:`some`,predicate:v(t.some??t.none,r.target)})}}else n.push(...y(e,i))}return{kind:`and`,children:n}}function y(e,t){return t===void 0?[]:Object(t)===t?Object.entries(t).filter(([,e])=>e!==void 0).map(([t,n])=>({kind:`scalar`,field:e,operator:t,value:n})):[{kind:`scalar`,field:e,operator:`eq`,value:t}]}function b(e,t){return r(e,t)===0}function x(e){let t=/* @__PURE__ */ new Map;if(e.kind===`scalar`){e.operator===`eq`&&!Array.isArray(e.value)&&t.set(e.field,e.value);let n=Array.isArray(e.value)?e.value:void 0;return e.operator===`in`&&n&&n.every(e=>b(e,n[0]))&&t.set(e.field,n[0]),{values:t,impossible:!1}}if(e.kind===`not`||e.kind===`relation`)return{values:t,impossible:!1};let n=e.children.map(x);if(e.kind===`or`){let e=n;for(let[n,r]of e[0]?.values??[])e.every(e=>e.values.has(n)&&b(e.values.get(n),r))&&t.set(n,r);return{values:t,impossible:e.every(e=>e.impossible)}}let r=n.some(e=>e.impossible);for(let e of n)for(let[n,i]of e.values)t.has(n)&&!b(t.get(n),i)&&(r=!0),t.set(n,i);return{values:t,impossible:r}}function S(e,t){let n=[];for(let r of e.fields){if(!t.has(r))break;n.push({field:r,value:t.get(r)})}return n}function C(e){switch(e.kind){case`id`:case`string`:case`float64`:case`int64`:case`boolean`:case`null`:case`literal`:return;case`union`:for(let t of e.members)C(t);return;default:throw Error(`Unsupported scalar filter kind "${e.kind}"`)}}function w(e){switch(e.kind){case`string`:case`float64`:case`int64`:return!0;case`literal`:return n.union([n.string(),n.number(),n.bigint()]).safeParse(e.value).success;case`union`:return e.members.every(w);default:return!1}}function T(e){return h({fields:e,relations:{}})}function E(e,t){let r=e.fields,i=Object.fromEntries(Object.entries(r).filter(([e])=>![`and`,`or`,`not`,`fields`].includes(e))),a={};for(let[r,i]of Object.entries(e.relations)){if(t===4)continue;let e=E(i.target,t+1);a[r]=(i.cardinality===`one`?e.nullable():n.union([n.object({some:e}).strict(),n.object({none:e}).strict()])).optional(),f.set(a[r],i)}let o=n.object({...i,...a,fields:n.object(r).strict().optional()}).strict();if(t===4)return o;let s=E(e,t+1);return o.extend({and:n.array(s).min(1).max(256).optional(),or:n.array(s).min(1).max(256).optional(),not:s.optional()})}function D(e,t){let n=[{value:e,depth:0,context:`expression`,grammar:t}],r=0;for(;n.length;){let e=n.pop();if(++r>256||e.depth>4)return!1;if(Object(e.value)!==e.value)continue;let t=e.value,i=Array.isArray(t)?t:void 0,a=Object.getPrototypeOf(t);if((i?a!==Array.prototype:a!==Object.prototype&&a!==null)||i&&i.length+r+n.length>256)return!1;let o=Reflect.ownKeys(t);if(o.length-+!!i+r+n.length>256||i&&o.length!==i.length+1)return!1;let s=e.context===`expression`?[...Object.keys(e.grammar.fields),...Object.keys(e.grammar.relations),`and`,`or`,`not`,`fields`]:e.context===`fields`?Object.keys(e.grammar.fields):e.context===`predicate`?[`eq`,`in`,`gt`,`gte`,`lt`,`lte`,`exists`]:e.context===`many`?[`some`,`none`]:[];for(let e of s)if(!Object.hasOwn(t,e)&&e in t)return!1;for(let r of o){if(i&&r===`length`)continue;let a=Object.getOwnPropertyDescriptor(t,r),o=String(r);if(!a.enumerable||!(`value`in a)||o!==r||i&&(!/^(0|[1-9]\d*)$/.test(o)||Number(o)>=i.length))return!1;let s=`value`,c=e.depth,l=e.grammar;if(e.context===`expression`){if(s=`predicate`,o===`fields`)s=`fields`;else if(o===`and`||o===`or`)s=`composition`,c++;else if(o===`not`)s=`expression`,c++;else if(e.grammar.relations[o]){let t=e.grammar.relations[o];s=t.cardinality===`one`?`expression`:`many`,l=t.target,c++}}else e.context===`fields`?s=`predicate`:(e.context===`composition`||e.context===`many`)&&(s=`expression`);n.push({value:a.value,depth:c,context:s,grammar:l})}}return!0}export{a as MAX_FILTER_DEPTH,s as MAX_FILTER_IN_VALUES,o as MAX_FILTER_NODES,i as MAX_PAGE_SIZE,g as compileFilters,l as nativePagination,h as relationFilterSchema,u as scalarPaginationSchema,d as scalarPredicateSchema};
|
|
2
|
+
//# sourceMappingURL=filters.mjs.map
|