@syncular/core 0.1.3 → 0.2.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 (231) hide show
  1. package/README.md +5 -19
  2. package/dist/blob-ref.d.ts +21 -0
  3. package/dist/blob-ref.js +97 -0
  4. package/dist/bytes.d.ts +45 -0
  5. package/dist/bytes.js +242 -0
  6. package/dist/canonical-json.d.ts +10 -0
  7. package/dist/canonical-json.js +18 -0
  8. package/dist/constants.d.ts +5 -0
  9. package/dist/constants.js +5 -0
  10. package/dist/crypto.d.ts +60 -0
  11. package/dist/crypto.js +251 -0
  12. package/dist/errors.d.ts +14 -0
  13. package/dist/errors.js +17 -0
  14. package/dist/frames.d.ts +24 -0
  15. package/dist/frames.js +42 -0
  16. package/dist/index.d.ts +18 -26
  17. package/dist/index.js +18 -43
  18. package/dist/message.d.ts +157 -0
  19. package/dist/message.js +786 -0
  20. package/dist/realtime.d.ts +92 -0
  21. package/dist/realtime.js +222 -0
  22. package/dist/render.d.ts +22 -0
  23. package/dist/render.js +273 -0
  24. package/dist/row-codec.d.ts +39 -0
  25. package/dist/row-codec.js +176 -0
  26. package/dist/segment.d.ts +18 -0
  27. package/dist/segment.js +105 -0
  28. package/dist/stream.d.ts +20 -0
  29. package/dist/stream.js +102 -0
  30. package/package.json +7 -59
  31. package/src/blob-ref.ts +135 -0
  32. package/src/bytes.ts +301 -0
  33. package/src/canonical-json.ts +20 -0
  34. package/src/constants.ts +7 -0
  35. package/src/crypto.ts +335 -0
  36. package/src/errors.ts +18 -0
  37. package/src/frames.ts +47 -0
  38. package/src/index.ts +18 -43
  39. package/src/message.ts +1024 -0
  40. package/src/realtime.ts +340 -0
  41. package/src/render.ts +325 -0
  42. package/src/row-codec.ts +265 -0
  43. package/src/segment.ts +150 -0
  44. package/src/stream.ts +128 -0
  45. package/dist/blobs.d.ts +0 -153
  46. package/dist/blobs.d.ts.map +0 -1
  47. package/dist/blobs.js +0 -47
  48. package/dist/blobs.js.map +0 -1
  49. package/dist/column-codecs.d.ts +0 -59
  50. package/dist/column-codecs.d.ts.map +0 -1
  51. package/dist/column-codecs.js +0 -159
  52. package/dist/column-codecs.js.map +0 -1
  53. package/dist/conflict.d.ts +0 -22
  54. package/dist/conflict.d.ts.map +0 -1
  55. package/dist/conflict.js +0 -81
  56. package/dist/conflict.js.map +0 -1
  57. package/dist/database.d.ts +0 -7
  58. package/dist/database.d.ts.map +0 -1
  59. package/dist/database.js +0 -14
  60. package/dist/database.js.map +0 -1
  61. package/dist/dialect.d.ts +0 -7
  62. package/dist/dialect.d.ts.map +0 -1
  63. package/dist/dialect.js +0 -2
  64. package/dist/dialect.js.map +0 -1
  65. package/dist/error-responses.d.ts +0 -393
  66. package/dist/error-responses.d.ts.map +0 -1
  67. package/dist/error-responses.js +0 -393
  68. package/dist/error-responses.js.map +0 -1
  69. package/dist/http/api-client.d.ts +0 -11
  70. package/dist/http/api-client.d.ts.map +0 -1
  71. package/dist/http/api-client.js +0 -30
  72. package/dist/http/api-client.js.map +0 -1
  73. package/dist/http/blob.d.ts +0 -6
  74. package/dist/http/blob.d.ts.map +0 -1
  75. package/dist/http/blob.js +0 -46
  76. package/dist/http/blob.js.map +0 -1
  77. package/dist/http/generated/api.d.ts +0 -5241
  78. package/dist/http/generated/api.d.ts.map +0 -1
  79. package/dist/http/generated/api.js +0 -6
  80. package/dist/http/generated/api.js.map +0 -1
  81. package/dist/http/index.d.ts +0 -18
  82. package/dist/http/index.d.ts.map +0 -1
  83. package/dist/http/index.js +0 -212
  84. package/dist/http/index.js.map +0 -1
  85. package/dist/http/shared.d.ts +0 -45
  86. package/dist/http/shared.d.ts.map +0 -1
  87. package/dist/http/shared.js +0 -122
  88. package/dist/http/shared.js.map +0 -1
  89. package/dist/http/transport-client.d.ts +0 -31
  90. package/dist/http/transport-client.d.ts.map +0 -1
  91. package/dist/http/transport-client.js +0 -193
  92. package/dist/http/transport-client.js.map +0 -1
  93. package/dist/index.d.ts.map +0 -1
  94. package/dist/index.js.map +0 -1
  95. package/dist/kysely-column-codecs.d.ts +0 -31
  96. package/dist/kysely-column-codecs.d.ts.map +0 -1
  97. package/dist/kysely-column-codecs.js +0 -452
  98. package/dist/kysely-column-codecs.js.map +0 -1
  99. package/dist/logger.d.ts +0 -29
  100. package/dist/logger.d.ts.map +0 -1
  101. package/dist/logger.js +0 -26
  102. package/dist/logger.js.map +0 -1
  103. package/dist/proxy/index.d.ts +0 -54
  104. package/dist/proxy/index.d.ts.map +0 -1
  105. package/dist/proxy/index.js +0 -7
  106. package/dist/proxy/index.js.map +0 -1
  107. package/dist/schemas/blobs.d.ts +0 -82
  108. package/dist/schemas/blobs.d.ts.map +0 -1
  109. package/dist/schemas/blobs.js +0 -64
  110. package/dist/schemas/blobs.js.map +0 -1
  111. package/dist/schemas/common.d.ts +0 -100
  112. package/dist/schemas/common.d.ts.map +0 -1
  113. package/dist/schemas/common.js +0 -64
  114. package/dist/schemas/common.js.map +0 -1
  115. package/dist/schemas/index.d.ts +0 -7
  116. package/dist/schemas/index.d.ts.map +0 -1
  117. package/dist/schemas/index.js +0 -7
  118. package/dist/schemas/index.js.map +0 -1
  119. package/dist/schemas/sync.d.ts +0 -1065
  120. package/dist/schemas/sync.d.ts.map +0 -1
  121. package/dist/schemas/sync.js +0 -361
  122. package/dist/schemas/sync.js.map +0 -1
  123. package/dist/scopes/index.d.ts +0 -113
  124. package/dist/scopes/index.d.ts.map +0 -1
  125. package/dist/scopes/index.js +0 -103
  126. package/dist/scopes/index.js.map +0 -1
  127. package/dist/sentry.d.ts +0 -31
  128. package/dist/sentry.d.ts.map +0 -1
  129. package/dist/sentry.js +0 -147
  130. package/dist/sentry.js.map +0 -1
  131. package/dist/snapshot-chunks.d.ts +0 -110
  132. package/dist/snapshot-chunks.d.ts.map +0 -1
  133. package/dist/snapshot-chunks.js +0 -682
  134. package/dist/snapshot-chunks.js.map +0 -1
  135. package/dist/sync-packs.d.ts +0 -33
  136. package/dist/sync-packs.d.ts.map +0 -1
  137. package/dist/sync-packs.js +0 -821
  138. package/dist/sync-packs.js.map +0 -1
  139. package/dist/telemetry.d.ts +0 -114
  140. package/dist/telemetry.d.ts.map +0 -1
  141. package/dist/telemetry.js +0 -114
  142. package/dist/telemetry.js.map +0 -1
  143. package/dist/transforms.d.ts +0 -146
  144. package/dist/transforms.d.ts.map +0 -1
  145. package/dist/transforms.js +0 -155
  146. package/dist/transforms.js.map +0 -1
  147. package/dist/types.d.ts +0 -210
  148. package/dist/types.d.ts.map +0 -1
  149. package/dist/types.js +0 -20
  150. package/dist/types.js.map +0 -1
  151. package/dist/utils/bytes.d.ts +0 -4
  152. package/dist/utils/bytes.d.ts.map +0 -1
  153. package/dist/utils/bytes.js +0 -66
  154. package/dist/utils/bytes.js.map +0 -1
  155. package/dist/utils/compression.d.ts +0 -22
  156. package/dist/utils/compression.d.ts.map +0 -1
  157. package/dist/utils/compression.js +0 -129
  158. package/dist/utils/compression.js.map +0 -1
  159. package/dist/utils/crypto.d.ts +0 -13
  160. package/dist/utils/crypto.d.ts.map +0 -1
  161. package/dist/utils/crypto.js +0 -107
  162. package/dist/utils/crypto.js.map +0 -1
  163. package/dist/utils/id.d.ts +0 -3
  164. package/dist/utils/id.d.ts.map +0 -1
  165. package/dist/utils/id.js +0 -25
  166. package/dist/utils/id.js.map +0 -1
  167. package/dist/utils/index.d.ts +0 -10
  168. package/dist/utils/index.d.ts.map +0 -1
  169. package/dist/utils/index.js +0 -10
  170. package/dist/utils/index.js.map +0 -1
  171. package/dist/utils/internal-runtime.d.ts +0 -16
  172. package/dist/utils/internal-runtime.d.ts.map +0 -1
  173. package/dist/utils/internal-runtime.js +0 -14
  174. package/dist/utils/internal-runtime.js.map +0 -1
  175. package/dist/utils/object.d.ts +0 -2
  176. package/dist/utils/object.d.ts.map +0 -1
  177. package/dist/utils/object.js +0 -4
  178. package/dist/utils/object.js.map +0 -1
  179. package/dist/utils/realtime-change-index.d.ts +0 -9
  180. package/dist/utils/realtime-change-index.d.ts.map +0 -1
  181. package/dist/utils/realtime-change-index.js +0 -36
  182. package/dist/utils/realtime-change-index.js.map +0 -1
  183. package/dist/utils/realtime-connection-registry.d.ts +0 -40
  184. package/dist/utils/realtime-connection-registry.d.ts.map +0 -1
  185. package/dist/utils/realtime-connection-registry.js +0 -237
  186. package/dist/utils/realtime-connection-registry.js.map +0 -1
  187. package/dist/utils/table-registry.d.ts +0 -10
  188. package/dist/utils/table-registry.d.ts.map +0 -1
  189. package/dist/utils/table-registry.js +0 -54
  190. package/dist/utils/table-registry.js.map +0 -1
  191. package/dist/utils/url.d.ts +0 -3
  192. package/dist/utils/url.d.ts.map +0 -1
  193. package/dist/utils/url.js +0 -20
  194. package/dist/utils/url.js.map +0 -1
  195. package/src/blobs.ts +0 -219
  196. package/src/column-codecs.ts +0 -262
  197. package/src/conflict.ts +0 -92
  198. package/src/database.ts +0 -21
  199. package/src/dialect.ts +0 -8
  200. package/src/error-responses.ts +0 -423
  201. package/src/http/api-client.ts +0 -39
  202. package/src/http/blob.ts +0 -95
  203. package/src/http/generated/api.ts +0 -5241
  204. package/src/http/index.ts +0 -365
  205. package/src/http/shared.ts +0 -220
  206. package/src/http/transport-client.ts +0 -314
  207. package/src/kysely-column-codecs.ts +0 -585
  208. package/src/logger.ts +0 -38
  209. package/src/proxy/index.ts +0 -57
  210. package/src/schemas/blobs.ts +0 -110
  211. package/src/schemas/common.ts +0 -89
  212. package/src/schemas/index.ts +0 -7
  213. package/src/schemas/sync.ts +0 -517
  214. package/src/scopes/index.ts +0 -237
  215. package/src/sentry.ts +0 -200
  216. package/src/snapshot-chunks.ts +0 -964
  217. package/src/sync-packs.ts +0 -1096
  218. package/src/telemetry.ts +0 -243
  219. package/src/transforms.ts +0 -256
  220. package/src/types.ts +0 -267
  221. package/src/utils/bytes.ts +0 -75
  222. package/src/utils/compression.ts +0 -187
  223. package/src/utils/crypto.ts +0 -146
  224. package/src/utils/id.ts +0 -34
  225. package/src/utils/index.ts +0 -9
  226. package/src/utils/internal-runtime.ts +0 -39
  227. package/src/utils/object.ts +0 -3
  228. package/src/utils/realtime-change-index.ts +0 -44
  229. package/src/utils/realtime-connection-registry.ts +0 -286
  230. package/src/utils/table-registry.ts +0 -67
  231. package/src/utils/url.ts +0 -29
package/src/http/index.ts DELETED
@@ -1,365 +0,0 @@
1
- /**
2
- * @syncular/core/http - HTTP transport for Sync
3
- *
4
- * Provides:
5
- * - a lightweight fetch-based SyncTransport for client runtime use
6
- * - a separately exported typed API client for advanced/console use
7
- */
8
-
9
- import type {
10
- SyncBootstrapApplyMode,
11
- SyncCombinedRequest,
12
- SyncCombinedResponse,
13
- SyncTransport,
14
- SyncTransportCapabilities,
15
- SyncTransportOptions,
16
- } from '@syncular/core';
17
- import { SyncTransportError } from '@syncular/core';
18
- import type { SyncClient } from './api-client';
19
- import {
20
- applySnapshotScopesHeader,
21
- bytesToReadableStream,
22
- type ClientOptions,
23
- executeWithAuthRetry,
24
- executeWithTransientNetworkRetry,
25
- getErrorMessage,
26
- resolveSnapshotChunkRequestUrl,
27
- type SyncTransportPath,
28
- unwrap,
29
- } from './shared';
30
- import {
31
- createTransportApiClient,
32
- createTransportAuthRetryResolver,
33
- HTTP_TRANSPORT_SOURCE,
34
- type HttpTransportSource,
35
- } from './transport-client';
36
-
37
- export type {
38
- SyncAuthErrorContext,
39
- SyncAuthLifecycle,
40
- SyncAuthOperation,
41
- SyncTransport,
42
- SyncTransportOptions,
43
- } from '@syncular/core';
44
- export { createApiClient } from './api-client';
45
- export type { operations } from './generated/api';
46
- export { type ClientOptions, type SyncClient, type SyncTransportPath, unwrap };
47
-
48
- function detectDefaultTransportCapabilities(): SyncTransportCapabilities {
49
- const isReactNative =
50
- typeof navigator !== 'undefined' && navigator?.product === 'ReactNative';
51
- const snapshotChunkReadMode = isReactNative ? 'bytes' : 'stream';
52
- const gzipDecompressionMode =
53
- !isReactNative && typeof DecompressionStream !== 'undefined'
54
- ? 'stream'
55
- : 'buffered';
56
- const preferredBootstrapApplyMode: SyncBootstrapApplyMode =
57
- snapshotChunkReadMode === 'bytes' || gzipDecompressionMode === 'buffered'
58
- ? 'per-subscription'
59
- : 'single-transaction';
60
-
61
- return {
62
- snapshotChunkReadMode,
63
- gzipDecompressionMode,
64
- preferredBootstrapApplyMode,
65
- preferredSnapshotApplyYieldMs: isReactNative ? 0 : false,
66
- };
67
- }
68
-
69
- function mergeTransportCapabilities(
70
- overrides?: Partial<SyncTransportCapabilities>
71
- ): SyncTransportCapabilities {
72
- const defaults = detectDefaultTransportCapabilities();
73
- const merged: SyncTransportCapabilities = {
74
- ...defaults,
75
- ...overrides,
76
- };
77
-
78
- if (
79
- overrides?.preferredBootstrapApplyMode !== undefined &&
80
- overrides?.preferredSnapshotApplyYieldMs !== undefined
81
- ) {
82
- return merged;
83
- }
84
-
85
- const preferredBootstrapApplyMode: SyncBootstrapApplyMode =
86
- merged.snapshotChunkReadMode === 'bytes' ||
87
- merged.gzipDecompressionMode === 'buffered'
88
- ? 'per-subscription'
89
- : 'single-transaction';
90
-
91
- return {
92
- ...merged,
93
- preferredBootstrapApplyMode:
94
- overrides?.preferredBootstrapApplyMode ?? preferredBootstrapApplyMode,
95
- preferredSnapshotApplyYieldMs:
96
- overrides?.preferredSnapshotApplyYieldMs ??
97
- (merged.snapshotChunkReadMode === 'bytes' ||
98
- merged.gzipDecompressionMode === 'buffered'
99
- ? 0
100
- : false),
101
- };
102
- }
103
-
104
- function shouldUseResponseBodyStream(
105
- response: Response,
106
- capabilities: SyncTransportCapabilities
107
- ): boolean {
108
- if (!response.body) return false;
109
- if (capabilities.snapshotChunkReadMode === 'bytes') {
110
- return false;
111
- }
112
- return (
113
- typeof (response.body as ReadableStream<Uint8Array>).getReader ===
114
- 'function'
115
- );
116
- }
117
-
118
- export const REACT_NATIVE_TRANSPORT_CAPABILITIES: SyncTransportCapabilities = {
119
- snapshotChunkReadMode: 'bytes',
120
- gzipDecompressionMode: 'buffered',
121
- preferredBootstrapApplyMode: 'per-subscription',
122
- preferredSnapshotApplyYieldMs: 0,
123
- preferMaterializedSnapshots: true,
124
- };
125
-
126
- export function createHttpTransport(
127
- clientOrOptions: SyncClient | ClientOptions
128
- ): SyncTransport {
129
- const client = createTransportApiClient(clientOrOptions);
130
- const resolveAuthRetry = createTransportAuthRetryResolver(clientOrOptions);
131
- const transportOptions =
132
- 'baseUrl' in clientOrOptions ? clientOrOptions : undefined;
133
- const capabilities =
134
- 'baseUrl' in clientOrOptions
135
- ? mergeTransportCapabilities(clientOrOptions.capabilities)
136
- : mergeTransportCapabilities();
137
-
138
- const transport: SyncTransport = {
139
- capabilities,
140
- async sync(
141
- request: SyncCombinedRequest,
142
- transportRequestOptions?: SyncTransportOptions
143
- ): Promise<SyncCombinedResponse> {
144
- const { data, error, response } = await executeWithTransientNetworkRetry({
145
- execute: (signal) =>
146
- executeWithAuthRetry(
147
- (retrySignal) => client.sync(request, retrySignal),
148
- { ...transportRequestOptions, signal },
149
- 'sync',
150
- resolveAuthRetry
151
- ),
152
- options: transportRequestOptions,
153
- });
154
-
155
- if (error || !data) {
156
- throw new SyncTransportError(
157
- `Sync failed: ${getErrorMessage(error) || response.statusText}`,
158
- response.status
159
- );
160
- }
161
-
162
- return data as SyncCombinedResponse;
163
- },
164
-
165
- async fetchSnapshotChunk(
166
- request: {
167
- chunkId: string;
168
- scopeValues?: Record<string, string | string[]>;
169
- },
170
- options?: SyncTransportOptions
171
- ): Promise<Uint8Array> {
172
- if (!transportOptions) {
173
- const { data, error, response } = await executeWithAuthRetry(
174
- (signal) =>
175
- client.getSnapshotChunk(
176
- request.chunkId,
177
- request.scopeValues,
178
- signal
179
- ),
180
- options,
181
- 'snapshotChunk',
182
- resolveAuthRetry
183
- );
184
-
185
- if (error || !data) {
186
- throw new SyncTransportError(
187
- `Snapshot chunk download failed: ${getErrorMessage(error) || response.statusText}`,
188
- response.status
189
- );
190
- }
191
-
192
- return new Uint8Array(await (data as Blob).arrayBuffer());
193
- }
194
-
195
- const fetchImpl = transportOptions.fetch ?? globalThis.fetch;
196
- const requestUrl = resolveSnapshotChunkRequestUrl(
197
- transportOptions.baseUrl,
198
- request.chunkId,
199
- request.scopeValues
200
- );
201
-
202
- const performRequest = async (
203
- signal?: AbortSignal
204
- ): Promise<Response> => {
205
- const headers = new Headers();
206
- applySnapshotScopesHeader(headers, request.scopeValues);
207
- const extraHeaders = await transportOptions.getHeaders?.();
208
- if (extraHeaders) {
209
- for (const [key, value] of Object.entries(extraHeaders)) {
210
- headers.set(key, value);
211
- }
212
- }
213
- if (!headers.has('x-syncular-transport-path')) {
214
- headers.set(
215
- 'x-syncular-transport-path',
216
- transportOptions.transportPath ?? 'direct'
217
- );
218
- }
219
- return fetchImpl(requestUrl, {
220
- method: 'GET',
221
- headers,
222
- ...(signal ? { signal } : {}),
223
- });
224
- };
225
-
226
- let response = await performRequest(options?.signal);
227
- if (response.status === 401 || response.status === 403) {
228
- const shouldRetry = await resolveAuthRetry(
229
- {
230
- operation: 'snapshotChunk',
231
- status: response.status,
232
- },
233
- options
234
- );
235
- if (shouldRetry) {
236
- response = await performRequest(options?.signal);
237
- }
238
- }
239
-
240
- if (!response.ok) {
241
- let reason = response.statusText || 'Request failed';
242
- try {
243
- const maybeJson = (await response.json()) as
244
- | { error?: unknown; message?: unknown }
245
- | undefined;
246
- reason = getErrorMessage(maybeJson) || reason;
247
- } catch {
248
- // ignore parse failures
249
- }
250
- throw new SyncTransportError(
251
- `Snapshot chunk download failed: ${reason}`,
252
- response.status
253
- );
254
- }
255
-
256
- return new Uint8Array(await response.arrayBuffer());
257
- },
258
-
259
- async fetchSnapshotChunkStream(
260
- request: {
261
- chunkId: string;
262
- scopeValues?: Record<string, string | string[]>;
263
- },
264
- options?: SyncTransportOptions
265
- ): Promise<ReadableStream<Uint8Array>> {
266
- if (!transportOptions) {
267
- const bytes = await this.fetchSnapshotChunk(request, options);
268
- return bytesToReadableStream(bytes);
269
- }
270
-
271
- const fetchImpl = transportOptions.fetch ?? globalThis.fetch;
272
- const requestUrl = resolveSnapshotChunkRequestUrl(
273
- transportOptions.baseUrl,
274
- request.chunkId,
275
- request.scopeValues
276
- );
277
-
278
- const performRequest = async (
279
- signal?: AbortSignal
280
- ): Promise<Response> => {
281
- const headers = new Headers();
282
- applySnapshotScopesHeader(headers, request.scopeValues);
283
- const extraHeaders = await transportOptions.getHeaders?.();
284
- if (extraHeaders) {
285
- for (const [key, value] of Object.entries(extraHeaders)) {
286
- headers.set(key, value);
287
- }
288
- }
289
- if (!headers.has('x-syncular-transport-path')) {
290
- headers.set(
291
- 'x-syncular-transport-path',
292
- transportOptions.transportPath ?? 'direct'
293
- );
294
- }
295
- return fetchImpl(requestUrl, {
296
- method: 'GET',
297
- headers,
298
- ...(signal ? { signal } : {}),
299
- });
300
- };
301
-
302
- let response = await performRequest(options?.signal);
303
- if (response.status === 401 || response.status === 403) {
304
- const shouldRetry = await resolveAuthRetry(
305
- {
306
- operation: 'snapshotChunkStream',
307
- status: response.status,
308
- },
309
- options
310
- );
311
- if (shouldRetry) {
312
- response = await performRequest(options?.signal);
313
- }
314
- }
315
-
316
- if (!response.ok) {
317
- let reason = response.statusText || 'Request failed';
318
- try {
319
- const maybeJson = (await response.json()) as
320
- | { error?: unknown; message?: unknown }
321
- | undefined;
322
- reason = getErrorMessage(maybeJson) || reason;
323
- } catch {
324
- // ignore parse failures
325
- }
326
- throw new SyncTransportError(
327
- `Snapshot chunk download failed: ${reason}`,
328
- response.status
329
- );
330
- }
331
-
332
- if (!shouldUseResponseBodyStream(response, capabilities)) {
333
- const bytes = new Uint8Array(await response.arrayBuffer());
334
- return bytesToReadableStream(bytes);
335
- }
336
-
337
- return response.body as ReadableStream<Uint8Array>;
338
- },
339
- };
340
-
341
- Object.defineProperty(transport, HTTP_TRANSPORT_SOURCE, {
342
- configurable: false,
343
- enumerable: false,
344
- value: clientOrOptions as HttpTransportSource,
345
- writable: false,
346
- });
347
-
348
- return transport;
349
- }
350
-
351
- export function createReactNativeHttpTransport(
352
- clientOrOptions: SyncClient | ClientOptions
353
- ): SyncTransport {
354
- if (!('baseUrl' in clientOrOptions)) {
355
- return createHttpTransport(clientOrOptions);
356
- }
357
-
358
- return createHttpTransport({
359
- ...clientOrOptions,
360
- capabilities: {
361
- ...REACT_NATIVE_TRANSPORT_CAPABILITIES,
362
- ...clientOrOptions.capabilities,
363
- },
364
- });
365
- }
@@ -1,220 +0,0 @@
1
- import type {
2
- ScopeValues,
3
- SyncAuthErrorContext,
4
- SyncAuthLifecycle,
5
- SyncAuthOperation,
6
- SyncTransportCapabilities,
7
- SyncTransportOptions,
8
- } from '@syncular/core';
9
- import { resolveUrlFromBase } from '@syncular/core';
10
-
11
- export type SyncTransportPath = 'direct' | 'relay';
12
-
13
- export interface ClientOptions {
14
- /** Base URL for the API (e.g., 'https://api.example.com') */
15
- baseUrl: string;
16
- /** Function to get headers for requests (e.g., for auth tokens) */
17
- getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
18
- /** Shared auth lifecycle for all transport operations. */
19
- authLifecycle?: SyncAuthLifecycle;
20
- /** Custom fetch implementation (defaults to globalThis.fetch) */
21
- fetch?: typeof globalThis.fetch;
22
- /**
23
- * Transport path telemetry sent to the server.
24
- * Defaults to 'direct'.
25
- */
26
- transportPath?: SyncTransportPath;
27
- /**
28
- * Explicit runtime capabilities override.
29
- * Use this to avoid environment sniffing in runtimes like React Native.
30
- */
31
- capabilities?: Partial<SyncTransportCapabilities>;
32
- }
33
-
34
- export type ApiResult<T> = {
35
- data?: T;
36
- error?: unknown;
37
- response: Response;
38
- };
39
-
40
- export type ResolveAuthRetry = (
41
- context: SyncAuthErrorContext,
42
- options?: SyncTransportOptions
43
- ) => Promise<boolean>;
44
-
45
- const TRANSIENT_NETWORK_RETRY_DELAYS_MS = [75, 150, 300] as const;
46
-
47
- class ApiResponseError extends Error {
48
- constructor(
49
- message: string,
50
- public readonly error?: unknown
51
- ) {
52
- super(message);
53
- this.name = 'ApiResponseError';
54
- }
55
- }
56
-
57
- export function getErrorMessage(error: unknown): string {
58
- if (error && typeof error === 'object' && 'error' in error) {
59
- const inner = (error as { error: unknown }).error;
60
- if (typeof inner === 'string') return inner;
61
- }
62
- if (error && typeof error === 'object' && 'message' in error) {
63
- const msg = (error as { message: unknown }).message;
64
- if (typeof msg === 'string') return msg;
65
- }
66
- return 'Request failed';
67
- }
68
-
69
- export async function unwrap<T>(
70
- promise: Promise<{ data?: T; error?: unknown }>
71
- ): Promise<T> {
72
- const { data, error } = await promise;
73
- if (error || !data) {
74
- throw new ApiResponseError(getErrorMessage(error), error);
75
- }
76
- return data;
77
- }
78
-
79
- export async function executeWithAuthRetry<T>(
80
- execute: (signal?: AbortSignal) => Promise<ApiResult<T>>,
81
- options: SyncTransportOptions | undefined,
82
- operation: SyncAuthOperation,
83
- resolveAuthRetry: ResolveAuthRetry
84
- ): Promise<ApiResult<T>> {
85
- const first = await execute(options?.signal);
86
- if (first.response.status !== 401 && first.response.status !== 403) {
87
- return first;
88
- }
89
- const shouldRetry = await resolveAuthRetry(
90
- { operation, status: first.response.status },
91
- options
92
- );
93
- if (!shouldRetry) {
94
- return first;
95
- }
96
-
97
- return execute(options?.signal);
98
- }
99
-
100
- function isAbortError(error: unknown): boolean {
101
- return error instanceof Error && error.name === 'AbortError';
102
- }
103
-
104
- function isTransientNetworkError(error: unknown): boolean {
105
- if (isAbortError(error)) return false;
106
-
107
- const message =
108
- error instanceof Error
109
- ? error.message.toLowerCase()
110
- : String(error).toLowerCase();
111
-
112
- return (
113
- message.includes('fetch failed') ||
114
- message.includes('network') ||
115
- message.includes('socket') ||
116
- message.includes('econnreset') ||
117
- message.includes('econnrefused') ||
118
- message.includes('connection refused') ||
119
- message.includes('connection reset') ||
120
- message.includes('terminated') ||
121
- message.includes('eof')
122
- );
123
- }
124
-
125
- function delay(ms: number): Promise<void> {
126
- return new Promise((resolve) => {
127
- setTimeout(resolve, ms);
128
- });
129
- }
130
-
131
- export async function executeWithTransientNetworkRetry<T>(args: {
132
- execute: (signal?: AbortSignal) => Promise<T>;
133
- options?: SyncTransportOptions;
134
- }): Promise<T> {
135
- let lastError: unknown;
136
-
137
- for (
138
- let attemptIndex = 0;
139
- attemptIndex <= TRANSIENT_NETWORK_RETRY_DELAYS_MS.length;
140
- attemptIndex += 1
141
- ) {
142
- try {
143
- return await args.execute(args.options?.signal);
144
- } catch (error) {
145
- lastError = error;
146
- if (!isTransientNetworkError(error) || args.options?.signal?.aborted) {
147
- throw error;
148
- }
149
-
150
- const delayMs = TRANSIENT_NETWORK_RETRY_DELAYS_MS[attemptIndex];
151
- if (delayMs === undefined) {
152
- throw error;
153
- }
154
-
155
- await delay(delayMs);
156
-
157
- if (args.options?.signal?.aborted) {
158
- throw error;
159
- }
160
- }
161
- }
162
-
163
- throw lastError;
164
- }
165
-
166
- export const SNAPSHOT_SCOPES_HEADER = 'x-syncular-snapshot-scopes';
167
-
168
- export function resolveRequestUrl(baseUrl: string, path: string): string {
169
- return resolveUrlFromBase(
170
- baseUrl,
171
- path,
172
- typeof location === 'undefined' ? undefined : location.origin
173
- );
174
- }
175
-
176
- export function bytesToReadableStream(
177
- bytes: Uint8Array
178
- ): ReadableStream<Uint8Array> {
179
- return new ReadableStream<Uint8Array>({
180
- start(controller) {
181
- controller.enqueue(bytes);
182
- controller.close();
183
- },
184
- });
185
- }
186
-
187
- export function encodeSnapshotScopes(
188
- scopeValues: ScopeValues | undefined
189
- ): string | null {
190
- if (!scopeValues) return null;
191
- if (Object.keys(scopeValues).length === 0) return null;
192
- return JSON.stringify(scopeValues);
193
- }
194
-
195
- export function applySnapshotScopesHeader(
196
- headers: Headers,
197
- scopeValues: ScopeValues | undefined
198
- ): void {
199
- const encodedScopes = encodeSnapshotScopes(scopeValues);
200
- if (!encodedScopes) return;
201
- headers.set(SNAPSHOT_SCOPES_HEADER, encodedScopes);
202
- }
203
-
204
- export function resolveSnapshotChunkRequestUrl(
205
- baseUrl: string,
206
- chunkId: string,
207
- scopeValues: ScopeValues | undefined
208
- ): string {
209
- const requestUrl = new URL(
210
- resolveRequestUrl(
211
- baseUrl,
212
- `/sync/snapshot-chunks/${encodeURIComponent(chunkId)}`
213
- )
214
- );
215
- const encodedScopes = encodeSnapshotScopes(scopeValues);
216
- if (encodedScopes) {
217
- requestUrl.searchParams.set('scopes', encodedScopes);
218
- }
219
- return requestUrl.toString();
220
- }