@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.
- package/dist/cjs/collection.cjs +113 -94
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +38 -11
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/proxy.cjs +87 -248
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/proxy.d.cts +5 -5
- package/dist/cjs/query/compiled-query.cjs +23 -14
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.d.cts +3 -1
- package/dist/cjs/query/evaluators.cjs +20 -20
- package/dist/cjs/query/evaluators.cjs.map +1 -1
- package/dist/cjs/query/evaluators.d.cts +3 -2
- package/dist/cjs/query/extractors.cjs +20 -20
- package/dist/cjs/query/extractors.cjs.map +1 -1
- package/dist/cjs/query/extractors.d.cts +3 -3
- package/dist/cjs/query/group-by.cjs +12 -15
- package/dist/cjs/query/group-by.cjs.map +1 -1
- package/dist/cjs/query/group-by.d.cts +7 -7
- package/dist/cjs/query/joins.cjs +41 -55
- package/dist/cjs/query/joins.cjs.map +1 -1
- package/dist/cjs/query/joins.d.cts +3 -3
- package/dist/cjs/query/order-by.cjs +37 -84
- package/dist/cjs/query/order-by.cjs.map +1 -1
- package/dist/cjs/query/order-by.d.cts +2 -2
- package/dist/cjs/query/pipeline-compiler.cjs +13 -18
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
- package/dist/cjs/query/pipeline-compiler.d.cts +2 -1
- package/dist/cjs/query/query-builder.cjs +0 -12
- package/dist/cjs/query/query-builder.cjs.map +1 -1
- package/dist/cjs/query/query-builder.d.cts +4 -8
- package/dist/cjs/query/schema.d.cts +1 -6
- package/dist/cjs/query/select.cjs +35 -24
- package/dist/cjs/query/select.cjs.map +1 -1
- package/dist/cjs/query/select.d.cts +2 -2
- package/dist/cjs/query/types.d.cts +1 -0
- package/dist/cjs/transactions.cjs +17 -8
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +41 -7
- package/dist/esm/collection.d.ts +38 -11
- package/dist/esm/collection.js +113 -94
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/proxy.d.ts +5 -5
- package/dist/esm/proxy.js +87 -248
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/compiled-query.d.ts +3 -1
- package/dist/esm/query/compiled-query.js +23 -14
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/evaluators.d.ts +3 -2
- package/dist/esm/query/evaluators.js +21 -21
- package/dist/esm/query/evaluators.js.map +1 -1
- package/dist/esm/query/extractors.d.ts +3 -3
- package/dist/esm/query/extractors.js +20 -20
- package/dist/esm/query/extractors.js.map +1 -1
- package/dist/esm/query/group-by.d.ts +7 -7
- package/dist/esm/query/group-by.js +14 -17
- package/dist/esm/query/group-by.js.map +1 -1
- package/dist/esm/query/joins.d.ts +3 -3
- package/dist/esm/query/joins.js +42 -56
- package/dist/esm/query/joins.js.map +1 -1
- package/dist/esm/query/order-by.d.ts +2 -2
- package/dist/esm/query/order-by.js +39 -86
- package/dist/esm/query/order-by.js.map +1 -1
- package/dist/esm/query/pipeline-compiler.d.ts +2 -1
- package/dist/esm/query/pipeline-compiler.js +14 -19
- package/dist/esm/query/pipeline-compiler.js.map +1 -1
- package/dist/esm/query/query-builder.d.ts +4 -8
- package/dist/esm/query/query-builder.js +0 -12
- package/dist/esm/query/query-builder.js.map +1 -1
- package/dist/esm/query/schema.d.ts +1 -6
- package/dist/esm/query/select.d.ts +2 -2
- package/dist/esm/query/select.js +36 -25
- package/dist/esm/query/select.js.map +1 -1
- package/dist/esm/query/types.d.ts +1 -0
- package/dist/esm/transactions.js +17 -8
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +41 -7
- package/package.json +2 -2
- package/src/collection.ts +174 -121
- package/src/proxy.ts +141 -358
- package/src/query/compiled-query.ts +30 -15
- package/src/query/evaluators.ts +22 -21
- package/src/query/extractors.ts +24 -21
- package/src/query/group-by.ts +24 -22
- package/src/query/joins.ts +88 -75
- package/src/query/order-by.ts +56 -106
- package/src/query/pipeline-compiler.ts +34 -37
- package/src/query/query-builder.ts +9 -23
- package/src/query/schema.ts +1 -10
- package/src/query/select.ts +44 -32
- package/src/query/types.ts +1 -0
- package/src/transactions.ts +22 -13
- package/src/types.ts +48 -7
- package/dist/cjs/query/key-by.cjs +0 -43
- package/dist/cjs/query/key-by.cjs.map +0 -1
- package/dist/cjs/query/key-by.d.cts +0 -3
- package/dist/esm/query/key-by.d.ts +0 -3
- package/dist/esm/query/key-by.js +0 -43
- package/dist/esm/query/key-by.js.map +0 -1
- package/src/query/key-by.ts +0 -61
package/dist/esm/query/select.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { map } from "@electric-sql/d2ts";
|
|
2
|
-
import {
|
|
2
|
+
import { extractValueFromNamespacedRow, evaluateOperandOnNamespacedRow } from "./extractors.js";
|
|
3
3
|
function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
4
4
|
return pipeline.pipe(
|
|
5
|
-
map((
|
|
5
|
+
map(([key, namespacedRow]) => {
|
|
6
6
|
const result = {};
|
|
7
|
-
const isGroupedResult = query.groupBy && Object.keys(
|
|
8
|
-
(
|
|
7
|
+
const isGroupedResult = query.groupBy && Object.keys(namespacedRow).some(
|
|
8
|
+
(namespaceKey) => !Object.keys(inputs).includes(namespaceKey) && typeof namespacedRow[namespaceKey] !== `object`
|
|
9
9
|
);
|
|
10
|
+
if (!query.select) {
|
|
11
|
+
throw new Error(`Cannot process missing SELECT clause`);
|
|
12
|
+
}
|
|
10
13
|
for (const item of query.select) {
|
|
11
14
|
if (typeof item === `string`) {
|
|
12
15
|
if (item === `@*`) {
|
|
13
16
|
if (isGroupedResult) {
|
|
14
|
-
Object.assign(result,
|
|
17
|
+
Object.assign(result, namespacedRow);
|
|
15
18
|
} else {
|
|
16
|
-
Object.assign(
|
|
19
|
+
Object.assign(
|
|
20
|
+
result,
|
|
21
|
+
extractAllColumnsFromAllTables(namespacedRow)
|
|
22
|
+
);
|
|
17
23
|
}
|
|
18
24
|
continue;
|
|
19
25
|
}
|
|
@@ -24,7 +30,7 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
|
24
30
|
} else {
|
|
25
31
|
Object.assign(
|
|
26
32
|
result,
|
|
27
|
-
extractAllColumnsFromTable(
|
|
33
|
+
extractAllColumnsFromTable(namespacedRow, tableAlias)
|
|
28
34
|
);
|
|
29
35
|
}
|
|
30
36
|
continue;
|
|
@@ -32,11 +38,11 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
|
32
38
|
if (item.startsWith(`@`)) {
|
|
33
39
|
const columnRef = item.substring(1);
|
|
34
40
|
const alias = columnRef;
|
|
35
|
-
if (isGroupedResult && columnRef in
|
|
36
|
-
result[alias] =
|
|
41
|
+
if (isGroupedResult && columnRef in namespacedRow) {
|
|
42
|
+
result[alias] = namespacedRow[columnRef];
|
|
37
43
|
} else {
|
|
38
|
-
result[alias] =
|
|
39
|
-
|
|
44
|
+
result[alias] = extractValueFromNamespacedRow(
|
|
45
|
+
namespacedRow,
|
|
40
46
|
columnRef,
|
|
41
47
|
mainTableAlias,
|
|
42
48
|
void 0
|
|
@@ -52,22 +58,24 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
|
52
58
|
for (const [alias, expr] of Object.entries(item)) {
|
|
53
59
|
if (typeof expr === `string` && expr.startsWith(`@`)) {
|
|
54
60
|
const columnRef = expr.substring(1);
|
|
55
|
-
if (isGroupedResult && columnRef in
|
|
56
|
-
result[alias] =
|
|
61
|
+
if (isGroupedResult && columnRef in namespacedRow) {
|
|
62
|
+
result[alias] = namespacedRow[columnRef];
|
|
57
63
|
} else {
|
|
58
|
-
result[alias] =
|
|
59
|
-
|
|
64
|
+
result[alias] = extractValueFromNamespacedRow(
|
|
65
|
+
namespacedRow,
|
|
60
66
|
columnRef,
|
|
61
67
|
mainTableAlias,
|
|
62
68
|
void 0
|
|
63
69
|
);
|
|
64
70
|
}
|
|
65
71
|
} else if (typeof expr === `object`) {
|
|
66
|
-
if (isGroupedResult && alias in
|
|
67
|
-
result[alias] =
|
|
72
|
+
if (isGroupedResult && alias in namespacedRow) {
|
|
73
|
+
result[alias] = namespacedRow[alias];
|
|
74
|
+
} else if (expr.ORDER_INDEX) {
|
|
75
|
+
result[alias] = namespacedRow[mainTableAlias][alias];
|
|
68
76
|
} else {
|
|
69
|
-
result[alias] =
|
|
70
|
-
|
|
77
|
+
result[alias] = evaluateOperandOnNamespacedRow(
|
|
78
|
+
namespacedRow,
|
|
71
79
|
expr,
|
|
72
80
|
mainTableAlias,
|
|
73
81
|
void 0
|
|
@@ -77,22 +85,25 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
|
-
return result;
|
|
88
|
+
return [key, result];
|
|
81
89
|
})
|
|
82
90
|
);
|
|
83
91
|
}
|
|
84
|
-
function extractAllColumnsFromAllTables(
|
|
92
|
+
function extractAllColumnsFromAllTables(namespacedRow) {
|
|
85
93
|
const result = {};
|
|
86
|
-
for (const [tableAlias, tableData] of Object.entries(
|
|
94
|
+
for (const [tableAlias, tableData] of Object.entries(namespacedRow)) {
|
|
87
95
|
if (tableData && typeof tableData === `object`) {
|
|
88
|
-
Object.assign(
|
|
96
|
+
Object.assign(
|
|
97
|
+
result,
|
|
98
|
+
extractAllColumnsFromTable(namespacedRow, tableAlias)
|
|
99
|
+
);
|
|
89
100
|
}
|
|
90
101
|
}
|
|
91
102
|
return result;
|
|
92
103
|
}
|
|
93
|
-
function extractAllColumnsFromTable(
|
|
104
|
+
function extractAllColumnsFromTable(namespacedRow, tableAlias) {
|
|
94
105
|
const result = {};
|
|
95
|
-
const tableData =
|
|
106
|
+
const tableData = namespacedRow[tableAlias];
|
|
96
107
|
if (!tableData || typeof tableData !== `object`) {
|
|
97
108
|
return result;
|
|
98
109
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","sources":["../../../src/query/select.ts"],"sourcesContent":["import { map } from \"@electric-sql/d2ts\"\nimport {\n
|
|
1
|
+
{"version":3,"file":"select.js","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":[],"mappings":";;AAQO,SAAS,cACd,UACA,OACA,gBACA,QACa;AACb,SAAO,SAAS;AAAA,IACd,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,IAAI;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,IAAI;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,IAAI;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;"}
|
package/dist/esm/transactions.js
CHANGED
|
@@ -10,6 +10,7 @@ function generateUUID() {
|
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
const transactions = [];
|
|
13
|
+
let transactionStack = [];
|
|
13
14
|
function createTransaction(config) {
|
|
14
15
|
if (typeof config.mutationFn === `undefined`) {
|
|
15
16
|
throw `mutationFn is required when creating a transaction`;
|
|
@@ -22,7 +23,6 @@ function createTransaction(config) {
|
|
|
22
23
|
transactions.push(newTransaction);
|
|
23
24
|
return newTransaction;
|
|
24
25
|
}
|
|
25
|
-
let transactionStack = [];
|
|
26
26
|
function getActiveTransaction() {
|
|
27
27
|
if (transactionStack.length > 0) {
|
|
28
28
|
return transactionStack.slice(-1)[0];
|
|
@@ -36,6 +36,12 @@ function registerTransaction(tx) {
|
|
|
36
36
|
function unregisterTransaction(tx) {
|
|
37
37
|
transactionStack = transactionStack.filter((t) => t.id !== tx.id);
|
|
38
38
|
}
|
|
39
|
+
function removeFromPendingList(tx) {
|
|
40
|
+
const index = transactions.findIndex((t) => t.id === tx.id);
|
|
41
|
+
if (index !== -1) {
|
|
42
|
+
transactions.splice(index, 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
39
45
|
class Transaction {
|
|
40
46
|
constructor(config) {
|
|
41
47
|
this.id = config.id;
|
|
@@ -49,6 +55,9 @@ class Transaction {
|
|
|
49
55
|
}
|
|
50
56
|
setState(newState) {
|
|
51
57
|
this.state = newState;
|
|
58
|
+
if (newState === `completed` || newState === `failed`) {
|
|
59
|
+
removeFromPendingList(this);
|
|
60
|
+
}
|
|
52
61
|
}
|
|
53
62
|
mutate(callback) {
|
|
54
63
|
if (this.state !== `pending`) {
|
|
@@ -85,11 +94,11 @@ class Transaction {
|
|
|
85
94
|
}
|
|
86
95
|
this.setState(`failed`);
|
|
87
96
|
if (!isSecondaryRollback) {
|
|
88
|
-
const
|
|
89
|
-
this.mutations.forEach((m) =>
|
|
90
|
-
transactions
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
const mutationIds = /* @__PURE__ */ new Set();
|
|
98
|
+
this.mutations.forEach((m) => mutationIds.add(m.key));
|
|
99
|
+
for (const t of transactions) {
|
|
100
|
+
t.state === `pending` && t.mutations.some((m) => mutationIds.has(m.key)) && t.rollback({ isSecondaryRollback: true });
|
|
101
|
+
}
|
|
93
102
|
}
|
|
94
103
|
this.isPersisted.reject((_a = this.error) == null ? void 0 : _a.error);
|
|
95
104
|
this.touchCollection();
|
|
@@ -98,13 +107,13 @@ class Transaction {
|
|
|
98
107
|
// Tell collection that something has changed with the transaction
|
|
99
108
|
touchCollection() {
|
|
100
109
|
const hasCalled = /* @__PURE__ */ new Set();
|
|
101
|
-
this.mutations
|
|
110
|
+
for (const mutation of this.mutations) {
|
|
102
111
|
if (!hasCalled.has(mutation.collection.id)) {
|
|
103
112
|
mutation.collection.transactions.setState((state) => state);
|
|
104
113
|
mutation.collection.commitPendingTransactions();
|
|
105
114
|
hasCalled.add(mutation.collection.id);
|
|
106
115
|
}
|
|
107
|
-
}
|
|
116
|
+
}
|
|
108
117
|
}
|
|
109
118
|
async commit() {
|
|
110
119
|
if (this.state !== `pending`) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactions.js","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\
|
|
1
|
+
{"version":3,"file":"transactions.js","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":[],"mappings":"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"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { IStreamBuilder } from '@electric-sql/d2ts';
|
|
1
2
|
import { Collection } from './collection.js';
|
|
2
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
4
|
import { Transaction } from './transactions.js';
|
|
@@ -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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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>;
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/db",
|
|
3
3
|
"description": "A reactive client store for building super fast apps on sync",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.5",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@electric-sql/d2ts": "^0.1.
|
|
6
|
+
"@electric-sql/d2ts": "^0.1.6",
|
|
7
7
|
"@standard-schema/spec": "^1.0.0",
|
|
8
8
|
"@tanstack/store": "^0.7.0"
|
|
9
9
|
},
|