@syncular/client 0.0.6-95 → 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 (356) hide show
  1. package/README.md +713 -11
  2. package/dist/auth-leases.d.ts +11 -0
  3. package/dist/auth-leases.d.ts.map +1 -0
  4. package/dist/auth-leases.js +140 -0
  5. package/dist/auth-leases.js.map +1 -0
  6. package/dist/blob-limits.d.ts +15 -0
  7. package/dist/blob-limits.d.ts.map +1 -0
  8. package/dist/blob-limits.js +66 -0
  9. package/dist/blob-limits.js.map +1 -0
  10. package/dist/bridge-client.d.ts +69 -0
  11. package/dist/bridge-client.d.ts.map +1 -0
  12. package/dist/bridge-client.js +282 -0
  13. package/dist/bridge-client.js.map +1 -0
  14. package/dist/client-config.d.ts +6 -0
  15. package/dist/client-config.d.ts.map +1 -0
  16. package/dist/client-config.js +18 -0
  17. package/dist/client-config.js.map +1 -0
  18. package/dist/client.d.ts +65 -397
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +150 -960
  21. package/dist/client.js.map +1 -1
  22. package/dist/command-history.d.ts +35 -0
  23. package/dist/command-history.d.ts.map +1 -0
  24. package/dist/command-history.js +378 -0
  25. package/dist/command-history.js.map +1 -0
  26. package/dist/console-diagnostics.d.ts +33 -0
  27. package/dist/console-diagnostics.d.ts.map +1 -0
  28. package/dist/console-diagnostics.js +496 -0
  29. package/dist/console-diagnostics.js.map +1 -0
  30. package/dist/crdt-yjs/index.d.ts +4 -0
  31. package/dist/crdt-yjs/index.d.ts.map +1 -0
  32. package/dist/crdt-yjs/index.js +4 -0
  33. package/dist/crdt-yjs/index.js.map +1 -0
  34. package/dist/crdt-yjs/webview-host-facade.d.ts +126 -0
  35. package/dist/crdt-yjs/webview-host-facade.d.ts.map +1 -0
  36. package/dist/crdt-yjs/webview-host-facade.js +284 -0
  37. package/dist/crdt-yjs/webview-host-facade.js.map +1 -0
  38. package/dist/crdt-yjs/yjs-document-field-adapter.d.ts +153 -0
  39. package/dist/crdt-yjs/yjs-document-field-adapter.d.ts.map +1 -0
  40. package/dist/crdt-yjs/yjs-document-field-adapter.js +406 -0
  41. package/dist/crdt-yjs/yjs-document-field-adapter.js.map +1 -0
  42. package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts +73 -0
  43. package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts.map +1 -0
  44. package/dist/crdt-yjs/yjs-prosemirror-bridge.js +169 -0
  45. package/dist/crdt-yjs/yjs-prosemirror-bridge.js.map +1 -0
  46. package/dist/database.d.ts +83 -0
  47. package/dist/database.d.ts.map +1 -0
  48. package/dist/database.js +916 -0
  49. package/dist/database.js.map +1 -0
  50. package/dist/diagnostics.d.ts +10 -0
  51. package/dist/diagnostics.d.ts.map +1 -0
  52. package/dist/diagnostics.js +83 -0
  53. package/dist/diagnostics.js.map +1 -0
  54. package/dist/errors.d.ts +24 -0
  55. package/dist/errors.d.ts.map +1 -0
  56. package/dist/errors.js +211 -0
  57. package/dist/errors.js.map +1 -0
  58. package/dist/generated-bridge.d.ts +365 -0
  59. package/dist/generated-bridge.d.ts.map +1 -0
  60. package/dist/generated-bridge.js +250 -0
  61. package/dist/generated-bridge.js.map +1 -0
  62. package/dist/index.d.ts +13 -23
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +14 -25
  65. package/dist/index.js.map +1 -1
  66. package/dist/mutations.d.ts +4 -72
  67. package/dist/mutations.d.ts.map +1 -1
  68. package/dist/mutations.js +2 -568
  69. package/dist/mutations.js.map +1 -1
  70. package/dist/network.d.ts +3 -0
  71. package/dist/network.d.ts.map +1 -0
  72. package/dist/network.js +17 -0
  73. package/dist/network.js.map +1 -0
  74. package/dist/react/index.d.ts +169 -0
  75. package/dist/react/index.d.ts.map +1 -0
  76. package/dist/react/index.js +628 -0
  77. package/dist/react/index.js.map +1 -0
  78. package/dist/react-native/index.d.ts +35 -0
  79. package/dist/react-native/index.d.ts.map +1 -0
  80. package/dist/react-native/index.js +49 -0
  81. package/dist/react-native/index.js.map +1 -0
  82. package/dist/runtime-contract.d.ts +13 -0
  83. package/dist/runtime-contract.d.ts.map +1 -0
  84. package/dist/runtime-contract.js +24 -0
  85. package/dist/runtime-contract.js.map +1 -0
  86. package/dist/rust-client.d.ts +106 -0
  87. package/dist/rust-client.d.ts.map +1 -0
  88. package/dist/rust-client.js +936 -0
  89. package/dist/rust-client.js.map +1 -0
  90. package/dist/sentry.d.ts +35 -0
  91. package/dist/sentry.d.ts.map +1 -0
  92. package/dist/sentry.js +155 -0
  93. package/dist/sentry.js.map +1 -0
  94. package/dist/sql-safety.d.ts +3 -0
  95. package/dist/sql-safety.d.ts.map +1 -0
  96. package/dist/sql-safety.js +62 -0
  97. package/dist/sql-safety.js.map +1 -0
  98. package/dist/syncular-runtime-artifacts.json +61 -0
  99. package/dist/tauri/index.d.ts +35 -0
  100. package/dist/tauri/index.d.ts.map +1 -0
  101. package/dist/tauri/index.js +114 -0
  102. package/dist/tauri/index.js.map +1 -0
  103. package/dist/types.d.ts +1059 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +2 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/wasm/.syncular-wasm-profile +1 -0
  108. package/dist/wasm/syncular-runtime-artifact.json +21 -0
  109. package/dist/wasm/syncular.d.ts +207 -0
  110. package/dist/wasm/syncular.js +2341 -0
  111. package/dist/wasm/syncular_bg.wasm +0 -0
  112. package/dist/wasm/syncular_bg.wasm.d.ts +97 -0
  113. package/dist/wasm-bindings/runtime-contract.d.ts +22 -0
  114. package/dist/wasm-bindings/runtime-contract.d.ts.map +1 -0
  115. package/dist/wasm-bindings/runtime-contract.js +112 -0
  116. package/dist/wasm-bindings/runtime-contract.js.map +1 -0
  117. package/dist/wasm-core/.syncular-wasm-profile +1 -0
  118. package/dist/wasm-core/syncular-runtime-artifact.json +17 -0
  119. package/dist/wasm-core/syncular.d.ts +162 -0
  120. package/dist/wasm-core/syncular.js +1847 -0
  121. package/dist/wasm-core/syncular_bg.wasm +0 -0
  122. package/dist/wasm-core/syncular_bg.wasm.d.ts +77 -0
  123. package/dist/wasm-perf/.syncular-wasm-profile +1 -0
  124. package/dist/wasm-perf/syncular-runtime-artifact.json +21 -0
  125. package/dist/wasm-perf/syncular.d.ts +207 -0
  126. package/dist/wasm-perf/syncular.js +2341 -0
  127. package/dist/wasm-perf/syncular_bg.wasm +0 -0
  128. package/dist/wasm-perf/syncular_bg.wasm.d.ts +97 -0
  129. package/dist/wasm-runtime.d.ts +18 -0
  130. package/dist/wasm-runtime.d.ts.map +1 -0
  131. package/dist/wasm-runtime.js +28 -0
  132. package/dist/wasm-runtime.js.map +1 -0
  133. package/dist/worker-client.d.ts +123 -0
  134. package/dist/worker-client.d.ts.map +1 -0
  135. package/dist/worker-client.js +1686 -0
  136. package/dist/worker-client.js.map +1 -0
  137. package/dist/worker-entry.d.ts +2 -0
  138. package/dist/worker-entry.d.ts.map +1 -0
  139. package/dist/worker-entry.js +479 -0
  140. package/dist/worker-entry.js.map +1 -0
  141. package/dist/worker-protocol.d.ts +64 -0
  142. package/dist/worker-protocol.d.ts.map +1 -0
  143. package/dist/worker-protocol.js +16 -0
  144. package/dist/worker-protocol.js.map +1 -0
  145. package/dist/worker-realtime.d.ts +39 -0
  146. package/dist/worker-realtime.d.ts.map +1 -0
  147. package/dist/worker-realtime.js +677 -0
  148. package/dist/worker-realtime.js.map +1 -0
  149. package/package.json +124 -17
  150. package/src/auth-leases.ts +251 -0
  151. package/src/blob-limits.ts +98 -0
  152. package/src/bridge-client.ts +512 -0
  153. package/src/client-config.ts +29 -0
  154. package/src/client.ts +256 -1414
  155. package/src/command-history.ts +623 -0
  156. package/src/console-diagnostics.ts +617 -0
  157. package/src/crdt-yjs/index.ts +3 -0
  158. package/src/crdt-yjs/webview-host-facade.ts +477 -0
  159. package/src/crdt-yjs/yjs-document-field-adapter.ts +733 -0
  160. package/src/crdt-yjs/yjs-prosemirror-bridge.ts +272 -0
  161. package/src/database.ts +1342 -0
  162. package/src/diagnostics.ts +116 -0
  163. package/src/errors.ts +269 -0
  164. package/src/generated-bridge.ts +741 -0
  165. package/src/index.ts +21 -25
  166. package/src/mutations.ts +12 -821
  167. package/src/network.ts +32 -0
  168. package/src/react/index.ts +1036 -0
  169. package/src/react-native/index.ts +152 -0
  170. package/src/runtime-contract.ts +48 -0
  171. package/src/rust-client.ts +1488 -0
  172. package/src/sentry.ts +215 -0
  173. package/src/sql-safety.ts +61 -0
  174. package/src/tauri/index.ts +211 -0
  175. package/src/types.ts +1356 -0
  176. package/src/wasm-bindings/generated-wasm-bindings.d.ts +70 -0
  177. package/src/wasm-bindings/runtime-contract.ts +158 -0
  178. package/src/wasm-runtime.ts +76 -0
  179. package/src/worker-client.ts +2215 -0
  180. package/src/worker-entry.ts +584 -0
  181. package/src/worker-protocol.ts +109 -0
  182. package/src/worker-realtime.ts +843 -0
  183. package/dist/blobs/index.d.ts +0 -7
  184. package/dist/blobs/index.d.ts.map +0 -1
  185. package/dist/blobs/index.js +0 -7
  186. package/dist/blobs/index.js.map +0 -1
  187. package/dist/blobs/manager.d.ts +0 -345
  188. package/dist/blobs/manager.d.ts.map +0 -1
  189. package/dist/blobs/manager.js +0 -749
  190. package/dist/blobs/manager.js.map +0 -1
  191. package/dist/blobs/migrate.d.ts +0 -14
  192. package/dist/blobs/migrate.d.ts.map +0 -1
  193. package/dist/blobs/migrate.js +0 -59
  194. package/dist/blobs/migrate.js.map +0 -1
  195. package/dist/blobs/types.d.ts +0 -62
  196. package/dist/blobs/types.d.ts.map +0 -1
  197. package/dist/blobs/types.js +0 -5
  198. package/dist/blobs/types.js.map +0 -1
  199. package/dist/conflicts.d.ts +0 -31
  200. package/dist/conflicts.d.ts.map +0 -1
  201. package/dist/conflicts.js +0 -112
  202. package/dist/conflicts.js.map +0 -1
  203. package/dist/create-client.d.ts +0 -118
  204. package/dist/create-client.d.ts.map +0 -1
  205. package/dist/create-client.js +0 -178
  206. package/dist/create-client.js.map +0 -1
  207. package/dist/engine/SyncEngine.d.ts +0 -272
  208. package/dist/engine/SyncEngine.d.ts.map +0 -1
  209. package/dist/engine/SyncEngine.js +0 -1860
  210. package/dist/engine/SyncEngine.js.map +0 -1
  211. package/dist/engine/index.d.ts +0 -6
  212. package/dist/engine/index.d.ts.map +0 -1
  213. package/dist/engine/index.js +0 -6
  214. package/dist/engine/index.js.map +0 -1
  215. package/dist/engine/types.d.ts +0 -345
  216. package/dist/engine/types.d.ts.map +0 -1
  217. package/dist/engine/types.js +0 -7
  218. package/dist/engine/types.js.map +0 -1
  219. package/dist/handlers/collection.d.ts +0 -6
  220. package/dist/handlers/collection.d.ts.map +0 -1
  221. package/dist/handlers/collection.js +0 -21
  222. package/dist/handlers/collection.js.map +0 -1
  223. package/dist/handlers/create-handler.d.ts +0 -120
  224. package/dist/handlers/create-handler.d.ts.map +0 -1
  225. package/dist/handlers/create-handler.js +0 -145
  226. package/dist/handlers/create-handler.js.map +0 -1
  227. package/dist/handlers/types.d.ts +0 -83
  228. package/dist/handlers/types.d.ts.map +0 -1
  229. package/dist/handlers/types.js +0 -5
  230. package/dist/handlers/types.js.map +0 -1
  231. package/dist/migrate.d.ts +0 -19
  232. package/dist/migrate.d.ts.map +0 -1
  233. package/dist/migrate.js +0 -238
  234. package/dist/migrate.js.map +0 -1
  235. package/dist/outbox.d.ts +0 -112
  236. package/dist/outbox.d.ts.map +0 -1
  237. package/dist/outbox.js +0 -294
  238. package/dist/outbox.js.map +0 -1
  239. package/dist/plugins/incrementing-version.d.ts +0 -34
  240. package/dist/plugins/incrementing-version.d.ts.map +0 -1
  241. package/dist/plugins/incrementing-version.js +0 -83
  242. package/dist/plugins/incrementing-version.js.map +0 -1
  243. package/dist/plugins/index.d.ts +0 -3
  244. package/dist/plugins/index.d.ts.map +0 -1
  245. package/dist/plugins/index.js +0 -3
  246. package/dist/plugins/index.js.map +0 -1
  247. package/dist/plugins/types.d.ts +0 -49
  248. package/dist/plugins/types.d.ts.map +0 -1
  249. package/dist/plugins/types.js +0 -15
  250. package/dist/plugins/types.js.map +0 -1
  251. package/dist/proxy/connection.d.ts +0 -33
  252. package/dist/proxy/connection.d.ts.map +0 -1
  253. package/dist/proxy/connection.js +0 -153
  254. package/dist/proxy/connection.js.map +0 -1
  255. package/dist/proxy/dialect.d.ts +0 -46
  256. package/dist/proxy/dialect.d.ts.map +0 -1
  257. package/dist/proxy/dialect.js +0 -58
  258. package/dist/proxy/dialect.js.map +0 -1
  259. package/dist/proxy/driver.d.ts +0 -42
  260. package/dist/proxy/driver.d.ts.map +0 -1
  261. package/dist/proxy/driver.js +0 -78
  262. package/dist/proxy/driver.js.map +0 -1
  263. package/dist/proxy/index.d.ts +0 -10
  264. package/dist/proxy/index.d.ts.map +0 -1
  265. package/dist/proxy/index.js +0 -10
  266. package/dist/proxy/index.js.map +0 -1
  267. package/dist/proxy/mutations.d.ts +0 -9
  268. package/dist/proxy/mutations.d.ts.map +0 -1
  269. package/dist/proxy/mutations.js +0 -11
  270. package/dist/proxy/mutations.js.map +0 -1
  271. package/dist/pull-engine.d.ts +0 -48
  272. package/dist/pull-engine.d.ts.map +0 -1
  273. package/dist/pull-engine.js +0 -648
  274. package/dist/pull-engine.js.map +0 -1
  275. package/dist/push-engine.d.ts +0 -18
  276. package/dist/push-engine.d.ts.map +0 -1
  277. package/dist/push-engine.js +0 -167
  278. package/dist/push-engine.js.map +0 -1
  279. package/dist/query/FingerprintCollector.d.ts +0 -18
  280. package/dist/query/FingerprintCollector.d.ts.map +0 -1
  281. package/dist/query/FingerprintCollector.js +0 -28
  282. package/dist/query/FingerprintCollector.js.map +0 -1
  283. package/dist/query/QueryContext.d.ts +0 -33
  284. package/dist/query/QueryContext.d.ts.map +0 -1
  285. package/dist/query/QueryContext.js +0 -16
  286. package/dist/query/QueryContext.js.map +0 -1
  287. package/dist/query/fingerprint.d.ts +0 -61
  288. package/dist/query/fingerprint.d.ts.map +0 -1
  289. package/dist/query/fingerprint.js +0 -91
  290. package/dist/query/fingerprint.js.map +0 -1
  291. package/dist/query/index.d.ts +0 -7
  292. package/dist/query/index.d.ts.map +0 -1
  293. package/dist/query/index.js +0 -7
  294. package/dist/query/index.js.map +0 -1
  295. package/dist/query/tracked-select.d.ts +0 -18
  296. package/dist/query/tracked-select.d.ts.map +0 -1
  297. package/dist/query/tracked-select.js +0 -90
  298. package/dist/query/tracked-select.js.map +0 -1
  299. package/dist/schema.d.ts +0 -83
  300. package/dist/schema.d.ts.map +0 -1
  301. package/dist/schema.js +0 -7
  302. package/dist/schema.js.map +0 -1
  303. package/dist/subscription-state.d.ts +0 -46
  304. package/dist/subscription-state.d.ts.map +0 -1
  305. package/dist/subscription-state.js +0 -185
  306. package/dist/subscription-state.js.map +0 -1
  307. package/dist/sync-loop.d.ts +0 -34
  308. package/dist/sync-loop.d.ts.map +0 -1
  309. package/dist/sync-loop.js +0 -253
  310. package/dist/sync-loop.js.map +0 -1
  311. package/dist/sync.d.ts +0 -32
  312. package/dist/sync.d.ts.map +0 -1
  313. package/dist/sync.js +0 -55
  314. package/dist/sync.js.map +0 -1
  315. package/dist/utils/id.d.ts +0 -17
  316. package/dist/utils/id.d.ts.map +0 -1
  317. package/dist/utils/id.js +0 -46
  318. package/dist/utils/id.js.map +0 -1
  319. package/src/blobs/index.ts +0 -7
  320. package/src/blobs/manager.ts +0 -1027
  321. package/src/blobs/migrate.ts +0 -67
  322. package/src/blobs/types.ts +0 -84
  323. package/src/client.test.ts +0 -417
  324. package/src/conflicts.ts +0 -171
  325. package/src/create-client.test.ts +0 -83
  326. package/src/create-client.ts +0 -330
  327. package/src/engine/SyncEngine.test.ts +0 -254
  328. package/src/engine/SyncEngine.ts +0 -2375
  329. package/src/engine/index.ts +0 -6
  330. package/src/engine/types.ts +0 -419
  331. package/src/handlers/collection.ts +0 -36
  332. package/src/handlers/create-handler.ts +0 -330
  333. package/src/handlers/types.ts +0 -108
  334. package/src/migrate.ts +0 -318
  335. package/src/outbox.ts +0 -383
  336. package/src/plugins/incrementing-version.ts +0 -133
  337. package/src/plugins/index.ts +0 -2
  338. package/src/plugins/types.ts +0 -63
  339. package/src/proxy/connection.ts +0 -191
  340. package/src/proxy/dialect.ts +0 -76
  341. package/src/proxy/driver.ts +0 -126
  342. package/src/proxy/index.ts +0 -10
  343. package/src/proxy/mutations.ts +0 -18
  344. package/src/pull-engine.test.ts +0 -151
  345. package/src/pull-engine.ts +0 -855
  346. package/src/push-engine.ts +0 -216
  347. package/src/query/FingerprintCollector.ts +0 -29
  348. package/src/query/QueryContext.ts +0 -54
  349. package/src/query/fingerprint.ts +0 -109
  350. package/src/query/index.ts +0 -10
  351. package/src/query/tracked-select.ts +0 -139
  352. package/src/schema.ts +0 -94
  353. package/src/subscription-state.ts +0 -259
  354. package/src/sync-loop.ts +0 -379
  355. package/src/sync.ts +0 -170
  356. package/src/utils/id.ts +0 -55
@@ -1,216 +0,0 @@
1
- /**
2
- * @syncular/client - Sync push engine (commit-based)
3
- */
4
-
5
- import type {
6
- SyncPushRequest,
7
- SyncPushResponse,
8
- SyncTransport,
9
- } from '@syncular/core';
10
- import { countSyncMetric } from '@syncular/core';
11
- import type { Kysely } from 'kysely';
12
- import { upsertConflictsForRejectedCommit } from './conflicts';
13
- import {
14
- getNextSendableOutboxCommit,
15
- markOutboxCommitAcked,
16
- markOutboxCommitFailed,
17
- markOutboxCommitPending,
18
- } from './outbox';
19
- import type {
20
- SyncClientPlugin,
21
- SyncClientPluginContext,
22
- } from './plugins/types';
23
- import type { SyncClientDb } from './schema';
24
-
25
- export interface SyncPushOnceOptions {
26
- clientId: string;
27
- actorId?: string;
28
- plugins?: SyncClientPlugin[];
29
- }
30
-
31
- export interface SyncPushOnceResult {
32
- pushed: boolean;
33
- response?: SyncPushResponse;
34
- }
35
-
36
- interface TransportWithWsPush extends SyncTransport {
37
- pushViaWs(request: SyncPushRequest): Promise<SyncPushResponse | null>;
38
- }
39
-
40
- function hasPushViaWs(
41
- transport: SyncTransport
42
- ): transport is TransportWithWsPush {
43
- return 'pushViaWs' in transport && typeof transport.pushViaWs === 'function';
44
- }
45
-
46
- function clonePushRequest(request: SyncPushRequest): SyncPushRequest {
47
- if (typeof structuredClone === 'function') return structuredClone(request);
48
- return JSON.parse(JSON.stringify(request)) as SyncPushRequest;
49
- }
50
-
51
- export async function syncPushOnce<DB extends SyncClientDb>(
52
- db: Kysely<DB>,
53
- transport: SyncTransport,
54
- options: SyncPushOnceOptions
55
- ): Promise<SyncPushOnceResult> {
56
- // getNextSendableOutboxCommit now atomically claims the commit
57
- // (marks it as 'sending' and returns it in one operation)
58
- const next = await getNextSendableOutboxCommit(db);
59
- if (!next) return { pushed: false };
60
-
61
- const request: SyncPushRequest = {
62
- clientId: options.clientId,
63
- clientCommitId: next.client_commit_id,
64
- operations: next.operations,
65
- schemaVersion: next.schema_version,
66
- };
67
- const ctx: SyncClientPluginContext = {
68
- actorId: options.actorId ?? 'unknown',
69
- clientId: options.clientId,
70
- };
71
- const plugins = options.plugins ?? [];
72
-
73
- let requestToSend = request;
74
- if (plugins.length > 0) {
75
- try {
76
- requestToSend = clonePushRequest(request);
77
- for (const plugin of plugins) {
78
- if (!plugin.beforePush) continue;
79
- requestToSend = await plugin.beforePush(ctx, requestToSend);
80
- }
81
- } catch (err) {
82
- const message = err instanceof Error ? err.message : 'Unknown error';
83
- await markOutboxCommitPending(db, { id: next.id, error: message });
84
- throw err;
85
- }
86
- }
87
-
88
- let res: SyncPushResponse;
89
- let usedWsPush = false;
90
- try {
91
- // Try WS push first if the transport supports it
92
- let wsResponse: SyncPushResponse | null = null;
93
- if (hasPushViaWs(transport)) {
94
- wsResponse = await transport.pushViaWs(requestToSend);
95
- }
96
-
97
- if (wsResponse) {
98
- res = wsResponse;
99
- usedWsPush = true;
100
- } else {
101
- // Fall back to HTTP
102
- const combined = await transport.sync({
103
- clientId: requestToSend.clientId,
104
- push: {
105
- clientCommitId: requestToSend.clientCommitId,
106
- operations: requestToSend.operations,
107
- schemaVersion: requestToSend.schemaVersion,
108
- },
109
- });
110
- if (!combined.push) {
111
- throw new Error('Server returned no push response');
112
- }
113
- res = combined.push;
114
- }
115
- } catch (err) {
116
- const message = err instanceof Error ? err.message : 'Unknown error';
117
- // Treat transport exceptions as retryable. The sync loop already applies backoff,
118
- // and failed commits are reserved for terminal server rejections (e.g. conflicts).
119
- await markOutboxCommitPending(db, { id: next.id, error: message });
120
- throw err;
121
- }
122
-
123
- let responseToUse = res;
124
- if (plugins.length > 0) {
125
- try {
126
- for (const plugin of plugins) {
127
- if (!plugin.afterPush) continue;
128
- responseToUse = await plugin.afterPush(ctx, {
129
- request: requestToSend,
130
- response: responseToUse,
131
- });
132
- }
133
- } catch (err) {
134
- // The server already received and processed this commit. Persist the raw response
135
- // so we don't end up retrying a commit that was already applied.
136
- const responseJson = JSON.stringify(res);
137
-
138
- if (res.status === 'applied' || res.status === 'cached') {
139
- await markOutboxCommitAcked(db, {
140
- id: next.id,
141
- commitSeq: res.commitSeq ?? null,
142
- responseJson,
143
- });
144
- } else {
145
- await upsertConflictsForRejectedCommit(db, {
146
- outboxCommitId: next.id,
147
- clientCommitId: next.client_commit_id,
148
- response: res,
149
- });
150
- await markOutboxCommitFailed(db, {
151
- id: next.id,
152
- error: 'REJECTED',
153
- responseJson,
154
- });
155
- }
156
-
157
- throw err;
158
- }
159
- }
160
-
161
- const responseJson = JSON.stringify(responseToUse);
162
- const detectedConflicts = responseToUse.results.reduce(
163
- (count, result) => count + (result.status === 'conflict' ? 1 : 0),
164
- 0
165
- );
166
- if (detectedConflicts > 0 && !usedWsPush) {
167
- countSyncMetric('sync.conflicts.detected', detectedConflicts, {
168
- attributes: {
169
- source: 'client',
170
- transport: 'http',
171
- },
172
- });
173
- }
174
-
175
- if (responseToUse.status === 'applied' || responseToUse.status === 'cached') {
176
- await markOutboxCommitAcked(db, {
177
- id: next.id,
178
- commitSeq: responseToUse.commitSeq ?? null,
179
- responseJson,
180
- });
181
- return { pushed: true, response: responseToUse };
182
- }
183
-
184
- // Check if all errors are retriable - if so, keep pending for retry
185
- const errorResults = responseToUse.results.filter(
186
- (r) => r.status === 'error'
187
- );
188
- const allRetriable =
189
- errorResults.length > 0 && errorResults.every((r) => r.retriable === true);
190
-
191
- if (allRetriable) {
192
- // All errors are retriable - keep commit pending for retry
193
- const errorMessages = errorResults
194
- .map((r) => r.error ?? 'Unknown error')
195
- .join('; ');
196
- await markOutboxCommitPending(db, {
197
- id: next.id,
198
- error: `Retriable: ${errorMessages}`,
199
- responseJson,
200
- });
201
- return { pushed: true, response: responseToUse };
202
- }
203
-
204
- // Terminal rejection - mark as failed and record conflicts
205
- await upsertConflictsForRejectedCommit(db, {
206
- outboxCommitId: next.id,
207
- clientCommitId: next.client_commit_id,
208
- response: responseToUse,
209
- });
210
- await markOutboxCommitFailed(db, {
211
- id: next.id,
212
- error: 'REJECTED',
213
- responseJson,
214
- });
215
- return { pushed: true, response: responseToUse };
216
- }
@@ -1,29 +0,0 @@
1
- /**
2
- * @syncular/client - Fingerprint Collector
3
- *
4
- * Accumulates fingerprint data from multiple queries.
5
- */
6
-
7
- /**
8
- * Fingerprint collector accumulates fingerprint data from multiple queries
9
- */
10
- export class FingerprintCollector {
11
- private fingerprints: string[] = [];
12
-
13
- add(fingerprint: string): void {
14
- this.fingerprints.push(fingerprint);
15
- }
16
-
17
- /**
18
- * Get combined fingerprint from all collected fingerprints
19
- */
20
- getCombined(): string {
21
- if (this.fingerprints.length === 0) return '';
22
- if (this.fingerprints.length === 1) return this.fingerprints[0] ?? '';
23
- return this.fingerprints.join('|');
24
- }
25
-
26
- clear(): void {
27
- this.fingerprints = [];
28
- }
29
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * @syncular/client - Query Context
3
- *
4
- * Provides a query context with tracked selectFrom for scope tracking
5
- * and automatic fingerprint generation.
6
- */
7
-
8
- import type { Kysely } from 'kysely';
9
- import type { SyncClientDb } from '../schema';
10
- import type { FingerprintCollector } from './FingerprintCollector';
11
- import type { MutationTimestampSource } from './fingerprint';
12
- import { createTrackedSelectFrom } from './tracked-select';
13
-
14
- export type TrackedSelectFrom<DB> = ReturnType<
15
- typeof createTrackedSelectFrom<DB>
16
- >;
17
-
18
- /**
19
- * Query context provided to query functions.
20
- *
21
- * Only `selectFrom` is exposed to ensure proper scope tracking and fingerprinting.
22
- * If you need raw database access, use the db directly outside the query function.
23
- */
24
- export interface QueryContext<DB extends SyncClientDb = SyncClientDb> {
25
- /**
26
- * Wrapped selectFrom that:
27
- * 1. Registers table as watched scope
28
- * 2. Intercepts .execute() to auto-detect fingerprinting mode:
29
- * - Result has keyField (default: 'id')? -> row-level fingerprinting
30
- * - No keyField? -> value-based fingerprinting (for aggregates)
31
- */
32
- selectFrom: TrackedSelectFrom<DB>;
33
- }
34
-
35
- /**
36
- * Create a query context with tracked selectFrom.
37
- */
38
- export function createQueryContext<DB extends SyncClientDb>(
39
- db: Kysely<DB>,
40
- scopeCollector: Set<string>,
41
- fingerprintCollector: FingerprintCollector,
42
- engine: MutationTimestampSource,
43
- keyField = 'id'
44
- ): QueryContext<DB> {
45
- return {
46
- selectFrom: createTrackedSelectFrom(
47
- db,
48
- scopeCollector,
49
- fingerprintCollector,
50
- engine,
51
- keyField
52
- ),
53
- };
54
- }
@@ -1,109 +0,0 @@
1
- /**
2
- * @syncular/client - Fingerprint-based rerender optimization utilities
3
- *
4
- * Provides efficient fingerprint computation for query results to avoid
5
- * expensive deep equality checks. Uses mutation timestamps from the SyncEngine.
6
- */
7
-
8
- export interface MutationTimestampSource {
9
- getMutationTimestamp(table: string, rowId: string): number;
10
- }
11
-
12
- /**
13
- * Compute a fingerprint for query results based on length + ids + mutation timestamps.
14
- * Much faster than deep equality for large datasets.
15
- *
16
- * Fingerprint format: `length:id1@ts1,id2@ts2,...`
17
- *
18
- * @param rows - Query result rows (must have an id-like field)
19
- * @param engine - SyncEngine to look up mutation timestamps
20
- * @param table - Table name for timestamp lookup
21
- * @param keyField - Field name to use as row identifier (default: 'id')
22
- * @returns Fingerprint string for comparison
23
- *
24
- * @example
25
- * ```ts
26
- * const fingerprint = computeFingerprint(tasks, engine, 'tasks', 'id');
27
- * // Returns: "3:abc@1706123456789,def@1706123456790,ghi@0"
28
- * ```
29
- */
30
- export function computeFingerprint<T extends Record<string, unknown>>(
31
- rows: T[],
32
- engine: MutationTimestampSource,
33
- table: string,
34
- keyField = 'id'
35
- ): string {
36
- if (rows.length === 0) return '0:';
37
-
38
- const parts: string[] = [];
39
- for (const row of rows) {
40
- const id = String(row[keyField] ?? '');
41
- const ts = engine.getMutationTimestamp(table, id);
42
- parts.push(`${id}@${ts}`);
43
- }
44
-
45
- return `${rows.length}:${parts.join(',')}`;
46
- }
47
-
48
- /**
49
- * Check if rows have the required key field for fingerprinting.
50
- * Returns true for empty arrays (no data to fingerprint).
51
- *
52
- * @param rows - Query result rows to check
53
- * @param keyField - Field name to check for (default: 'id')
54
- * @returns true if rows can be fingerprinted, false otherwise
55
- *
56
- * @example
57
- * ```ts
58
- * // Can fingerprint - rows have 'id' field
59
- * canFingerprint([{ id: '1', name: 'foo' }]); // true
60
- *
61
- * // Cannot fingerprint - rows lack 'id' field (aggregates, etc.)
62
- * canFingerprint([{ count: 42 }]); // false
63
- * ```
64
- */
65
- export function canFingerprint<T>(rows: T[], keyField = 'id'): boolean {
66
- if (rows.length === 0) return true;
67
- return keyField in (rows[0] as Record<string, unknown>);
68
- }
69
-
70
- /**
71
- * Compute row-level fingerprint from query results.
72
- * Format: `table:count:id1@ts1,id2@ts2,...`
73
- */
74
- export function computeRowFingerprint(
75
- rows: unknown[],
76
- table: string,
77
- engine: MutationTimestampSource,
78
- keyField: string
79
- ): string {
80
- if (rows.length === 0) return `${table}:0:`;
81
-
82
- const parts: string[] = [];
83
- for (const row of rows) {
84
- const r = row as Record<string, unknown>;
85
- const id = String(r[keyField] ?? '');
86
- const ts = engine.getMutationTimestamp(table, id);
87
- parts.push(`${id}@${ts}`);
88
- }
89
-
90
- return `${table}:${rows.length}:${parts.join(',')}`;
91
- }
92
-
93
- /**
94
- * Compute value-based fingerprint for aggregate/scalar queries.
95
- * Format: `table:hash(value)`
96
- */
97
- export function computeValueFingerprint(table: string, value: unknown): string {
98
- // Simple hash of the JSON representation
99
- const json = JSON.stringify(value);
100
- return `${table}:${json}`;
101
- }
102
-
103
- /**
104
- * Check if result rows have the key field for row-level fingerprinting.
105
- */
106
- export function hasKeyField(rows: unknown[], keyField: string): boolean {
107
- if (rows.length === 0) return false;
108
- return keyField in (rows[0] as Record<string, unknown>);
109
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * @syncular/client - Query utilities exports
3
- */
4
-
5
- export { FingerprintCollector } from './FingerprintCollector';
6
- export {
7
- canFingerprint,
8
- computeFingerprint,
9
- } from './fingerprint';
10
- export { createQueryContext, type QueryContext } from './QueryContext';
@@ -1,139 +0,0 @@
1
- /**
2
- * @syncular/client - Tracked SelectFrom
3
- *
4
- * Provides a wrapped selectFrom that:
5
- * 1. Tracks tables as watched scopes when called
6
- * 2. Intercepts .execute() to auto-generate fingerprints
7
- */
8
-
9
- import type { Kysely } from 'kysely';
10
- import type { FingerprintCollector } from './FingerprintCollector';
11
-
12
- /** Portable type alias for Kysely's selectFrom method signature */
13
- type TrackedSelectFrom<DB> = Kysely<DB>['selectFrom'];
14
-
15
- import {
16
- computeRowFingerprint,
17
- computeValueFingerprint,
18
- hasKeyField,
19
- type MutationTimestampSource,
20
- } from './fingerprint';
21
-
22
- /**
23
- * Create a proxy that intercepts execute() to compute fingerprints.
24
- */
25
-
26
- type ExecutableQuery = {
27
- execute: () => Promise<unknown>;
28
- executeTakeFirst: () => Promise<unknown>;
29
- executeTakeFirstOrThrow: () => Promise<unknown>;
30
- };
31
-
32
- function isExecutableQuery(value: unknown): value is ExecutableQuery {
33
- if (typeof value !== 'object' || value === null) return false;
34
- return (
35
- typeof Reflect.get(value, 'execute') === 'function' &&
36
- typeof Reflect.get(value, 'executeTakeFirst') === 'function' &&
37
- typeof Reflect.get(value, 'executeTakeFirstOrThrow') === 'function'
38
- );
39
- }
40
-
41
- function createExecuteProxy<B extends ExecutableQuery>(
42
- builder: B,
43
- table: string,
44
- collector: FingerprintCollector,
45
- engine: MutationTimestampSource,
46
- keyField: string
47
- ): B {
48
- return new Proxy(builder, {
49
- get(target, prop: string | symbol) {
50
- if (prop === 'execute') {
51
- return async () => {
52
- const rows = await target.execute();
53
- // Auto-detect fingerprint mode based on result shape
54
- if (Array.isArray(rows)) {
55
- if (hasKeyField(rows, keyField)) {
56
- // Row-level fingerprinting - result has keyField
57
- const fp = computeRowFingerprint(rows, table, engine, keyField);
58
- collector.add(fp);
59
- } else {
60
- // Value-based fingerprinting - for aggregates/scalars
61
- const fp = computeValueFingerprint(table, rows);
62
- collector.add(fp);
63
- }
64
- } else {
65
- // Unexpected, but keep rerender behavior deterministic.
66
- const fp = computeValueFingerprint(table, rows);
67
- collector.add(fp);
68
- }
69
- return rows;
70
- };
71
- }
72
- if (prop === 'executeTakeFirst') {
73
- return async () => {
74
- const row = await target.executeTakeFirst();
75
- // Value-based fingerprinting for single-row queries
76
- const fp = computeValueFingerprint(table, row);
77
- collector.add(fp);
78
- return row;
79
- };
80
- }
81
- if (prop === 'executeTakeFirstOrThrow') {
82
- return async () => {
83
- const row = await target.executeTakeFirstOrThrow();
84
- // Value-based fingerprinting for single-row queries
85
- const fp = computeValueFingerprint(table, row);
86
- collector.add(fp);
87
- return row;
88
- };
89
- }
90
- // For other methods, return wrapped builder for chaining
91
- const value = Reflect.get(target, prop, target);
92
- if (typeof value === 'function') {
93
- return (...args: unknown[]) => {
94
- const result = Reflect.apply(value, target, args);
95
- if (isExecutableQuery(result)) {
96
- return createExecuteProxy(
97
- result,
98
- table,
99
- collector,
100
- engine,
101
- keyField
102
- );
103
- }
104
- return result;
105
- };
106
- }
107
- return value;
108
- },
109
- });
110
- }
111
-
112
- /**
113
- * Create a tracked selectFrom that registers scopes and generates fingerprints.
114
- */
115
- export function createTrackedSelectFrom<DB>(
116
- db: Kysely<DB>,
117
- scopeCollector: Set<string>,
118
- fingerprintCollector: FingerprintCollector,
119
- engine: MutationTimestampSource,
120
- keyField = 'id'
121
- ): TrackedSelectFrom<DB> {
122
- const selectFrom = <TB extends keyof DB & string>(table: TB) => {
123
- // 1. Register this table as a watched scope
124
- scopeCollector.add(table);
125
-
126
- // 2. Get the real query builder
127
- const builder = db.selectFrom(table);
128
-
129
- // 3. Return a proxy that intercepts .execute()
130
- return createExecuteProxy(
131
- builder,
132
- table,
133
- fingerprintCollector,
134
- engine,
135
- keyField
136
- );
137
- };
138
- return selectFrom as TrackedSelectFrom<DB>;
139
- }
package/src/schema.ts DELETED
@@ -1,94 +0,0 @@
1
- /**
2
- * @syncular/client - Sync client schema types (SQLite reference)
3
- *
4
- * These tables are sync-internal and should not collide with app tables.
5
- */
6
-
7
- import type { Generated, Kysely } from 'kysely';
8
- import type { SyncBlobClientDb } from './blobs/types';
9
-
10
- /**
11
- * Database executor type that both Kysely and Transaction satisfy.
12
- * Use this when a function needs to work with either a raw Kysely instance
13
- * or within a transaction context.
14
- */
15
- export type SyncClientExecutor<DB extends SyncClientDb = SyncClientDb> = Pick<
16
- Kysely<DB>,
17
- 'selectFrom' | 'insertInto' | 'updateTable' | 'deleteFrom'
18
- >;
19
-
20
- export type OutboxCommitStatus = 'pending' | 'sending' | 'acked' | 'failed';
21
-
22
- export type SubscriptionStatus = 'active' | 'revoked';
23
-
24
- export interface SyncSubscriptionStateTable {
25
- /** Logical profile id (default: 'default') */
26
- state_id: string;
27
- /** Subscription identifier (client-chosen) */
28
- subscription_id: string;
29
- /** Table name for this subscription */
30
- table: string;
31
- /** JSON string of ScopeValues for this subscription */
32
- scopes_json: string;
33
- /** JSON string of params */
34
- params_json: string;
35
- /** Per-subscription cursor (last applied commit_seq) */
36
- cursor: number;
37
- /**
38
- * JSON string of SyncBootstrapState (or null).
39
- * When set, indicates a paginated bootstrap is in progress for this subscription.
40
- */
41
- bootstrap_state_json: string | null;
42
- /** active | revoked */
43
- status: SubscriptionStatus;
44
- created_at: number;
45
- updated_at: number;
46
- }
47
-
48
- export interface SyncOutboxCommitsTable {
49
- /** Local row id (uuid) */
50
- id: string;
51
- /** Client-provided idempotency key (uuid) */
52
- client_commit_id: string;
53
- /** pending | sending | acked | failed */
54
- status: OutboxCommitStatus;
55
- /** JSON string of SyncOperation[] */
56
- operations_json: string;
57
- /** JSON string of the last SyncPushResponse (optional) */
58
- last_response_json: string | null;
59
- /** Last error (if any) */
60
- error: string | null;
61
- /** Created timestamp (ms since epoch) */
62
- created_at: number;
63
- /** Updated timestamp (ms since epoch) */
64
- updated_at: number;
65
- /** How many send attempts have been made */
66
- attempt_count: Generated<number>;
67
- /** Server commit_seq if acked (optional) */
68
- acked_commit_seq: number | null;
69
- /** Client schema version when commit was created (default: 1 for legacy) */
70
- schema_version: Generated<number>;
71
- }
72
-
73
- export interface SyncClientDb extends SyncBlobClientDb {
74
- sync_subscription_state: SyncSubscriptionStateTable;
75
- sync_outbox_commits: SyncOutboxCommitsTable;
76
- sync_conflicts: SyncConflictsTable;
77
- }
78
-
79
- export type ConflictResultStatus = 'conflict' | 'error';
80
-
81
- export interface SyncConflictsTable {
82
- id: string;
83
- outbox_commit_id: string;
84
- client_commit_id: string;
85
- op_index: number;
86
- result_status: ConflictResultStatus;
87
- message: string;
88
- code: string | null;
89
- server_version: number | null;
90
- server_row_json: string | null;
91
- created_at: number;
92
- resolved_at: number | null;
93
- resolution: string | null;
94
- }