@dbsp/types 1.7.1 → 1.8.0
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/column-list.ts","../src/intent/expression-intent.ts","../src/intent/recursive-intent.ts","../src/intent/select-function-allowlist.ts","../src/intent/type-guards.ts","../src/intent/where-intent.ts","../src/json-agg-order-key.ts","../src/loaded-schema.ts","../src/relation-key-fields.ts"],"sourcesContent":["/**\n * Shared relation-key normalizer.\n *\n * Relation metadata accepts either a single column or a composite column list.\n * Readers should normalize once and keep the full list instead of degrading to\n * the first column.\n */\nexport type ColumnListInput = string | readonly string[] | undefined;\n\nexport function toColumnList(key: ColumnListInput): readonly string[] {\n\tif (key === undefined) return [];\n\treturn typeof key === 'string' ? [key] : key;\n}\n","/**\n * @module intent/expression-intent\n * Expression intent types for computed/derived values in SELECT.\n */\n\nimport type { QueryIntent } from './query-intent.js';\nimport type { AggregateFunction } from './select-intent.js';\nimport type { WhereIntent } from './where-intent.js';\n\n// ============================================================================\n// Expression Intents - Computed/Derived Values\n// ============================================================================\n\n/**\n * COALESCE expression: returns first non-null value from a list of fields\n * @example { kind: 'coalesce', fields: ['name_fr', 'name_en'], as: 'display_name' }\n * → COALESCE(name_fr, name_en) AS display_name\n */\nexport interface CoalesceExpressionIntent {\n\treadonly kind: 'coalesce';\n\t/** Fields to check in order (first non-null wins) */\n\treadonly fields: readonly string[];\n\t/** Required alias for the result column */\n\treadonly as: string;\n}\n\n/**\n * Raw SQL expression (escape hatch for advanced use cases)\n * @example { kind: 'raw', sql: 'NOW()', as: 'current_time' }\n * → NOW() AS current_time\n * @warning Use sparingly - bypasses type safety and SQL injection protection\n */\nexport interface RawExpressionIntent {\n\treadonly kind: 'raw';\n\t/** Raw SQL fragment (must be safe, no user input!) */\n\treadonly sql: string;\n\t/** Required alias for the result column */\n\treadonly as: string;\n}\n\n/**\n * Simple column expression: just a column reference, optionally aliased\n * Used by NQL when a column is selected without modification\n * @example { kind: 'column', column: 'name' }\n * → SELECT \"name\"\n * @example { kind: 'column', column: 'name', as: 'userName' }\n * → SELECT \"name\" AS \"userName\"\n */\nexport interface ColumnExpressionIntent {\n\treadonly kind: 'column';\n\t/** Column name to select */\n\treadonly column: string;\n\t/** Optional alias for the result column */\n\treadonly as?: string;\n}\n\n/**\n * Column alias expression: simple column reference with alias\n * Uses native Kysely eb.ref().as() - type-safe and dialect-portable\n * @example { kind: 'columnAlias', column: 'name', alias: 'userName' }\n * → SELECT \"name\" AS \"userName\"\n */\nexport interface ColumnAliasIntent {\n\treadonly kind: 'columnAlias';\n\t/** Column name to select */\n\treadonly column: string;\n\t/** Alias for the result column */\n\treadonly alias: string;\n}\n\n/**\n * Relation column expression: select a column from a related table\n * Auto-creates JOIN via include mechanism and selects with custom alias\n * @example { kind: 'relationColumn', relation: 'category', column: 'name', as: 'categoryName' }\n * → SELECT t1.\"name\" AS \"categoryName\" (where t1 is the joined category table)\n * @example { kind: 'relationColumn', relation: 'category.parent', column: 'name', as: 'parentCategoryName' }\n * → Multi-level join: products → category → parent, select parent.name\n */\nexport interface RelationColumnIntent {\n\treadonly kind: 'relationColumn';\n\t/** Relation path to traverse (dot-separated for multi-level) */\n\treadonly relation: string;\n\t/** Column name to select from the target relation */\n\treadonly column: string;\n\t/** Alias for the result column */\n\treadonly as: string;\n}\n\n/**\n * Aggregate expression intent for SELECT expressions\n * @example { kind: 'aggregate', function: 'count', as: 'total' } → COUNT(*) AS total\n * @example { kind: 'aggregate', function: 'sum', field: 'price', as: 'total_price' } → SUM(price) AS total_price\n * @example { kind: 'aggregate', function: 'count', field: 'id', distinct: true, as: 'unique_count' }\n * → COUNT(DISTINCT id) AS unique_count\n */\nexport interface AggregateExpressionIntent {\n\treadonly kind: 'aggregate';\n\t/** Aggregate function */\n\treadonly function: AggregateFunction;\n\t/** Field to aggregate (or '*' for count) */\n\treadonly field: string | '*';\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n\t/** Whether to apply DISTINCT to the aggregate */\n\treadonly distinct?: boolean | undefined;\n\t/** Extra arguments for multi-arg aggregates like string_agg(field, separator) */\n\treadonly extraArgs?: readonly unknown[] | undefined;\n\t/** FILTER (WHERE ...) clause for conditional aggregation */\n\treadonly filter?: WhereIntent | undefined;\n}\n\n/**\n * Pseudo-column traversal keyword for self-referential relations.\n * Used by NQL to traverse hierarchical/tree structures.\n *\n * Default keywords: 'parent', 'child', 'ascendant', 'descendant'.\n * Custom keywords are supported via schema configuration\n * (e.g., 'manager', 'managee' via parentRole/childRole).\n */\nexport type PseudoColumnTraversal = string;\n\n/**\n * Pseudo-column expression intent for self-referential traversal.\n * Enables access to columns on related rows in hierarchical structures.\n *\n * @example { kind: 'pseudoColumn', traversal: 'parent', targetColumn: 'name', as: 'parent.name' }\n * → SELECT parent_row.name AS \"parent.name\" via CTE join\n * @example { kind: 'pseudoColumn', traversal: 'ascendant', targetColumn: 'title', as: 'ancestor_title' }\n * → Recursive CTE to find all ancestors, return their title column\n */\nexport interface PseudoColumnExpressionIntent {\n\treadonly kind: 'pseudoColumn';\n\t/** Traversal type: single-hop (parent/child) or recursive (ascendant/descendant) */\n\treadonly traversal: PseudoColumnTraversal;\n\t/** The column to access on the target row(s) */\n\treadonly targetColumn: string;\n\t/** Alias for result column (required) */\n\treadonly as: string;\n\t/** Optional bounded depth for ascendant[N] / descendant[N] syntax */\n\treadonly depth?: number;\n\t/** Custom role name for multi-FK tables (e.g., 'manager' in manager.ascendant) */\n\treadonly role?: string;\n\t/**\n\t * Chained traversals for multi-hop navigation (e.g., parent.parent.name → ['parent', 'parent']).\n\t * When present, overrides single `traversal` field. Each element generates a successive self-join.\n\t */\n\treadonly traversals?: readonly PseudoColumnTraversal[];\n}\n\n/**\n * Generic function expression (e.g., now(), upper(name), coalesce(a, b)).\n * Used for SQL functions that are not aggregates.\n */\nexport interface FunctionExpressionIntent {\n\treadonly kind: 'function';\n\t/** Function name (e.g., 'upper', 'now', 'coalesce') */\n\treadonly name: string;\n\t/** Function arguments */\n\treadonly args: readonly unknown[];\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n}\n\n/**\n * Scalar subquery in SELECT clause.\n * Produces a single value from a nested query.\n */\nexport interface SubqueryExpressionIntent {\n\treadonly kind: 'subquery';\n\t/** The nested query */\n\treadonly query: QueryIntent;\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n}\n\n/**\n * Arithmetic expression (e.g., price * quantity).\n * Binary operation with left operand, operator, and right operand.\n */\nexport interface ArithmeticExpressionIntent {\n\treadonly kind: 'arithmetic';\n\t/** Left operand (column name or value) */\n\treadonly left: string | number | unknown;\n\t/** Arithmetic operator */\n\treadonly operator: '+' | '-' | '*' | '/' | '%';\n\t/** Right operand (column name or value) */\n\treadonly right: string | number | unknown;\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n}\n\n/**\n * Literal value expression: string, number, boolean, or null.\n * Used in CASE THEN/ELSE clauses for constant values.\n */\nexport interface LiteralExpressionIntent {\n\treadonly kind: 'literal';\n\t/** The literal value */\n\treadonly value: string | number | boolean | null;\n\t/** Optional alias */\n\treadonly as?: string | undefined;\n}\n\n/**\n * Comparison expression: left operator right.\n * Used in CASE WHEN conditions.\n */\nexport interface ComparisonExpressionIntent {\n\treadonly kind: 'comparison';\n\t/** Left side column reference */\n\treadonly column: string;\n\t/** Comparison operator */\n\treadonly operator: '=' | '!=' | '<' | '>' | '<=' | '>=' | 'like';\n\t/** Right side value */\n\treadonly value: unknown;\n}\n\n/**\n * CASE expression: conditional logic in SELECT.\n * CASE WHEN condition THEN result [WHEN ...] [ELSE default] END\n */\nexport interface CaseExpressionIntent {\n\treadonly kind: 'case';\n\t/** Array of WHEN-THEN pairs */\n\treadonly when: ReadonlyArray<{\n\t\treadonly condition: WhereIntent;\n\t\treadonly result: ExpressionIntent;\n\t}>;\n\t/** Optional ELSE clause */\n\treadonly else?: ExpressionIntent | undefined;\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n}\n\n// ============================================================================\n// JSON/JSONB Operators (E13)\n// ============================================================================\n\n/**\n * JSON path extraction: col->'key' or col->>'key' (chained paths supported).\n * Also used for function notation: json_extract(col, 'key'), json_extract_text(col, 'key').\n */\nexport interface JsonExtractIntent {\n\treadonly kind: 'jsonExtract';\n\treadonly field: string;\n\treadonly path: readonly (string | ParamIntent)[];\n\t/** 'json' = returns JSON value (->), 'text' = returns text (->>) */\n\treadonly mode: 'json' | 'text';\n\treadonly as?: string | undefined;\n}\n\n/**\n * JSON containment: col @> value (contains) or col <@ value (contained by).\n */\nexport interface JsonContainsIntent {\n\treadonly kind: 'jsonContains';\n\treadonly field: string;\n\treadonly value: unknown;\n\t/** true = <@ (contained by), false = @> (contains) */\n\treadonly reversed: boolean;\n}\n\n/**\n * JSON key existence: col ? 'key'.\n */\nexport interface JsonExistsIntent {\n\treadonly kind: 'jsonExists';\n\treadonly field: string;\n\treadonly key: string;\n}\n\n/**\n * JSON path extraction with array path: col #> '{a,b}' or col #>> '{a,b}'.\n */\nexport interface JsonPathExtractIntent {\n\treadonly kind: 'jsonPathExtract';\n\treadonly field: string;\n\t/** PostgreSQL JSON path segments. Bound as one text[] parameter by adapters. */\n\treadonly path: readonly (string | ParamIntent)[];\n\t/** 'json' = returns JSON (#>), 'text' = returns text (#>>) */\n\treadonly mode: 'json' | 'text';\n\treadonly as?: string | undefined;\n}\n\n/** Custom binary operator expression (e.g., <=> for pgvector) */\nexport interface CustomOpExpressionIntent {\n\treadonly kind: 'customOp';\n\treadonly operator: string;\n\treadonly left: ExpressionIntent;\n\treadonly right: ExpressionIntent;\n\treadonly as?: string;\n}\n\n/** Custom function call (e.g., paradedb.score) */\n/** Represents a single ORDER BY entry inside an aggregate function. */\nexport interface AggOrderByArg {\n\t/** Discriminator to distinguish from regular expression args. */\n\treadonly __aggOrderBy: true;\n\treadonly field: string;\n\treadonly direction: 'asc' | 'desc';\n}\n\nexport interface CustomFnExpressionIntent {\n\treadonly kind: 'customFn';\n\treadonly name: string;\n\treadonly args: readonly ExpressionIntent[];\n\treadonly as?: string;\n\t/** FILTER (WHERE ...) clause for conditional aggregation */\n\treadonly filter?: WhereIntent | undefined;\n\t/** ORDER BY clause for ordered aggregates (e.g. array_agg(x ORDER BY y)) */\n\treadonly aggOrderBy?: readonly AggOrderByArg[] | undefined;\n}\n\n/** Column reference in custom expressions */\nexport interface RefExpressionIntent {\n\treadonly kind: 'ref';\n\treadonly column: string;\n}\n\n/**\n * Public bound-parameter node.\n *\n * A value that originated from an NQL named parameter or template binding is\n * represented explicitly in the public IntentAST. The inner value is opaque:\n * consumers must bind it as a value and must not inspect its shape to decide\n * whether it is query structure.\n *\n * @public\n */\nexport interface ParamExpressionIntent {\n\treadonly kind: 'param';\n\treadonly value: unknown;\n\t/** Optional alias when used as a SELECT expression. */\n\treadonly as?: string | undefined;\n}\n\n/** @public */\nexport type ParamIntent = ParamExpressionIntent;\n\n/** Single-level only — never recurse into .value; the inner bound value is opaque user data. @public */\nexport function isParamIntent(value: unknown): value is ParamIntent {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as Record<string, unknown>).kind === 'param' &&\n\t\t'value' in (value as Record<string, unknown>)\n\t);\n}\n\n/** Type cast expression */\nexport interface CastExpressionIntent {\n\treadonly kind: 'cast';\n\treadonly expr: ExpressionIntent;\n\treadonly typeName: string;\n}\n\n/** Named argument in a function call: name => value */\nexport interface NamedArgExpressionIntent {\n\treadonly kind: 'namedArg';\n\treadonly name: string;\n\treadonly value: ExpressionIntent;\n}\n\n/** Star/wildcard expression (*) — used in COUNT(*), SELECT *, etc. */\nexport interface StarExpressionIntent {\n\treadonly kind: 'star';\n}\n\n/** PostgreSQL ARRAY constructor: ARRAY[item1, item2, ...] */\nexport interface ArrayExpressionIntent {\n\treadonly kind: 'array';\n\treadonly elements: readonly ExpressionIntent[];\n\treadonly as?: string;\n}\n\n/** Unary operator expression (e.g., NOT, -, ~) */\nexport interface UnaryExpressionIntent {\n\treadonly kind: 'unary';\n\treadonly operator: string;\n\treadonly operand: ExpressionIntent;\n\treadonly as?: string;\n}\n\n/**\n * Expression intent union type - computed/derived values in SELECT\n * Extensible for future expression types\n */\nexport type ExpressionIntent =\n\t| ColumnExpressionIntent\n\t| CoalesceExpressionIntent\n\t| RawExpressionIntent\n\t| ColumnAliasIntent\n\t| RelationColumnIntent\n\t| WindowIntent\n\t| AggregateExpressionIntent\n\t| PseudoColumnExpressionIntent\n\t| FunctionExpressionIntent\n\t| SubqueryExpressionIntent\n\t| ArithmeticExpressionIntent\n\t| LiteralExpressionIntent\n\t| ComparisonExpressionIntent\n\t| CaseExpressionIntent\n\t| JsonExtractIntent\n\t| JsonContainsIntent\n\t| JsonExistsIntent\n\t| JsonPathExtractIntent\n\t| CustomOpExpressionIntent\n\t| CustomFnExpressionIntent\n\t| RefExpressionIntent\n\t| ParamExpressionIntent\n\t| CastExpressionIntent\n\t| UnaryExpressionIntent\n\t| NamedArgExpressionIntent\n\t| StarExpressionIntent\n\t| ArrayExpressionIntent;\n\n// ============================================================================\n// Window Functions (P3-A)\n// ============================================================================\n\n/**\n * Window function types for OVER clause analytics.\n * - Ranking: row_number, rank, dense_rank (no field required)\n * - Aggregate: sum, avg, count, min, max (field required)\n * - Offset: lag, lead (field required, offset/default deferred to P3+)\n */\nexport type WindowFunction =\n\t| 'row_number'\n\t| 'rank'\n\t| 'dense_rank'\n\t| 'sum'\n\t| 'avg'\n\t| 'count'\n\t| 'min'\n\t| 'max'\n\t| 'lag'\n\t| 'lead';\n\n/**\n * Order specification for window OVER clause.\n */\nexport interface WindowOrderBy {\n\treadonly field: string;\n\treadonly direction?: 'asc' | 'desc';\n}\n\n/**\n * Window function intent for analytics over partitions.\n * Produces SQL like: ROW_NUMBER() OVER (PARTITION BY x ORDER BY y) AS alias\n *\n * @example Row numbering\n * {\n * kind: 'window',\n * function: 'row_number',\n * alias: 'rn',\n * over: { orderBy: [{ field: 'created_at', direction: 'desc' }] }\n * }\n *\n * @example Running total\n * {\n * kind: 'window',\n * function: 'sum',\n * field: 'amount',\n * alias: 'running_total',\n * over: { partitionBy: ['account_id'], orderBy: [{ field: 'date' }] }\n * }\n */\n/**\n * Window function intent (ranking branch): ROW_NUMBER / RANK / DENSE_RANK — no field required.\n *\n * @example\n * { kind: 'window', function: 'row_number', alias: 'rn', over: { orderBy: [{ field: 'created_at', direction: 'desc' }] } }\n */\nexport interface RankingWindowIntent {\n\treadonly kind: 'window';\n\treadonly function: RankingWindowFunction;\n\treadonly field?: never;\n\t/** Result column alias (required) */\n\treadonly alias: string;\n\treadonly offset?: never;\n\treadonly defaultValue?: never;\n\t/** OVER clause specification */\n\treadonly over: {\n\t\treadonly partitionBy?: readonly string[] | undefined;\n\t\treadonly orderBy?: readonly WindowOrderBy[] | undefined;\n\t};\n}\n\n/**\n * Window function intent (aggregate branch): SUM / AVG / COUNT / MIN / MAX — field required.\n *\n * @example\n * { kind: 'window', function: 'sum', field: 'amount', alias: 'running_total', over: { partitionBy: ['account_id'] } }\n */\n/**\n * Window function intent (aggregate branch): SUM / AVG / COUNT / MIN / MAX.\n * Field is required for sum/avg/min/max; COUNT omits field to produce COUNT(*) OVER (...).\n *\n * @example SUM with field\n * { kind: 'window', function: 'sum', field: 'amount', alias: 'running_total', over: { partitionBy: ['account_id'] } }\n * @example COUNT(*)\n * { kind: 'window', function: 'count', alias: 'total', over: {} }\n */\nexport interface AggregateWindowIntent {\n\treadonly kind: 'window';\n\treadonly function: AggregateWindowFunction;\n\t/**\n\t * Field to aggregate over.\n\t * Required for sum/avg/min/max. Omit (or undefined) for COUNT(*) OVER (...).\n\t */\n\treadonly field?: string | undefined;\n\t/** Result column alias (required) */\n\treadonly alias: string;\n\treadonly offset?: never;\n\treadonly defaultValue?: never;\n\t/** OVER clause specification */\n\treadonly over: {\n\t\treadonly partitionBy?: readonly string[] | undefined;\n\t\treadonly orderBy?: readonly WindowOrderBy[] | undefined;\n\t};\n}\n\n/**\n * Window function intent (offset branch): LAG / LEAD — field required, offset optional.\n *\n * @example\n * { kind: 'window', function: 'lag', field: 'salary', alias: 'prev_salary', over: { orderBy: [{ field: 'date', direction: 'asc' }] } }\n */\nexport interface OffsetWindowIntent {\n\treadonly kind: 'window';\n\treadonly function: OffsetWindowFunction;\n\t/** Field to access from the offset row (required for lag/lead) */\n\treadonly field: string;\n\t/** Result column alias (required) */\n\treadonly alias: string;\n\t/** Offset for lag/lead (default: 1 in PostgreSQL) */\n\treadonly offset?: number | undefined;\n\t/** Default value for lag/lead when row doesn't exist */\n\treadonly defaultValue?: unknown;\n\t/** OVER clause specification */\n\treadonly over: {\n\t\treadonly partitionBy?: readonly string[] | undefined;\n\t\treadonly orderBy?: readonly WindowOrderBy[] | undefined;\n\t};\n}\n\n/**\n * Window function intent for analytics over partitions.\n * Discriminated by function group:\n * - RankingWindowIntent: row_number / rank / dense_rank (no field)\n * - AggregateWindowIntent: sum / avg / count / min / max (field required)\n * - OffsetWindowIntent: lag / lead (field required, offset optional)\n */\nexport type WindowIntent =\n\t| RankingWindowIntent\n\t| AggregateWindowIntent\n\t| OffsetWindowIntent;\n\n/**\n * Ranking window functions (no field required)\n */\nexport type RankingWindowFunction = 'row_number' | 'rank' | 'dense_rank';\n\n/**\n * Aggregate window functions (field required)\n */\nexport type AggregateWindowFunction = 'sum' | 'avg' | 'count' | 'min' | 'max';\n\n/**\n * Offset window functions (field required, offset/default deferred)\n */\nexport type OffsetWindowFunction = 'lag' | 'lead';\n","/**\n * @module intent/recursive-intent\n * Recursive CTE intent types for hierarchical data traversal (RFC-001).\n */\n\nimport type { OrderByIntent } from './include-intent.js';\nimport type { WhereIntent } from './where-intent.js';\n\n// ============================================================================\n// Recursive CTE Intent - Hierarchical Data Traversal (RFC-001)\n// ============================================================================\n\n/**\n * Node ID expression for recursive CTE anchor.\n * Used to define the join key for recursive traversal.\n */\nexport type RecursiveNodeIdExpr =\n\t| { readonly kind: 'column'; readonly name: string; readonly as?: string }\n\t| {\n\t\t\treadonly kind: 'literal';\n\t\t\treadonly value: unknown;\n\t\t\treadonly as?: string;\n\t }\n\t| {\n\t\t\treadonly kind: 'binary';\n\t\t\treadonly left: RecursiveNodeIdExpr;\n\t\t\treadonly op: string;\n\t\t\treadonly right: RecursiveNodeIdExpr;\n\t\t\treadonly as?: string;\n\t };\n\n/**\n * Get the alias for a node ID expression.\n * Used by both planner and compiler for consistent CTE column naming.\n *\n * @param expr - The node ID expression\n * @returns The alias to use (explicit alias, column name, or 'node_id' fallback)\n */\nexport function getNodeIdAlias(expr: RecursiveNodeIdExpr): string {\n\tif (expr.as) return expr.as;\n\tif (expr.kind === 'column') return expr.name;\n\tif (expr.kind === 'literal') return 'node_id';\n\t// Binary expression needs explicit alias\n\treturn 'node_id';\n}\n\n/**\n * Adjacency-list traversal (self-referential table).\n * Example: roles.parent_id → roles.id\n */\nexport interface AdjacencyTraversal {\n\treadonly kind: 'adjacency';\n\n\t/** Table containing hierarchical data */\n\treadonly nodeTable: string;\n\n\t/** Primary key column (e.g., \"id\") */\n\treadonly nodeId: string;\n\n\t/** Foreign key pointing to parent (e.g., \"parent_id\") */\n\treadonly parentId: string;\n\n\t/** Traversal direction */\n\treadonly direction: 'descendants' | 'ancestors';\n\n\t/** Filter applied to each step (e.g., active = true) */\n\treadonly stepWhere?: WhereIntent;\n}\n\n/**\n * Edge-table traversal (separate join table).\n * Example: role_inheritance(from_role_id, to_role_id)\n */\nexport interface EdgeTableTraversal {\n\treadonly kind: 'edge-table';\n\n\t/** Node table containing hierarchical data */\n\treadonly nodeTable: string;\n\n\t/** Edge table containing relationships */\n\treadonly edgeTable: string;\n\n\t/** Primary key column in node table (e.g., \"id\") */\n\treadonly nodeId: string;\n\n\t/** Source column in edge table (e.g., \"from_role_id\") */\n\treadonly edgeFrom: string;\n\n\t/** Target column in edge table (e.g., \"to_role_id\") */\n\treadonly edgeTo: string;\n\n\t/** Traversal direction */\n\treadonly direction: 'out' | 'in' | 'both';\n\n\t/** Filter on edges (e.g., relationship_type = 'inheritance') */\n\treadonly edgeWhere?: WhereIntent;\n\n\t/** Filter on nodes (e.g., active = true) */\n\treadonly nodeWhere?: WhereIntent;\n\n\t/** Edge attributes to include in result */\n\treadonly edgeSelect?: readonly string[];\n\n\t/**\n\t * Hint for edge storage semantics (only affects `direction: 'both'`).\n\t *\n\t * - 'unknown' (default): Edges may exist in both directions (A→B and B→A).\n\t * Uses UNION (distinct) to avoid duplicates. Safe but slower.\n\t * - 'directed-only': Caller guarantees edges are stored once only.\n\t * Uses UNION ALL for performance. INCORRECT if duplicates exist.\n\t */\n\treadonly edgeStorageHint?: 'unknown' | 'directed-only';\n}\n\n/**\n * Custom traversal for complex cases (P2 escape hatch).\n */\nexport interface CustomTraversal {\n\treadonly kind: 'custom';\n\t/** Explicit step query builder - reserved for P2 */\n\treadonly stepBuilder?: unknown;\n}\n\n/**\n * Recursive traversal type union.\n */\nexport type RecursiveTraversal =\n\t| AdjacencyTraversal\n\t| EdgeTableTraversal\n\t| CustomTraversal;\n\n/**\n * Tracking options for recursive traversal.\n */\nexport interface RecursiveTrackOptions {\n\t/** Depth counter (starts at 0) */\n\treadonly depth?: {\n\t\treadonly as?: string; // Default: \"depth\"\n\t};\n\n\t/** Path tracking for cycle detection + debugging */\n\treadonly path?: {\n\t\t/** Columns to trace in path (default: nodeId only) */\n\t\treadonly by?: 'nodeId' | readonly string[];\n\t\t/** Result column name (default: \"path\") */\n\t\treadonly as?: string;\n\t\t/** Storage strategy (default: 'array' for PostgreSQL, 'string' for others) */\n\t\treadonly strategy?: 'array' | 'string';\n\t\t/** Separator for string strategy (default: '/') */\n\t\treadonly separator?: string;\n\t};\n\n\t/** Cycle detection marker */\n\treadonly isCycle?: {\n\t\treadonly as?: string; // Default: \"is_cycle\"\n\t};\n}\n\n/**\n * Join clause for CTE emit composition.\n * Allows joining the CTE result with additional tables for final projection.\n */\nexport interface EmitJoinClause {\n\t/** Table to join with */\n\treadonly table: string;\n\n\t/** Join type (default: 'inner') */\n\treadonly type?: 'inner' | 'left';\n\n\t/** Alias for this table (auto-generated if not provided) */\n\treadonly as?: string;\n\n\t/** Join condition */\n\treadonly on: {\n\t\t/** Column from CTE or previous joined table */\n\t\treadonly left: string;\n\t\t/** Column from this table */\n\t\treadonly right: string;\n\t};\n\n\t/** Columns to select from this table */\n\treadonly select?: readonly (\n\t\t| string\n\t\t| { readonly column: string; readonly as: string }\n\t)[];\n}\n\n/**\n * Emit options for recursive CTE final projection.\n */\nexport interface RecursiveEmitOptions {\n\t/** Fields to select from CTE */\n\treadonly select?: readonly string[];\n\t/** Filter on generated rows */\n\treadonly where?: WhereIntent;\n\t/** Ordering */\n\treadonly orderBy?: readonly OrderByIntent[];\n\t/** Join CTE result with additional tables for composition */\n\treadonly joinWith?: readonly EmitJoinClause[];\n\t/** Apply DISTINCT to final result */\n\treadonly distinct?: boolean;\n}\n\n/**\n * PostgreSQL-specific options for recursive CTE (capability-gated).\n */\nexport interface RecursiveAdvancedOptions {\n\t/**\n\t * Cycle detection strategy (adapter-specific implementation).\n\t * - 'error': Throw on cycle detection\n\t * - 'stop': Stop traversal at cycle (prune branch)\n\t * - 'mark': Add is_cycle column to results\n\t *\n\t * PostgreSQL 14+ uses native CYCLE clause.\n\t * Other adapters may use application-level detection.\n\t */\n\treadonly cycle?: 'error' | 'stop' | 'mark';\n\n\t/**\n\t * Traversal search order (adapter-specific implementation).\n\t * - 'depth': Depth-first search order\n\t * - 'breadth': Breadth-first search order\n\t *\n\t * PostgreSQL 14+ uses native SEARCH clause.\n\t * Other adapters may use ORDER BY on depth column.\n\t */\n\treadonly search?: 'depth' | 'breadth';\n}\n\n/**\n * Deduplication strategy for recursive CTE.\n *\n * - 'none': No dedup. May return same node multiple times via different paths.\n * Fastest. Use when you need all paths or when graph is known to be a tree.\n *\n * - 'final': One row per nodeId in final output.\n * Implemented via `DISTINCT ON (nodeId)` (PostgreSQL) or\n * `ROW_NUMBER() OVER (PARTITION BY nodeId)` fallback.\n * ⚠️ NOT the same as `query.distinct()` which dedupes on entire row!\n *\n * Note: 'global' (UNION instead of UNION ALL) was considered but not implemented.\n * 'final' provides the same end result with better performance characteristics.\n */\nexport type RecursiveDedupe = 'none' | 'final';\n\n/**\n * Recursive CTE intent for hierarchical data traversal.\n *\n * Key invariant: anchor and step MUST produce identical column shape.\n * The planner validates this and auto-injects nodeIdExpr.\n *\n * @see RFC-001 for detailed specification\n */\nexport interface RecursiveIntent {\n\treadonly type: 'recursive';\n\n\t/** CTE name for the recursive query */\n\treadonly cteName: string;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// START (anchor/seed)\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\treadonly start: {\n\t\t/** Source table for anchor query */\n\t\treadonly from: string;\n\n\t\t/** Filter for seed rows (e.g., where id = $userId) */\n\t\treadonly where?: WhereIntent;\n\n\t\t/**\n\t\t * REQUIRED: Expression for node ID. Auto-injected into select.\n\t\t * This ensures the recursive join always has the key column.\n\t\t */\n\t\treadonly nodeIdExpr: RecursiveNodeIdExpr;\n\n\t\t/** Additional fields to select (beyond nodeId) */\n\t\treadonly select?: readonly string[];\n\t};\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// TRAVERSAL\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Traversal configuration (adjacency-list or edge-table) */\n\treadonly traversal: RecursiveTraversal;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// TRACKING (system columns)\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Tracking options for depth, path, and cycle detection */\n\treadonly track?: RecursiveTrackOptions;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// SAFETY\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Maximum recursion depth (REQUIRED) */\n\treadonly maxDepth: number;\n\n\t/** Maximum rows (optional safety limit) */\n\treadonly maxRows?: number;\n\n\t/** Deduplication strategy */\n\treadonly dedupe?: RecursiveDedupe;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// EMIT (final projection)\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Final projection options */\n\treadonly emit?: RecursiveEmitOptions;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// ADVANCED OPTIONS (capability-gated, adapter-specific implementation)\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Advanced recursive options (cycle detection, search order) */\n\treadonly advancedOptions?: RecursiveAdvancedOptions;\n}\n","/**\n * NQL-origin SELECT function allowlist.\n *\n * This is the shared security gate for function names that originated in NQL\n * SELECT text and later cross the IntentAST-to-SQL adapter boundary. Extend it\n * only after adding compiler and adapter coverage proving the function is\n * intentionally supported for NQL-origin SELECT projections.\n */\nexport const NQL_SELECT_AGGREGATE_FUNCTIONS = [\n\t'count',\n\t'sum',\n\t'avg',\n\t'min',\n\t'max',\n] as const;\n\nexport const NQL_SELECT_JSON_FUNCTIONS = [\n\t'json_extract',\n\t'json_extract_text',\n\t'json_path',\n\t'json_path_text',\n] as const;\n\nexport const NQL_SELECT_VALUE_FUNCTIONS = [\n\t'coalesce',\n\t'lower',\n\t'now',\n\t'round',\n\t'upper',\n] as const;\n\nexport const NQL_SELECT_SCALAR_FUNCTIONS = [\n\t...NQL_SELECT_AGGREGATE_FUNCTIONS,\n\t...NQL_SELECT_JSON_FUNCTIONS,\n\t...NQL_SELECT_VALUE_FUNCTIONS,\n] as const;\n\nexport const NQL_SELECT_WINDOW_ONLY_FUNCTIONS = [\n\t'row_number',\n\t'rank',\n\t'dense_rank',\n\t'lag',\n\t'lead',\n] as const;\n\nexport const NQL_SELECT_WINDOW_FUNCTIONS = [\n\t...NQL_SELECT_WINDOW_ONLY_FUNCTIONS,\n\t'count',\n\t'sum',\n\t'avg',\n\t'min',\n\t'max',\n] as const;\n\nexport const NQL_SELECT_SCALAR_FUNCTION_ALLOWLIST: ReadonlySet<string> =\n\tnew Set(NQL_SELECT_SCALAR_FUNCTIONS);\n\nexport const NQL_SELECT_WINDOW_FUNCTION_ALLOWLIST: ReadonlySet<string> =\n\tnew Set(NQL_SELECT_WINDOW_FUNCTIONS);\n\nexport const NQL_SELECT_FUNCTION_ALLOWLIST: ReadonlySet<string> = new Set([\n\t...NQL_SELECT_SCALAR_FUNCTIONS,\n\t...NQL_SELECT_WINDOW_FUNCTIONS,\n]);\n\ntype NqlSelectScalarFunction = (typeof NQL_SELECT_SCALAR_FUNCTIONS)[number];\ntype NqlSelectWindowFunction = (typeof NQL_SELECT_WINDOW_FUNCTIONS)[number];\n\nexport function isNqlSelectScalarFunctionAllowed(\n\tname: string,\n): name is NqlSelectScalarFunction {\n\treturn NQL_SELECT_SCALAR_FUNCTION_ALLOWLIST.has(name.toLowerCase());\n}\n\nexport function isNqlSelectFunctionAllowed(name: string): boolean {\n\treturn NQL_SELECT_FUNCTION_ALLOWLIST.has(name.toLowerCase());\n}\n\nexport function isNqlSelectWindowFunctionAllowed(\n\tname: string,\n): name is NqlSelectWindowFunction {\n\treturn NQL_SELECT_WINDOW_FUNCTION_ALLOWLIST.has(name.toLowerCase());\n}\n","/**\n * @module intent/type-guards\n * Type guard functions for all intent AST types.\n */\n\nimport type {\n\tAggregateWindowFunction,\n\tCoalesceExpressionIntent,\n\tColumnAliasIntent,\n\tExpressionIntent,\n\tOffsetWindowFunction,\n\tRankingWindowFunction,\n\tRawExpressionIntent,\n\tRelationColumnIntent,\n\tWindowFunction,\n\tWindowIntent,\n} from './expression-intent.js';\nimport type {\n\tDeleteIntent,\n\tInsertIntent,\n\tMutationIntent,\n\tUpdateIntent,\n\tUpsertIntent,\n} from './mutation-intent.js';\nimport type { QueryIntent } from './query-intent.js';\nimport type {\n\tAdjacencyTraversal,\n\tCustomTraversal,\n\tEdgeTableTraversal,\n\tRecursiveIntent,\n\tRecursiveTraversal,\n} from './recursive-intent.js';\nimport type {\n\tSelectAggregateIntent,\n\tSelectAllIntent,\n\tSelectFieldsIntent,\n\tSelectIntent,\n\tSelectWithExpressionsIntent,\n} from './select-intent.js';\nimport type {\n\tSubqueryRefIntent,\n\tWhereAndIntent,\n\tWhereAnyIntent,\n\tWhereComparisonIntent,\n\tWhereExistsIntent,\n\tWhereInIntent,\n\tWhereIntent,\n\tWhereLikeIntent,\n\tWhereNotExistsIntent,\n\tWhereNotIntent,\n\tWhereNullIntent,\n\tWhereOrIntent,\n\tWhereRangeIntent,\n\tWhereRelationFilterIntent,\n\tWhereSubqueryIntent,\n} from './where-intent.js';\n\n// ============================================================================\n// Window Intent Type Guards\n// ============================================================================\n\n/**\n * Check if an intent is a window function intent\n */\nexport function isWindowIntent(intent: unknown): intent is WindowIntent {\n\treturn (\n\t\ttypeof intent === 'object' &&\n\t\tintent !== null &&\n\t\t'kind' in intent &&\n\t\t(intent as Record<string, unknown>).kind === 'window'\n\t);\n}\n\n/**\n * Check if a window function requires a field (aggregate or offset functions)\n */\nexport function isAggregateWindowFunction(\n\tfn: WindowFunction,\n): fn is AggregateWindowFunction | OffsetWindowFunction {\n\treturn ['sum', 'avg', 'count', 'min', 'max', 'lag', 'lead'].includes(fn);\n}\n\n/**\n * Check if a window function is a ranking function (no field required)\n */\nexport function isRankingWindowFunction(\n\tfn: WindowFunction,\n): fn is RankingWindowFunction {\n\treturn ['row_number', 'rank', 'dense_rank'].includes(fn);\n}\n\n// ============================================================================\n// Where Intent Type Guards\n// ============================================================================\n\n/**\n * Check if a where intent is a comparison\n */\nexport function isWhereComparison(\n\twhere: WhereIntent,\n): where is WhereComparisonIntent {\n\treturn where.kind === 'comparison';\n}\n\n/**\n * Check if a where intent is a like filter\n */\nexport function isWhereLike(where: WhereIntent): where is WhereLikeIntent {\n\treturn where.kind === 'like';\n}\n\n/**\n * Check if a where intent is a subquery filter\n */\nexport function isWhereSubquery(\n\twhere: WhereIntent,\n): where is WhereSubqueryIntent {\n\treturn where.kind === 'subquery';\n}\n\n/**\n * Check if a value is a subquery ref (column reference in subquery)\n */\nexport function isSubqueryRef(value: unknown): value is SubqueryRefIntent {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'kind' in value &&\n\t\t(value as Record<string, unknown>).kind === 'ref'\n\t);\n}\n\n/**\n * Check if a where intent is an in filter\n */\nexport function isWhereIn(where: WhereIntent): where is WhereInIntent {\n\treturn where.kind === 'in';\n}\n\n/**\n * Check if a where intent is an any filter (= ANY($N::type[]))\n */\nexport function isWhereAny(where: WhereIntent): where is WhereAnyIntent {\n\treturn where.kind === 'any';\n}\n\n/**\n * Check if a where intent is a null filter\n */\nexport function isWhereNull(where: WhereIntent): where is WhereNullIntent {\n\treturn where.kind === 'null';\n}\n\n/**\n * Check if a where intent is a range filter (PostgreSQL range types)\n */\nexport function isWhereRange(where: WhereIntent): where is WhereRangeIntent {\n\treturn where.kind === 'range';\n}\n\n/**\n * Check if a where intent is a logical AND\n */\nexport function isWhereAnd(where: WhereIntent): where is WhereAndIntent {\n\treturn where.kind === 'and';\n}\n\n/**\n * Check if a where intent is a logical OR\n */\nexport function isWhereOr(where: WhereIntent): where is WhereOrIntent {\n\treturn where.kind === 'or';\n}\n\n/**\n * Check if a where intent is a logical NOT\n */\nexport function isWhereNot(where: WhereIntent): where is WhereNotIntent {\n\treturn where.kind === 'not';\n}\n\n/**\n * Check if a where intent is an exists filter\n */\nexport function isWhereExists(where: WhereIntent): where is WhereExistsIntent {\n\treturn where.kind === 'exists';\n}\n\n/**\n * Check if a where intent is a not exists filter\n */\nexport function isWhereNotExists(\n\twhere: WhereIntent,\n): where is WhereNotExistsIntent {\n\treturn where.kind === 'notExists';\n}\n\n/**\n * Check if a where intent is a relation filter\n */\nexport function isWhereRelationFilter(\n\twhere: WhereIntent,\n): where is WhereRelationFilterIntent {\n\treturn where.kind === 'relationFilter';\n}\n\n/**\n * Check if a where intent is any relation-based filter\n */\nexport function isWhereRelationBased(\n\twhere: WhereIntent,\n): where is\n\t| WhereExistsIntent\n\t| WhereNotExistsIntent\n\t| WhereRelationFilterIntent {\n\treturn (\n\t\twhere.kind === 'exists' ||\n\t\twhere.kind === 'notExists' ||\n\t\twhere.kind === 'relationFilter'\n\t);\n}\n\n/**\n * Check if a where intent is a logical operator (and/or/not)\n */\nexport function isWhereLogical(\n\twhere: WhereIntent,\n): where is WhereAndIntent | WhereOrIntent | WhereNotIntent {\n\treturn where.kind === 'and' || where.kind === 'or' || where.kind === 'not';\n}\n\n// ============================================================================\n// Select Intent Type Guards\n// ============================================================================\n\n/**\n * Check if a select intent selects all columns\n */\nexport function isSelectAll(select: SelectIntent): select is SelectAllIntent {\n\treturn select.type === 'all';\n}\n\n/**\n * Check if a select intent selects specific fields\n */\nexport function isSelectFields(\n\tselect: SelectIntent,\n): select is SelectFieldsIntent {\n\treturn select.type === 'fields';\n}\n\n/**\n * Check if a select intent is an aggregate select\n */\nexport function isSelectAggregate(\n\tselect: SelectIntent,\n): select is SelectAggregateIntent {\n\treturn select.type === 'aggregate';\n}\n\n/**\n * Check if a select intent has expressions\n */\nexport function isSelectWithExpressions(\n\tselect: SelectIntent,\n): select is SelectWithExpressionsIntent {\n\treturn select.type === 'expressions';\n}\n\n// ============================================================================\n// Expression Intent Type Guards\n// ============================================================================\n\n/**\n * Check if an expression is a COALESCE expression\n */\nexport function isCoalesceExpression(\n\texpr: ExpressionIntent,\n): expr is CoalesceExpressionIntent {\n\treturn expr.kind === 'coalesce';\n}\n\n/**\n * Check if an expression is a raw SQL expression\n */\nexport function isRawExpression(\n\texpr: ExpressionIntent,\n): expr is RawExpressionIntent {\n\treturn expr.kind === 'raw';\n}\n\n/**\n * Check if an expression is a column alias expression\n */\nexport function isColumnAliasExpression(\n\texpr: ExpressionIntent,\n): expr is ColumnAliasIntent {\n\treturn expr.kind === 'columnAlias';\n}\n\n/**\n * Check if an expression is a relation column expression\n */\nexport function isRelationColumnExpression(\n\texpr: ExpressionIntent,\n): expr is RelationColumnIntent {\n\treturn expr.kind === 'relationColumn';\n}\n\n// ============================================================================\n// Recursive CTE Type Guards\n// ============================================================================\n\n/**\n * Check if a traversal is adjacency-list based\n */\nexport function isAdjacencyTraversal(\n\ttraversal: RecursiveTraversal,\n): traversal is AdjacencyTraversal {\n\treturn traversal.kind === 'adjacency';\n}\n\n/**\n * Check if a traversal is edge-table based\n */\nexport function isEdgeTableTraversal(\n\ttraversal: RecursiveTraversal,\n): traversal is EdgeTableTraversal {\n\treturn traversal.kind === 'edge-table';\n}\n\n/**\n * Check if a traversal is custom\n */\nexport function isCustomTraversal(\n\ttraversal: RecursiveTraversal,\n): traversal is CustomTraversal {\n\treturn traversal.kind === 'custom';\n}\n\n/**\n * Check if an intent is a recursive CTE intent\n */\nexport function isRecursiveIntent(\n\tintent: QueryIntent | RecursiveIntent,\n): intent is RecursiveIntent {\n\treturn intent.type === 'recursive';\n}\n\n// ============================================================================\n// Mutation Intent Type Guards\n// ============================================================================\n\n/**\n * Check if an intent is an insert intent\n */\nexport function isInsertIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is InsertIntent {\n\treturn intent.type === 'insert';\n}\n\n/**\n * Check if an intent is an update intent\n */\nexport function isUpdateIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is UpdateIntent {\n\treturn intent.type === 'update';\n}\n\n/**\n * Check if an intent is a delete intent\n */\nexport function isDeleteIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is DeleteIntent {\n\treturn intent.type === 'delete';\n}\n\n/**\n * Check if an intent is an upsert intent (DX-026)\n */\nexport function isUpsertIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is UpsertIntent {\n\treturn intent.type === 'upsert';\n}\n\n/**\n * Check if an intent is any mutation intent\n */\nexport function isMutationIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is MutationIntent {\n\treturn (\n\t\tintent.type === 'insert' ||\n\t\tintent.type === 'insert_from' ||\n\t\tintent.type === 'upsert_from' ||\n\t\tintent.type === 'update' ||\n\t\tintent.type === 'batchUpdate' ||\n\t\tintent.type === 'delete' ||\n\t\tintent.type === 'upsert'\n\t);\n}\n","/**\n * @module intent/where-intent\n * Where intent types for filter conditions.\n */\n\nimport type { ColumnListInput } from '../column-list.js';\nimport type { RangeValue } from '../shared/utils.js';\nimport type { ExpressionIntent, ParamIntent } from './expression-intent.js';\nimport type { ComparisonOperator, NullOperator } from './operators.js';\nimport type { QueryIntent } from './query-intent.js';\nimport type { RecursiveExistsOptions } from './recursive-types.js';\n\nexport type { RangeValue };\n\n// ============================================================================\n// Where Intent - Filter Conditions\n// ============================================================================\n\n/**\n * Typed field reference for cross-table column comparisons in relation filters.\n *\n * When using aliased relation filters like `some(orders as o, o.total > minOrder)`,\n * the RHS `minOrder` is a reference to the parent table's column, not a literal value.\n * FieldRef captures this distinction so the adapter can compile it as a column reference\n * instead of a parameterized value.\n *\n * @example\n * // some(rel as r, r.col > bareCol) → value: { kind: 'fieldRef', column: 'bareCol', scope: 'outer' }\n * // some(rel as r, r.col > r.otherCol) → value: { kind: 'fieldRef', column: 'otherCol', scope: 'inner' }\n * // some(a as x, some(b as y, y.f > x.g)) → value: { kind: 'fieldRef', column: 'g', scope: 'outer', alias: 'x' }\n */\nexport interface FieldRef {\n\treadonly kind: 'fieldRef';\n\treadonly column: string;\n\treadonly scope: 'inner' | 'outer';\n\t/** Named alias for outer scope (when referencing a specific outer alias in nested filters) */\n\treadonly alias?: string;\n}\n\n/**\n * Type guard for FieldRef values\n */\nexport function isFieldRef(value: unknown): value is FieldRef {\n\treturn (\n\t\tvalue !== null &&\n\t\ttypeof value === 'object' &&\n\t\t(value as Record<string, unknown>).kind === 'fieldRef'\n\t);\n}\n\nexport interface WhereComparisonIntent {\n\treadonly kind: 'comparison';\n\treadonly field: string;\n\treadonly operator: ComparisonOperator;\n\treadonly value: unknown;\n\t/** JSON path extraction before comparison (e.g., data->'key' = 'val') */\n\treadonly jsonPath?: readonly (string | ParamIntent)[];\n\t/** JSON extraction mode: 'json' = ->, 'text' = ->> */\n\treadonly jsonMode?: 'json' | 'text';\n}\n\n/**\n * String filter: field like pattern\n */\nexport interface WhereLikeIntent {\n\treadonly kind: 'like';\n\treadonly field: string;\n\treadonly pattern: string | ParamIntent;\n\t/** Case-insensitive matching */\n\treadonly caseInsensitive?: boolean;\n\t/** Escape character for LIKE pattern (e.g. '\\\\' to escape _ and %) */\n\treadonly escape?: string;\n}\n\n/**\n * Array filter: field in [values]\n */\n/**\n * Array filter (values branch): field IN (v1, v2, ...)\n */\nexport interface WhereInValueIntent {\n\treadonly kind: 'in';\n\treadonly field: string;\n\treadonly values: readonly unknown[];\n\treadonly subquery?: never;\n\treadonly not?: boolean;\n}\n\n/**\n * Array filter (subquery branch): field IN (SELECT ...)\n */\nexport interface WhereInSubqueryIntent {\n\treadonly kind: 'in';\n\treadonly field: string;\n\treadonly subquery: QueryIntent;\n\treadonly values?: never;\n\treadonly not?: boolean;\n}\n\n/**\n * Array filter: field in [values] OR field IN (subquery)\n * XOR: exactly one of `values` or `subquery` must be present.\n */\nexport type WhereInIntent = WhereInValueIntent | WhereInSubqueryIntent;\n\n/**\n * Array membership filter using PostgreSQL ANY() operator.\n * Compiles to: \"col\" = ANY($N::type[])\n */\nexport interface WhereAnyIntent {\n\treadonly kind: 'any';\n\treadonly field: string;\n\treadonly values: readonly unknown[] | ParamIntent;\n}\n\n/**\n * Operand accepted by range WHERE filters.\n * - RangeValue: for range-to-range operators (overlaps, contains, containedBy)\n * - string: ISO date/timestamp literals (e.g. '2025-01-15', '2025-01-15T08:00:00Z')\n * - number: integer/numeric point values (e.g. 50000 for salary_range @> 50000)\n * - boolean: rarely used but valid PostgreSQL range operand\n */\nexport type RangeOperand = RangeValue | string | number | boolean;\n\n/**\n * Range operator for PostgreSQL range types.\n * - overlaps: && (ranges have common points)\n * - contains: @> (range contains value or range)\n * - containedBy: <@ (range is contained by another range)\n */\nexport type RangeOperator = 'overlaps' | 'contains' | 'containedBy' | 'between';\n\n/**\n * Range filter: field overlaps/contains/containedBy range value\n * PostgreSQL range types: daterange, tsrange, tstzrange, int4range, int8range, numrange\n *\n * @example\n * // Check if booking dates overlap a period\n * { kind: 'range', field: 'dates', operator: 'overlaps', value: { lower: '2025-01-15', upper: '2025-01-20' } }\n *\n * // Check if salary range contains a value\n * { kind: 'range', field: 'salary_range', operator: 'contains', value: 50000 }\n */\n/**\n * Range filter: field overlaps/contains/containedBy range value\n * PostgreSQL range types: daterange, tsrange, tstzrange, int4range, int8range, numrange\n *\n * @example\n * // Check if booking dates overlap a period\n * { kind: 'range', field: 'dates', operator: 'overlaps', value: { lower: '2025-01-15', upper: '2025-01-20' } }\n *\n * // Check if salary range contains a value\n * { kind: 'range', field: 'salary_range', operator: 'contains', value: 50000 }\n */\nexport interface WhereRangeIntent {\n\treadonly kind: 'range';\n\treadonly field: string;\n\treadonly operator: RangeOperator;\n\t/** Operand: RangeValue for range-to-range ops, or a scalar (string | number | boolean) for point-in-range ops */\n\treadonly value: RangeOperand;\n}\n\n/**\n * Null filter: field is null / is not null\n */\nexport interface WhereNullIntent {\n\treadonly kind: 'null';\n\treadonly field: string;\n\treadonly operator: NullOperator;\n}\n\n/**\n * Logical AND: all conditions must match\n */\nexport interface WhereAndIntent {\n\treadonly kind: 'and';\n\treadonly conditions: readonly WhereIntent[];\n}\n\n/**\n * Logical OR: at least one condition must match\n */\nexport interface WhereOrIntent {\n\treadonly kind: 'or';\n\treadonly conditions: readonly WhereIntent[];\n}\n\n/**\n * Logical NOT: condition must not match\n */\nexport interface WhereNotIntent {\n\treadonly kind: 'not';\n\treadonly condition: WhereIntent;\n}\n\n/**\n * Relation exists filter: filter by existence of related records\n * Critical for Q1 golden test - enables EXISTS subquery strategy\n *\n * @example\n * // Find users who have at least one published post\n * { kind: 'exists', relation: 'posts', where: { kind: 'comparison', field: 'status', operator: 'eq', value: 'published' } }\n */\nexport interface WhereExistsIntent {\n\treadonly kind: 'exists';\n\t/** Relation name to check existence */\n\treadonly relation: string;\n\t/** Optional filter on related records */\n\treadonly where?: WhereIntent;\n\t/**\n\t * Recursive options for ancestor/descendant existence checks.\n\t * When present, generates a recursive CTE instead of simple EXISTS.\n\t */\n\treadonly recursive?: RecursiveExistsOptions;\n\t/**\n\t * Optional JOIN declarations inside the EXISTS subquery.\n\t * Keys are relation names (used as aliases), values specify join type.\n\t * Enables filtering on joined tables inside the subquery.\n\t *\n\t * @example\n\t * exists('callers', {\n\t * include: { callerFile: { join: 'inner' } },\n\t * where: eq('callerFile.project_id', projectId)\n\t * })\n\t */\n\treadonly include?: Readonly<Record<string, { join?: 'inner' | 'left' }>>;\n}\n\n/**\n * Relation not exists filter: filter by absence of related records\n *\n * @example\n * // Find users who have no posts\n * { kind: 'notExists', relation: 'posts' }\n */\nexport interface WhereNotExistsIntent {\n\treadonly kind: 'notExists';\n\t/** Relation name to check absence */\n\treadonly relation: string;\n\t/** Optional filter on related records */\n\treadonly where?: WhereIntent;\n\t/**\n\t * Recursive options for ancestor/descendant absence checks.\n\t * When present, generates a recursive CTE instead of simple NOT EXISTS.\n\t */\n\treadonly recursive?: RecursiveExistsOptions;\n\t/**\n\t * Optional JOIN declarations inside the NOT EXISTS subquery.\n\t * Keys are relation names (used as aliases), values specify join type.\n\t * Enables filtering on joined tables inside the subquery.\n\t *\n\t * @example\n\t * notExists('callers', {\n\t * include: { callerFile: { join: 'inner' } },\n\t * where: eq('callerFile.project_id', projectId)\n\t * })\n\t */\n\treadonly include?: Readonly<Record<string, { join?: 'inner' | 'left' }>>;\n}\n\n/**\n * Raw EXISTS subquery filter using an arbitrary QueryIntent.\n * Unlike WhereExistsIntent (which uses FK-resolved relation names),\n * this wraps a fully-specified subquery for correlated EXISTS checks.\n *\n * @example\n * // EXISTS (SELECT 1 FROM symbols WHERE symbols.id = calls.symbol_id AND ...)\n * exists(subquery('symbols').where(eq('id', ref('calls.symbol_id'))))\n */\nexport interface WhereRawExistsIntent {\n\treadonly kind: 'rawExists';\n\t/** The subquery producing rows for the EXISTS check */\n\treadonly subquery: QueryIntent;\n}\n\n/**\n * Raw NOT EXISTS subquery filter using an arbitrary QueryIntent.\n *\n * @example\n * // NOT EXISTS (SELECT 1 FROM symbols WHERE ...)\n * notExists(subquery('symbols').where(...))\n */\nexport interface WhereRawNotExistsIntent {\n\treadonly kind: 'rawNotExists';\n\t/** The subquery producing rows for the NOT EXISTS check */\n\treadonly subquery: QueryIntent;\n}\n\n/**\n * Relation filter: filter parent by conditions on related records\n * More flexible than exists - allows filtering by related record attributes\n *\n * @example\n * // Find users whose latest post was created in 2024\n * { kind: 'relationFilter', relation: 'posts', where: {...}, mode: 'some' }\n */\nexport interface WhereRelationFilterIntent {\n\treadonly kind: 'relationFilter';\n\t/**\n\t * Relation path for filtering.\n\t * - Single relation: 'posts' or ['posts']\n\t * - Multi-hop (SPEC-002): ['author', 'company'] for author.company traversal\n\t */\n\treadonly relation: string | readonly string[];\n\t/** Filter conditions on related records */\n\treadonly where: WhereIntent;\n\t/**\n\t * Match mode:\n\t * - 'some': At least one related record matches (default)\n\t * - 'every': All related records match\n\t * - 'none': No related records match\n\t */\n\treadonly mode: 'some' | 'every' | 'none';\n\t/** Optional alias for complex conditions (SPEC-002) */\n\treadonly alias?: string | undefined;\n\t/**\n\t * Optional display/debug metadata for pre-resolved relation filters.\n\t * These fields are not trusted for compilation. Internal compiler paths that\n\t * prove a virtual source maps to a real model relation use a module-private\n\t * proof payload instead.\n\t */\n\treadonly targetTable?: string | undefined;\n\treadonly sourceColumn?: ColumnListInput;\n\treadonly targetColumn?: ColumnListInput;\n}\n\n// ============================================================================\n// Subquery Intent - Scalar Subquery in WHERE\n// ============================================================================\n\n/**\n * Reference to a parent query column in a subquery.\n * Used to create correlated subqueries.\n *\n * The `outer` field is a discriminator set by `outerRef()` in\n * `@dbsp/core` to distinguish a genuine outer-query reference from an\n * inner `ref()` expression (RefExpressionIntent), which has the same\n * structural shape `{ kind: 'ref', column }`. Converters that need to\n * detect correlated subqueries check `outer === true`; an intent built\n * without `outer` (i.e. a plain `{ kind: 'ref', column }`) is treated\n * as a non-correlated inner expression reference.\n *\n * @example\n * // Outer reference produced by outerRef('id'):\n * { kind: 'ref', column: 'id', outer: true }\n *\n * // Inner column reference (not a correlated outer ref):\n * { kind: 'ref', column: 'id' } // outer is absent / undefined\n */\nexport interface SubqueryRefIntent {\n\treadonly kind: 'ref';\n\t/** Column name or aliased column (e.g., 'id' or 't0.id') */\n\treadonly column: string;\n\t/**\n\t * Discriminator that marks this as a genuine outer-query reference\n\t * (set by `outerRef()`). Absent on plain inner expression refs.\n\t * Optional so that existing raw intents built per the type without\n\t * this field remain valid (non-breaking addition).\n\t */\n\treadonly outer?: true;\n}\n\n/**\n * Subquery intent for scalar subquery comparisons.\n * Produces correlated subqueries in SQL.\n *\n * @example\n * // Find products where price equals max price of category\n * {\n * kind: 'subquery',\n * field: 'price',\n * operator: 'eq',\n * subquery: { from: 'products', select: { kind: 'aggregate', fn: 'max', field: 'price' } }\n * }\n */\nexport interface WhereSubqueryIntent {\n\treadonly kind: 'subquery';\n\t/** Field to compare on the parent query */\n\treadonly field: string;\n\t/** Comparison operator */\n\treadonly operator: ComparisonOperator;\n\t/** Subquery producing scalar value */\n\treadonly subquery: QueryIntent;\n}\n\n/**\n * Scalar subquery intent - produces a single value.\n * Simplified QueryIntent for subquery context.\n */\n/** @deprecated Use QueryIntent instead — subqueries are full queries with contextual validation */\nexport type ScalarSubqueryIntent = QueryIntent;\n\n// ============================================================================\n// JSON/JSONB WHERE Intents (E13)\n// ============================================================================\n\n/**\n * JSON containment filter: col @> value or col <@ value.\n * @example { kind: 'jsonContains', field: 'data', value: '{\"active\":true}', reversed: false }\n * → WHERE \"data\" @> $1\n */\nexport interface WhereJsonContainsIntent {\n\treadonly kind: 'jsonContains';\n\treadonly field: string;\n\treadonly value: unknown;\n\t/** false = @> (field contains value), true = <@ (field contained by value) */\n\treadonly reversed: boolean;\n}\n\n/**\n * JSON key existence filter: col ? 'key'.\n * @example { kind: 'jsonExists', field: 'data', key: 'email' }\n * → WHERE \"data\" ? $1\n */\nexport interface WhereJsonExistsIntent {\n\treadonly kind: 'jsonExists';\n\treadonly field: string;\n\treadonly key: string | ParamIntent;\n}\n\n/** WHERE clause using a custom expression with comparison */\nexport interface WhereExpressionIntent {\n\treadonly kind: 'expression';\n\treadonly expr: ExpressionIntent;\n\treadonly operator: ComparisonOperator;\n\treadonly value: unknown;\n}\n\n/**\n * Where intent - filter conditions union type\n * Discriminated union using 'kind' field\n */\nexport type WhereIntent =\n\t| WhereComparisonIntent\n\t| WhereLikeIntent\n\t| WhereInIntent\n\t| WhereAnyIntent\n\t| WhereNullIntent\n\t| WhereRangeIntent\n\t| WhereAndIntent\n\t| WhereOrIntent\n\t| WhereNotIntent\n\t| WhereExistsIntent\n\t| WhereNotExistsIntent\n\t| WhereRawExistsIntent\n\t| WhereRawNotExistsIntent\n\t| WhereRelationFilterIntent\n\t| WhereSubqueryIntent\n\t| WhereJsonContainsIntent\n\t| WhereJsonExistsIntent\n\t| WhereExpressionIntent;\n","import { toColumnList } from './column-list.js';\nimport type { TableIR } from './model-ir.js';\n\n/**\n * Dialect-neutral json_agg order intent.\n *\n * `fallback` means the table has no declared primary key, so adapters should\n * realize the all-column deterministic fallback in their own dialect.\n */\nexport interface JsonAggOrderKey {\n\treadonly columns: readonly string[];\n\treadonly fallback: boolean;\n}\n\nexport function resolveJsonAggOrderKey(table: TableIR): JsonAggOrderKey {\n\tconst pkColumns = toColumnList(table.primaryKey);\n\treturn pkColumns.length > 0\n\t\t? { columns: pkColumns, fallback: false }\n\t\t: { columns: table.columns.map((col) => col.name), fallback: true };\n}\n","import type { ModelIR } from './model-ir.js';\n\n/**\n * Canonical shape of a `schema()` factory result as produced by\n * `createOrm({schema: ...})` callers and consumed by tooling\n * (cli, gui sidecar, mcp-server).\n *\n * ARCH-005: Schema type from schema() function.\n * Contains the definition, pre-computed ModelIR, and table names.\n */\nexport interface LoadedSchema {\n\treadonly definition: Record<string, unknown>;\n\treadonly model: ModelIR;\n\treadonly tableNames: readonly string[];\n}\n\n/**\n * Runtime type guard for `LoadedSchema` — verifies the object has\n * `definition`, `model` (with nested `tables` + `relations`), and\n * `tableNames` as an array. Structural check only — does not validate\n * the inner values of any field.\n *\n * Type guard for ARCH-005 schema() output.\n */\nexport function isValidSchema(schema: unknown): schema is LoadedSchema {\n\tif (\n\t\ttypeof schema !== 'object' ||\n\t\tschema === null ||\n\t\t!('model' in schema) ||\n\t\t!('definition' in schema) ||\n\t\t!('tableNames' in schema)\n\t) {\n\t\treturn false;\n\t}\n\tconst s = schema as LoadedSchema;\n\tif (typeof s.model !== 'object' || s.model === null) return false;\n\tif (!('tables' in s.model) || !('relations' in s.model)) return false;\n\tif (!Array.isArray(s.tableNames)) return false;\n\t// Validate required ModelIR methods are present\n\tif (typeof s.model.getTable !== 'function') return false;\n\tif (typeof s.model.getRelation !== 'function') return false;\n\tif (typeof s.model.getRelationsFrom !== 'function') return false;\n\tif (typeof s.model.getRelationsTo !== 'function') return false;\n\tif (typeof s.model.isAmbiguous !== 'function') return false;\n\treturn true;\n}\n","import type { ColumnListInput } from './column-list.js';\nimport { toColumnList } from './column-list.js';\nimport type { ForeignKeyIR, RelationIR } from './model-ir.js';\n\nconst DEFAULT_REFERENCED_COLUMN = 'id';\n\nexport type RelationKeyDirection = 'belongsTo' | 'inverse';\n\nexport type RelationKeyForeignKey = Pick<ForeignKeyIR, 'references'> & {\n\treadonly columns: ColumnListInput;\n};\n\nexport interface RelationKeyBuilderOptions {\n\treadonly defaultReferencedColumn?: string;\n\treadonly foreignKeyShape?: ColumnListInput;\n\treadonly referencedKeyShape?: ColumnListInput;\n}\n\nexport type RelationKeyFields = Pick<RelationIR, 'foreignKey'> &\n\tPartial<Pick<RelationIR, 'sourceKey' | 'targetKey'>>;\n\nfunction columnListValue(\n\tcolumns: readonly string[],\n\tshape?: ColumnListInput,\n): string | readonly string[] {\n\tif (shape !== undefined && typeof shape !== 'string') return columns;\n\treturn columns.length === 1 ? columns[0]! : columns;\n}\n\n/**\n * Build RelationIR key fields from a foreign key while preserving both sides.\n *\n * The local FK columns and referenced columns are normalized with toColumnList()\n * so composite keys cannot silently degrade to their first column.\n */\nexport function buildRelationKeyFields(\n\tfk: RelationKeyForeignKey,\n\tdirection: RelationKeyDirection,\n\toptions: RelationKeyBuilderOptions = {},\n): RelationKeyFields {\n\tconst foreignKeyColumns = toColumnList(fk.columns);\n\tconst referencedColumns = toColumnList(fk.references.columns);\n\n\tif (foreignKeyColumns.length !== referencedColumns.length) {\n\t\tthrow new Error(\n\t\t\t`Foreign key column count (${foreignKeyColumns.length}) must match referenced column count (${referencedColumns.length})`,\n\t\t);\n\t}\n\n\tconst foreignKey = columnListValue(\n\t\tforeignKeyColumns,\n\t\toptions.foreignKeyShape,\n\t);\n\tconst defaultReferencedColumn =\n\t\toptions.defaultReferencedColumn ?? DEFAULT_REFERENCED_COLUMN;\n\tconst isImplicitDefaultReference =\n\t\toptions.referencedKeyShape === undefined &&\n\t\treferencedColumns.length === 1 &&\n\t\treferencedColumns[0] === defaultReferencedColumn;\n\n\tif (!isImplicitDefaultReference) {\n\t\tconst referencedKey = columnListValue(\n\t\t\treferencedColumns,\n\t\t\toptions.referencedKeyShape,\n\t\t);\n\t\tif (direction === 'belongsTo') {\n\t\t\treturn { foreignKey, targetKey: referencedKey };\n\t\t}\n\t\treturn { foreignKey, sourceKey: referencedKey };\n\t}\n\n\treturn { foreignKey };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/column-list.ts","../src/intent/expression-intent.ts","../src/intent/recursive-intent.ts","../src/intent/select-function-allowlist.ts","../src/intent/type-guards.ts","../src/intent/where-intent.ts","../src/json-agg-order-key.ts","../src/loaded-schema.ts","../src/relation-key-fields.ts"],"sourcesContent":["/**\n * Shared relation-key normalizer.\n *\n * Relation metadata accepts either a single column or a composite column list.\n * Readers should normalize once and keep the full list instead of degrading to\n * the first column.\n */\nexport type ColumnListInput = string | readonly string[] | undefined;\n\nexport function toColumnList(key: ColumnListInput): readonly string[] {\n\tif (key === undefined) return [];\n\treturn typeof key === 'string' ? [key] : key;\n}\n","/**\n * @module intent/expression-intent\n * Expression intent types for computed/derived values in SELECT.\n */\n\nimport type { QueryIntent } from './query-intent.js';\nimport type { AggregateFunction } from './select-intent.js';\nimport type { WhereIntent } from './where-intent.js';\n\n// ============================================================================\n// Expression Intents - Computed/Derived Values\n// ============================================================================\n\n/**\n * COALESCE expression: returns first non-null value from a list of fields\n * @example { kind: 'coalesce', fields: ['name_fr', 'name_en'], as: 'display_name' }\n * → COALESCE(name_fr, name_en) AS display_name\n */\nexport interface CoalesceExpressionIntent {\n\treadonly kind: 'coalesce';\n\t/** Fields to check in order (first non-null wins) */\n\treadonly fields: readonly string[];\n\t/** Required alias for the result column */\n\treadonly as: string;\n}\n\n/**\n * Raw SQL expression (escape hatch for advanced use cases)\n * @example { kind: 'raw', sql: 'NOW()', as: 'current_time' }\n * → NOW() AS current_time\n * @warning Use sparingly - bypasses type safety and SQL injection protection\n */\nexport interface RawExpressionIntent {\n\treadonly kind: 'raw';\n\t/** Raw SQL fragment (must be safe, no user input!) */\n\treadonly sql: string;\n\t/** Required alias for the result column */\n\treadonly as: string;\n}\n\n/**\n * Simple column expression: just a column reference, optionally aliased\n * Used by NQL when a column is selected without modification\n * @example { kind: 'column', column: 'name' }\n * → SELECT \"name\"\n * @example { kind: 'column', column: 'name', as: 'userName' }\n * → SELECT \"name\" AS \"userName\"\n */\nexport interface ColumnExpressionIntent {\n\treadonly kind: 'column';\n\t/** Column name to select */\n\treadonly column: string;\n\t/** Optional alias for the result column */\n\treadonly as?: string;\n}\n\n/**\n * Column alias expression: simple column reference with alias\n * Uses native Kysely eb.ref().as() - type-safe and dialect-portable\n * @example { kind: 'columnAlias', column: 'name', alias: 'userName' }\n * → SELECT \"name\" AS \"userName\"\n */\nexport interface ColumnAliasIntent {\n\treadonly kind: 'columnAlias';\n\t/** Column name to select */\n\treadonly column: string;\n\t/** Alias for the result column */\n\treadonly alias: string;\n}\n\n/**\n * Relation column expression: select a column from a related table\n * Auto-creates JOIN via include mechanism and selects with custom alias\n * @example { kind: 'relationColumn', relation: 'category', column: 'name', as: 'categoryName' }\n * → SELECT t1.\"name\" AS \"categoryName\" (where t1 is the joined category table)\n * @example { kind: 'relationColumn', relation: 'category.parent', column: 'name', as: 'parentCategoryName' }\n * → Multi-level join: products → category → parent, select parent.name\n */\nexport interface RelationColumnIntent {\n\treadonly kind: 'relationColumn';\n\t/** Relation path to traverse (dot-separated for multi-level) */\n\treadonly relation: string;\n\t/** Column name to select from the target relation */\n\treadonly column: string;\n\t/** Alias for the result column */\n\treadonly as: string;\n}\n\n/**\n * Aggregate expression intent for SELECT expressions\n * @example { kind: 'aggregate', function: 'count', as: 'total' } → COUNT(*) AS total\n * @example { kind: 'aggregate', function: 'sum', field: 'price', as: 'total_price' } → SUM(price) AS total_price\n * @example { kind: 'aggregate', function: 'count', field: 'id', distinct: true, as: 'unique_count' }\n * → COUNT(DISTINCT id) AS unique_count\n */\nexport interface AggregateExpressionIntent {\n\treadonly kind: 'aggregate';\n\t/** Aggregate function */\n\treadonly function: AggregateFunction;\n\t/** Field to aggregate (or '*' for count) */\n\treadonly field: string | '*';\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n\t/** Whether to apply DISTINCT to the aggregate */\n\treadonly distinct?: boolean | undefined;\n\t/** Extra arguments for multi-arg aggregates like string_agg(field, separator) */\n\treadonly extraArgs?: readonly unknown[] | undefined;\n\t/** FILTER (WHERE ...) clause for conditional aggregation */\n\treadonly filter?: WhereIntent | undefined;\n}\n\n/**\n * Pseudo-column traversal keyword for self-referential relations.\n * Used by NQL to traverse hierarchical/tree structures.\n *\n * Default keywords: 'parent', 'child', 'ascendant', 'descendant'.\n * Custom keywords are supported via schema configuration\n * (e.g., 'manager', 'managee' via parentRole/childRole).\n */\nexport type PseudoColumnTraversal = string;\n\n/**\n * Pseudo-column expression intent for self-referential traversal.\n * Enables access to columns on related rows in hierarchical structures.\n *\n * @example { kind: 'pseudoColumn', traversal: 'parent', targetColumn: 'name', as: 'parent.name' }\n * → SELECT parent_row.name AS \"parent.name\" via CTE join\n * @example { kind: 'pseudoColumn', traversal: 'ascendant', targetColumn: 'title', as: 'ancestor_title' }\n * → Recursive CTE to find all ancestors, return their title column\n */\nexport interface PseudoColumnExpressionIntent {\n\treadonly kind: 'pseudoColumn';\n\t/** Traversal type: single-hop (parent/child) or recursive (ascendant/descendant) */\n\treadonly traversal: PseudoColumnTraversal;\n\t/** The column to access on the target row(s) */\n\treadonly targetColumn: string;\n\t/** Alias for result column (required) */\n\treadonly as: string;\n\t/** Optional bounded depth for ascendant[N] / descendant[N] syntax */\n\treadonly depth?: number;\n\t/** Custom role name for multi-FK tables (e.g., 'manager' in manager.ascendant) */\n\treadonly role?: string;\n\t/**\n\t * Chained traversals for multi-hop navigation (e.g., parent.parent.name → ['parent', 'parent']).\n\t * When present, overrides single `traversal` field. Each element generates a successive self-join.\n\t */\n\treadonly traversals?: readonly PseudoColumnTraversal[];\n}\n\n/**\n * Generic function expression (e.g., now(), upper(name), coalesce(a, b)).\n * Used for SQL functions that are not aggregates.\n */\nexport interface FunctionExpressionIntent {\n\treadonly kind: 'function';\n\t/** Function name (e.g., 'upper', 'now', 'coalesce') */\n\treadonly name: string;\n\t/** Function arguments */\n\treadonly args: readonly unknown[];\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n}\n\n/**\n * Scalar subquery in SELECT clause.\n * Produces a single value from a nested query.\n */\nexport interface SubqueryExpressionIntent {\n\treadonly kind: 'subquery';\n\t/** The nested query */\n\treadonly query: QueryIntent;\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n}\n\n/**\n * Arithmetic expression (e.g., price * quantity).\n * Binary operation with left operand, operator, and right operand.\n */\nexport interface ArithmeticExpressionIntent {\n\treadonly kind: 'arithmetic';\n\t/** Left operand (column name or value) */\n\treadonly left: string | number | unknown;\n\t/** Arithmetic operator */\n\treadonly operator: '+' | '-' | '*' | '/' | '%';\n\t/** Right operand (column name or value) */\n\treadonly right: string | number | unknown;\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n}\n\n/**\n * Literal value expression: string, number, boolean, or null.\n * Used in CASE THEN/ELSE clauses for constant values.\n */\nexport interface LiteralExpressionIntent {\n\treadonly kind: 'literal';\n\t/** The literal value */\n\treadonly value: string | number | boolean | null;\n\t/** Optional alias */\n\treadonly as?: string | undefined;\n}\n\n/**\n * Comparison expression: left operator right.\n * Used in CASE WHEN conditions.\n */\nexport interface ComparisonExpressionIntent {\n\treadonly kind: 'comparison';\n\t/** Left side column reference */\n\treadonly column: string;\n\t/** Comparison operator */\n\treadonly operator: '=' | '!=' | '<' | '>' | '<=' | '>=' | 'like';\n\t/** Right side value */\n\treadonly value: unknown;\n}\n\n/**\n * CASE expression: conditional logic in SELECT.\n * CASE WHEN condition THEN result [WHEN ...] [ELSE default] END\n */\nexport interface CaseExpressionIntent {\n\treadonly kind: 'case';\n\t/** Array of WHEN-THEN pairs */\n\treadonly when: ReadonlyArray<{\n\t\treadonly condition: WhereIntent;\n\t\treadonly result: ExpressionIntent;\n\t}>;\n\t/** Optional ELSE clause */\n\treadonly else?: ExpressionIntent | undefined;\n\t/** Alias for result column */\n\treadonly as?: string | undefined;\n}\n\n// ============================================================================\n// JSON/JSONB Operators (E13)\n// ============================================================================\n\n/**\n * JSON path extraction: col->'key' or col->>'key' (chained paths supported).\n * Also used for function notation: json_extract(col, 'key'), json_extract_text(col, 'key').\n */\nexport interface JsonExtractIntent {\n\treadonly kind: 'jsonExtract';\n\treadonly field: string;\n\treadonly path: readonly (string | ParamIntent)[];\n\t/** 'json' = returns JSON value (->), 'text' = returns text (->>) */\n\treadonly mode: 'json' | 'text';\n\treadonly as?: string | undefined;\n}\n\n/**\n * JSON containment: col @> value (contains) or col <@ value (contained by).\n */\nexport interface JsonContainsIntent {\n\treadonly kind: 'jsonContains';\n\treadonly field: string;\n\treadonly value: unknown;\n\t/** true = <@ (contained by), false = @> (contains) */\n\treadonly reversed: boolean;\n}\n\n/**\n * JSON key existence: col ? 'key'.\n */\nexport interface JsonExistsIntent {\n\treadonly kind: 'jsonExists';\n\treadonly field: string;\n\treadonly key: string;\n}\n\n/**\n * JSON path extraction with array path: col #> '{a,b}' or col #>> '{a,b}'.\n */\nexport interface JsonPathExtractIntent {\n\treadonly kind: 'jsonPathExtract';\n\treadonly field: string;\n\t/** PostgreSQL JSON path segments. Bound as one text[] parameter by adapters. */\n\treadonly path: readonly (string | ParamIntent)[];\n\t/** 'json' = returns JSON (#>), 'text' = returns text (#>>) */\n\treadonly mode: 'json' | 'text';\n\treadonly as?: string | undefined;\n}\n\n/** Custom binary operator expression (e.g., <=> for pgvector) */\nexport interface CustomOpExpressionIntent {\n\treadonly kind: 'customOp';\n\treadonly operator: string;\n\treadonly left: ExpressionIntent;\n\treadonly right: ExpressionIntent;\n\treadonly as?: string;\n}\n\n/** Custom function call (e.g., paradedb.score) */\n/** Represents a single ORDER BY entry inside an aggregate function. */\nexport interface AggOrderByArg {\n\t/** Discriminator to distinguish from regular expression args. */\n\treadonly __aggOrderBy: true;\n\treadonly field: string;\n\treadonly direction: 'asc' | 'desc';\n}\n\nexport interface CustomFnExpressionIntent {\n\treadonly kind: 'customFn';\n\treadonly name: string;\n\treadonly args: readonly ExpressionIntent[];\n\treadonly as?: string;\n\t/** FILTER (WHERE ...) clause for conditional aggregation */\n\treadonly filter?: WhereIntent | undefined;\n\t/** ORDER BY clause for ordered aggregates (e.g. array_agg(x ORDER BY y)) */\n\treadonly aggOrderBy?: readonly AggOrderByArg[] | undefined;\n}\n\n/** Column reference in custom expressions */\nexport interface RefExpressionIntent {\n\treadonly kind: 'ref';\n\treadonly column: string;\n}\n\n/**\n * Public bound-parameter node.\n *\n * A value that originated from an NQL named parameter or template binding is\n * represented explicitly in the public IntentAST. The inner value is opaque:\n * consumers must bind it as a value and must not inspect its shape to decide\n * whether it is query structure.\n *\n * @public\n */\nexport interface ParamExpressionIntent {\n\treadonly kind: 'param';\n\treadonly value: unknown;\n\t/** Optional alias when used as a SELECT expression. */\n\treadonly as?: string | undefined;\n}\n\n/** @public */\nexport type ParamIntent = ParamExpressionIntent;\n\n/** Single-level only — never recurse into .value; the inner bound value is opaque user data. @public */\nexport function isParamIntent(value: unknown): value is ParamIntent {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as Record<string, unknown>).kind === 'param' &&\n\t\t'value' in (value as Record<string, unknown>)\n\t);\n}\n\n/** Type cast expression */\nexport interface CastExpressionIntent {\n\treadonly kind: 'cast';\n\treadonly expr: ExpressionIntent;\n\treadonly typeName: string;\n}\n\n/** Named argument in a function call: name => value */\nexport interface NamedArgExpressionIntent {\n\treadonly kind: 'namedArg';\n\treadonly name: string;\n\treadonly value: ExpressionIntent;\n}\n\n/** Star/wildcard expression (*) — used in COUNT(*), SELECT *, etc. */\nexport interface StarExpressionIntent {\n\treadonly kind: 'star';\n}\n\n/** PostgreSQL ARRAY constructor: ARRAY[item1, item2, ...] */\nexport interface ArrayExpressionIntent {\n\treadonly kind: 'array';\n\treadonly elements: readonly ExpressionIntent[];\n\treadonly as?: string;\n}\n\n/** Unary operator expression (e.g., NOT, -, ~) */\nexport interface UnaryExpressionIntent {\n\treadonly kind: 'unary';\n\treadonly operator: string;\n\treadonly operand: ExpressionIntent;\n\treadonly as?: string;\n}\n\n/**\n * Expression intent union type - computed/derived values in SELECT\n * Extensible for future expression types\n */\nexport type ExpressionIntent =\n\t| ColumnExpressionIntent\n\t| CoalesceExpressionIntent\n\t| RawExpressionIntent\n\t| ColumnAliasIntent\n\t| RelationColumnIntent\n\t| WindowIntent\n\t| AggregateExpressionIntent\n\t| PseudoColumnExpressionIntent\n\t| FunctionExpressionIntent\n\t| SubqueryExpressionIntent\n\t| ArithmeticExpressionIntent\n\t| LiteralExpressionIntent\n\t| ComparisonExpressionIntent\n\t| CaseExpressionIntent\n\t| JsonExtractIntent\n\t| JsonContainsIntent\n\t| JsonExistsIntent\n\t| JsonPathExtractIntent\n\t| CustomOpExpressionIntent\n\t| CustomFnExpressionIntent\n\t| RefExpressionIntent\n\t| ParamExpressionIntent\n\t| CastExpressionIntent\n\t| UnaryExpressionIntent\n\t| NamedArgExpressionIntent\n\t| StarExpressionIntent\n\t| ArrayExpressionIntent;\n\n// ============================================================================\n// Window Functions (P3-A)\n// ============================================================================\n\n/**\n * Window function types for OVER clause analytics.\n * - Ranking: row_number, rank, dense_rank (no field required)\n * - Aggregate: sum, avg, count, min, max (field required)\n * - Offset: lag, lead (field required, offset/default deferred to P3+)\n */\nexport type WindowFunction =\n\t| 'row_number'\n\t| 'rank'\n\t| 'dense_rank'\n\t| 'sum'\n\t| 'avg'\n\t| 'count'\n\t| 'min'\n\t| 'max'\n\t| 'lag'\n\t| 'lead';\n\n/**\n * Order specification for window OVER clause.\n */\nexport interface WindowOrderBy {\n\treadonly field: string;\n\treadonly direction?: 'asc' | 'desc';\n}\n\n/**\n * Window function intent for analytics over partitions.\n * Produces SQL like: ROW_NUMBER() OVER (PARTITION BY x ORDER BY y) AS alias\n *\n * @example Row numbering\n * {\n * kind: 'window',\n * function: 'row_number',\n * alias: 'rn',\n * over: { orderBy: [{ field: 'created_at', direction: 'desc' }] }\n * }\n *\n * @example Running total\n * {\n * kind: 'window',\n * function: 'sum',\n * field: 'amount',\n * alias: 'running_total',\n * over: { partitionBy: ['account_id'], orderBy: [{ field: 'date' }] }\n * }\n */\n/**\n * Window function intent (ranking branch): ROW_NUMBER / RANK / DENSE_RANK — no field required.\n *\n * @example\n * { kind: 'window', function: 'row_number', alias: 'rn', over: { orderBy: [{ field: 'created_at', direction: 'desc' }] } }\n */\nexport interface RankingWindowIntent {\n\treadonly kind: 'window';\n\treadonly function: RankingWindowFunction;\n\treadonly field?: never;\n\t/** Result column alias (required) */\n\treadonly alias: string;\n\treadonly offset?: never;\n\treadonly defaultValue?: never;\n\t/** OVER clause specification */\n\treadonly over: {\n\t\treadonly partitionBy?: readonly string[] | undefined;\n\t\treadonly orderBy?: readonly WindowOrderBy[] | undefined;\n\t};\n}\n\n/**\n * Window function intent (aggregate branch): SUM / AVG / COUNT / MIN / MAX — field required.\n *\n * @example\n * { kind: 'window', function: 'sum', field: 'amount', alias: 'running_total', over: { partitionBy: ['account_id'] } }\n */\n/**\n * Window function intent (aggregate branch): SUM / AVG / COUNT / MIN / MAX.\n * Field is required for sum/avg/min/max; COUNT omits field to produce COUNT(*) OVER (...).\n *\n * @example SUM with field\n * { kind: 'window', function: 'sum', field: 'amount', alias: 'running_total', over: { partitionBy: ['account_id'] } }\n * @example COUNT(*)\n * { kind: 'window', function: 'count', alias: 'total', over: {} }\n */\nexport interface AggregateWindowIntent {\n\treadonly kind: 'window';\n\treadonly function: AggregateWindowFunction;\n\t/**\n\t * Field to aggregate over.\n\t * Required for sum/avg/min/max. Omit (or undefined) for COUNT(*) OVER (...).\n\t */\n\treadonly field?: string | undefined;\n\t/** Result column alias (required) */\n\treadonly alias: string;\n\treadonly offset?: never;\n\treadonly defaultValue?: never;\n\t/** OVER clause specification */\n\treadonly over: {\n\t\treadonly partitionBy?: readonly string[] | undefined;\n\t\treadonly orderBy?: readonly WindowOrderBy[] | undefined;\n\t};\n}\n\n/**\n * Window function intent (offset branch): LAG / LEAD — field required, offset optional.\n *\n * @example\n * { kind: 'window', function: 'lag', field: 'salary', alias: 'prev_salary', over: { orderBy: [{ field: 'date', direction: 'asc' }] } }\n */\nexport interface OffsetWindowIntent {\n\treadonly kind: 'window';\n\treadonly function: OffsetWindowFunction;\n\t/** Field to access from the offset row (required for lag/lead) */\n\treadonly field: string;\n\t/** Result column alias (required) */\n\treadonly alias: string;\n\t/** Offset for lag/lead (default: 1 in PostgreSQL) */\n\treadonly offset?: number | undefined;\n\t/** Default value for lag/lead when row doesn't exist */\n\treadonly defaultValue?: unknown;\n\t/** OVER clause specification */\n\treadonly over: {\n\t\treadonly partitionBy?: readonly string[] | undefined;\n\t\treadonly orderBy?: readonly WindowOrderBy[] | undefined;\n\t};\n}\n\n/**\n * Window function intent for analytics over partitions.\n * Discriminated by function group:\n * - RankingWindowIntent: row_number / rank / dense_rank (no field)\n * - AggregateWindowIntent: sum / avg / count / min / max (field required)\n * - OffsetWindowIntent: lag / lead (field required, offset optional)\n */\nexport type WindowIntent =\n\t| RankingWindowIntent\n\t| AggregateWindowIntent\n\t| OffsetWindowIntent;\n\n/**\n * Ranking window functions (no field required)\n */\nexport type RankingWindowFunction = 'row_number' | 'rank' | 'dense_rank';\n\n/**\n * Aggregate window functions (field required)\n */\nexport type AggregateWindowFunction = 'sum' | 'avg' | 'count' | 'min' | 'max';\n\n/**\n * Offset window functions (field required, offset/default deferred)\n */\nexport type OffsetWindowFunction = 'lag' | 'lead';\n","/**\n * @module intent/recursive-intent\n * Recursive CTE intent types for hierarchical data traversal (RFC-001).\n */\n\nimport type { OrderByIntent } from './include-intent.js';\nimport type { WhereIntent } from './where-intent.js';\n\n// ============================================================================\n// Recursive CTE Intent - Hierarchical Data Traversal (RFC-001)\n// ============================================================================\n\n/**\n * Node ID expression for recursive CTE anchor.\n * Used to define the join key for recursive traversal.\n */\nexport type RecursiveNodeIdExpr =\n\t| { readonly kind: 'column'; readonly name: string; readonly as?: string }\n\t| {\n\t\t\treadonly kind: 'literal';\n\t\t\treadonly value: unknown;\n\t\t\treadonly as?: string;\n\t }\n\t| {\n\t\t\treadonly kind: 'binary';\n\t\t\treadonly left: RecursiveNodeIdExpr;\n\t\t\treadonly op: string;\n\t\t\treadonly right: RecursiveNodeIdExpr;\n\t\t\treadonly as?: string;\n\t };\n\n/**\n * Get the alias for a node ID expression.\n * Used by both planner and compiler for consistent CTE column naming.\n *\n * @param expr - The node ID expression\n * @returns The alias to use (explicit alias, column name, or 'node_id' fallback)\n */\nexport function getNodeIdAlias(expr: RecursiveNodeIdExpr): string {\n\tif (expr.as) return expr.as;\n\tif (expr.kind === 'column') return expr.name;\n\tif (expr.kind === 'literal') return 'node_id';\n\t// Binary expression needs explicit alias\n\treturn 'node_id';\n}\n\n/**\n * Adjacency-list traversal (self-referential table).\n * Example: roles.parent_id → roles.id\n */\nexport interface AdjacencyTraversal {\n\treadonly kind: 'adjacency';\n\n\t/** Table containing hierarchical data */\n\treadonly nodeTable: string;\n\n\t/** Primary key column (e.g., \"id\") */\n\treadonly nodeId: string;\n\n\t/** Foreign key pointing to parent (e.g., \"parent_id\") */\n\treadonly parentId: string;\n\n\t/** Traversal direction */\n\treadonly direction: 'descendants' | 'ancestors';\n\n\t/** Filter applied to each step (e.g., active = true) */\n\treadonly stepWhere?: WhereIntent;\n}\n\n/**\n * Edge-table traversal (separate join table).\n * Example: role_inheritance(from_role_id, to_role_id)\n */\nexport interface EdgeTableTraversal {\n\treadonly kind: 'edge-table';\n\n\t/** Node table containing hierarchical data */\n\treadonly nodeTable: string;\n\n\t/** Edge table containing relationships */\n\treadonly edgeTable: string;\n\n\t/** Primary key column in node table (e.g., \"id\") */\n\treadonly nodeId: string;\n\n\t/** Source column in edge table (e.g., \"from_role_id\") */\n\treadonly edgeFrom: string;\n\n\t/** Target column in edge table (e.g., \"to_role_id\") */\n\treadonly edgeTo: string;\n\n\t/** Traversal direction */\n\treadonly direction: 'out' | 'in' | 'both';\n\n\t/** Filter on edges (e.g., relationship_type = 'inheritance') */\n\treadonly edgeWhere?: WhereIntent;\n\n\t/** Filter on nodes (e.g., active = true) */\n\treadonly nodeWhere?: WhereIntent;\n\n\t/** Edge attributes to include in result */\n\treadonly edgeSelect?: readonly string[];\n\n\t/**\n\t * Hint for edge storage semantics (only affects `direction: 'both'`).\n\t *\n\t * - 'unknown' (default): Edges may exist in both directions (A→B and B→A).\n\t * Uses UNION (distinct) to avoid duplicates. Safe but slower.\n\t * - 'directed-only': Caller guarantees edges are stored once only.\n\t * Uses UNION ALL for performance. INCORRECT if duplicates exist.\n\t */\n\treadonly edgeStorageHint?: 'unknown' | 'directed-only';\n}\n\n/**\n * Custom traversal for complex cases (P2 escape hatch).\n */\nexport interface CustomTraversal {\n\treadonly kind: 'custom';\n\t/** Explicit step query builder - reserved for P2 */\n\treadonly stepBuilder?: unknown;\n}\n\n/**\n * Recursive traversal type union.\n */\nexport type RecursiveTraversal =\n\t| AdjacencyTraversal\n\t| EdgeTableTraversal\n\t| CustomTraversal;\n\n/**\n * Tracking options for recursive traversal.\n */\nexport interface RecursiveTrackOptions {\n\t/** Depth counter (starts at 0) */\n\treadonly depth?: {\n\t\treadonly as?: string; // Default: \"depth\"\n\t};\n\n\t/** Path tracking for cycle detection + debugging */\n\treadonly path?: {\n\t\t/** Columns to trace in path (default: nodeId only) */\n\t\treadonly by?: 'nodeId' | readonly string[];\n\t\t/** Result column name (default: \"path\") */\n\t\treadonly as?: string;\n\t\t/** Storage strategy (default: 'array' for PostgreSQL, 'string' for others) */\n\t\treadonly strategy?: 'array' | 'string';\n\t\t/** Separator for string strategy (default: '/') */\n\t\treadonly separator?: string;\n\t};\n\n\t/** Cycle detection marker */\n\treadonly isCycle?: {\n\t\treadonly as?: string; // Default: \"is_cycle\"\n\t};\n}\n\n/**\n * Join clause for CTE emit composition.\n * Allows joining the CTE result with additional tables for final projection.\n */\nexport interface EmitJoinClause {\n\t/** Table to join with */\n\treadonly table: string;\n\n\t/** Join type (default: 'inner') */\n\treadonly type?: 'inner' | 'left';\n\n\t/** Alias for this table (auto-generated if not provided) */\n\treadonly as?: string;\n\n\t/** Join condition */\n\treadonly on: {\n\t\t/** Column from CTE or previous joined table */\n\t\treadonly left: string;\n\t\t/** Column from this table */\n\t\treadonly right: string;\n\t};\n\n\t/** Columns to select from this table */\n\treadonly select?: readonly (\n\t\t| string\n\t\t| { readonly column: string; readonly as: string }\n\t)[];\n}\n\n/**\n * Emit options for recursive CTE final projection.\n */\nexport interface RecursiveEmitOptions {\n\t/** Fields to select from CTE */\n\treadonly select?: readonly string[];\n\t/** Filter on generated rows */\n\treadonly where?: WhereIntent;\n\t/** Ordering */\n\treadonly orderBy?: readonly OrderByIntent[];\n\t/** Join CTE result with additional tables for composition */\n\treadonly joinWith?: readonly EmitJoinClause[];\n\t/** Apply DISTINCT to final result */\n\treadonly distinct?: boolean;\n}\n\n/**\n * PostgreSQL-specific options for recursive CTE (capability-gated).\n */\nexport interface RecursiveAdvancedOptions {\n\t/**\n\t * Cycle detection strategy (adapter-specific implementation).\n\t * - 'error': Throw on cycle detection\n\t * - 'stop': Stop traversal at cycle (prune branch)\n\t * - 'mark': Add is_cycle column to results\n\t *\n\t * PostgreSQL 14+ uses native CYCLE clause.\n\t * Other adapters may use application-level detection.\n\t */\n\treadonly cycle?: 'error' | 'stop' | 'mark';\n\n\t/**\n\t * Traversal search order (adapter-specific implementation).\n\t * - 'depth': Depth-first search order\n\t * - 'breadth': Breadth-first search order\n\t *\n\t * PostgreSQL 14+ uses native SEARCH clause.\n\t * Other adapters may use ORDER BY on depth column.\n\t */\n\treadonly search?: 'depth' | 'breadth';\n}\n\n/**\n * Deduplication strategy for recursive CTE.\n *\n * - 'none': No dedup. May return same node multiple times via different paths.\n * Fastest. Use when you need all paths or when graph is known to be a tree.\n *\n * - 'final': One row per nodeId in final output.\n * Implemented via `DISTINCT ON (nodeId)` (PostgreSQL) or\n * `ROW_NUMBER() OVER (PARTITION BY nodeId)` fallback.\n * ⚠️ NOT the same as `query.distinct()` which dedupes on entire row!\n *\n * Note: 'global' (UNION instead of UNION ALL) was considered but not implemented.\n * 'final' provides the same end result with better performance characteristics.\n */\nexport type RecursiveDedupe = 'none' | 'final';\n\n/**\n * Recursive CTE intent for hierarchical data traversal.\n *\n * Key invariant: anchor and step MUST produce identical column shape.\n * The planner validates this and auto-injects nodeIdExpr.\n *\n * @see RFC-001 for detailed specification\n */\nexport interface RecursiveIntent {\n\treadonly type: 'recursive';\n\n\t/** CTE name for the recursive query */\n\treadonly cteName: string;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// START (anchor/seed)\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\treadonly start: {\n\t\t/** Source table for anchor query */\n\t\treadonly from: string;\n\n\t\t/** Filter for seed rows (e.g., where id = $userId) */\n\t\treadonly where?: WhereIntent;\n\n\t\t/**\n\t\t * REQUIRED: Expression for node ID. Auto-injected into select.\n\t\t * This ensures the recursive join always has the key column.\n\t\t */\n\t\treadonly nodeIdExpr: RecursiveNodeIdExpr;\n\n\t\t/** Additional fields to select (beyond nodeId) */\n\t\treadonly select?: readonly string[];\n\t};\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// TRAVERSAL\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Traversal configuration (adjacency-list or edge-table) */\n\treadonly traversal: RecursiveTraversal;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// TRACKING (system columns)\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Tracking options for depth, path, and cycle detection */\n\treadonly track?: RecursiveTrackOptions;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// SAFETY\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Maximum recursion depth (REQUIRED) */\n\treadonly maxDepth: number;\n\n\t/** Maximum rows (optional safety limit) */\n\treadonly maxRows?: number;\n\n\t/** Deduplication strategy */\n\treadonly dedupe?: RecursiveDedupe;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// EMIT (final projection)\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Final projection options */\n\treadonly emit?: RecursiveEmitOptions;\n\n\t// ─────────────────────────────────────────────────────────────────────────\n\t// ADVANCED OPTIONS (capability-gated, adapter-specific implementation)\n\t// ─────────────────────────────────────────────────────────────────────────\n\n\t/** Advanced recursive options (cycle detection, search order) */\n\treadonly advancedOptions?: RecursiveAdvancedOptions;\n}\n","/**\n * NQL-origin SELECT function allowlist.\n *\n * This is the shared security gate for function names that originated in NQL\n * SELECT text and later cross the IntentAST-to-SQL adapter boundary. Extend it\n * only after adding compiler and adapter coverage proving the function is\n * intentionally supported for NQL-origin SELECT projections.\n */\nexport const NQL_SELECT_AGGREGATE_FUNCTIONS = [\n\t'count',\n\t'sum',\n\t'avg',\n\t'min',\n\t'max',\n] as const;\n\nexport const NQL_SELECT_JSON_FUNCTIONS = [\n\t'json_extract',\n\t'json_extract_text',\n\t'json_path',\n\t'json_path_text',\n] as const;\n\nexport const NQL_SELECT_VALUE_FUNCTIONS = [\n\t'coalesce',\n\t'lower',\n\t'now',\n\t'round',\n\t'upper',\n] as const;\n\nexport const NQL_SELECT_SCALAR_FUNCTIONS = [\n\t...NQL_SELECT_AGGREGATE_FUNCTIONS,\n\t...NQL_SELECT_JSON_FUNCTIONS,\n\t...NQL_SELECT_VALUE_FUNCTIONS,\n] as const;\n\nexport const NQL_SELECT_WINDOW_ONLY_FUNCTIONS = [\n\t'row_number',\n\t'rank',\n\t'dense_rank',\n\t'lag',\n\t'lead',\n] as const;\n\nexport const NQL_SELECT_WINDOW_FUNCTIONS = [\n\t...NQL_SELECT_WINDOW_ONLY_FUNCTIONS,\n\t'count',\n\t'sum',\n\t'avg',\n\t'min',\n\t'max',\n] as const;\n\nexport const NQL_SELECT_SCALAR_FUNCTION_ALLOWLIST: ReadonlySet<string> =\n\tnew Set(NQL_SELECT_SCALAR_FUNCTIONS);\n\nexport const NQL_SELECT_WINDOW_FUNCTION_ALLOWLIST: ReadonlySet<string> =\n\tnew Set(NQL_SELECT_WINDOW_FUNCTIONS);\n\nexport const NQL_SELECT_FUNCTION_ALLOWLIST: ReadonlySet<string> = new Set([\n\t...NQL_SELECT_SCALAR_FUNCTIONS,\n\t...NQL_SELECT_WINDOW_FUNCTIONS,\n]);\n\ntype NqlSelectScalarFunction = (typeof NQL_SELECT_SCALAR_FUNCTIONS)[number];\ntype NqlSelectWindowFunction = (typeof NQL_SELECT_WINDOW_FUNCTIONS)[number];\n\nexport function isNqlSelectScalarFunctionAllowed(\n\tname: string,\n): name is NqlSelectScalarFunction {\n\treturn NQL_SELECT_SCALAR_FUNCTION_ALLOWLIST.has(name.toLowerCase());\n}\n\nexport function isNqlSelectFunctionAllowed(name: string): boolean {\n\treturn NQL_SELECT_FUNCTION_ALLOWLIST.has(name.toLowerCase());\n}\n\nexport function isNqlSelectWindowFunctionAllowed(\n\tname: string,\n): name is NqlSelectWindowFunction {\n\treturn NQL_SELECT_WINDOW_FUNCTION_ALLOWLIST.has(name.toLowerCase());\n}\n","/**\n * @module intent/type-guards\n * Type guard functions for all intent AST types.\n */\n\nimport type {\n\tAggregateWindowFunction,\n\tCoalesceExpressionIntent,\n\tColumnAliasIntent,\n\tExpressionIntent,\n\tOffsetWindowFunction,\n\tRankingWindowFunction,\n\tRawExpressionIntent,\n\tRelationColumnIntent,\n\tWindowFunction,\n\tWindowIntent,\n} from './expression-intent.js';\nimport type {\n\tDeleteIntent,\n\tInsertIntent,\n\tMutationIntent,\n\tUpdateIntent,\n\tUpsertIntent,\n} from './mutation-intent.js';\nimport type { QueryIntent } from './query-intent.js';\nimport type {\n\tAdjacencyTraversal,\n\tCustomTraversal,\n\tEdgeTableTraversal,\n\tRecursiveIntent,\n\tRecursiveTraversal,\n} from './recursive-intent.js';\nimport type {\n\tSelectAggregateIntent,\n\tSelectAllIntent,\n\tSelectFieldsIntent,\n\tSelectIntent,\n\tSelectWithExpressionsIntent,\n} from './select-intent.js';\nimport type {\n\tSubqueryRefIntent,\n\tWhereAndIntent,\n\tWhereAnyIntent,\n\tWhereComparisonIntent,\n\tWhereExistsIntent,\n\tWhereInIntent,\n\tWhereIntent,\n\tWhereLikeIntent,\n\tWhereNotExistsIntent,\n\tWhereNotIntent,\n\tWhereNullIntent,\n\tWhereOrIntent,\n\tWhereRangeIntent,\n\tWhereRelationFilterIntent,\n\tWhereSubqueryIntent,\n} from './where-intent.js';\n\n// ============================================================================\n// Window Intent Type Guards\n// ============================================================================\n\n/**\n * Check if an intent is a window function intent\n */\nexport function isWindowIntent(intent: unknown): intent is WindowIntent {\n\treturn (\n\t\ttypeof intent === 'object' &&\n\t\tintent !== null &&\n\t\t'kind' in intent &&\n\t\t(intent as Record<string, unknown>).kind === 'window'\n\t);\n}\n\n/**\n * Check if a window function requires a field (aggregate or offset functions)\n */\nexport function isAggregateWindowFunction(\n\tfn: WindowFunction,\n): fn is AggregateWindowFunction | OffsetWindowFunction {\n\treturn ['sum', 'avg', 'count', 'min', 'max', 'lag', 'lead'].includes(fn);\n}\n\n/**\n * Check if a window function is a ranking function (no field required)\n */\nexport function isRankingWindowFunction(\n\tfn: WindowFunction,\n): fn is RankingWindowFunction {\n\treturn ['row_number', 'rank', 'dense_rank'].includes(fn);\n}\n\n// ============================================================================\n// Where Intent Type Guards\n// ============================================================================\n\n/**\n * Check if a where intent is a comparison\n */\nexport function isWhereComparison(\n\twhere: WhereIntent,\n): where is WhereComparisonIntent {\n\treturn where.kind === 'comparison';\n}\n\n/**\n * Check if a where intent is a like filter\n */\nexport function isWhereLike(where: WhereIntent): where is WhereLikeIntent {\n\treturn where.kind === 'like';\n}\n\n/**\n * Check if a where intent is a subquery filter\n */\nexport function isWhereSubquery(\n\twhere: WhereIntent,\n): where is WhereSubqueryIntent {\n\treturn where.kind === 'subquery';\n}\n\n/**\n * Check if a value is a subquery ref (column reference in subquery)\n */\nexport function isSubqueryRef(value: unknown): value is SubqueryRefIntent {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'kind' in value &&\n\t\t(value as Record<string, unknown>).kind === 'ref'\n\t);\n}\n\n/**\n * Check if a where intent is an in filter\n */\nexport function isWhereIn(where: WhereIntent): where is WhereInIntent {\n\treturn where.kind === 'in';\n}\n\n/**\n * Check if a where intent is an any filter (= ANY($N::type[]))\n */\nexport function isWhereAny(where: WhereIntent): where is WhereAnyIntent {\n\treturn where.kind === 'any';\n}\n\n/**\n * Check if a where intent is a null filter\n */\nexport function isWhereNull(where: WhereIntent): where is WhereNullIntent {\n\treturn where.kind === 'null';\n}\n\n/**\n * Check if a where intent is a range filter (PostgreSQL range types)\n */\nexport function isWhereRange(where: WhereIntent): where is WhereRangeIntent {\n\treturn where.kind === 'range';\n}\n\n/**\n * Check if a where intent is a logical AND\n */\nexport function isWhereAnd(where: WhereIntent): where is WhereAndIntent {\n\treturn where.kind === 'and';\n}\n\n/**\n * Check if a where intent is a logical OR\n */\nexport function isWhereOr(where: WhereIntent): where is WhereOrIntent {\n\treturn where.kind === 'or';\n}\n\n/**\n * Check if a where intent is a logical NOT\n */\nexport function isWhereNot(where: WhereIntent): where is WhereNotIntent {\n\treturn where.kind === 'not';\n}\n\n/**\n * Check if a where intent is an exists filter\n */\nexport function isWhereExists(where: WhereIntent): where is WhereExistsIntent {\n\treturn where.kind === 'exists';\n}\n\n/**\n * Check if a where intent is a not exists filter\n */\nexport function isWhereNotExists(\n\twhere: WhereIntent,\n): where is WhereNotExistsIntent {\n\treturn where.kind === 'notExists';\n}\n\n/**\n * Check if a where intent is a relation filter\n */\nexport function isWhereRelationFilter(\n\twhere: WhereIntent,\n): where is WhereRelationFilterIntent {\n\treturn where.kind === 'relationFilter';\n}\n\n/**\n * Check if a where intent is any relation-based filter\n */\nexport function isWhereRelationBased(\n\twhere: WhereIntent,\n): where is\n\t| WhereExistsIntent\n\t| WhereNotExistsIntent\n\t| WhereRelationFilterIntent {\n\treturn (\n\t\twhere.kind === 'exists' ||\n\t\twhere.kind === 'notExists' ||\n\t\twhere.kind === 'relationFilter'\n\t);\n}\n\n/**\n * Check if a where intent is a logical operator (and/or/not)\n */\nexport function isWhereLogical(\n\twhere: WhereIntent,\n): where is WhereAndIntent | WhereOrIntent | WhereNotIntent {\n\treturn where.kind === 'and' || where.kind === 'or' || where.kind === 'not';\n}\n\n// ============================================================================\n// Select Intent Type Guards\n// ============================================================================\n\n/**\n * Check if a select intent selects all columns\n */\nexport function isSelectAll(select: SelectIntent): select is SelectAllIntent {\n\treturn select.type === 'all';\n}\n\n/**\n * Check if a select intent selects specific fields\n */\nexport function isSelectFields(\n\tselect: SelectIntent,\n): select is SelectFieldsIntent {\n\treturn select.type === 'fields';\n}\n\n/**\n * Check if a select intent is an aggregate select\n */\nexport function isSelectAggregate(\n\tselect: SelectIntent,\n): select is SelectAggregateIntent {\n\treturn select.type === 'aggregate';\n}\n\n/**\n * Check if a select intent has expressions\n */\nexport function isSelectWithExpressions(\n\tselect: SelectIntent,\n): select is SelectWithExpressionsIntent {\n\treturn select.type === 'expressions';\n}\n\n// ============================================================================\n// Expression Intent Type Guards\n// ============================================================================\n\n/**\n * Check if an expression is a COALESCE expression\n */\nexport function isCoalesceExpression(\n\texpr: ExpressionIntent,\n): expr is CoalesceExpressionIntent {\n\treturn expr.kind === 'coalesce';\n}\n\n/**\n * Check if an expression is a raw SQL expression\n */\nexport function isRawExpression(\n\texpr: ExpressionIntent,\n): expr is RawExpressionIntent {\n\treturn expr.kind === 'raw';\n}\n\n/**\n * Check if an expression is a column alias expression\n */\nexport function isColumnAliasExpression(\n\texpr: ExpressionIntent,\n): expr is ColumnAliasIntent {\n\treturn expr.kind === 'columnAlias';\n}\n\n/**\n * Check if an expression is a relation column expression\n */\nexport function isRelationColumnExpression(\n\texpr: ExpressionIntent,\n): expr is RelationColumnIntent {\n\treturn expr.kind === 'relationColumn';\n}\n\n// ============================================================================\n// Recursive CTE Type Guards\n// ============================================================================\n\n/**\n * Check if a traversal is adjacency-list based\n */\nexport function isAdjacencyTraversal(\n\ttraversal: RecursiveTraversal,\n): traversal is AdjacencyTraversal {\n\treturn traversal.kind === 'adjacency';\n}\n\n/**\n * Check if a traversal is edge-table based\n */\nexport function isEdgeTableTraversal(\n\ttraversal: RecursiveTraversal,\n): traversal is EdgeTableTraversal {\n\treturn traversal.kind === 'edge-table';\n}\n\n/**\n * Check if a traversal is custom\n */\nexport function isCustomTraversal(\n\ttraversal: RecursiveTraversal,\n): traversal is CustomTraversal {\n\treturn traversal.kind === 'custom';\n}\n\n/**\n * Check if an intent is a recursive CTE intent\n */\nexport function isRecursiveIntent(\n\tintent: QueryIntent | RecursiveIntent,\n): intent is RecursiveIntent {\n\treturn intent.type === 'recursive';\n}\n\n// ============================================================================\n// Mutation Intent Type Guards\n// ============================================================================\n\n/**\n * Check if an intent is an insert intent\n */\nexport function isInsertIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is InsertIntent {\n\treturn intent.type === 'insert';\n}\n\n/**\n * Check if an intent is an update intent\n */\nexport function isUpdateIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is UpdateIntent {\n\treturn intent.type === 'update';\n}\n\n/**\n * Check if an intent is a delete intent\n */\nexport function isDeleteIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is DeleteIntent {\n\treturn intent.type === 'delete';\n}\n\n/**\n * Check if an intent is an upsert intent (DX-026)\n */\nexport function isUpsertIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is UpsertIntent {\n\treturn intent.type === 'upsert';\n}\n\n/**\n * Check if an intent is any mutation intent\n */\nexport function isMutationIntent(\n\tintent: QueryIntent | RecursiveIntent | MutationIntent,\n): intent is MutationIntent {\n\treturn (\n\t\tintent.type === 'insert' ||\n\t\tintent.type === 'insert_from' ||\n\t\tintent.type === 'upsert_from' ||\n\t\tintent.type === 'update' ||\n\t\tintent.type === 'batchUpdate' ||\n\t\tintent.type === 'delete' ||\n\t\tintent.type === 'upsert'\n\t);\n}\n","/**\n * @module intent/where-intent\n * Where intent types for filter conditions.\n */\n\nimport type { ColumnListInput } from '../column-list.js';\nimport type { RangeValue } from '../shared/utils.js';\nimport type { ExpressionIntent, ParamIntent } from './expression-intent.js';\nimport type { ComparisonOperator, NullOperator } from './operators.js';\nimport type { QueryIntent } from './query-intent.js';\nimport type { RecursiveExistsOptions } from './recursive-types.js';\n\nexport type { RangeValue };\n\n// ============================================================================\n// Where Intent - Filter Conditions\n// ============================================================================\n\n/**\n * Typed field reference for cross-table column comparisons in relation filters.\n *\n * When using aliased relation filters like `some(orders as o, o.total > minOrder)`,\n * the RHS `minOrder` is a reference to the parent table's column, not a literal value.\n * FieldRef captures this distinction so the adapter can compile it as a column reference\n * instead of a parameterized value.\n *\n * @example\n * // some(rel as r, r.col > bareCol) → value: { kind: 'fieldRef', column: 'bareCol', scope: 'outer' }\n * // some(rel as r, r.col > r.otherCol) → value: { kind: 'fieldRef', column: 'otherCol', scope: 'inner' }\n * // some(a as x, some(b as y, y.f > x.g)) → value: { kind: 'fieldRef', column: 'g', scope: 'outer', alias: 'x' }\n */\nexport interface FieldRef {\n\treadonly kind: 'fieldRef';\n\treadonly column: string;\n\treadonly scope: 'inner' | 'outer';\n\t/** Named alias for outer scope (when referencing a specific outer alias in nested filters) */\n\treadonly alias?: string;\n}\n\n/**\n * Type guard for FieldRef values\n */\nexport function isFieldRef(value: unknown): value is FieldRef {\n\treturn (\n\t\tvalue !== null &&\n\t\ttypeof value === 'object' &&\n\t\t(value as Record<string, unknown>).kind === 'fieldRef'\n\t);\n}\n\nexport interface WhereComparisonIntent {\n\treadonly kind: 'comparison';\n\treadonly field: string;\n\treadonly operator: ComparisonOperator;\n\treadonly value: unknown;\n\t/** JSON path extraction before comparison (e.g., data->'key' = 'val') */\n\treadonly jsonPath?: readonly (string | ParamIntent)[];\n\t/** JSON extraction mode: 'json' = ->, 'text' = ->> */\n\treadonly jsonMode?: 'json' | 'text';\n}\n\n/**\n * String filter: field like pattern\n */\nexport interface WhereLikeIntent {\n\treadonly kind: 'like';\n\treadonly field: string;\n\treadonly pattern: string | ParamIntent;\n\t/** Case-insensitive matching */\n\treadonly caseInsensitive?: boolean;\n\t/** Escape character for LIKE pattern (e.g. '\\\\' to escape _ and %) */\n\treadonly escape?: string;\n}\n\n/**\n * Array filter: field in [values]\n */\n/**\n * Array filter (values branch): field IN (v1, v2, ...)\n */\nexport interface WhereInValueIntent {\n\treadonly kind: 'in';\n\treadonly field: string;\n\treadonly values: readonly unknown[];\n\treadonly subquery?: never;\n\treadonly not?: boolean;\n}\n\n/**\n * Array filter (subquery branch): field IN (SELECT ...)\n */\nexport interface WhereInSubqueryIntent {\n\treadonly kind: 'in';\n\treadonly field: string;\n\treadonly subquery: QueryIntent;\n\treadonly values?: never;\n\treadonly not?: boolean;\n}\n\n/**\n * Array filter: field in [values] OR field IN (subquery)\n * XOR: exactly one of `values` or `subquery` must be present.\n */\nexport type WhereInIntent = WhereInValueIntent | WhereInSubqueryIntent;\n\n/**\n * Array membership filter using PostgreSQL ANY() operator.\n * Compiles to: \"col\" = ANY($N::type[])\n */\nexport interface WhereAnyIntent {\n\treadonly kind: 'any';\n\treadonly field: string;\n\treadonly values: readonly unknown[] | ParamIntent;\n}\n\n/**\n * Operand accepted by range WHERE filters.\n * - RangeValue: for range-to-range operators (overlaps, contains, containedBy)\n * - string: ISO date/timestamp literals (e.g. '2025-01-15', '2025-01-15T08:00:00Z')\n * - number: integer/numeric point values (e.g. 50000 for salary_range @> 50000)\n * - boolean: rarely used but valid PostgreSQL range operand\n */\nexport type RangeOperand = RangeValue | string | number | boolean;\n\n/**\n * Range operator for PostgreSQL range types.\n * - overlaps: && (ranges have common points)\n * - contains: @> (range contains value or range)\n * - containedBy: <@ (range is contained by another range)\n */\nexport type RangeOperator = 'overlaps' | 'contains' | 'containedBy' | 'between';\n\n/**\n * Range filter: field overlaps/contains/containedBy range value\n * PostgreSQL range types: daterange, tsrange, tstzrange, int4range, int8range, numrange\n *\n * @example\n * // Check if booking dates overlap a period\n * { kind: 'range', field: 'dates', operator: 'overlaps', value: { lower: '2025-01-15', upper: '2025-01-20' } }\n *\n * // Check if salary range contains a value\n * { kind: 'range', field: 'salary_range', operator: 'contains', value: 50000 }\n */\n/**\n * Range filter: field overlaps/contains/containedBy range value\n * PostgreSQL range types: daterange, tsrange, tstzrange, int4range, int8range, numrange\n *\n * @example\n * // Check if booking dates overlap a period\n * { kind: 'range', field: 'dates', operator: 'overlaps', value: { lower: '2025-01-15', upper: '2025-01-20' } }\n *\n * // Check if salary range contains a value\n * { kind: 'range', field: 'salary_range', operator: 'contains', value: 50000 }\n */\nexport interface WhereRangeIntent {\n\treadonly kind: 'range';\n\treadonly field: string;\n\treadonly operator: RangeOperator;\n\t/** Operand: RangeValue for range-to-range ops, or a scalar (string | number | boolean) for point-in-range ops */\n\treadonly value: RangeOperand;\n}\n\n/**\n * Null filter: field is null / is not null\n */\nexport interface WhereNullIntent {\n\treadonly kind: 'null';\n\treadonly field: string;\n\treadonly operator: NullOperator;\n}\n\n/**\n * Logical AND: all conditions must match\n */\nexport interface WhereAndIntent {\n\treadonly kind: 'and';\n\treadonly conditions: readonly WhereIntent[];\n}\n\n/**\n * Logical OR: at least one condition must match\n */\nexport interface WhereOrIntent {\n\treadonly kind: 'or';\n\treadonly conditions: readonly WhereIntent[];\n}\n\n/**\n * Logical NOT: condition must not match\n */\nexport interface WhereNotIntent {\n\treadonly kind: 'not';\n\treadonly condition: WhereIntent;\n}\n\n/**\n * Relation exists filter: filter by existence of related records\n * Critical for Q1 golden test - enables EXISTS subquery strategy\n *\n * @example\n * // Find users who have at least one published post\n * { kind: 'exists', relation: 'posts', where: { kind: 'comparison', field: 'status', operator: 'eq', value: 'published' } }\n */\nexport interface WhereExistsIntent {\n\treadonly kind: 'exists';\n\t/** Relation name to check existence */\n\treadonly relation: string;\n\t/** Optional filter on related records */\n\treadonly where?: WhereIntent;\n\t/**\n\t * Recursive options for ancestor/descendant existence checks.\n\t * When present, generates a recursive CTE instead of simple EXISTS.\n\t */\n\treadonly recursive?: RecursiveExistsOptions;\n\t/**\n\t * Optional JOIN declarations inside the EXISTS subquery.\n\t * Keys are relation names (used as aliases), values specify join type.\n\t * Enables filtering on joined tables inside the subquery.\n\t *\n\t * @example\n\t * exists('callers', {\n\t * include: { callerFile: { join: 'inner' } },\n\t * where: eq('callerFile.project_id', projectId)\n\t * })\n\t */\n\treadonly include?: Readonly<Record<string, { join?: 'inner' | 'left' }>>;\n}\n\n/**\n * Relation not exists filter: filter by absence of related records\n *\n * @example\n * // Find users who have no posts\n * { kind: 'notExists', relation: 'posts' }\n */\nexport interface WhereNotExistsIntent {\n\treadonly kind: 'notExists';\n\t/** Relation name to check absence */\n\treadonly relation: string;\n\t/** Optional filter on related records */\n\treadonly where?: WhereIntent;\n\t/**\n\t * Recursive options for ancestor/descendant absence checks.\n\t * When present, generates a recursive CTE instead of simple NOT EXISTS.\n\t */\n\treadonly recursive?: RecursiveExistsOptions;\n\t/**\n\t * Optional JOIN declarations inside the NOT EXISTS subquery.\n\t * Keys are relation names (used as aliases), values specify join type.\n\t * Enables filtering on joined tables inside the subquery.\n\t *\n\t * @example\n\t * notExists('callers', {\n\t * include: { callerFile: { join: 'inner' } },\n\t * where: eq('callerFile.project_id', projectId)\n\t * })\n\t */\n\treadonly include?: Readonly<Record<string, { join?: 'inner' | 'left' }>>;\n}\n\n/**\n * Raw EXISTS subquery filter using an arbitrary QueryIntent.\n * Unlike WhereExistsIntent (which uses FK-resolved relation names),\n * this wraps a fully-specified subquery for correlated EXISTS checks.\n *\n * @example\n * // EXISTS (SELECT 1 FROM symbols WHERE symbols.id = calls.symbol_id AND ...)\n * exists(subquery('symbols').where(eq('id', ref('calls.symbol_id'))))\n */\nexport interface WhereRawExistsIntent {\n\treadonly kind: 'rawExists';\n\t/** The subquery producing rows for the EXISTS check */\n\treadonly subquery: QueryIntent;\n}\n\n/**\n * Raw NOT EXISTS subquery filter using an arbitrary QueryIntent.\n *\n * @example\n * // NOT EXISTS (SELECT 1 FROM symbols WHERE ...)\n * notExists(subquery('symbols').where(...))\n */\nexport interface WhereRawNotExistsIntent {\n\treadonly kind: 'rawNotExists';\n\t/** The subquery producing rows for the NOT EXISTS check */\n\treadonly subquery: QueryIntent;\n}\n\n/**\n * Relation filter: filter parent by conditions on related records\n * More flexible than exists - allows filtering by related record attributes\n *\n * @example\n * // Find users whose latest post was created in 2024\n * { kind: 'relationFilter', relation: 'posts', where: {...}, mode: 'some' }\n */\nexport interface WhereRelationFilterIntent {\n\treadonly kind: 'relationFilter';\n\t/**\n\t * Relation path for filtering.\n\t * - Single relation: 'posts' or ['posts']\n\t * - Multi-hop (SPEC-002): ['author', 'company'] for author.company traversal\n\t */\n\treadonly relation: string | readonly string[];\n\t/** Filter conditions on related records */\n\treadonly where: WhereIntent;\n\t/**\n\t * Match mode:\n\t * - 'some': At least one related record matches (default)\n\t * - 'every': All related records match\n\t * - 'none': No related records match\n\t */\n\treadonly mode: 'some' | 'every' | 'none';\n\t/** Optional alias for complex conditions (SPEC-002) */\n\treadonly alias?: string | undefined;\n\t/**\n\t * Optional display/debug metadata for pre-resolved relation filters.\n\t * These fields are not trusted for compilation. Internal compiler paths that\n\t * prove a virtual source maps to a real model relation use a module-private\n\t * proof payload instead.\n\t */\n\treadonly targetTable?: string | undefined;\n\treadonly sourceColumn?: ColumnListInput;\n\treadonly targetColumn?: ColumnListInput;\n}\n\n// ============================================================================\n// Subquery Intent - Scalar Subquery in WHERE\n// ============================================================================\n\n/**\n * Reference to a parent query column in a subquery.\n * Used to create correlated subqueries.\n *\n * The `outer` field is a discriminator set by `outerRef()` in\n * `@dbsp/core` to distinguish a genuine outer-query reference from an\n * inner `ref()` expression (RefExpressionIntent), which has the same\n * structural shape `{ kind: 'ref', column }`. Converters that need to\n * detect correlated subqueries check `outer === true`; an intent built\n * without `outer` (i.e. a plain `{ kind: 'ref', column }`) is treated\n * as a non-correlated inner expression reference.\n *\n * @example\n * // Outer reference produced by outerRef('id'):\n * { kind: 'ref', column: 'id', outer: true }\n *\n * // Inner column reference (not a correlated outer ref):\n * { kind: 'ref', column: 'id' } // outer is absent / undefined\n */\nexport interface SubqueryRefIntent {\n\treadonly kind: 'ref';\n\t/** Column name or aliased column (e.g., 'id' or 't0.id') */\n\treadonly column: string;\n\t/**\n\t * Discriminator that marks this as a genuine outer-query reference\n\t * (set by `outerRef()`). Absent on plain inner expression refs.\n\t * Optional so that existing raw intents built per the type without\n\t * this field remain valid (non-breaking addition).\n\t */\n\treadonly outer?: true;\n}\n\n/**\n * Subquery intent for scalar subquery comparisons.\n * Produces correlated subqueries in SQL.\n *\n * @example\n * // Find products where price equals max price of category\n * {\n * kind: 'subquery',\n * field: 'price',\n * operator: 'eq',\n * subquery: { from: 'products', select: { kind: 'aggregate', fn: 'max', field: 'price' } }\n * }\n */\nexport interface WhereSubqueryIntent {\n\treadonly kind: 'subquery';\n\t/** Field to compare on the parent query */\n\treadonly field: string;\n\t/** Comparison operator */\n\treadonly operator: ComparisonOperator;\n\t/** Subquery producing scalar value */\n\treadonly subquery: QueryIntent;\n}\n\n/**\n * Scalar subquery intent - produces a single value.\n * Simplified QueryIntent for subquery context.\n */\n/** @deprecated Use QueryIntent instead — subqueries are full queries with contextual validation */\nexport type ScalarSubqueryIntent = QueryIntent;\n\n// ============================================================================\n// JSON/JSONB WHERE Intents (E13)\n// ============================================================================\n\n/**\n * JSON containment filter: col @> value or col <@ value.\n * @example { kind: 'jsonContains', field: 'data', value: '{\"active\":true}', reversed: false }\n * → WHERE \"data\" @> $1\n */\nexport interface WhereJsonContainsIntent {\n\treadonly kind: 'jsonContains';\n\treadonly field: string;\n\treadonly value: unknown;\n\t/** false = @> (field contains value), true = <@ (field contained by value) */\n\treadonly reversed: boolean;\n}\n\n/**\n * JSON key existence filter: col ? 'key'.\n * @example { kind: 'jsonExists', field: 'data', key: 'email' }\n * → WHERE \"data\" ? $1\n */\nexport interface WhereJsonExistsIntent {\n\treadonly kind: 'jsonExists';\n\treadonly field: string;\n\treadonly key: string | ParamIntent;\n}\n\n/** WHERE clause using a custom expression with comparison */\nexport interface WhereExpressionIntent {\n\treadonly kind: 'expression';\n\treadonly expr: ExpressionIntent;\n\treadonly operator: ComparisonOperator;\n\treadonly value: unknown;\n}\n\n/**\n * Where intent - filter conditions union type\n * Discriminated union using 'kind' field\n */\nexport type WhereIntent =\n\t| WhereComparisonIntent\n\t| WhereLikeIntent\n\t| WhereInIntent\n\t| WhereAnyIntent\n\t| WhereNullIntent\n\t| WhereRangeIntent\n\t| WhereAndIntent\n\t| WhereOrIntent\n\t| WhereNotIntent\n\t| WhereExistsIntent\n\t| WhereNotExistsIntent\n\t| WhereRawExistsIntent\n\t| WhereRawNotExistsIntent\n\t| WhereRelationFilterIntent\n\t| WhereSubqueryIntent\n\t| WhereJsonContainsIntent\n\t| WhereJsonExistsIntent\n\t| WhereExpressionIntent;\n","import { toColumnList } from './column-list.js';\nimport type { TableIR } from './model-ir.js';\n\n/**\n * Dialect-neutral json_agg order intent.\n *\n * `fallback` means the table has no declared primary key, so adapters should\n * realize the all-column deterministic fallback in their own dialect.\n */\nexport interface JsonAggOrderKey {\n\treadonly columns: readonly string[];\n\treadonly fallback: boolean;\n}\n\nexport function resolveJsonAggOrderKey(table: TableIR): JsonAggOrderKey {\n\tconst pkColumns = toColumnList(table.primaryKey);\n\treturn pkColumns.length > 0\n\t\t? { columns: pkColumns, fallback: false }\n\t\t: { columns: table.columns.map((col) => col.name), fallback: true };\n}\n","import type { ModelIR } from './model-ir.js';\n\n/**\n * Canonical shape of a `schema()` factory result as produced by\n * `createOrm({schema: ...})` callers and consumed by tooling\n * (cli, gui sidecar, mcp-server).\n *\n * ARCH-005: Schema type from schema() function.\n * Contains the definition, pre-computed ModelIR, and table names.\n */\nexport interface LoadedSchema {\n\treadonly definition: Record<string, unknown>;\n\treadonly model: ModelIR;\n\treadonly tableNames: readonly string[];\n}\n\n/**\n * Runtime type guard for `LoadedSchema` — verifies the object has\n * `definition`, `model` (with nested `tables` + `relations`), and\n * `tableNames` as an array. Structural check only — does not validate\n * the inner values of any field.\n *\n * Type guard for ARCH-005 schema() output.\n */\nexport function isValidSchema(schema: unknown): schema is LoadedSchema {\n\tif (\n\t\ttypeof schema !== 'object' ||\n\t\tschema === null ||\n\t\t!('model' in schema) ||\n\t\t!('definition' in schema) ||\n\t\t!('tableNames' in schema)\n\t) {\n\t\treturn false;\n\t}\n\tconst s = schema as LoadedSchema;\n\tif (typeof s.model !== 'object' || s.model === null) return false;\n\tif (!('tables' in s.model) || !('relations' in s.model)) return false;\n\tif (!Array.isArray(s.tableNames)) return false;\n\t// Validate required ModelIR methods are present\n\tif (typeof s.model.getTable !== 'function') return false;\n\tif (typeof s.model.getRelation !== 'function') return false;\n\tif (typeof s.model.getRelationsFrom !== 'function') return false;\n\tif (typeof s.model.getRelationsTo !== 'function') return false;\n\tif (typeof s.model.isAmbiguous !== 'function') return false;\n\treturn true;\n}\n","import type { ColumnListInput } from './column-list.js';\nimport { toColumnList } from './column-list.js';\nimport type { ForeignKeyIR, RelationIR } from './model-ir.js';\n\nconst DEFAULT_REFERENCED_COLUMN = 'id';\n\nexport type RelationKeyDirection = 'belongsTo' | 'inverse';\n\nexport type RelationKeyForeignKey = Pick<ForeignKeyIR, 'references'> & {\n\treadonly columns: ColumnListInput;\n};\n\nexport interface RelationKeyBuilderOptions {\n\treadonly defaultReferencedColumn?: string;\n\treadonly foreignKeyShape?: ColumnListInput;\n\treadonly referencedKeyShape?: ColumnListInput;\n}\n\nexport type RelationKeyFields = Pick<RelationIR, 'foreignKey'> &\n\tPartial<Pick<RelationIR, 'sourceKey' | 'targetKey'>>;\n\nfunction columnListValue(\n\tcolumns: readonly string[],\n\tshape?: ColumnListInput,\n): string | readonly string[] {\n\tif (shape !== undefined && typeof shape !== 'string') return columns;\n\t// biome-ignore lint/style/noNonNullAssertion: columns.length === 1 guaranteed by the ternary condition on this line\n\treturn columns.length === 1 ? columns[0]! : columns;\n}\n\n/**\n * Build RelationIR key fields from a foreign key while preserving both sides.\n *\n * The local FK columns and referenced columns are normalized with toColumnList()\n * so composite keys cannot silently degrade to their first column.\n */\nexport function buildRelationKeyFields(\n\tfk: RelationKeyForeignKey,\n\tdirection: RelationKeyDirection,\n\toptions: RelationKeyBuilderOptions = {},\n): RelationKeyFields {\n\tconst foreignKeyColumns = toColumnList(fk.columns);\n\tconst referencedColumns = toColumnList(fk.references.columns);\n\n\tif (foreignKeyColumns.length !== referencedColumns.length) {\n\t\tthrow new Error(\n\t\t\t`Foreign key column count (${foreignKeyColumns.length}) must match referenced column count (${referencedColumns.length})`,\n\t\t);\n\t}\n\n\tconst foreignKey = columnListValue(\n\t\tforeignKeyColumns,\n\t\toptions.foreignKeyShape,\n\t);\n\tconst defaultReferencedColumn =\n\t\toptions.defaultReferencedColumn ?? DEFAULT_REFERENCED_COLUMN;\n\tconst isImplicitDefaultReference =\n\t\toptions.referencedKeyShape === undefined &&\n\t\treferencedColumns.length === 1 &&\n\t\treferencedColumns[0] === defaultReferencedColumn;\n\n\tif (!isImplicitDefaultReference) {\n\t\tconst referencedKey = columnListValue(\n\t\t\treferencedColumns,\n\t\t\toptions.referencedKeyShape,\n\t\t);\n\t\tif (direction === 'belongsTo') {\n\t\t\treturn { foreignKey, targetKey: referencedKey };\n\t\t}\n\t\treturn { foreignKey, sourceKey: referencedKey };\n\t}\n\n\treturn { foreignKey };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -240,6 +240,8 @@ interface IndexIR {
|
|
|
240
240
|
readonly columns: readonly string[];
|
|
241
241
|
/** Whether this is a unique index */
|
|
242
242
|
readonly unique?: boolean;
|
|
243
|
+
/** PG15+ — for a UNIQUE index, treat NULLs as not distinct (`NULLS NOT DISTINCT`); ignored for non-unique indexes */
|
|
244
|
+
readonly nullsNotDistinct?: boolean;
|
|
243
245
|
/** Index access method (default: btree) */
|
|
244
246
|
readonly method?: string;
|
|
245
247
|
/** Partial index predicate (WHERE clause) */
|
|
@@ -358,6 +360,8 @@ interface AmbiguityCheckResult {
|
|
|
358
360
|
interface ModelIR {
|
|
359
361
|
/** Table definitions indexed by name */
|
|
360
362
|
readonly tables: ReadonlyMap<string, TableIR>;
|
|
363
|
+
/** Declared tables outside this managed model that foreign keys may reference */
|
|
364
|
+
readonly externalTables?: ReadonlySet<string>;
|
|
361
365
|
/** Relation definitions indexed by "source.name" */
|
|
362
366
|
readonly relations: ReadonlyMap<string, RelationIR>;
|
|
363
367
|
/** ENUM type definitions indexed by name */
|
|
@@ -3210,6 +3214,8 @@ type CreateIndexOptions = {
|
|
|
3210
3214
|
readonly with?: Readonly<Record<string, unknown>>;
|
|
3211
3215
|
readonly where?: string;
|
|
3212
3216
|
readonly unique?: boolean;
|
|
3217
|
+
/** PG15+ — for UNIQUE indexes, emit NULLS NOT DISTINCT. Ignored for non-unique indexes. */
|
|
3218
|
+
readonly nullsNotDistinct?: boolean;
|
|
3213
3219
|
readonly ifNotExists?: boolean;
|
|
3214
3220
|
readonly concurrently?: boolean;
|
|
3215
3221
|
};
|
package/dist/index.js
CHANGED
package/dist/internal.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbsp/types",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Shared type definitions for the db-semantic-planner ecosystem",
|
|
5
5
|
"author": "Olivier Orabona <oorabona@users.noreply.github.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=
|
|
8
|
+
"node": ">=22"
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
11
|
"typescript",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"dist"
|
|
44
44
|
],
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@types/node": "^
|
|
47
|
-
"typescript": "~
|
|
46
|
+
"@types/node": "^26.1.0",
|
|
47
|
+
"typescript": "~6.0.3",
|
|
48
48
|
"tsup": "^8.5.1",
|
|
49
|
-
"vitest": "^4.1.
|
|
49
|
+
"vitest": "^4.1.9"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsup",
|