@syncular/server 0.0.6-55 → 0.0.6-66
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/handlers/collection.d.ts +12 -0
- package/dist/handlers/collection.d.ts.map +1 -0
- package/dist/handlers/collection.js +64 -0
- package/dist/handlers/collection.js.map +1 -0
- package/dist/handlers/create-handler.d.ts +9 -9
- package/dist/handlers/create-handler.d.ts.map +1 -1
- package/dist/handlers/create-handler.js.map +1 -1
- package/dist/handlers/index.d.ts +1 -1
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/index.js +1 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/types.d.ts +18 -12
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/proxy/collection.d.ts +9 -0
- package/dist/proxy/collection.d.ts.map +1 -0
- package/dist/proxy/collection.js +21 -0
- package/dist/proxy/collection.js.map +1 -0
- package/dist/proxy/handler.d.ts +3 -3
- package/dist/proxy/handler.d.ts.map +1 -1
- package/dist/proxy/handler.js +2 -1
- package/dist/proxy/handler.js.map +1 -1
- package/dist/proxy/index.d.ts +1 -1
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +3 -3
- package/dist/proxy/index.js.map +1 -1
- package/dist/pull.d.ts +5 -5
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +8 -8
- package/dist/pull.js.map +1 -1
- package/dist/push.d.ts +5 -5
- package/dist/push.d.ts.map +1 -1
- package/dist/push.js +5 -3
- package/dist/push.js.map +1 -1
- package/dist/subscriptions/resolve.d.ts +5 -4
- package/dist/subscriptions/resolve.d.ts.map +1 -1
- package/dist/subscriptions/resolve.js +4 -2
- package/dist/subscriptions/resolve.js.map +1 -1
- package/dist/sync.d.ts +21 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +23 -0
- package/dist/sync.js.map +1 -0
- package/package.json +2 -2
- package/src/handlers/collection.ts +121 -0
- package/src/handlers/create-handler.ts +23 -14
- package/src/handlers/index.ts +1 -1
- package/src/handlers/types.ts +29 -12
- package/src/index.ts +1 -0
- package/src/notify.test.ts +17 -17
- package/src/proxy/collection.ts +39 -0
- package/src/proxy/handler.test.ts +9 -7
- package/src/proxy/handler.ts +4 -4
- package/src/proxy/index.ts +8 -3
- package/src/pull.ts +35 -23
- package/src/push.ts +15 -8
- package/src/subscriptions/resolve.ts +11 -5
- package/src/sync.ts +101 -0
- package/dist/handlers/registry.d.ts +0 -20
- package/dist/handlers/registry.d.ts.map +0 -1
- package/dist/handlers/registry.js +0 -88
- package/dist/handlers/registry.js.map +0 -1
- package/dist/proxy/registry.d.ts +0 -35
- package/dist/proxy/registry.d.ts.map +0 -1
- package/dist/proxy/registry.js +0 -49
- package/dist/proxy/registry.js.map +0 -1
- package/src/handlers/registry.ts +0 -109
- package/src/proxy/registry.ts +0 -56
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
ServerContext,
|
|
40
40
|
ServerSnapshotContext,
|
|
41
41
|
ServerTableHandler,
|
|
42
|
+
SyncServerAuth,
|
|
42
43
|
} from './types';
|
|
43
44
|
|
|
44
45
|
/**
|
|
@@ -78,6 +79,7 @@ export interface CreateServerHandlerOptions<
|
|
|
78
79
|
ServerDB extends SyncCoreDb,
|
|
79
80
|
ClientDB,
|
|
80
81
|
TableName extends keyof ServerDB & keyof ClientDB & string,
|
|
82
|
+
Auth extends SyncServerAuth = SyncServerAuth,
|
|
81
83
|
ScopeDefs extends
|
|
82
84
|
readonly SimpleScopeDefinition[] = readonly SimpleScopeDefinition[],
|
|
83
85
|
> {
|
|
@@ -124,7 +126,7 @@ export interface CreateServerHandlerOptions<
|
|
|
124
126
|
* })
|
|
125
127
|
*/
|
|
126
128
|
resolveScopes: (
|
|
127
|
-
ctx: ServerContext<ServerDB>
|
|
129
|
+
ctx: ServerContext<ServerDB, Auth>
|
|
128
130
|
) => Promise<ScopeValuesFromPatterns<ScopeDefs>>;
|
|
129
131
|
|
|
130
132
|
/**
|
|
@@ -132,7 +134,7 @@ export interface CreateServerHandlerOptions<
|
|
|
132
134
|
* Use ctx.schemaVersion to handle older client versions.
|
|
133
135
|
*/
|
|
134
136
|
transformInbound?: (
|
|
135
|
-
row: ClientDB[TableName]
|
|
137
|
+
row: Selectable<ClientDB[TableName]>,
|
|
136
138
|
ctx: { schemaVersion?: number }
|
|
137
139
|
) => Updateable<ServerDB[TableName]>;
|
|
138
140
|
|
|
@@ -141,7 +143,7 @@ export interface CreateServerHandlerOptions<
|
|
|
141
143
|
*/
|
|
142
144
|
transformOutbound?: (
|
|
143
145
|
row: Selectable<ServerDB[TableName]>
|
|
144
|
-
) => ClientDB[TableName]
|
|
146
|
+
) => Selectable<ClientDB[TableName]>;
|
|
145
147
|
|
|
146
148
|
/**
|
|
147
149
|
* Optional column codec resolver.
|
|
@@ -162,19 +164,19 @@ export interface CreateServerHandlerOptions<
|
|
|
162
164
|
* Return true to allow, or an error object to reject.
|
|
163
165
|
*/
|
|
164
166
|
authorize?: (
|
|
165
|
-
ctx: ServerApplyOperationContext<ServerDB>,
|
|
167
|
+
ctx: ServerApplyOperationContext<ServerDB, Auth>,
|
|
166
168
|
op: SyncOperation
|
|
167
169
|
) => Promise<AuthorizeResult>;
|
|
168
170
|
|
|
169
171
|
/**
|
|
170
172
|
* Override: Build snapshot query.
|
|
171
173
|
*/
|
|
172
|
-
snapshot?: ServerTableHandler<ServerDB>['snapshot'];
|
|
174
|
+
snapshot?: ServerTableHandler<ServerDB, Auth>['snapshot'];
|
|
173
175
|
|
|
174
176
|
/**
|
|
175
177
|
* Override: Apply operation.
|
|
176
178
|
*/
|
|
177
|
-
applyOperation?: ServerTableHandler<ServerDB>['applyOperation'];
|
|
179
|
+
applyOperation?: ServerTableHandler<ServerDB, Auth>['applyOperation'];
|
|
178
180
|
|
|
179
181
|
/**
|
|
180
182
|
* Custom scope extraction from row (for complex scope logic).
|
|
@@ -213,11 +215,18 @@ export function createServerHandler<
|
|
|
213
215
|
ServerDB extends SyncCoreDb,
|
|
214
216
|
ClientDB,
|
|
215
217
|
TableName extends keyof ServerDB & keyof ClientDB & string,
|
|
218
|
+
Auth extends SyncServerAuth = SyncServerAuth,
|
|
216
219
|
ScopeDefs extends
|
|
217
220
|
readonly SimpleScopeDefinition[] = readonly SimpleScopeDefinition[],
|
|
218
221
|
>(
|
|
219
|
-
options: CreateServerHandlerOptions<
|
|
220
|
-
|
|
222
|
+
options: CreateServerHandlerOptions<
|
|
223
|
+
ServerDB,
|
|
224
|
+
ClientDB,
|
|
225
|
+
TableName,
|
|
226
|
+
Auth,
|
|
227
|
+
ScopeDefs
|
|
228
|
+
>
|
|
229
|
+
): ServerTableHandler<ServerDB, Auth> {
|
|
221
230
|
type OverloadParameters<T> = T extends (...args: infer A) => unknown
|
|
222
231
|
? A
|
|
223
232
|
: never;
|
|
@@ -298,7 +307,7 @@ export function createServerHandler<
|
|
|
298
307
|
const extractScopesImpl = customExtractScopes ?? defaultExtractScopes;
|
|
299
308
|
|
|
300
309
|
const resolveScopesImpl = async (
|
|
301
|
-
ctx: ServerContext<ServerDB>
|
|
310
|
+
ctx: ServerContext<ServerDB, Auth>
|
|
302
311
|
): Promise<ScopeValues> => {
|
|
303
312
|
const resolved = await resolveScopes(ctx);
|
|
304
313
|
const normalized: ScopeValues = {};
|
|
@@ -312,7 +321,7 @@ export function createServerHandler<
|
|
|
312
321
|
|
|
313
322
|
const applyOutboundTransform = (
|
|
314
323
|
row: Selectable<ServerDB[TableName]>
|
|
315
|
-
): ClientDB[TableName] => {
|
|
324
|
+
): Selectable<ClientDB[TableName]> => {
|
|
316
325
|
if (transformOutbound) {
|
|
317
326
|
return transformOutbound(row);
|
|
318
327
|
}
|
|
@@ -323,7 +332,7 @@ export function createServerHandler<
|
|
|
323
332
|
resolveTableCodecs(recordRow),
|
|
324
333
|
codecDialect
|
|
325
334
|
);
|
|
326
|
-
return transformed as ClientDB[TableName]
|
|
335
|
+
return transformed as Selectable<ClientDB[TableName]>;
|
|
327
336
|
};
|
|
328
337
|
|
|
329
338
|
const applyInboundTransform = (
|
|
@@ -331,7 +340,7 @@ export function createServerHandler<
|
|
|
331
340
|
schemaVersion: number | undefined
|
|
332
341
|
): Updateable<ServerDB[TableName]> => {
|
|
333
342
|
if (transformInbound) {
|
|
334
|
-
return transformInbound(row as ClientDB[TableName]
|
|
343
|
+
return transformInbound(row as Selectable<ClientDB[TableName]>, {
|
|
335
344
|
schemaVersion,
|
|
336
345
|
});
|
|
337
346
|
}
|
|
@@ -346,7 +355,7 @@ export function createServerHandler<
|
|
|
346
355
|
|
|
347
356
|
// Default snapshot implementation
|
|
348
357
|
const defaultSnapshot = async (
|
|
349
|
-
ctx: ServerSnapshotContext<ServerDB>,
|
|
358
|
+
ctx: ServerSnapshotContext<ServerDB, string, Auth>,
|
|
350
359
|
_params: Record<string, unknown> | undefined
|
|
351
360
|
): Promise<{ rows: unknown[]; nextCursor: string | null }> => {
|
|
352
361
|
const trx = ctx.db;
|
|
@@ -415,7 +424,7 @@ export function createServerHandler<
|
|
|
415
424
|
|
|
416
425
|
// Default applyOperation implementation
|
|
417
426
|
const defaultApplyOperation = async (
|
|
418
|
-
ctx: ServerApplyOperationContext<ServerDB>,
|
|
427
|
+
ctx: ServerApplyOperationContext<ServerDB, Auth>,
|
|
419
428
|
op: SyncOperation,
|
|
420
429
|
opIndex: number
|
|
421
430
|
): Promise<ApplyOperationResult> => {
|
package/src/handlers/index.ts
CHANGED
package/src/handlers/types.ts
CHANGED
|
@@ -11,6 +11,11 @@ import type { ZodSchema, z } from 'zod';
|
|
|
11
11
|
import type { DbExecutor } from '../dialect/types';
|
|
12
12
|
import type { SyncCoreDb } from '../schema';
|
|
13
13
|
|
|
14
|
+
export interface SyncServerAuth {
|
|
15
|
+
actorId: string;
|
|
16
|
+
partitionId?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
/**
|
|
15
20
|
* Emitted change to be stored in the oplog.
|
|
16
21
|
* Uses JSONB scopes instead of scope_keys array.
|
|
@@ -41,11 +46,16 @@ export interface ApplyOperationResult {
|
|
|
41
46
|
/**
|
|
42
47
|
* Context for server operations.
|
|
43
48
|
*/
|
|
44
|
-
export interface ServerContext<
|
|
49
|
+
export interface ServerContext<
|
|
50
|
+
DB extends SyncCoreDb = SyncCoreDb,
|
|
51
|
+
Auth extends SyncServerAuth = SyncServerAuth,
|
|
52
|
+
> {
|
|
45
53
|
/** Database connection (transaction in applyOperation) */
|
|
46
54
|
db: DbExecutor<DB>;
|
|
47
55
|
/** Actor ID (user ID from auth) */
|
|
48
56
|
actorId: string;
|
|
57
|
+
/** Full auth payload returned by authenticate */
|
|
58
|
+
auth: Auth;
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
/**
|
|
@@ -54,7 +64,8 @@ export interface ServerContext<DB extends SyncCoreDb = SyncCoreDb> {
|
|
|
54
64
|
export interface ServerSnapshotContext<
|
|
55
65
|
DB extends SyncCoreDb = SyncCoreDb,
|
|
56
66
|
ScopeKeys extends string = string,
|
|
57
|
-
|
|
67
|
+
Auth extends SyncServerAuth = SyncServerAuth,
|
|
68
|
+
> extends ServerContext<DB, Auth> {
|
|
58
69
|
/** Database executor for the snapshot */
|
|
59
70
|
db: DbExecutor<DB>;
|
|
60
71
|
/** Effective scope values for this subscription */
|
|
@@ -68,8 +79,10 @@ export interface ServerSnapshotContext<
|
|
|
68
79
|
/**
|
|
69
80
|
* Context passed to applyOperation method.
|
|
70
81
|
*/
|
|
71
|
-
export interface ServerApplyOperationContext<
|
|
72
|
-
extends
|
|
82
|
+
export interface ServerApplyOperationContext<
|
|
83
|
+
DB extends SyncCoreDb = SyncCoreDb,
|
|
84
|
+
Auth extends SyncServerAuth = SyncServerAuth,
|
|
85
|
+
> extends ServerContext<DB, Auth> {
|
|
73
86
|
/** Database executor for the operation */
|
|
74
87
|
trx: DbExecutor<DB>;
|
|
75
88
|
/** Client/device identifier */
|
|
@@ -127,6 +140,7 @@ interface ServerScopeConfig {
|
|
|
127
140
|
*/
|
|
128
141
|
export interface ServerHandlerOptions<
|
|
129
142
|
DB extends SyncCoreDb = SyncCoreDb,
|
|
143
|
+
Auth extends SyncServerAuth = SyncServerAuth,
|
|
130
144
|
Scopes extends Record<ScopePattern, Record<string, string>> = Record<
|
|
131
145
|
ScopePattern,
|
|
132
146
|
Record<string, string>
|
|
@@ -159,7 +173,7 @@ export interface ServerHandlerOptions<
|
|
|
159
173
|
* project_id: ctx.user.projectIds,
|
|
160
174
|
* })
|
|
161
175
|
*/
|
|
162
|
-
resolveScopes: (ctx: ServerContext<DB>) => Promise<ScopeValues>;
|
|
176
|
+
resolveScopes: (ctx: ServerContext<DB, Auth>) => Promise<ScopeValues>;
|
|
163
177
|
|
|
164
178
|
/**
|
|
165
179
|
* Optional Zod schema for subscription parameters.
|
|
@@ -191,7 +205,7 @@ export interface ServerHandlerOptions<
|
|
|
191
205
|
*/
|
|
192
206
|
transformInbound?: (
|
|
193
207
|
payload: Record<string, unknown>,
|
|
194
|
-
ctx: ServerApplyOperationContext<DB>
|
|
208
|
+
ctx: ServerApplyOperationContext<DB, Auth>
|
|
195
209
|
) => Partial<DB[TableName & keyof DB]>;
|
|
196
210
|
|
|
197
211
|
/**
|
|
@@ -206,7 +220,7 @@ export interface ServerHandlerOptions<
|
|
|
206
220
|
* Default uses keyset pagination ordered by primary key.
|
|
207
221
|
*/
|
|
208
222
|
snapshot?: (
|
|
209
|
-
ctx: ServerSnapshotContext<DB>,
|
|
223
|
+
ctx: ServerSnapshotContext<DB, string, Auth>,
|
|
210
224
|
params: Params extends ZodSchema ? z.infer<Params> : undefined
|
|
211
225
|
) => Promise<{ rows: unknown[]; nextCursor: string | null }>;
|
|
212
226
|
|
|
@@ -214,7 +228,7 @@ export interface ServerHandlerOptions<
|
|
|
214
228
|
* Custom apply operation implementation.
|
|
215
229
|
*/
|
|
216
230
|
applyOperation?: (
|
|
217
|
-
ctx: ServerApplyOperationContext<DB>,
|
|
231
|
+
ctx: ServerApplyOperationContext<DB, Auth>,
|
|
218
232
|
op: SyncOperation,
|
|
219
233
|
opIndex: number
|
|
220
234
|
) => Promise<ApplyOperationResult>;
|
|
@@ -224,7 +238,10 @@ export interface ServerHandlerOptions<
|
|
|
224
238
|
* Server-side table handler for snapshots and mutations.
|
|
225
239
|
* This is the internal handler interface used by the sync engine.
|
|
226
240
|
*/
|
|
227
|
-
export interface ServerTableHandler<
|
|
241
|
+
export interface ServerTableHandler<
|
|
242
|
+
DB extends SyncCoreDb = SyncCoreDb,
|
|
243
|
+
Auth extends SyncServerAuth = SyncServerAuth,
|
|
244
|
+
> {
|
|
228
245
|
/** Table name */
|
|
229
246
|
table: string;
|
|
230
247
|
|
|
@@ -244,7 +261,7 @@ export interface ServerTableHandler<DB extends SyncCoreDb = SyncCoreDb> {
|
|
|
244
261
|
/**
|
|
245
262
|
* Resolve allowed scope values for the current actor.
|
|
246
263
|
*/
|
|
247
|
-
resolveScopes: (ctx: ServerContext<DB>) => Promise<ScopeValues>;
|
|
264
|
+
resolveScopes: (ctx: ServerContext<DB, Auth>) => Promise<ScopeValues>;
|
|
248
265
|
|
|
249
266
|
/**
|
|
250
267
|
* Extract stored scopes from a row.
|
|
@@ -255,7 +272,7 @@ export interface ServerTableHandler<DB extends SyncCoreDb = SyncCoreDb> {
|
|
|
255
272
|
* Build a bootstrap snapshot page.
|
|
256
273
|
*/
|
|
257
274
|
snapshot(
|
|
258
|
-
ctx: ServerSnapshotContext<DB>,
|
|
275
|
+
ctx: ServerSnapshotContext<DB, string, Auth>,
|
|
259
276
|
params: Record<string, unknown> | undefined
|
|
260
277
|
): Promise<{ rows: unknown[]; nextCursor: string | null }>;
|
|
261
278
|
|
|
@@ -263,7 +280,7 @@ export interface ServerTableHandler<DB extends SyncCoreDb = SyncCoreDb> {
|
|
|
263
280
|
* Apply a single operation.
|
|
264
281
|
*/
|
|
265
282
|
applyOperation(
|
|
266
|
-
ctx: ServerApplyOperationContext<DB>,
|
|
283
|
+
ctx: ServerApplyOperationContext<DB, Auth>,
|
|
267
284
|
op: SyncOperation,
|
|
268
285
|
opIndex: number
|
|
269
286
|
): Promise<ApplyOperationResult>;
|
package/src/index.ts
CHANGED
package/src/notify.test.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
2
2
|
import { createBunSqliteDb } from '../../dialect-bun-sqlite/src';
|
|
3
3
|
import { createSqliteServerDialect } from '../../server-dialect-sqlite/src';
|
|
4
|
-
import { createServerHandler } from './handlers';
|
|
5
|
-
import { TableRegistry } from './handlers/registry';
|
|
4
|
+
import { createServerHandler, createServerHandlerCollection } from './handlers';
|
|
6
5
|
import { ensureSyncSchema } from './migrate';
|
|
7
6
|
import { EXTERNAL_CLIENT_ID, notifyExternalDataChange } from './notify';
|
|
8
7
|
import { pull } from './pull';
|
|
@@ -227,15 +226,14 @@ describe('pull re-bootstrap after external data change', () => {
|
|
|
227
226
|
resolveScopes: async () => ({ catalog_id: '*' }),
|
|
228
227
|
});
|
|
229
228
|
|
|
230
|
-
const handlers =
|
|
231
|
-
handlers.register(codesHandler);
|
|
229
|
+
const handlers = createServerHandlerCollection<TestDb>([codesHandler]);
|
|
232
230
|
|
|
233
231
|
// 1. Initial bootstrap pull
|
|
234
232
|
const firstPull = await pull({
|
|
235
233
|
db,
|
|
236
234
|
dialect,
|
|
237
235
|
handlers,
|
|
238
|
-
actorId: 'u1',
|
|
236
|
+
auth: { actorId: 'u1' },
|
|
239
237
|
request: {
|
|
240
238
|
clientId: 'client-1',
|
|
241
239
|
limitCommits: 10,
|
|
@@ -262,7 +260,7 @@ describe('pull re-bootstrap after external data change', () => {
|
|
|
262
260
|
db,
|
|
263
261
|
dialect,
|
|
264
262
|
handlers,
|
|
265
|
-
actorId: 'u1',
|
|
263
|
+
auth: { actorId: 'u1' },
|
|
266
264
|
request: {
|
|
267
265
|
clientId: 'client-1',
|
|
268
266
|
limitCommits: 10,
|
|
@@ -296,7 +294,7 @@ describe('pull re-bootstrap after external data change', () => {
|
|
|
296
294
|
db,
|
|
297
295
|
dialect,
|
|
298
296
|
handlers,
|
|
299
|
-
actorId: 'u1',
|
|
297
|
+
auth: { actorId: 'u1' },
|
|
300
298
|
request: {
|
|
301
299
|
clientId: 'client-1',
|
|
302
300
|
limitCommits: 10,
|
|
@@ -341,16 +339,17 @@ describe('pull re-bootstrap after external data change', () => {
|
|
|
341
339
|
resolveScopes: async () => ({ catalog_id: '*' }),
|
|
342
340
|
});
|
|
343
341
|
|
|
344
|
-
const handlers =
|
|
345
|
-
|
|
346
|
-
|
|
342
|
+
const handlers = createServerHandlerCollection<TestDb>([
|
|
343
|
+
tasksHandler,
|
|
344
|
+
codesHandler,
|
|
345
|
+
]);
|
|
347
346
|
|
|
348
347
|
// 1. Bootstrap pull for tasks
|
|
349
348
|
const firstPull = await pull({
|
|
350
349
|
db,
|
|
351
350
|
dialect,
|
|
352
351
|
handlers,
|
|
353
|
-
actorId: 'u1',
|
|
352
|
+
auth: { actorId: 'u1' },
|
|
354
353
|
request: {
|
|
355
354
|
clientId: 'client-1',
|
|
356
355
|
limitCommits: 10,
|
|
@@ -382,7 +381,7 @@ describe('pull re-bootstrap after external data change', () => {
|
|
|
382
381
|
db,
|
|
383
382
|
dialect,
|
|
384
383
|
handlers,
|
|
385
|
-
actorId: 'u1',
|
|
384
|
+
auth: { actorId: 'u1' },
|
|
386
385
|
request: {
|
|
387
386
|
clientId: 'client-1',
|
|
388
387
|
limitCommits: 10,
|
|
@@ -437,16 +436,17 @@ describe('pull re-bootstrap after external data change', () => {
|
|
|
437
436
|
resolveScopes: async () => ({ catalog_id: '*' }),
|
|
438
437
|
});
|
|
439
438
|
|
|
440
|
-
const handlers =
|
|
441
|
-
|
|
442
|
-
|
|
439
|
+
const handlers = createServerHandlerCollection<TestDb>([
|
|
440
|
+
tasksHandler,
|
|
441
|
+
codesHandler,
|
|
442
|
+
]);
|
|
443
443
|
|
|
444
444
|
// 1. Bootstrap both subscriptions
|
|
445
445
|
const firstPull = await pull({
|
|
446
446
|
db,
|
|
447
447
|
dialect,
|
|
448
448
|
handlers,
|
|
449
|
-
actorId: 'u1',
|
|
449
|
+
auth: { actorId: 'u1' },
|
|
450
450
|
request: {
|
|
451
451
|
clientId: 'client-1',
|
|
452
452
|
limitCommits: 10,
|
|
@@ -480,7 +480,7 @@ describe('pull re-bootstrap after external data change', () => {
|
|
|
480
480
|
db,
|
|
481
481
|
dialect,
|
|
482
482
|
handlers,
|
|
483
|
-
actorId: 'u1',
|
|
483
|
+
auth: { actorId: 'u1' },
|
|
484
484
|
request: {
|
|
485
485
|
clientId: 'client-1',
|
|
486
486
|
limitCommits: 10,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ProxyTableHandler } from './types';
|
|
2
|
+
|
|
3
|
+
export interface ProxyHandlerCollection {
|
|
4
|
+
handlers: ProxyTableHandler[];
|
|
5
|
+
byTable: ReadonlyMap<string, ProxyTableHandler>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createProxyHandlerCollection(
|
|
9
|
+
handlers: ProxyTableHandler[]
|
|
10
|
+
): ProxyHandlerCollection {
|
|
11
|
+
const byTable = new Map<string, ProxyTableHandler>();
|
|
12
|
+
for (const handler of handlers) {
|
|
13
|
+
if (byTable.has(handler.table)) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Proxy table handler already registered: ${handler.table}`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
byTable.set(handler.table, handler);
|
|
19
|
+
}
|
|
20
|
+
return { handlers, byTable };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getProxyHandler(
|
|
24
|
+
collection: ProxyHandlerCollection,
|
|
25
|
+
tableName: string
|
|
26
|
+
): ProxyTableHandler | undefined {
|
|
27
|
+
return collection.byTable.get(tableName);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getProxyHandlerOrThrow(
|
|
31
|
+
collection: ProxyHandlerCollection,
|
|
32
|
+
tableName: string
|
|
33
|
+
): ProxyTableHandler {
|
|
34
|
+
const handler = collection.byTable.get(tableName);
|
|
35
|
+
if (!handler) {
|
|
36
|
+
throw new Error(`No proxy table handler for table: ${tableName}`);
|
|
37
|
+
}
|
|
38
|
+
return handler;
|
|
39
|
+
}
|
|
@@ -4,8 +4,8 @@ import { createBunSqliteDb } from '../../../dialect-bun-sqlite/src';
|
|
|
4
4
|
import { createSqliteServerDialect } from '../../../server-dialect-sqlite/src';
|
|
5
5
|
import { ensureSyncSchema } from '../migrate';
|
|
6
6
|
import type { SyncCoreDb } from '../schema';
|
|
7
|
+
import { createProxyHandlerCollection } from './collection';
|
|
7
8
|
import { executeProxyQuery } from './handler';
|
|
8
|
-
import { ProxyTableRegistry } from './registry';
|
|
9
9
|
|
|
10
10
|
interface TasksTable {
|
|
11
11
|
id: string;
|
|
@@ -21,12 +21,14 @@ interface ProxyTestDb extends SyncCoreDb {
|
|
|
21
21
|
describe('executeProxyQuery', () => {
|
|
22
22
|
let db: Kysely<ProxyTestDb>;
|
|
23
23
|
const dialect = createSqliteServerDialect();
|
|
24
|
-
const handlers =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const handlers = createProxyHandlerCollection([
|
|
25
|
+
{
|
|
26
|
+
table: 'tasks',
|
|
27
|
+
computeScopes: (row) => ({
|
|
28
|
+
user_id: String(row.user_id),
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
30
32
|
|
|
31
33
|
beforeEach(async () => {
|
|
32
34
|
db = createBunSqliteDb<ProxyTestDb>({ path: ':memory:' });
|
package/src/proxy/handler.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { Kysely, RawBuilder } from 'kysely';
|
|
|
8
8
|
import { sql } from 'kysely';
|
|
9
9
|
import type { ServerSyncDialect } from '../dialect/types';
|
|
10
10
|
import type { SyncCoreDb } from '../schema';
|
|
11
|
+
import { getProxyHandler, type ProxyHandlerCollection } from './collection';
|
|
11
12
|
import {
|
|
12
13
|
appendReturning,
|
|
13
14
|
detectMutation,
|
|
@@ -15,7 +16,6 @@ import {
|
|
|
15
16
|
hasReturningWildcard,
|
|
16
17
|
} from './mutation-detector';
|
|
17
18
|
import { createOplogEntries } from './oplog';
|
|
18
|
-
import type { ProxyTableRegistry } from './registry';
|
|
19
19
|
import type { ProxyQueryContext } from './types';
|
|
20
20
|
|
|
21
21
|
export interface ExecuteProxyQueryArgs<DB extends SyncCoreDb = SyncCoreDb> {
|
|
@@ -23,8 +23,8 @@ export interface ExecuteProxyQueryArgs<DB extends SyncCoreDb = SyncCoreDb> {
|
|
|
23
23
|
db: Kysely<DB>;
|
|
24
24
|
/** Server sync dialect */
|
|
25
25
|
dialect: ServerSyncDialect;
|
|
26
|
-
/** Proxy table
|
|
27
|
-
handlers:
|
|
26
|
+
/** Proxy table handlers for oplog generation */
|
|
27
|
+
handlers: ProxyHandlerCollection;
|
|
28
28
|
/** Query context (actor/client IDs) */
|
|
29
29
|
ctx: ProxyQueryContext;
|
|
30
30
|
/** SQL query string */
|
|
@@ -111,7 +111,7 @@ export async function executeProxyQuery<DB extends SyncCoreDb>(
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// Check if this table has a registered handler
|
|
114
|
-
const handler = handlers
|
|
114
|
+
const handler = getProxyHandler(handlers, mutation.tableName);
|
|
115
115
|
if (!handler) {
|
|
116
116
|
// No handler registered - execute without oplog
|
|
117
117
|
// This allows proxy operations on non-synced tables
|
package/src/proxy/index.ts
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
* Server-side proxy functionality for database access.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// Oplog creation
|
|
8
|
+
// Collections
|
|
9
|
+
export {
|
|
10
|
+
createProxyHandlerCollection,
|
|
11
|
+
getProxyHandler,
|
|
12
|
+
getProxyHandlerOrThrow,
|
|
13
|
+
type ProxyHandlerCollection,
|
|
14
|
+
} from './collection';
|
|
7
15
|
// Query execution
|
|
8
16
|
export {
|
|
9
17
|
type ExecuteProxyQueryArgs,
|
|
@@ -12,7 +20,4 @@ export {
|
|
|
12
20
|
} from './handler';
|
|
13
21
|
// Mutation detection
|
|
14
22
|
export { type DetectedMutation, detectMutation } from './mutation-detector';
|
|
15
|
-
// Oplog creation
|
|
16
|
-
// Registry
|
|
17
|
-
export { ProxyTableRegistry } from './registry';
|
|
18
23
|
// Types
|
package/src/pull.ts
CHANGED
|
@@ -22,7 +22,12 @@ import {
|
|
|
22
22
|
} from '@syncular/core';
|
|
23
23
|
import type { Kysely } from 'kysely';
|
|
24
24
|
import type { DbExecutor, ServerSyncDialect } from './dialect/types';
|
|
25
|
-
import
|
|
25
|
+
import {
|
|
26
|
+
getServerBootstrapOrderFor,
|
|
27
|
+
getServerHandlerOrThrow,
|
|
28
|
+
type ServerHandlerCollection,
|
|
29
|
+
} from './handlers/collection';
|
|
30
|
+
import type { ServerTableHandler, SyncServerAuth } from './handlers/types';
|
|
26
31
|
import { EXTERNAL_CLIENT_ID } from './notify';
|
|
27
32
|
import type { SyncCoreDb } from './schema';
|
|
28
33
|
import {
|
|
@@ -241,12 +246,14 @@ async function readExternalDataChanges<DB extends SyncCoreDb>(
|
|
|
241
246
|
}));
|
|
242
247
|
}
|
|
243
248
|
|
|
244
|
-
export async function pull<
|
|
249
|
+
export async function pull<
|
|
250
|
+
DB extends SyncCoreDb,
|
|
251
|
+
Auth extends SyncServerAuth,
|
|
252
|
+
>(args: {
|
|
245
253
|
db: Kysely<DB>;
|
|
246
254
|
dialect: ServerSyncDialect;
|
|
247
|
-
handlers:
|
|
248
|
-
|
|
249
|
-
partitionId?: string;
|
|
255
|
+
handlers: ServerHandlerCollection<DB, Auth>;
|
|
256
|
+
auth: Auth;
|
|
250
257
|
request: SyncPullRequest;
|
|
251
258
|
/**
|
|
252
259
|
* Optional snapshot chunk storage adapter.
|
|
@@ -257,7 +264,7 @@ export async function pull<DB extends SyncCoreDb>(args: {
|
|
|
257
264
|
}): Promise<PullResult> {
|
|
258
265
|
const { request, dialect } = args;
|
|
259
266
|
const db = args.db;
|
|
260
|
-
const partitionId = args.partitionId ?? 'default';
|
|
267
|
+
const partitionId = args.auth.partitionId ?? 'default';
|
|
261
268
|
const requestedSubscriptionCount = Array.isArray(request.subscriptions)
|
|
262
269
|
? request.subscriptions.length
|
|
263
270
|
: 0;
|
|
@@ -293,7 +300,7 @@ export async function pull<DB extends SyncCoreDb>(args: {
|
|
|
293
300
|
// Resolve effective scopes for each subscription
|
|
294
301
|
const resolved = await resolveEffectiveScopesForSubscriptions({
|
|
295
302
|
db,
|
|
296
|
-
|
|
303
|
+
auth: args.auth,
|
|
297
304
|
subscriptions: request.subscriptions ?? [],
|
|
298
305
|
handlers: args.handlers,
|
|
299
306
|
});
|
|
@@ -347,7 +354,7 @@ export async function pull<DB extends SyncCoreDb>(args: {
|
|
|
347
354
|
for (const sub of resolved) {
|
|
348
355
|
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
349
356
|
// Validate table handler exists (throws if not registered)
|
|
350
|
-
args.handlers
|
|
357
|
+
getServerHandlerOrThrow(args.handlers, sub.table);
|
|
351
358
|
|
|
352
359
|
if (
|
|
353
360
|
sub.status === 'revoked' ||
|
|
@@ -379,9 +386,10 @@ export async function pull<DB extends SyncCoreDb>(args: {
|
|
|
379
386
|
latestExternalCommitForTable > cursor);
|
|
380
387
|
|
|
381
388
|
if (needsBootstrap) {
|
|
382
|
-
const tables =
|
|
383
|
-
.
|
|
384
|
-
.
|
|
389
|
+
const tables = getServerBootstrapOrderFor(
|
|
390
|
+
args.handlers,
|
|
391
|
+
sub.table
|
|
392
|
+
).map((handler) => handler.table);
|
|
385
393
|
|
|
386
394
|
const initState: SyncBootstrapState = {
|
|
387
395
|
asOfCommitSeq: maxCommitSeq,
|
|
@@ -539,7 +547,8 @@ export async function pull<DB extends SyncCoreDb>(args: {
|
|
|
539
547
|
) {
|
|
540
548
|
if (!nextState) break;
|
|
541
549
|
|
|
542
|
-
const nextTableName =
|
|
550
|
+
const nextTableName: string | undefined =
|
|
551
|
+
nextState.tables[nextState.tableIndex];
|
|
543
552
|
if (!nextTableName) {
|
|
544
553
|
if (activeBundle) {
|
|
545
554
|
activeBundle.isLastPage = true;
|
|
@@ -550,7 +559,8 @@ export async function pull<DB extends SyncCoreDb>(args: {
|
|
|
550
559
|
break;
|
|
551
560
|
}
|
|
552
561
|
|
|
553
|
-
const tableHandler =
|
|
562
|
+
const tableHandler: ServerTableHandler<DB, Auth> =
|
|
563
|
+
getServerHandlerOrThrow(args.handlers, nextTableName);
|
|
554
564
|
if (!activeBundle || activeBundle.table !== nextTableName) {
|
|
555
565
|
if (activeBundle) {
|
|
556
566
|
await flushSnapshotBundle(activeBundle);
|
|
@@ -568,16 +578,18 @@ export async function pull<DB extends SyncCoreDb>(args: {
|
|
|
568
578
|
};
|
|
569
579
|
}
|
|
570
580
|
|
|
571
|
-
const page
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
+
const page: { rows: unknown[]; nextCursor: string | null } =
|
|
582
|
+
await tableHandler.snapshot(
|
|
583
|
+
{
|
|
584
|
+
db: trx,
|
|
585
|
+
actorId: args.auth.actorId,
|
|
586
|
+
auth: args.auth,
|
|
587
|
+
scopeValues: effectiveScopes,
|
|
588
|
+
cursor: nextState.rowCursor,
|
|
589
|
+
limit: limitSnapshotRows,
|
|
590
|
+
},
|
|
591
|
+
sub.params
|
|
592
|
+
);
|
|
581
593
|
|
|
582
594
|
const rowFrames = encodeSnapshotRowFrames(page.rows ?? []);
|
|
583
595
|
activeBundle.rowFrameParts.push(rowFrames);
|