@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
@@ -4,18 +4,24 @@ const d2ts = require("@electric-sql/d2ts");
4
4
  const extractors = require("./extractors.cjs");
5
5
  function processSelect(pipeline, query, mainTableAlias, inputs) {
6
6
  return pipeline.pipe(
7
- d2ts.map((nestedRow) => {
7
+ d2ts.map(([key, namespacedRow]) => {
8
8
  const result = {};
9
- const isGroupedResult = query.groupBy && Object.keys(nestedRow).some(
10
- (key) => !Object.keys(inputs).includes(key) && typeof nestedRow[key] !== `object`
9
+ const isGroupedResult = query.groupBy && Object.keys(namespacedRow).some(
10
+ (namespaceKey) => !Object.keys(inputs).includes(namespaceKey) && typeof namespacedRow[namespaceKey] !== `object`
11
11
  );
12
+ if (!query.select) {
13
+ throw new Error(`Cannot process missing SELECT clause`);
14
+ }
12
15
  for (const item of query.select) {
13
16
  if (typeof item === `string`) {
14
17
  if (item === `@*`) {
15
18
  if (isGroupedResult) {
16
- Object.assign(result, nestedRow);
19
+ Object.assign(result, namespacedRow);
17
20
  } else {
18
- Object.assign(result, extractAllColumnsFromAllTables(nestedRow));
21
+ Object.assign(
22
+ result,
23
+ extractAllColumnsFromAllTables(namespacedRow)
24
+ );
19
25
  }
20
26
  continue;
21
27
  }
@@ -26,7 +32,7 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
26
32
  } else {
27
33
  Object.assign(
28
34
  result,
29
- extractAllColumnsFromTable(nestedRow, tableAlias)
35
+ extractAllColumnsFromTable(namespacedRow, tableAlias)
30
36
  );
31
37
  }
32
38
  continue;
@@ -34,11 +40,11 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
34
40
  if (item.startsWith(`@`)) {
35
41
  const columnRef = item.substring(1);
36
42
  const alias = columnRef;
37
- if (isGroupedResult && columnRef in nestedRow) {
38
- result[alias] = nestedRow[columnRef];
43
+ if (isGroupedResult && columnRef in namespacedRow) {
44
+ result[alias] = namespacedRow[columnRef];
39
45
  } else {
40
- result[alias] = extractors.extractValueFromNestedRow(
41
- nestedRow,
46
+ result[alias] = extractors.extractValueFromNamespacedRow(
47
+ namespacedRow,
42
48
  columnRef,
43
49
  mainTableAlias,
44
50
  void 0
@@ -54,22 +60,24 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
54
60
  for (const [alias, expr] of Object.entries(item)) {
55
61
  if (typeof expr === `string` && expr.startsWith(`@`)) {
56
62
  const columnRef = expr.substring(1);
57
- if (isGroupedResult && columnRef in nestedRow) {
58
- result[alias] = nestedRow[columnRef];
63
+ if (isGroupedResult && columnRef in namespacedRow) {
64
+ result[alias] = namespacedRow[columnRef];
59
65
  } else {
60
- result[alias] = extractors.extractValueFromNestedRow(
61
- nestedRow,
66
+ result[alias] = extractors.extractValueFromNamespacedRow(
67
+ namespacedRow,
62
68
  columnRef,
63
69
  mainTableAlias,
64
70
  void 0
65
71
  );
66
72
  }
67
73
  } else if (typeof expr === `object`) {
68
- if (isGroupedResult && alias in nestedRow) {
69
- result[alias] = nestedRow[alias];
74
+ if (isGroupedResult && alias in namespacedRow) {
75
+ result[alias] = namespacedRow[alias];
76
+ } else if (expr.ORDER_INDEX) {
77
+ result[alias] = namespacedRow[mainTableAlias][alias];
70
78
  } else {
71
- result[alias] = extractors.evaluateOperandOnNestedRow(
72
- nestedRow,
79
+ result[alias] = extractors.evaluateOperandOnNamespacedRow(
80
+ namespacedRow,
73
81
  expr,
74
82
  mainTableAlias,
75
83
  void 0
@@ -79,22 +87,25 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
79
87
  }
80
88
  }
81
89
  }
82
- return result;
90
+ return [key, result];
83
91
  })
84
92
  );
85
93
  }
86
- function extractAllColumnsFromAllTables(nestedRow) {
94
+ function extractAllColumnsFromAllTables(namespacedRow) {
87
95
  const result = {};
88
- for (const [tableAlias, tableData] of Object.entries(nestedRow)) {
96
+ for (const [tableAlias, tableData] of Object.entries(namespacedRow)) {
89
97
  if (tableData && typeof tableData === `object`) {
90
- Object.assign(result, extractAllColumnsFromTable(nestedRow, tableAlias));
98
+ Object.assign(
99
+ result,
100
+ extractAllColumnsFromTable(namespacedRow, tableAlias)
101
+ );
91
102
  }
92
103
  }
93
104
  return result;
94
105
  }
95
- function extractAllColumnsFromTable(nestedRow, tableAlias) {
106
+ function extractAllColumnsFromTable(namespacedRow, tableAlias) {
96
107
  const result = {};
97
- const tableData = nestedRow[tableAlias];
108
+ const tableData = namespacedRow[tableAlias];
98
109
  if (!tableData || typeof tableData !== `object`) {
99
110
  return result;
100
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"select.cjs","sources":["../../../src/query/select.ts"],"sourcesContent":["import { map } from \"@electric-sql/d2ts\"\nimport {\n evaluateOperandOnNestedRow,\n extractValueFromNestedRow,\n} from \"./extractors\"\nimport type { IStreamBuilder } from \"@electric-sql/d2ts\"\nimport type { ConditionOperand, Query } from \"./schema\"\n\nexport function processSelect(\n pipeline: IStreamBuilder<Record<string, unknown>>,\n query: Query,\n mainTableAlias: string,\n inputs: Record<string, IStreamBuilder<Record<string, unknown>>>\n) {\n return pipeline.pipe(\n map((nestedRow: Record<string, unknown>) => {\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(nestedRow).some(\n (key) =>\n !Object.keys(inputs).includes(key) &&\n typeof nestedRow[key] !== `object`\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, nestedRow)\n } else {\n // Extract all columns from all tables\n Object.assign(result, extractAllColumnsFromAllTables(nestedRow))\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(nestedRow, 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 nestedRow) {\n result[alias] = nestedRow[columnRef]\n } else {\n // Extract the value from the nested structure\n result[alias] = extractValueFromNestedRow(\n nestedRow,\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 nestedRow) {\n result[alias] = nestedRow[columnRef]\n } else {\n // Extract the value from the nested structure\n result[alias] = extractValueFromNestedRow(\n nestedRow,\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 nestedRow) {\n result[alias] = nestedRow[alias]\n } else {\n // This might be a function call\n result[alias] = evaluateOperandOnNestedRow(\n nestedRow,\n expr as ConditionOperand,\n mainTableAlias,\n undefined\n )\n }\n }\n }\n }\n }\n\n return result\n })\n )\n}\n\n// Helper function to extract all columns from all tables in a nested row\nfunction extractAllColumnsFromAllTables(\n nestedRow: 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(nestedRow)) {\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(result, extractAllColumnsFromTable(nestedRow, tableAlias))\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 nestedRow: 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 = nestedRow[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","extractValueFromNestedRow","evaluateOperandOnNestedRow"],"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 } 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":";;;;AAQO,SAAS,cACd,UACA,OACA,gBACA,QACa;AACb,SAAO,SAAS;AAAA,IACdA,KAAAA,IAAI,CAAC,CAAC,KAAK,aAAa,MAAM;AAC5B,YAAM,SAAkC,CAAC;AAIzC,YAAM,kBACJ,MAAM,WACN,OAAO,KAAK,aAAa,EAAE;AAAA,QACzB,CAAC,iBACC,CAAC,OAAO,KAAK,MAAM,EAAE,SAAS,YAAY,KAC1C,OAAO,cAAc,YAAY,MAAM;AAAA,MAC3C;AAEE,UAAA,CAAC,MAAM,QAAQ;AACX,cAAA,IAAI,MAAM,sCAAsC;AAAA,MAAA;AAG7C,iBAAA,QAAQ,MAAM,QAAQ;AAC3B,YAAA,OAAO,SAAS,UAAU;AAE5B,cAAK,SAAoB,MAAM;AAE7B,gBAAI,iBAAiB;AACZ,qBAAA,OAAO,QAAQ,aAAa;AAAA,YAAA,OAC9B;AAEE,qBAAA;AAAA,gBACL;AAAA,gBACA,+BAA+B,aAAa;AAAA,cAC9C;AAAA,YAAA;AAEF;AAAA,UAAA;AAIF,cACG,KAAgB,WAAW,GAAG,KAC9B,KAAgB,SAAS,IAAI,GAC9B;AACA,kBAAM,aAAc,KAAgB,MAAM,GAAG,EAAE;AAG/C,gBAAI,iBAAiB;AAGnB;AAAA,YAAA,OACK;AAEE,qBAAA;AAAA,gBACL;AAAA,gBACA,2BAA2B,eAAe,UAAU;AAAA,cACtD;AAAA,YAAA;AAEF;AAAA,UAAA;AAIG,cAAA,KAAgB,WAAW,GAAG,GAAG;AAC9B,kBAAA,YAAa,KAAgB,UAAU,CAAC;AAC9C,kBAAM,QAAQ;AAGV,gBAAA,mBAAmB,aAAa,eAAe;AAC1C,qBAAA,KAAK,IAAI,cAAc,SAAS;AAAA,YAAA,OAClC;AAEL,qBAAO,KAAK,IAAIC,WAAA;AAAA,gBACd;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,cACF;AAAA,YAAA;AAKE,gBAAA,MAAM,SAAS,GAAG,GAAG;AACvB,oBAAM,aAAa,MAAM,MAAM,GAAG,EAAE,CAAC;AAC9B,qBAAA,UAAW,IAAI,OAAO,KAAK;AAClC,qBAAO,OAAO,KAAK;AAAA,YAAA;AAAA,UACrB;AAAA,QACF,OACK;AAEL,qBAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,IAAI,GAAG;AAChD,gBAAI,OAAO,SAAS,YAAa,KAAgB,WAAW,GAAG,GAAG;AAC1D,oBAAA,YAAa,KAAgB,UAAU,CAAC;AAG1C,kBAAA,mBAAmB,aAAa,eAAe;AAC1C,uBAAA,KAAK,IAAI,cAAc,SAAS;AAAA,cAAA,OAClC;AAEL,uBAAO,KAAK,IAAIA,WAAA;AAAA,kBACd;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,gBACF;AAAA,cAAA;AAAA,YAEJ,WAAW,OAAO,SAAS,UAAU;AAE/B,kBAAA,mBAAmB,SAAS,eAAe;AACtC,uBAAA,KAAK,IAAI,cAAc,KAAK;AAAA,cAAA,WACzB,KAAkC,aAAa;AACzD,uBAAO,KAAK,IAAI,cAAc,cAAc,EAAG,KAAK;AAAA,cAAA,OAC/C;AAEL,uBAAO,KAAK,IAAIC,WAAA;AAAA,kBACd;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,gBACF;AAAA,cAAA;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGK,aAAA,CAAC,KAAK,MAAM;AAAA,IACpB,CAAA;AAAA,EACH;AACF;AAGA,SAAS,+BACP,eACyB;AACzB,QAAM,SAAkC,CAAC;AAGzC,aAAW,CAAC,YAAY,SAAS,KAAK,OAAO,QAAQ,aAAa,GAAG;AAC/D,QAAA,aAAa,OAAO,cAAc,UAAU;AAGvC,aAAA;AAAA,QACL;AAAA,QACA,2BAA2B,eAAe,UAAU;AAAA,MACtD;AAAA,IAAA;AAAA,EACF;AAGK,SAAA;AACT;AAGA,SAAS,2BACP,eACA,YACyB;AACzB,QAAM,SAAkC,CAAC;AAGnC,QAAA,YAAY,cAAc,UAAU;AAK1C,MAAI,CAAC,aAAa,OAAO,cAAc,UAAU;AACxC,WAAA;AAAA,EAAA;AAIT,aAAW,CAAC,YAAY,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC3D,WAAO,UAAU,IAAI;AAAA,EAAA;AAGhB,SAAA;AACT;;"}
@@ -1,3 +1,3 @@
1
- import { IStreamBuilder } from '@electric-sql/d2ts';
2
1
  import { Query } from './schema.cjs';
3
- export declare function processSelect(pipeline: IStreamBuilder<Record<string, unknown>>, query: Query, mainTableAlias: string, inputs: Record<string, IStreamBuilder<Record<string, unknown>>>): IStreamBuilder<Record<string, unknown>>;
2
+ import { KeyedStream, NamespacedAndKeyedStream } from '../types.cjs';
3
+ export declare function processSelect(pipeline: NamespacedAndKeyedStream, query: Query, mainTableAlias: string, inputs: Record<string, KeyedStream>): KeyedStream;
@@ -6,6 +6,7 @@ export type Context<TBaseSchema extends Schema = Schema, TSchema extends Schema
6
6
  schema: TSchema;
7
7
  default?: keyof TSchema;
8
8
  result?: Record<string, unknown>;
9
+ hasJoin?: boolean;
9
10
  };
10
11
  export type Flatten<T> = {
11
12
  [K in keyof T]: T[K];
@@ -12,6 +12,7 @@ function generateUUID() {
12
12
  });
13
13
  }
14
14
  const transactions = [];
15
+ let transactionStack = [];
15
16
  function createTransaction(config) {
16
17
  if (typeof config.mutationFn === `undefined`) {
17
18
  throw `mutationFn is required when creating a transaction`;
@@ -24,7 +25,6 @@ function createTransaction(config) {
24
25
  transactions.push(newTransaction);
25
26
  return newTransaction;
26
27
  }
27
- let transactionStack = [];
28
28
  function getActiveTransaction() {
29
29
  if (transactionStack.length > 0) {
30
30
  return transactionStack.slice(-1)[0];
@@ -38,6 +38,12 @@ function registerTransaction(tx) {
38
38
  function unregisterTransaction(tx) {
39
39
  transactionStack = transactionStack.filter((t) => t.id !== tx.id);
40
40
  }
41
+ function removeFromPendingList(tx) {
42
+ const index = transactions.findIndex((t) => t.id === tx.id);
43
+ if (index !== -1) {
44
+ transactions.splice(index, 1);
45
+ }
46
+ }
41
47
  class Transaction {
42
48
  constructor(config) {
43
49
  this.id = config.id;
@@ -51,6 +57,9 @@ class Transaction {
51
57
  }
52
58
  setState(newState) {
53
59
  this.state = newState;
60
+ if (newState === `completed` || newState === `failed`) {
61
+ removeFromPendingList(this);
62
+ }
54
63
  }
55
64
  mutate(callback) {
56
65
  if (this.state !== `pending`) {
@@ -87,11 +96,11 @@ class Transaction {
87
96
  }
88
97
  this.setState(`failed`);
89
98
  if (!isSecondaryRollback) {
90
- const mutationKeys = /* @__PURE__ */ new Set();
91
- this.mutations.forEach((m) => mutationKeys.add(m.key));
92
- transactions.forEach(
93
- (t) => t.state === `pending` && t.mutations.some((m) => mutationKeys.has(m.key)) && t.rollback({ isSecondaryRollback: true })
94
- );
99
+ const mutationIds = /* @__PURE__ */ new Set();
100
+ this.mutations.forEach((m) => mutationIds.add(m.key));
101
+ for (const t of transactions) {
102
+ t.state === `pending` && t.mutations.some((m) => mutationIds.has(m.key)) && t.rollback({ isSecondaryRollback: true });
103
+ }
95
104
  }
96
105
  this.isPersisted.reject((_a = this.error) == null ? void 0 : _a.error);
97
106
  this.touchCollection();
@@ -100,13 +109,13 @@ class Transaction {
100
109
  // Tell collection that something has changed with the transaction
101
110
  touchCollection() {
102
111
  const hasCalled = /* @__PURE__ */ new Set();
103
- this.mutations.forEach((mutation) => {
112
+ for (const mutation of this.mutations) {
104
113
  if (!hasCalled.has(mutation.collection.id)) {
105
114
  mutation.collection.transactions.setState((state) => state);
106
115
  mutation.collection.commitPendingTransactions();
107
116
  hasCalled.add(mutation.collection.id);
108
117
  }
109
- });
118
+ }
110
119
  }
111
120
  async commit() {
112
121
  if (this.state !== `pending`) {
@@ -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> = []\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 transactions.push(newTransaction)\n\n return newTransaction\n}\n\nlet transactionStack: Array<Transaction> = []\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\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\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 keys\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationKeys = new Set()\n this.mutations.forEach((m) => mutationKeys.add(m.key))\n transactions.forEach(\n (t) =>\n t.state === `pending` &&\n t.mutations.some((m) => mutationKeys.has(m.key)) &&\n t.rollback({ isSecondaryRollback: true })\n )\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n\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 this.mutations.forEach((mutation) => {\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} 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,3 +1,4 @@
1
+ import { IStreamBuilder } from '@electric-sql/d2ts';
1
2
  import { Collection } from './collection.cjs';
2
3
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
4
  import { Transaction } from './transactions.cjs';
@@ -11,7 +12,7 @@ export interface PendingMutation<T extends object = Record<string, unknown>> {
11
12
  original: Record<string, unknown>;
12
13
  modified: Record<string, unknown>;
13
14
  changes: Record<string, unknown>;
14
- key: string;
15
+ key: any;
15
16
  type: OperationType;
16
17
  metadata: unknown;
17
18
  syncMetadata: Record<string, unknown>;
@@ -36,7 +37,7 @@ export interface TransactionConfig {
36
37
  }
37
38
  export type { Transaction };
38
39
  type Value<TExtensions = never> = string | number | boolean | bigint | null | TExtensions | Array<Value<TExtensions>> | {
39
- [key: string]: Value<TExtensions>;
40
+ [key: string | number | symbol]: Value<TExtensions>;
40
41
  };
41
42
  export type Row<TExtensions = never> = Record<string, Value<TExtensions>>;
42
43
  export type OperationType = `insert` | `update` | `delete`;
@@ -44,17 +45,17 @@ export interface SyncConfig<T extends object = Record<string, unknown>> {
44
45
  sync: (params: {
45
46
  collection: Collection<T>;
46
47
  begin: () => void;
47
- write: (message: ChangeMessage<T>) => void;
48
+ write: (message: Omit<ChangeMessage<T>, `key`>) => void;
48
49
  commit: () => void;
49
50
  }) => void;
50
51
  /**
51
52
  * Get the sync metadata for insert operations
52
- * @returns Record containing primaryKey and relation information
53
+ * @returns Record containing relation information
53
54
  */
54
55
  getSyncMetadata?: () => Record<string, unknown>;
55
56
  }
56
57
  export interface ChangeMessage<T extends object = Record<string, unknown>> {
57
- key: string;
58
+ key: any;
58
59
  value: T;
59
60
  previousValue?: T;
60
61
  type: OperationType;
@@ -83,12 +84,45 @@ export interface OperationConfig {
83
84
  metadata?: Record<string, unknown>;
84
85
  }
85
86
  export interface InsertConfig {
86
- key?: string | Array<string | undefined>;
87
87
  metadata?: Record<string, unknown>;
88
88
  }
89
89
  export interface CollectionConfig<T extends object = Record<string, unknown>> {
90
- id: string;
90
+ id?: string;
91
91
  sync: SyncConfig<T>;
92
92
  schema?: StandardSchema<T>;
93
+ /**
94
+ * Function to extract the ID from an object
95
+ * This is required for update/delete operations which now only accept IDs
96
+ * @param item The item to extract the ID from
97
+ * @returns The ID string for the item
98
+ * @example
99
+ * // For a collection with a 'uuid' field as the primary key
100
+ * getId: (item) => item.uuid
101
+ */
102
+ getId: (item: T) => any;
93
103
  }
94
104
  export type ChangesPayload<T extends object = Record<string, unknown>> = Array<ChangeMessage<T>>;
105
+ /**
106
+ * An input row from a collection
107
+ */
108
+ export type InputRow = [unknown, Record<string, unknown>];
109
+ /**
110
+ * A keyed stream is a stream of rows
111
+ * This is used as the inputs from a collection to a query
112
+ */
113
+ export type KeyedStream = IStreamBuilder<InputRow>;
114
+ /**
115
+ * A namespaced row is a row withing a pipeline that had each table wrapped in its alias
116
+ */
117
+ export type NamespacedRow = Record<string, Record<string, unknown>>;
118
+ /**
119
+ * A keyed namespaced row is a row with a key and a namespaced row
120
+ * This is the main representation of a row in a query pipeline
121
+ */
122
+ export type KeyedNamespacedRow = [unknown, NamespacedRow];
123
+ /**
124
+ * A namespaced and keyed stream is a stream of rows
125
+ * This is used throughout a query pipeline and as the output from a query without
126
+ * a `select` clause.
127
+ */
128
+ export type NamespacedAndKeyedStream = IStreamBuilder<KeyedNamespacedRow>;
@@ -2,6 +2,14 @@ import { Derived, Store } from '@tanstack/store';
2
2
  import { SortedMap } from './SortedMap.js';
3
3
  import { ChangeMessage, CollectionConfig, InsertConfig, OperationConfig, OptimisticChangeMessage, Transaction } from './types.js';
4
4
  export declare const collectionsStore: Store<Map<string, Collection<any>>, (cb: Map<string, Collection<any>>) => Map<string, Collection<any>>>;
5
+ /**
6
+ * Creates a new Collection instance with the given configuration
7
+ *
8
+ * @template T - The type of items in the collection
9
+ * @param config - Configuration for the collection, including id and sync
10
+ * @returns A new Collection instance
11
+ */
12
+ export declare function createCollection<T extends object = Record<string, unknown>>(config: CollectionConfig<T>): Collection<T>;
5
13
  /**
6
14
  * Preloads a collection with the given configuration
7
15
  * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)
@@ -55,7 +63,6 @@ export declare class Collection<T extends object = Record<string, unknown>> {
55
63
  private syncedKeys;
56
64
  config: CollectionConfig<T>;
57
65
  private hasReceivedFirstCommit;
58
- objectKeyMap: WeakMap<object, string>;
59
66
  private onFirstCommitCallbacks;
60
67
  /**
61
68
  * Register a callback to be executed on the next commit
@@ -63,22 +70,23 @@ export declare class Collection<T extends object = Record<string, unknown>> {
63
70
  * @param callback Function to call after the next commit
64
71
  */
65
72
  onFirstCommit(callback: () => void): void;
66
- id: `${string}-${string}-${string}-${string}-${string}`;
73
+ id: string;
67
74
  /**
68
75
  * Creates a new Collection instance
69
76
  *
70
77
  * @param config - Configuration object for the collection
71
78
  * @throws Error if sync config is missing
72
79
  */
73
- constructor(config?: CollectionConfig<T>);
80
+ constructor(config: CollectionConfig<T>);
74
81
  /**
75
82
  * Attempts to commit pending synced transactions if there are no active transactions
76
83
  * This method processes operations from pending transactions and applies them to the synced data
77
84
  */
78
85
  commitPendingTransactions: () => void;
79
86
  private ensureStandardSchema;
87
+ private getKeyFromId;
88
+ generateObjectKey(id: any, item: any): string;
80
89
  private validateData;
81
- private generateKey;
82
90
  /**
83
91
  * Inserts one or more items into the collection
84
92
  * @param items - Single item or array of items to insert
@@ -118,24 +126,43 @@ export declare class Collection<T extends object = Record<string, unknown>> {
118
126
  * // Update with metadata
119
127
  * update(todo, { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
120
128
  */
121
- update<TItem extends object = T>(item: TItem, configOrCallback: ((draft: TItem) => void) | OperationConfig, maybeCallback?: (draft: TItem) => void): Transaction;
122
- update<TItem extends object = T>(items: Array<TItem>, configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig, maybeCallback?: (draft: Array<TItem>) => void): Transaction;
129
+ /**
130
+ * Updates one or more items in the collection using a callback function
131
+ * @param ids - Single ID or array of IDs to update
132
+ * @param configOrCallback - Either update configuration or update callback
133
+ * @param maybeCallback - Update callback if config was provided
134
+ * @returns A Transaction object representing the update operation(s)
135
+ * @throws {SchemaValidationError} If the updated data fails schema validation
136
+ * @example
137
+ * // Update a single item
138
+ * update("todo-1", (draft) => { draft.completed = true })
139
+ *
140
+ * // Update multiple items
141
+ * update(["todo-1", "todo-2"], (drafts) => {
142
+ * drafts.forEach(draft => { draft.completed = true })
143
+ * })
144
+ *
145
+ * // Update with metadata
146
+ * update("todo-1", { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
147
+ */
148
+ update<TItem extends object = T>(id: unknown, configOrCallback: ((draft: TItem) => void) | OperationConfig, maybeCallback?: (draft: TItem) => void): Transaction;
149
+ update<TItem extends object = T>(ids: Array<unknown>, configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig, maybeCallback?: (draft: Array<TItem>) => void): Transaction;
123
150
  /**
124
151
  * Deletes one or more items from the collection
125
- * @param items - Single item/key or array of items/keys to delete
152
+ * @param ids - Single ID or array of IDs to delete
126
153
  * @param config - Optional configuration including metadata
127
154
  * @returns A Transaction object representing the delete operation(s)
128
155
  * @example
129
156
  * // Delete a single item
130
- * delete(todo)
157
+ * delete("todo-1")
131
158
  *
132
159
  * // Delete multiple items
133
- * delete([todo1, todo2])
160
+ * delete(["todo-1", "todo-2"])
134
161
  *
135
162
  * // Delete with metadata
136
- * delete(todo, { metadata: { reason: "completed" } })
163
+ * delete("todo-1", { metadata: { reason: "completed" } })
137
164
  */
138
- delete: (items: Array<T | string> | T | string, config?: OperationConfig) => Transaction;
165
+ delete: (ids: Array<string> | string, config?: OperationConfig) => Transaction;
139
166
  /**
140
167
  * Gets the current state of the collection as a Map
141
168
  *