@syncular/client 0.1.3 → 0.2.1

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 (233) hide show
  1. package/README.md +227 -709
  2. package/dist/apply.d.ts +80 -0
  3. package/dist/apply.js +301 -0
  4. package/dist/blob.d.ts +127 -0
  5. package/dist/blob.js +201 -0
  6. package/dist/bun-database.d.ts +22 -0
  7. package/dist/bun-database.js +61 -0
  8. package/dist/client.d.ts +320 -69
  9. package/dist/client.js +1627 -163
  10. package/dist/content-type.d.ts +2 -0
  11. package/dist/content-type.js +2 -0
  12. package/dist/database.d.ts +35 -80
  13. package/dist/database.js +32 -947
  14. package/dist/encryption.d.ts +47 -0
  15. package/dist/encryption.js +75 -0
  16. package/dist/errors.d.ts +8 -22
  17. package/dist/errors.js +10 -207
  18. package/dist/http.d.ts +32 -0
  19. package/dist/http.js +196 -0
  20. package/dist/index.d.ts +28 -16
  21. package/dist/index.js +25 -12
  22. package/dist/invalidation.d.ts +69 -0
  23. package/dist/invalidation.js +84 -0
  24. package/dist/leader-lock.d.ts +28 -0
  25. package/dist/leader-lock.js +38 -0
  26. package/dist/multi-tab.d.ts +134 -0
  27. package/dist/multi-tab.js +399 -0
  28. package/dist/node-database.d.ts +41 -0
  29. package/dist/node-database.js +133 -0
  30. package/dist/outbox.d.ts +56 -0
  31. package/dist/outbox.js +140 -0
  32. package/dist/schema.d.ts +107 -0
  33. package/dist/schema.js +267 -0
  34. package/dist/state.d.ts +40 -0
  35. package/dist/state.js +77 -0
  36. package/dist/transport.d.ts +44 -0
  37. package/dist/transport.js +7 -0
  38. package/dist/wasm-database.d.ts +30 -0
  39. package/dist/wasm-database.js +170 -0
  40. package/dist/window.d.ts +64 -0
  41. package/dist/window.js +0 -0
  42. package/dist/worker-entry.d.ts +16 -2
  43. package/dist/worker-entry.js +300 -456
  44. package/dist/worker-host.d.ts +174 -0
  45. package/dist/worker-host.js +536 -0
  46. package/dist/worker-protocol.d.ts +173 -61
  47. package/dist/worker-protocol.js +7 -16
  48. package/package.json +45 -112
  49. package/src/apply.ts +435 -0
  50. package/src/blob.ts +333 -0
  51. package/src/bun-database.ts +75 -0
  52. package/src/client.ts +2247 -277
  53. package/src/content-type.ts +2 -0
  54. package/src/database.ts +63 -1389
  55. package/src/encryption.ts +123 -0
  56. package/src/errors.ts +11 -265
  57. package/src/http.ts +254 -0
  58. package/src/index.ts +25 -20
  59. package/src/invalidation.ts +128 -0
  60. package/src/leader-lock.ts +68 -0
  61. package/src/multi-tab.ts +550 -0
  62. package/src/node-database.ts +180 -0
  63. package/src/outbox.ts +206 -0
  64. package/src/schema.ts +397 -0
  65. package/src/state.ts +126 -0
  66. package/src/transport.ts +52 -0
  67. package/src/wasm-database.ts +279 -0
  68. package/src/window.ts +0 -0
  69. package/src/worker-entry.ts +391 -545
  70. package/src/worker-host.ts +800 -0
  71. package/src/worker-protocol.ts +204 -99
  72. package/dist/auth-leases.d.ts +0 -11
  73. package/dist/auth-leases.d.ts.map +0 -1
  74. package/dist/auth-leases.js +0 -140
  75. package/dist/auth-leases.js.map +0 -1
  76. package/dist/blob-limits.d.ts +0 -15
  77. package/dist/blob-limits.d.ts.map +0 -1
  78. package/dist/blob-limits.js +0 -66
  79. package/dist/blob-limits.js.map +0 -1
  80. package/dist/bridge-client.d.ts +0 -69
  81. package/dist/bridge-client.d.ts.map +0 -1
  82. package/dist/bridge-client.js +0 -282
  83. package/dist/bridge-client.js.map +0 -1
  84. package/dist/client-config.d.ts +0 -6
  85. package/dist/client-config.d.ts.map +0 -1
  86. package/dist/client-config.js +0 -18
  87. package/dist/client-config.js.map +0 -1
  88. package/dist/client.d.ts.map +0 -1
  89. package/dist/client.js.map +0 -1
  90. package/dist/command-history.d.ts +0 -35
  91. package/dist/command-history.d.ts.map +0 -1
  92. package/dist/command-history.js +0 -378
  93. package/dist/command-history.js.map +0 -1
  94. package/dist/console-diagnostics.d.ts +0 -33
  95. package/dist/console-diagnostics.d.ts.map +0 -1
  96. package/dist/console-diagnostics.js +0 -496
  97. package/dist/console-diagnostics.js.map +0 -1
  98. package/dist/crdt-yjs/index.d.ts +0 -4
  99. package/dist/crdt-yjs/index.d.ts.map +0 -1
  100. package/dist/crdt-yjs/index.js +0 -4
  101. package/dist/crdt-yjs/index.js.map +0 -1
  102. package/dist/crdt-yjs/webview-host-facade.d.ts +0 -126
  103. package/dist/crdt-yjs/webview-host-facade.d.ts.map +0 -1
  104. package/dist/crdt-yjs/webview-host-facade.js +0 -284
  105. package/dist/crdt-yjs/webview-host-facade.js.map +0 -1
  106. package/dist/crdt-yjs/yjs-document-field-adapter.d.ts +0 -153
  107. package/dist/crdt-yjs/yjs-document-field-adapter.d.ts.map +0 -1
  108. package/dist/crdt-yjs/yjs-document-field-adapter.js +0 -406
  109. package/dist/crdt-yjs/yjs-document-field-adapter.js.map +0 -1
  110. package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts +0 -73
  111. package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts.map +0 -1
  112. package/dist/crdt-yjs/yjs-prosemirror-bridge.js +0 -169
  113. package/dist/crdt-yjs/yjs-prosemirror-bridge.js.map +0 -1
  114. package/dist/database.d.ts.map +0 -1
  115. package/dist/database.js.map +0 -1
  116. package/dist/diagnostics.d.ts +0 -10
  117. package/dist/diagnostics.d.ts.map +0 -1
  118. package/dist/diagnostics.js +0 -83
  119. package/dist/diagnostics.js.map +0 -1
  120. package/dist/errors.d.ts.map +0 -1
  121. package/dist/errors.js.map +0 -1
  122. package/dist/generated-bridge.d.ts +0 -365
  123. package/dist/generated-bridge.d.ts.map +0 -1
  124. package/dist/generated-bridge.js +0 -250
  125. package/dist/generated-bridge.js.map +0 -1
  126. package/dist/index.d.ts.map +0 -1
  127. package/dist/index.js.map +0 -1
  128. package/dist/mutations.d.ts +0 -72
  129. package/dist/mutations.d.ts.map +0 -1
  130. package/dist/mutations.js +0 -63
  131. package/dist/mutations.js.map +0 -1
  132. package/dist/network.d.ts +0 -3
  133. package/dist/network.d.ts.map +0 -1
  134. package/dist/network.js +0 -17
  135. package/dist/network.js.map +0 -1
  136. package/dist/react/index.d.ts +0 -169
  137. package/dist/react/index.d.ts.map +0 -1
  138. package/dist/react/index.js +0 -628
  139. package/dist/react/index.js.map +0 -1
  140. package/dist/react-native/index.d.ts +0 -35
  141. package/dist/react-native/index.d.ts.map +0 -1
  142. package/dist/react-native/index.js +0 -49
  143. package/dist/react-native/index.js.map +0 -1
  144. package/dist/runtime-contract.d.ts +0 -13
  145. package/dist/runtime-contract.d.ts.map +0 -1
  146. package/dist/runtime-contract.js +0 -24
  147. package/dist/runtime-contract.js.map +0 -1
  148. package/dist/rust-client.d.ts +0 -106
  149. package/dist/rust-client.d.ts.map +0 -1
  150. package/dist/rust-client.js +0 -938
  151. package/dist/rust-client.js.map +0 -1
  152. package/dist/sentry.d.ts +0 -35
  153. package/dist/sentry.d.ts.map +0 -1
  154. package/dist/sentry.js +0 -155
  155. package/dist/sentry.js.map +0 -1
  156. package/dist/sql-safety.d.ts +0 -3
  157. package/dist/sql-safety.d.ts.map +0 -1
  158. package/dist/sql-safety.js +0 -62
  159. package/dist/sql-safety.js.map +0 -1
  160. package/dist/syncular-runtime-artifacts.json +0 -61
  161. package/dist/tauri/index.d.ts +0 -35
  162. package/dist/tauri/index.d.ts.map +0 -1
  163. package/dist/tauri/index.js +0 -114
  164. package/dist/tauri/index.js.map +0 -1
  165. package/dist/types.d.ts +0 -1096
  166. package/dist/types.d.ts.map +0 -1
  167. package/dist/types.js +0 -2
  168. package/dist/types.js.map +0 -1
  169. package/dist/wasm/.syncular-wasm-profile +0 -1
  170. package/dist/wasm/syncular-runtime-artifact.json +0 -21
  171. package/dist/wasm/syncular.d.ts +0 -207
  172. package/dist/wasm/syncular.js +0 -2341
  173. package/dist/wasm/syncular_bg.wasm +0 -0
  174. package/dist/wasm/syncular_bg.wasm.d.ts +0 -97
  175. package/dist/wasm-bindings/runtime-contract.d.ts +0 -22
  176. package/dist/wasm-bindings/runtime-contract.d.ts.map +0 -1
  177. package/dist/wasm-bindings/runtime-contract.js +0 -112
  178. package/dist/wasm-bindings/runtime-contract.js.map +0 -1
  179. package/dist/wasm-core/.syncular-wasm-profile +0 -1
  180. package/dist/wasm-core/syncular-runtime-artifact.json +0 -17
  181. package/dist/wasm-core/syncular.d.ts +0 -162
  182. package/dist/wasm-core/syncular.js +0 -1847
  183. package/dist/wasm-core/syncular_bg.wasm +0 -0
  184. package/dist/wasm-core/syncular_bg.wasm.d.ts +0 -77
  185. package/dist/wasm-perf/.syncular-wasm-profile +0 -1
  186. package/dist/wasm-perf/syncular-runtime-artifact.json +0 -21
  187. package/dist/wasm-perf/syncular.d.ts +0 -207
  188. package/dist/wasm-perf/syncular.js +0 -2341
  189. package/dist/wasm-perf/syncular_bg.wasm +0 -0
  190. package/dist/wasm-perf/syncular_bg.wasm.d.ts +0 -97
  191. package/dist/wasm-runtime.d.ts +0 -23
  192. package/dist/wasm-runtime.d.ts.map +0 -1
  193. package/dist/wasm-runtime.js +0 -69
  194. package/dist/wasm-runtime.js.map +0 -1
  195. package/dist/worker-client.d.ts +0 -123
  196. package/dist/worker-client.d.ts.map +0 -1
  197. package/dist/worker-client.js +0 -1735
  198. package/dist/worker-client.js.map +0 -1
  199. package/dist/worker-entry.d.ts.map +0 -1
  200. package/dist/worker-entry.js.map +0 -1
  201. package/dist/worker-protocol.d.ts.map +0 -1
  202. package/dist/worker-protocol.js.map +0 -1
  203. package/dist/worker-realtime.d.ts +0 -39
  204. package/dist/worker-realtime.d.ts.map +0 -1
  205. package/dist/worker-realtime.js +0 -677
  206. package/dist/worker-realtime.js.map +0 -1
  207. package/src/auth-leases.ts +0 -251
  208. package/src/blob-limits.ts +0 -98
  209. package/src/bridge-client.ts +0 -512
  210. package/src/client-config.ts +0 -29
  211. package/src/command-history.ts +0 -623
  212. package/src/console-diagnostics.ts +0 -617
  213. package/src/crdt-yjs/index.ts +0 -3
  214. package/src/crdt-yjs/webview-host-facade.ts +0 -477
  215. package/src/crdt-yjs/yjs-document-field-adapter.ts +0 -733
  216. package/src/crdt-yjs/yjs-prosemirror-bridge.ts +0 -272
  217. package/src/diagnostics.ts +0 -116
  218. package/src/generated-bridge.ts +0 -741
  219. package/src/mutations.ts +0 -168
  220. package/src/network.ts +0 -32
  221. package/src/react/index.ts +0 -1036
  222. package/src/react-native/index.ts +0 -152
  223. package/src/runtime-contract.ts +0 -48
  224. package/src/rust-client.ts +0 -1491
  225. package/src/sentry.ts +0 -215
  226. package/src/sql-safety.ts +0 -61
  227. package/src/tauri/index.ts +0 -211
  228. package/src/types.ts +0 -1397
  229. package/src/wasm-bindings/generated-wasm-bindings.d.ts +0 -70
  230. package/src/wasm-bindings/runtime-contract.ts +0 -158
  231. package/src/wasm-runtime.ts +0 -145
  232. package/src/worker-client.ts +0 -2289
  233. package/src/worker-realtime.ts +0 -843
@@ -1,512 +0,0 @@
1
- import {
2
- randomId,
3
- type SyncAuthLeaseIssueRequest,
4
- type SyncOperation,
5
- } from '@syncular/core';
6
- import {
7
- type CompiledQuery,
8
- type DatabaseConnection,
9
- type Dialect,
10
- Kysely,
11
- type QueryResult,
12
- } from 'kysely';
13
- import { BaseSqliteDialect, BaseSqliteDriver } from 'kysely-generic-sqlite';
14
- import type {
15
- SyncularBlobClientLike,
16
- SyncularClientLike,
17
- SyncularClientStatus,
18
- } from './client';
19
- import {
20
- createMutationsApi,
21
- type MutationReceipt,
22
- type MutationsApi,
23
- type MutationsCommitFn,
24
- type MutationsTx,
25
- } from './mutations';
26
- import { assertSyncularReadonlySql } from './sql-safety';
27
- import type {
28
- SyncularAuthLeaseRecord,
29
- SyncularClientEventSink,
30
- SyncularClientEventType,
31
- SyncularConflictResolution,
32
- SyncularConflictStats,
33
- SyncularConflictSummary,
34
- SyncularConnectionState,
35
- SyncularDiagnosticSnapshot,
36
- SyncularLifecycleState,
37
- SyncularOutboxStats,
38
- SyncularPresenceEntry,
39
- SyncularPresenceSink,
40
- SyncularSubscriptionSpec,
41
- SyncularSyncRequestOptions,
42
- SyncularSyncResult,
43
- } from './types';
44
-
45
- export interface SyncularBridgeQueryRequest {
46
- sql: string;
47
- parameters: readonly unknown[];
48
- }
49
-
50
- export interface SyncularBridgeQueryResult<
51
- Row extends Record<string, unknown> = Record<string, unknown>,
52
- > {
53
- rows: Row[];
54
- numAffectedRows?: bigint | number | string;
55
- insertId?: bigint | number | string | null;
56
- }
57
-
58
- export interface SyncularBridgeMutationBatch {
59
- operations: SyncOperation[];
60
- }
61
-
62
- export interface SyncularBridgeStatus {
63
- lifecycle?: SyncularLifecycleState;
64
- connection?: SyncularConnectionState;
65
- outbox?: SyncularOutboxStats | null;
66
- conflicts?: SyncularConflictStats | null;
67
- }
68
-
69
- export interface SyncularBridgePresence {
70
- get<TMetadata = Record<string, unknown>>(
71
- scopeKey: string
72
- ): SyncularPresenceEntry<TMetadata>[];
73
- join(scopeKey: string, metadata?: Record<string, unknown>): void;
74
- leave(scopeKey: string): void;
75
- updateMetadata(scopeKey: string, metadata: Record<string, unknown>): void;
76
- onChange<TMetadata = Record<string, unknown>>(
77
- listener: SyncularPresenceSink<TMetadata>
78
- ): () => void;
79
- }
80
-
81
- export interface SyncularBridgeConflicts {
82
- list(): Promise<SyncularConflictSummary[]>;
83
- retryKeepLocal(id: string): Promise<string>;
84
- resolve(id: string, resolution: SyncularConflictResolution): Promise<void>;
85
- }
86
-
87
- export interface SyncularBridge {
88
- open?(): Promise<void> | void;
89
- close?(): Promise<void> | void;
90
- executeSql<Row extends Record<string, unknown> = Record<string, unknown>>(
91
- request: SyncularBridgeQueryRequest
92
- ): Promise<SyncularBridgeQueryResult<Row>> | SyncularBridgeQueryResult<Row>;
93
- applyMutationsCommit(
94
- batch: SyncularBridgeMutationBatch
95
- ): Promise<string | MutationReceipt> | string | MutationReceipt;
96
- applyLeasedMutationsCommit?(
97
- batch: SyncularBridgeMutationBatch
98
- ): Promise<string | MutationReceipt> | string | MutationReceipt;
99
- sync?(): Promise<SyncularSyncResult>;
100
- resumeFromBackground?(
101
- options?: SyncularSyncRequestOptions
102
- ): Promise<SyncularSyncResult>;
103
- start?(): Promise<void>;
104
- stop?(): Promise<void>;
105
- setSubscriptions?(
106
- subscriptions: readonly SyncularSubscriptionSpec[]
107
- ): Promise<void>;
108
- getStatus?(): Promise<SyncularBridgeStatus> | SyncularBridgeStatus;
109
- issueAuthLease?(
110
- request: SyncAuthLeaseIssueRequest
111
- ): Promise<SyncularAuthLeaseRecord>;
112
- upsertAuthLease?(lease: SyncularAuthLeaseRecord): Promise<void>;
113
- authLease?(leaseId: string): Promise<SyncularAuthLeaseRecord | null>;
114
- activeAuthLeases?(
115
- actorId?: string | null,
116
- nowMs?: number
117
- ): Promise<SyncularAuthLeaseRecord[]>;
118
- diagnosticSnapshot?(): Promise<SyncularDiagnosticSnapshot>;
119
- on?<T extends SyncularClientEventType>(
120
- event: T,
121
- listener: SyncularClientEventSink<T>
122
- ): () => void;
123
- presence?: SyncularBridgePresence;
124
- conflicts?: SyncularBridgeConflicts;
125
- blobs?: Partial<SyncularBlobClientLike>;
126
- }
127
-
128
- export interface CreateSyncularBridgeClientOptions {
129
- bridge: SyncularBridge;
130
- sync?: {
131
- autoSyncAfterMutation?: boolean;
132
- };
133
- idColumn?: string;
134
- versionColumn?: string | null;
135
- omitColumns?: readonly string[];
136
- }
137
-
138
- export function createSyncularBridgeDialect(bridge: SyncularBridge): Dialect {
139
- return new BaseSqliteDialect(() => new SyncularBridgeDriver(bridge));
140
- }
141
-
142
- export async function createSyncularBridgeClient<DB>(
143
- options: CreateSyncularBridgeClientOptions
144
- ): Promise<SyncularClientLike<DB>> {
145
- await options.bridge.open?.();
146
- const dialect = createSyncularBridgeDialect(options.bridge);
147
- const db = new Kysely<DB>({ dialect });
148
- let closed = false;
149
- const syncAfterMutation = options.sync?.autoSyncAfterMutation !== false;
150
-
151
- const client: SyncularClientLike<DB> = {
152
- db,
153
- dialect,
154
- mutations: createSyncularBridgeMutations<DB>(options, async () => {
155
- if (syncAfterMutation) await client.sync().catch(() => undefined);
156
- }),
157
- leasedMutations: createSyncularBridgeMutations<DB>(
158
- options,
159
- async () => {
160
- if (syncAfterMutation) await client.sync().catch(() => undefined);
161
- },
162
- { leased: true }
163
- ),
164
- blobs: createBridgeBlobs(options.bridge),
165
- on: (event, listener) => options.bridge.on?.(event, listener) ?? noop,
166
- getStatus: () => resolveBridgeStatusSnapshot(options.bridge),
167
- setSubscriptions: async (subscriptions) => {
168
- await options.bridge.setSubscriptions?.(subscriptions);
169
- },
170
- resumeFromBackground: (syncOptions) =>
171
- requireBridgeMethod(
172
- options.bridge.resumeFromBackground,
173
- 'resumeFromBackground'
174
- )(syncOptions),
175
- issueAuthLease: (request) =>
176
- requireBridgeMethod(
177
- options.bridge.issueAuthLease,
178
- 'issueAuthLease'
179
- )(request),
180
- upsertAuthLease: (lease) =>
181
- requireBridgeMethod(
182
- options.bridge.upsertAuthLease,
183
- 'upsertAuthLease'
184
- )(lease),
185
- authLease: (leaseId) =>
186
- requireBridgeMethod(options.bridge.authLease, 'authLease')(leaseId),
187
- activeAuthLeases: (actorId, nowMs) =>
188
- requireBridgeMethod(options.bridge.activeAuthLeases, 'activeAuthLeases')(
189
- actorId,
190
- nowMs
191
- ),
192
- diagnosticSnapshot: () =>
193
- requireBridgeMethod(
194
- options.bridge.diagnosticSnapshot,
195
- 'diagnosticSnapshot'
196
- )(),
197
- presence: createBridgePresence(options.bridge),
198
- conflicts: createBridgeConflicts(options.bridge),
199
- start: async () => {
200
- await options.bridge.start?.();
201
- },
202
- stop: async () => {
203
- await options.bridge.stop?.();
204
- },
205
- sync: async () => {
206
- if (!options.bridge.sync) {
207
- throw new Error('Syncular bridge does not implement sync().');
208
- }
209
- return await options.bridge.sync();
210
- },
211
- async close() {
212
- if (closed) return;
213
- closed = true;
214
- await options.bridge.stop?.();
215
- await db.destroy();
216
- await options.bridge.close?.();
217
- },
218
- };
219
-
220
- return client;
221
- }
222
-
223
- function createSyncularBridgeMutations<DB>(
224
- options: CreateSyncularBridgeClientOptions,
225
- afterCommit: () => Promise<void>,
226
- mode: { leased?: boolean } = {}
227
- ): MutationsApi<DB, undefined> {
228
- const commit = createSyncularBridgeCommit<DB>(options, afterCommit, mode);
229
- return createMutationsApi(commit);
230
- }
231
-
232
- function createSyncularBridgeCommit<DB>(
233
- options: CreateSyncularBridgeClientOptions,
234
- afterCommit: () => Promise<void>,
235
- mode: { leased?: boolean } = {}
236
- ): MutationsCommitFn<DB, { operations: SyncOperation[] }, undefined> {
237
- const idColumn = options.idColumn ?? 'id';
238
- const versionColumn = options.versionColumn ?? 'server_version';
239
- const omitColumns = options.omitColumns ?? [];
240
-
241
- return async (fn) => {
242
- const operations: SyncOperation[] = [];
243
- const makeTxTable = (table: string) => ({
244
- async insert(values: unknown) {
245
- const raw = objectRecord(values);
246
- const id =
247
- typeof raw[idColumn] === 'string' && raw[idColumn]
248
- ? raw[idColumn]
249
- : randomId();
250
- operations.push({
251
- table,
252
- row_id: id,
253
- op: 'upsert',
254
- payload: sanitizeOperationPayload({ ...raw, [idColumn]: id }, [
255
- idColumn,
256
- ...(versionColumn ? [versionColumn] : []),
257
- ...omitColumns,
258
- ]),
259
- base_version: null,
260
- });
261
- return id;
262
- },
263
- async insertMany(rows: unknown[]) {
264
- const ids: string[] = [];
265
- for (const row of rows) ids.push(await this.insert(row));
266
- return ids;
267
- },
268
- async update(
269
- id: string,
270
- patch: unknown,
271
- opts?: { baseVersion?: number | null }
272
- ) {
273
- operations.push(upsertOperation(table, id, patch, opts));
274
- },
275
- async delete(id: string, opts?: { baseVersion?: number | null }) {
276
- operations.push({
277
- table,
278
- row_id: id,
279
- op: 'delete',
280
- payload: null,
281
- base_version: opts?.baseVersion ?? null,
282
- });
283
- },
284
- async upsert(
285
- id: string,
286
- patch: unknown,
287
- opts?: { baseVersion?: number | null }
288
- ) {
289
- operations.push(upsertOperation(table, id, patch, opts));
290
- },
291
- });
292
-
293
- const tx = new Proxy(
294
- {},
295
- {
296
- get(_target, prop) {
297
- if (prop === 'then') return undefined;
298
- return typeof prop === 'string' ? makeTxTable(prop) : undefined;
299
- },
300
- }
301
- ) as MutationsTx<DB>;
302
-
303
- const result = await fn(tx);
304
- if (operations.length === 0) throw new Error('No mutations were enqueued');
305
- const commitFn = mode.leased
306
- ? requireBridgeMethod(
307
- options.bridge.applyLeasedMutationsCommit,
308
- 'applyLeasedMutationsCommit'
309
- )
310
- : options.bridge.applyMutationsCommit.bind(options.bridge);
311
- const receipt = normalizeMutationReceipt(await commitFn({ operations }));
312
- await afterCommit();
313
- return { result, receipt, meta: { operations } };
314
- };
315
-
316
- function upsertOperation(
317
- table: string,
318
- id: string,
319
- patch: unknown,
320
- opts?: { baseVersion?: number | null }
321
- ): SyncOperation {
322
- return {
323
- table,
324
- row_id: id,
325
- op: 'upsert',
326
- payload: sanitizeOperationPayload(objectRecord(patch), [
327
- idColumn,
328
- ...(versionColumn ? [versionColumn] : []),
329
- ...omitColumns,
330
- ]),
331
- base_version: opts?.baseVersion ?? null,
332
- };
333
- }
334
- }
335
-
336
- class SyncularBridgeDriver extends BaseSqliteDriver {
337
- constructor(bridge: SyncularBridge) {
338
- super(async () => {
339
- this.conn = new SyncularBridgeConnection(bridge);
340
- });
341
- }
342
-
343
- async destroy(): Promise<void> {
344
- this.conn = undefined;
345
- }
346
- }
347
-
348
- class SyncularBridgeConnection implements DatabaseConnection {
349
- constructor(private readonly bridge: SyncularBridge) {}
350
-
351
- async executeQuery<R>(compiledQuery: CompiledQuery): Promise<QueryResult<R>> {
352
- assertSyncularReadonlySql(compiledQuery.sql);
353
- const result = await this.bridge.executeSql<R & Record<string, unknown>>({
354
- sql: compiledQuery.sql,
355
- parameters: compiledQuery.parameters,
356
- });
357
- return {
358
- rows: result.rows as R[],
359
- numAffectedRows:
360
- result.numAffectedRows == null
361
- ? undefined
362
- : BigInt(result.numAffectedRows),
363
- insertId: result.insertId == null ? undefined : BigInt(result.insertId),
364
- };
365
- }
366
-
367
- streamQuery<R>(
368
- _compiledQuery: CompiledQuery,
369
- _chunkSize?: number
370
- ): AsyncIterableIterator<QueryResult<R>> {
371
- throw new Error('Syncular bridge dialect does not support streaming.');
372
- }
373
- }
374
-
375
- function createBridgePresence(bridge: SyncularBridge): SyncularBridgePresence {
376
- return (
377
- bridge.presence ?? {
378
- get: () => [],
379
- join: unsupportedFn('presence.join'),
380
- leave: unsupportedFn('presence.leave'),
381
- updateMetadata: unsupportedFn('presence.updateMetadata'),
382
- onChange: () => noop,
383
- }
384
- );
385
- }
386
-
387
- function createBridgeConflicts(
388
- bridge: SyncularBridge
389
- ): SyncularBridgeConflicts {
390
- return (
391
- bridge.conflicts ?? {
392
- list: async () => [],
393
- retryKeepLocal: unsupportedFn('conflicts.retryKeepLocal'),
394
- resolve: unsupportedFn('conflicts.resolve'),
395
- }
396
- );
397
- }
398
-
399
- function createBridgeBlobs(bridge: SyncularBridge): SyncularBlobClientLike {
400
- return {
401
- getUploadQueueStats: async () =>
402
- bridge.blobs?.getUploadQueueStats?.() ?? {
403
- pending: 0,
404
- uploading: 0,
405
- failed: 0,
406
- },
407
- processUploadQueue: async (options) =>
408
- bridge.blobs?.processUploadQueue?.(options) ?? { uploaded: 0, failed: 0 },
409
- retrieve:
410
- bridge.blobs?.retrieve ??
411
- (async () => {
412
- throw new Error('Syncular bridge does not implement blob retrieval.');
413
- }),
414
- };
415
- }
416
-
417
- function resolveBridgeStatusSnapshot(
418
- bridge: SyncularBridge
419
- ): SyncularClientStatus {
420
- const snapshot = readSynchronousStatus(bridge);
421
- const connection = snapshot.connection ?? {
422
- closed: false,
423
- pendingRequests: 0,
424
- realtime: 'disconnected',
425
- };
426
- const lifecycle = snapshot.lifecycle ?? {
427
- phase: connection.closed ? 'closed' : 'offline',
428
- realtime: connection.realtime,
429
- online: connection.realtime === 'connected',
430
- requiresAction: false,
431
- pendingRequests: connection.pendingRequests,
432
- ...(snapshot.outbox ? { outbox: snapshot.outbox } : {}),
433
- ...(snapshot.conflicts ? { conflicts: snapshot.conflicts } : {}),
434
- };
435
- const outbox = snapshot.outbox ?? lifecycle.outbox ?? null;
436
- const conflicts = snapshot.conflicts ?? lifecycle.conflicts ?? null;
437
- return {
438
- lifecycle,
439
- connection,
440
- outbox,
441
- conflicts,
442
- isConnected: connection.realtime === 'connected' && !connection.closed,
443
- isSyncing:
444
- lifecycle.phase === 'syncing' || lifecycle.phase === 'recovering',
445
- hasPendingMutations: (outbox?.pending ?? 0) + (outbox?.sending ?? 0) > 0,
446
- hasConflicts: (conflicts?.unresolved ?? 0) > 0,
447
- requiresAction: lifecycle.requiresAction,
448
- };
449
- }
450
-
451
- function readSynchronousStatus(bridge: SyncularBridge): SyncularBridgeStatus {
452
- if (!bridge.getStatus) return {};
453
- const snapshot = bridge.getStatus();
454
- if (isPromiseLike(snapshot)) {
455
- throw new Error(
456
- 'Syncular bridge getStatus() must be synchronous for React snapshots.'
457
- );
458
- }
459
- return snapshot;
460
- }
461
-
462
- function normalizeMutationReceipt(
463
- value: string | MutationReceipt
464
- ): MutationReceipt {
465
- if (typeof value === 'string')
466
- return { commitId: value, clientCommitId: value };
467
- return value;
468
- }
469
-
470
- function sanitizeOperationPayload(
471
- payload: Record<string, unknown>,
472
- omitColumns: readonly string[]
473
- ): Record<string, unknown> {
474
- const omit = new Set(omitColumns);
475
- const out: Record<string, unknown> = {};
476
- for (const [key, value] of Object.entries(payload)) {
477
- if (!omit.has(key)) out[key] = value;
478
- }
479
- return out;
480
- }
481
-
482
- function objectRecord(value: unknown): Record<string, unknown> {
483
- return value && typeof value === 'object' && !Array.isArray(value)
484
- ? { ...(value as Record<string, unknown>) }
485
- : {};
486
- }
487
-
488
- function unsupportedFn<Args extends unknown[], Result>(
489
- method: string
490
- ): (...args: Args) => Result {
491
- return () => {
492
- throw new Error(`Syncular bridge does not implement ${method}.`);
493
- };
494
- }
495
-
496
- function requireBridgeMethod<Fn extends (...args: any[]) => any>(
497
- fn: Fn | undefined,
498
- method: string
499
- ): Fn {
500
- if (typeof fn === 'function') return fn;
501
- return unsupportedFn(method) as Fn;
502
- }
503
-
504
- function isPromiseLike(value: unknown): value is Promise<unknown> {
505
- return (
506
- value != null &&
507
- typeof value === 'object' &&
508
- typeof (value as { then?: unknown }).then === 'function'
509
- );
510
- }
511
-
512
- function noop(): void {}
@@ -1,29 +0,0 @@
1
- import type {
2
- ResolvedSyncularClientConfig,
3
- SyncularClientConfig,
4
- SyncularStorage,
5
- } from './types';
6
-
7
- export const SYNCULAR_DEFAULT_STORAGE = 'opfsSahPool' satisfies SyncularStorage;
8
- export const SYNCULAR_LOCAL_DISABLED_BASE_URL = 'syncular-local://disabled';
9
-
10
- export function resolveSyncularClientConfig(
11
- config: SyncularClientConfig
12
- ): ResolvedSyncularClientConfig {
13
- const mode = config.mode ?? 'remote';
14
- if (mode === 'remote' && !config.baseUrl) {
15
- throw new Error('Syncular remote clients require config.baseUrl');
16
- }
17
- return {
18
- ...config,
19
- mode,
20
- baseUrl: config.baseUrl ?? SYNCULAR_LOCAL_DISABLED_BASE_URL,
21
- storage: config.storage ?? SYNCULAR_DEFAULT_STORAGE,
22
- };
23
- }
24
-
25
- export function isSyncularRemoteMode(
26
- config: Pick<ResolvedSyncularClientConfig, 'mode'>
27
- ): boolean {
28
- return config.mode === 'remote';
29
- }