@firtoz/db-helpers 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/db-helpers",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "TanStack DB helpers and utilities",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -48,10 +48,13 @@
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@standard-schema/spec": ">=1.1.0",
51
- "@tanstack/db": ">=0.5.25"
51
+ "@tanstack/db": ">=0.5.26"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@standard-schema/spec": "^1.1.0",
55
- "@tanstack/db": "^0.5.25"
55
+ "@tanstack/db": "^0.5.26"
56
+ },
57
+ "dependencies": {
58
+ "@firtoz/maybe-error": "^1.5.2"
56
59
  }
57
60
  }
package/src/index.ts CHANGED
@@ -1,3 +1,9 @@
1
+ export type {
2
+ CollectionUtils,
3
+ ExternalSyncEvent,
4
+ ExternalSyncHandler,
5
+ SyncMessage,
6
+ } from "./sync-types";
1
7
  export {
2
8
  createMemoryCollection,
3
9
  memoryCollectionOptions,
@@ -1,3 +1,5 @@
1
+ import type { SyncMessage } from "./sync-types";
2
+ import { exhaustiveGuard } from "@firtoz/maybe-error";
1
3
  import type { StandardSchemaV1 } from "@standard-schema/spec";
2
4
  import {
3
5
  type Collection,
@@ -16,72 +18,100 @@ type MemoryCollectionConfig<TSchema extends StandardSchemaV1> = Omit<
16
18
  "onInsert" | "onUpdate" | "onDelete" | "sync" | "schema"
17
19
  > & {
18
20
  schema: TSchema;
21
+ /** Called when a local mutation is written to the sync layer; use to broadcast to other peers. */
22
+ onBroadcast?: (
23
+ changes: SyncMessage<InferSchemaOutput<TSchema>, string | number>[],
24
+ ) => void;
19
25
  };
20
26
 
21
- type MemoryUtils = {
27
+ type MemoryUtils<
28
+ TItem = unknown,
29
+ TKey extends string | number = string | number,
30
+ > = {
22
31
  truncate: () => Promise<void>;
32
+ /**
33
+ * Apply incoming sync messages without triggering onInsert/onUpdate/onDelete.
34
+ * Uses the sync layer (begin/write/commit) so state updates and subscribeChanges fire,
35
+ * but no rebroadcast occurs.
36
+ */
37
+ receiveSync: (messages: SyncMessage<TItem, TKey>[]) => Promise<void>;
23
38
  };
24
39
 
25
40
  export function memoryCollectionOptions<TSchema extends StandardSchemaV1>(
26
41
  config: MemoryCollectionConfig<TSchema>,
27
42
  ): CollectionConfig<InferSchemaOutput<TSchema>, string | number, TSchema> & {
28
- utils: MemoryUtils;
43
+ utils: MemoryUtils<InferSchemaOutput<TSchema>, string | number>;
29
44
  schema: TSchema;
30
45
  } {
31
46
  type TItem = InferSchemaOutput<TSchema>;
47
+ type TKey = string | number;
32
48
  let syncParams: Parameters<SyncConfig<TItem>["sync"]>[0] | null = null;
33
49
 
34
50
  const sync: SyncConfig<TItem>["sync"] = (params) => {
35
51
  syncParams = params;
36
-
37
52
  params.markReady();
38
-
39
- // Return cleanup function
40
53
  return () => {};
41
54
  };
42
55
 
43
- // All mutation handlers use the same transaction sender
44
- const onInsert = async (params: InsertMutationFnParams<TItem>) => {
56
+ const writeChanges = (writes: SyncMessage<TItem, TKey>[]) => {
45
57
  if (!syncParams) {
46
58
  throw new Error("Sync parameters not initialized");
47
59
  }
48
60
  syncParams.begin();
49
- for (const mutation of params.transaction.mutations) {
50
- syncParams.write({
51
- type: "insert",
52
- value: mutation.modified,
53
- });
61
+ for (const msg of writes) {
62
+ switch (msg.type) {
63
+ case "insert":
64
+ syncParams.write({ type: "insert", value: msg.value });
65
+ break;
66
+ case "update":
67
+ syncParams.write({
68
+ type: "update",
69
+ value: msg.value,
70
+ previousValue: msg.previousValue,
71
+ });
72
+ break;
73
+ case "delete":
74
+ syncParams.write({ type: "delete", key: msg.key });
75
+ break;
76
+ case "truncate":
77
+ syncParams.truncate();
78
+ break;
79
+ default:
80
+ exhaustiveGuard(msg);
81
+ }
54
82
  }
55
83
  syncParams.commit();
56
84
  };
57
85
 
58
- const onUpdate = async (params: UpdateMutationFnParams<TItem>) => {
59
- if (!syncParams) {
60
- throw new Error("Sync parameters not initialized");
86
+ const onInsert = async (params: InsertMutationFnParams<TItem>) => {
87
+ const writes: SyncMessage<TItem, TKey>[] = [];
88
+ for (const mutation of params.transaction.mutations) {
89
+ writes.push({ type: "insert", value: mutation.modified });
61
90
  }
62
- syncParams.begin();
91
+ writeChanges(writes);
92
+ config.onBroadcast?.(writes);
93
+ };
94
+
95
+ const onUpdate = async (params: UpdateMutationFnParams<TItem>) => {
96
+ const writes: SyncMessage<TItem, TKey>[] = [];
63
97
  for (const mutation of params.transaction.mutations) {
64
- syncParams.write({
98
+ writes.push({
65
99
  type: "update",
66
100
  value: mutation.modified,
67
101
  previousValue: mutation.original,
68
102
  });
69
103
  }
70
- syncParams.commit();
104
+ writeChanges(writes);
105
+ config.onBroadcast?.(writes);
71
106
  };
72
107
 
73
108
  const onDelete = async (params: DeleteMutationFnParams<TItem>) => {
74
- if (!syncParams) {
75
- throw new Error("Sync parameters not initialized");
76
- }
77
- syncParams.begin();
109
+ const writes: SyncMessage<TItem, TKey>[] = [];
78
110
  for (const mutation of params.transaction.mutations) {
79
- syncParams.write({
80
- type: "delete",
81
- key: mutation.key,
82
- });
111
+ writes.push({ type: "delete", key: mutation.key as TKey });
83
112
  }
84
- syncParams?.commit();
113
+ writeChanges(writes);
114
+ config.onBroadcast?.(writes);
85
115
  };
86
116
 
87
117
  const truncate = async () => {
@@ -93,6 +123,11 @@ export function memoryCollectionOptions<TSchema extends StandardSchemaV1>(
93
123
  syncParams.commit();
94
124
  };
95
125
 
126
+ const receiveSync = async (messages: SyncMessage<TItem, TKey>[]) => {
127
+ if (messages.length === 0) return;
128
+ writeChanges(messages);
129
+ };
130
+
96
131
  return {
97
132
  id: config.id,
98
133
  schema: config.schema,
@@ -103,6 +138,7 @@ export function memoryCollectionOptions<TSchema extends StandardSchemaV1>(
103
138
  onDelete,
104
139
  utils: {
105
140
  truncate,
141
+ receiveSync,
106
142
  },
107
143
  };
108
144
  }
@@ -110,7 +146,7 @@ export function memoryCollectionOptions<TSchema extends StandardSchemaV1>(
110
146
  export type MemoryCollection<TSchema extends StandardSchemaV1> = Collection<
111
147
  InferSchemaOutput<TSchema>,
112
148
  string | number,
113
- MemoryUtils,
149
+ MemoryUtils<InferSchemaOutput<TSchema>, string | number>,
114
150
  TSchema,
115
151
  InferSchemaInput<TSchema>
116
152
  >;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Generic collection sync types. Used by memory, IndexedDB, SQLite, and other
3
+ * collection backends for a unified sync protocol.
4
+ */
5
+
6
+ /**
7
+ * Canonical per-mutation sync message. Use for broadcast and receive across all collection types.
8
+ */
9
+ export type SyncMessage<
10
+ T = unknown,
11
+ TKey extends string | number = string | number,
12
+ > =
13
+ | { type: "insert"; value: T }
14
+ | { type: "update"; value: T; previousValue: T }
15
+ | { type: "delete"; key: TKey }
16
+ | { type: "truncate" };
17
+
18
+ /**
19
+ * External sync event (batched). Used internally by the sync layer.
20
+ */
21
+ export type ExternalSyncEvent<T> =
22
+ | { type: "insert"; items: T[] }
23
+ | { type: "update"; items: T[] }
24
+ | { type: "delete"; items: T[] }
25
+ | { type: "truncate" };
26
+
27
+ /**
28
+ * Handler for external sync events (internal use).
29
+ */
30
+ export type ExternalSyncHandler<T> = (event: ExternalSyncEvent<T>) => void;
31
+
32
+ /**
33
+ * Collection utils: truncate and receiveSync (canonical sync protocol).
34
+ */
35
+ export interface CollectionUtils<T = unknown> {
36
+ /**
37
+ * Clear all data from the store (truncate).
38
+ * This clears the backend store and updates the local reactive store.
39
+ */
40
+ truncate: () => Promise<void>;
41
+ /**
42
+ * Apply incoming sync messages without triggering mutation handlers.
43
+ * Use the same SyncMessage[] shape for memory and backend collections.
44
+ */
45
+ receiveSync: (messages: SyncMessage<T>[]) => Promise<void>;
46
+ }