@syncular/client 0.0.6-96 → 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,67 +0,0 @@
1
- /**
2
- * @syncular/client - Blob storage migrations
3
- */
4
-
5
- import type { Kysely } from 'kysely';
6
-
7
- /**
8
- * Ensures the client blob schema exists in the database.
9
- * Safe to call multiple times (idempotent).
10
- */
11
- export async function ensureClientBlobSchema<DB>(
12
- db: Kysely<DB>
13
- ): Promise<void> {
14
- // Blob cache table
15
- await db.schema
16
- .createTable('sync_blob_cache')
17
- .ifNotExists()
18
- .addColumn('hash', 'text', (col) => col.primaryKey())
19
- .addColumn('size', 'integer', (col) => col.notNull())
20
- .addColumn('mime_type', 'text', (col) => col.notNull())
21
- .addColumn('body', 'blob', (col) => col.notNull())
22
- .addColumn('encrypted', 'integer', (col) => col.notNull().defaultTo(0))
23
- .addColumn('key_id', 'text')
24
- .addColumn('cached_at', 'bigint', (col) => col.notNull())
25
- .addColumn('last_accessed_at', 'bigint', (col) => col.notNull())
26
- .execute();
27
-
28
- await db.schema
29
- .createIndex('idx_sync_blob_cache_last_accessed')
30
- .ifNotExists()
31
- .on('sync_blob_cache')
32
- .columns(['last_accessed_at'])
33
- .execute();
34
-
35
- // Blob upload outbox table
36
- await db.schema
37
- .createTable('sync_blob_outbox')
38
- .ifNotExists()
39
- .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
40
- .addColumn('hash', 'text', (col) => col.notNull().unique())
41
- .addColumn('size', 'integer', (col) => col.notNull())
42
- .addColumn('mime_type', 'text', (col) => col.notNull())
43
- .addColumn('body', 'blob', (col) => col.notNull())
44
- .addColumn('encrypted', 'integer', (col) => col.notNull().defaultTo(0))
45
- .addColumn('key_id', 'text')
46
- .addColumn('status', 'text', (col) => col.notNull())
47
- .addColumn('attempt_count', 'integer', (col) => col.notNull().defaultTo(0))
48
- .addColumn('error', 'text')
49
- .addColumn('created_at', 'bigint', (col) => col.notNull())
50
- .addColumn('updated_at', 'bigint', (col) => col.notNull())
51
- .execute();
52
-
53
- await db.schema
54
- .createIndex('idx_sync_blob_outbox_status')
55
- .ifNotExists()
56
- .on('sync_blob_outbox')
57
- .columns(['status', 'created_at'])
58
- .execute();
59
- }
60
-
61
- /**
62
- * Drops the client blob schema from the database.
63
- */
64
- export async function dropClientBlobSchema<DB>(db: Kysely<DB>): Promise<void> {
65
- await db.schema.dropTable('sync_blob_outbox').ifExists().execute();
66
- await db.schema.dropTable('sync_blob_cache').ifExists().execute();
67
- }
@@ -1,84 +0,0 @@
1
- /**
2
- * @syncular/client - Blob storage types
3
- */
4
-
5
- import type { Generated } from 'kysely';
6
-
7
- // ============================================================================
8
- // Blob Cache Table
9
- // ============================================================================
10
-
11
- /**
12
- * Local blob cache table.
13
- * Stores downloaded blobs for offline access.
14
- */
15
- export interface SyncBlobCacheTable {
16
- /** SHA-256 hash with prefix: "sha256:<hex>" */
17
- hash: string;
18
- /** Size in bytes */
19
- size: number;
20
- /** MIME type */
21
- mime_type: string;
22
- /** Blob content */
23
- body: Uint8Array;
24
- /** Whether the blob is encrypted */
25
- encrypted: number; // SQLite boolean
26
- /** Encryption key ID (if encrypted) */
27
- key_id: string | null;
28
- /** When the blob was cached */
29
- cached_at: number;
30
- /** Last accessed timestamp (for LRU eviction) */
31
- last_accessed_at: number;
32
- }
33
-
34
- // ============================================================================
35
- // Blob Upload Outbox Table
36
- // ============================================================================
37
-
38
- export type BlobUploadStatus =
39
- | 'pending'
40
- | 'uploading'
41
- | 'uploaded'
42
- | 'confirming'
43
- | 'complete'
44
- | 'failed';
45
-
46
- /**
47
- * Blob upload outbox table.
48
- * Tracks pending blob uploads for offline support.
49
- */
50
- export interface SyncBlobOutboxTable {
51
- /** Local row id */
52
- id: Generated<number>;
53
- /** SHA-256 hash with prefix: "sha256:<hex>" */
54
- hash: string;
55
- /** Size in bytes */
56
- size: number;
57
- /** MIME type */
58
- mime_type: string;
59
- /** Blob content (stored locally until upload completes) */
60
- body: Uint8Array;
61
- /** Whether the blob is encrypted */
62
- encrypted: number; // SQLite boolean
63
- /** Encryption key ID (if encrypted) */
64
- key_id: string | null;
65
- /** Upload status */
66
- status: BlobUploadStatus;
67
- /** Upload attempt count */
68
- attempt_count: number;
69
- /** Last error message */
70
- error: string | null;
71
- /** When the upload was queued */
72
- created_at: number;
73
- /** When the upload was last updated */
74
- updated_at: number;
75
- }
76
-
77
- // ============================================================================
78
- // Database Interface
79
- // ============================================================================
80
-
81
- export interface SyncBlobClientDb {
82
- sync_blob_cache: SyncBlobCacheTable;
83
- sync_blob_outbox: SyncBlobOutboxTable;
84
- }
@@ -1,417 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
2
- import { createDatabase, type SyncTransport } from '@syncular/core';
3
- import type { Kysely } from 'kysely';
4
- import { sql } from 'kysely';
5
- import { createBunSqliteDialect } from '../../dialect-bun-sqlite/src';
6
- import { ensureClientBlobSchema } from './blobs/migrate';
7
- import { Client, type ClientBlobStorage } from './client';
8
- import { SyncEngine } from './engine/SyncEngine';
9
- import type { ClientHandlerCollection } from './handlers/collection';
10
- import { ensureClientSyncSchema } from './migrate';
11
- import type { SyncClientDb } from './schema';
12
-
13
- interface TasksTable {
14
- id: string;
15
- title: string;
16
- server_version: number;
17
- }
18
-
19
- interface TestDb extends SyncClientDb {
20
- tasks: TasksTable;
21
- }
22
-
23
- const noopTransport: SyncTransport = {
24
- async sync() {
25
- return {};
26
- },
27
- async fetchSnapshotChunk() {
28
- return new Uint8Array();
29
- },
30
- };
31
-
32
- function createMemoryBlobStorage(): ClientBlobStorage {
33
- const memory = new Map<string, Uint8Array>();
34
- return {
35
- async write(hash, data) {
36
- if (data instanceof ReadableStream) {
37
- const reader = data.getReader();
38
- const chunks: Uint8Array[] = [];
39
- let total = 0;
40
- while (true) {
41
- const chunk = await reader.read();
42
- if (chunk.done) break;
43
- chunks.push(chunk.value);
44
- total += chunk.value.length;
45
- }
46
- const combined = new Uint8Array(total);
47
- let offset = 0;
48
- for (const chunk of chunks) {
49
- combined.set(chunk, offset);
50
- offset += chunk.length;
51
- }
52
- memory.set(hash, combined);
53
- return;
54
- }
55
- memory.set(hash, new Uint8Array(data));
56
- },
57
- async read(hash) {
58
- const data = memory.get(hash);
59
- return data ? new Uint8Array(data) : null;
60
- },
61
- async delete(hash) {
62
- memory.delete(hash);
63
- },
64
- async exists(hash) {
65
- return memory.has(hash);
66
- },
67
- };
68
- }
69
-
70
- describe('Client conflict events', () => {
71
- let db: Kysely<TestDb>;
72
- let client: Client<TestDb>;
73
- let engine: SyncEngine<TestDb>;
74
-
75
- async function seedConflict(id: string): Promise<void> {
76
- const now = Date.now();
77
- await sql`
78
- insert into ${sql.table('sync_outbox_commits')} (
79
- ${sql.ref('id')},
80
- ${sql.ref('client_commit_id')},
81
- ${sql.ref('status')},
82
- ${sql.ref('operations_json')},
83
- ${sql.ref('last_response_json')},
84
- ${sql.ref('error')},
85
- ${sql.ref('created_at')},
86
- ${sql.ref('updated_at')},
87
- ${sql.ref('attempt_count')},
88
- ${sql.ref('acked_commit_seq')},
89
- ${sql.ref('schema_version')}
90
- ) values (
91
- ${'outbox-1'},
92
- ${'commit-1'},
93
- ${'failed'},
94
- ${JSON.stringify([
95
- {
96
- table: 'tasks',
97
- row_id: 't1',
98
- op: 'upsert',
99
- payload: { id: 't1', title: 'local', server_version: 1 },
100
- },
101
- ])},
102
- ${null},
103
- ${'conflict'},
104
- ${now},
105
- ${now},
106
- ${1},
107
- ${null},
108
- ${1}
109
- )
110
- `.execute(db);
111
-
112
- await sql`
113
- insert into ${sql.table('sync_conflicts')} (
114
- ${sql.ref('id')},
115
- ${sql.ref('outbox_commit_id')},
116
- ${sql.ref('client_commit_id')},
117
- ${sql.ref('op_index')},
118
- ${sql.ref('result_status')},
119
- ${sql.ref('message')},
120
- ${sql.ref('code')},
121
- ${sql.ref('server_version')},
122
- ${sql.ref('server_row_json')},
123
- ${sql.ref('created_at')},
124
- ${sql.ref('resolved_at')},
125
- ${sql.ref('resolution')}
126
- ) values (
127
- ${id},
128
- ${'outbox-1'},
129
- ${'commit-1'},
130
- ${0},
131
- ${'conflict'},
132
- ${'server conflict'},
133
- ${'CONFLICT'},
134
- ${2},
135
- ${JSON.stringify({ id: 't1', title: 'server', server_version: 2 })},
136
- ${now},
137
- ${null},
138
- ${null}
139
- )
140
- `.execute(db);
141
- }
142
-
143
- async function runConflictCheck(
144
- clientInstance: Client<TestDb>
145
- ): Promise<void> {
146
- const checker = Reflect.get(clientInstance, 'checkForNewConflicts');
147
- if (typeof checker !== 'function') {
148
- throw new Error('Expected checkForNewConflicts to be callable');
149
- }
150
- await checker.call(clientInstance);
151
- }
152
-
153
- beforeEach(async () => {
154
- db = createDatabase<TestDb>({
155
- dialect: createBunSqliteDialect({ path: ':memory:' }),
156
- family: 'sqlite',
157
- });
158
- await ensureClientSyncSchema(db);
159
- await db.schema
160
- .createTable('tasks')
161
- .addColumn('id', 'text', (col) => col.primaryKey())
162
- .addColumn('title', 'text', (col) => col.notNull())
163
- .addColumn('server_version', 'integer', (col) =>
164
- col.notNull().defaultTo(0)
165
- )
166
- .execute();
167
-
168
- const handlers: ClientHandlerCollection<TestDb> = [];
169
- client = new Client<TestDb>({
170
- db,
171
- transport: noopTransport,
172
- tableHandlers: handlers,
173
- clientId: 'client-1',
174
- actorId: 'u1',
175
- subscriptions: [],
176
- });
177
-
178
- engine = new SyncEngine<TestDb>({
179
- db,
180
- transport: noopTransport,
181
- handlers: handlers,
182
- actorId: 'u1',
183
- clientId: 'client-1',
184
- subscriptions: [],
185
- });
186
- Reflect.set(client, 'engine', engine);
187
- });
188
-
189
- afterEach(async () => {
190
- client.destroy();
191
- await db.destroy();
192
- });
193
-
194
- it('emits conflict:resolved with the resolved conflict payload', async () => {
195
- await seedConflict('conflict-1');
196
-
197
- const resolvedEvents: Array<{ id: string }> = [];
198
- client.on('conflict:resolved', (conflict) => {
199
- resolvedEvents.push({ id: conflict.id });
200
- });
201
-
202
- await client.resolveConflict('conflict-1', { strategy: 'keep-local' });
203
-
204
- expect(resolvedEvents).toEqual([{ id: 'conflict-1' }]);
205
-
206
- const resolvedRow = await sql<{ resolved_at: number | null }>`
207
- select ${sql.ref('resolved_at')}
208
- from ${sql.table('sync_conflicts')}
209
- where ${sql.ref('id')} = ${'conflict-1'}
210
- limit 1
211
- `.execute(db);
212
- expect(resolvedRow.rows[0]?.resolved_at).not.toBeNull();
213
- });
214
-
215
- it('emits conflict:new only once per unresolved conflict id', async () => {
216
- await seedConflict('conflict-1');
217
-
218
- const newEvents: string[] = [];
219
- client.on('conflict:new', (conflict) => {
220
- newEvents.push(conflict.id);
221
- });
222
-
223
- await runConflictCheck(client);
224
- await runConflictCheck(client);
225
-
226
- expect(newEvents).toEqual(['conflict-1']);
227
- });
228
- });
229
-
230
- describe('Client blob upload queue recovery', () => {
231
- let db: Kysely<TestDb>;
232
- let client: Client<TestDb>;
233
- let initiateCalls = 0;
234
-
235
- async function insertBlobOutboxRow(input: {
236
- hash: string;
237
- status: string;
238
- attemptCount: number;
239
- updatedAt: number;
240
- }): Promise<void> {
241
- const now = Date.now();
242
- await sql`
243
- insert into ${sql.table('sync_blob_outbox')} (
244
- ${sql.join([
245
- sql.ref('hash'),
246
- sql.ref('size'),
247
- sql.ref('mime_type'),
248
- sql.ref('body'),
249
- sql.ref('encrypted'),
250
- sql.ref('key_id'),
251
- sql.ref('status'),
252
- sql.ref('attempt_count'),
253
- sql.ref('error'),
254
- sql.ref('created_at'),
255
- sql.ref('updated_at'),
256
- ])}
257
- ) values (
258
- ${sql.join([
259
- sql.val(input.hash),
260
- sql.val(3),
261
- sql.val('application/octet-stream'),
262
- sql.val(new Uint8Array([1, 2, 3])),
263
- sql.val(0),
264
- sql.val(null),
265
- sql.val(input.status),
266
- sql.val(input.attemptCount),
267
- sql.val(null),
268
- sql.val(now),
269
- sql.val(input.updatedAt),
270
- ])}
271
- )
272
- `.execute(db);
273
- }
274
-
275
- beforeEach(async () => {
276
- db = createDatabase<TestDb>({
277
- dialect: createBunSqliteDialect({ path: ':memory:' }),
278
- family: 'sqlite',
279
- });
280
- await ensureClientSyncSchema(db);
281
- await ensureClientBlobSchema(db);
282
- initiateCalls = 0;
283
-
284
- const handlers: ClientHandlerCollection<TestDb> = [];
285
- const transport: SyncTransport = {
286
- ...noopTransport,
287
- blobs: {
288
- async initiateUpload() {
289
- initiateCalls++;
290
- return { exists: true };
291
- },
292
- async completeUpload() {
293
- return { ok: true };
294
- },
295
- async getDownloadUrl() {
296
- return {
297
- url: 'https://example.invalid/blob',
298
- expiresAt: new Date(0).toISOString(),
299
- };
300
- },
301
- },
302
- };
303
-
304
- client = new Client<TestDb>({
305
- db,
306
- transport,
307
- tableHandlers: handlers,
308
- blobStorage: createMemoryBlobStorage(),
309
- clientId: 'client-1',
310
- actorId: 'u1',
311
- subscriptions: [],
312
- });
313
- });
314
-
315
- afterEach(async () => {
316
- client.destroy();
317
- await db.destroy();
318
- });
319
-
320
- it('requeues stale uploading rows and uploads them on the next queue run', async () => {
321
- await insertBlobOutboxRow({
322
- hash: 'sha256:stale-upload',
323
- status: 'uploading',
324
- attemptCount: 0,
325
- updatedAt: Date.now() - 31_000,
326
- });
327
-
328
- const result = await client.blobs!.processUploadQueue();
329
-
330
- expect(result.uploaded).toBe(1);
331
- expect(result.failed).toBe(0);
332
- expect(initiateCalls).toBe(1);
333
-
334
- const remaining = await sql<{ count: number | bigint }>`
335
- select count(${sql.ref('hash')}) as count
336
- from ${sql.table('sync_blob_outbox')}
337
- where ${sql.ref('hash')} = ${'sha256:stale-upload'}
338
- `.execute(db);
339
- expect(Number(remaining.rows[0]?.count ?? 0)).toBe(0);
340
- });
341
-
342
- it('marks stale uploading rows as failed after max retries', async () => {
343
- await insertBlobOutboxRow({
344
- hash: 'sha256:stale-failed',
345
- status: 'uploading',
346
- attemptCount: 2,
347
- updatedAt: Date.now() - 31_000,
348
- });
349
-
350
- await client.blobs!.processUploadQueue();
351
-
352
- expect(initiateCalls).toBe(0);
353
-
354
- const rowResult = await sql<{
355
- status: string;
356
- attempt_count: number;
357
- error: string | null;
358
- }>`
359
- select
360
- ${sql.ref('status')} as status,
361
- ${sql.ref('attempt_count')} as attempt_count,
362
- ${sql.ref('error')} as error
363
- from ${sql.table('sync_blob_outbox')}
364
- where ${sql.ref('hash')} = ${'sha256:stale-failed'}
365
- limit 1
366
- `.execute(db);
367
- const row = rowResult.rows[0];
368
- if (!row) {
369
- throw new Error('Expected stale failed row to remain in outbox');
370
- }
371
- expect(row.status).toBe('failed');
372
- expect(row.attempt_count).toBe(3);
373
- expect(row.error).toContain('Upload timed out while in uploading state');
374
- });
375
- });
376
-
377
- describe('Client inspector snapshot', () => {
378
- let db: Kysely<TestDb>;
379
- let client: Client<TestDb>;
380
-
381
- beforeEach(async () => {
382
- db = createDatabase<TestDb>({
383
- dialect: createBunSqliteDialect({ path: ':memory:' }),
384
- family: 'sqlite',
385
- });
386
- await ensureClientSyncSchema(db);
387
-
388
- const handlers: ClientHandlerCollection<TestDb> = [];
389
- client = new Client<TestDb>({
390
- db,
391
- transport: noopTransport,
392
- tableHandlers: handlers,
393
- clientId: 'client-inspector',
394
- actorId: 'u1',
395
- subscriptions: [],
396
- });
397
- });
398
-
399
- afterEach(async () => {
400
- client.destroy();
401
- await db.destroy();
402
- });
403
-
404
- it('returns a serializable inspector snapshot', async () => {
405
- await client.start();
406
- await client.sync();
407
-
408
- const snapshot = await client.getInspectorSnapshot({ eventLimit: 20 });
409
-
410
- expect(snapshot).not.toBeNull();
411
- expect(snapshot?.version).toBe(1);
412
- expect(snapshot?.generatedAt).toBeGreaterThan(0);
413
- expect(Array.isArray(snapshot?.recentEvents)).toBe(true);
414
- expect(snapshot?.recentEvents.length).toBeGreaterThan(0);
415
- expect(snapshot?.diagnostics).toBeDefined();
416
- });
417
- });