@syncular/core 0.0.1 → 0.0.2-127

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 (70) hide show
  1. package/README.md +24 -0
  2. package/dist/blobs.d.ts +9 -4
  3. package/dist/blobs.d.ts.map +1 -1
  4. package/dist/blobs.js +0 -12
  5. package/dist/blobs.js.map +1 -1
  6. package/dist/column-codecs.d.ts +55 -0
  7. package/dist/column-codecs.d.ts.map +1 -0
  8. package/dist/column-codecs.js +124 -0
  9. package/dist/column-codecs.js.map +1 -0
  10. package/dist/index.d.ts +11 -9
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +21 -7
  13. package/dist/index.js.map +1 -1
  14. package/dist/kysely-serialize.d.ts +1 -1
  15. package/dist/kysely-serialize.d.ts.map +1 -1
  16. package/dist/logger.d.ts +7 -32
  17. package/dist/logger.d.ts.map +1 -1
  18. package/dist/logger.js +6 -40
  19. package/dist/logger.js.map +1 -1
  20. package/dist/proxy/types.d.ts +0 -9
  21. package/dist/proxy/types.d.ts.map +1 -1
  22. package/dist/schemas/index.js +3 -3
  23. package/dist/schemas/sync.d.ts +120 -6
  24. package/dist/schemas/sync.d.ts.map +1 -1
  25. package/dist/schemas/sync.js +17 -3
  26. package/dist/schemas/sync.js.map +1 -1
  27. package/dist/scopes/index.d.ts +39 -64
  28. package/dist/scopes/index.d.ts.map +1 -1
  29. package/dist/scopes/index.js +9 -154
  30. package/dist/scopes/index.js.map +1 -1
  31. package/dist/snapshot-chunks.d.ts +26 -0
  32. package/dist/snapshot-chunks.d.ts.map +1 -0
  33. package/dist/snapshot-chunks.js +89 -0
  34. package/dist/snapshot-chunks.js.map +1 -0
  35. package/dist/telemetry.d.ts +114 -0
  36. package/dist/telemetry.d.ts.map +1 -0
  37. package/dist/telemetry.js +113 -0
  38. package/dist/telemetry.js.map +1 -0
  39. package/dist/types.d.ts +12 -9
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/types.js.map +1 -1
  42. package/dist/utils/id.d.ts +2 -0
  43. package/dist/utils/id.d.ts.map +1 -0
  44. package/dist/utils/id.js +8 -0
  45. package/dist/utils/id.js.map +1 -0
  46. package/dist/utils/index.d.ts +3 -0
  47. package/dist/utils/index.d.ts.map +1 -0
  48. package/dist/utils/index.js +3 -0
  49. package/dist/utils/index.js.map +1 -0
  50. package/dist/utils/object.d.ts +2 -0
  51. package/dist/utils/object.d.ts.map +1 -0
  52. package/dist/utils/object.js +4 -0
  53. package/dist/utils/object.js.map +1 -0
  54. package/package.json +28 -8
  55. package/src/__tests__/telemetry.test.ts +170 -0
  56. package/src/__tests__/utils.test.ts +27 -0
  57. package/src/blobs.ts +15 -14
  58. package/src/column-codecs.ts +228 -0
  59. package/src/index.ts +15 -41
  60. package/src/kysely-serialize.ts +1 -1
  61. package/src/logger.ts +10 -68
  62. package/src/proxy/types.ts +0 -10
  63. package/src/schemas/sync.ts +27 -3
  64. package/src/scopes/index.ts +72 -200
  65. package/src/snapshot-chunks.ts +112 -0
  66. package/src/telemetry.ts +238 -0
  67. package/src/types.ts +14 -18
  68. package/src/utils/id.ts +7 -0
  69. package/src/utils/index.ts +2 -0
  70. package/src/utils/object.ts +3 -0
@@ -0,0 +1,170 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { logSyncEvent } from '../logger';
3
+ import {
4
+ captureSyncException,
5
+ configureSyncTelemetry,
6
+ countSyncMetric,
7
+ distributionSyncMetric,
8
+ gaugeSyncMetric,
9
+ getSyncTelemetry,
10
+ resetSyncTelemetry,
11
+ type SyncMetricOptions,
12
+ type SyncSpan,
13
+ type SyncSpanOptions,
14
+ type SyncTelemetry,
15
+ type SyncTelemetryEvent,
16
+ startSyncSpan,
17
+ } from '../telemetry';
18
+
19
+ interface CapturedCountMetric {
20
+ name: string;
21
+ value: number | undefined;
22
+ options: SyncMetricOptions | undefined;
23
+ }
24
+
25
+ interface CapturedValueMetric {
26
+ name: string;
27
+ value: number;
28
+ options: SyncMetricOptions | undefined;
29
+ }
30
+
31
+ function createTestTelemetry(calls: {
32
+ logs: SyncTelemetryEvent[];
33
+ countMetrics: CapturedCountMetric[];
34
+ gaugeMetrics: CapturedValueMetric[];
35
+ distributionMetrics: CapturedValueMetric[];
36
+ spans: SyncSpanOptions[];
37
+ exceptions: Array<{
38
+ error: unknown;
39
+ context: Record<string, unknown> | undefined;
40
+ }>;
41
+ }): SyncTelemetry {
42
+ return {
43
+ log(event) {
44
+ calls.logs.push(event);
45
+ },
46
+ tracer: {
47
+ startSpan(options, callback) {
48
+ calls.spans.push(options);
49
+ const span: SyncSpan = {
50
+ setAttribute() {},
51
+ setAttributes() {},
52
+ setStatus() {},
53
+ };
54
+ return callback(span);
55
+ },
56
+ },
57
+ metrics: {
58
+ count(name, value, options) {
59
+ calls.countMetrics.push({ name, value, options });
60
+ },
61
+ gauge(name, value, options) {
62
+ calls.gaugeMetrics.push({ name, value, options });
63
+ },
64
+ distribution(name, value, options) {
65
+ calls.distributionMetrics.push({ name, value, options });
66
+ },
67
+ },
68
+ captureException(error, context) {
69
+ calls.exceptions.push({ error, context });
70
+ },
71
+ };
72
+ }
73
+
74
+ describe('sync telemetry configuration', () => {
75
+ test('routes logger, metrics, spans, and exceptions to configured backend', () => {
76
+ const calls = {
77
+ logs: [] as SyncTelemetryEvent[],
78
+ countMetrics: [] as CapturedCountMetric[],
79
+ gaugeMetrics: [] as CapturedValueMetric[],
80
+ distributionMetrics: [] as CapturedValueMetric[],
81
+ spans: [] as SyncSpanOptions[],
82
+ exceptions: [] as Array<{
83
+ error: unknown;
84
+ context: Record<string, unknown> | undefined;
85
+ }>,
86
+ };
87
+ const telemetry = createTestTelemetry(calls);
88
+ const previous = getSyncTelemetry();
89
+
90
+ try {
91
+ configureSyncTelemetry(telemetry);
92
+
93
+ logSyncEvent({ event: 'sync.test.log', rowCount: 3 });
94
+
95
+ const spanResult = startSyncSpan(
96
+ {
97
+ name: 'sync.test.span',
98
+ op: 'sync.test',
99
+ attributes: { transport: 'ws' },
100
+ },
101
+ () => 'done'
102
+ );
103
+
104
+ countSyncMetric('sync.test.count', 2, {
105
+ attributes: { source: 'unit-test' },
106
+ });
107
+ gaugeSyncMetric('sync.test.gauge', 7, { unit: 'millisecond' });
108
+ distributionSyncMetric('sync.test.dist', 13);
109
+ captureSyncException(new Error('boom'), {
110
+ operation: 'unit-test',
111
+ });
112
+
113
+ expect(spanResult).toBe('done');
114
+ expect(calls.logs).toEqual([{ event: 'sync.test.log', rowCount: 3 }]);
115
+ expect(calls.spans).toEqual([
116
+ {
117
+ name: 'sync.test.span',
118
+ op: 'sync.test',
119
+ attributes: { transport: 'ws' },
120
+ },
121
+ ]);
122
+ expect(calls.countMetrics).toEqual([
123
+ {
124
+ name: 'sync.test.count',
125
+ value: 2,
126
+ options: { attributes: { source: 'unit-test' } },
127
+ },
128
+ ]);
129
+ expect(calls.gaugeMetrics).toEqual([
130
+ {
131
+ name: 'sync.test.gauge',
132
+ value: 7,
133
+ options: { unit: 'millisecond' },
134
+ },
135
+ ]);
136
+ expect(calls.distributionMetrics).toEqual([
137
+ {
138
+ name: 'sync.test.dist',
139
+ value: 13,
140
+ options: undefined,
141
+ },
142
+ ]);
143
+ expect(calls.exceptions).toHaveLength(1);
144
+ expect(calls.exceptions[0]?.context).toEqual({ operation: 'unit-test' });
145
+ } finally {
146
+ configureSyncTelemetry(previous);
147
+ }
148
+ });
149
+
150
+ test('resetSyncTelemetry swaps out custom telemetry backend', () => {
151
+ const calls = {
152
+ logs: [] as SyncTelemetryEvent[],
153
+ countMetrics: [] as CapturedCountMetric[],
154
+ gaugeMetrics: [] as CapturedValueMetric[],
155
+ distributionMetrics: [] as CapturedValueMetric[],
156
+ spans: [] as SyncSpanOptions[],
157
+ exceptions: [] as Array<{
158
+ error: unknown;
159
+ context: Record<string, unknown> | undefined;
160
+ }>,
161
+ };
162
+ const telemetry = createTestTelemetry(calls);
163
+
164
+ configureSyncTelemetry(telemetry);
165
+ resetSyncTelemetry();
166
+ logSyncEvent({ event: 'sync.default.logger' });
167
+
168
+ expect(calls.logs).toHaveLength(0);
169
+ });
170
+ });
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { isRecord, randomId } from '../utils';
3
+
4
+ describe('isRecord', () => {
5
+ it('returns true for plain objects', () => {
6
+ expect(isRecord({ a: 1 })).toBe(true);
7
+ });
8
+
9
+ it('returns false for null and arrays', () => {
10
+ expect(isRecord(null)).toBe(false);
11
+ expect(isRecord([])).toBe(false);
12
+ });
13
+ });
14
+
15
+ describe('randomId', () => {
16
+ it('returns a non-empty string id', () => {
17
+ const id = randomId();
18
+ expect(typeof id).toBe('string');
19
+ expect(id.length > 0).toBe(true);
20
+ });
21
+
22
+ it('generates different ids across sequential calls', () => {
23
+ const first = randomId();
24
+ const second = randomId();
25
+ expect(first).not.toBe(second);
26
+ });
27
+ });
package/src/blobs.ts CHANGED
@@ -133,10 +133,25 @@ export interface BlobStorageAdapter {
133
133
  metadata?: Record<string, unknown>
134
134
  ): Promise<void>;
135
135
 
136
+ /**
137
+ * Store blob data directly from a stream.
138
+ * Preferred for large payloads to avoid full buffering in memory.
139
+ */
140
+ putStream?(
141
+ hash: string,
142
+ stream: ReadableStream<Uint8Array>,
143
+ metadata?: Record<string, unknown>
144
+ ): Promise<void>;
145
+
136
146
  /**
137
147
  * Get blob data directly (for adapters that support direct retrieval).
138
148
  */
139
149
  get?(hash: string): Promise<Uint8Array | null>;
150
+
151
+ /**
152
+ * Get blob data directly as a stream (for adapters that support stream retrieval).
153
+ */
154
+ getStream?(hash: string): Promise<ReadableStream<Uint8Array> | null>;
140
155
  }
141
156
 
142
157
  // ============================================================================
@@ -185,17 +200,3 @@ export function parseBlobHash(hash: string): string | null {
185
200
  export function createBlobHash(hexHash: string): string {
186
201
  return `sha256:${hexHash.toLowerCase()}`;
187
202
  }
188
-
189
- /**
190
- * Check if a value looks like a BlobRef.
191
- */
192
- export function isBlobRef(value: unknown): value is BlobRef {
193
- if (typeof value !== 'object' || value === null) return false;
194
- const obj = value as Record<string, unknown>;
195
- return (
196
- typeof obj.hash === 'string' &&
197
- obj.hash.startsWith('sha256:') &&
198
- typeof obj.size === 'number' &&
199
- typeof obj.mimeType === 'string'
200
- );
201
- }
@@ -0,0 +1,228 @@
1
+ export type ColumnCodecDialect = 'sqlite' | 'postgres';
2
+
3
+ export interface ColumnCodecTypeImport {
4
+ name: string;
5
+ from: string;
6
+ }
7
+
8
+ export type ColumnCodecType =
9
+ | string
10
+ | { type: string; import?: ColumnCodecTypeImport };
11
+
12
+ export interface ColumnCodec<App, Db> {
13
+ ts: ColumnCodecType;
14
+ toDb(value: App): Db;
15
+ fromDb(value: Db): App;
16
+ dialects?: Partial<
17
+ Record<
18
+ ColumnCodecDialect,
19
+ {
20
+ toDb?(value: App): Db;
21
+ fromDb?(value: Db): App;
22
+ }
23
+ >
24
+ >;
25
+ }
26
+
27
+ export type AnyColumnCodec = ColumnCodec<unknown, unknown>;
28
+
29
+ export interface ColumnCodecColumn {
30
+ table: string;
31
+ column: string;
32
+ sqlType?: string;
33
+ nullable?: boolean;
34
+ isPrimaryKey?: boolean;
35
+ hasDefault?: boolean;
36
+ dialect?: ColumnCodecDialect;
37
+ }
38
+
39
+ export type TableColumnCodecs = Record<string, AnyColumnCodec>;
40
+
41
+ export type ColumnCodecSource = (
42
+ column: ColumnCodecColumn
43
+ ) => AnyColumnCodec | undefined;
44
+
45
+ function hasCodecs(tableCodecs: TableColumnCodecs): boolean {
46
+ return Object.keys(tableCodecs).length > 0;
47
+ }
48
+
49
+ function resolveCodecToDb(
50
+ codec: AnyColumnCodec,
51
+ dialect: ColumnCodecDialect
52
+ ): (value: unknown) => unknown {
53
+ return codec.dialects?.[dialect]?.toDb ?? codec.toDb;
54
+ }
55
+
56
+ function resolveCodecFromDb(
57
+ codec: AnyColumnCodec,
58
+ dialect: ColumnCodecDialect
59
+ ): (value: unknown) => unknown {
60
+ return codec.dialects?.[dialect]?.fromDb ?? codec.fromDb;
61
+ }
62
+
63
+ export function toTableColumnCodecs(
64
+ table: string,
65
+ codecSource: ColumnCodecSource | undefined,
66
+ columns: Iterable<string>,
67
+ options: {
68
+ dialect?: ColumnCodecDialect;
69
+ sqlTypes?: Record<string, string | undefined>;
70
+ } = {}
71
+ ): TableColumnCodecs {
72
+ if (!codecSource) return {};
73
+ const out: TableColumnCodecs = {};
74
+
75
+ for (const column of columns) {
76
+ if (column.length === 0) continue;
77
+ const codec = codecSource({
78
+ table,
79
+ column,
80
+ sqlType: options.sqlTypes?.[column],
81
+ dialect: options.dialect,
82
+ });
83
+ if (codec) out[column] = codec;
84
+ }
85
+
86
+ return out;
87
+ }
88
+
89
+ export function applyCodecToDbValue(
90
+ codec: AnyColumnCodec,
91
+ value: unknown,
92
+ dialect: ColumnCodecDialect = 'sqlite'
93
+ ): unknown {
94
+ if (value === null || value === undefined) return value;
95
+ const transform = resolveCodecToDb(codec, dialect);
96
+ return transform(value);
97
+ }
98
+
99
+ export function applyCodecFromDbValue(
100
+ codec: AnyColumnCodec,
101
+ value: unknown,
102
+ dialect: ColumnCodecDialect = 'sqlite'
103
+ ): unknown {
104
+ if (value === null || value === undefined) return value;
105
+ const transform = resolveCodecFromDb(codec, dialect);
106
+ return transform(value);
107
+ }
108
+
109
+ export function applyCodecsToDbRow(
110
+ row: Record<string, unknown>,
111
+ tableCodecs: TableColumnCodecs,
112
+ dialect: ColumnCodecDialect = 'sqlite'
113
+ ): Record<string, unknown> {
114
+ if (!hasCodecs(tableCodecs)) return { ...row };
115
+
116
+ const transformed: Record<string, unknown> = { ...row };
117
+ for (const [column, codec] of Object.entries(tableCodecs)) {
118
+ if (!(column in transformed)) continue;
119
+ transformed[column] = applyCodecToDbValue(
120
+ codec,
121
+ transformed[column],
122
+ dialect
123
+ );
124
+ }
125
+ return transformed;
126
+ }
127
+
128
+ export function applyCodecsFromDbRow(
129
+ row: Record<string, unknown>,
130
+ tableCodecs: TableColumnCodecs,
131
+ dialect: ColumnCodecDialect = 'sqlite'
132
+ ): Record<string, unknown> {
133
+ if (!hasCodecs(tableCodecs)) return { ...row };
134
+
135
+ const transformed: Record<string, unknown> = { ...row };
136
+ for (const [column, codec] of Object.entries(tableCodecs)) {
137
+ if (!(column in transformed)) continue;
138
+ transformed[column] = applyCodecFromDbValue(
139
+ codec,
140
+ transformed[column],
141
+ dialect
142
+ );
143
+ }
144
+ return transformed;
145
+ }
146
+
147
+ function parseBooleanValue(value: unknown): boolean {
148
+ if (typeof value === 'boolean') return value;
149
+ if (typeof value === 'number') return value === 1;
150
+ if (typeof value === 'string') {
151
+ const normalized = value.trim().toLowerCase();
152
+ return normalized === '1' || normalized === 'true';
153
+ }
154
+ return Boolean(value);
155
+ }
156
+
157
+ export function numberBoolean(): ColumnCodec<
158
+ boolean,
159
+ number | boolean | string
160
+ > {
161
+ return {
162
+ ts: 'boolean',
163
+ toDb: (value) => (value ? 1 : 0),
164
+ fromDb: (value) => parseBooleanValue(value),
165
+ dialects: {
166
+ postgres: {
167
+ toDb: (value) => value,
168
+ fromDb: (value) => parseBooleanValue(value),
169
+ },
170
+ },
171
+ };
172
+ }
173
+
174
+ export interface StringJsonCodecOptions<T> {
175
+ ts?: ColumnCodecType;
176
+ import?: ColumnCodecTypeImport;
177
+ stringify?: (value: T) => string;
178
+ parse?: (value: string) => T;
179
+ }
180
+
181
+ export function stringJson<T = unknown>(
182
+ options: StringJsonCodecOptions<T> = {}
183
+ ): ColumnCodec<T, string | T> {
184
+ const stringify = options.stringify ?? ((value: T) => JSON.stringify(value));
185
+ const parse = options.parse ?? ((value: string) => JSON.parse(value) as T);
186
+
187
+ const ts: ColumnCodecType =
188
+ options.ts ??
189
+ (options.import
190
+ ? { type: options.import.name, import: options.import }
191
+ : 'unknown');
192
+
193
+ return {
194
+ ts,
195
+ toDb: (value) => stringify(value),
196
+ fromDb: (value) => {
197
+ if (typeof value === 'string') {
198
+ return parse(value);
199
+ }
200
+ return value as T;
201
+ },
202
+ };
203
+ }
204
+
205
+ export function timestampDate(): ColumnCodec<Date, string | Date> {
206
+ return {
207
+ ts: 'Date',
208
+ toDb: (value) => value.toISOString(),
209
+ fromDb: (value) =>
210
+ value instanceof Date ? value : new Date(String(value)),
211
+ };
212
+ }
213
+
214
+ export function dateString(): ColumnCodec<string, string | Date> {
215
+ return {
216
+ ts: 'string',
217
+ toDb: (value) => value,
218
+ fromDb: (value) =>
219
+ value instanceof Date ? value.toISOString().slice(0, 10) : String(value),
220
+ };
221
+ }
222
+
223
+ export const codecs = {
224
+ numberBoolean,
225
+ stringJson,
226
+ timestampDate,
227
+ dateString,
228
+ };
package/src/index.ts CHANGED
@@ -11,54 +11,28 @@
11
11
  */
12
12
 
13
13
  // Blob transport/storage types and utilities (protocol types come from ./schemas)
14
- export type {
15
- BlobSignDownloadOptions,
16
- BlobSignedUpload,
17
- BlobSignUploadOptions,
18
- BlobStorageAdapter,
19
- BlobTransport,
20
- } from './blobs';
21
- export {
22
- createBlobHash,
23
- createBlobRef,
24
- parseBlobHash,
25
- } from './blobs';
14
+ export * from './blobs';
15
+ // Column-level codecs shared by typegen and runtime paths
16
+ export * from './column-codecs';
26
17
  // Conflict detection utilities
18
+ export * from './conflict';
27
19
  // Kysely plugin utilities
28
- export { SerializePlugin } from './kysely-serialize';
20
+ export * from './kysely-serialize';
29
21
  // Logging utilities
30
- export {
31
- createSyncTimer,
32
- logSyncEvent,
33
- } from './logger';
22
+ export * from './logger';
34
23
  // Proxy protocol types
35
- export type {
36
- ProxyHandshake,
37
- ProxyHandshakeAck,
38
- ProxyMessage,
39
- ProxyResponse,
40
- } from './proxy';
24
+ export * from './proxy';
41
25
  // Schemas (Zod)
42
26
  export * from './schemas';
43
27
  // Scope types, patterns, and utilities
44
- export type {
45
- ScopeDefinition,
46
- ScopePattern,
47
- ScopeValues,
48
- StoredScopes,
49
- } from './scopes';
50
- export { extractScopeVars, normalizeScopes } from './scopes';
28
+ export * from './scopes';
29
+ // Snapshot chunk encoding helpers
30
+ export * from './snapshot-chunks';
31
+ // Telemetry abstraction
32
+ export * from './telemetry';
51
33
  // Data transformation hooks
52
34
  export * from './transforms';
53
-
54
35
  // Transport and conflict types (protocol types come from ./schemas)
55
- export type {
56
- ConflictCheckResult,
57
- MergeResult,
58
- MergeResultConflict,
59
- MergeResultOk,
60
- SyncTransport,
61
- SyncTransportBlobs,
62
- SyncTransportOptions,
63
- } from './types';
64
- export { SyncTransportError } from './types';
36
+ export * from './types';
37
+ // Shared runtime utilities
38
+ export * from './utils';
@@ -196,7 +196,7 @@ class BaseSerializePlugin implements KyselyPlugin {
196
196
  }
197
197
  }
198
198
 
199
- export interface SerializePluginOptions {
199
+ interface SerializePluginOptions {
200
200
  serializer?: Serializer;
201
201
  deserializer?: Deserializer;
202
202
  skipNodeKind?: Array<RootOperationNode['kind']>;
package/src/logger.ts CHANGED
@@ -1,69 +1,27 @@
1
1
  /**
2
2
  * @syncular/core - Structured logging utilities for sync operations
3
3
  *
4
- * Outputs JSON lines for easy parsing by log aggregation tools.
5
- * Each log event includes a timestamp and event type.
4
+ * Uses the active telemetry backend configured via `configureSyncTelemetry()`.
6
5
  */
7
6
 
8
- /**
9
- * Sync log event structure
10
- */
11
- interface SyncLogEvent {
12
- /** Event type identifier */
13
- event: string;
14
- /** User ID (optional) */
15
- userId?: string;
16
- /** Operation duration in milliseconds (optional) */
17
- durationMs?: number;
18
- /** Number of rows affected (optional) */
19
- rowCount?: number;
20
- /** Whether a full reset was required (optional) */
21
- resetRequired?: boolean;
22
- /** Error message if operation failed (optional) */
23
- error?: string;
24
- /** Additional arbitrary properties */
25
- [key: string]: unknown;
26
- }
7
+ import { getSyncTelemetry, type SyncTelemetryEvent } from './telemetry';
27
8
 
28
9
  /**
29
- * Logger function type - allows custom logging implementations
10
+ * Sync log event structure.
30
11
  */
31
- type SyncLogger = (event: SyncLogEvent) => void;
12
+ export type SyncLogEvent = SyncTelemetryEvent;
32
13
 
33
14
  /**
34
- * Default logger that outputs JSON lines to console.
35
- * Non-blocking - defers logging to avoid blocking the event loop.
36
- *
37
- * On server (Node.js), uses setImmediate.
38
- * On client (browser), uses setTimeout(0).
15
+ * Logger function type.
39
16
  */
40
- function createDefaultLogger(): SyncLogger {
41
- // Detect environment
42
- const isNode =
43
- typeof globalThis !== 'undefined' &&
44
- typeof globalThis.setImmediate === 'function';
45
-
46
- const defer = isNode
47
- ? (fn: () => void) => globalThis.setImmediate(fn)
48
- : (fn: () => void) => setTimeout(fn, 0);
49
-
50
- return (event: SyncLogEvent) => {
51
- defer(() => {
52
- console.log(
53
- JSON.stringify({
54
- timestamp: new Date().toISOString(),
55
- ...event,
56
- })
57
- );
58
- });
59
- };
60
- }
17
+ export type SyncLogger = (event: SyncLogEvent) => void;
61
18
 
62
19
  /**
63
- * Log a sync event using the default logger.
64
- * For custom logging, create your own logger with createDefaultLogger pattern.
20
+ * Log a sync event using the currently configured telemetry backend.
65
21
  */
66
- export const logSyncEvent: SyncLogger = createDefaultLogger();
22
+ export const logSyncEvent: SyncLogger = (event) => {
23
+ getSyncTelemetry().log(event);
24
+ };
67
25
 
68
26
  /**
69
27
  * Create a timer for measuring operation duration.
@@ -78,19 +36,3 @@ export function createSyncTimer(): () => number {
78
36
  const start = performance.now();
79
37
  return () => Math.round(performance.now() - start);
80
38
  }
81
-
82
- /**
83
- * Create a scoped logger that automatically adds context to all events.
84
- *
85
- * @example
86
- * const log = createScopedLogger({ userId: 'user123', shape: 'teams' });
87
- * log({ event: 'pull_start' }); // Includes userId and shape
88
- */
89
- export function createScopedLogger(
90
- context: Record<string, unknown>,
91
- baseLogger: SyncLogger = logSyncEvent
92
- ): SyncLogger {
93
- return (event: SyncLogEvent) => {
94
- baseLogger({ ...context, ...event });
95
- };
96
- }
@@ -34,16 +34,6 @@ export interface ProxyResponse {
34
34
  error?: string;
35
35
  }
36
36
 
37
- /**
38
- * Options for establishing a proxy connection.
39
- */
40
- export interface ProxyConnectOptions {
41
- /** Actor ID for oplog tracking */
42
- actorId: string;
43
- /** Client ID for oplog tracking */
44
- clientId: string;
45
- }
46
-
47
37
  /**
48
38
  * Handshake message sent when connection is established.
49
39
  */