reflectdb 0.1.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1260 -0
  3. package/dist/cjs/client/index.cjs +1252 -0
  4. package/dist/cjs/client/index.d.cts +629 -0
  5. package/dist/cjs/client/storage/indexeddb.cjs +253 -0
  6. package/dist/cjs/client/storage/indexeddb.d.cts +85 -0
  7. package/dist/cjs/core/index.cjs +202 -0
  8. package/dist/cjs/core/index.d.cts +473 -0
  9. package/dist/cjs/react/index.cjs +1512 -0
  10. package/dist/cjs/react/index.d.cts +748 -0
  11. package/dist/cjs/server/drizzle.cjs +413 -0
  12. package/dist/cjs/server/drizzle.d.cts +233 -0
  13. package/dist/cjs/server/index.cjs +4486 -0
  14. package/dist/cjs/server/index.d.cts +1988 -0
  15. package/dist/cjs/svelte/index.cjs +1414 -0
  16. package/dist/cjs/svelte/index.d.cts +645 -0
  17. package/dist/cjs/transport/bun-ws.cjs +244 -0
  18. package/dist/cjs/transport/bun-ws.d.cts +215 -0
  19. package/dist/cjs/transport/polling.cjs +285 -0
  20. package/dist/cjs/transport/polling.d.cts +210 -0
  21. package/dist/cjs/transport/sse.cjs +312 -0
  22. package/dist/cjs/transport/sse.d.cts +205 -0
  23. package/dist/cjs/transport/ws.cjs +330 -0
  24. package/dist/cjs/transport/ws.d.cts +236 -0
  25. package/dist/cjs/vanilla/index.cjs +1430 -0
  26. package/dist/cjs/vanilla/index.d.cts +657 -0
  27. package/dist/client/index.d.ts +629 -0
  28. package/dist/client/index.js +106 -0
  29. package/dist/client/storage/indexeddb.d.ts +85 -0
  30. package/dist/client/storage/indexeddb.js +213 -0
  31. package/dist/core/index.d.ts +473 -0
  32. package/dist/core/index.js +56 -0
  33. package/dist/react/index.d.ts +748 -0
  34. package/dist/react/index.js +366 -0
  35. package/dist/server/drizzle.d.ts +233 -0
  36. package/dist/server/drizzle.js +9 -0
  37. package/dist/server/index.d.ts +1988 -0
  38. package/dist/server/index.js +3959 -0
  39. package/dist/shared/esm-3tkwvysa.js +54 -0
  40. package/dist/shared/esm-b7xs9cde.js +4 -0
  41. package/dist/shared/esm-rw7jjtrv.js +58 -0
  42. package/dist/shared/esm-wkwx6bd9.js +25 -0
  43. package/dist/shared/esm-ytrd3hbq.js +1007 -0
  44. package/dist/shared/esm-z1xse19c.js +369 -0
  45. package/dist/svelte/index.d.ts +645 -0
  46. package/dist/svelte/index.js +262 -0
  47. package/dist/transport/bun-ws.d.ts +215 -0
  48. package/dist/transport/bun-ws.js +140 -0
  49. package/dist/transport/polling.d.ts +210 -0
  50. package/dist/transport/polling.js +181 -0
  51. package/dist/transport/sse.d.ts +205 -0
  52. package/dist/transport/sse.js +208 -0
  53. package/dist/transport/ws.d.ts +236 -0
  54. package/dist/transport/ws.js +226 -0
  55. package/dist/vanilla/index.d.ts +657 -0
  56. package/dist/vanilla/index.js +278 -0
  57. package/package.json +253 -0
@@ -0,0 +1,278 @@
1
+ import {
2
+ SyncClient,
3
+ pushSafely
4
+ } from "../shared/esm-ytrd3hbq.js";
5
+ import"../shared/esm-rw7jjtrv.js";
6
+ import"../shared/esm-3tkwvysa.js";
7
+ import"../shared/esm-b7xs9cde.js";
8
+
9
+ // src/vanilla/sync.ts
10
+ function createBrowserWsTransport(url) {
11
+ let ws = null;
12
+ let handler = null;
13
+ let intentionalClose = false;
14
+ const sendQueue = [];
15
+ function ensureConnected() {
16
+ if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {
17
+ return ws;
18
+ }
19
+ const socket = new WebSocket(url);
20
+ socket.onopen = () => {
21
+ for (const msg of sendQueue) {
22
+ socket.send(JSON.stringify(msg));
23
+ }
24
+ sendQueue.length = 0;
25
+ };
26
+ socket.onmessage = (event) => {
27
+ handler?.(JSON.parse(event.data));
28
+ };
29
+ socket.onclose = () => {
30
+ ws = null;
31
+ if (!intentionalClose) {
32
+ handler?.({ type: "disconnect", reason: "transport_closed" });
33
+ }
34
+ };
35
+ ws = socket;
36
+ return socket;
37
+ }
38
+ return {
39
+ async send(message) {
40
+ const socket = ensureConnected();
41
+ if (socket.readyState === WebSocket.OPEN) {
42
+ socket.send(JSON.stringify(message));
43
+ } else {
44
+ sendQueue.push(message);
45
+ }
46
+ },
47
+ subscribe(h) {
48
+ handler = h;
49
+ },
50
+ async close() {
51
+ intentionalClose = true;
52
+ if (ws) {
53
+ ws.close();
54
+ ws = null;
55
+ }
56
+ }
57
+ };
58
+ }
59
+ function createSync(config) {
60
+ const clientId = config.clientId ?? `browser-${crypto.randomUUID().slice(0, 8)}`;
61
+ const transport = createBrowserWsTransport(config.url);
62
+ const managedTables = new Set(config.tables ?? []);
63
+ const unmanagedRefCounts = new Map;
64
+ const unmanagedParamKeys = new Map;
65
+ function stableKey(obj) {
66
+ if (!obj)
67
+ return "";
68
+ const keys = Object.keys(obj).sort();
69
+ return keys.map((k) => `${k}:${JSON.stringify(obj[k])}`).join("|");
70
+ }
71
+ const client = new SyncClient({
72
+ clientId,
73
+ transport,
74
+ token: config.token,
75
+ storage: config.storage,
76
+ onReauth: () => config.onReauth?.() ?? Promise.reject(new Error("No onReauth handler")),
77
+ onError: (err) => config.onError?.(err)
78
+ });
79
+ async function connect() {
80
+ if (config.storage) {
81
+ await client.init();
82
+ }
83
+ await client.connect();
84
+ for (const table of managedTables) {
85
+ await client.sync(table);
86
+ }
87
+ await client.resume();
88
+ }
89
+ async function close() {
90
+ await client.close();
91
+ }
92
+ function getState() {
93
+ return client.getState();
94
+ }
95
+ function onStateChange(cb) {
96
+ let lastState = client.getState();
97
+ return client.subscribe(() => {
98
+ const current = client.getState();
99
+ if (current !== lastState) {
100
+ lastState = current;
101
+ cb(current);
102
+ }
103
+ });
104
+ }
105
+ function getPendingCount() {
106
+ return client.getPendingCount();
107
+ }
108
+ function onPendingChange(cb) {
109
+ let lastCount = client.getPendingCount();
110
+ return client.subscribe(() => {
111
+ const current = client.getPendingCount();
112
+ if (current !== lastCount) {
113
+ lastCount = current;
114
+ cb(current);
115
+ }
116
+ });
117
+ }
118
+ function sync(table, options) {
119
+ const isManaged = managedTables.has(table);
120
+ const paramKey = stableKey(options?.params);
121
+ if (!isManaged) {
122
+ const currentParamKey = unmanagedParamKeys.get(table);
123
+ const count = unmanagedRefCounts.get(table) ?? 0;
124
+ if (count === 0 || currentParamKey !== paramKey) {
125
+ if (currentParamKey !== undefined && currentParamKey !== paramKey) {
126
+ client.unsync(table);
127
+ }
128
+ client.sync(table, options?.params, options?.window ? { window: options.window } : undefined);
129
+ client.scheduleBootstrap();
130
+ unmanagedParamKeys.set(table, paramKey);
131
+ }
132
+ unmanagedRefCounts.set(table, count + 1);
133
+ }
134
+ return {
135
+ getRows(getOptions) {
136
+ return client.getRows(table, {
137
+ includeDeleted: getOptions?.includeDeleted ?? options?.includeDeleted
138
+ });
139
+ },
140
+ insert(rowId, payload) {
141
+ client.insert(table, rowId, payload);
142
+ pushSafely(client);
143
+ },
144
+ update(rowId, payload) {
145
+ client.update(table, rowId, payload);
146
+ pushSafely(client);
147
+ },
148
+ remove(rowId) {
149
+ client.delete(table, rowId);
150
+ pushSafely(client);
151
+ },
152
+ onChange(cb) {
153
+ return client.subscribeTable(table, cb);
154
+ },
155
+ destroy() {
156
+ if (isManaged)
157
+ return;
158
+ const count = (unmanagedRefCounts.get(table) ?? 1) - 1;
159
+ if (count <= 0) {
160
+ unmanagedRefCounts.delete(table);
161
+ unmanagedParamKeys.delete(table);
162
+ client.unsync(table);
163
+ } else {
164
+ unmanagedRefCounts.set(table, count);
165
+ }
166
+ }
167
+ };
168
+ }
169
+ function getRow(table, rowId) {
170
+ return client.getRow(table, rowId);
171
+ }
172
+ function onRowChange(table, rowId, cb) {
173
+ return client.subscribeTable(table, () => {
174
+ cb(client.getRow(table, rowId));
175
+ });
176
+ }
177
+ function ephemeral(ephConfig) {
178
+ let events = {};
179
+ const timers = new Map;
180
+ const changeListeners = new Set;
181
+ function notifyChange() {
182
+ for (const listener of changeListeners) {
183
+ listener(events);
184
+ }
185
+ }
186
+ const unsub = client.subscribeEphemeral(ephConfig.key, (event) => {
187
+ events = { ...events, [event.userId]: event.data };
188
+ if (ephConfig.ttlMs) {
189
+ const existing = timers.get(event.userId);
190
+ if (existing) {
191
+ clearTimeout(existing);
192
+ }
193
+ const timer = setTimeout(() => {
194
+ const next = { ...events };
195
+ delete next[event.userId];
196
+ events = next;
197
+ timers.delete(event.userId);
198
+ notifyChange();
199
+ }, ephConfig.ttlMs);
200
+ timers.set(event.userId, timer);
201
+ }
202
+ notifyChange();
203
+ });
204
+ return {
205
+ getEvents() {
206
+ return events;
207
+ },
208
+ broadcast(data) {
209
+ return client.sendEphemeral({
210
+ key: ephConfig.key,
211
+ userId: ephConfig.userId,
212
+ data,
213
+ ttlMs: ephConfig.ttlMs
214
+ });
215
+ },
216
+ onChange(cb) {
217
+ changeListeners.add(cb);
218
+ return () => {
219
+ changeListeners.delete(cb);
220
+ };
221
+ },
222
+ destroy() {
223
+ unsub();
224
+ for (const timer of timers.values()) {
225
+ clearTimeout(timer);
226
+ }
227
+ timers.clear();
228
+ changeListeners.clear();
229
+ }
230
+ };
231
+ }
232
+ function getTotalCount(table) {
233
+ return client.getTotalCount(table);
234
+ }
235
+ function onTotalCountChange(table, cb) {
236
+ let lastCount = client.getTotalCount(table);
237
+ return client.subscribeTable(table, () => {
238
+ const current = client.getTotalCount(table);
239
+ if (current !== lastCount) {
240
+ lastCount = current;
241
+ cb(current);
242
+ }
243
+ });
244
+ }
245
+ function loadMore(table, count) {
246
+ client.loadMore(table, count);
247
+ }
248
+ return {
249
+ client,
250
+ connect,
251
+ close,
252
+ getState,
253
+ onStateChange,
254
+ getPendingCount,
255
+ onPendingChange,
256
+ sync,
257
+ getRow,
258
+ onRowChange,
259
+ ephemeral,
260
+ getTotalCount,
261
+ onTotalCountChange,
262
+ loadMore
263
+ };
264
+ }
265
+ // src/vanilla/typed.ts
266
+ function createSyncVanilla() {
267
+ return {
268
+ createSync(config) {
269
+ const base = createSync(config);
270
+ return base;
271
+ }
272
+ };
273
+ }
274
+ export {
275
+ createSyncVanilla,
276
+ createSync,
277
+ createBrowserWsTransport
278
+ };
package/package.json ADDED
@@ -0,0 +1,253 @@
1
+ {
2
+ "name": "reflectdb",
3
+ "version": "0.1.0",
4
+ "description": "Real-time sync engine for TypeScript — a server database and offline-first browser clients stay in sync, with optimistic writes and typed queries.",
5
+ "keywords": [
6
+ "sync",
7
+ "sync-engine",
8
+ "sync-server",
9
+ "data-sync",
10
+ "state-sync",
11
+ "real-time",
12
+ "realtime",
13
+ "replication",
14
+ "live-query",
15
+ "local-first",
16
+ "offline-first",
17
+ "offline",
18
+ "optimistic-updates",
19
+ "conflict-resolution",
20
+ "last-write-wins",
21
+ "eventual-consistency",
22
+ "hybrid-logical-clock",
23
+ "hlc",
24
+ "op-log",
25
+ "websocket",
26
+ "sse",
27
+ "server-sent-events",
28
+ "long-polling",
29
+ "multiplayer",
30
+ "collaborative",
31
+ "collaborative-editing",
32
+ "presence",
33
+ "typescript",
34
+ "type-safe",
35
+ "react",
36
+ "react-hooks",
37
+ "svelte",
38
+ "drizzle",
39
+ "drizzle-orm",
40
+ "kysely",
41
+ "indexeddb",
42
+ "postgres",
43
+ "sqlite",
44
+ "bun"
45
+ ],
46
+ "homepage": "https://github.com/TimMikeladze/reflectdb#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/TimMikeladze/reflectdb/issues"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/TimMikeladze/reflectdb.git"
53
+ },
54
+ "funding": "https://github.com/sponsors/TimMikeladze",
55
+ "license": "MIT",
56
+ "author": "Tim Mikeladze <tim.mikeladze@gmail.com> (https://github.com/TimMikeladze)",
57
+ "type": "module",
58
+ "sideEffects": false,
59
+ "engines": {
60
+ "node": ">=22"
61
+ },
62
+ "files": [
63
+ "dist"
64
+ ],
65
+ "publishConfig": {
66
+ "access": "public"
67
+ },
68
+ "main": "./dist/cjs/core/index.cjs",
69
+ "module": "./dist/core/index.js",
70
+ "types": "./dist/core/index.d.ts",
71
+ "exports": {
72
+ ".": {
73
+ "import": {
74
+ "types": "./dist/core/index.d.ts",
75
+ "default": "./dist/core/index.js"
76
+ },
77
+ "require": {
78
+ "types": "./dist/cjs/core/index.d.cts",
79
+ "default": "./dist/cjs/core/index.cjs"
80
+ }
81
+ },
82
+ "./core": {
83
+ "import": {
84
+ "types": "./dist/core/index.d.ts",
85
+ "default": "./dist/core/index.js"
86
+ },
87
+ "require": {
88
+ "types": "./dist/cjs/core/index.d.cts",
89
+ "default": "./dist/cjs/core/index.cjs"
90
+ }
91
+ },
92
+ "./client": {
93
+ "import": {
94
+ "types": "./dist/client/index.d.ts",
95
+ "default": "./dist/client/index.js"
96
+ },
97
+ "require": {
98
+ "types": "./dist/cjs/client/index.d.cts",
99
+ "default": "./dist/cjs/client/index.cjs"
100
+ }
101
+ },
102
+ "./server": {
103
+ "import": {
104
+ "types": "./dist/server/index.d.ts",
105
+ "default": "./dist/server/index.js"
106
+ },
107
+ "require": {
108
+ "types": "./dist/cjs/server/index.d.cts",
109
+ "default": "./dist/cjs/server/index.cjs"
110
+ }
111
+ },
112
+ "./server/drizzle": {
113
+ "import": {
114
+ "types": "./dist/server/drizzle.d.ts",
115
+ "default": "./dist/server/drizzle.js"
116
+ },
117
+ "require": {
118
+ "types": "./dist/cjs/server/drizzle.d.cts",
119
+ "default": "./dist/cjs/server/drizzle.cjs"
120
+ }
121
+ },
122
+ "./transport/ws": {
123
+ "import": {
124
+ "types": "./dist/transport/ws.d.ts",
125
+ "default": "./dist/transport/ws.js"
126
+ },
127
+ "require": {
128
+ "types": "./dist/cjs/transport/ws.d.cts",
129
+ "default": "./dist/cjs/transport/ws.cjs"
130
+ }
131
+ },
132
+ "./transport/bun-ws": {
133
+ "import": {
134
+ "types": "./dist/transport/bun-ws.d.ts",
135
+ "default": "./dist/transport/bun-ws.js"
136
+ },
137
+ "require": {
138
+ "types": "./dist/cjs/transport/bun-ws.d.cts",
139
+ "default": "./dist/cjs/transport/bun-ws.cjs"
140
+ }
141
+ },
142
+ "./transport/sse": {
143
+ "import": {
144
+ "types": "./dist/transport/sse.d.ts",
145
+ "default": "./dist/transport/sse.js"
146
+ },
147
+ "require": {
148
+ "types": "./dist/cjs/transport/sse.d.cts",
149
+ "default": "./dist/cjs/transport/sse.cjs"
150
+ }
151
+ },
152
+ "./transport/polling": {
153
+ "import": {
154
+ "types": "./dist/transport/polling.d.ts",
155
+ "default": "./dist/transport/polling.js"
156
+ },
157
+ "require": {
158
+ "types": "./dist/cjs/transport/polling.d.cts",
159
+ "default": "./dist/cjs/transport/polling.cjs"
160
+ }
161
+ },
162
+ "./react": {
163
+ "import": {
164
+ "types": "./dist/react/index.d.ts",
165
+ "default": "./dist/react/index.js"
166
+ },
167
+ "require": {
168
+ "types": "./dist/cjs/react/index.d.cts",
169
+ "default": "./dist/cjs/react/index.cjs"
170
+ }
171
+ },
172
+ "./svelte": {
173
+ "import": {
174
+ "types": "./dist/svelte/index.d.ts",
175
+ "default": "./dist/svelte/index.js"
176
+ },
177
+ "require": {
178
+ "types": "./dist/cjs/svelte/index.d.cts",
179
+ "default": "./dist/cjs/svelte/index.cjs"
180
+ }
181
+ },
182
+ "./vanilla": {
183
+ "import": {
184
+ "types": "./dist/vanilla/index.d.ts",
185
+ "default": "./dist/vanilla/index.js"
186
+ },
187
+ "require": {
188
+ "types": "./dist/cjs/vanilla/index.d.cts",
189
+ "default": "./dist/cjs/vanilla/index.cjs"
190
+ }
191
+ },
192
+ "./client/storage/indexeddb": {
193
+ "import": {
194
+ "types": "./dist/client/storage/indexeddb.d.ts",
195
+ "default": "./dist/client/storage/indexeddb.js"
196
+ },
197
+ "require": {
198
+ "types": "./dist/cjs/client/storage/indexeddb.d.cts",
199
+ "default": "./dist/cjs/client/storage/indexeddb.cjs"
200
+ }
201
+ },
202
+ "./package.json": "./package.json"
203
+ },
204
+ "scripts": {
205
+ "build": "bunup --filter esm && bunup --filter cjs",
206
+ "format": "oxfmt",
207
+ "lint": "oxlint",
208
+ "prepare": "bun simple-git-hooks",
209
+ "release": "bumpp --commit --push --tag",
210
+ "test": "bun test",
211
+ "test:coverage": "bun test --coverage",
212
+ "test:watch": "bun test --watch",
213
+ "test:ui": "bun test/ui/server.tsx",
214
+ "test:sync": "bun test/ui/test-sync.ts",
215
+ "type-check": "tsc --noEmit",
216
+ "verify:exports": "bun scripts/verify-exports.ts",
217
+ "verify:node": "bun scripts/verify-node-consumer.ts"
218
+ },
219
+ "devDependencies": {
220
+ "@types/bun": "^1.3.10",
221
+ "@types/react": "^19.2.14",
222
+ "@types/react-dom": "^19.2.3",
223
+ "bumpp": "^10.4.1",
224
+ "bunup": "^0.16.31",
225
+ "drizzle-orm": "^0.45.1",
226
+ "fake-indexeddb": "^6.2.5",
227
+ "kysely": "^0.28.12",
228
+ "oxfmt": "^0.36.0",
229
+ "oxlint": "^1.51.0",
230
+ "react-dom": "^19.2.4",
231
+ "simple-git-hooks": "^2.13.1",
232
+ "typescript": "^5.9.3"
233
+ },
234
+ "peerDependencies": {
235
+ "drizzle-orm": ">=0.40.0",
236
+ "react": ">=18.0.0",
237
+ "typescript": ">=4.5.0"
238
+ },
239
+ "peerDependenciesMeta": {
240
+ "drizzle-orm": {
241
+ "optional": true
242
+ },
243
+ "react": {
244
+ "optional": true
245
+ },
246
+ "typescript": {
247
+ "optional": true
248
+ }
249
+ },
250
+ "simple-git-hooks": {
251
+ "pre-commit": "bun run lint && bun run type-check"
252
+ }
253
+ }