@tanstack/db 0.0.5 → 0.0.7

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.
Files changed (55) hide show
  1. package/dist/cjs/collection.cjs +86 -27
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +30 -13
  4. package/dist/cjs/index.cjs +1 -1
  5. package/dist/cjs/index.d.cts +1 -1
  6. package/dist/cjs/query/compiled-query.cjs +1 -1
  7. package/dist/cjs/query/compiled-query.cjs.map +1 -1
  8. package/dist/cjs/query/compiled-query.d.cts +1 -1
  9. package/dist/cjs/query/evaluators.cjs +15 -0
  10. package/dist/cjs/query/evaluators.cjs.map +1 -1
  11. package/dist/cjs/query/evaluators.d.cts +5 -1
  12. package/dist/cjs/query/pipeline-compiler.cjs +2 -2
  13. package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
  14. package/dist/cjs/query/query-builder.cjs +22 -17
  15. package/dist/cjs/query/query-builder.cjs.map +1 -1
  16. package/dist/cjs/query/query-builder.d.cts +12 -2
  17. package/dist/cjs/query/schema.d.cts +11 -5
  18. package/dist/cjs/query/select.cjs +12 -0
  19. package/dist/cjs/query/select.cjs.map +1 -1
  20. package/dist/cjs/transactions.cjs +3 -1
  21. package/dist/cjs/transactions.cjs.map +1 -1
  22. package/dist/cjs/types.d.cts +33 -0
  23. package/dist/esm/collection.d.ts +30 -13
  24. package/dist/esm/collection.js +87 -28
  25. package/dist/esm/collection.js.map +1 -1
  26. package/dist/esm/index.d.ts +1 -1
  27. package/dist/esm/index.js +2 -2
  28. package/dist/esm/query/compiled-query.d.ts +1 -1
  29. package/dist/esm/query/compiled-query.js +2 -2
  30. package/dist/esm/query/compiled-query.js.map +1 -1
  31. package/dist/esm/query/evaluators.d.ts +5 -1
  32. package/dist/esm/query/evaluators.js +16 -1
  33. package/dist/esm/query/evaluators.js.map +1 -1
  34. package/dist/esm/query/pipeline-compiler.js +3 -3
  35. package/dist/esm/query/pipeline-compiler.js.map +1 -1
  36. package/dist/esm/query/query-builder.d.ts +12 -2
  37. package/dist/esm/query/query-builder.js +22 -17
  38. package/dist/esm/query/query-builder.js.map +1 -1
  39. package/dist/esm/query/schema.d.ts +11 -5
  40. package/dist/esm/query/select.js +12 -0
  41. package/dist/esm/query/select.js.map +1 -1
  42. package/dist/esm/transactions.js +3 -1
  43. package/dist/esm/transactions.js.map +1 -1
  44. package/dist/esm/types.d.ts +33 -0
  45. package/package.json +1 -1
  46. package/src/collection.ts +164 -45
  47. package/src/index.ts +1 -1
  48. package/src/query/compiled-query.ts +4 -3
  49. package/src/query/evaluators.ts +27 -0
  50. package/src/query/pipeline-compiler.ts +3 -3
  51. package/src/query/query-builder.ts +40 -23
  52. package/src/query/schema.ts +17 -5
  53. package/src/query/select.ts +24 -1
  54. package/src/transactions.ts +8 -1
  55. package/src/types.ts +38 -0
@@ -2,6 +2,20 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const extractors = require("./extractors.cjs");
4
4
  const utils = require("./utils.cjs");
5
+ function evaluateWhereOnNamespacedRow(namespacedRow, where, mainTableAlias, joinedTableAlias) {
6
+ return where.every((item) => {
7
+ if (typeof item === `function`) {
8
+ return item(namespacedRow);
9
+ } else {
10
+ return evaluateConditionOnNamespacedRow(
11
+ namespacedRow,
12
+ item,
13
+ mainTableAlias,
14
+ joinedTableAlias
15
+ );
16
+ }
17
+ });
18
+ }
5
19
  function evaluateConditionOnNamespacedRow(namespacedRow, condition, mainTableAlias, joinedTableAlias) {
6
20
  if (condition.length === 3 && !Array.isArray(condition[0])) {
7
21
  const [left, comparator, right] = condition;
@@ -143,4 +157,5 @@ function evaluateSimpleConditionOnNamespacedRow(namespacedRow, left, comparator,
143
157
  }
144
158
  exports.evaluateConditionOnNamespacedRow = evaluateConditionOnNamespacedRow;
145
159
  exports.evaluateSimpleConditionOnNamespacedRow = evaluateSimpleConditionOnNamespacedRow;
160
+ exports.evaluateWhereOnNamespacedRow = evaluateWhereOnNamespacedRow;
146
161
  //# sourceMappingURL=evaluators.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"evaluators.cjs","sources":["../../../src/query/evaluators.ts"],"sourcesContent":["import { evaluateOperandOnNamespacedRow } from \"./extractors.js\"\nimport { compareValues, convertLikeToRegex, isValueInArray } from \"./utils.js\"\nimport type {\n Comparator,\n Condition,\n ConditionOperand,\n LogicalOperator,\n SimpleCondition,\n} from \"./schema.js\"\nimport type { NamespacedRow } from \"../types.js\"\n\n/**\n * Evaluates a condition against a nested row structure\n */\nexport function evaluateConditionOnNamespacedRow(\n namespacedRow: NamespacedRow,\n condition: Condition,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): boolean {\n // Handle simple conditions with exactly 3 elements\n if (condition.length === 3 && !Array.isArray(condition[0])) {\n const [left, comparator, right] = condition as SimpleCondition\n return evaluateSimpleConditionOnNamespacedRow(\n namespacedRow,\n left,\n comparator,\n right,\n mainTableAlias,\n joinedTableAlias\n )\n }\n\n // Handle flat composite conditions (multiple conditions in a single array)\n if (\n condition.length > 3 &&\n !Array.isArray(condition[0]) &&\n typeof condition[1] === `string` &&\n ![`and`, `or`].includes(condition[1] as string)\n ) {\n // Start with the first condition (first 3 elements)\n let result = evaluateSimpleConditionOnNamespacedRow(\n namespacedRow,\n condition[0],\n condition[1] as Comparator,\n condition[2],\n mainTableAlias,\n joinedTableAlias\n )\n\n // Process the rest in groups: logical operator, then 3 elements for each condition\n for (let i = 3; i < condition.length; i += 4) {\n const logicalOp = condition[i] as LogicalOperator\n\n // Make sure we have a complete condition to evaluate\n if (i + 3 <= condition.length) {\n const nextResult = evaluateSimpleConditionOnNamespacedRow(\n namespacedRow,\n condition[i + 1],\n condition[i + 2] as Comparator,\n condition[i + 3],\n mainTableAlias,\n joinedTableAlias\n )\n\n // Apply the logical operator\n if (logicalOp === `and`) {\n result = result && nextResult\n } else {\n // logicalOp === `or`\n result = result || nextResult\n }\n }\n }\n\n return result\n }\n\n // Handle nested composite conditions where the first element is an array\n if (condition.length > 0 && Array.isArray(condition[0])) {\n // Start with the first condition\n let result = evaluateConditionOnNamespacedRow(\n namespacedRow,\n condition[0] as Condition,\n mainTableAlias,\n joinedTableAlias\n )\n\n // Process the rest of the conditions and logical operators in pairs\n for (let i = 1; i < condition.length; i += 2) {\n if (i + 1 >= condition.length) break // Make sure we have a pair\n\n const operator = condition[i] as LogicalOperator\n const nextCondition = condition[i + 1] as Condition\n\n // Apply the logical operator\n if (operator === `and`) {\n result =\n result &&\n evaluateConditionOnNamespacedRow(\n namespacedRow,\n nextCondition,\n mainTableAlias,\n joinedTableAlias\n )\n } else {\n // logicalOp === `or`\n result =\n result ||\n evaluateConditionOnNamespacedRow(\n namespacedRow,\n nextCondition,\n mainTableAlias,\n joinedTableAlias\n )\n }\n }\n\n return result\n }\n\n // Fallback - this should not happen with valid conditions\n return true\n}\n\n/**\n * Evaluates a simple condition against a nested row structure\n */\nexport function evaluateSimpleConditionOnNamespacedRow(\n namespacedRow: Record<string, unknown>,\n left: ConditionOperand,\n comparator: Comparator,\n right: ConditionOperand,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): boolean {\n const leftValue = evaluateOperandOnNamespacedRow(\n namespacedRow,\n left,\n mainTableAlias,\n joinedTableAlias\n )\n\n const rightValue = evaluateOperandOnNamespacedRow(\n namespacedRow,\n right,\n mainTableAlias,\n joinedTableAlias\n )\n\n // The rest of the function remains the same as evaluateSimpleCondition\n switch (comparator) {\n case `=`:\n return leftValue === rightValue\n case `!=`:\n return leftValue !== rightValue\n case `<`:\n return compareValues(leftValue, rightValue, `<`)\n case `<=`:\n return compareValues(leftValue, rightValue, `<=`)\n case `>`:\n return compareValues(leftValue, rightValue, `>`)\n case `>=`:\n return compareValues(leftValue, rightValue, `>=`)\n case `like`:\n case `not like`:\n if (typeof leftValue === `string` && typeof rightValue === `string`) {\n // Convert SQL LIKE pattern to proper regex pattern\n const pattern = convertLikeToRegex(rightValue)\n const matches = new RegExp(`^${pattern}$`, `i`).test(leftValue)\n return comparator === `like` ? matches : !matches\n }\n return comparator === `like` ? false : true\n case `in`:\n // If right value is not an array, we can't do an IN operation\n if (!Array.isArray(rightValue)) {\n return false\n }\n\n // For empty arrays, nothing is contained in them\n if (rightValue.length === 0) {\n return false\n }\n\n // Handle array-to-array comparison (check if any element in leftValue exists in rightValue)\n if (Array.isArray(leftValue)) {\n return leftValue.some((item) => isValueInArray(item, rightValue))\n }\n\n // Handle single value comparison\n return isValueInArray(leftValue, rightValue)\n\n case `not in`:\n // If right value is not an array, everything is \"not in\" it\n if (!Array.isArray(rightValue)) {\n return true\n }\n\n // For empty arrays, everything is \"not in\" them\n if (rightValue.length === 0) {\n return true\n }\n\n // Handle array-to-array comparison (check if no element in leftValue exists in rightValue)\n if (Array.isArray(leftValue)) {\n return !leftValue.some((item) => isValueInArray(item, rightValue))\n }\n\n // Handle single value comparison\n return !isValueInArray(leftValue, rightValue)\n\n case `is`:\n return leftValue === rightValue\n case `is not`:\n // Properly handle null/undefined checks\n if (rightValue === null) {\n return leftValue !== null && leftValue !== undefined\n }\n return leftValue !== rightValue\n default:\n return false\n }\n}\n"],"names":["evaluateOperandOnNamespacedRow","compareValues","convertLikeToRegex","isValueInArray"],"mappings":"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"}
1
+ {"version":3,"file":"evaluators.cjs","sources":["../../../src/query/evaluators.ts"],"sourcesContent":["import { evaluateOperandOnNamespacedRow } from \"./extractors.js\"\nimport { compareValues, convertLikeToRegex, isValueInArray } from \"./utils.js\"\nimport type {\n Comparator,\n Condition,\n ConditionOperand,\n LogicalOperator,\n SimpleCondition,\n Where,\n WhereCallback,\n} from \"./schema.js\"\nimport type { NamespacedRow } from \"../types.js\"\n\n/**\n * Evaluates a Where clause (which is always an array of conditions and/or callbacks) against a nested row structure\n */\nexport function evaluateWhereOnNamespacedRow(\n namespacedRow: NamespacedRow,\n where: Where,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): boolean {\n // Where is always an array of conditions and/or callbacks\n // Evaluate all items and combine with AND logic\n return where.every((item) => {\n if (typeof item === `function`) {\n return (item as WhereCallback)(namespacedRow)\n } else {\n return evaluateConditionOnNamespacedRow(\n namespacedRow,\n item as Condition,\n mainTableAlias,\n joinedTableAlias\n )\n }\n })\n}\n\n/**\n * Evaluates a condition against a nested row structure\n */\nexport function evaluateConditionOnNamespacedRow(\n namespacedRow: NamespacedRow,\n condition: Condition,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): boolean {\n // Handle simple conditions with exactly 3 elements\n if (condition.length === 3 && !Array.isArray(condition[0])) {\n const [left, comparator, right] = condition as SimpleCondition\n return evaluateSimpleConditionOnNamespacedRow(\n namespacedRow,\n left,\n comparator,\n right,\n mainTableAlias,\n joinedTableAlias\n )\n }\n\n // Handle flat composite conditions (multiple conditions in a single array)\n if (\n condition.length > 3 &&\n !Array.isArray(condition[0]) &&\n typeof condition[1] === `string` &&\n ![`and`, `or`].includes(condition[1] as string)\n ) {\n // Start with the first condition (first 3 elements)\n let result = evaluateSimpleConditionOnNamespacedRow(\n namespacedRow,\n condition[0],\n condition[1] as Comparator,\n condition[2],\n mainTableAlias,\n joinedTableAlias\n )\n\n // Process the rest in groups: logical operator, then 3 elements for each condition\n for (let i = 3; i < condition.length; i += 4) {\n const logicalOp = condition[i] as LogicalOperator\n\n // Make sure we have a complete condition to evaluate\n if (i + 3 <= condition.length) {\n const nextResult = evaluateSimpleConditionOnNamespacedRow(\n namespacedRow,\n condition[i + 1],\n condition[i + 2] as Comparator,\n condition[i + 3],\n mainTableAlias,\n joinedTableAlias\n )\n\n // Apply the logical operator\n if (logicalOp === `and`) {\n result = result && nextResult\n } else {\n // logicalOp === `or`\n result = result || nextResult\n }\n }\n }\n\n return result\n }\n\n // Handle nested composite conditions where the first element is an array\n if (condition.length > 0 && Array.isArray(condition[0])) {\n // Start with the first condition\n let result = evaluateConditionOnNamespacedRow(\n namespacedRow,\n condition[0] as Condition,\n mainTableAlias,\n joinedTableAlias\n )\n\n // Process the rest of the conditions and logical operators in pairs\n for (let i = 1; i < condition.length; i += 2) {\n if (i + 1 >= condition.length) break // Make sure we have a pair\n\n const operator = condition[i] as LogicalOperator\n const nextCondition = condition[i + 1] as Condition\n\n // Apply the logical operator\n if (operator === `and`) {\n result =\n result &&\n evaluateConditionOnNamespacedRow(\n namespacedRow,\n nextCondition,\n mainTableAlias,\n joinedTableAlias\n )\n } else {\n // logicalOp === `or`\n result =\n result ||\n evaluateConditionOnNamespacedRow(\n namespacedRow,\n nextCondition,\n mainTableAlias,\n joinedTableAlias\n )\n }\n }\n\n return result\n }\n\n // Fallback - this should not happen with valid conditions\n return true\n}\n\n/**\n * Evaluates a simple condition against a nested row structure\n */\nexport function evaluateSimpleConditionOnNamespacedRow(\n namespacedRow: Record<string, unknown>,\n left: ConditionOperand,\n comparator: Comparator,\n right: ConditionOperand,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): boolean {\n const leftValue = evaluateOperandOnNamespacedRow(\n namespacedRow,\n left,\n mainTableAlias,\n joinedTableAlias\n )\n\n const rightValue = evaluateOperandOnNamespacedRow(\n namespacedRow,\n right,\n mainTableAlias,\n joinedTableAlias\n )\n\n // The rest of the function remains the same as evaluateSimpleCondition\n switch (comparator) {\n case `=`:\n return leftValue === rightValue\n case `!=`:\n return leftValue !== rightValue\n case `<`:\n return compareValues(leftValue, rightValue, `<`)\n case `<=`:\n return compareValues(leftValue, rightValue, `<=`)\n case `>`:\n return compareValues(leftValue, rightValue, `>`)\n case `>=`:\n return compareValues(leftValue, rightValue, `>=`)\n case `like`:\n case `not like`:\n if (typeof leftValue === `string` && typeof rightValue === `string`) {\n // Convert SQL LIKE pattern to proper regex pattern\n const pattern = convertLikeToRegex(rightValue)\n const matches = new RegExp(`^${pattern}$`, `i`).test(leftValue)\n return comparator === `like` ? matches : !matches\n }\n return comparator === `like` ? false : true\n case `in`:\n // If right value is not an array, we can't do an IN operation\n if (!Array.isArray(rightValue)) {\n return false\n }\n\n // For empty arrays, nothing is contained in them\n if (rightValue.length === 0) {\n return false\n }\n\n // Handle array-to-array comparison (check if any element in leftValue exists in rightValue)\n if (Array.isArray(leftValue)) {\n return leftValue.some((item) => isValueInArray(item, rightValue))\n }\n\n // Handle single value comparison\n return isValueInArray(leftValue, rightValue)\n\n case `not in`:\n // If right value is not an array, everything is \"not in\" it\n if (!Array.isArray(rightValue)) {\n return true\n }\n\n // For empty arrays, everything is \"not in\" them\n if (rightValue.length === 0) {\n return true\n }\n\n // Handle array-to-array comparison (check if no element in leftValue exists in rightValue)\n if (Array.isArray(leftValue)) {\n return !leftValue.some((item) => isValueInArray(item, rightValue))\n }\n\n // Handle single value comparison\n return !isValueInArray(leftValue, rightValue)\n\n case `is`:\n return leftValue === rightValue\n case `is not`:\n // Properly handle null/undefined checks\n if (rightValue === null) {\n return leftValue !== null && leftValue !== undefined\n }\n return leftValue !== rightValue\n default:\n return false\n }\n}\n"],"names":["evaluateOperandOnNamespacedRow","compareValues","convertLikeToRegex","isValueInArray"],"mappings":"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"}
@@ -1,5 +1,9 @@
1
- import { Comparator, Condition, ConditionOperand } from './schema.js';
1
+ import { Comparator, Condition, ConditionOperand, Where } from './schema.js';
2
2
  import { NamespacedRow } from '../types.js';
3
+ /**
4
+ * Evaluates a Where clause (which is always an array of conditions and/or callbacks) against a nested row structure
5
+ */
6
+ export declare function evaluateWhereOnNamespacedRow(namespacedRow: NamespacedRow, where: Where, mainTableAlias?: string, joinedTableAlias?: string): boolean;
3
7
  /**
4
8
  * Evaluates a condition against a nested row structure
5
9
  */
@@ -49,7 +49,7 @@ function compileQueryPipeline(query, inputs) {
49
49
  if (query.where) {
50
50
  pipeline = pipeline.pipe(
51
51
  d2ts.filter(([_key, row]) => {
52
- const result = evaluators.evaluateConditionOnNamespacedRow(
52
+ const result = evaluators.evaluateWhereOnNamespacedRow(
53
53
  row,
54
54
  query.where,
55
55
  mainTableAlias
@@ -64,7 +64,7 @@ function compileQueryPipeline(query, inputs) {
64
64
  if (query.having) {
65
65
  pipeline = pipeline.pipe(
66
66
  d2ts.filter(([_key, row]) => {
67
- const result = evaluators.evaluateConditionOnNamespacedRow(
67
+ const result = evaluators.evaluateWhereOnNamespacedRow(
68
68
  row,
69
69
  query.having,
70
70
  mainTableAlias
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline-compiler.cjs","sources":["../../../src/query/pipeline-compiler.ts"],"sourcesContent":["import { filter, map } from \"@electric-sql/d2ts\"\nimport { evaluateConditionOnNamespacedRow } from \"./evaluators.js\"\nimport { processJoinClause } from \"./joins.js\"\nimport { processGroupBy } from \"./group-by.js\"\nimport { processOrderBy } from \"./order-by.js\"\nimport { processSelect } from \"./select.js\"\nimport type { Query } from \"./schema.js\"\nimport type { IStreamBuilder } from \"@electric-sql/d2ts\"\nimport type {\n InputRow,\n KeyedStream,\n NamespacedAndKeyedStream,\n} from \"../types.js\"\n\n/**\n * Compiles a query into a D2 pipeline\n * @param query The query to compile\n * @param inputs Mapping of table names to input streams\n * @returns A stream builder representing the compiled query\n */\nexport function compileQueryPipeline<T extends IStreamBuilder<unknown>>(\n query: Query,\n inputs: Record<string, KeyedStream>\n): T {\n // Create a copy of the inputs map to avoid modifying the original\n const allInputs = { ...inputs }\n\n // Process WITH queries if they exist\n if (query.with && query.with.length > 0) {\n // Process each WITH query in order\n for (const withQuery of query.with) {\n // Ensure the WITH query has an alias\n if (!withQuery.as) {\n throw new Error(`WITH query must have an \"as\" property`)\n }\n\n // Check if this CTE name already exists in the inputs\n if (allInputs[withQuery.as]) {\n throw new Error(`CTE with name \"${withQuery.as}\" already exists`)\n }\n\n // Create a new query without the 'with' property to avoid circular references\n const withQueryWithoutWith = { ...withQuery, with: undefined }\n\n // Compile the WITH query using the current set of inputs\n // (which includes previously compiled WITH queries)\n const compiledWithQuery = compileQueryPipeline(\n withQueryWithoutWith,\n allInputs\n )\n\n // Add the compiled query to the inputs map using its alias\n allInputs[withQuery.as] = compiledWithQuery as KeyedStream\n }\n }\n\n // Create a map of table aliases to inputs\n const tables: Record<string, KeyedStream> = {}\n\n // The main table is the one in the FROM clause\n const mainTableAlias = query.as || query.from\n\n // Get the main input from the inputs map (now including CTEs)\n const input = allInputs[query.from]\n if (!input) {\n throw new Error(`Input for table \"${query.from}\" not found in inputs map`)\n }\n\n tables[mainTableAlias] = input\n\n // Prepare the initial pipeline with the main table wrapped in its alias\n let pipeline: NamespacedAndKeyedStream = input.pipe(\n map(([key, row]) => {\n // Initialize the record with a nested structure\n const ret = [key, { [mainTableAlias]: row }] as [\n string,\n Record<string, typeof row>,\n ]\n return ret\n })\n )\n\n // Process JOIN clauses if they exist\n if (query.join) {\n pipeline = processJoinClause(\n pipeline,\n query,\n tables,\n mainTableAlias,\n allInputs\n )\n }\n\n // Process the WHERE clause if it exists\n if (query.where) {\n pipeline = pipeline.pipe(\n filter(([_key, row]) => {\n const result = evaluateConditionOnNamespacedRow(\n row,\n query.where!,\n mainTableAlias\n )\n return result\n })\n )\n }\n\n // Process the GROUP BY clause if it exists\n if (query.groupBy) {\n pipeline = processGroupBy(pipeline, query, mainTableAlias)\n }\n\n // Process the HAVING clause if it exists\n // This works similarly to WHERE but is applied after any aggregations\n if (query.having) {\n pipeline = pipeline.pipe(\n filter(([_key, row]) => {\n // For HAVING, we're working with the flattened row that contains both\n // the group by keys and the aggregate results directly\n const result = evaluateConditionOnNamespacedRow(\n row,\n query.having!,\n mainTableAlias\n )\n return result\n })\n )\n }\n\n // Process orderBy parameter if it exists\n if (query.orderBy) {\n pipeline = processOrderBy(pipeline, query, mainTableAlias)\n } else if (query.limit !== undefined || query.offset !== undefined) {\n // If there's a limit or offset without orderBy, throw an error\n throw new Error(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n\n // Process the SELECT clause - this is where we flatten the structure\n const resultPipeline: KeyedStream | NamespacedAndKeyedStream = query.select\n ? processSelect(pipeline, query, mainTableAlias, allInputs)\n : !query.join && !query.groupBy\n ? pipeline.pipe(\n map(([key, row]) => [key, row[mainTableAlias]] as InputRow)\n )\n : pipeline\n return resultPipeline as T\n}\n"],"names":["map","processJoinClause","filter","evaluateConditionOnNamespacedRow","processGroupBy","processOrderBy","processSelect"],"mappings":";;;;;;;;AAoBgB,SAAA,qBACd,OACA,QACG;AAEG,QAAA,YAAY,EAAE,GAAG,OAAO;AAG9B,MAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,GAAG;AAE5B,eAAA,aAAa,MAAM,MAAM;AAE9B,UAAA,CAAC,UAAU,IAAI;AACX,cAAA,IAAI,MAAM,uCAAuC;AAAA,MAAA;AAIrD,UAAA,UAAU,UAAU,EAAE,GAAG;AAC3B,cAAM,IAAI,MAAM,kBAAkB,UAAU,EAAE,kBAAkB;AAAA,MAAA;AAIlE,YAAM,uBAAuB,EAAE,GAAG,WAAW,MAAM,OAAU;AAI7D,YAAM,oBAAoB;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAGU,gBAAA,UAAU,EAAE,IAAI;AAAA,IAAA;AAAA,EAC5B;AAIF,QAAM,SAAsC,CAAC;AAGvC,QAAA,iBAAiB,MAAM,MAAM,MAAM;AAGnC,QAAA,QAAQ,UAAU,MAAM,IAAI;AAClC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB,MAAM,IAAI,2BAA2B;AAAA,EAAA;AAG3E,SAAO,cAAc,IAAI;AAGzB,MAAI,WAAqC,MAAM;AAAA,IAC7CA,KAAAA,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM;AAEZ,YAAA,MAAM,CAAC,KAAK,EAAE,CAAC,cAAc,GAAG,KAAK;AAIpC,aAAA;AAAA,IACR,CAAA;AAAA,EACH;AAGA,MAAI,MAAM,MAAM;AACH,eAAAC,MAAA;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EAAA;AAIF,MAAI,MAAM,OAAO;AACf,eAAW,SAAS;AAAA,MAClBC,KAAAA,OAAO,CAAC,CAAC,MAAM,GAAG,MAAM;AACtB,cAAM,SAASC,WAAA;AAAA,UACb;AAAA,UACA,MAAM;AAAA,UACN;AAAA,QACF;AACO,eAAA;AAAA,MACR,CAAA;AAAA,IACH;AAAA,EAAA;AAIF,MAAI,MAAM,SAAS;AACN,eAAAC,QAAA,eAAe,UAAU,OAAO,cAAc;AAAA,EAAA;AAK3D,MAAI,MAAM,QAAQ;AAChB,eAAW,SAAS;AAAA,MAClBF,KAAAA,OAAO,CAAC,CAAC,MAAM,GAAG,MAAM;AAGtB,cAAM,SAASC,WAAA;AAAA,UACb;AAAA,UACA,MAAM;AAAA,UACN;AAAA,QACF;AACO,eAAA;AAAA,MACR,CAAA;AAAA,IACH;AAAA,EAAA;AAIF,MAAI,MAAM,SAAS;AACN,eAAAE,QAAA,eAAe,UAAU,OAAO,cAAc;AAAA,EAAA,WAChD,MAAM,UAAU,UAAa,MAAM,WAAW,QAAW;AAElE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EAAA;AAIF,QAAM,iBAAyD,MAAM,SACjEC,OAAAA,cAAc,UAAU,OAAO,gBAAgB,SAAS,IACxD,CAAC,MAAM,QAAQ,CAAC,MAAM,UACpB,SAAS;AAAA,IACPN,SAAI,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,cAAc,CAAC,CAAa;AAAA,EAAA,IAE5D;AACC,SAAA;AACT;;"}
1
+ {"version":3,"file":"pipeline-compiler.cjs","sources":["../../../src/query/pipeline-compiler.ts"],"sourcesContent":["import { filter, map } from \"@electric-sql/d2ts\"\nimport { evaluateWhereOnNamespacedRow } from \"./evaluators.js\"\nimport { processJoinClause } from \"./joins.js\"\nimport { processGroupBy } from \"./group-by.js\"\nimport { processOrderBy } from \"./order-by.js\"\nimport { processSelect } from \"./select.js\"\nimport type { Query } from \"./schema.js\"\nimport type { IStreamBuilder } from \"@electric-sql/d2ts\"\nimport type {\n InputRow,\n KeyedStream,\n NamespacedAndKeyedStream,\n} from \"../types.js\"\n\n/**\n * Compiles a query into a D2 pipeline\n * @param query The query to compile\n * @param inputs Mapping of table names to input streams\n * @returns A stream builder representing the compiled query\n */\nexport function compileQueryPipeline<T extends IStreamBuilder<unknown>>(\n query: Query,\n inputs: Record<string, KeyedStream>\n): T {\n // Create a copy of the inputs map to avoid modifying the original\n const allInputs = { ...inputs }\n\n // Process WITH queries if they exist\n if (query.with && query.with.length > 0) {\n // Process each WITH query in order\n for (const withQuery of query.with) {\n // Ensure the WITH query has an alias\n if (!withQuery.as) {\n throw new Error(`WITH query must have an \"as\" property`)\n }\n\n // Check if this CTE name already exists in the inputs\n if (allInputs[withQuery.as]) {\n throw new Error(`CTE with name \"${withQuery.as}\" already exists`)\n }\n\n // Create a new query without the 'with' property to avoid circular references\n const withQueryWithoutWith = { ...withQuery, with: undefined }\n\n // Compile the WITH query using the current set of inputs\n // (which includes previously compiled WITH queries)\n const compiledWithQuery = compileQueryPipeline(\n withQueryWithoutWith,\n allInputs\n )\n\n // Add the compiled query to the inputs map using its alias\n allInputs[withQuery.as] = compiledWithQuery as KeyedStream\n }\n }\n\n // Create a map of table aliases to inputs\n const tables: Record<string, KeyedStream> = {}\n\n // The main table is the one in the FROM clause\n const mainTableAlias = query.as || query.from\n\n // Get the main input from the inputs map (now including CTEs)\n const input = allInputs[query.from]\n if (!input) {\n throw new Error(`Input for table \"${query.from}\" not found in inputs map`)\n }\n\n tables[mainTableAlias] = input\n\n // Prepare the initial pipeline with the main table wrapped in its alias\n let pipeline: NamespacedAndKeyedStream = input.pipe(\n map(([key, row]) => {\n // Initialize the record with a nested structure\n const ret = [key, { [mainTableAlias]: row }] as [\n string,\n Record<string, typeof row>,\n ]\n return ret\n })\n )\n\n // Process JOIN clauses if they exist\n if (query.join) {\n pipeline = processJoinClause(\n pipeline,\n query,\n tables,\n mainTableAlias,\n allInputs\n )\n }\n\n // Process the WHERE clause if it exists\n if (query.where) {\n pipeline = pipeline.pipe(\n filter(([_key, row]) => {\n const result = evaluateWhereOnNamespacedRow(\n row,\n query.where!,\n mainTableAlias\n )\n return result\n })\n )\n }\n\n // Process the GROUP BY clause if it exists\n if (query.groupBy) {\n pipeline = processGroupBy(pipeline, query, mainTableAlias)\n }\n\n // Process the HAVING clause if it exists\n // This works similarly to WHERE but is applied after any aggregations\n if (query.having) {\n pipeline = pipeline.pipe(\n filter(([_key, row]) => {\n // For HAVING, we're working with the flattened row that contains both\n // the group by keys and the aggregate results directly\n const result = evaluateWhereOnNamespacedRow(\n row,\n query.having!,\n mainTableAlias\n )\n return result\n })\n )\n }\n\n // Process orderBy parameter if it exists\n if (query.orderBy) {\n pipeline = processOrderBy(pipeline, query, mainTableAlias)\n } else if (query.limit !== undefined || query.offset !== undefined) {\n // If there's a limit or offset without orderBy, throw an error\n throw new Error(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n\n // Process the SELECT clause - this is where we flatten the structure\n const resultPipeline: KeyedStream | NamespacedAndKeyedStream = query.select\n ? processSelect(pipeline, query, mainTableAlias, allInputs)\n : !query.join && !query.groupBy\n ? pipeline.pipe(\n map(([key, row]) => [key, row[mainTableAlias]] as InputRow)\n )\n : pipeline\n return resultPipeline as T\n}\n"],"names":["map","processJoinClause","filter","evaluateWhereOnNamespacedRow","processGroupBy","processOrderBy","processSelect"],"mappings":";;;;;;;;AAoBgB,SAAA,qBACd,OACA,QACG;AAEG,QAAA,YAAY,EAAE,GAAG,OAAO;AAG9B,MAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,GAAG;AAE5B,eAAA,aAAa,MAAM,MAAM;AAE9B,UAAA,CAAC,UAAU,IAAI;AACX,cAAA,IAAI,MAAM,uCAAuC;AAAA,MAAA;AAIrD,UAAA,UAAU,UAAU,EAAE,GAAG;AAC3B,cAAM,IAAI,MAAM,kBAAkB,UAAU,EAAE,kBAAkB;AAAA,MAAA;AAIlE,YAAM,uBAAuB,EAAE,GAAG,WAAW,MAAM,OAAU;AAI7D,YAAM,oBAAoB;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAGU,gBAAA,UAAU,EAAE,IAAI;AAAA,IAAA;AAAA,EAC5B;AAIF,QAAM,SAAsC,CAAC;AAGvC,QAAA,iBAAiB,MAAM,MAAM,MAAM;AAGnC,QAAA,QAAQ,UAAU,MAAM,IAAI;AAClC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB,MAAM,IAAI,2BAA2B;AAAA,EAAA;AAG3E,SAAO,cAAc,IAAI;AAGzB,MAAI,WAAqC,MAAM;AAAA,IAC7CA,KAAAA,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM;AAEZ,YAAA,MAAM,CAAC,KAAK,EAAE,CAAC,cAAc,GAAG,KAAK;AAIpC,aAAA;AAAA,IACR,CAAA;AAAA,EACH;AAGA,MAAI,MAAM,MAAM;AACH,eAAAC,MAAA;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EAAA;AAIF,MAAI,MAAM,OAAO;AACf,eAAW,SAAS;AAAA,MAClBC,KAAAA,OAAO,CAAC,CAAC,MAAM,GAAG,MAAM;AACtB,cAAM,SAASC,WAAA;AAAA,UACb;AAAA,UACA,MAAM;AAAA,UACN;AAAA,QACF;AACO,eAAA;AAAA,MACR,CAAA;AAAA,IACH;AAAA,EAAA;AAIF,MAAI,MAAM,SAAS;AACN,eAAAC,QAAA,eAAe,UAAU,OAAO,cAAc;AAAA,EAAA;AAK3D,MAAI,MAAM,QAAQ;AAChB,eAAW,SAAS;AAAA,MAClBF,KAAAA,OAAO,CAAC,CAAC,MAAM,GAAG,MAAM;AAGtB,cAAM,SAASC,WAAA;AAAA,UACb;AAAA,UACA,MAAM;AAAA,UACN;AAAA,QACF;AACO,eAAA;AAAA,MACR,CAAA;AAAA,IACH;AAAA,EAAA;AAIF,MAAI,MAAM,SAAS;AACN,eAAAE,QAAA,eAAe,UAAU,OAAO,cAAc;AAAA,EAAA,WAChD,MAAM,UAAU,UAAa,MAAM,WAAW,QAAW;AAElE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EAAA;AAIF,QAAM,iBAAyD,MAAM,SACjEC,OAAAA,cAAc,UAAU,OAAO,gBAAgB,SAAS,IACxD,CAAC,MAAM,QAAQ,CAAC,MAAM,UACpB,SAAS;AAAA,IACPN,SAAI,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,cAAc,CAAC,CAAa;AAAA,EAAA,IAE5D;AACC,SAAA;AACT;;"}
@@ -55,8 +55,9 @@ class BaseQueryBuilder {
55
55
  /**
56
56
  * Specify what columns to select.
57
57
  * Overwrites any previous select clause.
58
+ * Also supports callback functions that receive the row context and return selected data.
58
59
  *
59
- * @param selects The columns to select
60
+ * @param selects The columns to select (can include callbacks)
60
61
  * @returns A new QueryBuilder with the select clause set
61
62
  */
62
63
  select(...selects) {
@@ -110,52 +111,56 @@ class BaseQueryBuilder {
110
111
  /**
111
112
  * Add a where clause to filter the results.
112
113
  * Can be called multiple times to add AND conditions.
114
+ * Also supports callback functions that receive the row context.
113
115
  *
114
- * @param leftOrCondition The left operand or complete condition
116
+ * @param leftOrConditionOrCallback The left operand, complete condition, or callback function
115
117
  * @param operator Optional comparison operator
116
118
  * @param right Optional right operand
117
119
  * @returns A new QueryBuilder with the where clause added
118
120
  */
119
- where(leftOrCondition, operator, right) {
121
+ where(leftOrConditionOrCallback, operator, right) {
120
122
  const newBuilder = new BaseQueryBuilder();
121
123
  Object.assign(newBuilder.query, this.query);
122
124
  let condition;
123
- if (operator !== void 0 && right !== void 0) {
124
- condition = [leftOrCondition, operator, right];
125
+ if (typeof leftOrConditionOrCallback === `function`) {
126
+ condition = leftOrConditionOrCallback;
127
+ } else if (operator !== void 0 && right !== void 0) {
128
+ condition = [leftOrConditionOrCallback, operator, right];
125
129
  } else {
126
- condition = leftOrCondition;
130
+ condition = leftOrConditionOrCallback;
127
131
  }
128
132
  if (!newBuilder.query.where) {
129
- newBuilder.query.where = condition;
133
+ newBuilder.query.where = [condition];
130
134
  } else {
131
- const andArray = [newBuilder.query.where, `and`, condition];
132
- newBuilder.query.where = andArray;
135
+ newBuilder.query.where = [...newBuilder.query.where, condition];
133
136
  }
134
137
  return newBuilder;
135
138
  }
136
139
  /**
137
140
  * Add a having clause to filter the grouped results.
138
141
  * Can be called multiple times to add AND conditions.
142
+ * Also supports callback functions that receive the row context.
139
143
  *
140
- * @param leftOrCondition The left operand or complete condition
144
+ * @param leftOrConditionOrCallback The left operand, complete condition, or callback function
141
145
  * @param operator Optional comparison operator
142
146
  * @param right Optional right operand
143
147
  * @returns A new QueryBuilder with the having clause added
144
148
  */
145
- having(leftOrCondition, operator, right) {
149
+ having(leftOrConditionOrCallback, operator, right) {
146
150
  const newBuilder = new BaseQueryBuilder();
147
151
  Object.assign(newBuilder.query, this.query);
148
152
  let condition;
149
- if (operator !== void 0 && right !== void 0) {
150
- condition = [leftOrCondition, operator, right];
153
+ if (typeof leftOrConditionOrCallback === `function`) {
154
+ condition = leftOrConditionOrCallback;
155
+ } else if (operator !== void 0 && right !== void 0) {
156
+ condition = [leftOrConditionOrCallback, operator, right];
151
157
  } else {
152
- condition = leftOrCondition;
158
+ condition = leftOrConditionOrCallback;
153
159
  }
154
160
  if (!newBuilder.query.having) {
155
- newBuilder.query.having = condition;
161
+ newBuilder.query.having = [condition];
156
162
  } else {
157
- const andArray = [newBuilder.query.having, `and`, condition];
158
- newBuilder.query.having = andArray;
163
+ newBuilder.query.having = [...newBuilder.query.having, condition];
159
164
  }
160
165
  return newBuilder;
161
166
  }
@@ -1 +1 @@
1
- {"version":3,"file":"query-builder.cjs","sources":["../../../src/query/query-builder.ts"],"sourcesContent":["import type { Collection } from \"../collection\"\nimport type {\n Comparator,\n Condition,\n From,\n JoinClause,\n Limit,\n LiteralValue,\n Offset,\n OrderBy,\n Query,\n Select,\n WithQuery,\n} from \"./schema.js\"\nimport type {\n Context,\n Flatten,\n InferResultTypeFromSelectTuple,\n Input,\n InputReference,\n PropertyReference,\n PropertyReferenceString,\n RemoveIndexSignature,\n Schema,\n} from \"./types.js\"\n\ntype CollectionRef = { [K: string]: Collection<any> }\n\nexport class BaseQueryBuilder<TContext extends Context<Schema>> {\n private readonly query: Partial<Query<TContext>> = {}\n\n /**\n * Create a new QueryBuilder instance.\n */\n constructor(query: Partial<Query<TContext>> = {}) {\n this.query = query\n }\n\n from<TCollectionRef extends CollectionRef>(\n collectionRef: TCollectionRef\n ): QueryBuilder<{\n baseSchema: Flatten<\n TContext[`baseSchema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n >\n schema: Flatten<{\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }>\n default: keyof TCollectionRef & string\n }>\n\n from<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n >(\n collection: T\n ): QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n default: T\n }>\n\n from<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string,\n >(\n collection: T,\n as: TAs\n ): QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n default: TAs\n }>\n\n /**\n * Specify the collection to query from.\n * This is the first method that must be called in the chain.\n *\n * @param collection The collection name to query from\n * @param as Optional alias for the collection\n * @returns A new QueryBuilder with the from clause set\n */\n from<\n T extends\n | InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n | CollectionRef,\n TAs extends string | undefined,\n >(collection: T, as?: TAs) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof collection === `object` && collection !== null) {\n return this.fromCollectionRef(collection)\n } else if (typeof collection === `string`) {\n return this.fromInputReference(\n collection as InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n as\n )\n } else {\n throw new Error(`Invalid collection type`)\n }\n }\n\n private fromCollectionRef<TCollectionRef extends CollectionRef>(\n collectionRef: TCollectionRef\n ) {\n const keys = Object.keys(collectionRef)\n if (keys.length !== 1) {\n throw new Error(`Expected exactly one key`)\n }\n\n const key = keys[0]!\n const collection = collectionRef[key]!\n\n const newBuilder = new BaseQueryBuilder()\n Object.assign(newBuilder.query, this.query)\n newBuilder.query.from = key as From<TContext>\n newBuilder.query.collections ??= {}\n newBuilder.query.collections[key] = collection\n\n return newBuilder as unknown as QueryBuilder<{\n baseSchema: TContext[`baseSchema`] & {\n [K in keyof TCollectionRef &\n string]: (TCollectionRef[keyof TCollectionRef] extends Collection<\n infer T\n >\n ? T\n : never) &\n Input\n }\n schema: {\n [K in keyof TCollectionRef &\n string]: (TCollectionRef[keyof TCollectionRef] extends Collection<\n infer T\n >\n ? T\n : never) &\n Input\n }\n default: keyof TCollectionRef & string\n }>\n }\n\n private fromInputReference<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string | undefined,\n >(collection: T, as?: TAs) {\n const newBuilder = new BaseQueryBuilder()\n Object.assign(newBuilder.query, this.query)\n newBuilder.query.from = collection as From<TContext>\n if (as) {\n newBuilder.query.as = as\n }\n\n // Calculate the result type without deep nesting\n type ResultSchema = TAs extends undefined\n ? { [K in T]: TContext[`baseSchema`][T] }\n : { [K in string & TAs]: TContext[`baseSchema`][T] }\n\n type ResultDefault = TAs extends undefined ? T : string & TAs\n\n // Use simpler type assertion to avoid excessive depth\n return newBuilder as unknown as QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: ResultSchema\n default: ResultDefault\n }>\n }\n\n /**\n * Specify what columns to select.\n * Overwrites any previous select clause.\n *\n * @param selects The columns to select\n * @returns A new QueryBuilder with the select clause set\n */\n select<TSelects extends Array<Select<TContext>>>(\n this: QueryBuilder<TContext>,\n ...selects: TSelects\n ) {\n // Validate function calls in the selects\n // Need to use a type assertion to bypass deep recursive type checking\n const validatedSelects = selects.map((select) => {\n // If the select is an object with aliases, validate each value\n if (\n typeof select === `object` &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n select !== null &&\n !Array.isArray(select)\n ) {\n const result: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(select)) {\n // If it's a function call (object with a single key that is an allowed function name)\n if (\n typeof value === `object` &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const keys = Object.keys(value)\n if (keys.length === 1) {\n const funcName = keys[0]!\n // List of allowed function names from AllowedFunctionName\n const allowedFunctions = [\n `SUM`,\n `COUNT`,\n `AVG`,\n `MIN`,\n `MAX`,\n `DATE`,\n `JSON_EXTRACT`,\n `JSON_EXTRACT_PATH`,\n `UPPER`,\n `LOWER`,\n `COALESCE`,\n `CONCAT`,\n `LENGTH`,\n `ORDER_INDEX`,\n ]\n\n if (!allowedFunctions.includes(funcName)) {\n console.warn(\n `Unsupported function: ${funcName}. Expected one of: ${allowedFunctions.join(`, `)}`\n )\n }\n }\n }\n\n result[key] = value\n }\n\n return result\n }\n\n return select\n })\n\n // Ensure we have an orderByIndex in the select if we have an orderBy\n // This is required if select is called after orderBy\n if (this._query.orderBy) {\n validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `numeric` } })\n }\n\n const newBuilder = new BaseQueryBuilder<TContext>(\n (this as BaseQueryBuilder<TContext>).query\n )\n newBuilder.query.select = validatedSelects as Array<Select<TContext>>\n\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `result`> & {\n result: InferResultTypeFromSelectTuple<TContext, TSelects>\n }\n >\n >\n }\n\n /**\n * Add a where clause comparing two values.\n */\n where(\n left: PropertyReferenceString<TContext> | LiteralValue,\n operator: Comparator,\n right: PropertyReferenceString<TContext> | LiteralValue\n ): QueryBuilder<TContext>\n\n /**\n * Add a where clause with a complete condition object.\n */\n where(condition: Condition<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a where clause to filter the results.\n * Can be called multiple times to add AND conditions.\n *\n * @param leftOrCondition The left operand or complete condition\n * @param operator Optional comparison operator\n * @param right Optional right operand\n * @returns A new QueryBuilder with the where clause added\n */\n where(\n leftOrCondition: any,\n operator?: any,\n right?: any\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n // Use simplistic approach to avoid deep type errors\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n let condition: any\n\n // Determine if this is a complete condition or individual parts\n if (operator !== undefined && right !== undefined) {\n // Create a condition from parts\n condition = [leftOrCondition, operator, right]\n } else {\n // Use the provided condition directly\n condition = leftOrCondition\n }\n\n if (!newBuilder.query.where) {\n newBuilder.query.where = condition\n } else {\n // Create a composite condition with AND\n // Use any to bypass type checking issues\n const andArray: any = [newBuilder.query.where, `and`, condition]\n newBuilder.query.where = andArray\n }\n\n return newBuilder as unknown as QueryBuilder<TContext>\n }\n\n /**\n * Add a having clause comparing two values.\n * For filtering results after they have been grouped.\n */\n having(\n left: PropertyReferenceString<TContext> | LiteralValue,\n operator: Comparator,\n right: PropertyReferenceString<TContext> | LiteralValue\n ): QueryBuilder<TContext>\n\n /**\n * Add a having clause with a complete condition object.\n * For filtering results after they have been grouped.\n */\n having(condition: Condition<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a having clause to filter the grouped results.\n * Can be called multiple times to add AND conditions.\n *\n * @param leftOrCondition The left operand or complete condition\n * @param operator Optional comparison operator\n * @param right Optional right operand\n * @returns A new QueryBuilder with the having clause added\n */\n having(\n leftOrCondition: any,\n operator?: any,\n right?: any\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n let condition: any\n\n // Determine if this is a complete condition or individual parts\n if (operator !== undefined && right !== undefined) {\n // Create a condition from parts\n condition = [leftOrCondition, operator, right]\n } else {\n // Use the provided condition directly\n condition = leftOrCondition\n }\n\n if (!newBuilder.query.having) {\n newBuilder.query.having = condition\n } else {\n // Create a composite condition with AND\n // Use any to bypass type checking issues\n const andArray: any = [newBuilder.query.having, `and`, condition]\n newBuilder.query.having = andArray\n }\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Add a join clause to the query using a CollectionRef.\n */\n join<TCollectionRef extends CollectionRef>(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TCollectionRef\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n hasJoin: true\n }\n >\n >\n\n /**\n * Add a join clause to the query without specifying an alias.\n * The collection name will be used as the default alias.\n */\n join<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: T\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: { [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]> }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n hasJoin: true\n }\n >\n >\n\n /**\n * Add a join clause to the query with a specified alias.\n */\n join<\n TFrom extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as: TAs\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n hasJoin: true\n }\n >\n >\n\n join<\n TFrom extends\n | InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n | CollectionRef,\n TAs extends string | undefined = undefined,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as?: TAs\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] &\n (TFrom extends CollectionRef\n ? {\n [K in keyof TFrom & string]: RemoveIndexSignature<\n (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &\n Input\n >\n }\n : TFrom extends InputReference<infer TRef>\n ? {\n [K in keyof TRef & string]: RemoveIndexSignature<\n TRef[keyof TRef]\n >\n }\n : never)\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] &\n (TFrom extends CollectionRef\n ? {\n [K in keyof TFrom & string]: RemoveIndexSignature<\n (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &\n Input\n >\n }\n : TFrom extends InputReference<infer TRef>\n ? {\n [K in keyof TRef & string]: RemoveIndexSignature<\n TRef[keyof TRef]\n >\n }\n : never)\n }>\n >\n }): QueryBuilder<any> {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof joinClause.from === `object` && joinClause.from !== null) {\n return this.joinCollectionRef(\n joinClause as {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: CollectionRef\n on: Condition<any>\n where?: Condition<any>\n }\n )\n } else {\n return this.joinInputReference(\n joinClause as {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n as?: TAs\n on: Condition<any>\n where?: Condition<any>\n }\n )\n }\n }\n\n private joinCollectionRef<TCollectionRef extends CollectionRef>(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TCollectionRef\n on: Condition<any>\n where?: Condition<any>\n }): QueryBuilder<any> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Get the collection key\n const keys = Object.keys(joinClause.from)\n if (keys.length !== 1) {\n throw new Error(`Expected exactly one key in CollectionRef`)\n }\n const key = keys[0]!\n const collection = joinClause.from[key]\n if (!collection) {\n throw new Error(`Collection not found for key: ${key}`)\n }\n\n // Create a copy of the join clause for the query\n const joinClauseCopy = {\n type: joinClause.type,\n from: key,\n on: joinClause.on,\n where: joinClause.where,\n } as JoinClause<TContext>\n\n // Add the join clause to the query\n if (!newBuilder.query.join) {\n newBuilder.query.join = [joinClauseCopy]\n } else {\n newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]\n }\n\n // Add the collection to the collections map\n newBuilder.query.collections ??= {}\n newBuilder.query.collections[key] = collection\n\n // Return the new builder with updated schema type\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }\n >\n >\n }\n\n private joinInputReference<\n TFrom extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string | undefined = undefined,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as?: TAs\n on: Condition<any>\n where?: Condition<any>\n }): QueryBuilder<any> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Create a copy of the join clause for the query\n const joinClauseCopy = { ...joinClause } as JoinClause<TContext>\n\n // Add the join clause to the query\n if (!newBuilder.query.join) {\n newBuilder.query.join = [joinClauseCopy]\n } else {\n newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]\n }\n\n // Determine the alias or use the collection name as default\n const _effectiveAlias = joinClause.as ?? joinClause.from\n\n // Return the new builder with updated schema type\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in typeof _effectiveAlias]: TContext[`baseSchema`][TFrom]\n }\n }\n >\n >\n }\n\n /**\n * Add an orderBy clause to sort the results.\n * Overwrites any previous orderBy clause.\n *\n * @param orderBy The order specification\n * @returns A new QueryBuilder with the orderBy clause set\n */\n orderBy(orderBy: OrderBy<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the orderBy clause\n newBuilder.query.orderBy = orderBy\n\n // Ensure we have an orderByIndex in the select if we have an orderBy\n // This is required if select is called before orderBy\n newBuilder.query.select = [\n ...(newBuilder.query.select ?? []),\n { _orderByIndex: { ORDER_INDEX: `numeric` } },\n ]\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Set a limit on the number of results returned.\n *\n * @param limit Maximum number of results to return\n * @returns A new QueryBuilder with the limit set\n */\n limit(limit: Limit<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the limit\n newBuilder.query.limit = limit\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Set an offset to skip a number of results.\n *\n * @param offset Number of results to skip\n * @returns A new QueryBuilder with the offset set\n */\n offset(offset: Offset<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the offset\n newBuilder.query.offset = offset\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Add a groupBy clause to group the results by one or more columns.\n *\n * @param groupBy The column(s) to group by\n * @returns A new QueryBuilder with the groupBy clause set\n */\n groupBy(\n groupBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the groupBy clause\n newBuilder.query.groupBy = groupBy\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Define a Common Table Expression (CTE) that can be referenced in the main query.\n * This allows referencing the CTE by name in subsequent from/join clauses.\n *\n * @param name The name of the CTE\n * @param queryBuilderCallback A function that builds the CTE query\n * @returns A new QueryBuilder with the CTE added\n */\n with<TName extends string, TResult = Record<string, unknown>>(\n name: TName,\n queryBuilderCallback: (\n builder: InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>\n ) => QueryBuilder<any>\n ): InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }\n schema: TContext[`schema`]\n }> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Create a new builder for the CTE\n const cteBuilder = new BaseQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>()\n\n // Get the CTE query from the callback\n const cteQueryBuilder = queryBuilderCallback(\n cteBuilder as InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>\n )\n\n // Get the query from the builder\n const cteQuery = cteQueryBuilder._query\n\n // Add an 'as' property to the CTE\n const withQuery: WithQuery<any> = {\n ...cteQuery,\n as: name,\n }\n\n // Add the CTE to the with array\n if (!newBuilder.query.with) {\n newBuilder.query.with = [withQuery]\n } else {\n newBuilder.query.with = [...newBuilder.query.with, withQuery]\n }\n\n // Use a type cast that simplifies the type structure to avoid recursion\n return newBuilder as unknown as InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }\n schema: TContext[`schema`]\n }>\n }\n\n get _query(): Query<TContext> {\n return this.query as Query<TContext>\n }\n}\n\nexport type InitialQueryBuilder<TContext extends Context<Schema>> = Pick<\n BaseQueryBuilder<TContext>,\n `from` | `with`\n>\n\nexport type QueryBuilder<TContext extends Context<Schema>> = Omit<\n BaseQueryBuilder<TContext>,\n `from`\n>\n\n/**\n * Create a new query builder with the given schema\n */\nexport function queryBuilder<TBaseSchema extends Schema = {}>() {\n return new BaseQueryBuilder<{\n baseSchema: TBaseSchema\n schema: {}\n }>() as InitialQueryBuilder<{\n baseSchema: TBaseSchema\n schema: {}\n }>\n}\n\nexport type ResultsFromContext<TContext extends Context<Schema>> = Flatten<\n TContext[`result`] extends object\n ? TContext[`result`] // If there is a select we will have a result type\n : TContext[`hasJoin`] extends true\n ? TContext[`schema`] // If there is a join, the query returns the namespaced schema\n : TContext[`default`] extends keyof TContext[`schema`]\n ? TContext[`schema`][TContext[`default`]] // If there is no join we return the flat default schema\n : never // Should never happen\n>\n\nexport type ResultFromQueryBuilder<TQueryBuilder> = Flatten<\n TQueryBuilder extends QueryBuilder<infer C>\n ? C extends { result: infer R }\n ? R\n : never\n : never\n>\n"],"names":[],"mappings":";;AA4BO,MAAM,iBAAmD;AAAA;AAAA;AAAA;AAAA,EAM9D,YAAY,QAAkC,IAAI;AALlD,SAAiB,QAAkC,CAAC;AAMlD,SAAK,QAAQ;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmEf,KAQE,YAAe,IAAU;AAEzB,QAAI,OAAO,eAAe,YAAY,eAAe,MAAM;AAClD,aAAA,KAAK,kBAAkB,UAAU;AAAA,IAC1C,WAAW,OAAO,eAAe,UAAU;AACzC,aAAO,KAAK;AAAA,QACV;AAAA,QAIA;AAAA,MACF;AAAA,IAAA,OACK;AACC,YAAA,IAAI,MAAM,yBAAyB;AAAA,IAAA;AAAA,EAC3C;AAAA,EAGM,kBACN,eACA;;AACM,UAAA,OAAO,OAAO,KAAK,aAAa;AAClC,QAAA,KAAK,WAAW,GAAG;AACf,YAAA,IAAI,MAAM,0BAA0B;AAAA,IAAA;AAGtC,UAAA,MAAM,KAAK,CAAC;AACZ,UAAA,aAAa,cAAc,GAAG;AAE9B,UAAA,aAAa,IAAI,iBAAiB;AACxC,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAC1C,eAAW,MAAM,OAAO;AACb,qBAAA,OAAM,gBAAN,GAAM,cAAgB,CAAC;AACvB,eAAA,MAAM,YAAY,GAAG,IAAI;AAE7B,WAAA;AAAA,EAAA;AAAA,EAuBD,mBAMN,YAAe,IAAU;AACnB,UAAA,aAAa,IAAI,iBAAiB;AACxC,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAC1C,eAAW,MAAM,OAAO;AACxB,QAAI,IAAI;AACN,iBAAW,MAAM,KAAK;AAAA,IAAA;AAWjB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcT,UAEK,SACH;AAGA,UAAM,mBAAmB,QAAQ,IAAI,CAAC,WAAW;AAE/C,UACE,OAAO,WAAW;AAAA,MAElB,WAAW,QACX,CAAC,MAAM,QAAQ,MAAM,GACrB;AACA,cAAM,SAA8B,CAAC;AAErC,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAG/C,cAAA,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,GACpB;AACM,kBAAA,OAAO,OAAO,KAAK,KAAK;AAC1B,gBAAA,KAAK,WAAW,GAAG;AACf,oBAAA,WAAW,KAAK,CAAC;AAEvB,oBAAM,mBAAmB;AAAA,gBACvB;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,cACF;AAEA,kBAAI,CAAC,iBAAiB,SAAS,QAAQ,GAAG;AAChC,wBAAA;AAAA,kBACN,yBAAyB,QAAQ,sBAAsB,iBAAiB,KAAK,IAAI,CAAC;AAAA,gBACpF;AAAA,cAAA;AAAA,YACF;AAAA,UACF;AAGF,iBAAO,GAAG,IAAI;AAAA,QAAA;AAGT,eAAA;AAAA,MAAA;AAGF,aAAA;AAAA,IAAA,CACR;AAIG,QAAA,KAAK,OAAO,SAAS;AACvB,uBAAiB,KAAK,EAAE,eAAe,EAAE,aAAa,UAAA,GAAa;AAAA,IAAA;AAGrE,UAAM,aAAa,IAAI;AAAA,MACpB,KAAoC;AAAA,IACvC;AACA,eAAW,MAAM,SAAS;AAEnB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgCT,MACE,iBACA,UACA,OACwB;AAGlB,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAEtC,QAAA;AAGA,QAAA,aAAa,UAAa,UAAU,QAAW;AAErC,kBAAA,CAAC,iBAAiB,UAAU,KAAK;AAAA,IAAA,OACxC;AAEO,kBAAA;AAAA,IAAA;AAGV,QAAA,CAAC,WAAW,MAAM,OAAO;AAC3B,iBAAW,MAAM,QAAQ;AAAA,IAAA,OACpB;AAGL,YAAM,WAAgB,CAAC,WAAW,MAAM,OAAO,OAAO,SAAS;AAC/D,iBAAW,MAAM,QAAQ;AAAA,IAAA;AAGpB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BT,OACE,iBACA,UACA,OACwB;AAElB,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAEtC,QAAA;AAGA,QAAA,aAAa,UAAa,UAAU,QAAW;AAErC,kBAAA,CAAC,iBAAiB,UAAU,KAAK;AAAA,IAAA,OACxC;AAEO,kBAAA;AAAA,IAAA;AAGV,QAAA,CAAC,WAAW,MAAM,QAAQ;AAC5B,iBAAW,MAAM,SAAS;AAAA,IAAA,OACrB;AAGL,YAAM,WAAgB,CAAC,WAAW,MAAM,QAAQ,OAAO,SAAS;AAChE,iBAAW,MAAM,SAAS;AAAA,IAAA;AAGrB,WAAA;AAAA,EAAA;AAAA,EAgIT,KAQE,YA4CoB;AAEpB,QAAI,OAAO,WAAW,SAAS,YAAY,WAAW,SAAS,MAAM;AACnE,aAAO,KAAK;AAAA,QACV;AAAA,MAMF;AAAA,IAAA,OACK;AACL,aAAO,KAAK;AAAA,QACV;AAAA,MAUF;AAAA,IAAA;AAAA,EACF;AAAA,EAGM,kBAAwD,YAK1C;;AAEd,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAG1C,UAAM,OAAO,OAAO,KAAK,WAAW,IAAI;AACpC,QAAA,KAAK,WAAW,GAAG;AACf,YAAA,IAAI,MAAM,2CAA2C;AAAA,IAAA;AAEvD,UAAA,MAAM,KAAK,CAAC;AACZ,UAAA,aAAa,WAAW,KAAK,GAAG;AACtC,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,iCAAiC,GAAG,EAAE;AAAA,IAAA;AAIxD,UAAM,iBAAiB;AAAA,MACrB,MAAM,WAAW;AAAA,MACjB,MAAM;AAAA,MACN,IAAI,WAAW;AAAA,MACf,OAAO,WAAW;AAAA,IACpB;AAGI,QAAA,CAAC,WAAW,MAAM,MAAM;AACf,iBAAA,MAAM,OAAO,CAAC,cAAc;AAAA,IAAA,OAClC;AACL,iBAAW,MAAM,OAAO,CAAC,GAAG,WAAW,MAAM,MAAM,cAAc;AAAA,IAAA;AAIxD,qBAAA,OAAM,gBAAN,GAAM,cAAgB,CAAC;AACvB,eAAA,MAAM,YAAY,GAAG,IAAI;AAG7B,WAAA;AAAA,EAAA;AAAA,EAgBD,mBAMN,YAMoB;AAEd,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAGpC,UAAA,iBAAiB,EAAE,GAAG,WAAW;AAGnC,QAAA,CAAC,WAAW,MAAM,MAAM;AACf,iBAAA,MAAM,OAAO,CAAC,cAAc;AAAA,IAAA,OAClC;AACL,iBAAW,MAAM,OAAO,CAAC,GAAG,WAAW,MAAM,MAAM,cAAc;AAAA,IAAA;AAI3C,eAAW,MAAM,WAAW;AAG7C,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBT,QAAQ,SAAoD;AAEpD,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAG1C,eAAW,MAAM,UAAU;AAI3B,eAAW,MAAM,SAAS;AAAA,MACxB,GAAI,WAAW,MAAM,UAAU,CAAC;AAAA,MAChC,EAAE,eAAe,EAAE,aAAa,UAAY,EAAA;AAAA,IAC9C;AAEO,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAST,MAAM,OAAgD;AAE9C,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAG1C,eAAW,MAAM,QAAQ;AAElB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAST,OAAO,QAAkD;AAEjD,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAG1C,eAAW,MAAM,SAAS;AAEnB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAST,QACE,SACwB;AAElB,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAG1C,eAAW,MAAM,UAAU;AAEpB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWT,KACE,MACA,sBASC;AAEK,UAAA,aAAa,IAAI,iBAA2B;AAClD,WAAO,OAAO,WAAW,OAAO,KAAK,KAAK;AAGpC,UAAA,aAAa,IAAI,iBAGpB;AAGH,UAAM,kBAAkB;AAAA,MACtB;AAAA,IAIF;AAGA,UAAM,WAAW,gBAAgB;AAGjC,UAAM,YAA4B;AAAA,MAChC,GAAG;AAAA,MACH,IAAI;AAAA,IACN;AAGI,QAAA,CAAC,WAAW,MAAM,MAAM;AACf,iBAAA,MAAM,OAAO,CAAC,SAAS;AAAA,IAAA,OAC7B;AACL,iBAAW,MAAM,OAAO,CAAC,GAAG,WAAW,MAAM,MAAM,SAAS;AAAA,IAAA;AAIvD,WAAA;AAAA,EAAA;AAAA,EAMT,IAAI,SAA0B;AAC5B,WAAO,KAAK;AAAA,EAAA;AAEhB;AAeO,SAAS,eAAgD;AAC9D,SAAO,IAAI,iBAGR;AAIL;;;"}
1
+ {"version":3,"file":"query-builder.cjs","sources":["../../../src/query/query-builder.ts"],"sourcesContent":["import type { Collection } from \"../collection\"\nimport type {\n Comparator,\n Condition,\n From,\n JoinClause,\n Limit,\n LiteralValue,\n Offset,\n OrderBy,\n Query,\n Select,\n WhereCallback,\n WithQuery,\n} from \"./schema.js\"\nimport type {\n Context,\n Flatten,\n InferResultTypeFromSelectTuple,\n Input,\n InputReference,\n PropertyReference,\n PropertyReferenceString,\n RemoveIndexSignature,\n Schema,\n} from \"./types.js\"\n\ntype CollectionRef = { [K: string]: Collection<any> }\n\nexport class BaseQueryBuilder<TContext extends Context<Schema>> {\n private readonly query: Partial<Query<TContext>> = {}\n\n /**\n * Create a new QueryBuilder instance.\n */\n constructor(query: Partial<Query<TContext>> = {}) {\n this.query = query\n }\n\n from<TCollectionRef extends CollectionRef>(\n collectionRef: TCollectionRef\n ): QueryBuilder<{\n baseSchema: Flatten<\n TContext[`baseSchema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n >\n schema: Flatten<{\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }>\n default: keyof TCollectionRef & string\n }>\n\n from<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n >(\n collection: T\n ): QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n default: T\n }>\n\n from<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string,\n >(\n collection: T,\n as: TAs\n ): QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n default: TAs\n }>\n\n /**\n * Specify the collection to query from.\n * This is the first method that must be called in the chain.\n *\n * @param collection The collection name to query from\n * @param as Optional alias for the collection\n * @returns A new QueryBuilder with the from clause set\n */\n from<\n T extends\n | InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n | CollectionRef,\n TAs extends string | undefined,\n >(collection: T, as?: TAs) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof collection === `object` && collection !== null) {\n return this.fromCollectionRef(collection)\n } else if (typeof collection === `string`) {\n return this.fromInputReference(\n collection as InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n as\n )\n } else {\n throw new Error(`Invalid collection type`)\n }\n }\n\n private fromCollectionRef<TCollectionRef extends CollectionRef>(\n collectionRef: TCollectionRef\n ) {\n const keys = Object.keys(collectionRef)\n if (keys.length !== 1) {\n throw new Error(`Expected exactly one key`)\n }\n\n const key = keys[0]!\n const collection = collectionRef[key]!\n\n const newBuilder = new BaseQueryBuilder()\n Object.assign(newBuilder.query, this.query)\n newBuilder.query.from = key as From<TContext>\n newBuilder.query.collections ??= {}\n newBuilder.query.collections[key] = collection\n\n return newBuilder as unknown as QueryBuilder<{\n baseSchema: TContext[`baseSchema`] & {\n [K in keyof TCollectionRef &\n string]: (TCollectionRef[keyof TCollectionRef] extends Collection<\n infer T\n >\n ? T\n : never) &\n Input\n }\n schema: {\n [K in keyof TCollectionRef &\n string]: (TCollectionRef[keyof TCollectionRef] extends Collection<\n infer T\n >\n ? T\n : never) &\n Input\n }\n default: keyof TCollectionRef & string\n }>\n }\n\n private fromInputReference<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string | undefined,\n >(collection: T, as?: TAs) {\n const newBuilder = new BaseQueryBuilder()\n Object.assign(newBuilder.query, this.query)\n newBuilder.query.from = collection as From<TContext>\n if (as) {\n newBuilder.query.as = as\n }\n\n // Calculate the result type without deep nesting\n type ResultSchema = TAs extends undefined\n ? { [K in T]: TContext[`baseSchema`][T] }\n : { [K in string & TAs]: TContext[`baseSchema`][T] }\n\n type ResultDefault = TAs extends undefined ? T : string & TAs\n\n // Use simpler type assertion to avoid excessive depth\n return newBuilder as unknown as QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: ResultSchema\n default: ResultDefault\n }>\n }\n\n /**\n * Specify what columns to select.\n * Overwrites any previous select clause.\n * Also supports callback functions that receive the row context and return selected data.\n *\n * @param selects The columns to select (can include callbacks)\n * @returns A new QueryBuilder with the select clause set\n */\n select<TSelects extends Array<Select<TContext>>>(\n this: QueryBuilder<TContext>,\n ...selects: TSelects\n ) {\n // Validate function calls in the selects\n // Need to use a type assertion to bypass deep recursive type checking\n const validatedSelects = selects.map((select) => {\n // If the select is an object with aliases, validate each value\n if (\n typeof select === `object` &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n select !== null &&\n !Array.isArray(select)\n ) {\n const result: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(select)) {\n // If it's a function call (object with a single key that is an allowed function name)\n if (\n typeof value === `object` &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const keys = Object.keys(value)\n if (keys.length === 1) {\n const funcName = keys[0]!\n // List of allowed function names from AllowedFunctionName\n const allowedFunctions = [\n `SUM`,\n `COUNT`,\n `AVG`,\n `MIN`,\n `MAX`,\n `DATE`,\n `JSON_EXTRACT`,\n `JSON_EXTRACT_PATH`,\n `UPPER`,\n `LOWER`,\n `COALESCE`,\n `CONCAT`,\n `LENGTH`,\n `ORDER_INDEX`,\n ]\n\n if (!allowedFunctions.includes(funcName)) {\n console.warn(\n `Unsupported function: ${funcName}. Expected one of: ${allowedFunctions.join(`, `)}`\n )\n }\n }\n }\n\n result[key] = value\n }\n\n return result\n }\n\n return select\n })\n\n // Ensure we have an orderByIndex in the select if we have an orderBy\n // This is required if select is called after orderBy\n if (this._query.orderBy) {\n validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `numeric` } })\n }\n\n const newBuilder = new BaseQueryBuilder<TContext>(\n (this as BaseQueryBuilder<TContext>).query\n )\n newBuilder.query.select = validatedSelects as Array<Select<TContext>>\n\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `result`> & {\n result: InferResultTypeFromSelectTuple<TContext, TSelects>\n }\n >\n >\n }\n\n /**\n * Add a where clause comparing two values.\n */\n where(\n left: PropertyReferenceString<TContext> | LiteralValue,\n operator: Comparator,\n right: PropertyReferenceString<TContext> | LiteralValue\n ): QueryBuilder<TContext>\n\n /**\n * Add a where clause with a complete condition object.\n */\n where(condition: Condition<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a where clause with a callback function.\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a where clause to filter the results.\n * Can be called multiple times to add AND conditions.\n * Also supports callback functions that receive the row context.\n *\n * @param leftOrConditionOrCallback The left operand, complete condition, or callback function\n * @param operator Optional comparison operator\n * @param right Optional right operand\n * @returns A new QueryBuilder with the where clause added\n */\n where(\n leftOrConditionOrCallback: any,\n operator?: any,\n right?: any\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n // Use simplistic approach to avoid deep type errors\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n let condition: any\n\n // Determine if this is a callback, complete condition, or individual parts\n if (typeof leftOrConditionOrCallback === `function`) {\n // It's a callback function\n condition = leftOrConditionOrCallback\n } else if (operator !== undefined && right !== undefined) {\n // Create a condition from parts\n condition = [leftOrConditionOrCallback, operator, right]\n } else {\n // Use the provided condition directly\n condition = leftOrConditionOrCallback\n }\n\n // Where is always an array, so initialize or append\n if (!newBuilder.query.where) {\n newBuilder.query.where = [condition]\n } else {\n newBuilder.query.where = [...newBuilder.query.where, condition]\n }\n\n return newBuilder as unknown as QueryBuilder<TContext>\n }\n\n /**\n * Add a having clause comparing two values.\n * For filtering results after they have been grouped.\n */\n having(\n left: PropertyReferenceString<TContext> | LiteralValue,\n operator: Comparator,\n right: PropertyReferenceString<TContext> | LiteralValue\n ): QueryBuilder<TContext>\n\n /**\n * Add a having clause with a complete condition object.\n * For filtering results after they have been grouped.\n */\n having(condition: Condition<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a having clause with a callback function.\n * For filtering results after they have been grouped.\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a having clause to filter the grouped results.\n * Can be called multiple times to add AND conditions.\n * Also supports callback functions that receive the row context.\n *\n * @param leftOrConditionOrCallback The left operand, complete condition, or callback function\n * @param operator Optional comparison operator\n * @param right Optional right operand\n * @returns A new QueryBuilder with the having clause added\n */\n having(\n leftOrConditionOrCallback: any,\n operator?: any,\n right?: any\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n let condition: any\n\n // Determine if this is a callback, complete condition, or individual parts\n if (typeof leftOrConditionOrCallback === `function`) {\n // It's a callback function\n condition = leftOrConditionOrCallback\n } else if (operator !== undefined && right !== undefined) {\n // Create a condition from parts\n condition = [leftOrConditionOrCallback, operator, right]\n } else {\n // Use the provided condition directly\n condition = leftOrConditionOrCallback\n }\n\n // Having is always an array, so initialize or append\n if (!newBuilder.query.having) {\n newBuilder.query.having = [condition]\n } else {\n newBuilder.query.having = [...newBuilder.query.having, condition]\n }\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Add a join clause to the query using a CollectionRef.\n */\n join<TCollectionRef extends CollectionRef>(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TCollectionRef\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n hasJoin: true\n }\n >\n >\n\n /**\n * Add a join clause to the query without specifying an alias.\n * The collection name will be used as the default alias.\n */\n join<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: T\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: { [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]> }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n hasJoin: true\n }\n >\n >\n\n /**\n * Add a join clause to the query with a specified alias.\n */\n join<\n TFrom extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as: TAs\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n hasJoin: true\n }\n >\n >\n\n join<\n TFrom extends\n | InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n | CollectionRef,\n TAs extends string | undefined = undefined,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as?: TAs\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] &\n (TFrom extends CollectionRef\n ? {\n [K in keyof TFrom & string]: RemoveIndexSignature<\n (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &\n Input\n >\n }\n : TFrom extends InputReference<infer TRef>\n ? {\n [K in keyof TRef & string]: RemoveIndexSignature<\n TRef[keyof TRef]\n >\n }\n : never)\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] &\n (TFrom extends CollectionRef\n ? {\n [K in keyof TFrom & string]: RemoveIndexSignature<\n (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &\n Input\n >\n }\n : TFrom extends InputReference<infer TRef>\n ? {\n [K in keyof TRef & string]: RemoveIndexSignature<\n TRef[keyof TRef]\n >\n }\n : never)\n }>\n >\n }): QueryBuilder<any> {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof joinClause.from === `object` && joinClause.from !== null) {\n return this.joinCollectionRef(\n joinClause as {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: CollectionRef\n on: Condition<any>\n where?: Condition<any>\n }\n )\n } else {\n return this.joinInputReference(\n joinClause as {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n as?: TAs\n on: Condition<any>\n where?: Condition<any>\n }\n )\n }\n }\n\n private joinCollectionRef<TCollectionRef extends CollectionRef>(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TCollectionRef\n on: Condition<any>\n where?: Condition<any>\n }): QueryBuilder<any> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Get the collection key\n const keys = Object.keys(joinClause.from)\n if (keys.length !== 1) {\n throw new Error(`Expected exactly one key in CollectionRef`)\n }\n const key = keys[0]!\n const collection = joinClause.from[key]\n if (!collection) {\n throw new Error(`Collection not found for key: ${key}`)\n }\n\n // Create a copy of the join clause for the query\n const joinClauseCopy = {\n type: joinClause.type,\n from: key,\n on: joinClause.on,\n where: joinClause.where,\n } as JoinClause<TContext>\n\n // Add the join clause to the query\n if (!newBuilder.query.join) {\n newBuilder.query.join = [joinClauseCopy]\n } else {\n newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]\n }\n\n // Add the collection to the collections map\n newBuilder.query.collections ??= {}\n newBuilder.query.collections[key] = collection\n\n // Return the new builder with updated schema type\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }\n >\n >\n }\n\n private joinInputReference<\n TFrom extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string | undefined = undefined,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as?: TAs\n on: Condition<any>\n where?: Condition<any>\n }): QueryBuilder<any> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Create a copy of the join clause for the query\n const joinClauseCopy = { ...joinClause } as JoinClause<TContext>\n\n // Add the join clause to the query\n if (!newBuilder.query.join) {\n newBuilder.query.join = [joinClauseCopy]\n } else {\n newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]\n }\n\n // Determine the alias or use the collection name as default\n const _effectiveAlias = joinClause.as ?? joinClause.from\n\n // Return the new builder with updated schema type\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in typeof _effectiveAlias]: TContext[`baseSchema`][TFrom]\n }\n }\n >\n >\n }\n\n /**\n * Add an orderBy clause to sort the results.\n * Overwrites any previous orderBy clause.\n *\n * @param orderBy The order specification\n * @returns A new QueryBuilder with the orderBy clause set\n */\n orderBy(orderBy: OrderBy<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the orderBy clause\n newBuilder.query.orderBy = orderBy\n\n // Ensure we have an orderByIndex in the select if we have an orderBy\n // This is required if select is called before orderBy\n newBuilder.query.select = [\n ...(newBuilder.query.select ?? []),\n { _orderByIndex: { ORDER_INDEX: `numeric` } },\n ]\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Set a limit on the number of results returned.\n *\n * @param limit Maximum number of results to return\n * @returns A new QueryBuilder with the limit set\n */\n limit(limit: Limit<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the limit\n newBuilder.query.limit = limit\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Set an offset to skip a number of results.\n *\n * @param offset Number of results to skip\n * @returns A new QueryBuilder with the offset set\n */\n offset(offset: Offset<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the offset\n newBuilder.query.offset = offset\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Add a groupBy clause to group the results by one or more columns.\n *\n * @param groupBy The column(s) to group by\n * @returns A new QueryBuilder with the groupBy clause set\n */\n groupBy(\n groupBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the groupBy clause\n newBuilder.query.groupBy = groupBy\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Define a Common Table Expression (CTE) that can be referenced in the main query.\n * This allows referencing the CTE by name in subsequent from/join clauses.\n *\n * @param name The name of the CTE\n * @param queryBuilderCallback A function that builds the CTE query\n * @returns A new QueryBuilder with the CTE added\n */\n with<TName extends string, TResult = Record<string, unknown>>(\n name: TName,\n queryBuilderCallback: (\n builder: InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>\n ) => QueryBuilder<any>\n ): InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }\n schema: TContext[`schema`]\n }> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Create a new builder for the CTE\n const cteBuilder = new BaseQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>()\n\n // Get the CTE query from the callback\n const cteQueryBuilder = queryBuilderCallback(\n cteBuilder as InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>\n )\n\n // Get the query from the builder\n const cteQuery = cteQueryBuilder._query\n\n // Add an 'as' property to the CTE\n const withQuery: WithQuery<any> = {\n ...cteQuery,\n as: name,\n }\n\n // Add the CTE to the with array\n if (!newBuilder.query.with) {\n newBuilder.query.with = [withQuery]\n } else {\n newBuilder.query.with = [...newBuilder.query.with, withQuery]\n }\n\n // Use a type cast that simplifies the type structure to avoid recursion\n return newBuilder as unknown as InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }\n schema: TContext[`schema`]\n }>\n }\n\n get _query(): Query<TContext> {\n return this.query as Query<TContext>\n }\n}\n\nexport type InitialQueryBuilder<TContext extends Context<Schema>> = Pick<\n BaseQueryBuilder<TContext>,\n `from` | `with`\n>\n\nexport type QueryBuilder<TContext extends Context<Schema>> = Omit<\n BaseQueryBuilder<TContext>,\n `from`\n>\n\n/**\n * Create a new query builder with the given schema\n */\nexport function queryBuilder<TBaseSchema extends Schema = {}>() {\n return new BaseQueryBuilder<{\n baseSchema: TBaseSchema\n schema: {}\n }>() as InitialQueryBuilder<{\n baseSchema: TBaseSchema\n schema: {}\n }>\n}\n\nexport type ResultsFromContext<TContext extends Context<Schema>> = Flatten<\n TContext[`result`] extends object\n ? TContext[`result`] // If there is a select we will have a result type\n : TContext[`hasJoin`] extends true\n ? TContext[`schema`] // If there is a join, the query returns the namespaced schema\n : TContext[`default`] extends keyof TContext[`schema`]\n ? TContext[`schema`][TContext[`default`]] // If there is no join we return the flat default schema\n : never // Should never happen\n>\n\nexport type ResultFromQueryBuilder<TQueryBuilder> = Flatten<\n TQueryBuilder extends QueryBuilder<infer C>\n ? C extends { result: infer R }\n ? R\n : never\n : never\n>\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { Collection } from '../collection.cjs';
2
- import { Comparator, Condition, Limit, LiteralValue, Offset, OrderBy, Query, Select } from './schema.js';
2
+ import { Comparator, Condition, Limit, LiteralValue, Offset, OrderBy, Query, Select, WhereCallback } from './schema.js';
3
3
  import { Context, Flatten, InferResultTypeFromSelectTuple, Input, InputReference, PropertyReference, PropertyReferenceString, RemoveIndexSignature, Schema } from './types.js';
4
4
  type CollectionRef = {
5
5
  [K: string]: Collection<any>;
@@ -44,8 +44,9 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
44
44
  /**
45
45
  * Specify what columns to select.
46
46
  * Overwrites any previous select clause.
47
+ * Also supports callback functions that receive the row context and return selected data.
47
48
  *
48
- * @param selects The columns to select
49
+ * @param selects The columns to select (can include callbacks)
49
50
  * @returns A new QueryBuilder with the select clause set
50
51
  */
51
52
  select<TSelects extends Array<Select<TContext>>>(this: QueryBuilder<TContext>, ...selects: TSelects): QueryBuilder<Flatten<Omit<TContext, `result`> & {
@@ -59,6 +60,10 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
59
60
  * Add a where clause with a complete condition object.
60
61
  */
61
62
  where(condition: Condition<TContext>): QueryBuilder<TContext>;
63
+ /**
64
+ * Add a where clause with a callback function.
65
+ */
66
+ where(callback: WhereCallback<TContext>): QueryBuilder<TContext>;
62
67
  /**
63
68
  * Add a having clause comparing two values.
64
69
  * For filtering results after they have been grouped.
@@ -69,6 +74,11 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
69
74
  * For filtering results after they have been grouped.
70
75
  */
71
76
  having(condition: Condition<TContext>): QueryBuilder<TContext>;
77
+ /**
78
+ * Add a having clause with a callback function.
79
+ * For filtering results after they have been grouped.
80
+ */
81
+ having(callback: WhereCallback<TContext>): QueryBuilder<TContext>;
72
82
  /**
73
83
  * Add a join clause to the query using a CollectionRef.
74
84
  */
@@ -59,15 +59,21 @@ export type OrderBy<TContext extends Context = Context> = PropertyReferenceStrin
59
59
  }>;
60
60
  export type Select<TContext extends Context = Context> = PropertyReferenceString<TContext> | {
61
61
  [alias: string]: PropertyReference<TContext> | FunctionCall<TContext> | AggregateFunctionCall<TContext>;
62
- } | WildcardReferenceString<TContext>;
62
+ } | WildcardReferenceString<TContext> | SelectCallback<TContext>;
63
+ export type SelectCallback<TContext extends Context = Context> = (context: TContext extends {
64
+ schema: infer S;
65
+ } ? S : any) => any;
63
66
  export type As<TContext extends Context = Context> = string;
64
67
  export type From<TContext extends Context = Context> = InputReference<{
65
68
  baseSchema: TContext[`baseSchema`];
66
69
  schema: TContext[`baseSchema`];
67
70
  }>;
68
- export type Where<TContext extends Context = Context> = Condition<TContext>;
71
+ export type WhereCallback<TContext extends Context = Context> = (context: TContext extends {
72
+ schema: infer S;
73
+ } ? S : any) => boolean;
74
+ export type Where<TContext extends Context = Context> = Array<Condition<TContext> | WhereCallback<TContext>>;
75
+ export type Having<TContext extends Context = Context> = Where<TContext>;
69
76
  export type GroupBy<TContext extends Context = Context> = PropertyReference<TContext> | Array<PropertyReference<TContext>>;
70
- export type Having<TContext extends Context = Context> = Condition<TContext>;
71
77
  export type Limit<TContext extends Context = Context> = number;
72
78
  export type Offset<TContext extends Context = Context> = number;
73
79
  export interface BaseQuery<TContext extends Context = Context> {
@@ -75,9 +81,9 @@ export interface BaseQuery<TContext extends Context = Context> {
75
81
  as?: As<TContext>;
76
82
  from: From<TContext>;
77
83
  join?: Array<JoinClause<TContext>>;
78
- where?: Condition<TContext>;
84
+ where?: Where<TContext>;
79
85
  groupBy?: GroupBy<TContext>;
80
- having?: Condition<TContext>;
86
+ having?: Having<TContext>;
81
87
  orderBy?: OrderBy<TContext>;
82
88
  limit?: Limit<TContext>;
83
89
  offset?: Offset<TContext>;
@@ -13,6 +13,18 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
13
13
  throw new Error(`Cannot process missing SELECT clause`);
14
14
  }
15
15
  for (const item of query.select) {
16
+ if (typeof item === `function`) {
17
+ const callback = item;
18
+ const callbackResult = callback(namespacedRow);
19
+ if (callbackResult && typeof callbackResult === `object` && !Array.isArray(callbackResult)) {
20
+ Object.assign(result, callbackResult);
21
+ } else {
22
+ console.warn(
23
+ `SelectCallback returned a non-object value. SelectCallbacks should return objects with key-value pairs.`
24
+ );
25
+ }
26
+ continue;
27
+ }
16
28
  if (typeof item === `string`) {
17
29
  if (item === `@*`) {
18
30
  if (isGroupedResult) {
@@ -1 +1 @@
1
- {"version":3,"file":"select.cjs","sources":["../../../src/query/select.ts"],"sourcesContent":["import { map } from \"@electric-sql/d2ts\"\nimport {\n evaluateOperandOnNamespacedRow,\n extractValueFromNamespacedRow,\n} from \"./extractors\"\nimport type { ConditionOperand, Query } from \"./schema\"\nimport type { KeyedStream, NamespacedAndKeyedStream } from \"../types\"\n\nexport function processSelect(\n pipeline: NamespacedAndKeyedStream,\n query: Query,\n mainTableAlias: string,\n inputs: Record<string, KeyedStream>\n): KeyedStream {\n return pipeline.pipe(\n map(([key, namespacedRow]) => {\n const result: Record<string, unknown> = {}\n\n // Check if this is a grouped result (has no nested table structure)\n // If it's a grouped result, we need to handle it differently\n const isGroupedResult =\n query.groupBy &&\n Object.keys(namespacedRow).some(\n (namespaceKey) =>\n !Object.keys(inputs).includes(namespaceKey) &&\n typeof namespacedRow[namespaceKey] !== `object`\n )\n\n if (!query.select) {\n throw new Error(`Cannot process missing SELECT clause`)\n }\n\n for (const item of query.select) {\n if (typeof item === `string`) {\n // Handle wildcard select - all columns from all tables\n if ((item as string) === `@*`) {\n // For grouped results, just return the row as is\n if (isGroupedResult) {\n Object.assign(result, namespacedRow)\n } else {\n // Extract all columns from all tables\n Object.assign(\n result,\n extractAllColumnsFromAllTables(namespacedRow)\n )\n }\n continue\n }\n\n // Handle @table.* syntax - all columns from a specific table\n if (\n (item as string).startsWith(`@`) &&\n (item as string).endsWith(`.*`)\n ) {\n const tableAlias = (item as string).slice(1, -2) // Remove the '@' and '.*' parts\n\n // For grouped results, check if we have columns from this table\n if (isGroupedResult) {\n // In grouped results, we don't have the nested structure anymore\n // So we can't extract by table. Just continue to the next item.\n continue\n } else {\n // Extract all columns from the specified table\n Object.assign(\n result,\n extractAllColumnsFromTable(namespacedRow, tableAlias)\n )\n }\n continue\n }\n\n // Handle simple column references like \"@table.column\" or \"@column\"\n if ((item as string).startsWith(`@`)) {\n const columnRef = (item as string).substring(1)\n const alias = columnRef\n\n // For grouped results, check if the column is directly in the row first\n if (isGroupedResult && columnRef in namespacedRow) {\n result[alias] = namespacedRow[columnRef]\n } else {\n // Extract the value from the nested structure\n result[alias] = extractValueFromNamespacedRow(\n namespacedRow,\n columnRef,\n mainTableAlias,\n undefined\n )\n }\n\n // If the alias contains a dot (table.column),\n // use just the column part as the field name\n if (alias.includes(`.`)) {\n const columnName = alias.split(`.`)[1]\n result[columnName!] = result[alias]\n delete result[alias]\n }\n }\n } else {\n // Handle aliased columns like { alias: \"@column_name\" }\n for (const [alias, expr] of Object.entries(item)) {\n if (typeof expr === `string` && (expr as string).startsWith(`@`)) {\n const columnRef = (expr as string).substring(1)\n\n // For grouped results, check if the column is directly in the row first\n if (isGroupedResult && columnRef in namespacedRow) {\n result[alias] = namespacedRow[columnRef]\n } else {\n // Extract the value from the nested structure\n result[alias] = extractValueFromNamespacedRow(\n namespacedRow,\n columnRef,\n mainTableAlias,\n undefined\n )\n }\n } else if (typeof expr === `object`) {\n // For grouped results, the aggregate results are already in the row\n if (isGroupedResult && alias in namespacedRow) {\n result[alias] = namespacedRow[alias]\n } else if ((expr as { ORDER_INDEX: unknown }).ORDER_INDEX) {\n result[alias] = namespacedRow[mainTableAlias]![alias]\n } else {\n // This might be a function call\n result[alias] = evaluateOperandOnNamespacedRow(\n namespacedRow,\n expr as ConditionOperand,\n mainTableAlias,\n undefined\n )\n }\n }\n }\n }\n }\n\n return [key, result] as [string, typeof result]\n })\n )\n}\n\n// Helper function to extract all columns from all tables in a nested row\nfunction extractAllColumnsFromAllTables(\n namespacedRow: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n // Process each table in the nested row\n for (const [tableAlias, tableData] of Object.entries(namespacedRow)) {\n if (tableData && typeof tableData === `object`) {\n // Add all columns from this table to the result\n // If there are column name conflicts, the last table's columns will overwrite previous ones\n Object.assign(\n result,\n extractAllColumnsFromTable(namespacedRow, tableAlias)\n )\n }\n }\n\n return result\n}\n\n// Helper function to extract all columns from a table in a nested row\nfunction extractAllColumnsFromTable(\n namespacedRow: Record<string, unknown>,\n tableAlias: string\n): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n // Get the table data\n const tableData = namespacedRow[tableAlias] as\n | Record<string, unknown>\n | null\n | undefined\n\n if (!tableData || typeof tableData !== `object`) {\n return result\n }\n\n // Add all columns from the table to the result\n for (const [columnName, value] of Object.entries(tableData)) {\n result[columnName] = value\n }\n\n return result\n}\n"],"names":["map","extractValueFromNamespacedRow","evaluateOperandOnNamespacedRow"],"mappings":"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"}
1
+ {"version":3,"file":"select.cjs","sources":["../../../src/query/select.ts"],"sourcesContent":["import { map } from \"@electric-sql/d2ts\"\nimport {\n evaluateOperandOnNamespacedRow,\n extractValueFromNamespacedRow,\n} from \"./extractors\"\nimport type { ConditionOperand, Query, SelectCallback } from \"./schema\"\nimport type { KeyedStream, NamespacedAndKeyedStream } from \"../types\"\n\nexport function processSelect(\n pipeline: NamespacedAndKeyedStream,\n query: Query,\n mainTableAlias: string,\n inputs: Record<string, KeyedStream>\n): KeyedStream {\n return pipeline.pipe(\n map(([key, namespacedRow]) => {\n const result: Record<string, unknown> = {}\n\n // Check if this is a grouped result (has no nested table structure)\n // If it's a grouped result, we need to handle it differently\n const isGroupedResult =\n query.groupBy &&\n Object.keys(namespacedRow).some(\n (namespaceKey) =>\n !Object.keys(inputs).includes(namespaceKey) &&\n typeof namespacedRow[namespaceKey] !== `object`\n )\n\n if (!query.select) {\n throw new Error(`Cannot process missing SELECT clause`)\n }\n\n for (const item of query.select) {\n // Handle callback functions\n if (typeof item === `function`) {\n const callback = item as SelectCallback\n const callbackResult = callback(namespacedRow)\n\n // If the callback returns an object, merge its properties into the result\n if (\n callbackResult &&\n typeof callbackResult === `object` &&\n !Array.isArray(callbackResult)\n ) {\n Object.assign(result, callbackResult)\n } else {\n // If the callback returns a primitive value, we can't merge it\n // This would need a specific key, but since we don't have one, we'll skip it\n // In practice, select callbacks should return objects with keys\n console.warn(\n `SelectCallback returned a non-object value. SelectCallbacks should return objects with key-value pairs.`\n )\n }\n continue\n }\n\n if (typeof item === `string`) {\n // Handle wildcard select - all columns from all tables\n if ((item as string) === `@*`) {\n // For grouped results, just return the row as is\n if (isGroupedResult) {\n Object.assign(result, namespacedRow)\n } else {\n // Extract all columns from all tables\n Object.assign(\n result,\n extractAllColumnsFromAllTables(namespacedRow)\n )\n }\n continue\n }\n\n // Handle @table.* syntax - all columns from a specific table\n if (\n (item as string).startsWith(`@`) &&\n (item as string).endsWith(`.*`)\n ) {\n const tableAlias = (item as string).slice(1, -2) // Remove the '@' and '.*' parts\n\n // For grouped results, check if we have columns from this table\n if (isGroupedResult) {\n // In grouped results, we don't have the nested structure anymore\n // So we can't extract by table. Just continue to the next item.\n continue\n } else {\n // Extract all columns from the specified table\n Object.assign(\n result,\n extractAllColumnsFromTable(namespacedRow, tableAlias)\n )\n }\n continue\n }\n\n // Handle simple column references like \"@table.column\" or \"@column\"\n if ((item as string).startsWith(`@`)) {\n const columnRef = (item as string).substring(1)\n const alias = columnRef\n\n // For grouped results, check if the column is directly in the row first\n if (isGroupedResult && columnRef in namespacedRow) {\n result[alias] = namespacedRow[columnRef]\n } else {\n // Extract the value from the nested structure\n result[alias] = extractValueFromNamespacedRow(\n namespacedRow,\n columnRef,\n mainTableAlias,\n undefined\n )\n }\n\n // If the alias contains a dot (table.column),\n // use just the column part as the field name\n if (alias.includes(`.`)) {\n const columnName = alias.split(`.`)[1]\n result[columnName!] = result[alias]\n delete result[alias]\n }\n }\n } else {\n // Handle aliased columns like { alias: \"@column_name\" }\n for (const [alias, expr] of Object.entries(item)) {\n if (typeof expr === `string` && (expr as string).startsWith(`@`)) {\n const columnRef = (expr as string).substring(1)\n\n // For grouped results, check if the column is directly in the row first\n if (isGroupedResult && columnRef in namespacedRow) {\n result[alias] = namespacedRow[columnRef]\n } else {\n // Extract the value from the nested structure\n result[alias] = extractValueFromNamespacedRow(\n namespacedRow,\n columnRef,\n mainTableAlias,\n undefined\n )\n }\n } else if (typeof expr === `object`) {\n // For grouped results, the aggregate results are already in the row\n if (isGroupedResult && alias in namespacedRow) {\n result[alias] = namespacedRow[alias]\n } else if ((expr as { ORDER_INDEX: unknown }).ORDER_INDEX) {\n result[alias] = namespacedRow[mainTableAlias]![alias]\n } else {\n // This might be a function call\n result[alias] = evaluateOperandOnNamespacedRow(\n namespacedRow,\n expr as ConditionOperand,\n mainTableAlias,\n undefined\n )\n }\n }\n }\n }\n }\n\n return [key, result] as [string, typeof result]\n })\n )\n}\n\n// Helper function to extract all columns from all tables in a nested row\nfunction extractAllColumnsFromAllTables(\n namespacedRow: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n // Process each table in the nested row\n for (const [tableAlias, tableData] of Object.entries(namespacedRow)) {\n if (tableData && typeof tableData === `object`) {\n // Add all columns from this table to the result\n // If there are column name conflicts, the last table's columns will overwrite previous ones\n Object.assign(\n result,\n extractAllColumnsFromTable(namespacedRow, tableAlias)\n )\n }\n }\n\n return result\n}\n\n// Helper function to extract all columns from a table in a nested row\nfunction extractAllColumnsFromTable(\n namespacedRow: Record<string, unknown>,\n tableAlias: string\n): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n // Get the table data\n const tableData = namespacedRow[tableAlias] as\n | Record<string, unknown>\n | null\n | undefined\n\n if (!tableData || typeof tableData !== `object`) {\n return result\n }\n\n // Add all columns from the table to the result\n for (const [columnName, value] of Object.entries(tableData)) {\n result[columnName] = value\n }\n\n return result\n}\n"],"names":["map","extractValueFromNamespacedRow","evaluateOperandOnNamespacedRow"],"mappings":"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"}
@@ -124,9 +124,11 @@ class Transaction {
124
124
  this.setState(`persisting`);
125
125
  if (this.mutations.length === 0) {
126
126
  this.setState(`completed`);
127
+ return this;
127
128
  }
128
129
  try {
129
- await this.mutationFn({ transaction: this });
130
+ const transactionWithMutations = this;
131
+ await this.mutationFn({ transaction: transactionWithMutations });
130
132
  this.setState(`completed`);
131
133
  this.touchCollection();
132
134
  this.isPersisted.resolve(this);
@@ -1 +1 @@
1
- {"version":3,"file":"transactions.cjs","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n PendingMutation,\n TransactionConfig,\n TransactionState,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction> = []\nlet transactionStack: Array<Transaction> = []\n\nexport function createTransaction(config: TransactionConfig): Transaction {\n if (typeof config.mutationFn === `undefined`) {\n throw `mutationFn is required when creating a transaction`\n }\n\n let transactionId = config.id\n if (!transactionId) {\n transactionId = generateUUID()\n }\n const newTransaction = new Transaction({ ...config, id: transactionId })\n\n transactions.push(newTransaction)\n\n return newTransaction\n}\n\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nfunction removeFromPendingList(tx: Transaction) {\n const index = transactions.findIndex((t) => t.id === tx.id)\n if (index !== -1) {\n transactions.splice(index, 1)\n }\n}\n\nexport class Transaction {\n public id: string\n public state: TransactionState\n public mutationFn\n public mutations: Array<PendingMutation<any>>\n public isPersisted: Deferred<Transaction>\n public autoCommit: boolean\n public createdAt: Date\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig) {\n this.id = config.id!\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n\n if (newState === `completed` || newState === `failed`) {\n removeFromPendingList(this)\n }\n }\n\n mutate(callback: () => void): Transaction {\n if (this.state !== `pending`) {\n throw `You can no longer call .mutate() as the transaction is no longer pending`\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit()\n }\n\n return this\n }\n\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.key === newMutation.key\n )\n\n if (existingIndex >= 0) {\n // Replace existing mutation\n this.mutations[existingIndex] = newMutation\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw `You can no longer call .rollback() as the transaction is already completed`\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same ids\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationIds = new Set()\n this.mutations.forEach((m) => mutationIds.add(m.key))\n for (const t of transactions) {\n t.state === `pending` &&\n t.mutations.some((m) => mutationIds.has(m.key)) &&\n t.rollback({ isSecondaryRollback: true })\n }\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n for (const mutation of this.mutations) {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection.transactions.setState((state) => state)\n mutation.collection.commitPendingTransactions()\n hasCalled.add(mutation.collection.id)\n }\n }\n }\n\n async commit(): Promise<Transaction> {\n if (this.state !== `pending`) {\n throw `You can no longer call .commit() as the transaction is no longer pending`\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n }\n\n // Run mutationFn\n try {\n await this.mutationFn({ transaction: this })\n\n this.setState(`completed`)\n this.touchCollection()\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Update transaction with error information\n this.error = {\n message: error instanceof Error ? error.message : String(error),\n error: error instanceof Error ? error : new Error(String(error)),\n }\n\n // rollback the transaction\n return this.rollback()\n }\n\n return this\n }\n}\n"],"names":["createDeferred"],"mappings":"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"}
1
+ {"version":3,"file":"transactions.cjs","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n PendingMutation,\n TransactionConfig,\n TransactionState,\n TransactionWithMutations,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction> = []\nlet transactionStack: Array<Transaction> = []\n\nexport function createTransaction(config: TransactionConfig): Transaction {\n if (typeof config.mutationFn === `undefined`) {\n throw `mutationFn is required when creating a transaction`\n }\n\n let transactionId = config.id\n if (!transactionId) {\n transactionId = generateUUID()\n }\n const newTransaction = new Transaction({ ...config, id: transactionId })\n\n transactions.push(newTransaction)\n\n return newTransaction\n}\n\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nfunction removeFromPendingList(tx: Transaction) {\n const index = transactions.findIndex((t) => t.id === tx.id)\n if (index !== -1) {\n transactions.splice(index, 1)\n }\n}\n\nexport class Transaction {\n public id: string\n public state: TransactionState\n public mutationFn\n public mutations: Array<PendingMutation<any>>\n public isPersisted: Deferred<Transaction>\n public autoCommit: boolean\n public createdAt: Date\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig) {\n this.id = config.id!\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n\n if (newState === `completed` || newState === `failed`) {\n removeFromPendingList(this)\n }\n }\n\n mutate(callback: () => void): Transaction {\n if (this.state !== `pending`) {\n throw `You can no longer call .mutate() as the transaction is no longer pending`\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit()\n }\n\n return this\n }\n\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.key === newMutation.key\n )\n\n if (existingIndex >= 0) {\n // Replace existing mutation\n this.mutations[existingIndex] = newMutation\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw `You can no longer call .rollback() as the transaction is already completed`\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same ids\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationIds = new Set()\n this.mutations.forEach((m) => mutationIds.add(m.key))\n for (const t of transactions) {\n t.state === `pending` &&\n t.mutations.some((m) => mutationIds.has(m.key)) &&\n t.rollback({ isSecondaryRollback: true })\n }\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n for (const mutation of this.mutations) {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection.transactions.setState((state) => state)\n mutation.collection.commitPendingTransactions()\n hasCalled.add(mutation.collection.id)\n }\n }\n }\n\n async commit(): Promise<Transaction> {\n if (this.state !== `pending`) {\n throw `You can no longer call .commit() as the transaction is no longer pending`\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n\n return this\n }\n\n // Run mutationFn\n try {\n // At this point we know there's at least one mutation\n // Use type assertion to tell TypeScript about this guarantee\n const transactionWithMutations =\n this as unknown as TransactionWithMutations\n await this.mutationFn({ transaction: transactionWithMutations })\n\n this.setState(`completed`)\n this.touchCollection()\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Update transaction with error information\n this.error = {\n message: error instanceof Error ? error.message : String(error),\n error: error instanceof Error ? error : new Error(String(error)),\n }\n\n // rollback the transaction\n return this.rollback()\n }\n\n return this\n }\n}\n"],"names":["createDeferred"],"mappings":"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"}