@tanstack/db 0.0.11 → 0.0.13
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/SortedMap.cjs +38 -11
- package/dist/cjs/SortedMap.cjs.map +1 -1
- package/dist/cjs/SortedMap.d.cts +10 -0
- package/dist/cjs/collection.cjs +476 -144
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +107 -32
- package/dist/cjs/index.cjs +2 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/optimistic-action.cjs +21 -0
- package/dist/cjs/optimistic-action.cjs.map +1 -0
- package/dist/cjs/optimistic-action.d.cts +39 -0
- package/dist/cjs/query/compiled-query.cjs +38 -16
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/query-builder.cjs +2 -2
- package/dist/cjs/query/query-builder.cjs.map +1 -1
- package/dist/cjs/transactions.cjs +3 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +83 -10
- package/dist/esm/SortedMap.d.ts +10 -0
- package/dist/esm/SortedMap.js +38 -11
- package/dist/esm/SortedMap.js.map +1 -1
- package/dist/esm/collection.d.ts +107 -32
- package/dist/esm/collection.js +477 -145
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +3 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/optimistic-action.d.ts +39 -0
- package/dist/esm/optimistic-action.js +21 -0
- package/dist/esm/optimistic-action.js.map +1 -0
- package/dist/esm/query/compiled-query.js +38 -16
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/query-builder.js +2 -2
- package/dist/esm/query/query-builder.js.map +1 -1
- package/dist/esm/transactions.js +3 -1
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +83 -10
- package/package.json +1 -1
- package/src/SortedMap.ts +46 -13
- package/src/collection.ts +689 -239
- package/src/index.ts +1 -0
- package/src/optimistic-action.ts +65 -0
- package/src/query/compiled-query.ts +79 -21
- package/src/query/query-builder.ts +2 -2
- package/src/transactions.ts +6 -1
- package/src/types.ts +124 -8
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CreateOptimisticActionsOptions, Transaction } from './types.cjs';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an optimistic action function that applies local optimistic updates immediately
|
|
4
|
+
* before executing the actual mutation on the server.
|
|
5
|
+
*
|
|
6
|
+
* This pattern allows for responsive UI updates while the actual mutation is in progress.
|
|
7
|
+
* The optimistic update is applied via the `onMutate` callback, and the server mutation
|
|
8
|
+
* is executed via the `mutationFn`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const addTodo = createOptimisticAction<string>({
|
|
13
|
+
* onMutate: (text) => {
|
|
14
|
+
* // Instantly applies local optimistic state
|
|
15
|
+
* todoCollection.insert({
|
|
16
|
+
* id: uuid(),
|
|
17
|
+
* text,
|
|
18
|
+
* completed: false
|
|
19
|
+
* })
|
|
20
|
+
* },
|
|
21
|
+
* mutationFn: async (text, params) => {
|
|
22
|
+
* // Persist the todo to your backend
|
|
23
|
+
* const response = await fetch('/api/todos', {
|
|
24
|
+
* method: 'POST',
|
|
25
|
+
* body: JSON.stringify({ text, completed: false }),
|
|
26
|
+
* })
|
|
27
|
+
* return response.json()
|
|
28
|
+
* }
|
|
29
|
+
* })
|
|
30
|
+
*
|
|
31
|
+
* // Usage
|
|
32
|
+
* const transaction = addTodo('New Todo Item')
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @template TVariables - The type of variables that will be passed to the action function
|
|
36
|
+
* @param options - Configuration options for the optimistic action
|
|
37
|
+
* @returns A function that accepts variables of type TVariables and returns a Transaction
|
|
38
|
+
*/
|
|
39
|
+
export declare function createOptimisticAction<TVariables = unknown>(options: CreateOptimisticActionsOptions<TVariables>): (variables: TVariables) => Transaction;
|
|
@@ -20,7 +20,12 @@ class CompiledQuery {
|
|
|
20
20
|
const inputs = Object.fromEntries(
|
|
21
21
|
Object.entries(collections).map(([key]) => [key, graph.newInput()])
|
|
22
22
|
);
|
|
23
|
-
const sync = ({
|
|
23
|
+
const sync = ({
|
|
24
|
+
begin,
|
|
25
|
+
write,
|
|
26
|
+
commit,
|
|
27
|
+
collection: collection2
|
|
28
|
+
}) => {
|
|
24
29
|
pipelineCompiler.compileQueryPipeline(
|
|
25
30
|
query,
|
|
26
31
|
inputs
|
|
@@ -44,12 +49,17 @@ class CompiledQuery {
|
|
|
44
49
|
}, /* @__PURE__ */ new Map()).forEach((changes, rawKey) => {
|
|
45
50
|
const { deletes, inserts, value } = changes;
|
|
46
51
|
const valueWithKey = { ...value, _key: rawKey };
|
|
47
|
-
if (inserts &&
|
|
52
|
+
if (inserts && deletes === 0) {
|
|
48
53
|
write({
|
|
49
54
|
value: valueWithKey,
|
|
50
55
|
type: `insert`
|
|
51
56
|
});
|
|
52
|
-
} else if (
|
|
57
|
+
} else if (
|
|
58
|
+
// Insert & update(s) (updates are a delete & insert)
|
|
59
|
+
inserts > deletes || // Just update(s) but the item is already in the collection (so
|
|
60
|
+
// was inserted previously).
|
|
61
|
+
inserts === deletes && collection2.has(valueWithKey._key)
|
|
62
|
+
) {
|
|
53
63
|
write({
|
|
54
64
|
value: valueWithKey,
|
|
55
65
|
type: `update`
|
|
@@ -59,6 +69,10 @@ class CompiledQuery {
|
|
|
59
69
|
value: valueWithKey,
|
|
60
70
|
type: `delete`
|
|
61
71
|
});
|
|
72
|
+
} else {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`This should never happen ${JSON.stringify(changes)}`
|
|
75
|
+
);
|
|
62
76
|
}
|
|
63
77
|
});
|
|
64
78
|
commit();
|
|
@@ -68,12 +82,24 @@ class CompiledQuery {
|
|
|
68
82
|
};
|
|
69
83
|
this.graph = graph;
|
|
70
84
|
this.inputs = inputs;
|
|
85
|
+
const compare = query.orderBy ? (val1, val2) => {
|
|
86
|
+
const x = val1;
|
|
87
|
+
const y = val2;
|
|
88
|
+
if (x._orderByIndex < y._orderByIndex) {
|
|
89
|
+
return -1;
|
|
90
|
+
} else if (x._orderByIndex > y._orderByIndex) {
|
|
91
|
+
return 1;
|
|
92
|
+
} else {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
} : void 0;
|
|
71
96
|
this.resultCollection = collection.createCollection({
|
|
72
|
-
id: crypto.randomUUID(),
|
|
73
|
-
// TODO: remove when we don't require any more
|
|
74
97
|
getKey: (val) => {
|
|
75
98
|
return val._key;
|
|
76
99
|
},
|
|
100
|
+
gcTime: 0,
|
|
101
|
+
startSync: true,
|
|
102
|
+
compare,
|
|
77
103
|
sync: {
|
|
78
104
|
sync
|
|
79
105
|
}
|
|
@@ -108,20 +134,16 @@ class CompiledQuery {
|
|
|
108
134
|
throw new Error(`Query is stopped`);
|
|
109
135
|
}
|
|
110
136
|
Object.entries(this.inputCollections).forEach(([key, collection2]) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
const unsubscribe = collection2.subscribeChanges(
|
|
138
|
+
(changes) => {
|
|
139
|
+
this.sendChangesToInput(key, changes, collection2.config.getKey);
|
|
140
|
+
this.runGraph();
|
|
141
|
+
},
|
|
142
|
+
{ includeInitialState: true }
|
|
115
143
|
);
|
|
116
|
-
});
|
|
117
|
-
this.runGraph();
|
|
118
|
-
Object.entries(this.inputCollections).forEach(([key, collection2]) => {
|
|
119
|
-
const unsubscribe = collection2.subscribeChanges((changes) => {
|
|
120
|
-
this.sendChangesToInput(key, changes, collection2.config.getKey);
|
|
121
|
-
this.runGraph();
|
|
122
|
-
});
|
|
123
144
|
this.unsubscribeCallbacks.push(unsubscribe);
|
|
124
145
|
});
|
|
146
|
+
this.runGraph();
|
|
125
147
|
this.state = `running`;
|
|
126
148
|
return () => {
|
|
127
149
|
this.stop();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiled-query.cjs","sources":["../../../src/query/compiled-query.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@electric-sql/d2mini\"\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/d2mini\"\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 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()\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((data) => {\n begin()\n data\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 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(new MultiSet(multiSetArray))\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.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.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":["D2","compileQueryPipeline","output","createCollection","MultiSet","collection"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compiled-query.cjs","sources":["../../../src/query/compiled-query.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@electric-sql/d2mini\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQueryPipeline } from \"./pipeline-compiler.js\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type { Collection } from \"../collection.js\"\nimport type { ChangeMessage, ResolveType, SyncConfig } from \"../types.js\"\nimport type {\n IStreamBuilder,\n MultiSetArray,\n RootStreamBuilder,\n} from \"@electric-sql/d2mini\"\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 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()\n const inputs = Object.fromEntries(\n Object.entries(collections).map(([key]) => [key, graph.newInput<any>()])\n )\n\n // Use TResults directly to ensure type compatibility\n const sync: SyncConfig<TResults>[`sync`] = ({\n begin,\n write,\n commit,\n collection,\n }) => {\n compileQueryPipeline<IStreamBuilder<[unknown, TResults]>>(\n query,\n inputs\n ).pipe(\n output((data) => {\n begin()\n data\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\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value: valueWithKey,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n collection.has(valueWithKey._key as string | number))\n ) {\n write({\n value: valueWithKey,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value: valueWithKey,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n graph.finalize()\n }\n\n this.graph = graph\n this.inputs = inputs\n\n const compare = query.orderBy\n ? (\n val1: ResolveType<\n TResults,\n StandardSchemaV1,\n Record<string, unknown>\n >,\n val2: ResolveType<TResults, StandardSchemaV1, Record<string, unknown>>\n ): number => {\n // The query builder always adds an _orderByIndex property if the results are ordered\n const x = val1 as TResults & { _orderByIndex: number }\n const y = val2 as TResults & { _orderByIndex: number }\n if (x._orderByIndex < y._orderByIndex) {\n return -1\n } else if (x._orderByIndex > y._orderByIndex) {\n return 1\n } else {\n return 0\n }\n }\n : undefined\n\n this.resultCollection = createCollection<TResults>({\n getKey: (val: unknown) => {\n return (val as any)._key\n },\n gcTime: 0,\n startSync: true,\n compare,\n sync: {\n sync: sync as unknown as (params: {\n collection: Collection<\n ResolveType<TResults, never, Record<string, unknown>>,\n string | number,\n {}\n >\n begin: () => void\n write: (\n message: Omit<\n ChangeMessage<\n ResolveType<TResults, never, Record<string, unknown>>,\n string | number\n >,\n `key`\n >\n ) => void\n commit: () => void\n }) => void,\n },\n }) as unknown as Collection<TResults, string | number, {}>\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(new MultiSet(multiSetArray))\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 // Subscribe to changes\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n const unsubscribe = collection.subscribeChanges(\n (changes) => {\n this.sendChangesToInput(key, changes, collection.config.getKey)\n this.runGraph()\n },\n { includeInitialState: true }\n )\n\n this.unsubscribeCallbacks.push(unsubscribe)\n })\n\n this.runGraph()\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":["D2","collection","compileQueryPipeline","output","createCollection","MultiSet"],"mappings":"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"}
|
|
@@ -100,7 +100,7 @@ class BaseQueryBuilder {
|
|
|
100
100
|
return select;
|
|
101
101
|
});
|
|
102
102
|
if (this._query.orderBy) {
|
|
103
|
-
validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `
|
|
103
|
+
validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `fractional` } });
|
|
104
104
|
}
|
|
105
105
|
const newBuilder = new BaseQueryBuilder(
|
|
106
106
|
this.query
|
|
@@ -228,7 +228,7 @@ class BaseQueryBuilder {
|
|
|
228
228
|
newBuilder.query.orderBy = orderBy;
|
|
229
229
|
newBuilder.query.select = [
|
|
230
230
|
...newBuilder.query.select ?? [],
|
|
231
|
-
{ _orderByIndex: { ORDER_INDEX: `
|
|
231
|
+
{ _orderByIndex: { ORDER_INDEX: `fractional` } }
|
|
232
232
|
];
|
|
233
233
|
return newBuilder;
|
|
234
234
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-builder.cjs","sources":["../../../src/query/query-builder.ts"],"sourcesContent":["import type { Collection } from \"../collection\"\nimport type {\n Comparator,\n ComparatorValue,\n Condition,\n From,\n JoinClause,\n Limit,\n LiteralValue,\n Offset,\n OrderBy,\n Query,\n Select,\n WhereCallback,\n WithQuery,\n} from \"./schema.js\"\nimport type {\n Context,\n Flatten,\n InferResultTypeFromSelectTuple,\n Input,\n InputReference,\n PropertyReference,\n PropertyReferenceString,\n RemoveIndexSignature,\n Schema,\n} from \"./types.js\"\n\ntype CollectionRef = { [K: string]: Collection<any> }\n\nexport class BaseQueryBuilder<TContext extends Context<Schema>> {\n private readonly query: Partial<Query<TContext>> = {}\n\n /**\n * Create a new QueryBuilder instance.\n */\n constructor(query: Partial<Query<TContext>> = {}) {\n this.query = query\n }\n\n from<TCollectionRef extends CollectionRef>(\n collectionRef: TCollectionRef\n ): QueryBuilder<{\n baseSchema: Flatten<\n TContext[`baseSchema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n >\n schema: Flatten<{\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }>\n default: keyof TCollectionRef & string\n }>\n\n from<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n >(\n collection: T\n ): QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n default: T\n }>\n\n from<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string,\n >(\n collection: T,\n as: TAs\n ): QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n default: TAs\n }>\n\n /**\n * Specify the collection to query from.\n * This is the first method that must be called in the chain.\n *\n * @param collection The collection name to query from\n * @param as Optional alias for the collection\n * @returns A new QueryBuilder with the from clause set\n */\n from<\n T extends\n | InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n | CollectionRef,\n TAs extends string | undefined,\n >(collection: T, as?: TAs) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof collection === `object` && collection !== null) {\n return this.fromCollectionRef(collection)\n } else if (typeof collection === `string`) {\n return this.fromInputReference(\n collection as InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n as\n )\n } else {\n throw new Error(`Invalid collection type`)\n }\n }\n\n private fromCollectionRef<TCollectionRef extends CollectionRef>(\n collectionRef: TCollectionRef\n ) {\n const keys = Object.keys(collectionRef)\n if (keys.length !== 1) {\n throw new Error(`Expected exactly one key`)\n }\n\n const key = keys[0]!\n const collection = collectionRef[key]!\n\n const newBuilder = new BaseQueryBuilder()\n Object.assign(newBuilder.query, this.query)\n newBuilder.query.from = key as From<TContext>\n newBuilder.query.collections ??= {}\n newBuilder.query.collections[key] = collection\n\n return newBuilder as unknown as QueryBuilder<{\n baseSchema: TContext[`baseSchema`] & {\n [K in keyof TCollectionRef &\n string]: (TCollectionRef[keyof TCollectionRef] extends Collection<\n infer T\n >\n ? T\n : never) &\n Input\n }\n schema: {\n [K in keyof TCollectionRef &\n string]: (TCollectionRef[keyof TCollectionRef] extends Collection<\n infer T\n >\n ? T\n : never) &\n Input\n }\n default: keyof TCollectionRef & string\n }>\n }\n\n private fromInputReference<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string | undefined,\n >(collection: T, as?: TAs) {\n const newBuilder = new BaseQueryBuilder()\n Object.assign(newBuilder.query, this.query)\n newBuilder.query.from = collection as From<TContext>\n if (as) {\n newBuilder.query.as = as\n }\n\n // Calculate the result type without deep nesting\n type ResultSchema = TAs extends undefined\n ? { [K in T]: TContext[`baseSchema`][T] }\n : { [K in string & TAs]: TContext[`baseSchema`][T] }\n\n type ResultDefault = TAs extends undefined ? T : string & TAs\n\n // Use simpler type assertion to avoid excessive depth\n return newBuilder as unknown as QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: ResultSchema\n default: ResultDefault\n }>\n }\n\n /**\n * Specify what columns to select.\n * Overwrites any previous select clause.\n * Also supports callback functions that receive the row context and return selected data.\n *\n * @param selects The columns to select (can include callbacks)\n * @returns A new QueryBuilder with the select clause set\n */\n select<TSelects extends Array<Select<TContext>>>(\n this: QueryBuilder<TContext>,\n ...selects: TSelects\n ) {\n // Validate function calls in the selects\n // Need to use a type assertion to bypass deep recursive type checking\n const validatedSelects = selects.map((select) => {\n // If the select is an object with aliases, validate each value\n if (\n typeof select === `object` &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n select !== null &&\n !Array.isArray(select)\n ) {\n const result: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(select)) {\n // If it's a function call (object with a single key that is an allowed function name)\n if (\n typeof value === `object` &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const keys = Object.keys(value)\n if (keys.length === 1) {\n const funcName = keys[0]!\n // List of allowed function names from AllowedFunctionName\n const allowedFunctions = [\n `SUM`,\n `COUNT`,\n `AVG`,\n `MIN`,\n `MAX`,\n `DATE`,\n `JSON_EXTRACT`,\n `JSON_EXTRACT_PATH`,\n `UPPER`,\n `LOWER`,\n `COALESCE`,\n `CONCAT`,\n `LENGTH`,\n `ORDER_INDEX`,\n ]\n\n if (!allowedFunctions.includes(funcName)) {\n console.warn(\n `Unsupported function: ${funcName}. Expected one of: ${allowedFunctions.join(`, `)}`\n )\n }\n }\n }\n\n result[key] = value\n }\n\n return result\n }\n\n return select\n })\n\n // Ensure we have an orderByIndex in the select if we have an orderBy\n // This is required if select is called after orderBy\n if (this._query.orderBy) {\n validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `numeric` } })\n }\n\n const newBuilder = new BaseQueryBuilder<TContext>(\n (this as BaseQueryBuilder<TContext>).query\n )\n newBuilder.query.select = validatedSelects as Array<Select<TContext>>\n\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `result`> & {\n result: InferResultTypeFromSelectTuple<TContext, TSelects>\n }\n >\n >\n }\n\n /**\n * Add a where clause comparing two values.\n */\n where<T extends Comparator>(\n left: PropertyReferenceString<TContext> | LiteralValue,\n operator: T,\n right: ComparatorValue<T, TContext>\n ): QueryBuilder<TContext>\n\n /**\n * Add a where clause with a complete condition object.\n */\n where(condition: Condition<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a where clause with a callback function.\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a where clause to filter the results.\n * Can be called multiple times to add AND conditions.\n * Also supports callback functions that receive the row context.\n *\n * @param leftOrConditionOrCallback The left operand, complete condition, or callback function\n * @param operator Optional comparison operator\n * @param right Optional right operand\n * @returns A new QueryBuilder with the where clause added\n */\n where(\n leftOrConditionOrCallback: any,\n operator?: any,\n right?: any\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n // Use simplistic approach to avoid deep type errors\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n let condition: any\n\n // Determine if this is a callback, complete condition, or individual parts\n if (typeof leftOrConditionOrCallback === `function`) {\n // It's a callback function\n condition = leftOrConditionOrCallback\n } else if (operator !== undefined && right !== undefined) {\n // Create a condition from parts\n condition = [leftOrConditionOrCallback, operator, right]\n } else {\n // Use the provided condition directly\n condition = leftOrConditionOrCallback\n }\n\n // Where is always an array, so initialize or append\n if (!newBuilder.query.where) {\n newBuilder.query.where = [condition]\n } else {\n newBuilder.query.where = [...newBuilder.query.where, condition]\n }\n\n return newBuilder as unknown as QueryBuilder<TContext>\n }\n\n /**\n * Add a having clause comparing two values.\n * For filtering results after they have been grouped.\n */\n having(\n left: PropertyReferenceString<TContext> | LiteralValue,\n operator: Comparator,\n right: PropertyReferenceString<TContext> | LiteralValue\n ): QueryBuilder<TContext>\n\n /**\n * Add a having clause with a complete condition object.\n * For filtering results after they have been grouped.\n */\n having(condition: Condition<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a having clause with a callback function.\n * For filtering results after they have been grouped.\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a having clause to filter the grouped results.\n * Can be called multiple times to add AND conditions.\n * Also supports callback functions that receive the row context.\n *\n * @param leftOrConditionOrCallback The left operand, complete condition, or callback function\n * @param operator Optional comparison operator\n * @param right Optional right operand\n * @returns A new QueryBuilder with the having clause added\n */\n having(\n leftOrConditionOrCallback: any,\n operator?: any,\n right?: any\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n let condition: any\n\n // Determine if this is a callback, complete condition, or individual parts\n if (typeof leftOrConditionOrCallback === `function`) {\n // It's a callback function\n condition = leftOrConditionOrCallback\n } else if (operator !== undefined && right !== undefined) {\n // Create a condition from parts\n condition = [leftOrConditionOrCallback, operator, right]\n } else {\n // Use the provided condition directly\n condition = leftOrConditionOrCallback\n }\n\n // Having is always an array, so initialize or append\n if (!newBuilder.query.having) {\n newBuilder.query.having = [condition]\n } else {\n newBuilder.query.having = [...newBuilder.query.having, condition]\n }\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Add a join clause to the query using a CollectionRef.\n */\n join<TCollectionRef extends CollectionRef>(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TCollectionRef\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n hasJoin: true\n }\n >\n >\n\n /**\n * Add a join clause to the query without specifying an alias.\n * The collection name will be used as the default alias.\n */\n join<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: T\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: { [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]> }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n hasJoin: true\n }\n >\n >\n\n /**\n * Add a join clause to the query with a specified alias.\n */\n join<\n TFrom extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as: TAs\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n hasJoin: true\n }\n >\n >\n\n join<\n TFrom extends\n | InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n | CollectionRef,\n TAs extends string | undefined = undefined,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as?: TAs\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] &\n (TFrom extends CollectionRef\n ? {\n [K in keyof TFrom & string]: RemoveIndexSignature<\n (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &\n Input\n >\n }\n : TFrom extends InputReference<infer TRef>\n ? {\n [K in keyof TRef & string]: RemoveIndexSignature<\n TRef[keyof TRef]\n >\n }\n : never)\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] &\n (TFrom extends CollectionRef\n ? {\n [K in keyof TFrom & string]: RemoveIndexSignature<\n (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &\n Input\n >\n }\n : TFrom extends InputReference<infer TRef>\n ? {\n [K in keyof TRef & string]: RemoveIndexSignature<\n TRef[keyof TRef]\n >\n }\n : never)\n }>\n >\n }): QueryBuilder<any> {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof joinClause.from === `object` && joinClause.from !== null) {\n return this.joinCollectionRef(\n joinClause as {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: CollectionRef\n on: Condition<any>\n where?: Condition<any>\n }\n )\n } else {\n return this.joinInputReference(\n joinClause as {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n as?: TAs\n on: Condition<any>\n where?: Condition<any>\n }\n )\n }\n }\n\n private joinCollectionRef<TCollectionRef extends CollectionRef>(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TCollectionRef\n on: Condition<any>\n where?: Condition<any>\n }): QueryBuilder<any> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Get the collection key\n const keys = Object.keys(joinClause.from)\n if (keys.length !== 1) {\n throw new Error(`Expected exactly one key in CollectionRef`)\n }\n const key = keys[0]!\n const collection = joinClause.from[key]\n if (!collection) {\n throw new Error(`Collection not found for key: ${key}`)\n }\n\n // Create a copy of the join clause for the query\n const joinClauseCopy = {\n type: joinClause.type,\n from: key,\n on: joinClause.on,\n where: joinClause.where,\n } as JoinClause<TContext>\n\n // Add the join clause to the query\n if (!newBuilder.query.join) {\n newBuilder.query.join = [joinClauseCopy]\n } else {\n newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]\n }\n\n // Add the collection to the collections map\n newBuilder.query.collections ??= {}\n newBuilder.query.collections[key] = collection\n\n // Return the new builder with updated schema type\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }\n >\n >\n }\n\n private joinInputReference<\n TFrom extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string | undefined = undefined,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as?: TAs\n on: Condition<any>\n where?: Condition<any>\n }): QueryBuilder<any> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Create a copy of the join clause for the query\n const joinClauseCopy = { ...joinClause } as JoinClause<TContext>\n\n // Add the join clause to the query\n if (!newBuilder.query.join) {\n newBuilder.query.join = [joinClauseCopy]\n } else {\n newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]\n }\n\n // Determine the alias or use the collection name as default\n const _effectiveAlias = joinClause.as ?? joinClause.from\n\n // Return the new builder with updated schema type\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in typeof _effectiveAlias]: TContext[`baseSchema`][TFrom]\n }\n }\n >\n >\n }\n\n /**\n * Add an orderBy clause to sort the results.\n * Overwrites any previous orderBy clause.\n *\n * @param orderBy The order specification\n * @returns A new QueryBuilder with the orderBy clause set\n */\n orderBy(orderBy: OrderBy<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the orderBy clause\n newBuilder.query.orderBy = orderBy\n\n // Ensure we have an orderByIndex in the select if we have an orderBy\n // This is required if select is called before orderBy\n newBuilder.query.select = [\n ...(newBuilder.query.select ?? []),\n { _orderByIndex: { ORDER_INDEX: `numeric` } },\n ]\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Set a limit on the number of results returned.\n *\n * @param limit Maximum number of results to return\n * @returns A new QueryBuilder with the limit set\n */\n limit(limit: Limit<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the limit\n newBuilder.query.limit = limit\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Set an offset to skip a number of results.\n *\n * @param offset Number of results to skip\n * @returns A new QueryBuilder with the offset set\n */\n offset(offset: Offset<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the offset\n newBuilder.query.offset = offset\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Add a groupBy clause to group the results by one or more columns.\n *\n * @param groupBy The column(s) to group by\n * @returns A new QueryBuilder with the groupBy clause set\n */\n groupBy(\n groupBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the groupBy clause\n newBuilder.query.groupBy = groupBy\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Define a Common Table Expression (CTE) that can be referenced in the main query.\n * This allows referencing the CTE by name in subsequent from/join clauses.\n *\n * @param name The name of the CTE\n * @param queryBuilderCallback A function that builds the CTE query\n * @returns A new QueryBuilder with the CTE added\n */\n with<TName extends string, TResult = Record<string, unknown>>(\n name: TName,\n queryBuilderCallback: (\n builder: InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>\n ) => QueryBuilder<any>\n ): InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }\n schema: TContext[`schema`]\n }> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Create a new builder for the CTE\n const cteBuilder = new BaseQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>()\n\n // Get the CTE query from the callback\n const cteQueryBuilder = queryBuilderCallback(\n cteBuilder as InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>\n )\n\n // Get the query from the builder\n const cteQuery = cteQueryBuilder._query\n\n // Add an 'as' property to the CTE\n const withQuery: WithQuery<any> = {\n ...cteQuery,\n as: name,\n }\n\n // Add the CTE to the with array\n if (!newBuilder.query.with) {\n newBuilder.query.with = [withQuery]\n } else {\n newBuilder.query.with = [...newBuilder.query.with, withQuery]\n }\n\n // Use a type cast that simplifies the type structure to avoid recursion\n return newBuilder as unknown as InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }\n schema: TContext[`schema`]\n }>\n }\n\n get _query(): Query<TContext> {\n return this.query as Query<TContext>\n }\n}\n\nexport type InitialQueryBuilder<TContext extends Context<Schema>> = Pick<\n BaseQueryBuilder<TContext>,\n `from` | `with`\n>\n\nexport type QueryBuilder<TContext extends Context<Schema>> = Omit<\n BaseQueryBuilder<TContext>,\n `from`\n>\n\n/**\n * Create a new query builder with the given schema\n */\nexport function queryBuilder<TBaseSchema extends Schema = {}>() {\n return new BaseQueryBuilder<{\n baseSchema: TBaseSchema\n schema: {}\n }>() as InitialQueryBuilder<{\n baseSchema: TBaseSchema\n schema: {}\n }>\n}\n\nexport type ResultsFromContext<TContext extends Context<Schema>> = Flatten<\n TContext[`result`] extends object\n ? TContext[`result`] // If there is a select we will have a result type\n : TContext[`hasJoin`] extends true\n ? TContext[`schema`] // If there is a join, the query returns the namespaced schema\n : TContext[`default`] extends keyof TContext[`schema`]\n ? TContext[`schema`][TContext[`default`]] // If there is no join we return the flat default schema\n : never // Should never happen\n>\n\nexport type ResultFromQueryBuilder<TQueryBuilder> = Flatten<\n TQueryBuilder extends QueryBuilder<infer C>\n ? C extends { result: infer R }\n ? R\n : never\n : never\n>\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"query-builder.cjs","sources":["../../../src/query/query-builder.ts"],"sourcesContent":["import type { Collection } from \"../collection\"\nimport type {\n Comparator,\n ComparatorValue,\n Condition,\n From,\n JoinClause,\n Limit,\n LiteralValue,\n Offset,\n OrderBy,\n Query,\n Select,\n WhereCallback,\n WithQuery,\n} from \"./schema.js\"\nimport type {\n Context,\n Flatten,\n InferResultTypeFromSelectTuple,\n Input,\n InputReference,\n PropertyReference,\n PropertyReferenceString,\n RemoveIndexSignature,\n Schema,\n} from \"./types.js\"\n\ntype CollectionRef = { [K: string]: Collection<any> }\n\nexport class BaseQueryBuilder<TContext extends Context<Schema>> {\n private readonly query: Partial<Query<TContext>> = {}\n\n /**\n * Create a new QueryBuilder instance.\n */\n constructor(query: Partial<Query<TContext>> = {}) {\n this.query = query\n }\n\n from<TCollectionRef extends CollectionRef>(\n collectionRef: TCollectionRef\n ): QueryBuilder<{\n baseSchema: Flatten<\n TContext[`baseSchema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n >\n schema: Flatten<{\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }>\n default: keyof TCollectionRef & string\n }>\n\n from<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n >(\n collection: T\n ): QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n default: T\n }>\n\n from<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string,\n >(\n collection: T,\n as: TAs\n ): QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n default: TAs\n }>\n\n /**\n * Specify the collection to query from.\n * This is the first method that must be called in the chain.\n *\n * @param collection The collection name to query from\n * @param as Optional alias for the collection\n * @returns A new QueryBuilder with the from clause set\n */\n from<\n T extends\n | InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n | CollectionRef,\n TAs extends string | undefined,\n >(collection: T, as?: TAs) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof collection === `object` && collection !== null) {\n return this.fromCollectionRef(collection)\n } else if (typeof collection === `string`) {\n return this.fromInputReference(\n collection as InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n as\n )\n } else {\n throw new Error(`Invalid collection type`)\n }\n }\n\n private fromCollectionRef<TCollectionRef extends CollectionRef>(\n collectionRef: TCollectionRef\n ) {\n const keys = Object.keys(collectionRef)\n if (keys.length !== 1) {\n throw new Error(`Expected exactly one key`)\n }\n\n const key = keys[0]!\n const collection = collectionRef[key]!\n\n const newBuilder = new BaseQueryBuilder()\n Object.assign(newBuilder.query, this.query)\n newBuilder.query.from = key as From<TContext>\n newBuilder.query.collections ??= {}\n newBuilder.query.collections[key] = collection\n\n return newBuilder as unknown as QueryBuilder<{\n baseSchema: TContext[`baseSchema`] & {\n [K in keyof TCollectionRef &\n string]: (TCollectionRef[keyof TCollectionRef] extends Collection<\n infer T\n >\n ? T\n : never) &\n Input\n }\n schema: {\n [K in keyof TCollectionRef &\n string]: (TCollectionRef[keyof TCollectionRef] extends Collection<\n infer T\n >\n ? T\n : never) &\n Input\n }\n default: keyof TCollectionRef & string\n }>\n }\n\n private fromInputReference<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string | undefined,\n >(collection: T, as?: TAs) {\n const newBuilder = new BaseQueryBuilder()\n Object.assign(newBuilder.query, this.query)\n newBuilder.query.from = collection as From<TContext>\n if (as) {\n newBuilder.query.as = as\n }\n\n // Calculate the result type without deep nesting\n type ResultSchema = TAs extends undefined\n ? { [K in T]: TContext[`baseSchema`][T] }\n : { [K in string & TAs]: TContext[`baseSchema`][T] }\n\n type ResultDefault = TAs extends undefined ? T : string & TAs\n\n // Use simpler type assertion to avoid excessive depth\n return newBuilder as unknown as QueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: ResultSchema\n default: ResultDefault\n }>\n }\n\n /**\n * Specify what columns to select.\n * Overwrites any previous select clause.\n * Also supports callback functions that receive the row context and return selected data.\n *\n * @param selects The columns to select (can include callbacks)\n * @returns A new QueryBuilder with the select clause set\n */\n select<TSelects extends Array<Select<TContext>>>(\n this: QueryBuilder<TContext>,\n ...selects: TSelects\n ) {\n // Validate function calls in the selects\n // Need to use a type assertion to bypass deep recursive type checking\n const validatedSelects = selects.map((select) => {\n // If the select is an object with aliases, validate each value\n if (\n typeof select === `object` &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n select !== null &&\n !Array.isArray(select)\n ) {\n const result: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(select)) {\n // If it's a function call (object with a single key that is an allowed function name)\n if (\n typeof value === `object` &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const keys = Object.keys(value)\n if (keys.length === 1) {\n const funcName = keys[0]!\n // List of allowed function names from AllowedFunctionName\n const allowedFunctions = [\n `SUM`,\n `COUNT`,\n `AVG`,\n `MIN`,\n `MAX`,\n `DATE`,\n `JSON_EXTRACT`,\n `JSON_EXTRACT_PATH`,\n `UPPER`,\n `LOWER`,\n `COALESCE`,\n `CONCAT`,\n `LENGTH`,\n `ORDER_INDEX`,\n ]\n\n if (!allowedFunctions.includes(funcName)) {\n console.warn(\n `Unsupported function: ${funcName}. Expected one of: ${allowedFunctions.join(`, `)}`\n )\n }\n }\n }\n\n result[key] = value\n }\n\n return result\n }\n\n return select\n })\n\n // Ensure we have an orderByIndex in the select if we have an orderBy\n // This is required if select is called after orderBy\n if (this._query.orderBy) {\n validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `fractional` } })\n }\n\n const newBuilder = new BaseQueryBuilder<TContext>(\n (this as BaseQueryBuilder<TContext>).query\n )\n newBuilder.query.select = validatedSelects as Array<Select<TContext>>\n\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `result`> & {\n result: InferResultTypeFromSelectTuple<TContext, TSelects>\n }\n >\n >\n }\n\n /**\n * Add a where clause comparing two values.\n */\n where<T extends Comparator>(\n left: PropertyReferenceString<TContext> | LiteralValue,\n operator: T,\n right: ComparatorValue<T, TContext>\n ): QueryBuilder<TContext>\n\n /**\n * Add a where clause with a complete condition object.\n */\n where(condition: Condition<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a where clause with a callback function.\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a where clause to filter the results.\n * Can be called multiple times to add AND conditions.\n * Also supports callback functions that receive the row context.\n *\n * @param leftOrConditionOrCallback The left operand, complete condition, or callback function\n * @param operator Optional comparison operator\n * @param right Optional right operand\n * @returns A new QueryBuilder with the where clause added\n */\n where(\n leftOrConditionOrCallback: any,\n operator?: any,\n right?: any\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n // Use simplistic approach to avoid deep type errors\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n let condition: any\n\n // Determine if this is a callback, complete condition, or individual parts\n if (typeof leftOrConditionOrCallback === `function`) {\n // It's a callback function\n condition = leftOrConditionOrCallback\n } else if (operator !== undefined && right !== undefined) {\n // Create a condition from parts\n condition = [leftOrConditionOrCallback, operator, right]\n } else {\n // Use the provided condition directly\n condition = leftOrConditionOrCallback\n }\n\n // Where is always an array, so initialize or append\n if (!newBuilder.query.where) {\n newBuilder.query.where = [condition]\n } else {\n newBuilder.query.where = [...newBuilder.query.where, condition]\n }\n\n return newBuilder as unknown as QueryBuilder<TContext>\n }\n\n /**\n * Add a having clause comparing two values.\n * For filtering results after they have been grouped.\n */\n having(\n left: PropertyReferenceString<TContext> | LiteralValue,\n operator: Comparator,\n right: PropertyReferenceString<TContext> | LiteralValue\n ): QueryBuilder<TContext>\n\n /**\n * Add a having clause with a complete condition object.\n * For filtering results after they have been grouped.\n */\n having(condition: Condition<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a having clause with a callback function.\n * For filtering results after they have been grouped.\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext>\n\n /**\n * Add a having clause to filter the grouped results.\n * Can be called multiple times to add AND conditions.\n * Also supports callback functions that receive the row context.\n *\n * @param leftOrConditionOrCallback The left operand, complete condition, or callback function\n * @param operator Optional comparison operator\n * @param right Optional right operand\n * @returns A new QueryBuilder with the having clause added\n */\n having(\n leftOrConditionOrCallback: any,\n operator?: any,\n right?: any\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n let condition: any\n\n // Determine if this is a callback, complete condition, or individual parts\n if (typeof leftOrConditionOrCallback === `function`) {\n // It's a callback function\n condition = leftOrConditionOrCallback\n } else if (operator !== undefined && right !== undefined) {\n // Create a condition from parts\n condition = [leftOrConditionOrCallback, operator, right]\n } else {\n // Use the provided condition directly\n condition = leftOrConditionOrCallback\n }\n\n // Having is always an array, so initialize or append\n if (!newBuilder.query.having) {\n newBuilder.query.having = [condition]\n } else {\n newBuilder.query.having = [...newBuilder.query.having, condition]\n }\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Add a join clause to the query using a CollectionRef.\n */\n join<TCollectionRef extends CollectionRef>(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TCollectionRef\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n hasJoin: true\n }\n >\n >\n\n /**\n * Add a join clause to the query without specifying an alias.\n * The collection name will be used as the default alias.\n */\n join<\n T extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: T\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: { [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]> }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>\n }\n hasJoin: true\n }\n >\n >\n\n /**\n * Add a join clause to the query with a specified alias.\n */\n join<\n TFrom extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as: TAs\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] & {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n }>\n >\n }): QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>\n }\n hasJoin: true\n }\n >\n >\n\n join<\n TFrom extends\n | InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n | CollectionRef,\n TAs extends string | undefined = undefined,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as?: TAs\n on: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] &\n (TFrom extends CollectionRef\n ? {\n [K in keyof TFrom & string]: RemoveIndexSignature<\n (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &\n Input\n >\n }\n : TFrom extends InputReference<infer TRef>\n ? {\n [K in keyof TRef & string]: RemoveIndexSignature<\n TRef[keyof TRef]\n >\n }\n : never)\n }>\n >\n where?: Condition<\n Flatten<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`schema`] &\n (TFrom extends CollectionRef\n ? {\n [K in keyof TFrom & string]: RemoveIndexSignature<\n (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &\n Input\n >\n }\n : TFrom extends InputReference<infer TRef>\n ? {\n [K in keyof TRef & string]: RemoveIndexSignature<\n TRef[keyof TRef]\n >\n }\n : never)\n }>\n >\n }): QueryBuilder<any> {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (typeof joinClause.from === `object` && joinClause.from !== null) {\n return this.joinCollectionRef(\n joinClause as {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: CollectionRef\n on: Condition<any>\n where?: Condition<any>\n }\n )\n } else {\n return this.joinInputReference(\n joinClause as {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>\n as?: TAs\n on: Condition<any>\n where?: Condition<any>\n }\n )\n }\n }\n\n private joinCollectionRef<TCollectionRef extends CollectionRef>(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TCollectionRef\n on: Condition<any>\n where?: Condition<any>\n }): QueryBuilder<any> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Get the collection key\n const keys = Object.keys(joinClause.from)\n if (keys.length !== 1) {\n throw new Error(`Expected exactly one key in CollectionRef`)\n }\n const key = keys[0]!\n const collection = joinClause.from[key]\n if (!collection) {\n throw new Error(`Collection not found for key: ${key}`)\n }\n\n // Create a copy of the join clause for the query\n const joinClauseCopy = {\n type: joinClause.type,\n from: key,\n on: joinClause.on,\n where: joinClause.where,\n } as JoinClause<TContext>\n\n // Add the join clause to the query\n if (!newBuilder.query.join) {\n newBuilder.query.join = [joinClauseCopy]\n } else {\n newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]\n }\n\n // Add the collection to the collections map\n newBuilder.query.collections ??= {}\n newBuilder.query.collections[key] = collection\n\n // Return the new builder with updated schema type\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in keyof TCollectionRef & string]: RemoveIndexSignature<\n (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>\n ? T\n : never) &\n Input\n >\n }\n }\n >\n >\n }\n\n private joinInputReference<\n TFrom extends InputReference<{\n baseSchema: TContext[`baseSchema`]\n schema: TContext[`baseSchema`]\n }>,\n TAs extends string | undefined = undefined,\n >(joinClause: {\n type: `inner` | `left` | `right` | `full` | `cross`\n from: TFrom\n as?: TAs\n on: Condition<any>\n where?: Condition<any>\n }): QueryBuilder<any> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Create a copy of the join clause for the query\n const joinClauseCopy = { ...joinClause } as JoinClause<TContext>\n\n // Add the join clause to the query\n if (!newBuilder.query.join) {\n newBuilder.query.join = [joinClauseCopy]\n } else {\n newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]\n }\n\n // Determine the alias or use the collection name as default\n const _effectiveAlias = joinClause.as ?? joinClause.from\n\n // Return the new builder with updated schema type\n return newBuilder as QueryBuilder<\n Flatten<\n Omit<TContext, `schema`> & {\n schema: TContext[`schema`] & {\n [K in typeof _effectiveAlias]: TContext[`baseSchema`][TFrom]\n }\n }\n >\n >\n }\n\n /**\n * Add an orderBy clause to sort the results.\n * Overwrites any previous orderBy clause.\n *\n * @param orderBy The order specification\n * @returns A new QueryBuilder with the orderBy clause set\n */\n orderBy(orderBy: OrderBy<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the orderBy clause\n newBuilder.query.orderBy = orderBy\n\n // Ensure we have an orderByIndex in the select if we have an orderBy\n // This is required if select is called before orderBy\n newBuilder.query.select = [\n ...(newBuilder.query.select ?? []),\n { _orderByIndex: { ORDER_INDEX: `fractional` } },\n ]\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Set a limit on the number of results returned.\n *\n * @param limit Maximum number of results to return\n * @returns A new QueryBuilder with the limit set\n */\n limit(limit: Limit<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the limit\n newBuilder.query.limit = limit\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Set an offset to skip a number of results.\n *\n * @param offset Number of results to skip\n * @returns A new QueryBuilder with the offset set\n */\n offset(offset: Offset<TContext>): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the offset\n newBuilder.query.offset = offset\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Add a groupBy clause to group the results by one or more columns.\n *\n * @param groupBy The column(s) to group by\n * @returns A new QueryBuilder with the groupBy clause set\n */\n groupBy(\n groupBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>\n ): QueryBuilder<TContext> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Set the groupBy clause\n newBuilder.query.groupBy = groupBy\n\n return newBuilder as QueryBuilder<TContext>\n }\n\n /**\n * Define a Common Table Expression (CTE) that can be referenced in the main query.\n * This allows referencing the CTE by name in subsequent from/join clauses.\n *\n * @param name The name of the CTE\n * @param queryBuilderCallback A function that builds the CTE query\n * @returns A new QueryBuilder with the CTE added\n */\n with<TName extends string, TResult = Record<string, unknown>>(\n name: TName,\n queryBuilderCallback: (\n builder: InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>\n ) => QueryBuilder<any>\n ): InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }\n schema: TContext[`schema`]\n }> {\n // Create a new builder with a copy of the current query\n const newBuilder = new BaseQueryBuilder<TContext>()\n Object.assign(newBuilder.query, this.query)\n\n // Create a new builder for the CTE\n const cteBuilder = new BaseQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>()\n\n // Get the CTE query from the callback\n const cteQueryBuilder = queryBuilderCallback(\n cteBuilder as InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`]\n schema: {}\n }>\n )\n\n // Get the query from the builder\n const cteQuery = cteQueryBuilder._query\n\n // Add an 'as' property to the CTE\n const withQuery: WithQuery<any> = {\n ...cteQuery,\n as: name,\n }\n\n // Add the CTE to the with array\n if (!newBuilder.query.with) {\n newBuilder.query.with = [withQuery]\n } else {\n newBuilder.query.with = [...newBuilder.query.with, withQuery]\n }\n\n // Use a type cast that simplifies the type structure to avoid recursion\n return newBuilder as unknown as InitialQueryBuilder<{\n baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }\n schema: TContext[`schema`]\n }>\n }\n\n get _query(): Query<TContext> {\n return this.query as Query<TContext>\n }\n}\n\nexport type InitialQueryBuilder<TContext extends Context<Schema>> = Pick<\n BaseQueryBuilder<TContext>,\n `from` | `with`\n>\n\nexport type QueryBuilder<TContext extends Context<Schema>> = Omit<\n BaseQueryBuilder<TContext>,\n `from`\n>\n\n/**\n * Create a new query builder with the given schema\n */\nexport function queryBuilder<TBaseSchema extends Schema = {}>() {\n return new BaseQueryBuilder<{\n baseSchema: TBaseSchema\n schema: {}\n }>() as InitialQueryBuilder<{\n baseSchema: TBaseSchema\n schema: {}\n }>\n}\n\nexport type ResultsFromContext<TContext extends Context<Schema>> = Flatten<\n TContext[`result`] extends object\n ? TContext[`result`] // If there is a select we will have a result type\n : TContext[`hasJoin`] extends true\n ? TContext[`schema`] // If there is a join, the query returns the namespaced schema\n : TContext[`default`] extends keyof TContext[`schema`]\n ? TContext[`schema`][TContext[`default`]] // If there is no join we return the flat default schema\n : never // Should never happen\n>\n\nexport type ResultFromQueryBuilder<TQueryBuilder> = Flatten<\n TQueryBuilder extends QueryBuilder<infer C>\n ? C extends { result: infer R }\n ? R\n : never\n : never\n>\n"],"names":[],"mappings":"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"}
|
|
@@ -105,7 +105,9 @@ class Transaction {
|
|
|
105
105
|
for (const mutation of this.mutations) {
|
|
106
106
|
if (!hasCalled.has(mutation.collection.id)) {
|
|
107
107
|
mutation.collection.onTransactionStateChange();
|
|
108
|
-
mutation.collection.
|
|
108
|
+
if (mutation.collection.pendingSyncedTransactions.length > 0) {
|
|
109
|
+
mutation.collection.commitPendingTransactions();
|
|
110
|
+
}
|
|
109
111
|
hasCalled.add(mutation.collection.id);
|
|
110
112
|
}
|
|
111
113
|
}
|
|
@@ -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 MutationFn,\n PendingMutation,\n TransactionConfig,\n TransactionState,\n TransactionWithMutations,\n} from \"./types\"\n\nconst transactions: Array<Transaction<any>> = []\nlet transactionStack: Array<Transaction<any>> = []\n\nexport function createTransaction<\n TData extends object = Record<string, unknown>,\n>(config: TransactionConfig<TData>): Transaction<TData> {\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 = crypto.randomUUID()\n }\n const newTransaction = new Transaction<TData>({\n ...config,\n id: transactionId,\n })\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":["createDeferred"],"mappings":";;;AAUA,MAAM,eAAwC,CAAC;AAC/C,IAAI,mBAA4C,CAAC;AAE1C,SAAS,kBAEd,QAAsD;AAClD,MAAA,OAAO,OAAO,eAAe,aAAa;AACtC,UAAA;AAAA,EAAA;AAGR,MAAI,gBAAgB,OAAO;AAC3B,MAAI,CAAC,eAAe;AAClB,oBAAgB,OAAO,WAAW;AAAA,EAAA;AAE9B,QAAA,iBAAiB,IAAI,YAAmB;AAAA,IAC5C,GAAG;AAAA,IACH,IAAI;AAAA,EAAA,CACL;AAED,eAAa,KAAK,cAAc;AAEzB,SAAA;AACT;AAEO,SAAS,uBAAgD;AAC1D,MAAA,iBAAiB,SAAS,GAAG;AAC/B,WAAO,iBAAiB,MAAM,EAAE,EAAE,CAAC;AAAA,EAAA,OAC9B;AACE,WAAA;AAAA,EAAA;AAEX;AAEA,SAAS,oBAAoB,IAAsB;AACjD,mBAAiB,KAAK,EAAE;AAC1B;AAEA,SAAS,sBAAsB,IAAsB;AACnD,qBAAmB,iBAAiB,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,EAAE;AAClE;AAEA,SAAS,sBAAsB,IAAsB;AAC7C,QAAA,QAAQ,aAAa,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,EAAE;AAC1D,MAAI,UAAU,IAAI;AACH,iBAAA,OAAO,OAAO,CAAC;AAAA,EAAA;AAEhC;AAEO,MAAM,YAAwD;AAAA,EAcnE,YAAY,QAA8B;AACxC,SAAK,KAAK,OAAO;AACjB,SAAK,aAAa,OAAO;AACzB,SAAK,QAAQ;AACb,SAAK,YAAY,CAAC;AAClB,SAAK,cAAcA,wBAA+B;AAC7C,SAAA,aAAa,OAAO,cAAc;AAClC,SAAA,gCAAgB,KAAK;AACrB,SAAA,WAAW,OAAO,YAAY,CAAC;AAAA,EAAA;AAAA,EAGtC,SAAS,UAA4B;AACnC,SAAK,QAAQ;AAET,QAAA,aAAa,eAAe,aAAa,UAAU;AACrD,4BAAsB,IAAI;AAAA,IAAA;AAAA,EAC5B;AAAA,EAGF,OAAO,UAAsC;AACvC,QAAA,KAAK,UAAU,WAAW;AACtB,YAAA;AAAA,IAAA;AAGR,wBAAoB,IAAI;AACpB,QAAA;AACO,eAAA;AAAA,IAAA,UACT;AACA,4BAAsB,IAAI;AAAA,IAAA;AAG5B,QAAI,KAAK,YAAY;AACnB,WAAK,OAAO;AAAA,IAAA;AAGP,WAAA;AAAA,EAAA;AAAA,EAGT,eAAe,WAA8C;AAC3D,eAAW,eAAe,WAAW;AAC7B,YAAA,gBAAgB,KAAK,UAAU;AAAA,QACnC,CAAC,MAAM,EAAE,cAAc,YAAY;AAAA,MACrC;AAEA,UAAI,iBAAiB,GAAG;AAEjB,aAAA,UAAU,aAAa,IAAI;AAAA,MAAA,OAC3B;AAEA,aAAA,UAAU,KAAK,WAAW;AAAA,MAAA;AAAA,IACjC;AAAA,EACF;AAAA,EAGF,SAAS,QAA4D;;AAC7D,UAAA,uBAAsB,iCAAQ,wBAAuB;AACvD,QAAA,KAAK,UAAU,aAAa;AACxB,YAAA;AAAA,IAAA;AAGR,SAAK,SAAS,QAAQ;AAItB,QAAI,CAAC,qBAAqB;AAClB,YAAA,kCAAkB,IAAI;AACvB,WAAA,UAAU,QAAQ,CAAC,MAAM,YAAY,IAAI,EAAE,SAAS,CAAC;AAC1D,iBAAW,KAAK,cAAc;AAC5B,UAAE,UAAU,aACV,EAAE,UAAU,KAAK,CAAC,MAAM,YAAY,IAAI,EAAE,SAAS,CAAC,KACpD,EAAE,SAAS,EAAE,qBAAqB,MAAM;AAAA,MAAA;AAAA,IAC5C;AAIF,SAAK,YAAY,QAAO,UAAK,UAAL,mBAAY,KAAK;AACzC,SAAK,gBAAgB;AAEd,WAAA;AAAA,EAAA;AAAA;AAAA,EAIT,kBAAwB;AAChB,UAAA,gCAAgB,IAAI;AACf,eAAA,YAAY,KAAK,WAAW;AACrC,UAAI,CAAC,UAAU,IAAI,SAAS,WAAW,EAAE,GAAG;AAC1C,iBAAS,WAAW,yBAAyB;
|
|
1
|
+
{"version":3,"file":"transactions.cjs","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\nconst transactions: Array<Transaction<any>> = []\nlet transactionStack: Array<Transaction<any>> = []\n\nexport function createTransaction<\n TData extends object = Record<string, unknown>,\n>(config: TransactionConfig<TData>): Transaction<TData> {\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 = crypto.randomUUID()\n }\n const newTransaction = new Transaction<TData>({\n ...config,\n id: transactionId,\n })\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\n // Only call commitPendingTransactions if there are pending sync transactions\n if (mutation.collection.pendingSyncedTransactions.length > 0) {\n mutation.collection.commitPendingTransactions()\n }\n\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":["createDeferred"],"mappings":"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"}
|
package/dist/cjs/types.d.cts
CHANGED
|
@@ -2,6 +2,23 @@ import { IStreamBuilder } from '@electric-sql/d2mini';
|
|
|
2
2
|
import { Collection } from './collection.cjs';
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
import { Transaction } from './transactions.cjs';
|
|
5
|
+
/**
|
|
6
|
+
* Helper type to extract the output type from a standard schema
|
|
7
|
+
*
|
|
8
|
+
* @internal This is used by the type resolution system
|
|
9
|
+
*/
|
|
10
|
+
export type InferSchemaOutput<T> = T extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<T> extends object ? StandardSchemaV1.InferOutput<T> : Record<string, unknown> : Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Helper type to determine the final type based on priority:
|
|
13
|
+
* 1. Explicit generic TExplicit (if not 'unknown')
|
|
14
|
+
* 2. Schema output type (if schema provided)
|
|
15
|
+
* 3. Fallback type TFallback
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* This type is used internally to resolve the collection item type based on the provided generics and schema.
|
|
19
|
+
* Users should not need to use this type directly, but understanding the priority order helps when defining collections.
|
|
20
|
+
*/
|
|
21
|
+
export type ResolveType<TExplicit, TSchema extends StandardSchemaV1 = never, TFallback extends object = Record<string, unknown>> = unknown extends TExplicit ? [TSchema] extends [never] ? TFallback : InferSchemaOutput<TSchema> : TExplicit extends object ? TExplicit : Record<string, unknown>;
|
|
5
22
|
export type TransactionState = `pending` | `persisting` | `completed` | `failed`;
|
|
6
23
|
/**
|
|
7
24
|
* Represents a utility function that can be attached to a collection
|
|
@@ -15,11 +32,11 @@ export type UtilsRecord = Record<string, Fn>;
|
|
|
15
32
|
* Represents a pending mutation within a transaction
|
|
16
33
|
* Contains information about the original and modified data, as well as metadata
|
|
17
34
|
*/
|
|
18
|
-
export interface PendingMutation<T extends object = Record<string, unknown
|
|
35
|
+
export interface PendingMutation<T extends object = Record<string, unknown>, TOperation extends OperationType = OperationType> {
|
|
19
36
|
mutationId: string;
|
|
20
|
-
original:
|
|
37
|
+
original: TOperation extends `insert` ? {} : T;
|
|
21
38
|
modified: T;
|
|
22
|
-
changes: Partial<T>;
|
|
39
|
+
changes: TOperation extends `insert` ? T : TOperation extends `delete` ? T : Partial<T>;
|
|
23
40
|
globalKey: string;
|
|
24
41
|
key: any;
|
|
25
42
|
type: OperationType;
|
|
@@ -44,8 +61,8 @@ export type NonEmptyArray<T> = [T, ...Array<T>];
|
|
|
44
61
|
* Utility type for a Transaction with at least one mutation
|
|
45
62
|
* This is used internally by the Transaction.commit method
|
|
46
63
|
*/
|
|
47
|
-
export type TransactionWithMutations<T extends object = Record<string, unknown
|
|
48
|
-
mutations: NonEmptyArray<PendingMutation<T>>;
|
|
64
|
+
export type TransactionWithMutations<T extends object = Record<string, unknown>, TOperation extends OperationType = OperationType> = Transaction<T> & {
|
|
65
|
+
mutations: NonEmptyArray<PendingMutation<T, TOperation>>;
|
|
49
66
|
};
|
|
50
67
|
export interface TransactionConfig<T extends object = Record<string, unknown>> {
|
|
51
68
|
/** Unique identifier for the transaction */
|
|
@@ -55,6 +72,15 @@ export interface TransactionConfig<T extends object = Record<string, unknown>> {
|
|
|
55
72
|
/** Custom metadata to associate with the transaction */
|
|
56
73
|
metadata?: Record<string, unknown>;
|
|
57
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Options for the createOptimisticAction helper
|
|
77
|
+
*/
|
|
78
|
+
export interface CreateOptimisticActionsOptions<TVars = unknown> extends Omit<TransactionConfig, `mutationFn`> {
|
|
79
|
+
/** Function to apply optimistic updates locally before the mutation completes */
|
|
80
|
+
onMutate: (vars: TVars) => void;
|
|
81
|
+
/** Function to execute the mutation on the server */
|
|
82
|
+
mutationFn: (vars: TVars, params: MutationFnParams) => Promise<any>;
|
|
83
|
+
}
|
|
58
84
|
export type { Transaction };
|
|
59
85
|
type Value<TExtensions = never> = string | number | boolean | bigint | null | TExtensions | Array<Value<TExtensions>> | {
|
|
60
86
|
[key: string | number | symbol]: Value<TExtensions>;
|
|
@@ -106,10 +132,36 @@ export interface OperationConfig {
|
|
|
106
132
|
export interface InsertConfig {
|
|
107
133
|
metadata?: Record<string, unknown>;
|
|
108
134
|
}
|
|
109
|
-
export
|
|
135
|
+
export type UpdateMutationFnParams<T extends object = Record<string, unknown>> = {
|
|
136
|
+
transaction: TransactionWithMutations<T, `update`>;
|
|
137
|
+
};
|
|
138
|
+
export type InsertMutationFnParams<T extends object = Record<string, unknown>> = {
|
|
139
|
+
transaction: TransactionWithMutations<T, `insert`>;
|
|
140
|
+
};
|
|
141
|
+
export type DeleteMutationFnParams<T extends object = Record<string, unknown>> = {
|
|
142
|
+
transaction: TransactionWithMutations<T, `delete`>;
|
|
143
|
+
};
|
|
144
|
+
export type InsertMutationFn<T extends object = Record<string, unknown>> = (params: InsertMutationFnParams<T>) => Promise<any>;
|
|
145
|
+
export type UpdateMutationFn<T extends object = Record<string, unknown>> = (params: UpdateMutationFnParams<T>) => Promise<any>;
|
|
146
|
+
export type DeleteMutationFn<T extends object = Record<string, unknown>> = (params: DeleteMutationFnParams<T>) => Promise<any>;
|
|
147
|
+
/**
|
|
148
|
+
* Collection status values for lifecycle management
|
|
149
|
+
*/
|
|
150
|
+
export type CollectionStatus =
|
|
151
|
+
/** Collection is created but sync hasn't started yet (when startSync config is false) */
|
|
152
|
+
`idle`
|
|
153
|
+
/** Sync has started but hasn't received the first commit yet */
|
|
154
|
+
| `loading`
|
|
155
|
+
/** Collection has received at least one commit and is ready for use */
|
|
156
|
+
| `ready`
|
|
157
|
+
/** An error occurred during sync initialization */
|
|
158
|
+
| `error`
|
|
159
|
+
/** Collection has been cleaned up and resources freed */
|
|
160
|
+
| `cleaned-up`;
|
|
161
|
+
export interface CollectionConfig<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = StandardSchemaV1> {
|
|
110
162
|
id?: string;
|
|
111
163
|
sync: SyncConfig<T, TKey>;
|
|
112
|
-
schema?:
|
|
164
|
+
schema?: TSchema;
|
|
113
165
|
/**
|
|
114
166
|
* Function to extract the ID from an object
|
|
115
167
|
* This is required for update/delete operations which now only accept IDs
|
|
@@ -120,24 +172,45 @@ export interface CollectionConfig<T extends object = Record<string, unknown>, TK
|
|
|
120
172
|
* getKey: (item) => item.uuid
|
|
121
173
|
*/
|
|
122
174
|
getKey: (item: T) => TKey;
|
|
175
|
+
/**
|
|
176
|
+
* Time in milliseconds after which the collection will be garbage collected
|
|
177
|
+
* when it has no active subscribers. Defaults to 5 minutes (300000ms).
|
|
178
|
+
*/
|
|
179
|
+
gcTime?: number;
|
|
180
|
+
/**
|
|
181
|
+
* Whether to start syncing immediately when the collection is created.
|
|
182
|
+
* Defaults to false for lazy loading. Set to true to immediately sync.
|
|
183
|
+
*/
|
|
184
|
+
startSync?: boolean;
|
|
185
|
+
/**
|
|
186
|
+
* Optional function to compare two items.
|
|
187
|
+
* This is used to order the items in the collection.
|
|
188
|
+
* @param x The first item to compare
|
|
189
|
+
* @param y The second item to compare
|
|
190
|
+
* @returns A number indicating the order of the items
|
|
191
|
+
* @example
|
|
192
|
+
* // For a collection with a 'createdAt' field
|
|
193
|
+
* compare: (x, y) => x.createdAt.getTime() - y.createdAt.getTime()
|
|
194
|
+
*/
|
|
195
|
+
compare?: (x: T, y: T) => number;
|
|
123
196
|
/**
|
|
124
197
|
* Optional asynchronous handler function called before an insert operation
|
|
125
198
|
* @param params Object containing transaction and mutation information
|
|
126
199
|
* @returns Promise resolving to any value
|
|
127
200
|
*/
|
|
128
|
-
onInsert?:
|
|
201
|
+
onInsert?: InsertMutationFn<T>;
|
|
129
202
|
/**
|
|
130
203
|
* Optional asynchronous handler function called before an update operation
|
|
131
204
|
* @param params Object containing transaction and mutation information
|
|
132
205
|
* @returns Promise resolving to any value
|
|
133
206
|
*/
|
|
134
|
-
onUpdate?:
|
|
207
|
+
onUpdate?: UpdateMutationFn<T>;
|
|
135
208
|
/**
|
|
136
209
|
* Optional asynchronous handler function called before a delete operation
|
|
137
210
|
* @param params Object containing transaction and mutation information
|
|
138
211
|
* @returns Promise resolving to any value
|
|
139
212
|
*/
|
|
140
|
-
onDelete?:
|
|
213
|
+
onDelete?: DeleteMutationFn<T>;
|
|
141
214
|
}
|
|
142
215
|
export type ChangesPayload<T extends object = Record<string, unknown>> = Array<ChangeMessage<T>>;
|
|
143
216
|
/**
|
package/dist/esm/SortedMap.d.ts
CHANGED
|
@@ -21,6 +21,16 @@ export declare class SortedMap<TKey, TValue> {
|
|
|
21
21
|
* @returns -1 if a < b, 1 if a > b, 0 if equal
|
|
22
22
|
*/
|
|
23
23
|
private defaultComparator;
|
|
24
|
+
/**
|
|
25
|
+
* Finds the index where a key-value pair should be inserted to maintain sort order.
|
|
26
|
+
* Uses binary search to find the correct position based on the value.
|
|
27
|
+
* Hence, it is in O(log n) time.
|
|
28
|
+
*
|
|
29
|
+
* @param key - The key to find position for
|
|
30
|
+
* @param value - The value to compare against
|
|
31
|
+
* @returns The index where the key should be inserted
|
|
32
|
+
*/
|
|
33
|
+
private indexOf;
|
|
24
34
|
/**
|
|
25
35
|
* Sets a key-value pair in the map and maintains sort order
|
|
26
36
|
*
|