@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.
- package/dist/blobs/index.d.ts +7 -0
- package/dist/blobs/index.d.ts.map +1 -0
- package/dist/blobs/index.js +7 -0
- package/dist/blobs/index.js.map +1 -0
- package/dist/blobs/manager.d.ts +345 -0
- package/dist/blobs/manager.d.ts.map +1 -0
- package/dist/blobs/manager.js +749 -0
- package/dist/blobs/manager.js.map +1 -0
- package/dist/blobs/migrate.d.ts +14 -0
- package/dist/blobs/migrate.d.ts.map +1 -0
- package/dist/blobs/migrate.js +59 -0
- package/dist/blobs/migrate.js.map +1 -0
- package/dist/blobs/types.d.ts +62 -0
- package/dist/blobs/types.d.ts.map +1 -0
- package/dist/blobs/types.js +5 -0
- package/dist/blobs/types.js.map +1 -0
- package/dist/client.d.ts +338 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +834 -0
- package/dist/client.js.map +1 -0
- package/dist/conflicts.d.ts +31 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/conflicts.js +118 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/create-client.d.ts +115 -0
- package/dist/create-client.d.ts.map +1 -0
- package/dist/create-client.js +162 -0
- package/dist/create-client.js.map +1 -0
- package/dist/engine/SyncEngine.d.ts +215 -0
- package/dist/engine/SyncEngine.d.ts.map +1 -0
- package/dist/engine/SyncEngine.js +1066 -0
- package/dist/engine/SyncEngine.js.map +1 -0
- package/dist/engine/index.d.ts +6 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +6 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/types.d.ts +230 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +7 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/handlers/create-handler.d.ts +110 -0
- package/dist/handlers/create-handler.d.ts.map +1 -0
- package/dist/handlers/create-handler.js +140 -0
- package/dist/handlers/create-handler.js.map +1 -0
- package/dist/handlers/registry.d.ts +15 -0
- package/dist/handlers/registry.d.ts.map +1 -0
- package/dist/handlers/registry.js +29 -0
- package/dist/handlers/registry.js.map +1 -0
- package/dist/handlers/types.d.ts +83 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/types.js +5 -0
- package/dist/handlers/types.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/migrate.d.ts +19 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +106 -0
- package/dist/migrate.js.map +1 -0
- package/dist/mutations.d.ts +138 -0
- package/dist/mutations.d.ts.map +1 -0
- package/dist/mutations.js +611 -0
- package/dist/mutations.js.map +1 -0
- package/dist/outbox.d.ts +112 -0
- package/dist/outbox.d.ts.map +1 -0
- package/dist/outbox.js +304 -0
- package/dist/outbox.js.map +1 -0
- package/dist/plugins/incrementing-version.d.ts +34 -0
- package/dist/plugins/incrementing-version.d.ts.map +1 -0
- package/dist/plugins/incrementing-version.js +83 -0
- package/dist/plugins/incrementing-version.js.map +1 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +3 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/types.d.ts +49 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +15 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/proxy/connection.d.ts +33 -0
- package/dist/proxy/connection.d.ts.map +1 -0
- package/dist/proxy/connection.js +153 -0
- package/dist/proxy/connection.js.map +1 -0
- package/dist/proxy/dialect.d.ts +46 -0
- package/dist/proxy/dialect.d.ts.map +1 -0
- package/dist/proxy/dialect.js +58 -0
- package/dist/proxy/dialect.js.map +1 -0
- package/dist/proxy/driver.d.ts +42 -0
- package/dist/proxy/driver.d.ts.map +1 -0
- package/dist/proxy/driver.js +78 -0
- package/dist/proxy/driver.js.map +1 -0
- package/dist/proxy/index.d.ts +10 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +10 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/mutations.d.ts +9 -0
- package/dist/proxy/mutations.d.ts.map +1 -0
- package/dist/proxy/mutations.js +11 -0
- package/dist/proxy/mutations.js.map +1 -0
- package/dist/pull-engine.d.ts +45 -0
- package/dist/pull-engine.d.ts.map +1 -0
- package/dist/pull-engine.js +391 -0
- package/dist/pull-engine.js.map +1 -0
- package/dist/push-engine.d.ts +18 -0
- package/dist/push-engine.d.ts.map +1 -0
- package/dist/push-engine.js +155 -0
- package/dist/push-engine.js.map +1 -0
- package/dist/query/FingerprintCollector.d.ts +18 -0
- package/dist/query/FingerprintCollector.d.ts.map +1 -0
- package/dist/query/FingerprintCollector.js +28 -0
- package/dist/query/FingerprintCollector.js.map +1 -0
- package/dist/query/QueryContext.d.ts +33 -0
- package/dist/query/QueryContext.d.ts.map +1 -0
- package/dist/query/QueryContext.js +16 -0
- package/dist/query/QueryContext.js.map +1 -0
- package/dist/query/fingerprint.d.ts +61 -0
- package/dist/query/fingerprint.d.ts.map +1 -0
- package/dist/query/fingerprint.js +91 -0
- package/dist/query/fingerprint.js.map +1 -0
- package/dist/query/index.d.ts +7 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +7 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/tracked-select.d.ts +18 -0
- package/dist/query/tracked-select.d.ts.map +1 -0
- package/dist/query/tracked-select.js +90 -0
- package/dist/query/tracked-select.js.map +1 -0
- package/dist/schema.d.ts +83 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +7 -0
- package/dist/schema.js.map +1 -0
- package/dist/sync-loop.d.ts +32 -0
- package/dist/sync-loop.d.ts.map +1 -0
- package/dist/sync-loop.js +249 -0
- package/dist/sync-loop.js.map +1 -0
- package/dist/utils/id.d.ts +8 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +19 -0
- package/dist/utils/id.js.map +1 -0
- package/package.json +58 -0
- package/src/blobs/index.ts +7 -0
- package/src/blobs/manager.ts +1027 -0
- package/src/blobs/migrate.ts +67 -0
- package/src/blobs/types.ts +84 -0
- package/src/client.ts +1222 -0
- package/src/conflicts.ts +180 -0
- package/src/create-client.ts +297 -0
- package/src/engine/SyncEngine.ts +1337 -0
- package/src/engine/index.ts +6 -0
- package/src/engine/types.ts +268 -0
- package/src/handlers/create-handler.ts +287 -0
- package/src/handlers/registry.ts +36 -0
- package/src/handlers/types.ts +102 -0
- package/src/index.ts +25 -0
- package/src/migrate.ts +122 -0
- package/src/mutations.ts +926 -0
- package/src/outbox.ts +397 -0
- package/src/plugins/incrementing-version.ts +133 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/types.ts +63 -0
- package/src/proxy/connection.ts +191 -0
- package/src/proxy/dialect.ts +76 -0
- package/src/proxy/driver.ts +126 -0
- package/src/proxy/index.ts +10 -0
- package/src/proxy/mutations.ts +18 -0
- package/src/pull-engine.ts +518 -0
- package/src/push-engine.ts +201 -0
- package/src/query/FingerprintCollector.ts +29 -0
- package/src/query/QueryContext.ts +54 -0
- package/src/query/fingerprint.ts +109 -0
- package/src/query/index.ts +10 -0
- package/src/query/tracked-select.ts +139 -0
- package/src/schema.ts +94 -0
- package/src/sync-loop.ts +368 -0
- 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,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
|
+
}
|