UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1 lines 16.7 kB
{"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CompareOptions } from './builder/types'\nimport type { Collection, CollectionImpl } from '../collection/index.js'\nimport type { NamespacedRow } from '../types'\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n distinct?: true\n singleResult?: true\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type IncludesMaterialization =\n | `collection`\n | `array`\n | `singleton`\n | `concat`\n\nexport const INCLUDES_SCALAR_FIELD = `__includes_scalar__`\n\nexport type From = CollectionRef | QueryRef | UnionFrom | UnionAll\n\nexport type Select = {\n [alias: string]:\n | BasicExpression\n | Aggregate\n | Select\n | IncludesSubquery\n | ConditionalSelect\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where =\n | BasicExpression<boolean>\n | { expression: BasicExpression<boolean>; residual?: boolean }\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n compareOptions: CompareOptions\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\nlet nextCollectionSourceId = 0\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n /** Opaque runtime identity; aliases are lexical names only. */\n public readonly sourceId!: string\n constructor(\n public collection: CollectionImpl,\n public alias: string,\n ) {\n super()\n Object.defineProperty(this, `sourceId`, {\n value: `source-${++nextCollectionSourceId}`,\n enumerable: false,\n })\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string,\n ) {\n super()\n }\n}\n\nexport class UnionFrom extends BaseExpression {\n public type = `unionFrom` as const\n constructor(public sources: Array<CollectionRef | QueryRef>) {\n super()\n }\n\n get alias(): string {\n return this.sources[0]?.alias ?? ``\n }\n}\n\nexport class UnionAll extends BaseExpression {\n public type = `unionAll` as const\n /**\n * Result-level UNION ALL. Downstream query clauses see the union result row\n * shape, not the branch source aliases. Optimizers may push safe operations\n * into branches, but compiler phases should treat this as a derived relation\n * unless they are explicitly handling branch lowering.\n */\n constructor(public queries: Array<QueryIR>) {\n super()\n }\n\n get alias(): string {\n return ``\n }\n}\n\nexport class PropRef<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string>, // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T, // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>,\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>,\n ) {\n super()\n }\n}\n\nexport class IncludesSubquery extends BaseExpression {\n public type = `includesSubquery` as const\n constructor(\n public query: QueryIR, // Child query (correlation WHERE removed)\n public correlationField: PropRef, // Parent-side ref (e.g., project.id)\n public childCorrelationField: PropRef, // Child-side ref (e.g., issue.projectId)\n public fieldName: string, // Result field name (e.g., \"issues\")\n public parentFilters?: Array<Where>, // WHERE clauses referencing parent aliases (applied post-join)\n public parentProjection?: Array<PropRef>, // Parent field refs used by parentFilters\n public materialization: IncludesMaterialization = `collection`,\n public scalarField?: string,\n ) {\n super()\n }\n}\n\nexport type ConditionalSelectBranch = {\n condition: BasicExpression\n value: SelectValueExpression\n}\n\nexport type SelectValueExpression =\n | BasicExpression\n | Aggregate\n | Select\n | IncludesSubquery\n | ConditionalSelect\n\nexport class ConditionalSelect extends BaseExpression {\n public type = `conditionalSelect` as const\n constructor(\n public branches: Array<ConditionalSelectBranch>,\n public defaultValue?: SelectValueExpression,\n ) {\n super()\n }\n}\n\n/**\n * Runtime helper to detect IR expression-like objects.\n * Prefer this over ad-hoc local implementations to keep behavior consistent.\n */\nexport function isExpressionLike(value: any): boolean {\n if (\n value instanceof Aggregate ||\n value instanceof ConditionalSelect ||\n value instanceof Func ||\n value instanceof PropRef ||\n value instanceof Value ||\n value instanceof IncludesSubquery\n ) {\n return true\n }\n\n if (!value || typeof value !== `object`) {\n return false\n }\n\n if (value.type === `conditionalSelect`) {\n return Array.isArray(value.branches)\n }\n\n if (value.type === `agg` || value.type === `func`) {\n return typeof value.name === `string` && Array.isArray(value.args)\n }\n\n if (value.type === `ref`) {\n return Array.isArray(value.path)\n }\n\n if (value.type === `val`) {\n return `value` in value\n }\n\n if (value.type === `includesSubquery`) {\n return `query` in value && `fieldName` in value\n }\n\n return false\n}\n\n/** Returns each lexical Collection source in a query tree once. */\nexport function collectCollectionSources(query: QueryIR): Array<CollectionRef> {\n const sources: Array<CollectionRef> = []\n const seen = new Set<string>()\n\n const visitSource = (source: QueryIR[`from`]): void => {\n if (source.type === `collectionRef`) {\n if (!seen.has(source.sourceId)) {\n seen.add(source.sourceId)\n sources.push(source)\n }\n } else if (source.type === `queryRef`) {\n visitQuery(source.query)\n } else if (source.type === `unionFrom`) {\n source.sources.forEach(visitSource)\n } else {\n source.queries.forEach(visitQuery)\n }\n }\n\n const visitSelectValue = (value: any): void => {\n if (value instanceof IncludesSubquery) {\n visitQuery(value.query)\n } else if (value instanceof ConditionalSelect) {\n value.branches.forEach((branch) => visitSelectValue(branch.value))\n if (value.defaultValue !== undefined) {\n visitSelectValue(value.defaultValue)\n }\n } else if (\n value !== null &&\n typeof value === `object` &&\n !Array.isArray(value) &&\n !isExpressionLike(value) &&\n value.__refProxy !== true\n ) {\n Object.values(value).forEach(visitSelectValue)\n }\n }\n\n const visitQuery = (current: QueryIR): void => {\n visitSource(current.from)\n current.join?.forEach(({ from }) => visitSource(from))\n if (current.select) Object.values(current.select).forEach(visitSelectValue)\n }\n\n visitQuery(query)\n return sources\n}\n\n/**\n * Helper functions for working with Where clauses\n */\n\n/**\n * Extract the expression from a Where clause\n */\nexport function getWhereExpression(where: Where): BasicExpression<boolean> {\n return typeof where === `object` && `expression` in where\n ? where.expression\n : where\n}\n\n/**\n * Extract the expression from a HAVING clause\n * HAVING clauses can contain aggregates, unlike regular WHERE clauses\n */\nexport function getHavingExpression(\n having: Having,\n): BasicExpression | Aggregate {\n return typeof having === `object` && `expression` in having\n ? having.expression\n : having\n}\n\n/**\n * Check if a Where clause is marked as residual\n */\nexport function isResidualWhere(where: Where): boolean {\n return (\n typeof where === `object` &&\n `expression` in where &&\n where.residual === true\n )\n}\n\n/**\n * Create a residual Where clause from an expression\n */\nexport function createResidualWhere(\n expression: BasicExpression<boolean>,\n): Where {\n return { expression, residual: true }\n}\n\nfunction getRefFromAlias(\n query: QueryIR,\n alias: string,\n): CollectionRef | QueryRef | void {\n if (query.from.type === `unionFrom`) {\n for (const source of query.from.sources) {\n if (source.alias === alias) {\n return source\n }\n }\n } else if (query.from.type !== `unionAll` && query.from.alias === alias) {\n return query.from\n }\n\n for (const join of query.join || []) {\n if (join.from.alias === alias) {\n return join.from\n }\n }\n}\n\n/**\n * Follows the given reference in a query\n * until its finds the root field the reference points to.\n * @returns The collection, its alias, and the path to the root field in this collection.\n * `alias` is the alias under which the resolved collection is referenced in the\n * query it was reached from (when the ref crosses into a joined source). It is\n * left undefined when the ref simply resolves to a field on the passed-in\n * `collection`, in which case the caller already knows the alias.\n */\nexport function followRef(\n query: QueryIR,\n ref: PropRef<any>,\n collection: Collection,\n): {\n collection: Collection\n path: Array<string>\n alias?: string\n sourceId?: string\n} | void {\n if (ref.path.length === 0) {\n return\n }\n\n if (ref.path.length === 1) {\n // This field should be part of this collection\n const field = ref.path[0]!\n // is it part of the select clause?\n if (query.select) {\n const selectedField = query.select[field]\n if (selectedField && selectedField.type === `ref`) {\n return followRef(query, selectedField, collection)\n }\n }\n\n // Either this field is not part of the select clause\n // and thus it must be part of the collection itself\n // or it is part of the select but is not a reference\n // so we can stop here and don't have to follow it\n return { collection, path: [field] }\n }\n\n if (ref.path.length > 1) {\n // This is a nested field\n const [alias, ...rest] = ref.path\n const aliasRef = getRefFromAlias(query, alias!)\n if (!aliasRef) {\n return\n }\n\n if (aliasRef.type === `queryRef`) {\n return followRef(aliasRef.query, new PropRef(rest), collection)\n } else {\n // This is a reference to a collection\n // we can't follow it further\n // so the field must be on the collection itself.\n // Report the alias too: when the ref crossed a join, this is the source\n // that actually holds the field (which may differ from the from clause).\n return {\n collection: aliasRef.collection,\n path: rest,\n alias,\n sourceId: aliasRef.sourceId,\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}