@tanstack/db 0.0.4 → 0.0.5

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 (102) hide show
  1. package/dist/cjs/collection.cjs +113 -94
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +38 -11
  4. package/dist/cjs/index.cjs +1 -0
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/proxy.cjs +87 -248
  7. package/dist/cjs/proxy.cjs.map +1 -1
  8. package/dist/cjs/proxy.d.cts +5 -5
  9. package/dist/cjs/query/compiled-query.cjs +23 -14
  10. package/dist/cjs/query/compiled-query.cjs.map +1 -1
  11. package/dist/cjs/query/compiled-query.d.cts +3 -1
  12. package/dist/cjs/query/evaluators.cjs +20 -20
  13. package/dist/cjs/query/evaluators.cjs.map +1 -1
  14. package/dist/cjs/query/evaluators.d.cts +3 -2
  15. package/dist/cjs/query/extractors.cjs +20 -20
  16. package/dist/cjs/query/extractors.cjs.map +1 -1
  17. package/dist/cjs/query/extractors.d.cts +3 -3
  18. package/dist/cjs/query/group-by.cjs +12 -15
  19. package/dist/cjs/query/group-by.cjs.map +1 -1
  20. package/dist/cjs/query/group-by.d.cts +7 -7
  21. package/dist/cjs/query/joins.cjs +41 -55
  22. package/dist/cjs/query/joins.cjs.map +1 -1
  23. package/dist/cjs/query/joins.d.cts +3 -3
  24. package/dist/cjs/query/order-by.cjs +37 -84
  25. package/dist/cjs/query/order-by.cjs.map +1 -1
  26. package/dist/cjs/query/order-by.d.cts +2 -2
  27. package/dist/cjs/query/pipeline-compiler.cjs +13 -18
  28. package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
  29. package/dist/cjs/query/pipeline-compiler.d.cts +2 -1
  30. package/dist/cjs/query/query-builder.cjs +0 -12
  31. package/dist/cjs/query/query-builder.cjs.map +1 -1
  32. package/dist/cjs/query/query-builder.d.cts +4 -8
  33. package/dist/cjs/query/schema.d.cts +1 -6
  34. package/dist/cjs/query/select.cjs +35 -24
  35. package/dist/cjs/query/select.cjs.map +1 -1
  36. package/dist/cjs/query/select.d.cts +2 -2
  37. package/dist/cjs/query/types.d.cts +1 -0
  38. package/dist/cjs/transactions.cjs +17 -8
  39. package/dist/cjs/transactions.cjs.map +1 -1
  40. package/dist/cjs/types.d.cts +41 -7
  41. package/dist/esm/collection.d.ts +38 -11
  42. package/dist/esm/collection.js +113 -94
  43. package/dist/esm/collection.js.map +1 -1
  44. package/dist/esm/index.js +2 -1
  45. package/dist/esm/proxy.d.ts +5 -5
  46. package/dist/esm/proxy.js +87 -248
  47. package/dist/esm/proxy.js.map +1 -1
  48. package/dist/esm/query/compiled-query.d.ts +3 -1
  49. package/dist/esm/query/compiled-query.js +23 -14
  50. package/dist/esm/query/compiled-query.js.map +1 -1
  51. package/dist/esm/query/evaluators.d.ts +3 -2
  52. package/dist/esm/query/evaluators.js +21 -21
  53. package/dist/esm/query/evaluators.js.map +1 -1
  54. package/dist/esm/query/extractors.d.ts +3 -3
  55. package/dist/esm/query/extractors.js +20 -20
  56. package/dist/esm/query/extractors.js.map +1 -1
  57. package/dist/esm/query/group-by.d.ts +7 -7
  58. package/dist/esm/query/group-by.js +14 -17
  59. package/dist/esm/query/group-by.js.map +1 -1
  60. package/dist/esm/query/joins.d.ts +3 -3
  61. package/dist/esm/query/joins.js +42 -56
  62. package/dist/esm/query/joins.js.map +1 -1
  63. package/dist/esm/query/order-by.d.ts +2 -2
  64. package/dist/esm/query/order-by.js +39 -86
  65. package/dist/esm/query/order-by.js.map +1 -1
  66. package/dist/esm/query/pipeline-compiler.d.ts +2 -1
  67. package/dist/esm/query/pipeline-compiler.js +14 -19
  68. package/dist/esm/query/pipeline-compiler.js.map +1 -1
  69. package/dist/esm/query/query-builder.d.ts +4 -8
  70. package/dist/esm/query/query-builder.js +0 -12
  71. package/dist/esm/query/query-builder.js.map +1 -1
  72. package/dist/esm/query/schema.d.ts +1 -6
  73. package/dist/esm/query/select.d.ts +2 -2
  74. package/dist/esm/query/select.js +36 -25
  75. package/dist/esm/query/select.js.map +1 -1
  76. package/dist/esm/query/types.d.ts +1 -0
  77. package/dist/esm/transactions.js +17 -8
  78. package/dist/esm/transactions.js.map +1 -1
  79. package/dist/esm/types.d.ts +41 -7
  80. package/package.json +2 -2
  81. package/src/collection.ts +174 -121
  82. package/src/proxy.ts +141 -358
  83. package/src/query/compiled-query.ts +30 -15
  84. package/src/query/evaluators.ts +22 -21
  85. package/src/query/extractors.ts +24 -21
  86. package/src/query/group-by.ts +24 -22
  87. package/src/query/joins.ts +88 -75
  88. package/src/query/order-by.ts +56 -106
  89. package/src/query/pipeline-compiler.ts +34 -37
  90. package/src/query/query-builder.ts +9 -23
  91. package/src/query/schema.ts +1 -10
  92. package/src/query/select.ts +44 -32
  93. package/src/query/types.ts +1 -0
  94. package/src/transactions.ts +22 -13
  95. package/src/types.ts +48 -7
  96. package/dist/cjs/query/key-by.cjs +0 -43
  97. package/dist/cjs/query/key-by.cjs.map +0 -1
  98. package/dist/cjs/query/key-by.d.cts +0 -3
  99. package/dist/esm/query/key-by.d.ts +0 -3
  100. package/dist/esm/query/key-by.js +0 -43
  101. package/dist/esm/query/key-by.js.map +0 -1
  102. package/src/query/key-by.ts +0 -61
@@ -1,29 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const functions = require("./functions.cjs");
4
- function extractValueFromNestedRow(nestedRow, columnRef, mainTableAlias, joinedTableAlias) {
4
+ function extractValueFromNamespacedRow(namespacedRow, columnRef, mainTableAlias, joinedTableAlias) {
5
5
  if (columnRef.includes(`.`)) {
6
6
  const [tableAlias, colName] = columnRef.split(`.`);
7
- const tableData = nestedRow[tableAlias];
7
+ const tableData = namespacedRow[tableAlias];
8
8
  if (!tableData) {
9
9
  return null;
10
10
  }
11
11
  const value = tableData[colName];
12
12
  return value;
13
13
  } else {
14
- if (mainTableAlias && nestedRow[mainTableAlias]) {
15
- const mainTableData = nestedRow[mainTableAlias];
14
+ if (mainTableAlias && namespacedRow[mainTableAlias]) {
15
+ const mainTableData = namespacedRow[mainTableAlias];
16
16
  if (typeof mainTableData === `object` && columnRef in mainTableData) {
17
17
  return mainTableData[columnRef];
18
18
  }
19
19
  }
20
- if (joinedTableAlias && nestedRow[joinedTableAlias]) {
21
- const joinedTableData = nestedRow[joinedTableAlias];
20
+ if (joinedTableAlias && namespacedRow[joinedTableAlias]) {
21
+ const joinedTableData = namespacedRow[joinedTableAlias];
22
22
  if (typeof joinedTableData === `object` && columnRef in joinedTableData) {
23
23
  return joinedTableData[columnRef];
24
24
  }
25
25
  }
26
- for (const [_tableAlias, tableData] of Object.entries(nestedRow)) {
26
+ for (const [_tableAlias, tableData] of Object.entries(namespacedRow)) {
27
27
  if (tableData && typeof tableData === `object` && columnRef in tableData) {
28
28
  return tableData[columnRef];
29
29
  }
@@ -31,11 +31,11 @@ function extractValueFromNestedRow(nestedRow, columnRef, mainTableAlias, joinedT
31
31
  return void 0;
32
32
  }
33
33
  }
34
- function evaluateOperandOnNestedRow(nestedRow, operand, mainTableAlias, joinedTableAlias) {
34
+ function evaluateOperandOnNamespacedRow(namespacedRow, operand, mainTableAlias, joinedTableAlias) {
35
35
  if (typeof operand === `string` && operand.startsWith(`@`)) {
36
36
  const columnRef = operand.substring(1);
37
- return extractValueFromNestedRow(
38
- nestedRow,
37
+ return extractValueFromNamespacedRow(
38
+ namespacedRow,
39
39
  columnRef,
40
40
  mainTableAlias,
41
41
  joinedTableAlias
@@ -44,14 +44,14 @@ function evaluateOperandOnNestedRow(nestedRow, operand, mainTableAlias, joinedTa
44
44
  if (operand && typeof operand === `object` && `col` in operand) {
45
45
  const colRef = operand.col;
46
46
  if (typeof colRef === `string`) {
47
- const nestedValue = extractValueFromNestedRow(
48
- nestedRow,
47
+ const nestedValue = extractValueFromNamespacedRow(
48
+ namespacedRow,
49
49
  colRef,
50
50
  mainTableAlias,
51
51
  joinedTableAlias
52
52
  );
53
- if (nestedValue === void 0 && colRef in nestedRow) {
54
- return nestedRow[colRef];
53
+ if (nestedValue === void 0 && colRef in namespacedRow) {
54
+ return namespacedRow[colRef];
55
55
  }
56
56
  return nestedValue;
57
57
  }
@@ -61,14 +61,14 @@ function evaluateOperandOnNestedRow(nestedRow, operand, mainTableAlias, joinedTa
61
61
  const functionName = Object.keys(operand)[0];
62
62
  const args = operand[functionName];
63
63
  const evaluatedArgs = Array.isArray(args) ? args.map(
64
- (arg) => evaluateOperandOnNestedRow(
65
- nestedRow,
64
+ (arg) => evaluateOperandOnNamespacedRow(
65
+ namespacedRow,
66
66
  arg,
67
67
  mainTableAlias,
68
68
  joinedTableAlias
69
69
  )
70
- ) : evaluateOperandOnNestedRow(
71
- nestedRow,
70
+ ) : evaluateOperandOnNamespacedRow(
71
+ namespacedRow,
72
72
  args,
73
73
  mainTableAlias,
74
74
  joinedTableAlias
@@ -116,7 +116,7 @@ function extractJoinKey(row, operand, defaultTableAlias) {
116
116
  }
117
117
  return keyValue;
118
118
  }
119
- exports.evaluateOperandOnNestedRow = evaluateOperandOnNestedRow;
119
+ exports.evaluateOperandOnNamespacedRow = evaluateOperandOnNamespacedRow;
120
120
  exports.extractJoinKey = extractJoinKey;
121
- exports.extractValueFromNestedRow = extractValueFromNestedRow;
121
+ exports.extractValueFromNamespacedRow = extractValueFromNamespacedRow;
122
122
  //# sourceMappingURL=extractors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extractors.cjs","sources":["../../../src/query/extractors.ts"],"sourcesContent":["import { evaluateFunction, isFunctionCall } from \"./functions.js\"\nimport type { AllowedFunctionName, ConditionOperand } from \"./schema.js\"\n\n/**\n * Extracts a value from a nested row structure\n * @param nestedRow The nested row structure\n * @param columnRef The column reference (may include table.column format)\n * @param mainTableAlias The main table alias to check first for columns without table reference\n * @param joinedTableAlias The joined table alias to check second for columns without table reference\n * @returns The extracted value or undefined if not found\n */\nexport function extractValueFromNestedRow(\n nestedRow: Record<string, unknown>,\n columnRef: string,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): unknown {\n // Check if it's a table.column reference\n if (columnRef.includes(`.`)) {\n const [tableAlias, colName] = columnRef.split(`.`) as [string, string]\n\n // Get the table data\n const tableData = nestedRow[tableAlias] as\n | Record<string, unknown>\n | null\n | undefined\n\n if (!tableData) {\n return null\n }\n\n // Return the column value from that table\n const value = tableData[colName]\n return value\n } else {\n // If no table is specified, first try to find in the main table if provided\n if (mainTableAlias && nestedRow[mainTableAlias]) {\n const mainTableData = nestedRow[mainTableAlias] as Record<string, unknown>\n if (typeof mainTableData === `object` && columnRef in mainTableData) {\n return mainTableData[columnRef]\n }\n }\n\n // Then try the joined table if provided\n if (joinedTableAlias && nestedRow[joinedTableAlias]) {\n const joinedTableData = nestedRow[joinedTableAlias] as Record<\n string,\n unknown\n >\n if (typeof joinedTableData === `object` && columnRef in joinedTableData) {\n return joinedTableData[columnRef]\n }\n }\n\n // If not found in main or joined table, try to find the column in any table\n for (const [_tableAlias, tableData] of Object.entries(nestedRow)) {\n if (\n tableData &&\n typeof tableData === `object` &&\n columnRef in (tableData as Record<string, unknown>)\n ) {\n return (tableData as Record<string, unknown>)[columnRef]\n }\n }\n return undefined\n }\n}\n\n/**\n * Evaluates an operand against a nested row structure\n */\nexport function evaluateOperandOnNestedRow(\n nestedRow: Record<string, unknown>,\n operand: ConditionOperand,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): unknown {\n // Handle column references\n if (typeof operand === `string` && operand.startsWith(`@`)) {\n const columnRef = operand.substring(1)\n return extractValueFromNestedRow(\n nestedRow,\n columnRef,\n mainTableAlias,\n joinedTableAlias\n )\n }\n\n // Handle explicit column references\n if (operand && typeof operand === `object` && `col` in operand) {\n const colRef = (operand as { col: unknown }).col\n\n if (typeof colRef === `string`) {\n // First try to extract from nested row structure\n const nestedValue = extractValueFromNestedRow(\n nestedRow,\n colRef,\n mainTableAlias,\n joinedTableAlias\n )\n\n // If not found in nested structure, check if it's a direct property of the row\n // This is important for HAVING clauses that reference aggregated values\n if (nestedValue === undefined && colRef in nestedRow) {\n return nestedRow[colRef]\n }\n\n return nestedValue\n }\n\n return undefined\n }\n\n // Handle function calls\n if (operand && typeof operand === `object` && isFunctionCall(operand)) {\n // Get the function name (the only key in the object)\n const functionName = Object.keys(operand)[0] as AllowedFunctionName\n // Get the arguments using type assertion with specific function name\n const args = (operand as any)[functionName]\n\n // If the arguments are a reference or another expression, evaluate them first\n const evaluatedArgs = Array.isArray(args)\n ? args.map((arg) =>\n evaluateOperandOnNestedRow(\n nestedRow,\n arg as ConditionOperand,\n mainTableAlias,\n joinedTableAlias\n )\n )\n : evaluateOperandOnNestedRow(\n nestedRow,\n args as ConditionOperand,\n mainTableAlias,\n joinedTableAlias\n )\n\n // Call the function with the evaluated arguments\n return evaluateFunction(\n functionName,\n evaluatedArgs as ConditionOperand | Array<ConditionOperand>\n )\n }\n\n // Handle explicit literals\n if (operand && typeof operand === `object` && `value` in operand) {\n return (operand as { value: unknown }).value\n }\n\n // Handle literal values\n return operand\n}\n\n/**\n * Extracts a join key value from a row based on the operand\n * @param row The data row (not nested)\n * @param operand The operand to extract the key from\n * @param defaultTableAlias The default table alias\n * @returns The extracted key value\n */\nexport function extractJoinKey<T extends Record<string, unknown>>(\n row: T,\n operand: ConditionOperand,\n defaultTableAlias?: string\n): unknown {\n let keyValue: unknown\n\n // Handle column references (e.g., \"@orders.id\" or \"@id\")\n if (typeof operand === `string` && operand.startsWith(`@`)) {\n const columnRef = operand.substring(1)\n\n // If it contains a dot, extract the table and column\n if (columnRef.includes(`.`)) {\n const [tableAlias, colName] = columnRef.split(`.`) as [string, string]\n // If this is referencing the current table, extract from row directly\n if (tableAlias === defaultTableAlias) {\n keyValue = row[colName]\n } else {\n // This might be a column from another table, return undefined\n keyValue = undefined\n }\n } else {\n // No table specified, look directly in the row\n keyValue = row[columnRef]\n }\n } else if (operand && typeof operand === `object` && `col` in operand) {\n // Handle explicit column references like { col: \"orders.id\" } or { col: \"id\" }\n const colRef = (operand as { col: unknown }).col\n\n if (typeof colRef === `string`) {\n if (colRef.includes(`.`)) {\n const [tableAlias, colName] = colRef.split(`.`) as [string, string]\n // If this is referencing the current table, extract from row directly\n if (tableAlias === defaultTableAlias) {\n keyValue = row[colName]\n } else {\n // This might be a column from another table, return undefined\n keyValue = undefined\n }\n } else {\n // No table specified, look directly in the row\n keyValue = row[colRef]\n }\n }\n } else {\n // Handle literals or other types\n keyValue = operand\n }\n\n return keyValue\n}\n"],"names":["isFunctionCall","evaluateFunction"],"mappings":"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"}
1
+ {"version":3,"file":"extractors.cjs","sources":["../../../src/query/extractors.ts"],"sourcesContent":["import { evaluateFunction, isFunctionCall } from \"./functions.js\"\nimport type { AllowedFunctionName, ConditionOperand } from \"./schema.js\"\n\n/**\n * Extracts a value from a nested row structure\n * @param namespacedRow The nested row structure\n * @param columnRef The column reference (may include table.column format)\n * @param mainTableAlias The main table alias to check first for columns without table reference\n * @param joinedTableAlias The joined table alias to check second for columns without table reference\n * @returns The extracted value or undefined if not found\n */\nexport function extractValueFromNamespacedRow(\n namespacedRow: Record<string, unknown>,\n columnRef: string,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): unknown {\n // Check if it's a table.column reference\n if (columnRef.includes(`.`)) {\n const [tableAlias, colName] = columnRef.split(`.`) as [string, string]\n\n // Get the table data\n const tableData = namespacedRow[tableAlias] as\n | Record<string, unknown>\n | null\n | undefined\n\n if (!tableData) {\n return null\n }\n\n // Return the column value from that table\n const value = tableData[colName]\n return value\n } else {\n // If no table is specified, first try to find in the main table if provided\n if (mainTableAlias && namespacedRow[mainTableAlias]) {\n const mainTableData = namespacedRow[mainTableAlias] as Record<\n string,\n unknown\n >\n if (typeof mainTableData === `object` && columnRef in mainTableData) {\n return mainTableData[columnRef]\n }\n }\n\n // Then try the joined table if provided\n if (joinedTableAlias && namespacedRow[joinedTableAlias]) {\n const joinedTableData = namespacedRow[joinedTableAlias] as Record<\n string,\n unknown\n >\n if (typeof joinedTableData === `object` && columnRef in joinedTableData) {\n return joinedTableData[columnRef]\n }\n }\n\n // If not found in main or joined table, try to find the column in any table\n for (const [_tableAlias, tableData] of Object.entries(namespacedRow)) {\n if (\n tableData &&\n typeof tableData === `object` &&\n columnRef in (tableData as Record<string, unknown>)\n ) {\n return (tableData as Record<string, unknown>)[columnRef]\n }\n }\n return undefined\n }\n}\n\n/**\n * Evaluates an operand against a nested row structure\n */\nexport function evaluateOperandOnNamespacedRow(\n namespacedRow: Record<string, unknown>,\n operand: ConditionOperand,\n mainTableAlias?: string,\n joinedTableAlias?: string\n): unknown {\n // Handle column references\n if (typeof operand === `string` && operand.startsWith(`@`)) {\n const columnRef = operand.substring(1)\n return extractValueFromNamespacedRow(\n namespacedRow,\n columnRef,\n mainTableAlias,\n joinedTableAlias\n )\n }\n\n // Handle explicit column references\n if (operand && typeof operand === `object` && `col` in operand) {\n const colRef = (operand as { col: unknown }).col\n\n if (typeof colRef === `string`) {\n // First try to extract from nested row structure\n const nestedValue = extractValueFromNamespacedRow(\n namespacedRow,\n colRef,\n mainTableAlias,\n joinedTableAlias\n )\n\n // If not found in nested structure, check if it's a direct property of the row\n // This is important for HAVING clauses that reference aggregated values\n if (nestedValue === undefined && colRef in namespacedRow) {\n return namespacedRow[colRef]\n }\n\n return nestedValue\n }\n\n return undefined\n }\n\n // Handle function calls\n if (operand && typeof operand === `object` && isFunctionCall(operand)) {\n // Get the function name (the only key in the object)\n const functionName = Object.keys(operand)[0] as AllowedFunctionName\n // Get the arguments using type assertion with specific function name\n const args = (operand as any)[functionName]\n\n // If the arguments are a reference or another expression, evaluate them first\n const evaluatedArgs = Array.isArray(args)\n ? args.map((arg) =>\n evaluateOperandOnNamespacedRow(\n namespacedRow,\n arg as ConditionOperand,\n mainTableAlias,\n joinedTableAlias\n )\n )\n : evaluateOperandOnNamespacedRow(\n namespacedRow,\n args as ConditionOperand,\n mainTableAlias,\n joinedTableAlias\n )\n\n // Call the function with the evaluated arguments\n return evaluateFunction(\n functionName,\n evaluatedArgs as ConditionOperand | Array<ConditionOperand>\n )\n }\n\n // Handle explicit literals\n if (operand && typeof operand === `object` && `value` in operand) {\n return (operand as { value: unknown }).value\n }\n\n // Handle literal values\n return operand\n}\n\n/**\n * Extracts a join key value from a row based on the operand\n * @param row The data row (not nested)\n * @param operand The operand to extract the key from\n * @param defaultTableAlias The default table alias\n * @returns The extracted key value\n */\nexport function extractJoinKey<T extends Record<string, unknown>>(\n row: T,\n operand: ConditionOperand,\n defaultTableAlias?: string\n): unknown {\n let keyValue: unknown\n\n // Handle column references (e.g., \"@orders.id\" or \"@id\")\n if (typeof operand === `string` && operand.startsWith(`@`)) {\n const columnRef = operand.substring(1)\n\n // If it contains a dot, extract the table and column\n if (columnRef.includes(`.`)) {\n const [tableAlias, colName] = columnRef.split(`.`) as [string, string]\n // If this is referencing the current table, extract from row directly\n if (tableAlias === defaultTableAlias) {\n keyValue = row[colName]\n } else {\n // This might be a column from another table, return undefined\n keyValue = undefined\n }\n } else {\n // No table specified, look directly in the row\n keyValue = row[columnRef]\n }\n } else if (operand && typeof operand === `object` && `col` in operand) {\n // Handle explicit column references like { col: \"orders.id\" } or { col: \"id\" }\n const colRef = (operand as { col: unknown }).col\n\n if (typeof colRef === `string`) {\n if (colRef.includes(`.`)) {\n const [tableAlias, colName] = colRef.split(`.`) as [string, string]\n // If this is referencing the current table, extract from row directly\n if (tableAlias === defaultTableAlias) {\n keyValue = row[colName]\n } else {\n // This might be a column from another table, return undefined\n keyValue = undefined\n }\n } else {\n // No table specified, look directly in the row\n keyValue = row[colRef]\n }\n }\n } else {\n // Handle literals or other types\n keyValue = operand\n }\n\n return keyValue\n}\n"],"names":["isFunctionCall","evaluateFunction"],"mappings":"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"}
@@ -1,17 +1,17 @@
1
1
  import { ConditionOperand } from './schema.js';
2
2
  /**
3
3
  * Extracts a value from a nested row structure
4
- * @param nestedRow The nested row structure
4
+ * @param namespacedRow The nested row structure
5
5
  * @param columnRef The column reference (may include table.column format)
6
6
  * @param mainTableAlias The main table alias to check first for columns without table reference
7
7
  * @param joinedTableAlias The joined table alias to check second for columns without table reference
8
8
  * @returns The extracted value or undefined if not found
9
9
  */
10
- export declare function extractValueFromNestedRow(nestedRow: Record<string, unknown>, columnRef: string, mainTableAlias?: string, joinedTableAlias?: string): unknown;
10
+ export declare function extractValueFromNamespacedRow(namespacedRow: Record<string, unknown>, columnRef: string, mainTableAlias?: string, joinedTableAlias?: string): unknown;
11
11
  /**
12
12
  * Evaluates an operand against a nested row structure
13
13
  */
14
- export declare function evaluateOperandOnNestedRow(nestedRow: Record<string, unknown>, operand: ConditionOperand, mainTableAlias?: string, joinedTableAlias?: string): unknown;
14
+ export declare function evaluateOperandOnNamespacedRow(namespacedRow: Record<string, unknown>, operand: ConditionOperand, mainTableAlias?: string, joinedTableAlias?: string): unknown;
15
15
  /**
16
16
  * Extracts a join key value from a row based on the operand
17
17
  * @param row The data row (not nested)
@@ -6,14 +6,14 @@ const utils = require("./utils.cjs");
6
6
  const { sum, count, avg, min, max, median, mode } = d2ts.groupByOperators;
7
7
  function processGroupBy(pipeline, query, mainTableAlias) {
8
8
  const groupByColumns = Array.isArray(query.groupBy) ? query.groupBy : [query.groupBy];
9
- const keyExtractor = (nestedRow) => {
9
+ const keyExtractor = ([_oldKey, namespacedRow]) => {
10
10
  const key = {};
11
11
  for (const column of groupByColumns) {
12
12
  if (typeof column === `string` && column.startsWith(`@`)) {
13
13
  const columnRef = column.substring(1);
14
14
  const columnName = columnRef.includes(`.`) ? columnRef.split(`.`)[1] : columnRef;
15
- key[columnName] = extractors.extractValueFromNestedRow(
16
- nestedRow,
15
+ key[columnName] = extractors.extractValueFromNamespacedRow(
16
+ namespacedRow,
17
17
  columnRef,
18
18
  mainTableAlias
19
19
  );
@@ -22,6 +22,9 @@ function processGroupBy(pipeline, query, mainTableAlias) {
22
22
  return key;
23
23
  };
24
24
  const aggregates = {};
25
+ if (!query.select) {
26
+ throw new Error(`SELECT clause is required for GROUP BY`);
27
+ }
25
28
  for (const item of query.select) {
26
29
  if (typeof item === `object`) {
27
30
  for (const [alias, expr] of Object.entries(item)) {
@@ -38,28 +41,22 @@ function processGroupBy(pipeline, query, mainTableAlias) {
38
41
  }
39
42
  }
40
43
  if (Object.keys(aggregates).length > 0) {
41
- pipeline = pipeline.pipe(
42
- d2ts.groupBy(keyExtractor, aggregates),
43
- // Convert KeyValue<string, ResultType> to Record<string, unknown>
44
- d2ts.map(([_key, value]) => {
45
- return value;
46
- })
47
- );
44
+ pipeline = pipeline.pipe(d2ts.groupBy(keyExtractor, aggregates));
48
45
  }
49
46
  return pipeline;
50
47
  }
51
48
  function getAggregateFunction(functionName, columnRef, mainTableAlias) {
52
- const valueExtractor = (nestedRow) => {
49
+ const valueExtractor = ([_oldKey, namespacedRow]) => {
53
50
  let value;
54
51
  if (typeof columnRef === `string` && columnRef.startsWith(`@`)) {
55
- value = extractors.extractValueFromNestedRow(
56
- nestedRow,
52
+ value = extractors.extractValueFromNamespacedRow(
53
+ namespacedRow,
57
54
  columnRef.substring(1),
58
55
  mainTableAlias
59
56
  );
60
57
  } else {
61
- value = extractors.evaluateOperandOnNestedRow(
62
- nestedRow,
58
+ value = extractors.evaluateOperandOnNamespacedRow(
59
+ namespacedRow,
63
60
  columnRef,
64
61
  mainTableAlias
65
62
  );
@@ -1 +1 @@
1
- {"version":3,"file":"group-by.cjs","sources":["../../../src/query/group-by.ts"],"sourcesContent":["import { groupBy, groupByOperators, map } from \"@electric-sql/d2ts\"\nimport {\n evaluateOperandOnNestedRow,\n extractValueFromNestedRow,\n} from \"./extractors\"\nimport { isAggregateFunctionCall } from \"./utils\"\nimport type { ConditionOperand, FunctionCall, Query } from \"./schema\"\nimport type { IStreamBuilder } from \"@electric-sql/d2ts\"\n\nconst { sum, count, avg, min, max, median, mode } = groupByOperators\n\n/**\n * Process the groupBy clause in a D2QL query\n */\nexport function processGroupBy(\n pipeline: IStreamBuilder<Record<string, unknown>>,\n query: Query,\n mainTableAlias: string\n) {\n // Normalize groupBy to an array of column references\n const groupByColumns = Array.isArray(query.groupBy)\n ? query.groupBy\n : [query.groupBy]\n\n // Create a key extractor function for the groupBy operator\n const keyExtractor = (nestedRow: Record<string, unknown>) => {\n const key: Record<string, unknown> = {}\n\n // Extract each groupBy column value\n for (const column of groupByColumns) {\n if (typeof column === `string` && (column as string).startsWith(`@`)) {\n const columnRef = (column as string).substring(1)\n const columnName = columnRef.includes(`.`)\n ? columnRef.split(`.`)[1]\n : columnRef\n\n key[columnName!] = extractValueFromNestedRow(\n nestedRow,\n columnRef,\n mainTableAlias\n )\n }\n }\n\n return key\n }\n\n // Create aggregate functions for any aggregated columns in the SELECT clause\n const aggregates: Record<string, any> = {}\n\n // Scan the SELECT clause for aggregate functions\n for (const item of query.select) {\n if (typeof item === `object`) {\n for (const [alias, expr] of Object.entries(item)) {\n if (typeof expr === `object` && isAggregateFunctionCall(expr)) {\n // Get the function name (the only key in the object)\n const functionName = Object.keys(expr)[0]\n // Get the column reference or expression to aggregate\n const columnRef = (expr as FunctionCall)[\n functionName as keyof FunctionCall\n ]\n\n // Add the aggregate function to our aggregates object\n aggregates[alias] = getAggregateFunction(\n functionName!,\n columnRef,\n mainTableAlias\n )\n }\n }\n }\n }\n\n // Apply the groupBy operator if we have any aggregates\n if (Object.keys(aggregates).length > 0) {\n pipeline = pipeline.pipe(\n groupBy(keyExtractor, aggregates),\n // Convert KeyValue<string, ResultType> to Record<string, unknown>\n map(([_key, value]) => {\n // After groupBy, the value already contains both the key fields and aggregate results\n // We need to return it as is, not wrapped in a nested structure\n return value as Record<string, unknown>\n })\n )\n }\n\n return pipeline\n}\n\n/**\n * Helper function to get an aggregate function based on the function name\n */\nexport function getAggregateFunction(\n functionName: string,\n columnRef: string | ConditionOperand,\n mainTableAlias: string\n) {\n // Create a value extractor function for the column to aggregate\n const valueExtractor = (nestedRow: Record<string, unknown>) => {\n let value: unknown\n if (typeof columnRef === `string` && columnRef.startsWith(`@`)) {\n value = extractValueFromNestedRow(\n nestedRow,\n columnRef.substring(1),\n mainTableAlias\n )\n } else {\n value = evaluateOperandOnNestedRow(\n nestedRow,\n columnRef as ConditionOperand,\n mainTableAlias\n )\n }\n // Ensure we return a number for aggregate functions\n return typeof value === `number` ? value : 0\n }\n\n // Return the appropriate aggregate function\n switch (functionName.toUpperCase()) {\n case `SUM`:\n return sum(valueExtractor)\n case `COUNT`:\n return count() // count() doesn't need a value extractor\n case `AVG`:\n return avg(valueExtractor)\n case `MIN`:\n return min(valueExtractor)\n case `MAX`:\n return max(valueExtractor)\n case `MEDIAN`:\n return median(valueExtractor)\n case `MODE`:\n return mode(valueExtractor)\n default:\n throw new Error(`Unsupported aggregate function: ${functionName}`)\n }\n}\n"],"names":["groupByOperators","extractValueFromNestedRow","isAggregateFunctionCall","groupBy","map","evaluateOperandOnNestedRow"],"mappings":";;;;;AASA,MAAM,EAAE,KAAK,OAAO,KAAK,KAAK,KAAK,QAAQ,SAASA,KAAA;AAKpC,SAAA,eACd,UACA,OACA,gBACA;AAEM,QAAA,iBAAiB,MAAM,QAAQ,MAAM,OAAO,IAC9C,MAAM,UACN,CAAC,MAAM,OAAO;AAGZ,QAAA,eAAe,CAAC,cAAuC;AAC3D,UAAM,MAA+B,CAAC;AAGtC,eAAW,UAAU,gBAAgB;AACnC,UAAI,OAAO,WAAW,YAAa,OAAkB,WAAW,GAAG,GAAG;AAC9D,cAAA,YAAa,OAAkB,UAAU,CAAC;AAC1C,cAAA,aAAa,UAAU,SAAS,GAAG,IACrC,UAAU,MAAM,GAAG,EAAE,CAAC,IACtB;AAEJ,YAAI,UAAW,IAAIC,WAAA;AAAA,UACjB;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MAAA;AAAA,IACF;AAGK,WAAA;AAAA,EACT;AAGA,QAAM,aAAkC,CAAC;AAG9B,aAAA,QAAQ,MAAM,QAAQ;AAC3B,QAAA,OAAO,SAAS,UAAU;AAC5B,iBAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,IAAI,GAAG;AAChD,YAAI,OAAO,SAAS,YAAYC,MAAA,wBAAwB,IAAI,GAAG;AAE7D,gBAAM,eAAe,OAAO,KAAK,IAAI,EAAE,CAAC;AAElC,gBAAA,YAAa,KACjB,YACF;AAGA,qBAAW,KAAK,IAAI;AAAA,YAClB;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIF,MAAI,OAAO,KAAK,UAAU,EAAE,SAAS,GAAG;AACtC,eAAW,SAAS;AAAA,MAClBC,KAAA,QAAQ,cAAc,UAAU;AAAA;AAAA,MAEhCC,KAAAA,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;AAGd,eAAA;AAAA,MACR,CAAA;AAAA,IACH;AAAA,EAAA;AAGK,SAAA;AACT;AAKgB,SAAA,qBACd,cACA,WACA,gBACA;AAEM,QAAA,iBAAiB,CAAC,cAAuC;AACzD,QAAA;AACJ,QAAI,OAAO,cAAc,YAAY,UAAU,WAAW,GAAG,GAAG;AACtD,cAAAH,WAAA;AAAA,QACN;AAAA,QACA,UAAU,UAAU,CAAC;AAAA,QACrB;AAAA,MACF;AAAA,IAAA,OACK;AACG,cAAAI,WAAA;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IAAA;AAGK,WAAA,OAAO,UAAU,WAAW,QAAQ;AAAA,EAC7C;AAGQ,UAAA,aAAa,YAAe,GAAA;AAAA,IAClC,KAAK;AACH,aAAO,IAAI,cAAc;AAAA,IAC3B,KAAK;AACH,aAAO,MAAM;AAAA;AAAA,IACf,KAAK;AACH,aAAO,IAAI,cAAc;AAAA,IAC3B,KAAK;AACH,aAAO,IAAI,cAAc;AAAA,IAC3B,KAAK;AACH,aAAO,IAAI,cAAc;AAAA,IAC3B,KAAK;AACH,aAAO,OAAO,cAAc;AAAA,IAC9B,KAAK;AACH,aAAO,KAAK,cAAc;AAAA,IAC5B;AACE,YAAM,IAAI,MAAM,mCAAmC,YAAY,EAAE;AAAA,EAAA;AAEvE;;;"}
1
+ {"version":3,"file":"group-by.cjs","sources":["../../../src/query/group-by.ts"],"sourcesContent":["import { groupBy, groupByOperators } from \"@electric-sql/d2ts\"\nimport {\n evaluateOperandOnNamespacedRow,\n extractValueFromNamespacedRow,\n} from \"./extractors\"\nimport { isAggregateFunctionCall } from \"./utils\"\nimport type { ConditionOperand, FunctionCall, Query } from \"./schema\"\nimport type { NamespacedAndKeyedStream } from \"../types.js\"\n\nconst { sum, count, avg, min, max, median, mode } = groupByOperators\n\n/**\n * Process the groupBy clause in a D2QL query\n */\nexport function processGroupBy(\n pipeline: NamespacedAndKeyedStream,\n query: Query,\n mainTableAlias: string\n) {\n // Normalize groupBy to an array of column references\n const groupByColumns = Array.isArray(query.groupBy)\n ? query.groupBy\n : [query.groupBy]\n\n // Create a key extractor function for the groupBy operator\n const keyExtractor = ([_oldKey, namespacedRow]: [\n string,\n Record<string, unknown>,\n ]) => {\n const key: Record<string, unknown> = {}\n\n // Extract each groupBy column value\n for (const column of groupByColumns) {\n if (typeof column === `string` && (column as string).startsWith(`@`)) {\n const columnRef = (column as string).substring(1)\n const columnName = columnRef.includes(`.`)\n ? columnRef.split(`.`)[1]\n : columnRef\n\n key[columnName!] = extractValueFromNamespacedRow(\n namespacedRow,\n columnRef,\n mainTableAlias\n )\n }\n }\n\n return key\n }\n\n // Create aggregate functions for any aggregated columns in the SELECT clause\n const aggregates: Record<string, any> = {}\n\n if (!query.select) {\n throw new Error(`SELECT clause is required for GROUP BY`)\n }\n\n // Scan the SELECT clause for aggregate functions\n for (const item of query.select) {\n if (typeof item === `object`) {\n for (const [alias, expr] of Object.entries(item)) {\n if (typeof expr === `object` && isAggregateFunctionCall(expr)) {\n // Get the function name (the only key in the object)\n const functionName = Object.keys(expr)[0]\n // Get the column reference or expression to aggregate\n const columnRef = (expr as FunctionCall)[\n functionName as keyof FunctionCall\n ]\n\n // Add the aggregate function to our aggregates object\n aggregates[alias] = getAggregateFunction(\n functionName!,\n columnRef,\n mainTableAlias\n )\n }\n }\n }\n }\n\n // Apply the groupBy operator if we have any aggregates\n if (Object.keys(aggregates).length > 0) {\n pipeline = pipeline.pipe(groupBy(keyExtractor, aggregates))\n }\n\n return pipeline\n}\n\n/**\n * Helper function to get an aggregate function based on the function name\n */\nexport function getAggregateFunction(\n functionName: string,\n columnRef: string | ConditionOperand,\n mainTableAlias: string\n) {\n // Create a value extractor function for the column to aggregate\n const valueExtractor = ([_oldKey, namespacedRow]: [\n string,\n Record<string, unknown>,\n ]) => {\n let value: unknown\n if (typeof columnRef === `string` && columnRef.startsWith(`@`)) {\n value = extractValueFromNamespacedRow(\n namespacedRow,\n columnRef.substring(1),\n mainTableAlias\n )\n } else {\n value = evaluateOperandOnNamespacedRow(\n namespacedRow,\n columnRef as ConditionOperand,\n mainTableAlias\n )\n }\n // Ensure we return a number for aggregate functions\n return typeof value === `number` ? value : 0\n }\n\n // Return the appropriate aggregate function\n switch (functionName.toUpperCase()) {\n case `SUM`:\n return sum(valueExtractor)\n case `COUNT`:\n return count() // count() doesn't need a value extractor\n case `AVG`:\n return avg(valueExtractor)\n case `MIN`:\n return min(valueExtractor)\n case `MAX`:\n return max(valueExtractor)\n case `MEDIAN`:\n return median(valueExtractor)\n case `MODE`:\n return mode(valueExtractor)\n default:\n throw new Error(`Unsupported aggregate function: ${functionName}`)\n }\n}\n"],"names":["groupByOperators","extractValueFromNamespacedRow","isAggregateFunctionCall","groupBy","evaluateOperandOnNamespacedRow"],"mappings":";;;;;AASA,MAAM,EAAE,KAAK,OAAO,KAAK,KAAK,KAAK,QAAQ,SAASA,KAAA;AAKpC,SAAA,eACd,UACA,OACA,gBACA;AAEM,QAAA,iBAAiB,MAAM,QAAQ,MAAM,OAAO,IAC9C,MAAM,UACN,CAAC,MAAM,OAAO;AAGlB,QAAM,eAAe,CAAC,CAAC,SAAS,aAAa,MAGvC;AACJ,UAAM,MAA+B,CAAC;AAGtC,eAAW,UAAU,gBAAgB;AACnC,UAAI,OAAO,WAAW,YAAa,OAAkB,WAAW,GAAG,GAAG;AAC9D,cAAA,YAAa,OAAkB,UAAU,CAAC;AAC1C,cAAA,aAAa,UAAU,SAAS,GAAG,IACrC,UAAU,MAAM,GAAG,EAAE,CAAC,IACtB;AAEJ,YAAI,UAAW,IAAIC,WAAA;AAAA,UACjB;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MAAA;AAAA,IACF;AAGK,WAAA;AAAA,EACT;AAGA,QAAM,aAAkC,CAAC;AAErC,MAAA,CAAC,MAAM,QAAQ;AACX,UAAA,IAAI,MAAM,wCAAwC;AAAA,EAAA;AAI/C,aAAA,QAAQ,MAAM,QAAQ;AAC3B,QAAA,OAAO,SAAS,UAAU;AAC5B,iBAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,IAAI,GAAG;AAChD,YAAI,OAAO,SAAS,YAAYC,MAAA,wBAAwB,IAAI,GAAG;AAE7D,gBAAM,eAAe,OAAO,KAAK,IAAI,EAAE,CAAC;AAElC,gBAAA,YAAa,KACjB,YACF;AAGA,qBAAW,KAAK,IAAI;AAAA,YAClB;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIF,MAAI,OAAO,KAAK,UAAU,EAAE,SAAS,GAAG;AACtC,eAAW,SAAS,KAAKC,KAAQ,QAAA,cAAc,UAAU,CAAC;AAAA,EAAA;AAGrD,SAAA;AACT;AAKgB,SAAA,qBACd,cACA,WACA,gBACA;AAEA,QAAM,iBAAiB,CAAC,CAAC,SAAS,aAAa,MAGzC;AACA,QAAA;AACJ,QAAI,OAAO,cAAc,YAAY,UAAU,WAAW,GAAG,GAAG;AACtD,cAAAF,WAAA;AAAA,QACN;AAAA,QACA,UAAU,UAAU,CAAC;AAAA,QACrB;AAAA,MACF;AAAA,IAAA,OACK;AACG,cAAAG,WAAA;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IAAA;AAGK,WAAA,OAAO,UAAU,WAAW,QAAQ;AAAA,EAC7C;AAGQ,UAAA,aAAa,YAAe,GAAA;AAAA,IAClC,KAAK;AACH,aAAO,IAAI,cAAc;AAAA,IAC3B,KAAK;AACH,aAAO,MAAM;AAAA;AAAA,IACf,KAAK;AACH,aAAO,IAAI,cAAc;AAAA,IAC3B,KAAK;AACH,aAAO,IAAI,cAAc;AAAA,IAC3B,KAAK;AACH,aAAO,IAAI,cAAc;AAAA,IAC3B,KAAK;AACH,aAAO,OAAO,cAAc;AAAA,IAC9B,KAAK;AACH,aAAO,KAAK,cAAc;AAAA,IAC5B;AACE,YAAM,IAAI,MAAM,mCAAmC,YAAY,EAAE;AAAA,EAAA;AAEvE;;;"}
@@ -1,20 +1,20 @@
1
1
  import { ConditionOperand, Query } from './schema.cjs';
2
- import { IStreamBuilder } from '@electric-sql/d2ts';
2
+ import { NamespacedAndKeyedStream } from '../types.js';
3
3
  /**
4
4
  * Process the groupBy clause in a D2QL query
5
5
  */
6
- export declare function processGroupBy(pipeline: IStreamBuilder<Record<string, unknown>>, query: Query, mainTableAlias: string): IStreamBuilder<Record<string, unknown>>;
6
+ export declare function processGroupBy(pipeline: NamespacedAndKeyedStream, query: Query, mainTableAlias: string): NamespacedAndKeyedStream;
7
7
  /**
8
8
  * Helper function to get an aggregate function based on the function name
9
9
  */
10
10
  export declare function getAggregateFunction(functionName: string, columnRef: string | ConditionOperand, mainTableAlias: string): {
11
- preMap: (data: Record<string, unknown>) => number;
11
+ preMap: (data: [string, Record<string, unknown>]) => number;
12
12
  reduce: (values: [number, number][]) => number;
13
13
  postMap?: ((result: number) => number) | undefined;
14
14
  } | {
15
- pipe: (stream: IStreamBuilder<Record<string, unknown>>) => IStreamBuilder<import('@electric-sql/d2ts').KeyValue<string, number>>;
15
+ pipe: (stream: import('@electric-sql/d2ts').IStreamBuilder<[string, Record<string, unknown>]>) => import('@electric-sql/d2ts').IStreamBuilder<import('@electric-sql/d2ts').KeyValue<string, number>>;
16
16
  } | {
17
- preMap: (data: Record<string, unknown>) => {
17
+ preMap: (data: [string, Record<string, unknown>]) => {
18
18
  sum: number;
19
19
  count: number;
20
20
  };
@@ -30,11 +30,11 @@ export declare function getAggregateFunction(functionName: string, columnRef: st
30
30
  count: number;
31
31
  }) => number) | undefined;
32
32
  } | {
33
- preMap: (data: Record<string, unknown>) => number[];
33
+ preMap: (data: [string, Record<string, unknown>]) => number[];
34
34
  reduce: (values: [number[], number][]) => number[];
35
35
  postMap?: ((result: number[]) => number) | undefined;
36
36
  } | {
37
- preMap: (data: Record<string, unknown>) => Map<number, number>;
37
+ preMap: (data: [string, Record<string, unknown>]) => Map<number, number>;
38
38
  reduce: (values: [Map<number, number>, number][]) => Map<number, number>;
39
39
  postMap?: ((result: Map<number, number>) => number) | undefined;
40
40
  };
@@ -9,15 +9,12 @@ function processJoinClause(pipeline, query, tables, mainTableAlias, allInputs) {
9
9
  for (const joinClause of query.join) {
10
10
  const joinedTableAlias = joinClause.as || joinClause.from;
11
11
  const joinType = joinClause.type === `cross` ? `inner` : joinClause.type;
12
+ const [mainKeyRef, , joinedKeyRefs] = joinClause.on;
12
13
  const mainPipeline = pipeline.pipe(
13
- d2ts.map((nestedRow) => {
14
- const mainRow = nestedRow[mainTableAlias];
15
- const keyValue = extractors.extractJoinKey(
16
- mainRow,
17
- joinClause.on[0],
18
- mainTableAlias
19
- );
20
- return [keyValue, nestedRow];
14
+ d2ts.map(([currentKey, namespacedRow]) => {
15
+ const mainRow = namespacedRow[mainTableAlias];
16
+ const key = extractors.extractJoinKey(mainRow, mainKeyRef, mainTableAlias);
17
+ return [key, [currentKey, namespacedRow]];
21
18
  })
22
19
  );
23
20
  let joinedTableInput;
@@ -28,10 +25,10 @@ function processJoinClause(pipeline, query, tables, mainTableAlias, allInputs) {
28
25
  }
29
26
  tables[joinedTableAlias] = joinedTableInput;
30
27
  const joinedPipeline = joinedTableInput.pipe(
31
- d2ts.map((row) => {
32
- const nestedRow = { [joinedTableAlias]: row };
33
- const keyValue = extractors.extractJoinKey(row, joinClause.on[2], joinedTableAlias);
34
- return [keyValue, nestedRow];
28
+ d2ts.map(([currentKey, row]) => {
29
+ const namespacedRow = { [joinedTableAlias]: row };
30
+ const key = extractors.extractJoinKey(row, joinedKeyRefs, joinedTableAlias);
31
+ return [key, [currentKey, namespacedRow]];
35
32
  })
36
33
  );
37
34
  switch (joinType) {
@@ -78,74 +75,63 @@ function processJoinResults(mainTableAlias, joinedTableAlias, joinClause) {
78
75
  return pipeline.pipe(
79
76
  // Process the join result and handle nulls in the same step
80
77
  d2ts.map((result) => {
81
- const [_key, [mainNestedRow, joinedNestedRow]] = result;
78
+ const [_key, [main, joined]] = result;
79
+ const mainKey = main == null ? void 0 : main[0];
80
+ const mainNamespacedRow = main == null ? void 0 : main[1];
81
+ const joinedKey = joined == null ? void 0 : joined[0];
82
+ const joinedNamespacedRow = joined == null ? void 0 : joined[1];
82
83
  if (joinClause.type === `inner` || joinClause.type === `cross`) {
83
- if (!mainNestedRow || !joinedNestedRow) {
84
+ if (!mainNamespacedRow || !joinedNamespacedRow) {
84
85
  return void 0;
85
86
  }
86
87
  }
87
- if (joinClause.type === `left` && !mainNestedRow) {
88
+ if (joinClause.type === `left` && !mainNamespacedRow) {
88
89
  return void 0;
89
90
  }
90
- if (joinClause.type === `right` && !joinedNestedRow) {
91
+ if (joinClause.type === `right` && !joinedNamespacedRow) {
91
92
  return void 0;
92
93
  }
93
- const mergedNestedRow = {};
94
- if (mainNestedRow) {
95
- Object.entries(mainNestedRow).forEach(([tableAlias, tableData]) => {
96
- mergedNestedRow[tableAlias] = tableData;
97
- });
98
- }
99
- if (joinedNestedRow) {
100
- Object.entries(joinedNestedRow).forEach(([tableAlias, tableData]) => {
101
- mergedNestedRow[tableAlias] = tableData;
102
- });
103
- } else if (joinClause.type === `left` || joinClause.type === `full`) {
104
- mergedNestedRow[joinedTableAlias] = null;
105
- }
106
- if (!mainNestedRow && (joinClause.type === `right` || joinClause.type === `full`)) {
107
- mergedNestedRow[mainTableAlias] = null;
94
+ const mergedNamespacedRow = {};
95
+ if (mainNamespacedRow) {
96
+ Object.entries(mainNamespacedRow).forEach(
97
+ ([tableAlias, tableData]) => {
98
+ mergedNamespacedRow[tableAlias] = tableData;
99
+ }
100
+ );
108
101
  }
109
- return mergedNestedRow;
102
+ if (joinedNamespacedRow) {
103
+ Object.entries(joinedNamespacedRow).forEach(
104
+ ([tableAlias, tableData]) => {
105
+ mergedNamespacedRow[tableAlias] = tableData;
106
+ }
107
+ );
108
+ } else if (joinClause.type === `left` || joinClause.type === `full`) ;
109
+ if (!mainNamespacedRow && (joinClause.type === `right` || joinClause.type === `full`)) ;
110
+ const newKey = `[${mainKey},${joinedKey}]`;
111
+ return [newKey, mergedNamespacedRow];
110
112
  }),
111
113
  // Filter out undefined results
112
- d2ts.filter(
113
- (value) => value !== void 0
114
- ),
114
+ d2ts.filter((value) => value !== void 0),
115
115
  // Process the ON condition
116
- d2ts.filter((nestedRow) => {
116
+ d2ts.filter(([_key, namespacedRow]) => {
117
117
  if (!joinClause.on || joinClause.type === `cross`) {
118
118
  return true;
119
119
  }
120
- if (joinClause.type === `left` && nestedRow[joinedTableAlias] === null) {
120
+ if (joinClause.type === `left` && namespacedRow[joinedTableAlias] === void 0) {
121
121
  return true;
122
122
  }
123
- if (joinClause.type === `right` && nestedRow[mainTableAlias] === null) {
123
+ if (joinClause.type === `right` && namespacedRow[mainTableAlias] === void 0) {
124
124
  return true;
125
125
  }
126
- if (joinClause.type === `full` && (nestedRow[mainTableAlias] === null || nestedRow[joinedTableAlias] === null)) {
126
+ if (joinClause.type === `full` && (namespacedRow[mainTableAlias] === void 0 || namespacedRow[joinedTableAlias] === void 0)) {
127
127
  return true;
128
128
  }
129
- const result = evaluators.evaluateConditionOnNestedRow(
130
- nestedRow,
129
+ return evaluators.evaluateConditionOnNamespacedRow(
130
+ namespacedRow,
131
131
  joinClause.on,
132
132
  mainTableAlias,
133
133
  joinedTableAlias
134
134
  );
135
- return result;
136
- }),
137
- // Process the WHERE clause for the join if it exists
138
- d2ts.filter((nestedRow) => {
139
- if (!joinClause.where) {
140
- return true;
141
- }
142
- const result = evaluators.evaluateConditionOnNestedRow(
143
- nestedRow,
144
- joinClause.where,
145
- mainTableAlias,
146
- joinedTableAlias
147
- );
148
- return result;
149
135
  })
150
136
  );
151
137
  };
@@ -1 +1 @@
1
- {"version":3,"file":"joins.cjs","sources":["../../../src/query/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n} from \"@electric-sql/d2ts\"\nimport { evaluateConditionOnNestedRow } from \"./evaluators.js\"\nimport { extractJoinKey } from \"./extractors.js\"\nimport type { Query } from \"./index.js\"\nimport type { IStreamBuilder, JoinType } from \"@electric-sql/d2ts\"\n\n/**\n * Creates a processing pipeline for join clauses\n */\nexport function processJoinClause(\n pipeline: IStreamBuilder<Record<string, unknown>>,\n query: Query,\n tables: Record<string, IStreamBuilder<Record<string, unknown>>>,\n mainTableAlias: string,\n allInputs: Record<string, IStreamBuilder<Record<string, unknown>>>\n) {\n if (!query.join) return pipeline\n const input = allInputs[query.from]\n\n for (const joinClause of query.join) {\n // Create a stream for the joined table\n const joinedTableAlias = joinClause.as || joinClause.from\n\n // Get the right join type for the operator\n const joinType: JoinType =\n joinClause.type === `cross` ? `inner` : joinClause.type\n\n // We need to prepare the main pipeline and the joined pipeline\n // to have the correct key format for joining\n const mainPipeline = pipeline.pipe(\n map((nestedRow: Record<string, unknown>) => {\n // Extract the key from the ON condition left side for the main table\n const mainRow = nestedRow[mainTableAlias] as Record<string, unknown>\n\n // Extract the join key from the main row\n const keyValue = extractJoinKey(\n mainRow,\n joinClause.on[0],\n mainTableAlias\n )\n\n // Return [key, nestedRow] as a KeyValue type\n return [keyValue, nestedRow] as [unknown, Record<string, unknown>]\n })\n )\n\n // Get the joined table input from the inputs map\n let joinedTableInput: IStreamBuilder<Record<string, unknown>>\n\n if (allInputs[joinClause.from]) {\n // Use the provided input if available\n joinedTableInput = allInputs[joinClause.from]!\n } else {\n // Create a new input if not provided\n joinedTableInput = input!.graph.newInput<Record<string, unknown>>()\n }\n\n tables[joinedTableAlias] = joinedTableInput\n\n // Create a pipeline for the joined table\n const joinedPipeline = joinedTableInput.pipe(\n map((row: Record<string, unknown>) => {\n // Wrap the row in an object with the table alias as the key\n const nestedRow = { [joinedTableAlias]: row }\n\n // Extract the key from the ON condition right side for the joined table\n const keyValue = extractJoinKey(row, joinClause.on[2], joinedTableAlias)\n\n // Return [key, nestedRow] as a KeyValue type\n return [keyValue, nestedRow] as [unknown, Record<string, unknown>]\n })\n )\n\n // Apply join with appropriate typings based on join type\n switch (joinType) {\n case `inner`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `inner`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `left`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `left`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `right`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `right`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `full`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `full`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n default:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `inner`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n }\n }\n return pipeline\n}\n\n/**\n * Creates a processing pipeline for join results\n */\nexport function processJoinResults(\n mainTableAlias: string,\n joinedTableAlias: string,\n joinClause: { on: any; where?: any; type: string }\n) {\n return function (\n pipeline: IStreamBuilder<unknown>\n ): IStreamBuilder<Record<string, unknown>> {\n return pipeline.pipe(\n // Process the join result and handle nulls in the same step\n map((result: unknown) => {\n const [_key, [mainNestedRow, joinedNestedRow]] = result as [\n unknown,\n [\n Record<string, unknown> | undefined,\n Record<string, unknown> | undefined,\n ],\n ]\n\n // For inner joins, both sides should be non-null\n if (joinClause.type === `inner` || joinClause.type === `cross`) {\n if (!mainNestedRow || !joinedNestedRow) {\n return undefined // Will be filtered out\n }\n }\n\n // For left joins, the main row must be non-null\n if (joinClause.type === `left` && !mainNestedRow) {\n return undefined // Will be filtered out\n }\n\n // For right joins, the joined row must be non-null\n if (joinClause.type === `right` && !joinedNestedRow) {\n return undefined // Will be filtered out\n }\n\n // Merge the nested rows\n const mergedNestedRow: Record<string, unknown> = {}\n\n // Add main row data if it exists\n if (mainNestedRow) {\n Object.entries(mainNestedRow).forEach(([tableAlias, tableData]) => {\n mergedNestedRow[tableAlias] = tableData\n })\n }\n\n // If we have a joined row, add it to the merged result\n if (joinedNestedRow) {\n Object.entries(joinedNestedRow).forEach(([tableAlias, tableData]) => {\n mergedNestedRow[tableAlias] = tableData\n })\n } else if (joinClause.type === `left` || joinClause.type === `full`) {\n // For left or full joins, add the joined table with null data if missing\n mergedNestedRow[joinedTableAlias] = null\n }\n\n // For right or full joins, add the main table with null data if missing\n if (\n !mainNestedRow &&\n (joinClause.type === `right` || joinClause.type === `full`)\n ) {\n mergedNestedRow[mainTableAlias] = null\n }\n\n return mergedNestedRow\n }),\n // Filter out undefined results\n filter(\n (value: unknown): value is Record<string, unknown> =>\n value !== undefined\n ),\n // Process the ON condition\n filter((nestedRow: Record<string, unknown>) => {\n // If there's no ON condition, or it's a cross join, always return true\n if (!joinClause.on || joinClause.type === `cross`) {\n return true\n }\n\n // For LEFT JOIN, if the right side is null, we should include the row\n if (\n joinClause.type === `left` &&\n nestedRow[joinedTableAlias] === null\n ) {\n return true\n }\n\n // For RIGHT JOIN, if the left side is null, we should include the row\n if (joinClause.type === `right` && nestedRow[mainTableAlias] === null) {\n return true\n }\n\n // For FULL JOIN, if either side is null, we should include the row\n if (\n joinClause.type === `full` &&\n (nestedRow[mainTableAlias] === null ||\n nestedRow[joinedTableAlias] === null)\n ) {\n return true\n }\n\n const result = evaluateConditionOnNestedRow(\n nestedRow,\n joinClause.on,\n mainTableAlias,\n joinedTableAlias\n )\n return result\n }),\n // Process the WHERE clause for the join if it exists\n filter((nestedRow: Record<string, unknown>) => {\n if (!joinClause.where) {\n return true\n }\n\n const result = evaluateConditionOnNestedRow(\n nestedRow,\n joinClause.where,\n mainTableAlias,\n joinedTableAlias\n )\n return result\n })\n )\n }\n}\n"],"names":["map","extractJoinKey","joinOperator","consolidate","filter","evaluateConditionOnNestedRow"],"mappings":"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"}
1
+ {"version":3,"file":"joins.cjs","sources":["../../../src/query/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n} from \"@electric-sql/d2ts\"\nimport { evaluateConditionOnNamespacedRow } from \"./evaluators.js\"\nimport { extractJoinKey } from \"./extractors.js\"\nimport type { Query } from \"./index.js\"\nimport type { IStreamBuilder, JoinType } from \"@electric-sql/d2ts\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../types.js\"\n\n/**\n * Creates a processing pipeline for join clauses\n */\nexport function processJoinClause(\n pipeline: NamespacedAndKeyedStream,\n query: Query,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>\n) {\n if (!query.join) return pipeline\n const input = allInputs[query.from]\n\n for (const joinClause of query.join) {\n // Create a stream for the joined table\n const joinedTableAlias = joinClause.as || joinClause.from\n\n // Get the right join type for the operator\n const joinType: JoinType =\n joinClause.type === `cross` ? `inner` : joinClause.type\n\n // The `in` is formatted as ['@mainKeyRef', '=', '@joinedKeyRef']\n // Destructure the main key reference and the joined key references\n const [mainKeyRef, , joinedKeyRefs] = joinClause.on\n\n // We need to prepare the main pipeline and the joined pipeline\n // to have the correct key format for joining\n const mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the key from the ON condition left side for the main table\n const mainRow = namespacedRow[mainTableAlias]!\n\n // Extract the join key from the main row\n const key = extractJoinKey(mainRow, mainKeyRef, mainTableAlias)\n\n // Return [key, namespacedRow] as a KeyValue type\n return [key, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Get the joined table input from the inputs map\n let joinedTableInput: KeyedStream\n\n if (allInputs[joinClause.from]) {\n // Use the provided input if available\n joinedTableInput = allInputs[joinClause.from]!\n } else {\n // Create a new input if not provided\n joinedTableInput =\n input!.graph.newInput<[string, Record<string, unknown>]>()\n }\n\n tables[joinedTableAlias] = joinedTableInput\n\n // Create a pipeline for the joined table\n const joinedPipeline = joinedTableInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in an object with the table alias as the key\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the key from the ON condition right side for the joined table\n const key = extractJoinKey(row, joinedKeyRefs, joinedTableAlias)\n\n // Return [key, namespacedRow] as a KeyValue type\n return [key, [currentKey, namespacedRow]] as [\n string,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply join with appropriate typings based on join type\n switch (joinType) {\n case `inner`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `inner`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `left`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `left`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `right`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `right`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `full`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `full`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n default:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `inner`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n }\n }\n return pipeline\n}\n\n/**\n * Creates a processing pipeline for join results\n */\nexport function processJoinResults(\n mainTableAlias: string,\n joinedTableAlias: string,\n joinClause: { on: any; type: string }\n) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls in the same step\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // For inner joins, both sides should be non-null\n if (joinClause.type === `inner` || joinClause.type === `cross`) {\n if (!mainNamespacedRow || !joinedNamespacedRow) {\n return undefined // Will be filtered out\n }\n }\n\n // For left joins, the main row must be non-null\n if (joinClause.type === `left` && !mainNamespacedRow) {\n return undefined // Will be filtered out\n }\n\n // For right joins, the joined row must be non-null\n if (joinClause.type === `right` && !joinedNamespacedRow) {\n return undefined // Will be filtered out\n }\n\n // Merge the nested rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.entries(mainNamespacedRow).forEach(\n ([tableAlias, tableData]) => {\n mergedNamespacedRow[tableAlias] = tableData\n }\n )\n }\n\n // If we have a joined row, add it to the merged result\n if (joinedNamespacedRow) {\n Object.entries(joinedNamespacedRow).forEach(\n ([tableAlias, tableData]) => {\n mergedNamespacedRow[tableAlias] = tableData\n }\n )\n } else if (joinClause.type === `left` || joinClause.type === `full`) {\n // For left or full joins, add the joined table with undefined data if missing\n // mergedNamespacedRow[joinedTableAlias] = undefined\n }\n\n // For right or full joins, add the main table with undefined data if missing\n if (\n !mainNamespacedRow &&\n (joinClause.type === `right` || joinClause.type === `full`)\n ) {\n // mergedNamespacedRow[mainTableAlias] = undefined\n }\n\n // New key\n const newKey = `[${mainKey},${joinedKey}]`\n\n return [newKey, mergedNamespacedRow] as [\n string,\n typeof mergedNamespacedRow,\n ]\n }),\n // Filter out undefined results\n filter((value) => value !== undefined),\n // Process the ON condition\n filter(([_key, namespacedRow]: [string, NamespacedRow]) => {\n // If there's no ON condition, or it's a cross join, always return true\n if (!joinClause.on || joinClause.type === `cross`) {\n return true\n }\n\n // For LEFT JOIN, if the right side is null, we should include the row\n if (\n joinClause.type === `left` &&\n namespacedRow[joinedTableAlias] === undefined\n ) {\n return true\n }\n\n // For RIGHT JOIN, if the left side is null, we should include the row\n if (\n joinClause.type === `right` &&\n namespacedRow[mainTableAlias] === undefined\n ) {\n return true\n }\n\n // For FULL JOIN, if either side is null, we should include the row\n if (\n joinClause.type === `full` &&\n (namespacedRow[mainTableAlias] === undefined ||\n namespacedRow[joinedTableAlias] === undefined)\n ) {\n return true\n }\n\n return evaluateConditionOnNamespacedRow(\n namespacedRow,\n joinClause.on,\n mainTableAlias,\n joinedTableAlias\n )\n })\n )\n }\n}\n"],"names":["map","extractJoinKey","joinOperator","consolidate","filter","evaluateConditionOnNamespacedRow"],"mappings":"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"}
@@ -1,14 +1,14 @@
1
1
  import { Query } from './index.js';
2
2
  import { IStreamBuilder } from '@electric-sql/d2ts';
3
+ import { KeyedStream, NamespacedAndKeyedStream, NamespacedRow } from '../types.js';
3
4
  /**
4
5
  * Creates a processing pipeline for join clauses
5
6
  */
6
- export declare function processJoinClause(pipeline: IStreamBuilder<Record<string, unknown>>, query: Query, tables: Record<string, IStreamBuilder<Record<string, unknown>>>, mainTableAlias: string, allInputs: Record<string, IStreamBuilder<Record<string, unknown>>>): IStreamBuilder<Record<string, unknown>>;
7
+ export declare function processJoinClause(pipeline: NamespacedAndKeyedStream, query: Query, tables: Record<string, KeyedStream>, mainTableAlias: string, allInputs: Record<string, KeyedStream>): NamespacedAndKeyedStream;
7
8
  /**
8
9
  * Creates a processing pipeline for join results
9
10
  */
10
11
  export declare function processJoinResults(mainTableAlias: string, joinedTableAlias: string, joinClause: {
11
12
  on: any;
12
- where?: any;
13
13
  type: string;
14
- }): (pipeline: IStreamBuilder<unknown>) => IStreamBuilder<Record<string, unknown>>;
14
+ }): (pipeline: IStreamBuilder<[key: string, [[string, NamespacedRow] | undefined, [string, NamespacedRow] | undefined]]>) => NamespacedAndKeyedStream;