@tanstack/db 0.0.6 → 0.0.8
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 +452 -286
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +115 -26
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/proxy.cjs +2 -2
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.cjs +24 -38
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.d.cts +2 -2
- package/dist/cjs/query/order-by.cjs +41 -38
- package/dist/cjs/query/order-by.cjs.map +1 -1
- package/dist/cjs/query/schema.d.cts +3 -3
- package/dist/cjs/transactions.cjs +7 -6
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/transactions.d.cts +9 -9
- package/dist/cjs/types.d.cts +36 -22
- package/dist/esm/collection.d.ts +115 -26
- package/dist/esm/collection.js +453 -287
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/proxy.js +2 -2
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/compiled-query.d.ts +2 -2
- package/dist/esm/query/compiled-query.js +25 -39
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/order-by.js +41 -38
- package/dist/esm/query/order-by.js.map +1 -1
- package/dist/esm/query/schema.d.ts +3 -3
- package/dist/esm/transactions.d.ts +9 -9
- package/dist/esm/transactions.js +7 -6
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +36 -22
- package/package.json +2 -2
- package/src/collection.ts +652 -368
- package/src/index.ts +1 -1
- package/src/proxy.ts +2 -2
- package/src/query/compiled-query.ts +29 -39
- package/src/query/order-by.ts +69 -67
- package/src/query/schema.ts +3 -3
- package/src/transactions.ts +24 -22
- package/src/types.ts +54 -22
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { D2, MultiSet, output, MessageType } from "@electric-sql/d2ts";
|
|
2
|
-
import {
|
|
3
|
-
import { Collection } from "../collection.js";
|
|
2
|
+
import { createCollection } from "../collection.js";
|
|
4
3
|
import { compileQueryPipeline } from "./pipeline-compiler.js";
|
|
5
4
|
function compileQuery(queryBuilder) {
|
|
6
5
|
return new CompiledQuery(queryBuilder);
|
|
@@ -9,6 +8,7 @@ class CompiledQuery {
|
|
|
9
8
|
constructor(queryBuilder) {
|
|
10
9
|
this.state = `compiled`;
|
|
11
10
|
this.version = 0;
|
|
11
|
+
this.unsubscribeCallbacks = [];
|
|
12
12
|
const query = queryBuilder._query;
|
|
13
13
|
const collections = query.collections;
|
|
14
14
|
if (!collections) {
|
|
@@ -69,10 +69,10 @@ class CompiledQuery {
|
|
|
69
69
|
};
|
|
70
70
|
this.graph = graph;
|
|
71
71
|
this.inputs = inputs;
|
|
72
|
-
this.resultCollection =
|
|
72
|
+
this.resultCollection = createCollection({
|
|
73
73
|
id: crypto.randomUUID(),
|
|
74
74
|
// TODO: remove when we don't require any more
|
|
75
|
-
|
|
75
|
+
getKey: (val) => {
|
|
76
76
|
return val._key;
|
|
77
77
|
},
|
|
78
78
|
sync: {
|
|
@@ -83,11 +83,11 @@ class CompiledQuery {
|
|
|
83
83
|
get results() {
|
|
84
84
|
return this.resultCollection;
|
|
85
85
|
}
|
|
86
|
-
sendChangesToInput(inputKey, changes,
|
|
86
|
+
sendChangesToInput(inputKey, changes, getKey) {
|
|
87
87
|
const input = this.inputs[inputKey];
|
|
88
88
|
const multiSetArray = [];
|
|
89
89
|
for (const change of changes) {
|
|
90
|
-
const key =
|
|
90
|
+
const key = getKey(change.value);
|
|
91
91
|
if (change.type === `insert`) {
|
|
92
92
|
multiSetArray.push([[key, change.value], 1]);
|
|
93
93
|
} else if (change.type === `update`) {
|
|
@@ -120,47 +120,33 @@ class CompiledQuery {
|
|
|
120
120
|
} else if (this.state === `stopped`) {
|
|
121
121
|
throw new Error(`Query is stopped`);
|
|
122
122
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
});
|
|
131
|
-
this.incrementVersion();
|
|
132
|
-
this.sendFrontierToAllInputs();
|
|
133
|
-
this.runGraph();
|
|
123
|
+
Object.entries(this.inputCollections).forEach(([key, collection]) => {
|
|
124
|
+
this.sendChangesToInput(
|
|
125
|
+
key,
|
|
126
|
+
collection.currentStateAsChanges(),
|
|
127
|
+
collection.config.getKey
|
|
128
|
+
);
|
|
134
129
|
});
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.sendFrontierToAllInputs();
|
|
147
|
-
this.runGraph();
|
|
148
|
-
});
|
|
149
|
-
},
|
|
150
|
-
deps: Object.values(this.inputCollections).map(
|
|
151
|
-
(collection) => collection.derivedChanges
|
|
152
|
-
)
|
|
130
|
+
this.incrementVersion();
|
|
131
|
+
this.sendFrontierToAllInputs();
|
|
132
|
+
this.runGraph();
|
|
133
|
+
Object.entries(this.inputCollections).forEach(([key, collection]) => {
|
|
134
|
+
const unsubscribe = collection.subscribeChanges((changes) => {
|
|
135
|
+
this.sendChangesToInput(key, changes, collection.config.getKey);
|
|
136
|
+
this.incrementVersion();
|
|
137
|
+
this.sendFrontierToAllInputs();
|
|
138
|
+
this.runGraph();
|
|
139
|
+
});
|
|
140
|
+
this.unsubscribeCallbacks.push(unsubscribe);
|
|
153
141
|
});
|
|
154
|
-
this.unsubscribeEffect = changeEffect.mount();
|
|
155
142
|
this.state = `running`;
|
|
156
143
|
return () => {
|
|
157
144
|
this.stop();
|
|
158
145
|
};
|
|
159
146
|
}
|
|
160
147
|
stop() {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
this.unsubscribeEffect = void 0;
|
|
148
|
+
this.unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
149
|
+
this.unsubscribeCallbacks = [];
|
|
164
150
|
this.state = `stopped`;
|
|
165
151
|
}
|
|
166
152
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiled-query.js","sources":["../../../src/query/compiled-query.ts"],"sourcesContent":["import { D2, MessageType, MultiSet, output } from \"@electric-sql/d2ts\"\nimport {
|
|
1
|
+
{"version":3,"file":"compiled-query.js","sources":["../../../src/query/compiled-query.ts"],"sourcesContent":["import { D2, MessageType, MultiSet, output } from \"@electric-sql/d2ts\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQueryPipeline } from \"./pipeline-compiler.js\"\nimport type { Collection } from \"../collection.js\"\nimport type { ChangeMessage, SyncConfig } from \"../types.js\"\nimport type {\n IStreamBuilder,\n MultiSetArray,\n RootStreamBuilder,\n} from \"@electric-sql/d2ts\"\nimport type { QueryBuilder, ResultsFromContext } from \"./query-builder.js\"\nimport type { Context, Schema } from \"./types.js\"\n\nexport function compileQuery<TContext extends Context<Schema>>(\n queryBuilder: QueryBuilder<TContext>\n) {\n return new CompiledQuery<\n ResultsFromContext<TContext> & { _key?: string | number }\n >(queryBuilder)\n}\n\nexport class CompiledQuery<TResults extends object = Record<string, unknown>> {\n private graph: D2\n private inputs: Record<string, RootStreamBuilder<any>>\n private inputCollections: Record<string, Collection<any>>\n private resultCollection: Collection<TResults>\n public state: `compiled` | `running` | `stopped` = `compiled`\n private version = 0\n private unsubscribeCallbacks: Array<() => void> = []\n\n constructor(queryBuilder: QueryBuilder<Context<Schema>>) {\n const query = queryBuilder._query\n const collections = query.collections\n\n if (!collections) {\n throw new Error(`No collections provided`)\n }\n\n this.inputCollections = collections\n\n const graph = new D2({ initialFrontier: this.version })\n const inputs = Object.fromEntries(\n Object.entries(collections).map(([key]) => [key, graph.newInput<any>()])\n )\n\n const sync: SyncConfig<TResults>[`sync`] = ({ begin, write, commit }) => {\n compileQueryPipeline<IStreamBuilder<[unknown, TResults]>>(\n query,\n inputs\n ).pipe(\n output(({ type, data }) => {\n if (type === MessageType.DATA) {\n begin()\n data.collection\n .getInner()\n .reduce((acc, [[key, value], multiplicity]) => {\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResults }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value } = changes\n const valueWithKey = { ...value, _key: rawKey }\n if (inserts && !deletes) {\n write({\n value: valueWithKey,\n type: `insert`,\n })\n } else if (inserts >= deletes) {\n write({\n value: valueWithKey,\n type: `update`,\n })\n } else if (deletes > 0) {\n write({\n value: valueWithKey,\n type: `delete`,\n })\n }\n })\n commit()\n }\n })\n )\n graph.finalize()\n }\n\n this.graph = graph\n this.inputs = inputs\n this.resultCollection = createCollection<TResults>({\n id: crypto.randomUUID(), // TODO: remove when we don't require any more\n getKey: (val: unknown) => {\n return (val as any)._key\n },\n sync: {\n sync,\n },\n })\n }\n\n get results() {\n return this.resultCollection\n }\n\n private sendChangesToInput(\n inputKey: string,\n changes: Array<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n ) {\n const input = this.inputs[inputKey]!\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(this.version, new MultiSet(multiSetArray))\n }\n\n private sendFrontierToInput(inputKey: string) {\n const input = this.inputs[inputKey]!\n input.sendFrontier(this.version)\n }\n\n private sendFrontierToAllInputs() {\n Object.entries(this.inputs).forEach(([key]) => {\n this.sendFrontierToInput(key)\n })\n }\n\n private incrementVersion() {\n this.version++\n }\n\n private runGraph() {\n this.graph.run()\n }\n\n start() {\n if (this.state === `running`) {\n throw new Error(`Query is already running`)\n } else if (this.state === `stopped`) {\n throw new Error(`Query is stopped`)\n }\n\n // Send initial state\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n this.sendChangesToInput(\n key,\n collection.currentStateAsChanges(),\n collection.config.getKey\n )\n })\n this.incrementVersion()\n this.sendFrontierToAllInputs()\n this.runGraph()\n\n // Subscribe to changes\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n const unsubscribe = collection.subscribeChanges((changes) => {\n this.sendChangesToInput(key, changes, collection.config.getKey)\n this.incrementVersion()\n this.sendFrontierToAllInputs()\n this.runGraph()\n })\n\n this.unsubscribeCallbacks.push(unsubscribe)\n })\n\n this.state = `running`\n return () => {\n this.stop()\n }\n }\n\n stop() {\n this.unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n this.unsubscribeCallbacks = []\n this.state = `stopped`\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -50,16 +50,13 @@ function processOrderBy(resultPipeline, query, mainTableAlias) {
|
|
|
50
50
|
}
|
|
51
51
|
const valueExtractor = (namespacedRow) => {
|
|
52
52
|
if (orderByItems.length > 1) {
|
|
53
|
-
return orderByItems.map(
|
|
54
|
-
|
|
53
|
+
return orderByItems.map(
|
|
54
|
+
(item) => evaluateOperandOnNamespacedRow(
|
|
55
55
|
namespacedRow,
|
|
56
56
|
item.operand,
|
|
57
57
|
mainTableAlias
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
...[...val].map((c) => 65535 - c.charCodeAt(0))
|
|
61
|
-
) : val;
|
|
62
|
-
});
|
|
58
|
+
)
|
|
59
|
+
);
|
|
63
60
|
} else if (orderByItems.length === 1) {
|
|
64
61
|
const item = orderByItems[0];
|
|
65
62
|
const val = evaluateOperandOnNamespacedRow(
|
|
@@ -67,53 +64,59 @@ function processOrderBy(resultPipeline, query, mainTableAlias) {
|
|
|
67
64
|
item.operand,
|
|
68
65
|
mainTableAlias
|
|
69
66
|
);
|
|
70
|
-
return
|
|
71
|
-
...[...val].map((c) => 65535 - c.charCodeAt(0))
|
|
72
|
-
) : val;
|
|
67
|
+
return val;
|
|
73
68
|
}
|
|
74
69
|
return null;
|
|
75
70
|
};
|
|
76
|
-
const
|
|
77
|
-
if (typeof a === `number` && typeof b === `number`) {
|
|
78
|
-
return a - b;
|
|
79
|
-
}
|
|
71
|
+
const ascComparator = (a, b) => {
|
|
80
72
|
if (typeof a === `string` && typeof b === `string`) {
|
|
81
73
|
return a.localeCompare(b);
|
|
82
74
|
}
|
|
83
|
-
if (typeof a === `boolean` && typeof b === `boolean`) {
|
|
84
|
-
return a === b ? 0 : a ? 1 : -1;
|
|
85
|
-
}
|
|
86
|
-
if (a instanceof Date && b instanceof Date) {
|
|
87
|
-
return a.getTime() - b.getTime();
|
|
88
|
-
}
|
|
89
|
-
if (a === null || b === null) {
|
|
90
|
-
return 0;
|
|
91
|
-
}
|
|
92
75
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
93
76
|
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
94
|
-
const
|
|
95
|
-
const bVal = b[i];
|
|
96
|
-
let result;
|
|
97
|
-
if (typeof aVal === `boolean` && typeof bVal === `boolean`) {
|
|
98
|
-
result = aVal === bVal ? 0 : aVal ? 1 : -1;
|
|
99
|
-
} else if (typeof aVal === `number` && typeof bVal === `number`) {
|
|
100
|
-
result = aVal - bVal;
|
|
101
|
-
} else if (typeof aVal === `string` && typeof bVal === `string`) {
|
|
102
|
-
result = aVal.localeCompare(bVal);
|
|
103
|
-
} else {
|
|
104
|
-
result = comparator(aVal, bVal);
|
|
105
|
-
}
|
|
77
|
+
const result = ascComparator(a[i], b[i]);
|
|
106
78
|
if (result !== 0) {
|
|
107
79
|
return result;
|
|
108
80
|
}
|
|
109
81
|
}
|
|
110
82
|
return a.length - b.length;
|
|
111
83
|
}
|
|
112
|
-
|
|
113
|
-
|
|
84
|
+
const bothObjects = typeof a === `object` && typeof b === `object`;
|
|
85
|
+
const bothDates = a instanceof Date && b instanceof Date;
|
|
86
|
+
const notNull = a !== null && b !== null;
|
|
87
|
+
if (bothObjects && !bothDates && notNull) {
|
|
88
|
+
return a.toString().localeCompare(b.toString());
|
|
114
89
|
}
|
|
115
|
-
|
|
90
|
+
if (a < b) return -1;
|
|
91
|
+
if (a > b) return 1;
|
|
92
|
+
return 0;
|
|
93
|
+
};
|
|
94
|
+
const descComparator = (a, b) => {
|
|
95
|
+
return ascComparator(b, a);
|
|
96
|
+
};
|
|
97
|
+
const makeComparator = (orderByProps) => {
|
|
98
|
+
return (a, b) => {
|
|
99
|
+
if (orderByProps.length > 1) {
|
|
100
|
+
const arrayA = a;
|
|
101
|
+
const arrayB = b;
|
|
102
|
+
for (let i = 0; i < orderByProps.length; i++) {
|
|
103
|
+
const direction = orderByProps[i].direction;
|
|
104
|
+
const compareFn = direction === `desc` ? descComparator : ascComparator;
|
|
105
|
+
const result = compareFn(arrayA[i], arrayB[i]);
|
|
106
|
+
if (result !== 0) {
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return arrayA.length - arrayB.length;
|
|
111
|
+
}
|
|
112
|
+
if (orderByProps.length === 1) {
|
|
113
|
+
const direction = orderByProps[0].direction;
|
|
114
|
+
return direction === `desc` ? descComparator(a, b) : ascComparator(a, b);
|
|
115
|
+
}
|
|
116
|
+
return ascComparator(a, b);
|
|
117
|
+
};
|
|
116
118
|
};
|
|
119
|
+
const comparator = makeComparator(orderByItems);
|
|
117
120
|
if (hasOrderIndexColumn) {
|
|
118
121
|
if (orderIndexType === `numeric`) {
|
|
119
122
|
resultPipeline = resultPipeline.pipe(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"order-by.js","sources":["../../../src/query/order-by.ts"],"sourcesContent":["import {\n map,\n orderBy,\n orderByWithFractionalIndex,\n orderByWithIndex,\n} from \"@electric-sql/d2ts\"\nimport { evaluateOperandOnNamespacedRow } from \"./extractors\"\nimport { isOrderIndexFunctionCall } from \"./utils\"\nimport type { ConditionOperand, Query } from \"./schema\"\nimport type {\n KeyedNamespacedRow,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../types\"\n\nexport function processOrderBy(\n resultPipeline: NamespacedAndKeyedStream,\n query: Query,\n mainTableAlias: string\n) {\n // Check if any column in the SELECT clause is an ORDER_INDEX function call\n let hasOrderIndexColumn = false\n let orderIndexType: `numeric` | `fractional` = `numeric`\n let orderIndexAlias = ``\n\n // Scan the SELECT clause for ORDER_INDEX functions\n // TODO: Select is going to be optional in future - we will automatically add an\n // attribute for the index column\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` && isOrderIndexFunctionCall(expr)) {\n hasOrderIndexColumn = true\n orderIndexAlias = alias\n orderIndexType = getOrderIndexType(expr)\n break\n }\n }\n }\n if (hasOrderIndexColumn) break\n }\n\n // Normalize orderBy to an array of objects\n const orderByItems: Array<{\n operand: ConditionOperand\n direction: `asc` | `desc`\n }> = []\n\n if (typeof query.orderBy === `string`) {\n // Handle string format: '@column'\n orderByItems.push({\n operand: query.orderBy,\n direction: `asc`,\n })\n } else if (Array.isArray(query.orderBy)) {\n // Handle array format: ['@column1', { '@column2': 'desc' }]\n for (const item of query.orderBy) {\n if (typeof item === `string`) {\n orderByItems.push({\n operand: item,\n direction: `asc`,\n })\n } else if (typeof item === `object`) {\n for (const [column, direction] of Object.entries(item)) {\n orderByItems.push({\n operand: column,\n direction: direction as `asc` | `desc`,\n })\n }\n }\n }\n } else if (typeof query.orderBy === `object`) {\n // Handle object format: { '@column': 'desc' }\n for (const [column, direction] of Object.entries(query.orderBy)) {\n orderByItems.push({\n operand: column,\n direction: direction as `asc` | `desc`,\n })\n }\n }\n\n // Create a value extractor function for the orderBy operator\n // const valueExtractor = ([key, namespacedRow]: [\n const valueExtractor = (namespacedRow: NamespacedRow) => {\n // For multiple orderBy columns, create a composite key\n if (orderByItems.length > 1) {\n return orderByItems.map((item) => {\n const val = evaluateOperandOnNamespacedRow(\n namespacedRow,\n item.operand,\n mainTableAlias\n )\n\n // Reverse the value for 'desc' ordering\n return item.direction === `desc` && typeof val === `number`\n ? -val\n : item.direction === `desc` && typeof val === `string`\n ? String.fromCharCode(\n ...[...val].map((c) => 0xffff - c.charCodeAt(0))\n )\n : val\n })\n } else if (orderByItems.length === 1) {\n // For a single orderBy column, use the value directly\n const item = orderByItems[0]\n const val = evaluateOperandOnNamespacedRow(\n namespacedRow,\n item!.operand,\n mainTableAlias\n )\n\n // Reverse the value for 'desc' ordering\n return item!.direction === `desc` && typeof val === `number`\n ? -val\n : item!.direction === `desc` && typeof val === `string`\n ? String.fromCharCode(\n ...[...val].map((c) => 0xffff - c.charCodeAt(0))\n )\n : val\n }\n\n // Default case - no ordering\n return null\n }\n\n const comparator = (a: unknown, b: unknown): number => {\n // if a and b are both numbers compare them directly\n if (typeof a === `number` && typeof b === `number`) {\n return a - b\n }\n // if a and b are both strings, compare them lexicographically\n if (typeof a === `string` && typeof b === `string`) {\n return a.localeCompare(b)\n }\n // if a and b are both booleans, compare them\n if (typeof a === `boolean` && typeof b === `boolean`) {\n return a === b ? 0 : a ? 1 : -1\n }\n // if a and b are both dates, compare them\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime()\n }\n // if a and b are both null, return 0\n if (a === null || b === null) {\n return 0\n }\n\n // if a and b are both arrays, compare them element by element\n if (Array.isArray(a) && Array.isArray(b)) {\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n // Get the values from the array\n const aVal = a[i]\n const bVal = b[i]\n\n // Compare the values\n let result: number\n\n if (typeof aVal === `boolean` && typeof bVal === `boolean`) {\n // Special handling for booleans - false comes before true\n result = aVal === bVal ? 0 : aVal ? 1 : -1\n } else if (typeof aVal === `number` && typeof bVal === `number`) {\n // Numeric comparison\n result = aVal - bVal\n } else if (typeof aVal === `string` && typeof bVal === `string`) {\n // String comparison\n result = aVal.localeCompare(bVal)\n } else {\n // Default comparison using the general comparator\n result = comparator(aVal, bVal)\n }\n\n if (result !== 0) {\n return result\n }\n }\n // All elements are equal up to the minimum length\n return a.length - b.length\n }\n // if a and b are both null/undefined, return 0\n if (a == null && b == null) {\n return 0\n }\n // Fallback to string comparison for all other cases\n return (a as any).toString().localeCompare((b as any).toString())\n }\n\n // Apply the appropriate orderBy operator based on whether an ORDER_INDEX column is requested\n if (hasOrderIndexColumn) {\n if (orderIndexType === `numeric`) {\n // Use orderByWithIndex for numeric indices\n resultPipeline = resultPipeline.pipe(\n orderByWithIndex(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n }),\n map(([key, [value, index]]) => {\n // Add the index to the result\n // We add this to the main table alias for now\n // TODO: re are going to need to refactor the whole order by pipeline\n const result = {\n ...(value as Record<string, unknown>),\n [mainTableAlias]: {\n ...value[mainTableAlias],\n [orderIndexAlias]: index,\n },\n }\n return [key, result] as KeyedNamespacedRow\n })\n )\n } else {\n // Use orderByWithFractionalIndex for fractional indices\n resultPipeline = resultPipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n }),\n map(([key, [value, index]]) => {\n // Add the index to the result\n // We add this to the main table alias for now\n // TODO: re are going to need to refactor the whole order by pipeline\n const result = {\n ...(value as Record<string, unknown>),\n [mainTableAlias]: {\n ...value[mainTableAlias],\n [orderIndexAlias]: index,\n },\n }\n return [key, result] as KeyedNamespacedRow\n })\n )\n }\n } else {\n // Use regular orderBy if no index column is requested\n resultPipeline = resultPipeline.pipe(\n orderBy(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n })\n )\n }\n\n return resultPipeline\n}\n\n// Helper function to extract the ORDER_INDEX type from a function call\nfunction getOrderIndexType(obj: any): `numeric` | `fractional` {\n if (!isOrderIndexFunctionCall(obj)) {\n throw new Error(`Not an ORDER_INDEX function call`)\n }\n\n const arg = obj[`ORDER_INDEX`]\n if (arg === `numeric` || arg === true || arg === `default`) {\n return `numeric`\n } else if (arg === `fractional`) {\n return `fractional`\n } else {\n throw new Error(`Invalid ORDER_INDEX type: ` + arg)\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"order-by.js","sources":["../../../src/query/order-by.ts"],"sourcesContent":["import {\n map,\n orderBy,\n orderByWithFractionalIndex,\n orderByWithIndex,\n} from \"@electric-sql/d2ts\"\nimport { evaluateOperandOnNamespacedRow } from \"./extractors\"\nimport { isOrderIndexFunctionCall } from \"./utils\"\nimport type { ConditionOperand, Query } from \"./schema\"\nimport type {\n KeyedNamespacedRow,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../types\"\n\ntype OrderByItem = {\n operand: ConditionOperand\n direction: `asc` | `desc`\n}\n\ntype OrderByItems = Array<OrderByItem>\n\nexport function processOrderBy(\n resultPipeline: NamespacedAndKeyedStream,\n query: Query,\n mainTableAlias: string\n) {\n // Check if any column in the SELECT clause is an ORDER_INDEX function call\n let hasOrderIndexColumn = false\n let orderIndexType: `numeric` | `fractional` = `numeric`\n let orderIndexAlias = ``\n\n // Scan the SELECT clause for ORDER_INDEX functions\n // TODO: Select is going to be optional in future - we will automatically add an\n // attribute for the index column\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` && isOrderIndexFunctionCall(expr)) {\n hasOrderIndexColumn = true\n orderIndexAlias = alias\n orderIndexType = getOrderIndexType(expr)\n break\n }\n }\n }\n if (hasOrderIndexColumn) break\n }\n\n // Normalize orderBy to an array of objects\n const orderByItems: OrderByItems = []\n\n if (typeof query.orderBy === `string`) {\n // Handle string format: '@column'\n orderByItems.push({\n operand: query.orderBy,\n direction: `asc`,\n })\n } else if (Array.isArray(query.orderBy)) {\n // Handle array format: ['@column1', { '@column2': 'desc' }]\n for (const item of query.orderBy) {\n if (typeof item === `string`) {\n orderByItems.push({\n operand: item,\n direction: `asc`,\n })\n } else if (typeof item === `object`) {\n for (const [column, direction] of Object.entries(item)) {\n orderByItems.push({\n operand: column,\n direction: direction as `asc` | `desc`,\n })\n }\n }\n }\n } else if (typeof query.orderBy === `object`) {\n // Handle object format: { '@column': 'desc' }\n for (const [column, direction] of Object.entries(query.orderBy)) {\n orderByItems.push({\n operand: column,\n direction: direction as `asc` | `desc`,\n })\n }\n }\n\n // Create a value extractor function for the orderBy operator\n // const valueExtractor = ([key, namespacedRow]: [\n const valueExtractor = (namespacedRow: NamespacedRow) => {\n // For multiple orderBy columns, create a composite key\n if (orderByItems.length > 1) {\n return orderByItems.map((item) =>\n evaluateOperandOnNamespacedRow(\n namespacedRow,\n item.operand,\n mainTableAlias\n )\n )\n } else if (orderByItems.length === 1) {\n // For a single orderBy column, use the value directly\n const item = orderByItems[0]\n const val = evaluateOperandOnNamespacedRow(\n namespacedRow,\n item!.operand,\n mainTableAlias\n )\n return val\n }\n\n // Default case - no ordering\n return null\n }\n\n const ascComparator = (a: any, b: any): number => {\n // if a and b are both strings, compare them based on locale\n if (typeof a === `string` && typeof b === `string`) {\n return a.localeCompare(b)\n }\n\n // if a and b are both arrays, compare them element by element\n if (Array.isArray(a) && Array.isArray(b)) {\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n // Compare the values\n const result = ascComparator(a[i], b[i])\n\n if (result !== 0) {\n return result\n }\n }\n // All elements are equal up to the minimum length\n return a.length - b.length\n }\n\n // If at least one of the values is an object then we don't really know how to meaningfully compare them\n // therefore we turn them into strings and compare those\n // There are 2 exceptions:\n // 1) if both objects are dates then we can compare them\n // 2) if either object is nullish then we can't call toString on it\n const bothObjects = typeof a === `object` && typeof b === `object`\n const bothDates = a instanceof Date && b instanceof Date\n const notNull = a !== null && b !== null\n if (bothObjects && !bothDates && notNull) {\n // Every object should support `toString`\n return a.toString().localeCompare(b.toString())\n }\n\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n\n const descComparator = (a: unknown, b: unknown): number => {\n return ascComparator(b, a)\n }\n\n // Create a multi-property comparator that respects the order and direction of each property\n const makeComparator = (orderByProps: OrderByItems) => {\n return (a: unknown, b: unknown) => {\n // If we're comparing arrays (multiple properties), compare each property in order\n if (orderByProps.length > 1) {\n // `a` and `b` must be arrays since `orderByItems.length > 1`\n // hence the extracted values must be arrays\n const arrayA = a as Array<unknown>\n const arrayB = b as Array<unknown>\n for (let i = 0; i < orderByProps.length; i++) {\n const direction = orderByProps[i]!.direction\n const compareFn =\n direction === `desc` ? descComparator : ascComparator\n const result = compareFn(arrayA[i], arrayB[i])\n if (result !== 0) {\n return result\n }\n }\n // should normally always be 0 because\n // both values are extracted based on orderByItems\n return arrayA.length - arrayB.length\n }\n\n // Single property comparison\n if (orderByProps.length === 1) {\n const direction = orderByProps[0]!.direction\n return direction === `desc` ? descComparator(a, b) : ascComparator(a, b)\n }\n\n return ascComparator(a, b)\n }\n }\n const comparator = makeComparator(orderByItems)\n\n // Apply the appropriate orderBy operator based on whether an ORDER_INDEX column is requested\n if (hasOrderIndexColumn) {\n if (orderIndexType === `numeric`) {\n // Use orderByWithIndex for numeric indices\n resultPipeline = resultPipeline.pipe(\n orderByWithIndex(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n }),\n map(([key, [value, index]]) => {\n // Add the index to the result\n // We add this to the main table alias for now\n // TODO: re are going to need to refactor the whole order by pipeline\n const result = {\n ...(value as Record<string, unknown>),\n [mainTableAlias]: {\n ...value[mainTableAlias],\n [orderIndexAlias]: index,\n },\n }\n return [key, result] as KeyedNamespacedRow\n })\n )\n } else {\n // Use orderByWithFractionalIndex for fractional indices\n resultPipeline = resultPipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n }),\n map(([key, [value, index]]) => {\n // Add the index to the result\n // We add this to the main table alias for now\n // TODO: re are going to need to refactor the whole order by pipeline\n const result = {\n ...(value as Record<string, unknown>),\n [mainTableAlias]: {\n ...value[mainTableAlias],\n [orderIndexAlias]: index,\n },\n }\n return [key, result] as KeyedNamespacedRow\n })\n )\n }\n } else {\n // Use regular orderBy if no index column is requested\n resultPipeline = resultPipeline.pipe(\n orderBy(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n })\n )\n }\n\n return resultPipeline\n}\n\n// Helper function to extract the ORDER_INDEX type from a function call\nfunction getOrderIndexType(obj: any): `numeric` | `fractional` {\n if (!isOrderIndexFunctionCall(obj)) {\n throw new Error(`Not an ORDER_INDEX function call`)\n }\n\n const arg = obj[`ORDER_INDEX`]\n if (arg === `numeric` || arg === true || arg === `default`) {\n return `numeric`\n } else if (arg === `fractional`) {\n return `fractional`\n } else {\n throw new Error(`Invalid ORDER_INDEX type: ` + arg)\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -63,7 +63,7 @@ export type Select<TContext extends Context = Context> = PropertyReferenceString
|
|
|
63
63
|
export type SelectCallback<TContext extends Context = Context> = (context: TContext extends {
|
|
64
64
|
schema: infer S;
|
|
65
65
|
} ? S : any) => any;
|
|
66
|
-
export type As<
|
|
66
|
+
export type As<_TContext extends Context = Context> = string;
|
|
67
67
|
export type From<TContext extends Context = Context> = InputReference<{
|
|
68
68
|
baseSchema: TContext[`baseSchema`];
|
|
69
69
|
schema: TContext[`baseSchema`];
|
|
@@ -74,8 +74,8 @@ export type WhereCallback<TContext extends Context = Context> = (context: TConte
|
|
|
74
74
|
export type Where<TContext extends Context = Context> = Array<Condition<TContext> | WhereCallback<TContext>>;
|
|
75
75
|
export type Having<TContext extends Context = Context> = Where<TContext>;
|
|
76
76
|
export type GroupBy<TContext extends Context = Context> = PropertyReference<TContext> | Array<PropertyReference<TContext>>;
|
|
77
|
-
export type Limit<
|
|
78
|
-
export type Offset<
|
|
77
|
+
export type Limit<_TContext extends Context = Context> = number;
|
|
78
|
+
export type Offset<_TContext extends Context = Context> = number;
|
|
79
79
|
export interface BaseQuery<TContext extends Context = Context> {
|
|
80
80
|
select?: Array<Select<TContext>>;
|
|
81
81
|
as?: As<TContext>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Deferred } from './deferred.js';
|
|
2
|
-
import { PendingMutation, TransactionConfig, TransactionState } from './types.js';
|
|
2
|
+
import { MutationFn, PendingMutation, TransactionConfig, TransactionState } from './types.js';
|
|
3
3
|
export declare function createTransaction(config: TransactionConfig): Transaction;
|
|
4
4
|
export declare function getActiveTransaction(): Transaction | undefined;
|
|
5
|
-
export declare class Transaction {
|
|
5
|
+
export declare class Transaction<T extends object = Record<string, unknown>> {
|
|
6
6
|
id: string;
|
|
7
7
|
state: TransactionState;
|
|
8
|
-
mutationFn:
|
|
9
|
-
mutations: Array<PendingMutation<
|
|
10
|
-
isPersisted: Deferred<Transaction
|
|
8
|
+
mutationFn: MutationFn<T>;
|
|
9
|
+
mutations: Array<PendingMutation<T>>;
|
|
10
|
+
isPersisted: Deferred<Transaction<T>>;
|
|
11
11
|
autoCommit: boolean;
|
|
12
12
|
createdAt: Date;
|
|
13
13
|
metadata: Record<string, unknown>;
|
|
@@ -15,13 +15,13 @@ export declare class Transaction {
|
|
|
15
15
|
message: string;
|
|
16
16
|
error: Error;
|
|
17
17
|
};
|
|
18
|
-
constructor(config: TransactionConfig);
|
|
18
|
+
constructor(config: TransactionConfig<T>);
|
|
19
19
|
setState(newState: TransactionState): void;
|
|
20
|
-
mutate(callback: () => void): Transaction
|
|
20
|
+
mutate(callback: () => void): Transaction<T>;
|
|
21
21
|
applyMutations(mutations: Array<PendingMutation<any>>): void;
|
|
22
22
|
rollback(config?: {
|
|
23
23
|
isSecondaryRollback?: boolean;
|
|
24
|
-
}): Transaction
|
|
24
|
+
}): Transaction<T>;
|
|
25
25
|
touchCollection(): void;
|
|
26
|
-
commit(): Promise<Transaction
|
|
26
|
+
commit(): Promise<Transaction<T>>;
|
|
27
27
|
}
|
package/dist/esm/transactions.js
CHANGED
|
@@ -77,7 +77,7 @@ class Transaction {
|
|
|
77
77
|
applyMutations(mutations) {
|
|
78
78
|
for (const newMutation of mutations) {
|
|
79
79
|
const existingIndex = this.mutations.findIndex(
|
|
80
|
-
(m) => m.
|
|
80
|
+
(m) => m.globalKey === newMutation.globalKey
|
|
81
81
|
);
|
|
82
82
|
if (existingIndex >= 0) {
|
|
83
83
|
this.mutations[existingIndex] = newMutation;
|
|
@@ -95,9 +95,9 @@ class Transaction {
|
|
|
95
95
|
this.setState(`failed`);
|
|
96
96
|
if (!isSecondaryRollback) {
|
|
97
97
|
const mutationIds = /* @__PURE__ */ new Set();
|
|
98
|
-
this.mutations.forEach((m) => mutationIds.add(m.
|
|
98
|
+
this.mutations.forEach((m) => mutationIds.add(m.globalKey));
|
|
99
99
|
for (const t of transactions) {
|
|
100
|
-
t.state === `pending` && t.mutations.some((m) => mutationIds.has(m.
|
|
100
|
+
t.state === `pending` && t.mutations.some((m) => mutationIds.has(m.globalKey)) && t.rollback({ isSecondaryRollback: true });
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
this.isPersisted.reject((_a = this.error) == null ? void 0 : _a.error);
|
|
@@ -109,7 +109,7 @@ class Transaction {
|
|
|
109
109
|
const hasCalled = /* @__PURE__ */ new Set();
|
|
110
110
|
for (const mutation of this.mutations) {
|
|
111
111
|
if (!hasCalled.has(mutation.collection.id)) {
|
|
112
|
-
mutation.collection.
|
|
112
|
+
mutation.collection.onTransactionStateChange();
|
|
113
113
|
mutation.collection.commitPendingTransactions();
|
|
114
114
|
hasCalled.add(mutation.collection.id);
|
|
115
115
|
}
|
|
@@ -125,8 +125,9 @@ class Transaction {
|
|
|
125
125
|
return this;
|
|
126
126
|
}
|
|
127
127
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
await this.mutationFn({
|
|
129
|
+
transaction: this
|
|
130
|
+
});
|
|
130
131
|
this.setState(`completed`);
|
|
131
132
|
this.touchCollection();
|
|
132
133
|
this.isPersisted.resolve(this);
|
|
@@ -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 TransactionWithMutations,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction
|
|
1
|
+
{"version":3,"file":"transactions.js","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n MutationFn,\n PendingMutation,\n TransactionConfig,\n TransactionState,\n TransactionWithMutations,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction<any>> = []\nlet transactionStack: Array<Transaction<any>> = []\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<any>) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction<any>) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nfunction removeFromPendingList(tx: Transaction<any>) {\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<T extends object = Record<string, unknown>> {\n public id: string\n public state: TransactionState\n public mutationFn: MutationFn<T>\n public mutations: Array<PendingMutation<T>>\n public isPersisted: Deferred<Transaction<T>>\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<T>) {\n this.id = config.id!\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred<Transaction<T>>()\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<T> {\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.globalKey === newMutation.globalKey\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<T> {\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.globalKey))\n for (const t of transactions) {\n t.state === `pending` &&\n t.mutations.some((m) => mutationIds.has(m.globalKey)) &&\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.onTransactionStateChange()\n mutation.collection.commitPendingTransactions()\n hasCalled.add(mutation.collection.id)\n }\n }\n }\n\n async commit(): Promise<Transaction<T>> {\n if (this.state !== `pending`) {\n throw `You can no longer call .commit() as the transaction is no longer pending`\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n\n return this\n }\n\n // Run mutationFn\n try {\n // At this point we know there's at least one mutation\n // We've already verified mutations is non-empty, so this cast is safe\n // Use a direct type assertion instead of object spreading to preserve the original type\n await this.mutationFn({\n transaction: this as unknown as TransactionWithMutations<T>,\n })\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
|
@@ -3,42 +3,55 @@ import { Collection } from './collection.js';
|
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
import { Transaction } from './transactions.js';
|
|
5
5
|
export type TransactionState = `pending` | `persisting` | `completed` | `failed`;
|
|
6
|
+
/**
|
|
7
|
+
* Represents a utility function that can be attached to a collection
|
|
8
|
+
*/
|
|
9
|
+
export type Fn = (...args: Array<any>) => any;
|
|
10
|
+
/**
|
|
11
|
+
* A record of utility functions that can be attached to a collection
|
|
12
|
+
*/
|
|
13
|
+
export type UtilsRecord = Record<string, Fn>;
|
|
6
14
|
/**
|
|
7
15
|
* Represents a pending mutation within a transaction
|
|
8
16
|
* Contains information about the original and modified data, as well as metadata
|
|
9
17
|
*/
|
|
10
18
|
export interface PendingMutation<T extends object = Record<string, unknown>> {
|
|
11
19
|
mutationId: string;
|
|
12
|
-
original:
|
|
13
|
-
modified:
|
|
14
|
-
changes:
|
|
20
|
+
original: Partial<T>;
|
|
21
|
+
modified: T;
|
|
22
|
+
changes: Partial<T>;
|
|
23
|
+
globalKey: string;
|
|
15
24
|
key: any;
|
|
16
25
|
type: OperationType;
|
|
17
26
|
metadata: unknown;
|
|
18
27
|
syncMetadata: Record<string, unknown>;
|
|
19
28
|
createdAt: Date;
|
|
20
29
|
updatedAt: Date;
|
|
21
|
-
collection: Collection<T>;
|
|
30
|
+
collection: Collection<T, any>;
|
|
22
31
|
}
|
|
23
32
|
/**
|
|
24
33
|
* Configuration options for creating a new transaction
|
|
25
34
|
*/
|
|
26
|
-
export type MutationFnParams = {
|
|
27
|
-
transaction:
|
|
35
|
+
export type MutationFnParams<T extends object = Record<string, unknown>> = {
|
|
36
|
+
transaction: TransactionWithMutations<T>;
|
|
28
37
|
};
|
|
29
|
-
export type MutationFn = (params: MutationFnParams) => Promise<any>;
|
|
38
|
+
export type MutationFn<T extends object = Record<string, unknown>> = (params: MutationFnParams<T>) => Promise<any>;
|
|
39
|
+
/**
|
|
40
|
+
* Represents a non-empty array (at least one element)
|
|
41
|
+
*/
|
|
42
|
+
export type NonEmptyArray<T> = [T, ...Array<T>];
|
|
30
43
|
/**
|
|
31
44
|
* Utility type for a Transaction with at least one mutation
|
|
32
45
|
* This is used internally by the Transaction.commit method
|
|
33
46
|
*/
|
|
34
|
-
export type TransactionWithMutations<T extends object = Record<string, unknown>> = Transaction & {
|
|
35
|
-
mutations:
|
|
47
|
+
export type TransactionWithMutations<T extends object = Record<string, unknown>> = Transaction<T> & {
|
|
48
|
+
mutations: NonEmptyArray<PendingMutation<T>>;
|
|
36
49
|
};
|
|
37
|
-
export interface TransactionConfig {
|
|
50
|
+
export interface TransactionConfig<T extends object = Record<string, unknown>> {
|
|
38
51
|
/** Unique identifier for the transaction */
|
|
39
52
|
id?: string;
|
|
40
53
|
autoCommit?: boolean;
|
|
41
|
-
mutationFn: MutationFn
|
|
54
|
+
mutationFn: MutationFn<T>;
|
|
42
55
|
/** Custom metadata to associate with the transaction */
|
|
43
56
|
metadata?: Record<string, unknown>;
|
|
44
57
|
}
|
|
@@ -48,9 +61,9 @@ type Value<TExtensions = never> = string | number | boolean | bigint | null | TE
|
|
|
48
61
|
};
|
|
49
62
|
export type Row<TExtensions = never> = Record<string, Value<TExtensions>>;
|
|
50
63
|
export type OperationType = `insert` | `update` | `delete`;
|
|
51
|
-
export interface SyncConfig<T extends object = Record<string, unknown
|
|
64
|
+
export interface SyncConfig<T extends object = Record<string, unknown>, TKey extends string | number = string | number> {
|
|
52
65
|
sync: (params: {
|
|
53
|
-
collection: Collection<T>;
|
|
66
|
+
collection: Collection<T, TKey>;
|
|
54
67
|
begin: () => void;
|
|
55
68
|
write: (message: Omit<ChangeMessage<T>, `key`>) => void;
|
|
56
69
|
commit: () => void;
|
|
@@ -61,8 +74,8 @@ export interface SyncConfig<T extends object = Record<string, unknown>> {
|
|
|
61
74
|
*/
|
|
62
75
|
getSyncMetadata?: () => Record<string, unknown>;
|
|
63
76
|
}
|
|
64
|
-
export interface ChangeMessage<T extends object = Record<string, unknown
|
|
65
|
-
key:
|
|
77
|
+
export interface ChangeMessage<T extends object = Record<string, unknown>, TKey extends string | number = string | number> {
|
|
78
|
+
key: TKey;
|
|
66
79
|
value: T;
|
|
67
80
|
previousValue?: T;
|
|
68
81
|
type: OperationType;
|
|
@@ -93,9 +106,9 @@ export interface OperationConfig {
|
|
|
93
106
|
export interface InsertConfig {
|
|
94
107
|
metadata?: Record<string, unknown>;
|
|
95
108
|
}
|
|
96
|
-
export interface CollectionConfig<T extends object = Record<string, unknown
|
|
109
|
+
export interface CollectionConfig<T extends object = Record<string, unknown>, TKey extends string | number = string | number> {
|
|
97
110
|
id?: string;
|
|
98
|
-
sync: SyncConfig<T>;
|
|
111
|
+
sync: SyncConfig<T, TKey>;
|
|
99
112
|
schema?: StandardSchema<T>;
|
|
100
113
|
/**
|
|
101
114
|
* Function to extract the ID from an object
|
|
@@ -104,27 +117,27 @@ export interface CollectionConfig<T extends object = Record<string, unknown>> {
|
|
|
104
117
|
* @returns The ID string for the item
|
|
105
118
|
* @example
|
|
106
119
|
* // For a collection with a 'uuid' field as the primary key
|
|
107
|
-
*
|
|
120
|
+
* getKey: (item) => item.uuid
|
|
108
121
|
*/
|
|
109
|
-
|
|
122
|
+
getKey: (item: T) => TKey;
|
|
110
123
|
/**
|
|
111
124
|
* Optional asynchronous handler function called before an insert operation
|
|
112
125
|
* @param params Object containing transaction and mutation information
|
|
113
126
|
* @returns Promise resolving to any value
|
|
114
127
|
*/
|
|
115
|
-
onInsert?: MutationFn
|
|
128
|
+
onInsert?: MutationFn<T>;
|
|
116
129
|
/**
|
|
117
130
|
* Optional asynchronous handler function called before an update operation
|
|
118
131
|
* @param params Object containing transaction and mutation information
|
|
119
132
|
* @returns Promise resolving to any value
|
|
120
133
|
*/
|
|
121
|
-
onUpdate?: MutationFn
|
|
134
|
+
onUpdate?: MutationFn<T>;
|
|
122
135
|
/**
|
|
123
136
|
* Optional asynchronous handler function called before a delete operation
|
|
124
137
|
* @param params Object containing transaction and mutation information
|
|
125
138
|
* @returns Promise resolving to any value
|
|
126
139
|
*/
|
|
127
|
-
onDelete?: MutationFn
|
|
140
|
+
onDelete?: MutationFn<T>;
|
|
128
141
|
}
|
|
129
142
|
export type ChangesPayload<T extends object = Record<string, unknown>> = Array<ChangeMessage<T>>;
|
|
130
143
|
/**
|
|
@@ -151,3 +164,4 @@ export type KeyedNamespacedRow = [unknown, NamespacedRow];
|
|
|
151
164
|
* a `select` clause.
|
|
152
165
|
*/
|
|
153
166
|
export type NamespacedAndKeyedStream = IStreamBuilder<KeyedNamespacedRow>;
|
|
167
|
+
export type ChangeListener<T extends object = Record<string, unknown>, TKey extends string | number = string | number> = (changes: Array<ChangeMessage<T, TKey>>) => void;
|
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.8",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@electric-sql/d2ts": "^0.1.
|
|
6
|
+
"@electric-sql/d2ts": "^0.1.7",
|
|
7
7
|
"@standard-schema/spec": "^1.0.0",
|
|
8
8
|
"@tanstack/store": "^0.7.0"
|
|
9
9
|
},
|