@tanstack/db 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +0 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs +1 -5
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/types.d.cts +1 -0
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +2 -2
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +1 -2
- package/dist/cjs/query/ir.cjs +2 -2
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +2 -2
- package/dist/cjs/query/live-query-collection.cjs +1 -1
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +1 -1
- package/dist/esm/index.js +0 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.js +3 -7
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +1 -0
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js +3 -3
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -2
- package/dist/esm/query/ir.d.ts +2 -2
- package/dist/esm/query/ir.js +2 -2
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +1 -1
- package/dist/esm/query/live-query-collection.js +2 -2
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/package.json +1 -1
- package/src/query/builder/ref-proxy.ts +2 -2
- package/src/query/builder/types.ts +4 -0
- package/src/query/compiler/evaluators.ts +2 -2
- package/src/query/compiler/group-by.ts +3 -3
- package/src/query/index.ts +1 -11
- package/src/query/ir.ts +2 -2
- package/src/query/live-query-collection.ts +12 -9
package/dist/cjs/index.cjs
CHANGED
|
@@ -8,7 +8,6 @@ const proxy = require("./proxy.cjs");
|
|
|
8
8
|
const optimisticAction = require("./optimistic-action.cjs");
|
|
9
9
|
const index = require("./query/builder/index.cjs");
|
|
10
10
|
const functions = require("./query/builder/functions.cjs");
|
|
11
|
-
const refProxy = require("./query/builder/ref-proxy.cjs");
|
|
12
11
|
const index$1 = require("./query/compiler/index.cjs");
|
|
13
12
|
const liveQueryCollection = require("./query/live-query-collection.cjs");
|
|
14
13
|
exports.CollectionImpl = collection.CollectionImpl;
|
|
@@ -48,9 +47,6 @@ exports.not = functions.not;
|
|
|
48
47
|
exports.or = functions.or;
|
|
49
48
|
exports.sum = functions.sum;
|
|
50
49
|
exports.upper = functions.upper;
|
|
51
|
-
exports.isRefProxy = refProxy.isRefProxy;
|
|
52
|
-
exports.toExpression = refProxy.toExpression;
|
|
53
|
-
exports.val = refProxy.val;
|
|
54
50
|
exports.compileQuery = index$1.compileQuery;
|
|
55
51
|
exports.createLiveQueryCollection = liveQueryCollection.createLiveQueryCollection;
|
|
56
52
|
exports.liveQueryCollectionOptions = liveQueryCollection.liveQueryCollectionOptions;
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -76,7 +76,7 @@ function createRefProxy(aliases) {
|
|
|
76
76
|
}
|
|
77
77
|
function toExpression(value) {
|
|
78
78
|
if (isRefProxy(value)) {
|
|
79
|
-
return new ir.
|
|
79
|
+
return new ir.PropRef(value.__path);
|
|
80
80
|
}
|
|
81
81
|
if (value && typeof value === `object` && `type` in value && (value.type === `func` || value.type === `ref` || value.type === `val` || value.type === `agg`)) {
|
|
82
82
|
return value;
|
|
@@ -86,11 +86,7 @@ function toExpression(value) {
|
|
|
86
86
|
function isRefProxy(value) {
|
|
87
87
|
return value && typeof value === `object` && value.__refProxy === true;
|
|
88
88
|
}
|
|
89
|
-
function val(value) {
|
|
90
|
-
return new ir.Value(value);
|
|
91
|
-
}
|
|
92
89
|
exports.createRefProxy = createRefProxy;
|
|
93
90
|
exports.isRefProxy = isRefProxy;
|
|
94
91
|
exports.toExpression = toExpression;
|
|
95
|
-
exports.val = val;
|
|
96
92
|
//# sourceMappingURL=ref-proxy.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ref-proxy.cjs","sources":["../../../../src/query/builder/ref-proxy.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"ref-proxy.cjs","sources":["../../../../src/query/builder/ref-proxy.ts"],"sourcesContent":["import { PropRef, Value } from \"../ir.js\"\nimport type { BasicExpression } from \"../ir.js\"\n\nexport interface RefProxy<T = any> {\n /** @internal */\n readonly __refProxy: true\n /** @internal */\n readonly __path: Array<string>\n /** @internal */\n readonly __type: T\n}\n\n/**\n * Creates a proxy object that records property access paths\n * Used in callbacks like where, select, etc. to create type-safe references\n */\nexport function createRefProxy<T extends Record<string, any>>(\n aliases: Array<string>\n): RefProxy<T> & T {\n const cache = new Map<string, any>()\n const spreadSentinels = new Set<string>() // Track which aliases have been spread\n\n function createProxy(path: Array<string>): any {\n const pathKey = path.join(`.`)\n if (cache.has(pathKey)) {\n return cache.get(pathKey)\n }\n\n const proxy = new Proxy({} as any, {\n get(target, prop, receiver) {\n if (prop === `__refProxy`) return true\n if (prop === `__path`) return path\n if (prop === `__type`) return undefined // Type is only for TypeScript inference\n if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)\n\n const newPath = [...path, String(prop)]\n return createProxy(newPath)\n },\n\n has(target, prop) {\n if (prop === `__refProxy` || prop === `__path` || prop === `__type`)\n return true\n return Reflect.has(target, prop)\n },\n\n ownKeys(target) {\n // If this is a table-level proxy (path length 1), mark it as spread\n if (path.length === 1) {\n const aliasName = path[0]!\n spreadSentinels.add(aliasName)\n }\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target, prop) {\n if (prop === `__refProxy` || prop === `__path` || prop === `__type`) {\n return { enumerable: false, configurable: true }\n }\n return Reflect.getOwnPropertyDescriptor(target, prop)\n },\n })\n\n cache.set(pathKey, proxy)\n return proxy\n }\n\n // Create the root proxy with all aliases as top-level properties\n const rootProxy = new Proxy({} as any, {\n get(target, prop, receiver) {\n if (prop === `__refProxy`) return true\n if (prop === `__path`) return []\n if (prop === `__type`) return undefined // Type is only for TypeScript inference\n if (prop === `__spreadSentinels`) return spreadSentinels // Expose spread sentinels\n if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)\n\n const propStr = String(prop)\n if (aliases.includes(propStr)) {\n return createProxy([propStr])\n }\n\n return undefined\n },\n\n has(target, prop) {\n if (\n prop === `__refProxy` ||\n prop === `__path` ||\n prop === `__type` ||\n prop === `__spreadSentinels`\n )\n return true\n if (typeof prop === `string` && aliases.includes(prop)) return true\n return Reflect.has(target, prop)\n },\n\n ownKeys(_target) {\n return [...aliases, `__refProxy`, `__path`, `__type`, `__spreadSentinels`]\n },\n\n getOwnPropertyDescriptor(target, prop) {\n if (\n prop === `__refProxy` ||\n prop === `__path` ||\n prop === `__type` ||\n prop === `__spreadSentinels`\n ) {\n return { enumerable: false, configurable: true }\n }\n if (typeof prop === `string` && aliases.includes(prop)) {\n return { enumerable: true, configurable: true }\n }\n return undefined\n },\n })\n\n return rootProxy\n}\n\n/**\n * Converts a value to an Expression\n * If it's a RefProxy, creates a Ref, otherwise creates a Value\n */\nexport function toExpression<T = any>(value: T): BasicExpression<T>\nexport function toExpression(value: RefProxy<any>): BasicExpression<any>\nexport function toExpression(value: any): BasicExpression<any> {\n if (isRefProxy(value)) {\n return new PropRef(value.__path)\n }\n // If it's already an Expression (Func, Ref, Value) or Agg, return it directly\n if (\n value &&\n typeof value === `object` &&\n `type` in value &&\n (value.type === `func` ||\n value.type === `ref` ||\n value.type === `val` ||\n value.type === `agg`)\n ) {\n return value\n }\n return new Value(value)\n}\n\n/**\n * Type guard to check if a value is a RefProxy\n */\nexport function isRefProxy(value: any): value is RefProxy {\n return value && typeof value === `object` && value.__refProxy === true\n}\n\n/**\n * Helper to create a Value expression from a literal\n */\nexport function val<T>(value: T): BasicExpression<T> {\n return new Value(value)\n}\n"],"names":["PropRef","Value"],"mappings":"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"}
|
|
@@ -37,6 +37,7 @@ export type RefProxyFor<T> = OmitRefProxy<IsExactlyUndefined<T> extends true ? R
|
|
|
37
37
|
} & RefProxy<T> : RefProxy<T> : T extends Record<string, any> ? {
|
|
38
38
|
[K in keyof T]: T[K] extends Record<string, any> ? RefProxyFor<T[K]> & RefProxy<T[K]> : RefProxy<T[K]>;
|
|
39
39
|
} & RefProxy<T> : RefProxy<T>>;
|
|
40
|
+
export type Ref<T> = RefProxyFor<T>;
|
|
40
41
|
type OmitRefProxy<T> = Omit<T, `__refProxy` | `__path` | `__type`>;
|
|
41
42
|
export interface RefProxy<T = any> {
|
|
42
43
|
/** @internal */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluators.cjs","sources":["../../../../src/query/compiler/evaluators.ts"],"sourcesContent":["import type { BasicExpression, Func, Ref } from \"../ir.js\"\nimport type { NamespacedRow } from \"../../types.js\"\n\n/**\n * Compiled expression evaluator function type\n */\nexport type CompiledExpression = (namespacedRow: NamespacedRow) => any\n\n/**\n * Compiles an expression into an optimized evaluator function.\n * This eliminates branching during evaluation by pre-compiling the expression structure.\n */\nexport function compileExpression(expr: BasicExpression): CompiledExpression {\n switch (expr.type) {\n case `val`: {\n // For constant values, return a function that just returns the value\n const value = expr.value\n return () => value\n }\n\n case `ref`: {\n // For references, pre-compile the property path navigation\n return compileRef(expr)\n }\n\n case `func`: {\n // For functions, pre-compile the function and its arguments\n return compileFunction(expr)\n }\n\n default:\n throw new Error(`Unknown expression type: ${(expr as any).type}`)\n }\n}\n\n/**\n * Compiles a reference expression into an optimized evaluator\n */\nfunction compileRef(ref: Ref): CompiledExpression {\n const [tableAlias, ...propertyPath] = ref.path\n\n if (!tableAlias) {\n throw new Error(`Reference path cannot be empty`)\n }\n\n // Pre-compile the property path navigation\n if (propertyPath.length === 0) {\n // Simple table reference\n return (namespacedRow) => namespacedRow[tableAlias]\n } else if (propertyPath.length === 1) {\n // Single property access - most common case\n const prop = propertyPath[0]!\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n return tableData?.[prop]\n }\n } else {\n // Multiple property navigation\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n if (tableData === undefined) {\n return undefined\n }\n\n let value: any = tableData\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n }\n}\n\n/**\n * Compiles a function expression into an optimized evaluator\n */\nfunction compileFunction(func: Func): CompiledExpression {\n // Pre-compile all arguments\n const compiledArgs = func.args.map(compileExpression)\n\n switch (func.name) {\n // Comparison operators\n case `eq`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a === b\n }\n }\n case `gt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a > b\n }\n }\n case `gte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a >= b\n }\n }\n case `lt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a < b\n }\n }\n case `lte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a <= b\n }\n }\n\n // Boolean operators\n case `and`:\n return (namespacedRow) => {\n for (const compiledArg of compiledArgs) {\n if (!compiledArg(namespacedRow)) {\n return false\n }\n }\n return true\n }\n case `or`:\n return (namespacedRow) => {\n for (const compiledArg of compiledArgs) {\n if (compiledArg(namespacedRow)) {\n return true\n }\n }\n return false\n }\n case `not`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => !arg(namespacedRow)\n }\n\n // Array operators\n case `in`: {\n const valueEvaluator = compiledArgs[0]!\n const arrayEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const array = arrayEvaluator(namespacedRow)\n if (!Array.isArray(array)) {\n return false\n }\n return array.includes(value)\n }\n }\n\n // String operators\n case `like`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const pattern = patternEvaluator(namespacedRow)\n return evaluateLike(value, pattern, false)\n }\n }\n case `ilike`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const pattern = patternEvaluator(namespacedRow)\n return evaluateLike(value, pattern, true)\n }\n }\n\n // String functions\n case `upper`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n return typeof value === `string` ? value.toUpperCase() : value\n }\n }\n case `lower`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n return typeof value === `string` ? value.toLowerCase() : value\n }\n }\n case `length`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n if (typeof value === `string`) {\n return value.length\n }\n if (Array.isArray(value)) {\n return value.length\n }\n return 0\n }\n }\n case `concat`:\n return (namespacedRow) => {\n return compiledArgs\n .map((evaluator) => {\n const arg = evaluator(namespacedRow)\n try {\n return String(arg ?? ``)\n } catch {\n try {\n return JSON.stringify(arg) || ``\n } catch {\n return `[object]`\n }\n }\n })\n .join(``)\n }\n case `coalesce`:\n return (namespacedRow) => {\n for (const evaluator of compiledArgs) {\n const value = evaluator(namespacedRow)\n if (value !== null && value !== undefined) {\n return value\n }\n }\n return null\n }\n\n // Math functions\n case `add`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) + (b ?? 0)\n }\n }\n case `subtract`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) - (b ?? 0)\n }\n }\n case `multiply`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) * (b ?? 0)\n }\n }\n case `divide`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n const divisor = b ?? 0\n return divisor !== 0 ? (a ?? 0) / divisor : null\n }\n }\n\n default:\n throw new Error(`Unknown function: ${func.name}`)\n }\n}\n\n/**\n * Evaluates LIKE/ILIKE patterns\n */\nfunction evaluateLike(\n value: any,\n pattern: any,\n caseInsensitive: boolean\n): boolean {\n if (typeof value !== `string` || typeof pattern !== `string`) {\n return false\n }\n\n const searchValue = caseInsensitive ? value.toLowerCase() : value\n const searchPattern = caseInsensitive ? pattern.toLowerCase() : pattern\n\n // Convert SQL LIKE pattern to regex\n // First escape all regex special chars except % and _\n let regexPattern = searchPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, `\\\\$&`)\n\n // Then convert SQL wildcards to regex\n regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence\n regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char\n\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(searchValue)\n}\n"],"names":[],"mappings":";;AAYO,SAAS,kBAAkB,MAA2C;AAC3E,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK,OAAO;AAEV,YAAM,QAAQ,KAAK;AACnB,aAAO,MAAM;AAAA,IAAA;AAAA,IAGf,KAAK,OAAO;AAEV,aAAO,WAAW,IAAI;AAAA,IAAA;AAAA,IAGxB,KAAK,QAAQ;AAEX,aAAO,gBAAgB,IAAI;AAAA,IAAA;AAAA,IAG7B;AACE,YAAM,IAAI,MAAM,4BAA6B,KAAa,IAAI,EAAE;AAAA,EAAA;AAEtE;AAKA,SAAS,WAAW,KAA8B;AAChD,QAAM,CAAC,YAAY,GAAG,YAAY,IAAI,IAAI;AAE1C,MAAI,CAAC,YAAY;AACT,UAAA,IAAI,MAAM,gCAAgC;AAAA,EAAA;AAI9C,MAAA,aAAa,WAAW,GAAG;AAEtB,WAAA,CAAC,kBAAkB,cAAc,UAAU;AAAA,EAAA,WACzC,aAAa,WAAW,GAAG;AAE9B,UAAA,OAAO,aAAa,CAAC;AAC3B,WAAO,CAAC,kBAAkB;AAClB,YAAA,YAAY,cAAc,UAAU;AAC1C,aAAO,uCAAY;AAAA,IACrB;AAAA,EAAA,OACK;AAEL,WAAO,CAAC,kBAAkB;AAClB,YAAA,YAAY,cAAc,UAAU;AAC1C,UAAI,cAAc,QAAW;AACpB,eAAA;AAAA,MAAA;AAGT,UAAI,QAAa;AACjB,iBAAW,QAAQ,cAAc;AAC/B,YAAI,SAAS,MAAM;AACV,iBAAA;AAAA,QAAA;AAET,gBAAQ,MAAM,IAAI;AAAA,MAAA;AAEb,aAAA;AAAA,IACT;AAAA,EAAA;AAEJ;AAKA,SAAS,gBAAgB,MAAgC;AAEvD,QAAM,eAAe,KAAK,KAAK,IAAI,iBAAiB;AAEpD,UAAQ,KAAK,MAAM;AAAA;AAAA,IAEjB,KAAK,MAAM;AACH,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AAC5B,eAAO,MAAM;AAAA,MACf;AAAA,IAAA;AAAA,IAEF,KAAK,MAAM;AACH,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AAC5B,eAAO,IAAI;AAAA,MACb;AAAA,IAAA;AAAA,IAEF,KAAK,OAAO;AACJ,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AAC5B,eAAO,KAAK;AAAA,MACd;AAAA,IAAA;AAAA,IAEF,KAAK,MAAM;AACH,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AAC5B,eAAO,IAAI;AAAA,MACb;AAAA,IAAA;AAAA,IAEF,KAAK,OAAO;AACJ,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AAC5B,eAAO,KAAK;AAAA,MACd;AAAA,IAAA;AAAA;AAAA,IAIF,KAAK;AACH,aAAO,CAAC,kBAAkB;AACxB,mBAAW,eAAe,cAAc;AAClC,cAAA,CAAC,YAAY,aAAa,GAAG;AACxB,mBAAA;AAAA,UAAA;AAAA,QACT;AAEK,eAAA;AAAA,MACT;AAAA,IACF,KAAK;AACH,aAAO,CAAC,kBAAkB;AACxB,mBAAW,eAAe,cAAc;AAClC,cAAA,YAAY,aAAa,GAAG;AACvB,mBAAA;AAAA,UAAA;AAAA,QACT;AAEK,eAAA;AAAA,MACT;AAAA,IACF,KAAK,OAAO;AACJ,YAAA,MAAM,aAAa,CAAC;AAC1B,aAAO,CAAC,kBAAkB,CAAC,IAAI,aAAa;AAAA,IAAA;AAAA;AAAA,IAI9C,KAAK,MAAM;AACH,YAAA,iBAAiB,aAAa,CAAC;AAC/B,YAAA,iBAAiB,aAAa,CAAC;AACrC,aAAO,CAAC,kBAAkB;AAClB,cAAA,QAAQ,eAAe,aAAa;AACpC,cAAA,QAAQ,eAAe,aAAa;AAC1C,YAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AAClB,iBAAA;AAAA,QAAA;AAEF,eAAA,MAAM,SAAS,KAAK;AAAA,MAC7B;AAAA,IAAA;AAAA;AAAA,IAIF,KAAK,QAAQ;AACL,YAAA,iBAAiB,aAAa,CAAC;AAC/B,YAAA,mBAAmB,aAAa,CAAC;AACvC,aAAO,CAAC,kBAAkB;AAClB,cAAA,QAAQ,eAAe,aAAa;AACpC,cAAA,UAAU,iBAAiB,aAAa;AACvC,eAAA,aAAa,OAAO,SAAS,KAAK;AAAA,MAC3C;AAAA,IAAA;AAAA,IAEF,KAAK,SAAS;AACN,YAAA,iBAAiB,aAAa,CAAC;AAC/B,YAAA,mBAAmB,aAAa,CAAC;AACvC,aAAO,CAAC,kBAAkB;AAClB,cAAA,QAAQ,eAAe,aAAa;AACpC,cAAA,UAAU,iBAAiB,aAAa;AACvC,eAAA,aAAa,OAAO,SAAS,IAAI;AAAA,MAC1C;AAAA,IAAA;AAAA;AAAA,IAIF,KAAK,SAAS;AACN,YAAA,MAAM,aAAa,CAAC;AAC1B,aAAO,CAAC,kBAAkB;AAClB,cAAA,QAAQ,IAAI,aAAa;AAC/B,eAAO,OAAO,UAAU,WAAW,MAAM,gBAAgB;AAAA,MAC3D;AAAA,IAAA;AAAA,IAEF,KAAK,SAAS;AACN,YAAA,MAAM,aAAa,CAAC;AAC1B,aAAO,CAAC,kBAAkB;AAClB,cAAA,QAAQ,IAAI,aAAa;AAC/B,eAAO,OAAO,UAAU,WAAW,MAAM,gBAAgB;AAAA,MAC3D;AAAA,IAAA;AAAA,IAEF,KAAK,UAAU;AACP,YAAA,MAAM,aAAa,CAAC;AAC1B,aAAO,CAAC,kBAAkB;AAClB,cAAA,QAAQ,IAAI,aAAa;AAC3B,YAAA,OAAO,UAAU,UAAU;AAC7B,iBAAO,MAAM;AAAA,QAAA;AAEX,YAAA,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAO,MAAM;AAAA,QAAA;AAER,eAAA;AAAA,MACT;AAAA,IAAA;AAAA,IAEF,KAAK;AACH,aAAO,CAAC,kBAAkB;AACjB,eAAA,aACJ,IAAI,CAAC,cAAc;AACZ,gBAAA,MAAM,UAAU,aAAa;AAC/B,cAAA;AACK,mBAAA,OAAO,OAAO,EAAE;AAAA,UAAA,QACjB;AACF,gBAAA;AACK,qBAAA,KAAK,UAAU,GAAG,KAAK;AAAA,YAAA,QACxB;AACC,qBAAA;AAAA,YAAA;AAAA,UACT;AAAA,QACF,CACD,EACA,KAAK,EAAE;AAAA,MACZ;AAAA,IACF,KAAK;AACH,aAAO,CAAC,kBAAkB;AACxB,mBAAW,aAAa,cAAc;AAC9B,gBAAA,QAAQ,UAAU,aAAa;AACjC,cAAA,UAAU,QAAQ,UAAU,QAAW;AAClC,mBAAA;AAAA,UAAA;AAAA,QACT;AAEK,eAAA;AAAA,MACT;AAAA;AAAA,IAGF,KAAK,OAAO;AACJ,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AACpB,gBAAA,KAAK,MAAM,KAAK;AAAA,MAC1B;AAAA,IAAA;AAAA,IAEF,KAAK,YAAY;AACT,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AACpB,gBAAA,KAAK,MAAM,KAAK;AAAA,MAC1B;AAAA,IAAA;AAAA,IAEF,KAAK,YAAY;AACT,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AACpB,gBAAA,KAAK,MAAM,KAAK;AAAA,MAC1B;AAAA,IAAA;AAAA,IAEF,KAAK,UAAU;AACP,YAAA,OAAO,aAAa,CAAC;AACrB,YAAA,OAAO,aAAa,CAAC;AAC3B,aAAO,CAAC,kBAAkB;AAClB,cAAA,IAAI,KAAK,aAAa;AACtB,cAAA,IAAI,KAAK,aAAa;AAC5B,cAAM,UAAU,KAAK;AACrB,eAAO,YAAY,KAAK,KAAK,KAAK,UAAU;AAAA,MAC9C;AAAA,IAAA;AAAA,IAGF;AACE,YAAM,IAAI,MAAM,qBAAqB,KAAK,IAAI,EAAE;AAAA,EAAA;AAEtD;AAKA,SAAS,aACP,OACA,SACA,iBACS;AACT,MAAI,OAAO,UAAU,YAAY,OAAO,YAAY,UAAU;AACrD,WAAA;AAAA,EAAA;AAGT,QAAM,cAAc,kBAAkB,MAAM,YAAgB,IAAA;AAC5D,QAAM,gBAAgB,kBAAkB,QAAQ,YAAgB,IAAA;AAIhE,MAAI,eAAe,cAAc,QAAQ,uBAAuB,MAAM;AAGvD,iBAAA,aAAa,QAAQ,MAAM,IAAI;AAC/B,iBAAA,aAAa,QAAQ,MAAM,GAAG;AAE7C,QAAM,QAAQ,IAAI,OAAO,IAAI,YAAY,GAAG;AACrC,SAAA,MAAM,KAAK,WAAW;AAC/B;;"}
|
|
1
|
+
{"version":3,"file":"evaluators.cjs","sources":["../../../../src/query/compiler/evaluators.ts"],"sourcesContent":["import type { BasicExpression, Func, PropRef } from \"../ir.js\"\nimport type { NamespacedRow } from \"../../types.js\"\n\n/**\n * Compiled expression evaluator function type\n */\nexport type CompiledExpression = (namespacedRow: NamespacedRow) => any\n\n/**\n * Compiles an expression into an optimized evaluator function.\n * This eliminates branching during evaluation by pre-compiling the expression structure.\n */\nexport function compileExpression(expr: BasicExpression): CompiledExpression {\n switch (expr.type) {\n case `val`: {\n // For constant values, return a function that just returns the value\n const value = expr.value\n return () => value\n }\n\n case `ref`: {\n // For references, pre-compile the property path navigation\n return compileRef(expr)\n }\n\n case `func`: {\n // For functions, pre-compile the function and its arguments\n return compileFunction(expr)\n }\n\n default:\n throw new Error(`Unknown expression type: ${(expr as any).type}`)\n }\n}\n\n/**\n * Compiles a reference expression into an optimized evaluator\n */\nfunction compileRef(ref: PropRef): CompiledExpression {\n const [tableAlias, ...propertyPath] = ref.path\n\n if (!tableAlias) {\n throw new Error(`Reference path cannot be empty`)\n }\n\n // Pre-compile the property path navigation\n if (propertyPath.length === 0) {\n // Simple table reference\n return (namespacedRow) => namespacedRow[tableAlias]\n } else if (propertyPath.length === 1) {\n // Single property access - most common case\n const prop = propertyPath[0]!\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n return tableData?.[prop]\n }\n } else {\n // Multiple property navigation\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n if (tableData === undefined) {\n return undefined\n }\n\n let value: any = tableData\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n }\n}\n\n/**\n * Compiles a function expression into an optimized evaluator\n */\nfunction compileFunction(func: Func): CompiledExpression {\n // Pre-compile all arguments\n const compiledArgs = func.args.map(compileExpression)\n\n switch (func.name) {\n // Comparison operators\n case `eq`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a === b\n }\n }\n case `gt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a > b\n }\n }\n case `gte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a >= b\n }\n }\n case `lt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a < b\n }\n }\n case `lte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a <= b\n }\n }\n\n // Boolean operators\n case `and`:\n return (namespacedRow) => {\n for (const compiledArg of compiledArgs) {\n if (!compiledArg(namespacedRow)) {\n return false\n }\n }\n return true\n }\n case `or`:\n return (namespacedRow) => {\n for (const compiledArg of compiledArgs) {\n if (compiledArg(namespacedRow)) {\n return true\n }\n }\n return false\n }\n case `not`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => !arg(namespacedRow)\n }\n\n // Array operators\n case `in`: {\n const valueEvaluator = compiledArgs[0]!\n const arrayEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const array = arrayEvaluator(namespacedRow)\n if (!Array.isArray(array)) {\n return false\n }\n return array.includes(value)\n }\n }\n\n // String operators\n case `like`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const pattern = patternEvaluator(namespacedRow)\n return evaluateLike(value, pattern, false)\n }\n }\n case `ilike`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const pattern = patternEvaluator(namespacedRow)\n return evaluateLike(value, pattern, true)\n }\n }\n\n // String functions\n case `upper`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n return typeof value === `string` ? value.toUpperCase() : value\n }\n }\n case `lower`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n return typeof value === `string` ? value.toLowerCase() : value\n }\n }\n case `length`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n if (typeof value === `string`) {\n return value.length\n }\n if (Array.isArray(value)) {\n return value.length\n }\n return 0\n }\n }\n case `concat`:\n return (namespacedRow) => {\n return compiledArgs\n .map((evaluator) => {\n const arg = evaluator(namespacedRow)\n try {\n return String(arg ?? ``)\n } catch {\n try {\n return JSON.stringify(arg) || ``\n } catch {\n return `[object]`\n }\n }\n })\n .join(``)\n }\n case `coalesce`:\n return (namespacedRow) => {\n for (const evaluator of compiledArgs) {\n const value = evaluator(namespacedRow)\n if (value !== null && value !== undefined) {\n return value\n }\n }\n return null\n }\n\n // Math functions\n case `add`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) + (b ?? 0)\n }\n }\n case `subtract`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) - (b ?? 0)\n }\n }\n case `multiply`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) * (b ?? 0)\n }\n }\n case `divide`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n const divisor = b ?? 0\n return divisor !== 0 ? (a ?? 0) / divisor : null\n }\n }\n\n default:\n throw new Error(`Unknown function: ${func.name}`)\n }\n}\n\n/**\n * Evaluates LIKE/ILIKE patterns\n */\nfunction evaluateLike(\n value: any,\n pattern: any,\n caseInsensitive: boolean\n): boolean {\n if (typeof value !== `string` || typeof pattern !== `string`) {\n return false\n }\n\n const searchValue = caseInsensitive ? value.toLowerCase() : value\n const searchPattern = caseInsensitive ? pattern.toLowerCase() : pattern\n\n // Convert SQL LIKE pattern to regex\n // First escape all regex special chars except % and _\n let regexPattern = searchPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, `\\\\$&`)\n\n // Then convert SQL wildcards to regex\n regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence\n regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char\n\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(searchValue)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -232,7 +232,7 @@ function transformHavingClause(havingExpr, selectClause) {
|
|
|
232
232
|
const aggExpr = havingExpr;
|
|
233
233
|
for (const [alias, selectExpr] of Object.entries(selectClause)) {
|
|
234
234
|
if (selectExpr.type === `agg` && aggregatesEqual(aggExpr, selectExpr)) {
|
|
235
|
-
return new ir.
|
|
235
|
+
return new ir.PropRef([`result`, alias]);
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
throw new Error(
|
|
@@ -251,7 +251,7 @@ function transformHavingClause(havingExpr, selectClause) {
|
|
|
251
251
|
if (refExpr.path.length === 1) {
|
|
252
252
|
const alias = refExpr.path[0];
|
|
253
253
|
if (selectClause[alias]) {
|
|
254
|
-
return new ir.
|
|
254
|
+
return new ir.PropRef([`result`, alias]);
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
return havingExpr;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group-by.cjs","sources":["../../../../src/query/compiler/group-by.ts"],"sourcesContent":["import { filter, groupBy, groupByOperators, map } from \"@electric-sql/d2mini\"\nimport { Func, Ref } from \"../ir.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type {\n Aggregate,\n BasicExpression,\n GroupBy,\n Having,\n Select,\n} from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\n\nconst { sum, count, avg, min, max } = groupByOperators\n\n/**\n * Interface for caching the mapping between GROUP BY expressions and SELECT expressions\n */\ninterface GroupBySelectMapping {\n selectToGroupByIndex: Map<string, number> // Maps SELECT alias to GROUP BY expression index\n groupByExpressions: Array<any> // The GROUP BY expressions for reference\n}\n\n/**\n * Validates that all non-aggregate expressions in SELECT are present in GROUP BY\n * and creates a cached mapping for efficient lookup during processing\n */\nfunction validateAndCreateMapping(\n groupByClause: GroupBy,\n selectClause?: Select\n): GroupBySelectMapping {\n const selectToGroupByIndex = new Map<string, number>()\n const groupByExpressions = [...groupByClause]\n\n if (!selectClause) {\n return { selectToGroupByIndex, groupByExpressions }\n }\n\n // Validate each SELECT expression\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n // Aggregate expressions are allowed and don't need to be in GROUP BY\n continue\n }\n\n // Non-aggregate expression must be in GROUP BY\n const groupIndex = groupByExpressions.findIndex((groupExpr) =>\n expressionsEqual(expr, groupExpr)\n )\n\n if (groupIndex === -1) {\n throw new Error(\n `Non-aggregate expression '${alias}' in SELECT must also appear in GROUP BY clause`\n )\n }\n\n // Cache the mapping\n selectToGroupByIndex.set(alias, groupIndex)\n }\n\n return { selectToGroupByIndex, groupByExpressions }\n}\n\n/**\n * Processes the GROUP BY clause with optional HAVING and SELECT\n * Works with the new __select_results structure from early SELECT processing\n */\nexport function processGroupBy(\n pipeline: NamespacedAndKeyedStream,\n groupByClause: GroupBy,\n havingClauses?: Array<Having>,\n selectClause?: Select,\n fnHavingClauses?: Array<(row: any) => any>\n): NamespacedAndKeyedStream {\n // Handle empty GROUP BY (single-group aggregation)\n if (groupByClause.length === 0) {\n // For single-group aggregation, create a single group with all data\n const aggregates: Record<string, any> = {}\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n const aggExpr = expr\n aggregates[alias] = getAggregateFunction(aggExpr)\n }\n }\n }\n\n // Use a constant key for single group\n const keyExtractor = () => ({ __singleGroup: true })\n\n // Apply the groupBy operator with single group\n pipeline = pipeline.pipe(\n groupBy(keyExtractor, aggregates)\n ) as NamespacedAndKeyedStream\n\n // Update __select_results to include aggregate values\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing __select_results from early SELECT processing\n const selectResults = (aggregatedRow as any).__select_results || {}\n const finalResults: Record<string, any> = { ...selectResults }\n\n if (selectClause) {\n // Update with aggregate results\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n finalResults[alias] = aggregatedRow[alias]\n }\n // Non-aggregates keep their original values from early SELECT processing\n }\n }\n\n // Use a single key for the result and update __select_results\n return [\n `single_group`,\n {\n ...aggregatedRow,\n __select_results: finalResults,\n },\n ] as [unknown, Record<string, any>]\n })\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const transformedHavingClause = transformHavingClause(\n havingClause,\n selectClause || {}\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return compiledHaving(namespacedRow)\n })\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n return pipeline\n }\n\n // Multi-group aggregation logic...\n // Validate and create mapping for non-aggregate expressions in SELECT\n const mapping = validateAndCreateMapping(groupByClause, selectClause)\n\n // Pre-compile groupBy expressions\n const compiledGroupByExpressions = groupByClause.map(compileExpression)\n\n // Create a key extractor function using simple __key_X format\n const keyExtractor = ([, row]: [\n string,\n NamespacedRow & { __select_results?: any },\n ]) => {\n // Use the original namespaced row for GROUP BY expressions, not __select_results\n const namespacedRow = { ...row }\n delete (namespacedRow as any).__select_results\n\n const key: Record<string, unknown> = {}\n\n // Use simple __key_X format for each groupBy expression\n for (let i = 0; i < groupByClause.length; i++) {\n const compiledExpr = compiledGroupByExpressions[i]!\n const value = compiledExpr(namespacedRow)\n key[`__key_${i}`] = value\n }\n\n return key\n }\n\n // Create aggregate functions for any aggregated columns in the SELECT clause\n const aggregates: Record<string, any> = {}\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n const aggExpr = expr\n aggregates[alias] = getAggregateFunction(aggExpr)\n }\n }\n }\n\n // Apply the groupBy operator\n pipeline = pipeline.pipe(groupBy(keyExtractor, aggregates))\n\n // Update __select_results to handle GROUP BY results\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing __select_results from early SELECT processing\n const selectResults = (aggregatedRow as any).__select_results || {}\n const finalResults: Record<string, any> = {}\n\n if (selectClause) {\n // Process each SELECT expression\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type !== `agg`) {\n // Use cached mapping to get the corresponding __key_X for non-aggregates\n const groupIndex = mapping.selectToGroupByIndex.get(alias)\n if (groupIndex !== undefined) {\n finalResults[alias] = aggregatedRow[`__key_${groupIndex}`]\n } else {\n // Fallback to original SELECT results\n finalResults[alias] = selectResults[alias]\n }\n } else {\n // Use aggregate results\n finalResults[alias] = aggregatedRow[alias]\n }\n }\n } else {\n // No SELECT clause - just use the group keys\n for (let i = 0; i < groupByClause.length; i++) {\n finalResults[`__key_${i}`] = aggregatedRow[`__key_${i}`]\n }\n }\n\n // Generate a simple key for the live collection using group values\n let finalKey: unknown\n if (groupByClause.length === 1) {\n finalKey = aggregatedRow[`__key_0`]\n } else {\n const keyParts: Array<unknown> = []\n for (let i = 0; i < groupByClause.length; i++) {\n keyParts.push(aggregatedRow[`__key_${i}`])\n }\n finalKey = JSON.stringify(keyParts)\n }\n\n return [\n finalKey,\n {\n ...aggregatedRow,\n __select_results: finalResults,\n },\n ] as [unknown, Record<string, any>]\n })\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const transformedHavingClause = transformHavingClause(\n havingClause,\n selectClause || {}\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return compiledHaving(namespacedRow)\n })\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n return pipeline\n}\n\n/**\n * Helper function to check if two expressions are equal\n */\nfunction expressionsEqual(expr1: any, expr2: any): boolean {\n if (!expr1 || !expr2) return false\n if (expr1.type !== expr2.type) return false\n\n switch (expr1.type) {\n case `ref`:\n // Compare paths as arrays\n if (!expr1.path || !expr2.path) return false\n if (expr1.path.length !== expr2.path.length) return false\n return expr1.path.every(\n (segment: string, i: number) => segment === expr2.path[i]\n )\n case `val`:\n return expr1.value === expr2.value\n case `func`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i])\n )\n )\n case `agg`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i])\n )\n )\n default:\n return false\n }\n}\n\n/**\n * Helper function to get an aggregate function based on the Agg expression\n */\nfunction getAggregateFunction(aggExpr: Aggregate) {\n // Pre-compile the value extractor expression\n const compiledExpr = compileExpression(aggExpr.args[0]!)\n\n // Create a value extractor function for the expression to aggregate\n const valueExtractor = ([, namespacedRow]: [string, NamespacedRow]) => {\n const value = compiledExpr(namespacedRow)\n // Ensure we return a number for numeric aggregate functions\n return typeof value === `number` ? value : value != null ? Number(value) : 0\n }\n\n // Return the appropriate aggregate function\n switch (aggExpr.name.toLowerCase()) {\n case `sum`:\n return sum(valueExtractor)\n case `count`:\n return count() // count() doesn't need a value extractor\n case `avg`:\n return avg(valueExtractor)\n case `min`:\n return min(valueExtractor)\n case `max`:\n return max(valueExtractor)\n default:\n throw new Error(`Unsupported aggregate function: ${aggExpr.name}`)\n }\n}\n\n/**\n * Transforms a HAVING clause to replace Agg expressions with references to computed values\n */\nfunction transformHavingClause(\n havingExpr: BasicExpression | Aggregate,\n selectClause: Select\n): BasicExpression {\n switch (havingExpr.type) {\n case `agg`: {\n const aggExpr = havingExpr\n // Find matching aggregate in SELECT clause\n for (const [alias, selectExpr] of Object.entries(selectClause)) {\n if (selectExpr.type === `agg` && aggregatesEqual(aggExpr, selectExpr)) {\n // Replace with a reference to the computed aggregate\n return new Ref([`result`, alias])\n }\n }\n // If no matching aggregate found in SELECT, throw error\n throw new Error(\n `Aggregate function in HAVING clause must also be in SELECT clause: ${aggExpr.name}`\n )\n }\n\n case `func`: {\n const funcExpr = havingExpr\n // Transform function arguments recursively\n const transformedArgs = funcExpr.args.map(\n (arg: BasicExpression | Aggregate) =>\n transformHavingClause(arg, selectClause)\n )\n return new Func(funcExpr.name, transformedArgs)\n }\n\n case `ref`: {\n const refExpr = havingExpr\n // Check if this is a direct reference to a SELECT alias\n if (refExpr.path.length === 1) {\n const alias = refExpr.path[0]!\n if (selectClause[alias]) {\n // This is a reference to a SELECT alias, convert to result.alias\n return new Ref([`result`, alias])\n }\n }\n // Return as-is for other refs\n return havingExpr as BasicExpression\n }\n\n case `val`:\n // Return as-is\n return havingExpr as BasicExpression\n\n default:\n throw new Error(\n `Unknown expression type in HAVING clause: ${(havingExpr as any).type}`\n )\n }\n}\n\n/**\n * Checks if two aggregate expressions are equal\n */\nfunction aggregatesEqual(agg1: Aggregate, agg2: Aggregate): boolean {\n return (\n agg1.name === agg2.name &&\n agg1.args.length === agg2.args.length &&\n agg1.args.every((arg, i) => expressionsEqual(arg, agg2.args[i]))\n )\n}\n"],"names":["groupByOperators","aggregates","keyExtractor","groupBy","map","compileExpression","filter","Ref","Func"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"group-by.cjs","sources":["../../../../src/query/compiler/group-by.ts"],"sourcesContent":["import { filter, groupBy, groupByOperators, map } from \"@electric-sql/d2mini\"\nimport { Func, PropRef } from \"../ir.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type {\n Aggregate,\n BasicExpression,\n GroupBy,\n Having,\n Select,\n} from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\n\nconst { sum, count, avg, min, max } = groupByOperators\n\n/**\n * Interface for caching the mapping between GROUP BY expressions and SELECT expressions\n */\ninterface GroupBySelectMapping {\n selectToGroupByIndex: Map<string, number> // Maps SELECT alias to GROUP BY expression index\n groupByExpressions: Array<any> // The GROUP BY expressions for reference\n}\n\n/**\n * Validates that all non-aggregate expressions in SELECT are present in GROUP BY\n * and creates a cached mapping for efficient lookup during processing\n */\nfunction validateAndCreateMapping(\n groupByClause: GroupBy,\n selectClause?: Select\n): GroupBySelectMapping {\n const selectToGroupByIndex = new Map<string, number>()\n const groupByExpressions = [...groupByClause]\n\n if (!selectClause) {\n return { selectToGroupByIndex, groupByExpressions }\n }\n\n // Validate each SELECT expression\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n // Aggregate expressions are allowed and don't need to be in GROUP BY\n continue\n }\n\n // Non-aggregate expression must be in GROUP BY\n const groupIndex = groupByExpressions.findIndex((groupExpr) =>\n expressionsEqual(expr, groupExpr)\n )\n\n if (groupIndex === -1) {\n throw new Error(\n `Non-aggregate expression '${alias}' in SELECT must also appear in GROUP BY clause`\n )\n }\n\n // Cache the mapping\n selectToGroupByIndex.set(alias, groupIndex)\n }\n\n return { selectToGroupByIndex, groupByExpressions }\n}\n\n/**\n * Processes the GROUP BY clause with optional HAVING and SELECT\n * Works with the new __select_results structure from early SELECT processing\n */\nexport function processGroupBy(\n pipeline: NamespacedAndKeyedStream,\n groupByClause: GroupBy,\n havingClauses?: Array<Having>,\n selectClause?: Select,\n fnHavingClauses?: Array<(row: any) => any>\n): NamespacedAndKeyedStream {\n // Handle empty GROUP BY (single-group aggregation)\n if (groupByClause.length === 0) {\n // For single-group aggregation, create a single group with all data\n const aggregates: Record<string, any> = {}\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n const aggExpr = expr\n aggregates[alias] = getAggregateFunction(aggExpr)\n }\n }\n }\n\n // Use a constant key for single group\n const keyExtractor = () => ({ __singleGroup: true })\n\n // Apply the groupBy operator with single group\n pipeline = pipeline.pipe(\n groupBy(keyExtractor, aggregates)\n ) as NamespacedAndKeyedStream\n\n // Update __select_results to include aggregate values\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing __select_results from early SELECT processing\n const selectResults = (aggregatedRow as any).__select_results || {}\n const finalResults: Record<string, any> = { ...selectResults }\n\n if (selectClause) {\n // Update with aggregate results\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n finalResults[alias] = aggregatedRow[alias]\n }\n // Non-aggregates keep their original values from early SELECT processing\n }\n }\n\n // Use a single key for the result and update __select_results\n return [\n `single_group`,\n {\n ...aggregatedRow,\n __select_results: finalResults,\n },\n ] as [unknown, Record<string, any>]\n })\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const transformedHavingClause = transformHavingClause(\n havingClause,\n selectClause || {}\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return compiledHaving(namespacedRow)\n })\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n return pipeline\n }\n\n // Multi-group aggregation logic...\n // Validate and create mapping for non-aggregate expressions in SELECT\n const mapping = validateAndCreateMapping(groupByClause, selectClause)\n\n // Pre-compile groupBy expressions\n const compiledGroupByExpressions = groupByClause.map(compileExpression)\n\n // Create a key extractor function using simple __key_X format\n const keyExtractor = ([, row]: [\n string,\n NamespacedRow & { __select_results?: any },\n ]) => {\n // Use the original namespaced row for GROUP BY expressions, not __select_results\n const namespacedRow = { ...row }\n delete (namespacedRow as any).__select_results\n\n const key: Record<string, unknown> = {}\n\n // Use simple __key_X format for each groupBy expression\n for (let i = 0; i < groupByClause.length; i++) {\n const compiledExpr = compiledGroupByExpressions[i]!\n const value = compiledExpr(namespacedRow)\n key[`__key_${i}`] = value\n }\n\n return key\n }\n\n // Create aggregate functions for any aggregated columns in the SELECT clause\n const aggregates: Record<string, any> = {}\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n const aggExpr = expr\n aggregates[alias] = getAggregateFunction(aggExpr)\n }\n }\n }\n\n // Apply the groupBy operator\n pipeline = pipeline.pipe(groupBy(keyExtractor, aggregates))\n\n // Update __select_results to handle GROUP BY results\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing __select_results from early SELECT processing\n const selectResults = (aggregatedRow as any).__select_results || {}\n const finalResults: Record<string, any> = {}\n\n if (selectClause) {\n // Process each SELECT expression\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type !== `agg`) {\n // Use cached mapping to get the corresponding __key_X for non-aggregates\n const groupIndex = mapping.selectToGroupByIndex.get(alias)\n if (groupIndex !== undefined) {\n finalResults[alias] = aggregatedRow[`__key_${groupIndex}`]\n } else {\n // Fallback to original SELECT results\n finalResults[alias] = selectResults[alias]\n }\n } else {\n // Use aggregate results\n finalResults[alias] = aggregatedRow[alias]\n }\n }\n } else {\n // No SELECT clause - just use the group keys\n for (let i = 0; i < groupByClause.length; i++) {\n finalResults[`__key_${i}`] = aggregatedRow[`__key_${i}`]\n }\n }\n\n // Generate a simple key for the live collection using group values\n let finalKey: unknown\n if (groupByClause.length === 1) {\n finalKey = aggregatedRow[`__key_0`]\n } else {\n const keyParts: Array<unknown> = []\n for (let i = 0; i < groupByClause.length; i++) {\n keyParts.push(aggregatedRow[`__key_${i}`])\n }\n finalKey = JSON.stringify(keyParts)\n }\n\n return [\n finalKey,\n {\n ...aggregatedRow,\n __select_results: finalResults,\n },\n ] as [unknown, Record<string, any>]\n })\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const transformedHavingClause = transformHavingClause(\n havingClause,\n selectClause || {}\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return compiledHaving(namespacedRow)\n })\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n return pipeline\n}\n\n/**\n * Helper function to check if two expressions are equal\n */\nfunction expressionsEqual(expr1: any, expr2: any): boolean {\n if (!expr1 || !expr2) return false\n if (expr1.type !== expr2.type) return false\n\n switch (expr1.type) {\n case `ref`:\n // Compare paths as arrays\n if (!expr1.path || !expr2.path) return false\n if (expr1.path.length !== expr2.path.length) return false\n return expr1.path.every(\n (segment: string, i: number) => segment === expr2.path[i]\n )\n case `val`:\n return expr1.value === expr2.value\n case `func`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i])\n )\n )\n case `agg`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i])\n )\n )\n default:\n return false\n }\n}\n\n/**\n * Helper function to get an aggregate function based on the Agg expression\n */\nfunction getAggregateFunction(aggExpr: Aggregate) {\n // Pre-compile the value extractor expression\n const compiledExpr = compileExpression(aggExpr.args[0]!)\n\n // Create a value extractor function for the expression to aggregate\n const valueExtractor = ([, namespacedRow]: [string, NamespacedRow]) => {\n const value = compiledExpr(namespacedRow)\n // Ensure we return a number for numeric aggregate functions\n return typeof value === `number` ? value : value != null ? Number(value) : 0\n }\n\n // Return the appropriate aggregate function\n switch (aggExpr.name.toLowerCase()) {\n case `sum`:\n return sum(valueExtractor)\n case `count`:\n return count() // count() doesn't need a value extractor\n case `avg`:\n return avg(valueExtractor)\n case `min`:\n return min(valueExtractor)\n case `max`:\n return max(valueExtractor)\n default:\n throw new Error(`Unsupported aggregate function: ${aggExpr.name}`)\n }\n}\n\n/**\n * Transforms a HAVING clause to replace Agg expressions with references to computed values\n */\nfunction transformHavingClause(\n havingExpr: BasicExpression | Aggregate,\n selectClause: Select\n): BasicExpression {\n switch (havingExpr.type) {\n case `agg`: {\n const aggExpr = havingExpr\n // Find matching aggregate in SELECT clause\n for (const [alias, selectExpr] of Object.entries(selectClause)) {\n if (selectExpr.type === `agg` && aggregatesEqual(aggExpr, selectExpr)) {\n // Replace with a reference to the computed aggregate\n return new PropRef([`result`, alias])\n }\n }\n // If no matching aggregate found in SELECT, throw error\n throw new Error(\n `Aggregate function in HAVING clause must also be in SELECT clause: ${aggExpr.name}`\n )\n }\n\n case `func`: {\n const funcExpr = havingExpr\n // Transform function arguments recursively\n const transformedArgs = funcExpr.args.map(\n (arg: BasicExpression | Aggregate) =>\n transformHavingClause(arg, selectClause)\n )\n return new Func(funcExpr.name, transformedArgs)\n }\n\n case `ref`: {\n const refExpr = havingExpr\n // Check if this is a direct reference to a SELECT alias\n if (refExpr.path.length === 1) {\n const alias = refExpr.path[0]!\n if (selectClause[alias]) {\n // This is a reference to a SELECT alias, convert to result.alias\n return new PropRef([`result`, alias])\n }\n }\n // Return as-is for other refs\n return havingExpr as BasicExpression\n }\n\n case `val`:\n // Return as-is\n return havingExpr as BasicExpression\n\n default:\n throw new Error(\n `Unknown expression type in HAVING clause: ${(havingExpr as any).type}`\n )\n }\n}\n\n/**\n * Checks if two aggregate expressions are equal\n */\nfunction aggregatesEqual(agg1: Aggregate, agg2: Aggregate): boolean {\n return (\n agg1.name === agg2.name &&\n agg1.args.length === agg2.args.length &&\n agg1.args.every((arg, i) => expressionsEqual(arg, agg2.args[i]))\n )\n}\n"],"names":["groupByOperators","aggregates","keyExtractor","groupBy","map","compileExpression","filter","PropRef","Func"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { BaseQueryBuilder, Query, type InitialQueryBuilder, type QueryBuilder, type Context, type Source, type GetResult, } from './builder/index.js';
|
|
2
2
|
export { eq, gt, gte, lt, lte, and, or, not, inArray, like, ilike, upper, lower, length, concat, coalesce, add, count, avg, sum, min, max, } from './builder/functions.js';
|
|
3
|
-
export {
|
|
4
|
-
export type { QueryIR, BasicExpression as Expression, Aggregate, CollectionRef, QueryRef, JoinClause, } from './ir.js';
|
|
3
|
+
export type { Ref } from './builder/types.js';
|
|
5
4
|
export { compileQuery } from './compiler/index.js';
|
|
6
5
|
export { createLiveQueryCollection, liveQueryCollectionOptions, type LiveQueryCollectionConfig, } from './live-query-collection.js';
|
package/dist/cjs/query/ir.cjs
CHANGED
|
@@ -18,7 +18,7 @@ class QueryRef extends BaseExpression {
|
|
|
18
18
|
this.type = `queryRef`;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
class
|
|
21
|
+
class PropRef extends BaseExpression {
|
|
22
22
|
constructor(path) {
|
|
23
23
|
super();
|
|
24
24
|
this.path = path;
|
|
@@ -51,7 +51,7 @@ class Aggregate extends BaseExpression {
|
|
|
51
51
|
exports.Aggregate = Aggregate;
|
|
52
52
|
exports.CollectionRef = CollectionRef;
|
|
53
53
|
exports.Func = Func;
|
|
54
|
+
exports.PropRef = PropRef;
|
|
54
55
|
exports.QueryRef = QueryRef;
|
|
55
|
-
exports.Ref = Ref;
|
|
56
56
|
exports.Value = Value;
|
|
57
57
|
//# sourceMappingURL=ir.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CollectionImpl } from \"../collection\"\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\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate\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 = BasicExpression<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 direction: OrderByDirection\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\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 constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\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
|
|
1
|
+
{"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CollectionImpl } from \"../collection\"\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\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate\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 = BasicExpression<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 direction: OrderByDirection\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\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 constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\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 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"],"names":[],"mappings":";;AA4DA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACM,UAAA;AAHC,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAAA;AAOhB;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACM,UAAA;AAHC,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAAA;AAOhB;AAEO,MAAM,gBAAyB,eAAkB;AAAA,EAEtD,YACS,MACP;AACM,UAAA;AAFC,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAAA;AAMhB;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACM,UAAA;AAFC,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAAA;AAMhB;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACM,UAAA;AAHC,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAAA;AAOhB;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACM,UAAA;AAHC,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAAA;AAOhB;;;;;;;"}
|
package/dist/cjs/query/ir.d.cts
CHANGED
|
@@ -53,7 +53,7 @@ export declare class QueryRef extends BaseExpression {
|
|
|
53
53
|
type: "queryRef";
|
|
54
54
|
constructor(query: QueryIR, alias: string);
|
|
55
55
|
}
|
|
56
|
-
export declare class
|
|
56
|
+
export declare class PropRef<T = any> extends BaseExpression<T> {
|
|
57
57
|
path: Array<string>;
|
|
58
58
|
type: "ref";
|
|
59
59
|
constructor(path: Array<string>);
|
|
@@ -70,7 +70,7 @@ export declare class Func<T = any> extends BaseExpression<T> {
|
|
|
70
70
|
constructor(name: string, // such as eq, gt, lt, upper, lower, etc.
|
|
71
71
|
args: Array<BasicExpression>);
|
|
72
72
|
}
|
|
73
|
-
export type BasicExpression<T = any> =
|
|
73
|
+
export type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>;
|
|
74
74
|
export declare class Aggregate<T = any> extends BaseExpression<T> {
|
|
75
75
|
name: string;
|
|
76
76
|
args: Array<BasicExpression>;
|
|
@@ -7,7 +7,7 @@ const index = require("./builder/index.cjs");
|
|
|
7
7
|
let liveQueryCollectionCounter = 0;
|
|
8
8
|
function liveQueryCollectionOptions(config) {
|
|
9
9
|
const id = config.id || `live-query-${++liveQueryCollectionCounter}`;
|
|
10
|
-
const query = index.buildQuery(config.query);
|
|
10
|
+
const query = typeof config.query === `function` ? index.buildQuery(config.query) : index.getQueryIR(config.query);
|
|
11
11
|
const resultKeys = /* @__PURE__ */ new WeakMap();
|
|
12
12
|
const orderByIndices = /* @__PURE__ */ new WeakMap();
|
|
13
13
|
const compare = query.orderBy && query.orderBy.length > 0 ? (val1, val2) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@electric-sql/d2mini\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery } from \"./builder/index.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@electric-sql/d2mini\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function\n const query = buildQuery<TContext>(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every(\n (collection) => collection.status === `ready`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n pipelineCache = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>\n )\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n\n graph.finalize()\n\n const maybeRunGraph = () => {\n // We only run the graph if all the collections are ready\n if (allCollectionsReady()) {\n graph.run()\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Set up data flow from input collections to the compiled query\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n\n // Subscribe to changes\n const unsubscribe = collection.subscribeChanges(\n (changes: Array<ChangeMessage>) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph()\n },\n { includeInitialState: true }\n )\n unsubscribeCallbacks.add(unsubscribe)\n })\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n\n // Use a bridge function that handles the type compatibility cleanly\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n\n // Use a bridge function that handles the type compatibility cleanly\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Array<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n}\n\n/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n"],"names":["buildQuery","collection","D2","compileQuery","output","createCollection","MultiSet"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@electric-sql/d2mini\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery, getQueryIR } from \"./builder/index.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@electric-sql/d2mini\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function or instance\n const query =\n typeof config.query === `function`\n ? buildQuery<TContext>(config.query)\n : getQueryIR(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every(\n (collection) => collection.status === `ready`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n pipelineCache = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>\n )\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n\n graph.finalize()\n\n const maybeRunGraph = () => {\n // We only run the graph if all the collections are ready\n if (allCollectionsReady()) {\n graph.run()\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Set up data flow from input collections to the compiled query\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n\n // Subscribe to changes\n const unsubscribe = collection.subscribeChanges(\n (changes: Array<ChangeMessage>) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph()\n },\n { includeInitialState: true }\n )\n unsubscribeCallbacks.add(unsubscribe)\n })\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Array<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n}\n\n/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n"],"names":["buildQuery","getQueryIR","collection","D2","compileQuery","output","createCollection","MultiSet"],"mappings":"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"}
|
|
@@ -35,7 +35,7 @@ export interface LiveQueryCollectionConfig<TContext extends Context, TResult ext
|
|
|
35
35
|
/**
|
|
36
36
|
* Query builder function that defines the live query
|
|
37
37
|
*/
|
|
38
|
-
query: (q: InitialQueryBuilder) => QueryBuilder<TContext>;
|
|
38
|
+
query: ((q: InitialQueryBuilder) => QueryBuilder<TContext>) | QueryBuilder<TContext>;
|
|
39
39
|
/**
|
|
40
40
|
* Function to extract the key from result items
|
|
41
41
|
* If not provided, defaults to using the key from the D2 stream
|
package/dist/esm/index.js
CHANGED
|
@@ -6,7 +6,6 @@ import { createArrayChangeProxy, createChangeProxy, withArrayChangeTracking, wit
|
|
|
6
6
|
import { createOptimisticAction } from "./optimistic-action.js";
|
|
7
7
|
import { BaseQueryBuilder, Query } from "./query/builder/index.js";
|
|
8
8
|
import { add, and, avg, coalesce, concat, count, eq, gt, gte, ilike, inArray, length, like, lower, lt, lte, max, min, not, or, sum, upper } from "./query/builder/functions.js";
|
|
9
|
-
import { isRefProxy, toExpression, val } from "./query/builder/ref-proxy.js";
|
|
10
9
|
import { compileQuery } from "./query/compiler/index.js";
|
|
11
10
|
import { createLiveQueryCollection, liveQueryCollectionOptions } from "./query/live-query-collection.js";
|
|
12
11
|
export {
|
|
@@ -36,7 +35,6 @@ export {
|
|
|
36
35
|
gte,
|
|
37
36
|
ilike,
|
|
38
37
|
inArray,
|
|
39
|
-
isRefProxy,
|
|
40
38
|
length,
|
|
41
39
|
like,
|
|
42
40
|
liveQueryCollectionOptions,
|
|
@@ -48,9 +46,7 @@ export {
|
|
|
48
46
|
not,
|
|
49
47
|
or,
|
|
50
48
|
sum,
|
|
51
|
-
toExpression,
|
|
52
49
|
upper,
|
|
53
|
-
val,
|
|
54
50
|
withArrayChangeTracking,
|
|
55
51
|
withChangeTracking
|
|
56
52
|
};
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PropRef, Value } from "../ir.js";
|
|
2
2
|
function createRefProxy(aliases) {
|
|
3
3
|
const cache = /* @__PURE__ */ new Map();
|
|
4
4
|
const spreadSentinels = /* @__PURE__ */ new Set();
|
|
@@ -74,7 +74,7 @@ function createRefProxy(aliases) {
|
|
|
74
74
|
}
|
|
75
75
|
function toExpression(value) {
|
|
76
76
|
if (isRefProxy(value)) {
|
|
77
|
-
return new
|
|
77
|
+
return new PropRef(value.__path);
|
|
78
78
|
}
|
|
79
79
|
if (value && typeof value === `object` && `type` in value && (value.type === `func` || value.type === `ref` || value.type === `val` || value.type === `agg`)) {
|
|
80
80
|
return value;
|
|
@@ -84,13 +84,9 @@ function toExpression(value) {
|
|
|
84
84
|
function isRefProxy(value) {
|
|
85
85
|
return value && typeof value === `object` && value.__refProxy === true;
|
|
86
86
|
}
|
|
87
|
-
function val(value) {
|
|
88
|
-
return new Value(value);
|
|
89
|
-
}
|
|
90
87
|
export {
|
|
91
88
|
createRefProxy,
|
|
92
89
|
isRefProxy,
|
|
93
|
-
toExpression
|
|
94
|
-
val
|
|
90
|
+
toExpression
|
|
95
91
|
};
|
|
96
92
|
//# sourceMappingURL=ref-proxy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ref-proxy.js","sources":["../../../../src/query/builder/ref-proxy.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"ref-proxy.js","sources":["../../../../src/query/builder/ref-proxy.ts"],"sourcesContent":["import { PropRef, Value } from \"../ir.js\"\nimport type { BasicExpression } from \"../ir.js\"\n\nexport interface RefProxy<T = any> {\n /** @internal */\n readonly __refProxy: true\n /** @internal */\n readonly __path: Array<string>\n /** @internal */\n readonly __type: T\n}\n\n/**\n * Creates a proxy object that records property access paths\n * Used in callbacks like where, select, etc. to create type-safe references\n */\nexport function createRefProxy<T extends Record<string, any>>(\n aliases: Array<string>\n): RefProxy<T> & T {\n const cache = new Map<string, any>()\n const spreadSentinels = new Set<string>() // Track which aliases have been spread\n\n function createProxy(path: Array<string>): any {\n const pathKey = path.join(`.`)\n if (cache.has(pathKey)) {\n return cache.get(pathKey)\n }\n\n const proxy = new Proxy({} as any, {\n get(target, prop, receiver) {\n if (prop === `__refProxy`) return true\n if (prop === `__path`) return path\n if (prop === `__type`) return undefined // Type is only for TypeScript inference\n if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)\n\n const newPath = [...path, String(prop)]\n return createProxy(newPath)\n },\n\n has(target, prop) {\n if (prop === `__refProxy` || prop === `__path` || prop === `__type`)\n return true\n return Reflect.has(target, prop)\n },\n\n ownKeys(target) {\n // If this is a table-level proxy (path length 1), mark it as spread\n if (path.length === 1) {\n const aliasName = path[0]!\n spreadSentinels.add(aliasName)\n }\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target, prop) {\n if (prop === `__refProxy` || prop === `__path` || prop === `__type`) {\n return { enumerable: false, configurable: true }\n }\n return Reflect.getOwnPropertyDescriptor(target, prop)\n },\n })\n\n cache.set(pathKey, proxy)\n return proxy\n }\n\n // Create the root proxy with all aliases as top-level properties\n const rootProxy = new Proxy({} as any, {\n get(target, prop, receiver) {\n if (prop === `__refProxy`) return true\n if (prop === `__path`) return []\n if (prop === `__type`) return undefined // Type is only for TypeScript inference\n if (prop === `__spreadSentinels`) return spreadSentinels // Expose spread sentinels\n if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)\n\n const propStr = String(prop)\n if (aliases.includes(propStr)) {\n return createProxy([propStr])\n }\n\n return undefined\n },\n\n has(target, prop) {\n if (\n prop === `__refProxy` ||\n prop === `__path` ||\n prop === `__type` ||\n prop === `__spreadSentinels`\n )\n return true\n if (typeof prop === `string` && aliases.includes(prop)) return true\n return Reflect.has(target, prop)\n },\n\n ownKeys(_target) {\n return [...aliases, `__refProxy`, `__path`, `__type`, `__spreadSentinels`]\n },\n\n getOwnPropertyDescriptor(target, prop) {\n if (\n prop === `__refProxy` ||\n prop === `__path` ||\n prop === `__type` ||\n prop === `__spreadSentinels`\n ) {\n return { enumerable: false, configurable: true }\n }\n if (typeof prop === `string` && aliases.includes(prop)) {\n return { enumerable: true, configurable: true }\n }\n return undefined\n },\n })\n\n return rootProxy\n}\n\n/**\n * Converts a value to an Expression\n * If it's a RefProxy, creates a Ref, otherwise creates a Value\n */\nexport function toExpression<T = any>(value: T): BasicExpression<T>\nexport function toExpression(value: RefProxy<any>): BasicExpression<any>\nexport function toExpression(value: any): BasicExpression<any> {\n if (isRefProxy(value)) {\n return new PropRef(value.__path)\n }\n // If it's already an Expression (Func, Ref, Value) or Agg, return it directly\n if (\n value &&\n typeof value === `object` &&\n `type` in value &&\n (value.type === `func` ||\n value.type === `ref` ||\n value.type === `val` ||\n value.type === `agg`)\n ) {\n return value\n }\n return new Value(value)\n}\n\n/**\n * Type guard to check if a value is a RefProxy\n */\nexport function isRefProxy(value: any): value is RefProxy {\n return value && typeof value === `object` && value.__refProxy === true\n}\n\n/**\n * Helper to create a Value expression from a literal\n */\nexport function val<T>(value: T): BasicExpression<T> {\n return new Value(value)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -37,6 +37,7 @@ export type RefProxyFor<T> = OmitRefProxy<IsExactlyUndefined<T> extends true ? R
|
|
|
37
37
|
} & RefProxy<T> : RefProxy<T> : T extends Record<string, any> ? {
|
|
38
38
|
[K in keyof T]: T[K] extends Record<string, any> ? RefProxyFor<T[K]> & RefProxy<T[K]> : RefProxy<T[K]>;
|
|
39
39
|
} & RefProxy<T> : RefProxy<T>>;
|
|
40
|
+
export type Ref<T> = RefProxyFor<T>;
|
|
40
41
|
type OmitRefProxy<T> = Omit<T, `__refProxy` | `__path` | `__type`>;
|
|
41
42
|
export interface RefProxy<T = any> {
|
|
42
43
|
/** @internal */
|