@syncular/client 0.0.1-60

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 (176) hide show
  1. package/dist/blobs/index.d.ts +7 -0
  2. package/dist/blobs/index.d.ts.map +1 -0
  3. package/dist/blobs/index.js +7 -0
  4. package/dist/blobs/index.js.map +1 -0
  5. package/dist/blobs/manager.d.ts +345 -0
  6. package/dist/blobs/manager.d.ts.map +1 -0
  7. package/dist/blobs/manager.js +749 -0
  8. package/dist/blobs/manager.js.map +1 -0
  9. package/dist/blobs/migrate.d.ts +14 -0
  10. package/dist/blobs/migrate.d.ts.map +1 -0
  11. package/dist/blobs/migrate.js +59 -0
  12. package/dist/blobs/migrate.js.map +1 -0
  13. package/dist/blobs/types.d.ts +62 -0
  14. package/dist/blobs/types.d.ts.map +1 -0
  15. package/dist/blobs/types.js +5 -0
  16. package/dist/blobs/types.js.map +1 -0
  17. package/dist/client.d.ts +338 -0
  18. package/dist/client.d.ts.map +1 -0
  19. package/dist/client.js +834 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/conflicts.d.ts +31 -0
  22. package/dist/conflicts.d.ts.map +1 -0
  23. package/dist/conflicts.js +118 -0
  24. package/dist/conflicts.js.map +1 -0
  25. package/dist/create-client.d.ts +115 -0
  26. package/dist/create-client.d.ts.map +1 -0
  27. package/dist/create-client.js +162 -0
  28. package/dist/create-client.js.map +1 -0
  29. package/dist/engine/SyncEngine.d.ts +215 -0
  30. package/dist/engine/SyncEngine.d.ts.map +1 -0
  31. package/dist/engine/SyncEngine.js +1066 -0
  32. package/dist/engine/SyncEngine.js.map +1 -0
  33. package/dist/engine/index.d.ts +6 -0
  34. package/dist/engine/index.d.ts.map +1 -0
  35. package/dist/engine/index.js +6 -0
  36. package/dist/engine/index.js.map +1 -0
  37. package/dist/engine/types.d.ts +230 -0
  38. package/dist/engine/types.d.ts.map +1 -0
  39. package/dist/engine/types.js +7 -0
  40. package/dist/engine/types.js.map +1 -0
  41. package/dist/handlers/create-handler.d.ts +110 -0
  42. package/dist/handlers/create-handler.d.ts.map +1 -0
  43. package/dist/handlers/create-handler.js +140 -0
  44. package/dist/handlers/create-handler.js.map +1 -0
  45. package/dist/handlers/registry.d.ts +15 -0
  46. package/dist/handlers/registry.d.ts.map +1 -0
  47. package/dist/handlers/registry.js +29 -0
  48. package/dist/handlers/registry.js.map +1 -0
  49. package/dist/handlers/types.d.ts +83 -0
  50. package/dist/handlers/types.d.ts.map +1 -0
  51. package/dist/handlers/types.js +5 -0
  52. package/dist/handlers/types.js.map +1 -0
  53. package/dist/index.d.ts +24 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +24 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/migrate.d.ts +19 -0
  58. package/dist/migrate.d.ts.map +1 -0
  59. package/dist/migrate.js +106 -0
  60. package/dist/migrate.js.map +1 -0
  61. package/dist/mutations.d.ts +138 -0
  62. package/dist/mutations.d.ts.map +1 -0
  63. package/dist/mutations.js +611 -0
  64. package/dist/mutations.js.map +1 -0
  65. package/dist/outbox.d.ts +112 -0
  66. package/dist/outbox.d.ts.map +1 -0
  67. package/dist/outbox.js +304 -0
  68. package/dist/outbox.js.map +1 -0
  69. package/dist/plugins/incrementing-version.d.ts +34 -0
  70. package/dist/plugins/incrementing-version.d.ts.map +1 -0
  71. package/dist/plugins/incrementing-version.js +83 -0
  72. package/dist/plugins/incrementing-version.js.map +1 -0
  73. package/dist/plugins/index.d.ts +3 -0
  74. package/dist/plugins/index.d.ts.map +1 -0
  75. package/dist/plugins/index.js +3 -0
  76. package/dist/plugins/index.js.map +1 -0
  77. package/dist/plugins/types.d.ts +49 -0
  78. package/dist/plugins/types.d.ts.map +1 -0
  79. package/dist/plugins/types.js +15 -0
  80. package/dist/plugins/types.js.map +1 -0
  81. package/dist/proxy/connection.d.ts +33 -0
  82. package/dist/proxy/connection.d.ts.map +1 -0
  83. package/dist/proxy/connection.js +153 -0
  84. package/dist/proxy/connection.js.map +1 -0
  85. package/dist/proxy/dialect.d.ts +46 -0
  86. package/dist/proxy/dialect.d.ts.map +1 -0
  87. package/dist/proxy/dialect.js +58 -0
  88. package/dist/proxy/dialect.js.map +1 -0
  89. package/dist/proxy/driver.d.ts +42 -0
  90. package/dist/proxy/driver.d.ts.map +1 -0
  91. package/dist/proxy/driver.js +78 -0
  92. package/dist/proxy/driver.js.map +1 -0
  93. package/dist/proxy/index.d.ts +10 -0
  94. package/dist/proxy/index.d.ts.map +1 -0
  95. package/dist/proxy/index.js +10 -0
  96. package/dist/proxy/index.js.map +1 -0
  97. package/dist/proxy/mutations.d.ts +9 -0
  98. package/dist/proxy/mutations.d.ts.map +1 -0
  99. package/dist/proxy/mutations.js +11 -0
  100. package/dist/proxy/mutations.js.map +1 -0
  101. package/dist/pull-engine.d.ts +45 -0
  102. package/dist/pull-engine.d.ts.map +1 -0
  103. package/dist/pull-engine.js +391 -0
  104. package/dist/pull-engine.js.map +1 -0
  105. package/dist/push-engine.d.ts +18 -0
  106. package/dist/push-engine.d.ts.map +1 -0
  107. package/dist/push-engine.js +155 -0
  108. package/dist/push-engine.js.map +1 -0
  109. package/dist/query/FingerprintCollector.d.ts +18 -0
  110. package/dist/query/FingerprintCollector.d.ts.map +1 -0
  111. package/dist/query/FingerprintCollector.js +28 -0
  112. package/dist/query/FingerprintCollector.js.map +1 -0
  113. package/dist/query/QueryContext.d.ts +33 -0
  114. package/dist/query/QueryContext.d.ts.map +1 -0
  115. package/dist/query/QueryContext.js +16 -0
  116. package/dist/query/QueryContext.js.map +1 -0
  117. package/dist/query/fingerprint.d.ts +61 -0
  118. package/dist/query/fingerprint.d.ts.map +1 -0
  119. package/dist/query/fingerprint.js +91 -0
  120. package/dist/query/fingerprint.js.map +1 -0
  121. package/dist/query/index.d.ts +7 -0
  122. package/dist/query/index.d.ts.map +1 -0
  123. package/dist/query/index.js +7 -0
  124. package/dist/query/index.js.map +1 -0
  125. package/dist/query/tracked-select.d.ts +18 -0
  126. package/dist/query/tracked-select.d.ts.map +1 -0
  127. package/dist/query/tracked-select.js +90 -0
  128. package/dist/query/tracked-select.js.map +1 -0
  129. package/dist/schema.d.ts +83 -0
  130. package/dist/schema.d.ts.map +1 -0
  131. package/dist/schema.js +7 -0
  132. package/dist/schema.js.map +1 -0
  133. package/dist/sync-loop.d.ts +32 -0
  134. package/dist/sync-loop.d.ts.map +1 -0
  135. package/dist/sync-loop.js +249 -0
  136. package/dist/sync-loop.js.map +1 -0
  137. package/dist/utils/id.d.ts +8 -0
  138. package/dist/utils/id.d.ts.map +1 -0
  139. package/dist/utils/id.js +19 -0
  140. package/dist/utils/id.js.map +1 -0
  141. package/package.json +58 -0
  142. package/src/blobs/index.ts +7 -0
  143. package/src/blobs/manager.ts +1027 -0
  144. package/src/blobs/migrate.ts +67 -0
  145. package/src/blobs/types.ts +84 -0
  146. package/src/client.ts +1222 -0
  147. package/src/conflicts.ts +180 -0
  148. package/src/create-client.ts +297 -0
  149. package/src/engine/SyncEngine.ts +1337 -0
  150. package/src/engine/index.ts +6 -0
  151. package/src/engine/types.ts +268 -0
  152. package/src/handlers/create-handler.ts +287 -0
  153. package/src/handlers/registry.ts +36 -0
  154. package/src/handlers/types.ts +102 -0
  155. package/src/index.ts +25 -0
  156. package/src/migrate.ts +122 -0
  157. package/src/mutations.ts +926 -0
  158. package/src/outbox.ts +397 -0
  159. package/src/plugins/incrementing-version.ts +133 -0
  160. package/src/plugins/index.ts +2 -0
  161. package/src/plugins/types.ts +63 -0
  162. package/src/proxy/connection.ts +191 -0
  163. package/src/proxy/dialect.ts +76 -0
  164. package/src/proxy/driver.ts +126 -0
  165. package/src/proxy/index.ts +10 -0
  166. package/src/proxy/mutations.ts +18 -0
  167. package/src/pull-engine.ts +518 -0
  168. package/src/push-engine.ts +201 -0
  169. package/src/query/FingerprintCollector.ts +29 -0
  170. package/src/query/QueryContext.ts +54 -0
  171. package/src/query/fingerprint.ts +109 -0
  172. package/src/query/index.ts +10 -0
  173. package/src/query/tracked-select.ts +139 -0
  174. package/src/schema.ts +94 -0
  175. package/src/sync-loop.ts +368 -0
  176. package/src/utils/id.ts +20 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * @syncular/client - Proxy Connection
3
+ *
4
+ * DatabaseConnection implementation that proxies queries over WebSocket.
5
+ */
6
+
7
+ import type {
8
+ ProxyHandshake,
9
+ ProxyHandshakeAck,
10
+ ProxyMessage,
11
+ ProxyResponse,
12
+ } from '@syncular/core';
13
+ import type { CompiledQuery, DatabaseConnection, QueryResult } from 'kysely';
14
+
15
+ /**
16
+ * WebSocket wrapper that handles reconnection and message correlation.
17
+ */
18
+ export class ProxyConnection implements DatabaseConnection {
19
+ private ws: WebSocket;
20
+ private messageId = 0;
21
+ private pendingRequests = new Map<
22
+ string,
23
+ {
24
+ resolve: (response: ProxyResponse) => void;
25
+ reject: (error: Error) => void;
26
+ }
27
+ >();
28
+ private handshakeComplete = false;
29
+ private handshakePromise: Promise<void>;
30
+ private handshakeResolve: (() => void) | null = null;
31
+ private handshakeReject: ((error: Error) => void) | null = null;
32
+ private closed = false;
33
+
34
+ constructor(
35
+ ws: WebSocket,
36
+ private actorId: string,
37
+ private clientId: string
38
+ ) {
39
+ this.ws = ws;
40
+
41
+ this.handshakePromise = new Promise((resolve, reject) => {
42
+ this.handshakeResolve = resolve;
43
+ this.handshakeReject = reject;
44
+ });
45
+
46
+ this.ws.onmessage = (event) => {
47
+ this.handleMessage(event.data as string);
48
+ };
49
+
50
+ this.ws.onerror = (_event) => {
51
+ const error = new Error('WebSocket error');
52
+ this.rejectAllPending(error);
53
+ if (!this.handshakeComplete) {
54
+ this.handshakeReject?.(error);
55
+ }
56
+ };
57
+
58
+ this.ws.onclose = () => {
59
+ this.closed = true;
60
+ const error = new Error('WebSocket closed');
61
+ this.rejectAllPending(error);
62
+ if (!this.handshakeComplete) {
63
+ this.handshakeReject?.(error);
64
+ }
65
+ };
66
+
67
+ // Send handshake
68
+ const handshake: ProxyHandshake = {
69
+ type: 'handshake',
70
+ actorId: this.actorId,
71
+ clientId: this.clientId,
72
+ };
73
+ this.ws.send(JSON.stringify(handshake));
74
+ }
75
+
76
+ private handleMessage(data: string): void {
77
+ try {
78
+ const message = JSON.parse(data);
79
+
80
+ // Handle handshake acknowledgement
81
+ if (message.type === 'handshake_ack') {
82
+ const ack = message as ProxyHandshakeAck;
83
+ if (ack.ok) {
84
+ this.handshakeComplete = true;
85
+ this.handshakeResolve?.();
86
+ } else {
87
+ this.handshakeReject?.(new Error(ack.error ?? 'Handshake failed'));
88
+ }
89
+ return;
90
+ }
91
+
92
+ // Handle query responses
93
+ const response = message as ProxyResponse;
94
+ const pending = this.pendingRequests.get(response.id);
95
+ if (pending) {
96
+ this.pendingRequests.delete(response.id);
97
+ pending.resolve(response);
98
+ }
99
+ } catch {
100
+ // Ignore malformed messages
101
+ }
102
+ }
103
+
104
+ private rejectAllPending(error: Error): void {
105
+ for (const pending of this.pendingRequests.values()) {
106
+ pending.reject(error);
107
+ }
108
+ this.pendingRequests.clear();
109
+ }
110
+
111
+ private async send(message: ProxyMessage): Promise<ProxyResponse> {
112
+ // Wait for handshake to complete
113
+ await this.handshakePromise;
114
+
115
+ if (this.closed) {
116
+ throw new Error('Connection is closed');
117
+ }
118
+
119
+ return new Promise((resolve, reject) => {
120
+ this.pendingRequests.set(message.id, { resolve, reject });
121
+ this.ws.send(JSON.stringify(message));
122
+ });
123
+ }
124
+
125
+ private nextMessageId(): string {
126
+ return `${++this.messageId}`;
127
+ }
128
+
129
+ async executeQuery<R>(query: CompiledQuery): Promise<QueryResult<R>> {
130
+ const response = await this.send({
131
+ id: this.nextMessageId(),
132
+ type: 'query',
133
+ sql: query.sql,
134
+ parameters: query.parameters,
135
+ });
136
+
137
+ if (response.type === 'error') {
138
+ throw new Error(response.error ?? 'Query failed');
139
+ }
140
+
141
+ return {
142
+ rows: (response.rows ?? []) as R[],
143
+ numAffectedRows:
144
+ response.rowCount != null ? BigInt(response.rowCount) : undefined,
145
+ };
146
+ }
147
+
148
+ async beginTransaction(): Promise<void> {
149
+ const response = await this.send({
150
+ id: this.nextMessageId(),
151
+ type: 'begin',
152
+ });
153
+
154
+ if (response.type === 'error') {
155
+ throw new Error(response.error ?? 'Failed to begin transaction');
156
+ }
157
+ }
158
+
159
+ async commitTransaction(): Promise<void> {
160
+ const response = await this.send({
161
+ id: this.nextMessageId(),
162
+ type: 'commit',
163
+ });
164
+
165
+ if (response.type === 'error') {
166
+ throw new Error(response.error ?? 'Failed to commit transaction');
167
+ }
168
+ }
169
+
170
+ async rollbackTransaction(): Promise<void> {
171
+ const response = await this.send({
172
+ id: this.nextMessageId(),
173
+ type: 'rollback',
174
+ });
175
+
176
+ if (response.type === 'error') {
177
+ throw new Error(response.error ?? 'Failed to rollback transaction');
178
+ }
179
+ }
180
+
181
+ streamQuery<R>(): AsyncIterableIterator<QueryResult<R>> {
182
+ throw new Error('Streaming queries are not supported over proxy');
183
+ }
184
+
185
+ close(): void {
186
+ if (!this.closed) {
187
+ this.closed = true;
188
+ this.ws.close();
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @syncular/client - Proxy Dialect
3
+ *
4
+ * Kysely Dialect that proxies queries over WebSocket to a sync server.
5
+ */
6
+
7
+ import type {
8
+ DatabaseIntrospector,
9
+ Dialect,
10
+ DialectAdapter,
11
+ Driver,
12
+ Kysely,
13
+ QueryCompiler,
14
+ } from 'kysely';
15
+ import {
16
+ PostgresAdapter,
17
+ PostgresIntrospector,
18
+ PostgresQueryCompiler,
19
+ } from 'kysely';
20
+ import { ProxyDriver, type ProxyDriverConfigWithFactory } from './driver';
21
+
22
+ interface ProxyDialectConfig extends ProxyDriverConfigWithFactory {}
23
+
24
+ /**
25
+ * Creates a Kysely dialect that proxies queries over WebSocket.
26
+ *
27
+ * This dialect uses PostgreSQL query compilation and adapters since
28
+ * the proxy server typically runs against a PostgreSQL database.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * import { Kysely } from 'kysely';
33
+ * import { createProxyDialect } from '@syncular/client/proxy';
34
+ *
35
+ * const db = new Kysely<MySchema>({
36
+ * dialect: createProxyDialect({
37
+ * endpoint: 'wss://api.example.com/proxy',
38
+ * actorId: 'admin:worker',
39
+ * clientId: 'cf-worker-123',
40
+ * headers: { authorization: 'Bearer ...' },
41
+ * }),
42
+ * });
43
+ *
44
+ * // Full Kysely API works
45
+ * const tasks = await db.selectFrom('tasks').selectAll().execute();
46
+ *
47
+ * // Transactions work
48
+ * await db.transaction().execute(async (trx) => {
49
+ * const user = await trx.selectFrom('users')
50
+ * .where('id', '=', 'u1')
51
+ * .executeTakeFirst();
52
+ * await trx.insertInto('tasks')
53
+ * .values({ id: 'x', user_id: user.id, title: 'New' })
54
+ * .execute();
55
+ * });
56
+ * ```
57
+ */
58
+ export function createProxyDialect(config: ProxyDialectConfig): Dialect {
59
+ return {
60
+ createDriver(): Driver {
61
+ return new ProxyDriver(config);
62
+ },
63
+
64
+ createQueryCompiler(): QueryCompiler {
65
+ return new PostgresQueryCompiler();
66
+ },
67
+
68
+ createAdapter(): DialectAdapter {
69
+ return new PostgresAdapter();
70
+ },
71
+
72
+ createIntrospector(db: Kysely<any>): DatabaseIntrospector {
73
+ return new PostgresIntrospector(db);
74
+ },
75
+ };
76
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @syncular/client - Proxy Driver
3
+ *
4
+ * Kysely Driver that creates WebSocket connections for query execution.
5
+ */
6
+
7
+ import type { DatabaseConnection, Driver } from 'kysely';
8
+ import { ProxyConnection } from './connection';
9
+
10
+ interface ProxyDriverConfig {
11
+ /** WebSocket endpoint URL (wss://...) */
12
+ endpoint: string;
13
+ /** Actor ID for oplog tracking */
14
+ actorId: string;
15
+ /** Client ID for oplog tracking */
16
+ clientId: string;
17
+ /** Optional headers for authentication */
18
+ headers?: Record<string, string>;
19
+ }
20
+
21
+ /**
22
+ * Custom WebSocket factory type for environments that need it.
23
+ */
24
+ export type WebSocketFactory = (
25
+ url: string,
26
+ protocols?: string | string[]
27
+ ) => WebSocket;
28
+
29
+ /**
30
+ * Extended config with optional WebSocket factory.
31
+ */
32
+ export interface ProxyDriverConfigWithFactory extends ProxyDriverConfig {
33
+ /** Optional WebSocket factory for custom environments */
34
+ createWebSocket?: WebSocketFactory;
35
+ }
36
+
37
+ export class ProxyDriver implements Driver {
38
+ private connection: ProxyConnection | null = null;
39
+
40
+ constructor(private config: ProxyDriverConfigWithFactory) {}
41
+
42
+ async init(): Promise<void> {
43
+ // No initialization needed
44
+ }
45
+
46
+ async acquireConnection(): Promise<DatabaseConnection> {
47
+ // Reuse existing connection if available
48
+ if (this.connection) {
49
+ return this.connection as DatabaseConnection;
50
+ }
51
+
52
+ const ws = await this.createWebSocket();
53
+ this.connection = new ProxyConnection(
54
+ ws,
55
+ this.config.actorId,
56
+ this.config.clientId
57
+ );
58
+ return this.connection as DatabaseConnection;
59
+ }
60
+
61
+ private async createWebSocket(): Promise<WebSocket> {
62
+ return new Promise((resolve, reject) => {
63
+ // Build URL with auth info if needed
64
+ const url = new URL(this.config.endpoint);
65
+
66
+ // Note: WebSocket doesn't support custom headers directly,
67
+ // so auth is typically done via query params or after connection
68
+ // The server should handle auth during the handshake
69
+ if (this.config.headers?.authorization) {
70
+ url.searchParams.set(
71
+ 'authorization',
72
+ this.config.headers.authorization
73
+ );
74
+ }
75
+
76
+ let ws: WebSocket;
77
+ if (this.config.createWebSocket) {
78
+ ws = this.config.createWebSocket(url.toString());
79
+ } else {
80
+ ws = new WebSocket(url.toString());
81
+ }
82
+
83
+ ws.onopen = () => {
84
+ resolve(ws);
85
+ };
86
+
87
+ ws.onerror = (_event) => {
88
+ reject(new Error('Failed to connect to proxy endpoint'));
89
+ };
90
+
91
+ // Set a connection timeout
92
+ const timeout = setTimeout(() => {
93
+ ws.close();
94
+ reject(new Error('Connection timeout'));
95
+ }, 30000);
96
+
97
+ ws.onopen = () => {
98
+ clearTimeout(timeout);
99
+ resolve(ws);
100
+ };
101
+ });
102
+ }
103
+
104
+ async beginTransaction(connection: DatabaseConnection): Promise<void> {
105
+ await (connection as ProxyConnection).beginTransaction();
106
+ }
107
+
108
+ async commitTransaction(connection: DatabaseConnection): Promise<void> {
109
+ await (connection as ProxyConnection).commitTransaction();
110
+ }
111
+
112
+ async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
113
+ await (connection as ProxyConnection).rollbackTransaction();
114
+ }
115
+
116
+ async releaseConnection(): Promise<void> {
117
+ // Keep the connection alive for reuse
118
+ }
119
+
120
+ async destroy(): Promise<void> {
121
+ if (this.connection) {
122
+ this.connection.close();
123
+ this.connection = null;
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @syncular/client - Proxy Dialect Exports
3
+ *
4
+ * Kysely dialect for proxying queries over WebSocket to a sync server.
5
+ */
6
+
7
+ export * from './connection';
8
+ export * from './dialect';
9
+ export * from './driver';
10
+ export * from './mutations';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @syncular/client - Proxy mutations
3
+ *
4
+ * Exposes the same Proxy-based mutation interface as the offline client,
5
+ * but pushes commits to the server immediately (no local outbox/db writes).
6
+ */
7
+
8
+ import {
9
+ createPushMutations,
10
+ type MutationsApi,
11
+ type PushCommitConfig,
12
+ } from '../mutations';
13
+
14
+ export function createProxyMutations<DB extends Record<string, any>>(
15
+ config: PushCommitConfig
16
+ ): MutationsApi<DB, undefined> {
17
+ return createPushMutations(config);
18
+ }