@tanstack/db 0.0.4 → 0.0.6
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 +182 -113
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +43 -15
- 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 +35 -20
- package/dist/cjs/query/evaluators.cjs.map +1 -1
- package/dist/cjs/query/evaluators.d.cts +8 -3
- 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 +22 -29
- package/dist/cjs/query/query-builder.cjs.map +1 -1
- package/dist/cjs/query/query-builder.d.cts +16 -10
- package/dist/cjs/query/schema.d.cts +12 -11
- package/dist/cjs/query/select.cjs +47 -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 +20 -9
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +66 -7
- package/dist/esm/collection.d.ts +43 -15
- package/dist/esm/collection.js +183 -114
- 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 +8 -3
- package/dist/esm/query/evaluators.js +36 -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 +16 -10
- package/dist/esm/query/query-builder.js +22 -29
- package/dist/esm/query/query-builder.js.map +1 -1
- package/dist/esm/query/schema.d.ts +12 -11
- package/dist/esm/query/select.d.ts +2 -2
- package/dist/esm/query/select.js +48 -25
- package/dist/esm/query/select.js.map +1 -1
- package/dist/esm/query/types.d.ts +1 -0
- package/dist/esm/transactions.js +20 -9
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +66 -7
- package/package.json +2 -2
- package/src/collection.ts +286 -146
- package/src/proxy.ts +141 -358
- package/src/query/compiled-query.ts +30 -15
- package/src/query/evaluators.ts +49 -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 +49 -46
- package/src/query/schema.ts +18 -15
- package/src/query/select.ts +68 -33
- package/src/query/types.ts +1 -0
- package/src/transactions.ts +30 -14
- package/src/types.ts +76 -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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Collection } from '../collection.cjs';
|
|
2
|
-
import { Comparator, Condition, Limit, LiteralValue, Offset, OrderBy, Query, Select } from './schema.js';
|
|
2
|
+
import { Comparator, Condition, Limit, LiteralValue, Offset, OrderBy, Query, Select, WhereCallback } from './schema.js';
|
|
3
3
|
import { Context, Flatten, InferResultTypeFromSelectTuple, Input, InputReference, PropertyReference, PropertyReferenceString, RemoveIndexSignature, Schema } from './types.js';
|
|
4
4
|
type CollectionRef = {
|
|
5
5
|
[K: string]: Collection<any>;
|
|
@@ -44,8 +44,9 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
44
44
|
/**
|
|
45
45
|
* Specify what columns to select.
|
|
46
46
|
* Overwrites any previous select clause.
|
|
47
|
+
* Also supports callback functions that receive the row context and return selected data.
|
|
47
48
|
*
|
|
48
|
-
* @param selects The columns to select
|
|
49
|
+
* @param selects The columns to select (can include callbacks)
|
|
49
50
|
* @returns A new QueryBuilder with the select clause set
|
|
50
51
|
*/
|
|
51
52
|
select<TSelects extends Array<Select<TContext>>>(this: QueryBuilder<TContext>, ...selects: TSelects): QueryBuilder<Flatten<Omit<TContext, `result`> & {
|
|
@@ -59,6 +60,10 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
59
60
|
* Add a where clause with a complete condition object.
|
|
60
61
|
*/
|
|
61
62
|
where(condition: Condition<TContext>): QueryBuilder<TContext>;
|
|
63
|
+
/**
|
|
64
|
+
* Add a where clause with a callback function.
|
|
65
|
+
*/
|
|
66
|
+
where(callback: WhereCallback<TContext>): QueryBuilder<TContext>;
|
|
62
67
|
/**
|
|
63
68
|
* Add a having clause comparing two values.
|
|
64
69
|
* For filtering results after they have been grouped.
|
|
@@ -69,6 +74,11 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
69
74
|
* For filtering results after they have been grouped.
|
|
70
75
|
*/
|
|
71
76
|
having(condition: Condition<TContext>): QueryBuilder<TContext>;
|
|
77
|
+
/**
|
|
78
|
+
* Add a having clause with a callback function.
|
|
79
|
+
* For filtering results after they have been grouped.
|
|
80
|
+
*/
|
|
81
|
+
having(callback: WhereCallback<TContext>): QueryBuilder<TContext>;
|
|
72
82
|
/**
|
|
73
83
|
* Add a join clause to the query using a CollectionRef.
|
|
74
84
|
*/
|
|
@@ -91,6 +101,7 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
91
101
|
schema: TContext[`schema`] & {
|
|
92
102
|
[K in keyof TCollectionRef & string]: RemoveIndexSignature<(TCollectionRef[keyof TCollectionRef] extends Collection<infer T> ? T : never) & Input>;
|
|
93
103
|
};
|
|
104
|
+
hasJoin: true;
|
|
94
105
|
}>>;
|
|
95
106
|
/**
|
|
96
107
|
* Add a join clause to the query without specifying an alias.
|
|
@@ -118,6 +129,7 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
118
129
|
schema: TContext[`schema`] & {
|
|
119
130
|
[K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>;
|
|
120
131
|
};
|
|
132
|
+
hasJoin: true;
|
|
121
133
|
}>>;
|
|
122
134
|
/**
|
|
123
135
|
* Add a join clause to the query with a specified alias.
|
|
@@ -145,6 +157,7 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
145
157
|
schema: TContext[`schema`] & {
|
|
146
158
|
[K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>;
|
|
147
159
|
};
|
|
160
|
+
hasJoin: true;
|
|
148
161
|
}>>;
|
|
149
162
|
private joinCollectionRef;
|
|
150
163
|
private joinInputReference;
|
|
@@ -170,13 +183,6 @@ export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
170
183
|
* @returns A new QueryBuilder with the offset set
|
|
171
184
|
*/
|
|
172
185
|
offset(offset: Offset<TContext>): QueryBuilder<TContext>;
|
|
173
|
-
/**
|
|
174
|
-
* Specify which column(s) to use as keys in the output keyed stream.
|
|
175
|
-
*
|
|
176
|
-
* @param keyBy The column(s) to use as keys
|
|
177
|
-
* @returns A new QueryBuilder with the keyBy clause set
|
|
178
|
-
*/
|
|
179
|
-
keyBy(keyBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>): QueryBuilder<TContext>;
|
|
180
186
|
/**
|
|
181
187
|
* Add a groupBy clause to group the results by one or more columns.
|
|
182
188
|
*
|
|
@@ -212,7 +218,7 @@ export declare function queryBuilder<TBaseSchema extends Schema = {}>(): Initial
|
|
|
212
218
|
baseSchema: TBaseSchema;
|
|
213
219
|
schema: {};
|
|
214
220
|
}>;
|
|
215
|
-
export type ResultsFromContext<TContext extends Context<Schema>> = Flatten<TContext[`result`] extends object ? TContext[`result`] : TContext[`
|
|
221
|
+
export type ResultsFromContext<TContext extends Context<Schema>> = Flatten<TContext[`result`] extends object ? TContext[`result`] : TContext[`hasJoin`] extends true ? TContext[`schema`] : TContext[`default`] extends keyof TContext[`schema`] ? TContext[`schema`][TContext[`default`]] : never>;
|
|
216
222
|
export type ResultFromQueryBuilder<TQueryBuilder> = Flatten<TQueryBuilder extends QueryBuilder<infer C> ? C extends {
|
|
217
223
|
result: infer R;
|
|
218
224
|
} ? R : never : never>;
|
|
@@ -51,7 +51,6 @@ export interface JoinClause<TContext extends Context = Context> {
|
|
|
51
51
|
from: string;
|
|
52
52
|
as?: string;
|
|
53
53
|
on: Condition<TContext>;
|
|
54
|
-
where?: Condition<TContext>;
|
|
55
54
|
}
|
|
56
55
|
export type OrderBy<TContext extends Context = Context> = PropertyReferenceString<TContext> | {
|
|
57
56
|
[column in PropertyReferenceString<TContext>]?: `asc` | `desc`;
|
|
@@ -60,31 +59,36 @@ export type OrderBy<TContext extends Context = Context> = PropertyReferenceStrin
|
|
|
60
59
|
}>;
|
|
61
60
|
export type Select<TContext extends Context = Context> = PropertyReferenceString<TContext> | {
|
|
62
61
|
[alias: string]: PropertyReference<TContext> | FunctionCall<TContext> | AggregateFunctionCall<TContext>;
|
|
63
|
-
} | WildcardReferenceString<TContext>;
|
|
62
|
+
} | WildcardReferenceString<TContext> | SelectCallback<TContext>;
|
|
63
|
+
export type SelectCallback<TContext extends Context = Context> = (context: TContext extends {
|
|
64
|
+
schema: infer S;
|
|
65
|
+
} ? S : any) => any;
|
|
64
66
|
export type As<TContext extends Context = Context> = string;
|
|
65
67
|
export type From<TContext extends Context = Context> = InputReference<{
|
|
66
68
|
baseSchema: TContext[`baseSchema`];
|
|
67
69
|
schema: TContext[`baseSchema`];
|
|
68
70
|
}>;
|
|
69
|
-
export type
|
|
71
|
+
export type WhereCallback<TContext extends Context = Context> = (context: TContext extends {
|
|
72
|
+
schema: infer S;
|
|
73
|
+
} ? S : any) => boolean;
|
|
74
|
+
export type Where<TContext extends Context = Context> = Array<Condition<TContext> | WhereCallback<TContext>>;
|
|
75
|
+
export type Having<TContext extends Context = Context> = Where<TContext>;
|
|
70
76
|
export type GroupBy<TContext extends Context = Context> = PropertyReference<TContext> | Array<PropertyReference<TContext>>;
|
|
71
|
-
export type Having<TContext extends Context = Context> = Condition<TContext>;
|
|
72
77
|
export type Limit<TContext extends Context = Context> = number;
|
|
73
78
|
export type Offset<TContext extends Context = Context> = number;
|
|
74
79
|
export interface BaseQuery<TContext extends Context = Context> {
|
|
75
|
-
select
|
|
80
|
+
select?: Array<Select<TContext>>;
|
|
76
81
|
as?: As<TContext>;
|
|
77
82
|
from: From<TContext>;
|
|
78
83
|
join?: Array<JoinClause<TContext>>;
|
|
79
|
-
where?:
|
|
84
|
+
where?: Where<TContext>;
|
|
80
85
|
groupBy?: GroupBy<TContext>;
|
|
81
|
-
having?:
|
|
86
|
+
having?: Having<TContext>;
|
|
82
87
|
orderBy?: OrderBy<TContext>;
|
|
83
88
|
limit?: Limit<TContext>;
|
|
84
89
|
offset?: Offset<TContext>;
|
|
85
90
|
}
|
|
86
91
|
export interface Query<TContext extends Context = Context> extends BaseQuery<TContext> {
|
|
87
|
-
keyBy?: PropertyReference<TContext> | Array<PropertyReference<TContext>>;
|
|
88
92
|
with?: Array<WithQuery<TContext>>;
|
|
89
93
|
collections?: {
|
|
90
94
|
[K: string]: Collection<any>;
|
|
@@ -93,6 +97,3 @@ export interface Query<TContext extends Context = Context> extends BaseQuery<TCo
|
|
|
93
97
|
export interface WithQuery<TContext extends Context = Context> extends BaseQuery<TContext> {
|
|
94
98
|
as: string;
|
|
95
99
|
}
|
|
96
|
-
export interface KeyedQuery<TContext extends Context = Context> extends Query<TContext> {
|
|
97
|
-
keyBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>;
|
|
98
|
-
}
|
|
@@ -4,18 +4,36 @@ 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((
|
|
7
|
+
d2ts.map(([key, namespacedRow]) => {
|
|
8
8
|
const result = {};
|
|
9
|
-
const isGroupedResult = query.groupBy && Object.keys(
|
|
10
|
-
(
|
|
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) {
|
|
16
|
+
if (typeof item === `function`) {
|
|
17
|
+
const callback = item;
|
|
18
|
+
const callbackResult = callback(namespacedRow);
|
|
19
|
+
if (callbackResult && typeof callbackResult === `object` && !Array.isArray(callbackResult)) {
|
|
20
|
+
Object.assign(result, callbackResult);
|
|
21
|
+
} else {
|
|
22
|
+
console.warn(
|
|
23
|
+
`SelectCallback returned a non-object value. SelectCallbacks should return objects with key-value pairs.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
13
28
|
if (typeof item === `string`) {
|
|
14
29
|
if (item === `@*`) {
|
|
15
30
|
if (isGroupedResult) {
|
|
16
|
-
Object.assign(result,
|
|
31
|
+
Object.assign(result, namespacedRow);
|
|
17
32
|
} else {
|
|
18
|
-
Object.assign(
|
|
33
|
+
Object.assign(
|
|
34
|
+
result,
|
|
35
|
+
extractAllColumnsFromAllTables(namespacedRow)
|
|
36
|
+
);
|
|
19
37
|
}
|
|
20
38
|
continue;
|
|
21
39
|
}
|
|
@@ -26,7 +44,7 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
|
26
44
|
} else {
|
|
27
45
|
Object.assign(
|
|
28
46
|
result,
|
|
29
|
-
extractAllColumnsFromTable(
|
|
47
|
+
extractAllColumnsFromTable(namespacedRow, tableAlias)
|
|
30
48
|
);
|
|
31
49
|
}
|
|
32
50
|
continue;
|
|
@@ -34,11 +52,11 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
|
34
52
|
if (item.startsWith(`@`)) {
|
|
35
53
|
const columnRef = item.substring(1);
|
|
36
54
|
const alias = columnRef;
|
|
37
|
-
if (isGroupedResult && columnRef in
|
|
38
|
-
result[alias] =
|
|
55
|
+
if (isGroupedResult && columnRef in namespacedRow) {
|
|
56
|
+
result[alias] = namespacedRow[columnRef];
|
|
39
57
|
} else {
|
|
40
|
-
result[alias] = extractors.
|
|
41
|
-
|
|
58
|
+
result[alias] = extractors.extractValueFromNamespacedRow(
|
|
59
|
+
namespacedRow,
|
|
42
60
|
columnRef,
|
|
43
61
|
mainTableAlias,
|
|
44
62
|
void 0
|
|
@@ -54,22 +72,24 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
|
54
72
|
for (const [alias, expr] of Object.entries(item)) {
|
|
55
73
|
if (typeof expr === `string` && expr.startsWith(`@`)) {
|
|
56
74
|
const columnRef = expr.substring(1);
|
|
57
|
-
if (isGroupedResult && columnRef in
|
|
58
|
-
result[alias] =
|
|
75
|
+
if (isGroupedResult && columnRef in namespacedRow) {
|
|
76
|
+
result[alias] = namespacedRow[columnRef];
|
|
59
77
|
} else {
|
|
60
|
-
result[alias] = extractors.
|
|
61
|
-
|
|
78
|
+
result[alias] = extractors.extractValueFromNamespacedRow(
|
|
79
|
+
namespacedRow,
|
|
62
80
|
columnRef,
|
|
63
81
|
mainTableAlias,
|
|
64
82
|
void 0
|
|
65
83
|
);
|
|
66
84
|
}
|
|
67
85
|
} else if (typeof expr === `object`) {
|
|
68
|
-
if (isGroupedResult && alias in
|
|
69
|
-
result[alias] =
|
|
86
|
+
if (isGroupedResult && alias in namespacedRow) {
|
|
87
|
+
result[alias] = namespacedRow[alias];
|
|
88
|
+
} else if (expr.ORDER_INDEX) {
|
|
89
|
+
result[alias] = namespacedRow[mainTableAlias][alias];
|
|
70
90
|
} else {
|
|
71
|
-
result[alias] = extractors.
|
|
72
|
-
|
|
91
|
+
result[alias] = extractors.evaluateOperandOnNamespacedRow(
|
|
92
|
+
namespacedRow,
|
|
73
93
|
expr,
|
|
74
94
|
mainTableAlias,
|
|
75
95
|
void 0
|
|
@@ -79,22 +99,25 @@ function processSelect(pipeline, query, mainTableAlias, inputs) {
|
|
|
79
99
|
}
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
|
-
return result;
|
|
102
|
+
return [key, result];
|
|
83
103
|
})
|
|
84
104
|
);
|
|
85
105
|
}
|
|
86
|
-
function extractAllColumnsFromAllTables(
|
|
106
|
+
function extractAllColumnsFromAllTables(namespacedRow) {
|
|
87
107
|
const result = {};
|
|
88
|
-
for (const [tableAlias, tableData] of Object.entries(
|
|
108
|
+
for (const [tableAlias, tableData] of Object.entries(namespacedRow)) {
|
|
89
109
|
if (tableData && typeof tableData === `object`) {
|
|
90
|
-
Object.assign(
|
|
110
|
+
Object.assign(
|
|
111
|
+
result,
|
|
112
|
+
extractAllColumnsFromTable(namespacedRow, tableAlias)
|
|
113
|
+
);
|
|
91
114
|
}
|
|
92
115
|
}
|
|
93
116
|
return result;
|
|
94
117
|
}
|
|
95
|
-
function extractAllColumnsFromTable(
|
|
118
|
+
function extractAllColumnsFromTable(namespacedRow, tableAlias) {
|
|
96
119
|
const result = {};
|
|
97
|
-
const tableData =
|
|
120
|
+
const tableData = namespacedRow[tableAlias];
|
|
98
121
|
if (!tableData || typeof tableData !== `object`) {
|
|
99
122
|
return result;
|
|
100
123
|
}
|
|
@@ -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, SelectCallback } from \"./schema\"\nimport type { KeyedStream, NamespacedAndKeyedStream } from \"../types\"\n\nexport function processSelect(\n pipeline: NamespacedAndKeyedStream,\n query: Query,\n mainTableAlias: string,\n inputs: Record<string, KeyedStream>\n): KeyedStream {\n return pipeline.pipe(\n map(([key, namespacedRow]) => {\n const result: Record<string, unknown> = {}\n\n // Check if this is a grouped result (has no nested table structure)\n // If it's a grouped result, we need to handle it differently\n const isGroupedResult =\n query.groupBy &&\n Object.keys(namespacedRow).some(\n (namespaceKey) =>\n !Object.keys(inputs).includes(namespaceKey) &&\n typeof namespacedRow[namespaceKey] !== `object`\n )\n\n if (!query.select) {\n throw new Error(`Cannot process missing SELECT clause`)\n }\n\n for (const item of query.select) {\n // Handle callback functions\n if (typeof item === `function`) {\n const callback = item as SelectCallback\n const callbackResult = callback(namespacedRow)\n\n // If the callback returns an object, merge its properties into the result\n if (\n callbackResult &&\n typeof callbackResult === `object` &&\n !Array.isArray(callbackResult)\n ) {\n Object.assign(result, callbackResult)\n } else {\n // If the callback returns a primitive value, we can't merge it\n // This would need a specific key, but since we don't have one, we'll skip it\n // In practice, select callbacks should return objects with keys\n console.warn(\n `SelectCallback returned a non-object value. SelectCallbacks should return objects with key-value pairs.`\n )\n }\n continue\n }\n\n if (typeof item === `string`) {\n // Handle wildcard select - all columns from all tables\n if ((item as string) === `@*`) {\n // For grouped results, just return the row as is\n if (isGroupedResult) {\n Object.assign(result, namespacedRow)\n } else {\n // Extract all columns from all tables\n Object.assign(\n result,\n extractAllColumnsFromAllTables(namespacedRow)\n )\n }\n continue\n }\n\n // Handle @table.* syntax - all columns from a specific table\n if (\n (item as string).startsWith(`@`) &&\n (item as string).endsWith(`.*`)\n ) {\n const tableAlias = (item as string).slice(1, -2) // Remove the '@' and '.*' parts\n\n // For grouped results, check if we have columns from this table\n if (isGroupedResult) {\n // In grouped results, we don't have the nested structure anymore\n // So we can't extract by table. Just continue to the next item.\n continue\n } else {\n // Extract all columns from the specified table\n Object.assign(\n result,\n extractAllColumnsFromTable(namespacedRow, tableAlias)\n )\n }\n continue\n }\n\n // Handle simple column references like \"@table.column\" or \"@column\"\n if ((item as string).startsWith(`@`)) {\n const columnRef = (item as string).substring(1)\n const alias = columnRef\n\n // For grouped results, check if the column is directly in the row first\n if (isGroupedResult && columnRef in namespacedRow) {\n result[alias] = namespacedRow[columnRef]\n } else {\n // Extract the value from the nested structure\n result[alias] = extractValueFromNamespacedRow(\n namespacedRow,\n columnRef,\n mainTableAlias,\n undefined\n )\n }\n\n // If the alias contains a dot (table.column),\n // use just the column part as the field name\n if (alias.includes(`.`)) {\n const columnName = alias.split(`.`)[1]\n result[columnName!] = result[alias]\n delete result[alias]\n }\n }\n } else {\n // Handle aliased columns like { alias: \"@column_name\" }\n for (const [alias, expr] of Object.entries(item)) {\n if (typeof expr === `string` && (expr as string).startsWith(`@`)) {\n const columnRef = (expr as string).substring(1)\n\n // For grouped results, check if the column is directly in the row first\n if (isGroupedResult && columnRef in namespacedRow) {\n result[alias] = namespacedRow[columnRef]\n } else {\n // Extract the value from the nested structure\n result[alias] = extractValueFromNamespacedRow(\n namespacedRow,\n columnRef,\n mainTableAlias,\n undefined\n )\n }\n } else if (typeof expr === `object`) {\n // For grouped results, the aggregate results are already in the row\n if (isGroupedResult && alias in namespacedRow) {\n result[alias] = namespacedRow[alias]\n } else if ((expr as { ORDER_INDEX: unknown }).ORDER_INDEX) {\n result[alias] = namespacedRow[mainTableAlias]![alias]\n } else {\n // This might be a function call\n result[alias] = evaluateOperandOnNamespacedRow(\n namespacedRow,\n expr as ConditionOperand,\n mainTableAlias,\n undefined\n )\n }\n }\n }\n }\n }\n\n return [key, result] as [string, typeof result]\n })\n )\n}\n\n// Helper function to extract all columns from all tables in a nested row\nfunction extractAllColumnsFromAllTables(\n namespacedRow: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n // Process each table in the nested row\n for (const [tableAlias, tableData] of Object.entries(namespacedRow)) {\n if (tableData && typeof tableData === `object`) {\n // Add all columns from this table to the result\n // If there are column name conflicts, the last table's columns will overwrite previous ones\n Object.assign(\n result,\n extractAllColumnsFromTable(namespacedRow, tableAlias)\n )\n }\n }\n\n return result\n}\n\n// Helper function to extract all columns from a table in a nested row\nfunction extractAllColumnsFromTable(\n namespacedRow: Record<string, unknown>,\n tableAlias: string\n): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n // Get the table data\n const tableData = namespacedRow[tableAlias] as\n | Record<string, unknown>\n | null\n | undefined\n\n if (!tableData || typeof tableData !== `object`) {\n return result\n }\n\n // Add all columns from the table to the result\n for (const [columnName, value] of Object.entries(tableData)) {\n result[columnName] = value\n }\n\n return result\n}\n"],"names":["map","extractValueFromNamespacedRow","evaluateOperandOnNamespacedRow"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { IStreamBuilder } from '@electric-sql/d2ts';
|
|
2
1
|
import { Query } from './schema.cjs';
|
|
3
|
-
|
|
2
|
+
import { KeyedStream, NamespacedAndKeyedStream } from '../types.cjs';
|
|
3
|
+
export declare function processSelect(pipeline: NamespacedAndKeyedStream, query: Query, mainTableAlias: string, inputs: Record<string, KeyedStream>): KeyedStream;
|
|
@@ -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
|
|
91
|
-
this.mutations.forEach((m) =>
|
|
92
|
-
transactions
|
|
93
|
-
|
|
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
|
|
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`) {
|
|
@@ -115,9 +124,11 @@ class Transaction {
|
|
|
115
124
|
this.setState(`persisting`);
|
|
116
125
|
if (this.mutations.length === 0) {
|
|
117
126
|
this.setState(`completed`);
|
|
127
|
+
return this;
|
|
118
128
|
}
|
|
119
129
|
try {
|
|
120
|
-
|
|
130
|
+
const transactionWithMutations = this;
|
|
131
|
+
await this.mutationFn({ transaction: transactionWithMutations });
|
|
121
132
|
this.setState(`completed`);
|
|
122
133
|
this.touchCollection();
|
|
123
134
|
this.isPersisted.resolve(this);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactions.cjs","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n PendingMutation,\n TransactionConfig,\n TransactionState,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction> = []\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.cjs","sources":["../../src/transactions.ts"],"sourcesContent":["import { createDeferred } from \"./deferred\"\nimport type { Deferred } from \"./deferred\"\nimport type {\n PendingMutation,\n TransactionConfig,\n TransactionState,\n TransactionWithMutations,\n} from \"./types\"\n\nfunction generateUUID() {\n // Check if crypto.randomUUID is available (modern browsers and Node.js 15+)\n if (\n typeof crypto !== `undefined` &&\n typeof crypto.randomUUID === `function`\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback implementation for older environments\n return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {\n const r = (Math.random() * 16) | 0\n const v = c === `x` ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nconst transactions: Array<Transaction> = []\nlet transactionStack: Array<Transaction> = []\n\nexport function createTransaction(config: TransactionConfig): Transaction {\n if (typeof config.mutationFn === `undefined`) {\n throw `mutationFn is required when creating a transaction`\n }\n\n let transactionId = config.id\n if (!transactionId) {\n transactionId = generateUUID()\n }\n const newTransaction = new Transaction({ ...config, id: transactionId })\n\n transactions.push(newTransaction)\n\n return newTransaction\n}\n\nexport function getActiveTransaction(): Transaction | undefined {\n if (transactionStack.length > 0) {\n return transactionStack.slice(-1)[0]\n } else {\n return undefined\n }\n}\n\nfunction registerTransaction(tx: Transaction) {\n transactionStack.push(tx)\n}\n\nfunction unregisterTransaction(tx: Transaction) {\n transactionStack = transactionStack.filter((t) => t.id !== tx.id)\n}\n\nfunction removeFromPendingList(tx: Transaction) {\n const index = transactions.findIndex((t) => t.id === tx.id)\n if (index !== -1) {\n transactions.splice(index, 1)\n }\n}\n\nexport class Transaction {\n public id: string\n public state: TransactionState\n public mutationFn\n public mutations: Array<PendingMutation<any>>\n public isPersisted: Deferred<Transaction>\n public autoCommit: boolean\n public createdAt: Date\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig) {\n this.id = config.id!\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.metadata = config.metadata ?? {}\n }\n\n setState(newState: TransactionState) {\n this.state = newState\n\n if (newState === `completed` || newState === `failed`) {\n removeFromPendingList(this)\n }\n }\n\n mutate(callback: () => void): Transaction {\n if (this.state !== `pending`) {\n throw `You can no longer call .mutate() as the transaction is no longer pending`\n }\n\n registerTransaction(this)\n try {\n callback()\n } finally {\n unregisterTransaction(this)\n }\n\n if (this.autoCommit) {\n this.commit()\n }\n\n return this\n }\n\n applyMutations(mutations: Array<PendingMutation<any>>): void {\n for (const newMutation of mutations) {\n const existingIndex = this.mutations.findIndex(\n (m) => m.key === newMutation.key\n )\n\n if (existingIndex >= 0) {\n // Replace existing mutation\n this.mutations[existingIndex] = newMutation\n } else {\n // Insert new mutation\n this.mutations.push(newMutation)\n }\n }\n }\n\n rollback(config?: { isSecondaryRollback?: boolean }): Transaction {\n const isSecondaryRollback = config?.isSecondaryRollback ?? false\n if (this.state === `completed`) {\n throw `You can no longer call .rollback() as the transaction is already completed`\n }\n\n this.setState(`failed`)\n\n // See if there's any other transactions w/ mutations on the same ids\n // and roll them back as well.\n if (!isSecondaryRollback) {\n const mutationIds = new Set()\n this.mutations.forEach((m) => mutationIds.add(m.key))\n for (const t of transactions) {\n t.state === `pending` &&\n t.mutations.some((m) => mutationIds.has(m.key)) &&\n t.rollback({ isSecondaryRollback: true })\n }\n }\n\n // Reject the promise\n this.isPersisted.reject(this.error?.error)\n this.touchCollection()\n\n return this\n }\n\n // Tell collection that something has changed with the transaction\n touchCollection(): void {\n const hasCalled = new Set()\n for (const mutation of this.mutations) {\n if (!hasCalled.has(mutation.collection.id)) {\n mutation.collection.transactions.setState((state) => state)\n mutation.collection.commitPendingTransactions()\n hasCalled.add(mutation.collection.id)\n }\n }\n }\n\n async commit(): Promise<Transaction> {\n if (this.state !== `pending`) {\n throw `You can no longer call .commit() as the transaction is no longer pending`\n }\n\n this.setState(`persisting`)\n\n if (this.mutations.length === 0) {\n this.setState(`completed`)\n\n return this\n }\n\n // Run mutationFn\n try {\n // At this point we know there's at least one mutation\n // Use type assertion to tell TypeScript about this guarantee\n const transactionWithMutations =\n this as unknown as TransactionWithMutations\n await this.mutationFn({ transaction: transactionWithMutations })\n\n this.setState(`completed`)\n this.touchCollection()\n\n this.isPersisted.resolve(this)\n } catch (error) {\n // Update transaction with error information\n this.error = {\n message: error instanceof Error ? error.message : String(error),\n error: error instanceof Error ? error : new Error(String(error)),\n }\n\n // rollback the transaction\n return this.rollback()\n }\n\n return this\n }\n}\n"],"names":["createDeferred"],"mappings":"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"}
|
package/dist/cjs/types.d.cts
CHANGED
|
@@ -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:
|
|
15
|
+
key: any;
|
|
15
16
|
type: OperationType;
|
|
16
17
|
metadata: unknown;
|
|
17
18
|
syncMetadata: Record<string, unknown>;
|
|
@@ -26,6 +27,13 @@ export type MutationFnParams = {
|
|
|
26
27
|
transaction: Transaction;
|
|
27
28
|
};
|
|
28
29
|
export type MutationFn = (params: MutationFnParams) => Promise<any>;
|
|
30
|
+
/**
|
|
31
|
+
* Utility type for a Transaction with at least one mutation
|
|
32
|
+
* This is used internally by the Transaction.commit method
|
|
33
|
+
*/
|
|
34
|
+
export type TransactionWithMutations<T extends object = Record<string, unknown>> = Transaction & {
|
|
35
|
+
mutations: [PendingMutation<T>, ...Array<PendingMutation<T>>];
|
|
36
|
+
};
|
|
29
37
|
export interface TransactionConfig {
|
|
30
38
|
/** Unique identifier for the transaction */
|
|
31
39
|
id?: string;
|
|
@@ -36,7 +44,7 @@ export interface TransactionConfig {
|
|
|
36
44
|
}
|
|
37
45
|
export type { Transaction };
|
|
38
46
|
type Value<TExtensions = never> = string | number | boolean | bigint | null | TExtensions | Array<Value<TExtensions>> | {
|
|
39
|
-
[key: string]: Value<TExtensions>;
|
|
47
|
+
[key: string | number | symbol]: Value<TExtensions>;
|
|
40
48
|
};
|
|
41
49
|
export type Row<TExtensions = never> = Record<string, Value<TExtensions>>;
|
|
42
50
|
export type OperationType = `insert` | `update` | `delete`;
|
|
@@ -44,17 +52,17 @@ export interface SyncConfig<T extends object = Record<string, unknown>> {
|
|
|
44
52
|
sync: (params: {
|
|
45
53
|
collection: Collection<T>;
|
|
46
54
|
begin: () => void;
|
|
47
|
-
write: (message: ChangeMessage<T
|
|
55
|
+
write: (message: Omit<ChangeMessage<T>, `key`>) => void;
|
|
48
56
|
commit: () => void;
|
|
49
57
|
}) => void;
|
|
50
58
|
/**
|
|
51
59
|
* Get the sync metadata for insert operations
|
|
52
|
-
* @returns Record containing
|
|
60
|
+
* @returns Record containing relation information
|
|
53
61
|
*/
|
|
54
62
|
getSyncMetadata?: () => Record<string, unknown>;
|
|
55
63
|
}
|
|
56
64
|
export interface ChangeMessage<T extends object = Record<string, unknown>> {
|
|
57
|
-
key:
|
|
65
|
+
key: any;
|
|
58
66
|
value: T;
|
|
59
67
|
previousValue?: T;
|
|
60
68
|
type: OperationType;
|
|
@@ -83,12 +91,63 @@ export interface OperationConfig {
|
|
|
83
91
|
metadata?: Record<string, unknown>;
|
|
84
92
|
}
|
|
85
93
|
export interface InsertConfig {
|
|
86
|
-
key?: string | Array<string | undefined>;
|
|
87
94
|
metadata?: Record<string, unknown>;
|
|
88
95
|
}
|
|
89
96
|
export interface CollectionConfig<T extends object = Record<string, unknown>> {
|
|
90
|
-
id
|
|
97
|
+
id?: string;
|
|
91
98
|
sync: SyncConfig<T>;
|
|
92
99
|
schema?: StandardSchema<T>;
|
|
100
|
+
/**
|
|
101
|
+
* Function to extract the ID from an object
|
|
102
|
+
* This is required for update/delete operations which now only accept IDs
|
|
103
|
+
* @param item The item to extract the ID from
|
|
104
|
+
* @returns The ID string for the item
|
|
105
|
+
* @example
|
|
106
|
+
* // For a collection with a 'uuid' field as the primary key
|
|
107
|
+
* getId: (item) => item.uuid
|
|
108
|
+
*/
|
|
109
|
+
getId: (item: T) => any;
|
|
110
|
+
/**
|
|
111
|
+
* Optional asynchronous handler function called before an insert operation
|
|
112
|
+
* @param params Object containing transaction and mutation information
|
|
113
|
+
* @returns Promise resolving to any value
|
|
114
|
+
*/
|
|
115
|
+
onInsert?: MutationFn;
|
|
116
|
+
/**
|
|
117
|
+
* Optional asynchronous handler function called before an update operation
|
|
118
|
+
* @param params Object containing transaction and mutation information
|
|
119
|
+
* @returns Promise resolving to any value
|
|
120
|
+
*/
|
|
121
|
+
onUpdate?: MutationFn;
|
|
122
|
+
/**
|
|
123
|
+
* Optional asynchronous handler function called before a delete operation
|
|
124
|
+
* @param params Object containing transaction and mutation information
|
|
125
|
+
* @returns Promise resolving to any value
|
|
126
|
+
*/
|
|
127
|
+
onDelete?: MutationFn;
|
|
93
128
|
}
|
|
94
129
|
export type ChangesPayload<T extends object = Record<string, unknown>> = Array<ChangeMessage<T>>;
|
|
130
|
+
/**
|
|
131
|
+
* An input row from a collection
|
|
132
|
+
*/
|
|
133
|
+
export type InputRow = [unknown, Record<string, unknown>];
|
|
134
|
+
/**
|
|
135
|
+
* A keyed stream is a stream of rows
|
|
136
|
+
* This is used as the inputs from a collection to a query
|
|
137
|
+
*/
|
|
138
|
+
export type KeyedStream = IStreamBuilder<InputRow>;
|
|
139
|
+
/**
|
|
140
|
+
* A namespaced row is a row withing a pipeline that had each table wrapped in its alias
|
|
141
|
+
*/
|
|
142
|
+
export type NamespacedRow = Record<string, Record<string, unknown>>;
|
|
143
|
+
/**
|
|
144
|
+
* A keyed namespaced row is a row with a key and a namespaced row
|
|
145
|
+
* This is the main representation of a row in a query pipeline
|
|
146
|
+
*/
|
|
147
|
+
export type KeyedNamespacedRow = [unknown, NamespacedRow];
|
|
148
|
+
/**
|
|
149
|
+
* A namespaced and keyed stream is a stream of rows
|
|
150
|
+
* This is used throughout a query pipeline and as the output from a query without
|
|
151
|
+
* a `select` clause.
|
|
152
|
+
*/
|
|
153
|
+
export type NamespacedAndKeyedStream = IStreamBuilder<KeyedNamespacedRow>;
|