@tanstack/db 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/dist/cjs/collection.cjs +117 -104
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +18 -21
  4. package/dist/cjs/index.cjs +35 -13
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/index.d.cts +0 -1
  7. package/dist/cjs/query/builder/functions.cjs +107 -0
  8. package/dist/cjs/query/builder/functions.cjs.map +1 -0
  9. package/dist/cjs/query/builder/functions.d.cts +38 -0
  10. package/dist/cjs/query/builder/index.cjs +499 -0
  11. package/dist/cjs/query/builder/index.cjs.map +1 -0
  12. package/dist/cjs/query/builder/index.d.cts +324 -0
  13. package/dist/cjs/query/builder/ref-proxy.cjs +96 -0
  14. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -0
  15. package/dist/cjs/query/builder/ref-proxy.d.cts +28 -0
  16. package/dist/cjs/query/builder/types.d.cts +80 -0
  17. package/dist/cjs/query/compiler/evaluators.cjs +261 -0
  18. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -0
  19. package/dist/cjs/query/compiler/evaluators.d.cts +11 -0
  20. package/dist/cjs/query/compiler/group-by.cjs +271 -0
  21. package/dist/cjs/query/compiler/group-by.cjs.map +1 -0
  22. package/dist/cjs/query/compiler/group-by.d.cts +7 -0
  23. package/dist/cjs/query/compiler/index.cjs +181 -0
  24. package/dist/cjs/query/compiler/index.cjs.map +1 -0
  25. package/dist/cjs/query/compiler/index.d.cts +15 -0
  26. package/dist/cjs/query/compiler/joins.cjs +116 -0
  27. package/dist/cjs/query/compiler/joins.cjs.map +1 -0
  28. package/dist/cjs/query/compiler/joins.d.cts +11 -0
  29. package/dist/cjs/query/compiler/order-by.cjs +89 -0
  30. package/dist/cjs/query/compiler/order-by.cjs.map +1 -0
  31. package/dist/cjs/query/compiler/order-by.d.cts +9 -0
  32. package/dist/cjs/query/compiler/select.cjs +57 -0
  33. package/dist/cjs/query/compiler/select.cjs.map +1 -0
  34. package/dist/cjs/query/compiler/select.d.cts +15 -0
  35. package/dist/cjs/query/index.d.cts +6 -5
  36. package/dist/cjs/query/ir.cjs +57 -0
  37. package/dist/cjs/query/ir.cjs.map +1 -0
  38. package/dist/cjs/query/ir.d.cts +81 -0
  39. package/dist/cjs/query/live-query-collection.cjs +224 -0
  40. package/dist/cjs/query/live-query-collection.cjs.map +1 -0
  41. package/dist/cjs/query/live-query-collection.d.cts +124 -0
  42. package/dist/cjs/transactions.cjs +20 -13
  43. package/dist/cjs/transactions.cjs.map +1 -1
  44. package/dist/cjs/transactions.d.cts +10 -1
  45. package/dist/cjs/types.d.cts +13 -0
  46. package/dist/esm/collection.d.ts +18 -21
  47. package/dist/esm/collection.js +118 -105
  48. package/dist/esm/collection.js.map +1 -1
  49. package/dist/esm/index.d.ts +0 -1
  50. package/dist/esm/index.js +34 -12
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/query/builder/functions.d.ts +38 -0
  53. package/dist/esm/query/builder/functions.js +107 -0
  54. package/dist/esm/query/builder/functions.js.map +1 -0
  55. package/dist/esm/query/builder/index.d.ts +324 -0
  56. package/dist/esm/query/builder/index.js +499 -0
  57. package/dist/esm/query/builder/index.js.map +1 -0
  58. package/dist/esm/query/builder/ref-proxy.d.ts +28 -0
  59. package/dist/esm/query/builder/ref-proxy.js +96 -0
  60. package/dist/esm/query/builder/ref-proxy.js.map +1 -0
  61. package/dist/esm/query/builder/types.d.ts +80 -0
  62. package/dist/esm/query/compiler/evaluators.d.ts +11 -0
  63. package/dist/esm/query/compiler/evaluators.js +261 -0
  64. package/dist/esm/query/compiler/evaluators.js.map +1 -0
  65. package/dist/esm/query/compiler/group-by.d.ts +7 -0
  66. package/dist/esm/query/compiler/group-by.js +271 -0
  67. package/dist/esm/query/compiler/group-by.js.map +1 -0
  68. package/dist/esm/query/compiler/index.d.ts +15 -0
  69. package/dist/esm/query/compiler/index.js +181 -0
  70. package/dist/esm/query/compiler/index.js.map +1 -0
  71. package/dist/esm/query/compiler/joins.d.ts +11 -0
  72. package/dist/esm/query/compiler/joins.js +116 -0
  73. package/dist/esm/query/compiler/joins.js.map +1 -0
  74. package/dist/esm/query/compiler/order-by.d.ts +9 -0
  75. package/dist/esm/query/compiler/order-by.js +89 -0
  76. package/dist/esm/query/compiler/order-by.js.map +1 -0
  77. package/dist/esm/query/compiler/select.d.ts +15 -0
  78. package/dist/esm/query/compiler/select.js +57 -0
  79. package/dist/esm/query/compiler/select.js.map +1 -0
  80. package/dist/esm/query/index.d.ts +6 -5
  81. package/dist/esm/query/ir.d.ts +81 -0
  82. package/dist/esm/query/ir.js +57 -0
  83. package/dist/esm/query/ir.js.map +1 -0
  84. package/dist/esm/query/live-query-collection.d.ts +124 -0
  85. package/dist/esm/query/live-query-collection.js +224 -0
  86. package/dist/esm/query/live-query-collection.js.map +1 -0
  87. package/dist/esm/transactions.d.ts +10 -1
  88. package/dist/esm/transactions.js +20 -13
  89. package/dist/esm/transactions.js.map +1 -1
  90. package/dist/esm/types.d.ts +13 -0
  91. package/package.json +3 -4
  92. package/src/collection.ts +152 -129
  93. package/src/index.ts +0 -1
  94. package/src/query/builder/functions.ts +267 -0
  95. package/src/query/builder/index.ts +648 -0
  96. package/src/query/builder/ref-proxy.ts +156 -0
  97. package/src/query/builder/types.ts +278 -0
  98. package/src/query/compiler/evaluators.ts +315 -0
  99. package/src/query/compiler/group-by.ts +428 -0
  100. package/src/query/compiler/index.ts +276 -0
  101. package/src/query/compiler/joins.ts +228 -0
  102. package/src/query/compiler/order-by.ts +139 -0
  103. package/src/query/compiler/select.ts +173 -0
  104. package/src/query/index.ts +64 -5
  105. package/src/query/ir.ts +128 -0
  106. package/src/query/live-query-collection.ts +509 -0
  107. package/src/transactions.ts +27 -16
  108. package/src/types.ts +15 -0
  109. package/dist/cjs/query/compiled-query.cjs +0 -160
  110. package/dist/cjs/query/compiled-query.cjs.map +0 -1
  111. package/dist/cjs/query/compiled-query.d.cts +0 -20
  112. package/dist/cjs/query/evaluators.cjs +0 -161
  113. package/dist/cjs/query/evaluators.cjs.map +0 -1
  114. package/dist/cjs/query/evaluators.d.cts +0 -14
  115. package/dist/cjs/query/extractors.cjs +0 -122
  116. package/dist/cjs/query/extractors.cjs.map +0 -1
  117. package/dist/cjs/query/extractors.d.cts +0 -22
  118. package/dist/cjs/query/functions.cjs +0 -152
  119. package/dist/cjs/query/functions.cjs.map +0 -1
  120. package/dist/cjs/query/functions.d.cts +0 -21
  121. package/dist/cjs/query/group-by.cjs +0 -88
  122. package/dist/cjs/query/group-by.cjs.map +0 -1
  123. package/dist/cjs/query/group-by.d.cts +0 -40
  124. package/dist/cjs/query/joins.cjs +0 -141
  125. package/dist/cjs/query/joins.cjs.map +0 -1
  126. package/dist/cjs/query/joins.d.cts +0 -14
  127. package/dist/cjs/query/order-by.cjs +0 -185
  128. package/dist/cjs/query/order-by.cjs.map +0 -1
  129. package/dist/cjs/query/order-by.d.cts +0 -3
  130. package/dist/cjs/query/pipeline-compiler.cjs +0 -89
  131. package/dist/cjs/query/pipeline-compiler.cjs.map +0 -1
  132. package/dist/cjs/query/pipeline-compiler.d.cts +0 -10
  133. package/dist/cjs/query/query-builder.cjs +0 -307
  134. package/dist/cjs/query/query-builder.cjs.map +0 -1
  135. package/dist/cjs/query/query-builder.d.cts +0 -225
  136. package/dist/cjs/query/schema.d.cts +0 -100
  137. package/dist/cjs/query/select.cjs +0 -130
  138. package/dist/cjs/query/select.cjs.map +0 -1
  139. package/dist/cjs/query/select.d.cts +0 -3
  140. package/dist/cjs/query/types.d.cts +0 -189
  141. package/dist/cjs/query/utils.cjs +0 -154
  142. package/dist/cjs/query/utils.cjs.map +0 -1
  143. package/dist/cjs/query/utils.d.cts +0 -37
  144. package/dist/cjs/utils.cjs +0 -17
  145. package/dist/cjs/utils.cjs.map +0 -1
  146. package/dist/cjs/utils.d.cts +0 -3
  147. package/dist/esm/query/compiled-query.d.ts +0 -20
  148. package/dist/esm/query/compiled-query.js +0 -160
  149. package/dist/esm/query/compiled-query.js.map +0 -1
  150. package/dist/esm/query/evaluators.d.ts +0 -14
  151. package/dist/esm/query/evaluators.js +0 -161
  152. package/dist/esm/query/evaluators.js.map +0 -1
  153. package/dist/esm/query/extractors.d.ts +0 -22
  154. package/dist/esm/query/extractors.js +0 -122
  155. package/dist/esm/query/extractors.js.map +0 -1
  156. package/dist/esm/query/functions.d.ts +0 -21
  157. package/dist/esm/query/functions.js +0 -152
  158. package/dist/esm/query/functions.js.map +0 -1
  159. package/dist/esm/query/group-by.d.ts +0 -40
  160. package/dist/esm/query/group-by.js +0 -88
  161. package/dist/esm/query/group-by.js.map +0 -1
  162. package/dist/esm/query/joins.d.ts +0 -14
  163. package/dist/esm/query/joins.js +0 -141
  164. package/dist/esm/query/joins.js.map +0 -1
  165. package/dist/esm/query/order-by.d.ts +0 -3
  166. package/dist/esm/query/order-by.js +0 -185
  167. package/dist/esm/query/order-by.js.map +0 -1
  168. package/dist/esm/query/pipeline-compiler.d.ts +0 -10
  169. package/dist/esm/query/pipeline-compiler.js +0 -89
  170. package/dist/esm/query/pipeline-compiler.js.map +0 -1
  171. package/dist/esm/query/query-builder.d.ts +0 -225
  172. package/dist/esm/query/query-builder.js +0 -307
  173. package/dist/esm/query/query-builder.js.map +0 -1
  174. package/dist/esm/query/schema.d.ts +0 -100
  175. package/dist/esm/query/select.d.ts +0 -3
  176. package/dist/esm/query/select.js +0 -130
  177. package/dist/esm/query/select.js.map +0 -1
  178. package/dist/esm/query/types.d.ts +0 -189
  179. package/dist/esm/query/utils.d.ts +0 -37
  180. package/dist/esm/query/utils.js +0 -154
  181. package/dist/esm/query/utils.js.map +0 -1
  182. package/dist/esm/utils.d.ts +0 -3
  183. package/dist/esm/utils.js +0 -17
  184. package/dist/esm/utils.js.map +0 -1
  185. package/src/query/compiled-query.ts +0 -234
  186. package/src/query/evaluators.ts +0 -250
  187. package/src/query/extractors.ts +0 -214
  188. package/src/query/functions.ts +0 -297
  189. package/src/query/group-by.ts +0 -139
  190. package/src/query/joins.ts +0 -260
  191. package/src/query/order-by.ts +0 -264
  192. package/src/query/pipeline-compiler.ts +0 -149
  193. package/src/query/query-builder.ts +0 -902
  194. package/src/query/schema.ts +0 -268
  195. package/src/query/select.ts +0 -208
  196. package/src/query/types.ts +0 -418
  197. package/src/query/utils.ts +0 -245
  198. package/src/utils.ts +0 -15
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CollectionImpl } from \"../collection\"\nimport type { NamespacedRow } from \"../types\"\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where = BasicExpression<boolean>\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n direction: OrderByDirection\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class Ref<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string> // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = Ref<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n"],"names":[],"mappings":";;AA4DA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACM,UAAA;AAHC,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAAA;AAOhB;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACM,UAAA;AAHC,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAAA;AAOhB;AAEO,MAAM,YAAqB,eAAkB;AAAA,EAElD,YACS,MACP;AACM,UAAA;AAFC,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAAA;AAMhB;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACM,UAAA;AAFC,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAAA;AAMhB;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACM,UAAA;AAHC,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAAA;AAOhB;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACM,UAAA;AAHC,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAAA;AAOhB;;;;;;;"}
@@ -0,0 +1,81 @@
1
+ import { CollectionImpl } from '../collection.cjs';
2
+ import { NamespacedRow } from '../types.cjs';
3
+ export interface QueryIR {
4
+ from: From;
5
+ select?: Select;
6
+ join?: Join;
7
+ where?: Array<Where>;
8
+ groupBy?: GroupBy;
9
+ having?: Array<Having>;
10
+ orderBy?: OrderBy;
11
+ limit?: Limit;
12
+ offset?: Offset;
13
+ fnSelect?: (row: NamespacedRow) => any;
14
+ fnWhere?: Array<(row: NamespacedRow) => any>;
15
+ fnHaving?: Array<(row: NamespacedRow) => any>;
16
+ }
17
+ export type From = CollectionRef | QueryRef;
18
+ export type Select = {
19
+ [alias: string]: BasicExpression | Aggregate;
20
+ };
21
+ export type Join = Array<JoinClause>;
22
+ export interface JoinClause {
23
+ from: CollectionRef | QueryRef;
24
+ type: `left` | `right` | `inner` | `outer` | `full` | `cross`;
25
+ left: BasicExpression;
26
+ right: BasicExpression;
27
+ }
28
+ export type Where = BasicExpression<boolean>;
29
+ export type GroupBy = Array<BasicExpression>;
30
+ export type Having = Where;
31
+ export type OrderBy = Array<OrderByClause>;
32
+ export type OrderByClause = {
33
+ expression: BasicExpression;
34
+ direction: OrderByDirection;
35
+ };
36
+ export type OrderByDirection = `asc` | `desc`;
37
+ export type Limit = number;
38
+ export type Offset = number;
39
+ declare abstract class BaseExpression<T = any> {
40
+ abstract type: string;
41
+ /** @internal - Type brand for TypeScript inference */
42
+ readonly __returnType: T;
43
+ }
44
+ export declare class CollectionRef extends BaseExpression {
45
+ collection: CollectionImpl;
46
+ alias: string;
47
+ type: "collectionRef";
48
+ constructor(collection: CollectionImpl, alias: string);
49
+ }
50
+ export declare class QueryRef extends BaseExpression {
51
+ query: QueryIR;
52
+ alias: string;
53
+ type: "queryRef";
54
+ constructor(query: QueryIR, alias: string);
55
+ }
56
+ export declare class Ref<T = any> extends BaseExpression<T> {
57
+ path: Array<string>;
58
+ type: "ref";
59
+ constructor(path: Array<string>);
60
+ }
61
+ export declare class Value<T = any> extends BaseExpression<T> {
62
+ value: T;
63
+ type: "val";
64
+ constructor(value: T);
65
+ }
66
+ export declare class Func<T = any> extends BaseExpression<T> {
67
+ name: string;
68
+ args: Array<BasicExpression>;
69
+ type: "func";
70
+ constructor(name: string, // such as eq, gt, lt, upper, lower, etc.
71
+ args: Array<BasicExpression>);
72
+ }
73
+ export type BasicExpression<T = any> = Ref<T> | Value<T> | Func<T>;
74
+ export declare class Aggregate<T = any> extends BaseExpression<T> {
75
+ name: string;
76
+ args: Array<BasicExpression>;
77
+ type: "agg";
78
+ constructor(name: string, // such as count, avg, sum, min, max, etc.
79
+ args: Array<BasicExpression>);
80
+ }
81
+ export {};
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const d2mini = require("@electric-sql/d2mini");
4
+ const collection = require("../collection.cjs");
5
+ const index$1 = require("./compiler/index.cjs");
6
+ const index = require("./builder/index.cjs");
7
+ let liveQueryCollectionCounter = 0;
8
+ function liveQueryCollectionOptions(config) {
9
+ const id = config.id || `live-query-${++liveQueryCollectionCounter}`;
10
+ const query = index.buildQuery(config.query);
11
+ const resultKeys = /* @__PURE__ */ new WeakMap();
12
+ const orderByIndices = /* @__PURE__ */ new WeakMap();
13
+ const compare = query.orderBy && query.orderBy.length > 0 ? (val1, val2) => {
14
+ const index1 = orderByIndices.get(val1);
15
+ const index2 = orderByIndices.get(val2);
16
+ if (index1 && index2) {
17
+ if (index1 < index2) {
18
+ return -1;
19
+ } else if (index1 > index2) {
20
+ return 1;
21
+ } else {
22
+ return 0;
23
+ }
24
+ }
25
+ return 0;
26
+ } : void 0;
27
+ const collections = extractCollectionsFromQuery(query);
28
+ const allCollectionsReady = () => {
29
+ return Object.values(collections).every(
30
+ (collection2) => collection2.status === `ready`
31
+ );
32
+ };
33
+ let graphCache;
34
+ let inputsCache;
35
+ let pipelineCache;
36
+ const compileBasePipeline = () => {
37
+ graphCache = new d2mini.D2();
38
+ inputsCache = Object.fromEntries(
39
+ Object.entries(collections).map(([key]) => [
40
+ key,
41
+ graphCache.newInput()
42
+ ])
43
+ );
44
+ pipelineCache = index$1.compileQuery(
45
+ query,
46
+ inputsCache
47
+ );
48
+ };
49
+ const maybeCompileBasePipeline = () => {
50
+ if (!graphCache || !inputsCache || !pipelineCache) {
51
+ compileBasePipeline();
52
+ }
53
+ return {
54
+ graph: graphCache,
55
+ inputs: inputsCache,
56
+ pipeline: pipelineCache
57
+ };
58
+ };
59
+ compileBasePipeline();
60
+ const sync = {
61
+ rowUpdateMode: `full`,
62
+ sync: ({ begin, write, commit, collection: theCollection }) => {
63
+ const { graph, inputs, pipeline } = maybeCompileBasePipeline();
64
+ let messagesCount = 0;
65
+ pipeline.pipe(
66
+ d2mini.output((data) => {
67
+ const messages = data.getInner();
68
+ messagesCount += messages.length;
69
+ begin();
70
+ messages.reduce((acc, [[key, tupleData], multiplicity]) => {
71
+ const [value, orderByIndex] = tupleData;
72
+ const changes = acc.get(key) || {
73
+ deletes: 0,
74
+ inserts: 0,
75
+ value,
76
+ orderByIndex
77
+ };
78
+ if (multiplicity < 0) {
79
+ changes.deletes += Math.abs(multiplicity);
80
+ } else if (multiplicity > 0) {
81
+ changes.inserts += multiplicity;
82
+ changes.value = value;
83
+ changes.orderByIndex = orderByIndex;
84
+ }
85
+ acc.set(key, changes);
86
+ return acc;
87
+ }, /* @__PURE__ */ new Map()).forEach((changes, rawKey) => {
88
+ const { deletes, inserts, value, orderByIndex } = changes;
89
+ resultKeys.set(value, rawKey);
90
+ if (orderByIndex !== void 0) {
91
+ orderByIndices.set(value, orderByIndex);
92
+ }
93
+ if (inserts && deletes === 0) {
94
+ write({
95
+ value,
96
+ type: `insert`
97
+ });
98
+ } else if (
99
+ // Insert & update(s) (updates are a delete & insert)
100
+ inserts > deletes || // Just update(s) but the item is already in the collection (so
101
+ // was inserted previously).
102
+ inserts === deletes && theCollection.has(rawKey)
103
+ ) {
104
+ write({
105
+ value,
106
+ type: `update`
107
+ });
108
+ } else if (deletes > 0) {
109
+ write({
110
+ value,
111
+ type: `delete`
112
+ });
113
+ } else {
114
+ throw new Error(
115
+ `This should never happen ${JSON.stringify(changes)}`
116
+ );
117
+ }
118
+ });
119
+ commit();
120
+ })
121
+ );
122
+ graph.finalize();
123
+ const maybeRunGraph = () => {
124
+ if (allCollectionsReady()) {
125
+ graph.run();
126
+ if (messagesCount === 0) {
127
+ begin();
128
+ commit();
129
+ }
130
+ }
131
+ };
132
+ const unsubscribeCallbacks = /* @__PURE__ */ new Set();
133
+ Object.entries(collections).forEach(([collectionId, collection2]) => {
134
+ const input = inputs[collectionId];
135
+ const unsubscribe = collection2.subscribeChanges(
136
+ (changes) => {
137
+ sendChangesToInput(input, changes, collection2.config.getKey);
138
+ maybeRunGraph();
139
+ },
140
+ { includeInitialState: true }
141
+ );
142
+ unsubscribeCallbacks.add(unsubscribe);
143
+ });
144
+ maybeRunGraph();
145
+ return () => {
146
+ unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
147
+ };
148
+ }
149
+ };
150
+ return {
151
+ id,
152
+ getKey: config.getKey || ((item) => resultKeys.get(item)),
153
+ sync,
154
+ compare,
155
+ gcTime: config.gcTime || 5e3,
156
+ // 5 seconds by default for live queries
157
+ schema: config.schema,
158
+ onInsert: config.onInsert,
159
+ onUpdate: config.onUpdate,
160
+ onDelete: config.onDelete,
161
+ startSync: config.startSync
162
+ };
163
+ }
164
+ function createLiveQueryCollection(configOrQuery) {
165
+ if (typeof configOrQuery === `function`) {
166
+ const config = {
167
+ query: configOrQuery
168
+ };
169
+ const options = liveQueryCollectionOptions(config);
170
+ return bridgeToCreateCollection(options);
171
+ } else {
172
+ const config = configOrQuery;
173
+ const options = liveQueryCollectionOptions(config);
174
+ return bridgeToCreateCollection({
175
+ ...options,
176
+ utils: config.utils
177
+ });
178
+ }
179
+ }
180
+ function bridgeToCreateCollection(options) {
181
+ return collection.createCollection(options);
182
+ }
183
+ function sendChangesToInput(input, changes, getKey) {
184
+ const multiSetArray = [];
185
+ for (const change of changes) {
186
+ const key = getKey(change.value);
187
+ if (change.type === `insert`) {
188
+ multiSetArray.push([[key, change.value], 1]);
189
+ } else if (change.type === `update`) {
190
+ multiSetArray.push([[key, change.previousValue], -1]);
191
+ multiSetArray.push([[key, change.value], 1]);
192
+ } else {
193
+ multiSetArray.push([[key, change.value], -1]);
194
+ }
195
+ }
196
+ input.sendData(new d2mini.MultiSet(multiSetArray));
197
+ }
198
+ function extractCollectionsFromQuery(query) {
199
+ const collections = {};
200
+ function extractFromSource(source) {
201
+ if (source.type === `collectionRef`) {
202
+ collections[source.collection.id] = source.collection;
203
+ } else if (source.type === `queryRef`) {
204
+ extractFromQuery(source.query);
205
+ }
206
+ }
207
+ function extractFromQuery(q) {
208
+ if (q.from) {
209
+ extractFromSource(q.from);
210
+ }
211
+ if (q.join && Array.isArray(q.join)) {
212
+ for (const joinClause of q.join) {
213
+ if (joinClause.from) {
214
+ extractFromSource(joinClause.from);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ extractFromQuery(query);
220
+ return collections;
221
+ }
222
+ exports.createLiveQueryCollection = createLiveQueryCollection;
223
+ exports.liveQueryCollectionOptions = liveQueryCollectionOptions;
224
+ //# sourceMappingURL=live-query-collection.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@electric-sql/d2mini\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery } from \"./builder/index.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@electric-sql/d2mini\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function\n const query = buildQuery<TContext>(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every(\n (collection) => collection.status === `ready`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n pipelineCache = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>\n )\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\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 changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\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 theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\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\n graph.finalize()\n\n const maybeRunGraph = () => {\n // We only run the graph if all the collections are ready\n if (allCollectionsReady()) {\n graph.run()\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Set up data flow from input collections to the compiled query\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n\n // Subscribe to changes\n const unsubscribe = collection.subscribeChanges(\n (changes: Array<ChangeMessage>) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph()\n },\n { includeInitialState: true }\n )\n unsubscribeCallbacks.add(unsubscribe)\n })\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n\n // Use a bridge function that handles the type compatibility cleanly\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n\n // Use a bridge function that handles the type compatibility cleanly\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Array<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\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/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n"],"names":["buildQuery","collection","D2","compileQuery","output","createCollection","MultiSet"],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { InitialQueryBuilder, QueryBuilder } from './builder/index.js';
2
+ import { Collection } from '../collection.js';
3
+ import { CollectionConfig, UtilsRecord } from '../types.js';
4
+ import { Context, GetResult } from './builder/types.js';
5
+ /**
6
+ * Configuration interface for live query collection options
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const config: LiveQueryCollectionConfig<any, any> = {
11
+ * // id is optional - will auto-generate "live-query-1", "live-query-2", etc.
12
+ * query: (q) => q
13
+ * .from({ comment: commentsCollection })
14
+ * .join(
15
+ * { user: usersCollection },
16
+ * ({ comment, user }) => eq(comment.user_id, user.id)
17
+ * )
18
+ * .where(({ comment }) => eq(comment.active, true))
19
+ * .select(({ comment, user }) => ({
20
+ * id: comment.id,
21
+ * content: comment.content,
22
+ * authorName: user.name,
23
+ * })),
24
+ * // getKey is optional - defaults to using stream key
25
+ * getKey: (item) => item.id,
26
+ * }
27
+ * ```
28
+ */
29
+ export interface LiveQueryCollectionConfig<TContext extends Context, TResult extends object = GetResult<TContext> & object> {
30
+ /**
31
+ * Unique identifier for the collection
32
+ * If not provided, defaults to `live-query-${number}` with auto-incrementing number
33
+ */
34
+ id?: string;
35
+ /**
36
+ * Query builder function that defines the live query
37
+ */
38
+ query: (q: InitialQueryBuilder) => QueryBuilder<TContext>;
39
+ /**
40
+ * Function to extract the key from result items
41
+ * If not provided, defaults to using the key from the D2 stream
42
+ */
43
+ getKey?: (item: TResult) => string | number;
44
+ /**
45
+ * Optional schema for validation
46
+ */
47
+ schema?: CollectionConfig<TResult>[`schema`];
48
+ /**
49
+ * Optional mutation handlers
50
+ */
51
+ onInsert?: CollectionConfig<TResult>[`onInsert`];
52
+ onUpdate?: CollectionConfig<TResult>[`onUpdate`];
53
+ onDelete?: CollectionConfig<TResult>[`onDelete`];
54
+ /**
55
+ * Start sync / the query immediately
56
+ */
57
+ startSync?: boolean;
58
+ /**
59
+ * GC time for the collection
60
+ */
61
+ gcTime?: number;
62
+ }
63
+ /**
64
+ * Creates live query collection options for use with createCollection
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const options = liveQueryCollectionOptions({
69
+ * // id is optional - will auto-generate if not provided
70
+ * query: (q) => q
71
+ * .from({ post: postsCollection })
72
+ * .where(({ post }) => eq(post.published, true))
73
+ * .select(({ post }) => ({
74
+ * id: post.id,
75
+ * title: post.title,
76
+ * content: post.content,
77
+ * })),
78
+ * // getKey is optional - will use stream key if not provided
79
+ * })
80
+ *
81
+ * const collection = createCollection(options)
82
+ * ```
83
+ *
84
+ * @param config - Configuration options for the live query collection
85
+ * @returns Collection options that can be passed to createCollection
86
+ */
87
+ export declare function liveQueryCollectionOptions<TContext extends Context, TResult extends object = GetResult<TContext>>(config: LiveQueryCollectionConfig<TContext, TResult>): CollectionConfig<TResult>;
88
+ /**
89
+ * Creates a live query collection directly
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * // Minimal usage - just pass a query function
94
+ * const activeUsers = createLiveQueryCollection(
95
+ * (q) => q
96
+ * .from({ user: usersCollection })
97
+ * .where(({ user }) => eq(user.active, true))
98
+ * .select(({ user }) => ({ id: user.id, name: user.name }))
99
+ * )
100
+ *
101
+ * // Full configuration with custom options
102
+ * const searchResults = createLiveQueryCollection({
103
+ * id: "search-results", // Custom ID (auto-generated if omitted)
104
+ * query: (q) => q
105
+ * .from({ post: postsCollection })
106
+ * .where(({ post }) => like(post.title, `%${searchTerm}%`))
107
+ * .select(({ post }) => ({
108
+ * id: post.id,
109
+ * title: post.title,
110
+ * excerpt: post.excerpt,
111
+ * })),
112
+ * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)
113
+ * utils: {
114
+ * updateSearchTerm: (newTerm: string) => {
115
+ * // Custom utility functions
116
+ * }
117
+ * }
118
+ * })
119
+ * ```
120
+ */
121
+ export declare function createLiveQueryCollection<TContext extends Context, TResult extends object = GetResult<TContext>>(query: (q: InitialQueryBuilder) => QueryBuilder<TContext>): Collection<TResult, string | number, {}>;
122
+ export declare function createLiveQueryCollection<TContext extends Context, TResult extends object = GetResult<TContext>, TUtils extends UtilsRecord = {}>(config: LiveQueryCollectionConfig<TContext, TResult> & {
123
+ utils?: TUtils;
124
+ }): Collection<TResult, string | number, TUtils>;
@@ -3,18 +3,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const deferred = require("./deferred.cjs");
4
4
  const transactions = [];
5
5
  let transactionStack = [];
6
+ let sequenceNumber = 0;
6
7
  function createTransaction(config) {
7
- if (typeof config.mutationFn === `undefined`) {
8
- throw `mutationFn is required when creating a transaction`;
9
- }
10
- let transactionId = config.id;
11
- if (!transactionId) {
12
- transactionId = crypto.randomUUID();
13
- }
14
- const newTransaction = new Transaction({
15
- ...config,
16
- id: transactionId
17
- });
8
+ const newTransaction = new Transaction(config);
18
9
  transactions.push(newTransaction);
19
10
  return newTransaction;
20
11
  }
@@ -39,13 +30,17 @@ function removeFromPendingList(tx) {
39
30
  }
40
31
  class Transaction {
41
32
  constructor(config) {
42
- this.id = config.id;
33
+ if (typeof config.mutationFn === `undefined`) {
34
+ throw `mutationFn is required when creating a transaction`;
35
+ }
36
+ this.id = config.id ?? crypto.randomUUID();
43
37
  this.mutationFn = config.mutationFn;
44
38
  this.state = `pending`;
45
39
  this.mutations = [];
46
40
  this.isPersisted = deferred.createDeferred();
47
41
  this.autoCommit = config.autoCommit ?? true;
48
42
  this.createdAt = /* @__PURE__ */ new Date();
43
+ this.sequenceNumber = sequenceNumber++;
49
44
  this.metadata = config.metadata ?? {};
50
45
  }
51
46
  setState(newState) {
@@ -137,8 +132,20 @@ class Transaction {
137
132
  }
138
133
  return this;
139
134
  }
135
+ /**
136
+ * Compare two transactions by their createdAt time and sequence number in order
137
+ * to sort them in the order they were created.
138
+ * @param other - The other transaction to compare to
139
+ * @returns -1 if this transaction was created before the other, 1 if it was created after, 0 if they were created at the same time
140
+ */
141
+ compareCreatedAt(other) {
142
+ const createdAtComparison = this.createdAt.getTime() - other.createdAt.getTime();
143
+ if (createdAtComparison !== 0) {
144
+ return createdAtComparison;
145
+ }
146
+ return this.sequenceNumber - other.sequenceNumber;
147
+ }
140
148
  }
141
- exports.Transaction = Transaction;
142
149
  exports.createTransaction = createTransaction;
143
150
  exports.getActiveTransaction = getActiveTransaction;
144
151
  //# sourceMappingURL=transactions.cjs.map
@@ -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 OperationType,\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<\n T extends object = Record<string, unknown>,\n TOperation extends OperationType = OperationType,\n> {\n public id: string\n public state: TransactionState\n public mutationFn: MutationFn<T>\n public mutations: Array<PendingMutation<T, TOperation>>\n public isPersisted: Deferred<Transaction<T, TOperation>>\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, TOperation>>()\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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"}
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 OperationType,\n PendingMutation,\n TransactionConfig,\n TransactionState,\n TransactionWithMutations,\n} from \"./types\"\n\nconst transactions: Array<Transaction<any>> = []\nlet transactionStack: Array<Transaction<any>> = []\n\nlet sequenceNumber = 0\n\nexport function createTransaction<\n TData extends object = Record<string, unknown>,\n>(config: TransactionConfig<TData>): Transaction<TData> {\n const newTransaction = new Transaction<TData>(config)\n transactions.push(newTransaction)\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\nclass Transaction<\n T extends object = Record<string, unknown>,\n TOperation extends OperationType = OperationType,\n> {\n public id: string\n public state: TransactionState\n public mutationFn: MutationFn<T>\n public mutations: Array<PendingMutation<T, TOperation>>\n public isPersisted: Deferred<Transaction<T, TOperation>>\n public autoCommit: boolean\n public createdAt: Date\n public sequenceNumber: number\n public metadata: Record<string, unknown>\n public error?: {\n message: string\n error: Error\n }\n\n constructor(config: TransactionConfig<T>) {\n if (typeof config.mutationFn === `undefined`) {\n throw `mutationFn is required when creating a transaction`\n }\n this.id = config.id ?? crypto.randomUUID()\n this.mutationFn = config.mutationFn\n this.state = `pending`\n this.mutations = []\n this.isPersisted = createDeferred<Transaction<T, TOperation>>()\n this.autoCommit = config.autoCommit ?? true\n this.createdAt = new Date()\n this.sequenceNumber = sequenceNumber++\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 /**\n * Compare two transactions by their createdAt time and sequence number in order\n * to sort them in the order they were created.\n * @param other - The other transaction to compare to\n * @returns -1 if this transaction was created before the other, 1 if it was created after, 0 if they were created at the same time\n */\n compareCreatedAt(other: Transaction<any>): number {\n const createdAtComparison =\n this.createdAt.getTime() - other.createdAt.getTime()\n if (createdAtComparison !== 0) {\n return createdAtComparison\n }\n return this.sequenceNumber - other.sequenceNumber\n }\n}\n\nexport type { Transaction }\n"],"names":["createDeferred"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { Deferred } from './deferred.cjs';
2
2
  import { MutationFn, OperationType, PendingMutation, TransactionConfig, TransactionState } from './types.cjs';
3
3
  export declare function createTransaction<TData extends object = Record<string, unknown>>(config: TransactionConfig<TData>): Transaction<TData>;
4
4
  export declare function getActiveTransaction(): Transaction | undefined;
5
- export declare class Transaction<T extends object = Record<string, unknown>, TOperation extends OperationType = OperationType> {
5
+ declare class Transaction<T extends object = Record<string, unknown>, TOperation extends OperationType = OperationType> {
6
6
  id: string;
7
7
  state: TransactionState;
8
8
  mutationFn: MutationFn<T>;
@@ -10,6 +10,7 @@ export declare class Transaction<T extends object = Record<string, unknown>, TOp
10
10
  isPersisted: Deferred<Transaction<T, TOperation>>;
11
11
  autoCommit: boolean;
12
12
  createdAt: Date;
13
+ sequenceNumber: number;
13
14
  metadata: Record<string, unknown>;
14
15
  error?: {
15
16
  message: string;
@@ -24,4 +25,12 @@ export declare class Transaction<T extends object = Record<string, unknown>, TOp
24
25
  }): Transaction<T>;
25
26
  touchCollection(): void;
26
27
  commit(): Promise<Transaction<T>>;
28
+ /**
29
+ * Compare two transactions by their createdAt time and sequence number in order
30
+ * to sort them in the order they were created.
31
+ * @param other - The other transaction to compare to
32
+ * @returns -1 if this transaction was created before the other, 1 if it was created after, 0 if they were created at the same time
33
+ */
34
+ compareCreatedAt(other: Transaction<any>): number;
27
35
  }
36
+ export type { Transaction };
@@ -99,6 +99,14 @@ export interface SyncConfig<T extends object = Record<string, unknown>, TKey ext
99
99
  * @returns Record containing relation information
100
100
  */
101
101
  getSyncMetadata?: () => Record<string, unknown>;
102
+ /**
103
+ * The row update mode used to sync to the collection.
104
+ * @default `partial`
105
+ * @description
106
+ * - `partial`: Updates contain only the changes to the row.
107
+ * - `full`: Updates contain the entire row.
108
+ */
109
+ rowUpdateMode?: `partial` | `full`;
102
110
  }
103
111
  export interface ChangeMessage<T extends object = Record<string, unknown>, TKey extends string | number = string | number> {
104
112
  key: TKey;
@@ -222,6 +230,11 @@ export type InputRow = [unknown, Record<string, unknown>];
222
230
  * This is used as the inputs from a collection to a query
223
231
  */
224
232
  export type KeyedStream = IStreamBuilder<InputRow>;
233
+ /**
234
+ * Result stream type representing the output of compiled queries
235
+ * Always returns [key, [result, orderByIndex]] where orderByIndex is undefined for unordered queries
236
+ */
237
+ export type ResultStream = IStreamBuilder<[unknown, [any, string | undefined]]>;
225
238
  /**
226
239
  * A namespaced row is a row withing a pipeline that had each table wrapped in its alias
227
240
  */