@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
package/src/mutations.ts CHANGED
@@ -1,49 +1,8 @@
1
- /**
2
- * @syncular/client - Mutations API (Proxy-based, typed)
3
- *
4
- * Provides a dynamic `Proxy` mutation interface with Kysely typings:
5
- * - `mutations.tasks.insert({ ... })` (auto-generates id)
6
- * - `mutations.tasks.update(id, { ... })`
7
- * - `mutations.tasks.delete(id)`
8
- * - `mutations.$commit(async (tx) => { ... })` for batching
9
- *
10
- * Under the hood this compiles to sync `upsert/delete` operations.
11
- *
12
- * This module is framework-agnostic. `@syncular/client-react` wraps it to add
13
- * React state and automatic sync triggering.
14
- */
1
+ import type { SyncOperation } from '@syncular/core';
2
+ import type { Insertable, Updateable } from 'kysely';
15
3
 
16
- import type {
17
- ColumnCodecDialect,
18
- ColumnCodecSource,
19
- SyncOperation,
20
- SyncPushRequest,
21
- SyncPushResponse,
22
- SyncTransport,
23
- } from '@syncular/core';
24
- import {
25
- applyCodecsToDbRow,
26
- isRecord,
27
- randomId,
28
- toTableColumnCodecs,
29
- } from '@syncular/core';
30
- import type { Insertable, Kysely, Transaction, Updateable } from 'kysely';
31
- import { sql } from 'kysely';
32
- import { enqueueOutboxCommit } from './outbox';
33
- import type {
34
- SyncClientPlugin,
35
- SyncClientPluginContext,
36
- } from './plugins/types';
37
- import type { SyncClientDb } from './schema';
38
-
39
- /**
40
- * Base type for any database schema.
41
- * Uses an index signature to support runtime-determined table names.
42
- */
43
4
  type AnyDb = Record<string, Record<string, unknown>>;
44
5
 
45
- type SyncOpKind = 'upsert' | 'delete';
46
-
47
6
  type ReservedKeys = '$commit' | '$table';
48
7
  type KnownKeys<T> = string extends keyof T ? never : keyof T & string;
49
8
  type KnownTableKey<DB> = Exclude<KnownKeys<DB>, ReservedKeys>;
@@ -58,25 +17,17 @@ type UpdatePayload<Row> = Omit<Updateable<Row>, 'id'> & { id?: never };
58
17
  type BaseVersionOptions = { baseVersion?: number | null };
59
18
 
60
19
  export interface MutationReceipt {
61
- /**
62
- * Outbox commit id (when using outbox) or a generated id (when pushing directly).
63
- */
64
20
  commitId: string;
65
- /**
66
- * Protocol-level client commit id (sent to the server in push requests).
67
- */
68
21
  clientCommitId: string;
69
22
  }
70
23
 
71
- export interface OutboxCommitMeta {
72
- operations: SyncOperation[];
73
- localMutations: Array<{ table: string; rowId: string; op: SyncOpKind }>;
74
- }
75
-
76
- export interface PushCommitMeta {
24
+ export interface SyncularCommitMeta {
77
25
  operations: SyncOperation[];
78
- localMutations: Array<{ table: string; rowId: string; op: SyncOpKind }>;
79
- response: SyncPushResponse;
26
+ localMutations: Array<{
27
+ table: string;
28
+ rowId: string;
29
+ op: 'upsert' | 'delete';
30
+ }>;
80
31
  }
81
32
 
82
33
  export type TableMutations<DB, T extends keyof DB & string> = {
@@ -95,9 +46,6 @@ export type TableMutations<DB, T extends keyof DB & string> = {
95
46
  id: string,
96
47
  options?: BaseVersionOptions
97
48
  ) => Promise<MutationReceipt>;
98
- /**
99
- * Explicit upsert escape hatch. Prefer insert/update for clarity.
100
- */
101
49
  upsert: (
102
50
  id: string,
103
51
  patch: UpdatePayload<DB[T]>,
@@ -124,7 +72,6 @@ export type TableMutationsTx<DB, T extends keyof DB & string> = {
124
72
  export type MutationsTx<DB> = {
125
73
  [T in KnownTableKey<DB>]: TableMutationsTx<DB, T>;
126
74
  } & {
127
- // Index signature for dynamic access via Proxy
128
75
  [table: string]: TableMutationsTx<AnyDb, string>;
129
76
  };
130
77
 
@@ -146,189 +93,10 @@ export type MutationsApi<DB, CommitOptions = unknown> = {
146
93
  [T in KnownTableKey<DB>]: TableMutations<DB, T>;
147
94
  };
148
95
 
149
- function sanitizePayload(
150
- payload: Record<string, unknown>,
151
- args: { omit: string[] }
152
- ): Record<string, unknown> {
153
- if (args.omit.length === 0) return payload;
154
- const omitSet = new Set(args.omit);
155
- let changed = false;
156
- const out: Record<string, unknown> = {};
157
- for (const [k, v] of Object.entries(payload)) {
158
- if (omitSet.has(k)) {
159
- changed = true;
160
- continue;
161
- }
162
- out[k] = v;
163
- }
164
- return changed ? out : payload;
165
- }
166
-
167
- function coerceBaseVersion(value: unknown): number | null {
168
- if (value === null || value === undefined) return null;
169
- const n = typeof value === 'number' ? value : Number(value);
170
- if (!Number.isFinite(n)) return null;
171
- if (n <= 0) return null;
172
- return n;
173
- }
174
-
175
- function hasOwn(obj: object, key: string): boolean {
176
- return Object.hasOwn(obj, key);
177
- }
178
-
179
- /**
180
- * Valid SQL identifier regex.
181
- * Allows: alphanumeric, underscore, cannot start with digit
182
- */
183
- const VALID_IDENTIFIER_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
184
-
185
- /**
186
- * Validate table name to prevent SQL injection.
187
- * Table names are inserted directly into SQL, so they must be validated.
188
- */
189
- function validateTableName(table: string): void {
190
- if (!VALID_IDENTIFIER_REGEX.test(table)) {
191
- throw new Error(
192
- `Invalid table name "${table}". Table names must match /^[a-zA-Z_][a-zA-Z0-9_]*$/`
193
- );
194
- }
195
- }
196
-
197
- /**
198
- * Internal helpers for dynamic table operations.
199
- *
200
- * Kysely's query builder is typed around compile-time table names. For the
201
- * Proxy-based mutations API, we need runtime table/column names. We use
202
- * Kysely's `sql` builder with strict identifier validation to avoid unsafe
203
- * `any`/`unknown` casts while keeping the public API typed.
204
- */
205
- function validateColumnName(column: string): void {
206
- if (!VALID_IDENTIFIER_REGEX.test(column)) {
207
- throw new Error(
208
- `Invalid column name "${column}". Column names must match /^[a-zA-Z_][a-zA-Z0-9_]*$/`
209
- );
210
- }
211
- }
212
-
213
- async function readBaseVersion<T>(args: {
214
- trx: Transaction<T>;
215
- table: string;
216
- rowId: string;
217
- idColumn: string;
218
- versionColumn: string;
219
- }): Promise<number | null> {
220
- validateTableName(args.table);
221
- validateColumnName(args.idColumn);
222
- validateColumnName(args.versionColumn);
223
-
224
- const res = await sql<{ v: unknown }>`
225
- select ${sql.ref(args.versionColumn)} as v
226
- from ${sql.table(args.table)}
227
- where ${sql.ref(args.idColumn)} = ${sql.val(args.rowId)}
228
- limit 1
229
- `.execute(args.trx);
230
-
231
- return coerceBaseVersion(res.rows[0]?.v);
232
- }
233
-
234
- async function dynamicInsert<T>(
235
- trx: Transaction<T>,
236
- table: string,
237
- values: Record<string, unknown> | Record<string, unknown>[]
238
- ): Promise<void> {
239
- validateTableName(table);
240
- const rows = Array.isArray(values) ? values : [values];
241
- if (rows.length === 0) return;
242
-
243
- const columnsSet = new Set<string>();
244
- for (const row of rows) {
245
- for (const col of Object.keys(row)) {
246
- validateColumnName(col);
247
- columnsSet.add(col);
248
- }
249
- }
250
-
251
- const columns = Array.from(columnsSet);
252
- if (columns.length === 0) return;
253
-
254
- await sql`
255
- insert into ${sql.table(table)} (${sql.join(columns.map((c) => sql.ref(c)))})
256
- values ${sql.join(
257
- rows.map(
258
- (row) => sql`(${sql.join(columns.map((c) => sql.val(row[c] ?? null)))})`
259
- )
260
- )}
261
- `.execute(trx);
262
- }
263
-
264
- async function dynamicUpsert<T>(
265
- trx: Transaction<T>,
266
- table: string,
267
- idColumn: string,
268
- id: string,
269
- values: Record<string, unknown>
270
- ): Promise<void> {
271
- validateTableName(table);
272
- validateColumnName(idColumn);
273
-
274
- // Check if the row already exists
275
- const existing = await sql`
276
- select 1 from ${sql.table(table)}
277
- where ${sql.ref(idColumn)} = ${sql.val(id)}
278
- limit 1
279
- `.execute(trx);
280
-
281
- if (existing.rows.length > 0) {
282
- // Row exists: just update the provided columns
283
- await dynamicUpdate(trx, table, idColumn, id, values);
284
- } else {
285
- // Row doesn't exist: insert with all provided columns + id
286
- await dynamicInsert(trx, table, { ...values, [idColumn]: id });
287
- }
288
- }
289
-
290
- async function dynamicUpdate<T>(
291
- trx: Transaction<T>,
292
- table: string,
293
- idColumn: string,
294
- id: string,
295
- values: Record<string, unknown>
296
- ): Promise<void> {
297
- validateTableName(table);
298
- validateColumnName(idColumn);
299
-
300
- const setParts = Object.entries(values).map(([col, value]) => {
301
- validateColumnName(col);
302
- return sql`${sql.ref(col)} = ${sql.val(value)}`;
303
- });
304
-
305
- if (setParts.length === 0) return;
306
-
307
- await sql`
308
- update ${sql.table(table)}
309
- set ${sql.join(setParts)}
310
- where ${sql.ref(idColumn)} = ${sql.val(id)}
311
- `.execute(trx);
312
- }
313
-
314
- async function dynamicDelete<T>(
315
- trx: Transaction<T>,
316
- table: string,
317
- idColumn: string,
318
- id: string
319
- ): Promise<void> {
320
- validateTableName(table);
321
- validateColumnName(idColumn);
322
- await sql`
323
- delete from ${sql.table(table)}
324
- where ${sql.ref(idColumn)} = ${sql.val(id)}
325
- `.execute(trx);
326
- }
327
-
328
96
  export function createMutationsApi<DB, Meta = unknown, CommitOptions = unknown>(
329
97
  commit: MutationsCommitFn<DB, Meta, CommitOptions>
330
98
  ): MutationsApi<DB, CommitOptions> {
331
- const rootTableCache = new Map<string, any>();
99
+ const rootTableCache = new Map<string, TableMutations<AnyDb, string>>();
332
100
 
333
101
  const apiBase = {
334
102
  $commit: async <R>(
@@ -342,7 +110,7 @@ export function createMutationsApi<DB, Meta = unknown, CommitOptions = unknown>(
342
110
  const cached = rootTableCache.get(table);
343
111
  if (cached) return cached;
344
112
 
345
- const tableApi: TableMutations<any, any> = {
113
+ const tableApi: TableMutations<AnyDb, string> = {
346
114
  async insert(values) {
347
115
  const { result, receipt } = await commit(
348
116
  async (tx) => await tx[table]!.insert(values)
@@ -395,583 +163,6 @@ export function createMutationsApi<DB, Meta = unknown, CommitOptions = unknown>(
395
163
  }) as MutationsApi<DB, CommitOptions>;
396
164
  }
397
165
 
398
- export interface OutboxCommitConfig<DB extends SyncClientDb> {
399
- db: Kysely<DB>;
400
- idColumn?: string;
401
- versionColumn?: string | null;
402
- omitColumns?: string[];
403
- codecs?: ColumnCodecSource;
404
- codecDialect?: ColumnCodecDialect;
405
- }
406
-
407
- export function createOutboxCommit<DB extends SyncClientDb>(
408
- config: OutboxCommitConfig<DB>
409
- ): MutationsCommitFn<DB, OutboxCommitMeta, undefined> {
410
- const idColumn = config.idColumn ?? 'id';
411
- const versionColumn = config.versionColumn ?? 'server_version';
412
- const omitColumns = config.omitColumns ?? [];
413
- const codecDialect = config.codecDialect ?? 'sqlite';
414
-
415
- return async (fn) => {
416
- const operations: SyncOperation[] = [];
417
- const localMutations: Array<{
418
- table: string;
419
- rowId: string;
420
- op: SyncOpKind;
421
- }> = [];
422
-
423
- const { result, receipt } = await config.db
424
- .transaction()
425
- .execute(async (trx) => {
426
- const txTableCache = new Map<string, any>();
427
- const tableCodecCache = new Map<
428
- string,
429
- Map<string, ReturnType<typeof toTableColumnCodecs>>
430
- >();
431
- const resolveTableCodecs = (
432
- table: string,
433
- row: Record<string, unknown>
434
- ) => {
435
- const codecs = config.codecs;
436
- if (!codecs) return {};
437
- const columns = Object.keys(row);
438
- if (columns.length === 0) return {};
439
-
440
- let tableCache = tableCodecCache.get(table);
441
- if (!tableCache) {
442
- tableCache = new Map<
443
- string,
444
- ReturnType<typeof toTableColumnCodecs>
445
- >();
446
- tableCodecCache.set(table, tableCache);
447
- }
448
-
449
- const cacheKey = columns.slice().sort().join('\u0000');
450
- const cached = tableCache.get(cacheKey);
451
- if (cached) return cached;
452
-
453
- const resolved = toTableColumnCodecs(table, codecs, columns, {
454
- dialect: codecDialect,
455
- });
456
- tableCache.set(cacheKey, resolved);
457
- return resolved;
458
- };
459
-
460
- const makeTxTable = (table: string) => {
461
- const cached = txTableCache.get(table);
462
- if (cached) return cached;
463
-
464
- const tableApi: TableMutationsTx<any, any> = {
465
- async insert(values) {
466
- const raw = isRecord(values) ? values : {};
467
- const rawId = raw[idColumn];
468
- const id =
469
- typeof rawId === 'string' && rawId ? rawId : randomId();
470
-
471
- const row = { ...raw, [idColumn]: id };
472
- const dbRow = applyCodecsToDbRow(
473
- row,
474
- resolveTableCodecs(table, row),
475
- codecDialect
476
- );
477
-
478
- await dynamicInsert(trx, table, dbRow);
479
-
480
- const payload = sanitizePayload(row, {
481
- omit: [
482
- idColumn,
483
- ...(versionColumn ? [versionColumn] : []),
484
- ...omitColumns,
485
- ],
486
- });
487
-
488
- operations.push({
489
- table: table,
490
- row_id: id,
491
- op: 'upsert',
492
- payload,
493
- base_version: null,
494
- });
495
-
496
- localMutations.push({ table, rowId: id, op: 'upsert' });
497
- return id;
498
- },
499
-
500
- async insertMany(rows) {
501
- const ids: string[] = [];
502
- const toInsert: Record<string, unknown>[] = [];
503
-
504
- for (const values of rows) {
505
- const raw = isRecord(values) ? values : {};
506
- const rawId = raw[idColumn];
507
- const id =
508
- typeof rawId === 'string' && rawId ? rawId : randomId();
509
- ids.push(id);
510
- toInsert.push({ ...raw, [idColumn]: id });
511
- }
512
-
513
- const dbRows = toInsert.map((row) =>
514
- applyCodecsToDbRow(
515
- row,
516
- resolveTableCodecs(table, row),
517
- codecDialect
518
- )
519
- );
520
-
521
- if (dbRows.length > 0) {
522
- await dynamicInsert(trx, table, dbRows);
523
- }
524
-
525
- for (let i = 0; i < toInsert.length; i++) {
526
- const row = toInsert[i]!;
527
- const id = ids[i]!;
528
- const payload = sanitizePayload(row, {
529
- omit: [
530
- idColumn,
531
- ...(versionColumn ? [versionColumn] : []),
532
- ...omitColumns,
533
- ],
534
- });
535
-
536
- operations.push({
537
- table: table,
538
- row_id: id,
539
- op: 'upsert',
540
- payload,
541
- base_version: null,
542
- });
543
-
544
- localMutations.push({ table, rowId: id, op: 'upsert' });
545
- }
546
-
547
- return ids;
548
- },
549
-
550
- async update(id, patch, opts) {
551
- const rawPatch = isRecord(patch) ? patch : {};
552
- const sanitized = sanitizePayload(rawPatch, {
553
- omit: [
554
- idColumn,
555
- ...(versionColumn ? [versionColumn] : []),
556
- ...omitColumns,
557
- ],
558
- });
559
-
560
- const hasExplicitBaseVersion =
561
- !!opts && hasOwn(opts, 'baseVersion');
562
- const dbPatch = applyCodecsToDbRow(
563
- sanitized,
564
- resolveTableCodecs(table, sanitized),
565
- codecDialect
566
- );
567
-
568
- await dynamicUpdate(trx, table, idColumn, id, dbPatch);
569
-
570
- const baseVersion = hasExplicitBaseVersion
571
- ? (opts!.baseVersion ?? null)
572
- : versionColumn
573
- ? await readBaseVersion({
574
- trx: trx,
575
- table,
576
- rowId: id,
577
- idColumn,
578
- versionColumn,
579
- })
580
- : null;
581
-
582
- operations.push({
583
- table: table,
584
- row_id: id,
585
- op: 'upsert',
586
- payload: sanitized,
587
- base_version: coerceBaseVersion(baseVersion),
588
- });
589
-
590
- localMutations.push({ table, rowId: id, op: 'upsert' });
591
- },
592
-
593
- async delete(id, opts) {
594
- const hasExplicitBaseVersion =
595
- !!opts && hasOwn(opts, 'baseVersion');
596
- const baseVersion = hasExplicitBaseVersion
597
- ? (opts!.baseVersion ?? null)
598
- : versionColumn
599
- ? await readBaseVersion({
600
- trx: trx,
601
- table,
602
- rowId: id,
603
- idColumn,
604
- versionColumn,
605
- })
606
- : null;
607
-
608
- await dynamicDelete(trx, table, idColumn, id);
609
-
610
- operations.push({
611
- table: table,
612
- row_id: id,
613
- op: 'delete',
614
- payload: null,
615
- base_version: coerceBaseVersion(baseVersion),
616
- });
617
-
618
- localMutations.push({ table, rowId: id, op: 'delete' });
619
- },
620
-
621
- async upsert(id, patch, opts) {
622
- const rawPatch = isRecord(patch) ? patch : {};
623
- const sanitized = sanitizePayload(rawPatch, {
624
- omit: [
625
- idColumn,
626
- ...(versionColumn ? [versionColumn] : []),
627
- ...omitColumns,
628
- ],
629
- });
630
-
631
- const hasExplicitBaseVersion =
632
- !!opts && hasOwn(opts, 'baseVersion');
633
- const dbPatch = applyCodecsToDbRow(
634
- sanitized,
635
- resolveTableCodecs(table, sanitized),
636
- codecDialect
637
- );
638
-
639
- await dynamicUpsert(trx, table, idColumn, id, dbPatch);
640
-
641
- const baseVersion = hasExplicitBaseVersion
642
- ? (opts!.baseVersion ?? null)
643
- : versionColumn
644
- ? await readBaseVersion({
645
- trx: trx,
646
- table,
647
- rowId: id,
648
- idColumn,
649
- versionColumn,
650
- })
651
- : null;
652
-
653
- operations.push({
654
- table: table,
655
- row_id: id,
656
- op: 'upsert',
657
- payload: sanitized,
658
- base_version: coerceBaseVersion(baseVersion),
659
- });
660
-
661
- localMutations.push({ table, rowId: id, op: 'upsert' });
662
- },
663
- };
664
-
665
- txTableCache.set(table, tableApi);
666
- return tableApi;
667
- };
668
-
669
- const txProxy = new Proxy(
670
- {},
671
- {
672
- get(_target, prop) {
673
- if (prop === 'then') return undefined;
674
- if (typeof prop !== 'string') return undefined;
675
- return makeTxTable(prop);
676
- },
677
- }
678
- ) as MutationsTx<DB>;
679
-
680
- const result = await fn(txProxy);
681
-
682
- if (operations.length === 0) {
683
- throw new Error('No mutations were enqueued');
684
- }
685
-
686
- // Enqueue outbox commit within this transaction
687
- const receipt = await enqueueOutboxCommit(trx, { operations });
688
- return {
689
- result,
690
- receipt: { id: receipt.id, clientCommitId: receipt.clientCommitId },
691
- };
692
- });
693
-
694
- return {
695
- result,
696
- receipt: { commitId: receipt.id, clientCommitId: receipt.clientCommitId },
697
- meta: { operations, localMutations },
698
- };
699
- };
700
- }
701
-
702
- export function createOutboxMutations<DB extends SyncClientDb>(
703
- config: OutboxCommitConfig<DB>
704
- ): MutationsApi<DB, undefined> {
705
- return createMutationsApi(createOutboxCommit(config));
706
- }
707
-
708
- export interface PushCommitConfig {
709
- transport: SyncTransport;
710
- clientId: string;
711
- actorId?: string;
712
- plugins?: SyncClientPlugin[];
713
- idColumn?: string;
714
- versionColumn?: string | null;
715
- omitColumns?: string[];
716
- /** Client schema version (default: 1) */
717
- schemaVersion?: number;
718
- readBaseVersion?: (args: {
719
- table: string;
720
- rowId: string;
721
- idColumn: string;
722
- versionColumn: string;
723
- }) => Promise<number | null>;
724
- }
725
-
726
- function clonePushRequest(request: SyncPushRequest): SyncPushRequest {
727
- if (typeof structuredClone === 'function') return structuredClone(request);
728
- return JSON.parse(JSON.stringify(request)) as SyncPushRequest;
729
- }
730
-
731
- export function createPushCommit<DB = AnyDb>(
732
- config: PushCommitConfig
733
- ): MutationsCommitFn<DB, PushCommitMeta, undefined> {
734
- const idColumn = config.idColumn ?? 'id';
735
- const versionColumn = config.versionColumn ?? 'server_version';
736
- const omitColumns = config.omitColumns ?? [];
737
-
738
- return async (fn) => {
739
- const operations: SyncOperation[] = [];
740
- const localMutations: Array<{
741
- table: string;
742
- rowId: string;
743
- op: SyncOpKind;
744
- }> = [];
745
-
746
- const txTableCache = new Map<string, any>();
747
- const makeTxTable = (table: string) => {
748
- const cached = txTableCache.get(table);
749
- if (cached) return cached;
750
-
751
- const tableApi: TableMutationsTx<any, any> = {
752
- async insert(values) {
753
- const raw = isRecord(values) ? values : {};
754
- const rawId = raw[idColumn];
755
- const id = typeof rawId === 'string' && rawId ? rawId : randomId();
756
-
757
- const row = { ...raw, [idColumn]: id } as Record<string, unknown>;
758
-
759
- const payload = sanitizePayload(row, {
760
- omit: [
761
- idColumn,
762
- ...(versionColumn ? [versionColumn] : []),
763
- ...omitColumns,
764
- ],
765
- });
766
-
767
- operations.push({
768
- table: table,
769
- row_id: id,
770
- op: 'upsert',
771
- payload,
772
- base_version: null,
773
- });
774
-
775
- localMutations.push({ table, rowId: id, op: 'upsert' });
776
- return id;
777
- },
778
-
779
- async insertMany(rows) {
780
- const ids: string[] = [];
781
- const toUpsert: Record<string, unknown>[] = [];
782
-
783
- for (const values of rows) {
784
- const raw = isRecord(values) ? values : {};
785
- const rawId = raw[idColumn];
786
- const id = typeof rawId === 'string' && rawId ? rawId : randomId();
787
- ids.push(id);
788
- toUpsert.push({ ...raw, [idColumn]: id });
789
- }
790
-
791
- for (let i = 0; i < toUpsert.length; i++) {
792
- const row = toUpsert[i]!;
793
- const id = ids[i]!;
794
- const payload = sanitizePayload(row, {
795
- omit: [
796
- idColumn,
797
- ...(versionColumn ? [versionColumn] : []),
798
- ...omitColumns,
799
- ],
800
- });
801
-
802
- operations.push({
803
- table: table,
804
- row_id: id,
805
- op: 'upsert',
806
- payload,
807
- base_version: null,
808
- });
809
-
810
- localMutations.push({ table, rowId: id, op: 'upsert' });
811
- }
812
-
813
- return ids;
814
- },
815
-
816
- async update(id, patch, opts) {
817
- const rawPatch = isRecord(patch) ? patch : {};
818
- const sanitized = sanitizePayload(rawPatch, {
819
- omit: [
820
- idColumn,
821
- ...(versionColumn ? [versionColumn] : []),
822
- ...omitColumns,
823
- ],
824
- });
825
-
826
- const hasExplicitBaseVersion = !!opts && hasOwn(opts, 'baseVersion');
827
- const baseVersion = hasExplicitBaseVersion
828
- ? (opts!.baseVersion ?? null)
829
- : versionColumn && config.readBaseVersion
830
- ? await config.readBaseVersion({
831
- table,
832
- rowId: id,
833
- idColumn,
834
- versionColumn,
835
- })
836
- : null;
837
-
838
- operations.push({
839
- table: table,
840
- row_id: id,
841
- op: 'upsert',
842
- payload: sanitized,
843
- base_version: coerceBaseVersion(baseVersion),
844
- });
845
-
846
- localMutations.push({ table, rowId: id, op: 'upsert' });
847
- },
848
-
849
- async delete(id, opts) {
850
- const hasExplicitBaseVersion = !!opts && hasOwn(opts, 'baseVersion');
851
- const baseVersion = hasExplicitBaseVersion
852
- ? (opts!.baseVersion ?? null)
853
- : versionColumn && config.readBaseVersion
854
- ? await config.readBaseVersion({
855
- table,
856
- rowId: id,
857
- idColumn,
858
- versionColumn,
859
- })
860
- : null;
861
-
862
- operations.push({
863
- table: table,
864
- row_id: id,
865
- op: 'delete',
866
- payload: null,
867
- base_version: coerceBaseVersion(baseVersion),
868
- });
869
-
870
- localMutations.push({ table, rowId: id, op: 'delete' });
871
- },
872
-
873
- async upsert(id, patch, opts) {
874
- await tableApi.update(id, patch, opts);
875
- },
876
- };
877
-
878
- txTableCache.set(table, tableApi);
879
- return tableApi;
880
- };
881
-
882
- const txProxy = new Proxy(
883
- {},
884
- {
885
- get(_target, prop) {
886
- if (prop === 'then') return undefined;
887
- if (typeof prop !== 'string') return undefined;
888
- return makeTxTable(prop);
889
- },
890
- }
891
- ) as MutationsTx<DB>;
892
-
893
- const result = await fn(txProxy);
894
-
895
- if (operations.length === 0) {
896
- throw new Error('No mutations were enqueued');
897
- }
898
-
899
- const commitId = randomId();
900
- const clientCommitId = randomId();
901
-
902
- const request: SyncPushRequest = {
903
- clientId: config.clientId,
904
- clientCommitId,
905
- operations,
906
- schemaVersion: config.schemaVersion ?? 1,
907
- };
908
-
909
- const plugins = config.plugins ?? [];
910
- // Sort plugins by priority (lower numbers first, default 50)
911
- const sortedPlugins = [...plugins].sort(
912
- (a, b) => (a.priority ?? 50) - (b.priority ?? 50)
913
- );
914
- const ctx: SyncClientPluginContext = {
915
- actorId: config.actorId ?? 'unknown',
916
- clientId: config.clientId,
917
- };
918
-
919
- let requestToSend = request;
920
- if (sortedPlugins.length > 0) {
921
- requestToSend = clonePushRequest(request);
922
- for (const plugin of sortedPlugins) {
923
- if (!plugin.beforePush) continue;
924
- requestToSend = await plugin.beforePush(ctx, requestToSend);
925
- }
926
- }
927
-
928
- const combined = await config.transport.sync({
929
- clientId: requestToSend.clientId,
930
- push: {
931
- clientCommitId: requestToSend.clientCommitId,
932
- operations: requestToSend.operations,
933
- schemaVersion: requestToSend.schemaVersion,
934
- },
935
- });
936
- if (!combined.push) {
937
- throw new Error('Server returned no push response');
938
- }
939
- const rawResponse = combined.push;
940
-
941
- let response = rawResponse;
942
- if (sortedPlugins.length > 0) {
943
- // Run afterPush in reverse priority order (higher numbers first)
944
- for (const plugin of [...sortedPlugins].reverse()) {
945
- if (!plugin.afterPush) continue;
946
- response = await plugin.afterPush(ctx, {
947
- request: requestToSend,
948
- response,
949
- });
950
- }
951
- }
952
-
953
- if (response.status !== 'applied' && response.status !== 'cached') {
954
- const conflictCount = response.results.filter(
955
- (r) => r.status === 'conflict'
956
- ).length;
957
- const errorCount = response.results.filter(
958
- (r) => r.status === 'error'
959
- ).length;
960
- throw new Error(
961
- `Push rejected (${conflictCount} conflicts, ${errorCount} errors)`
962
- );
963
- }
964
-
965
- return {
966
- result,
967
- receipt: { commitId, clientCommitId },
968
- meta: { operations, localMutations, response },
969
- };
970
- };
971
- }
972
-
973
- export function createPushMutations<DB = AnyDb>(
974
- config: PushCommitConfig
975
- ): MutationsApi<DB, undefined> {
976
- return createMutationsApi(createPushCommit(config));
166
+ function hasOwn(obj: object, key: string): boolean {
167
+ return Object.hasOwn(obj, key);
977
168
  }