@tanstack/db 0.0.6 → 0.0.8

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