@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.
Files changed (70) hide show
  1. package/dist/handlers/collection.d.ts +12 -0
  2. package/dist/handlers/collection.d.ts.map +1 -0
  3. package/dist/handlers/collection.js +64 -0
  4. package/dist/handlers/collection.js.map +1 -0
  5. package/dist/handlers/create-handler.d.ts +9 -9
  6. package/dist/handlers/create-handler.d.ts.map +1 -1
  7. package/dist/handlers/create-handler.js.map +1 -1
  8. package/dist/handlers/index.d.ts +1 -1
  9. package/dist/handlers/index.d.ts.map +1 -1
  10. package/dist/handlers/index.js +1 -1
  11. package/dist/handlers/index.js.map +1 -1
  12. package/dist/handlers/types.d.ts +18 -12
  13. package/dist/handlers/types.d.ts.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/proxy/collection.d.ts +9 -0
  19. package/dist/proxy/collection.d.ts.map +1 -0
  20. package/dist/proxy/collection.js +21 -0
  21. package/dist/proxy/collection.js.map +1 -0
  22. package/dist/proxy/handler.d.ts +3 -3
  23. package/dist/proxy/handler.d.ts.map +1 -1
  24. package/dist/proxy/handler.js +2 -1
  25. package/dist/proxy/handler.js.map +1 -1
  26. package/dist/proxy/index.d.ts +1 -1
  27. package/dist/proxy/index.d.ts.map +1 -1
  28. package/dist/proxy/index.js +3 -3
  29. package/dist/proxy/index.js.map +1 -1
  30. package/dist/pull.d.ts +5 -5
  31. package/dist/pull.d.ts.map +1 -1
  32. package/dist/pull.js +8 -8
  33. package/dist/pull.js.map +1 -1
  34. package/dist/push.d.ts +5 -5
  35. package/dist/push.d.ts.map +1 -1
  36. package/dist/push.js +5 -3
  37. package/dist/push.js.map +1 -1
  38. package/dist/subscriptions/resolve.d.ts +5 -4
  39. package/dist/subscriptions/resolve.d.ts.map +1 -1
  40. package/dist/subscriptions/resolve.js +4 -2
  41. package/dist/subscriptions/resolve.js.map +1 -1
  42. package/dist/sync.d.ts +21 -0
  43. package/dist/sync.d.ts.map +1 -0
  44. package/dist/sync.js +23 -0
  45. package/dist/sync.js.map +1 -0
  46. package/package.json +2 -2
  47. package/src/handlers/collection.ts +121 -0
  48. package/src/handlers/create-handler.ts +23 -14
  49. package/src/handlers/index.ts +1 -1
  50. package/src/handlers/types.ts +29 -12
  51. package/src/index.ts +1 -0
  52. package/src/notify.test.ts +17 -17
  53. package/src/proxy/collection.ts +39 -0
  54. package/src/proxy/handler.test.ts +9 -7
  55. package/src/proxy/handler.ts +4 -4
  56. package/src/proxy/index.ts +8 -3
  57. package/src/pull.ts +35 -23
  58. package/src/push.ts +15 -8
  59. package/src/subscriptions/resolve.ts +11 -5
  60. package/src/sync.ts +101 -0
  61. package/dist/handlers/registry.d.ts +0 -20
  62. package/dist/handlers/registry.d.ts.map +0 -1
  63. package/dist/handlers/registry.js +0 -88
  64. package/dist/handlers/registry.js.map +0 -1
  65. package/dist/proxy/registry.d.ts +0 -35
  66. package/dist/proxy/registry.d.ts.map +0 -1
  67. package/dist/proxy/registry.js +0 -49
  68. package/dist/proxy/registry.js.map +0 -1
  69. package/src/handlers/registry.ts +0 -109
  70. 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<ServerDB, ClientDB, TableName, ScopeDefs>
220
- ): ServerTableHandler<ServerDB> {
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> => {
@@ -1,3 +1,3 @@
1
+ export * from './collection';
1
2
  export * from './create-handler';
2
- export * from './registry';
3
3
  export * from './types';
@@ -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<DB extends SyncCoreDb = SyncCoreDb> {
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
- > extends ServerContext<DB> {
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<DB extends SyncCoreDb = SyncCoreDb>
72
- extends ServerContext<DB> {
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<DB extends SyncCoreDb = SyncCoreDb> {
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
@@ -27,3 +27,4 @@ export * from './snapshot-chunks';
27
27
  export type { SnapshotChunkStorage } from './snapshot-chunks/types';
28
28
  export * from './stats';
29
29
  export * from './subscriptions';
30
+ export * from './sync';
@@ -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 = new TableRegistry<TestDb>();
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 = new TableRegistry<TestDb>();
345
- handlers.register(tasksHandler);
346
- handlers.register(codesHandler);
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 = new TableRegistry<TestDb>();
441
- handlers.register(tasksHandler);
442
- handlers.register(codesHandler);
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 = new ProxyTableRegistry().register({
25
- table: 'tasks',
26
- computeScopes: (row) => ({
27
- user_id: String(row.user_id),
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:' });
@@ -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 registry for oplog generation */
27
- handlers: ProxyTableRegistry;
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.get(mutation.tableName);
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
@@ -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 type { TableRegistry } from './handlers/registry';
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<DB extends SyncCoreDb>(args: {
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: TableRegistry<DB>;
248
- actorId: string;
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
- actorId: args.actorId,
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.getOrThrow(sub.table);
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 = args.handlers
383
- .getBootstrapOrderFor(sub.table)
384
- .map((handler) => handler.table);
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 = nextState.tables[nextState.tableIndex];
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 = args.handlers.getOrThrow(nextTableName);
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 = await tableHandler.snapshot(
572
- {
573
- db: trx,
574
- actorId: args.actorId,
575
- scopeValues: effectiveScopes,
576
- cursor: nextState.rowCursor,
577
- limit: limitSnapshotRows,
578
- },
579
- sub.params
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);