@syncular/server-hono 0.0.6-56 → 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.
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { logSyncEvent } from '@syncular/core';
19
- import type { SyncCoreDb } from '@syncular/server';
19
+ import type { SyncCoreDb, SyncServerAuth } from '@syncular/server';
20
20
  import {
21
21
  compactChanges,
22
22
  computePruneWatermarkCommitSeq,
@@ -391,9 +391,10 @@ const handlersResponseSchema = z.object({
391
391
  items: z.array(ConsoleHandlerSchema),
392
392
  });
393
393
 
394
- export function createConsoleRoutes<DB extends SyncCoreDb>(
395
- options: CreateConsoleRoutesOptions<DB>
396
- ): Hono {
394
+ export function createConsoleRoutes<
395
+ DB extends SyncCoreDb,
396
+ Auth extends SyncServerAuth,
397
+ >(options: CreateConsoleRoutesOptions<DB, Auth>): Hono {
397
398
  const routes = new Hono();
398
399
 
399
400
  routes.onError((error, context) => {
@@ -3337,143 +3338,153 @@ export function createConsoleRoutes<DB extends SyncCoreDb>(
3337
3338
  // -----------------------------------------------------------------------
3338
3339
  // Storage endpoints
3339
3340
  // -----------------------------------------------------------------------
3340
- if (options.blobBucket) {
3341
- const bucket = options.blobBucket;
3341
+ const bucket = options.blobBucket;
3342
3342
 
3343
- routes.get(
3344
- '/storage',
3345
- describeRoute({
3346
- tags: ['console'],
3347
- summary: 'List storage items',
3348
- responses: {
3349
- 200: {
3350
- description: 'Paginated list of storage items',
3351
- content: {
3352
- 'application/json': {
3353
- schema: resolver(ConsoleBlobListResponseSchema),
3354
- },
3343
+ routes.get(
3344
+ '/storage',
3345
+ describeRoute({
3346
+ tags: ['console'],
3347
+ summary: 'List storage items',
3348
+ responses: {
3349
+ 200: {
3350
+ description: 'Paginated list of storage items',
3351
+ content: {
3352
+ 'application/json': {
3353
+ schema: resolver(ConsoleBlobListResponseSchema),
3355
3354
  },
3356
3355
  },
3357
- 401: {
3358
- description: 'Unauthenticated',
3359
- content: {
3360
- 'application/json': { schema: resolver(ErrorResponseSchema) },
3361
- },
3356
+ },
3357
+ 401: {
3358
+ description: 'Unauthenticated',
3359
+ content: {
3360
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
3362
3361
  },
3363
3362
  },
3364
- }),
3365
- zValidator('query', ConsoleBlobListQuerySchema),
3366
- async (c) => {
3367
- const auth = await requireAuth(c);
3368
- if (!auth) return c.json({ error: 'UNAUTHENTICATED' }, 401);
3369
-
3370
- const { prefix, cursor, limit } = c.req.valid('query');
3371
- const listed = await bucket.list({
3372
- prefix: prefix || undefined,
3373
- cursor: cursor || undefined,
3374
- limit,
3375
- });
3363
+ },
3364
+ }),
3365
+ zValidator('query', ConsoleBlobListQuerySchema),
3366
+ async (c) => {
3367
+ const auth = await requireAuth(c);
3368
+ if (!auth) return c.json({ error: 'UNAUTHENTICATED' }, 401);
3376
3369
 
3377
- return c.json(
3378
- {
3379
- items: listed.objects.map((obj) => ({
3380
- key: obj.key,
3381
- size: obj.size,
3382
- uploaded: obj.uploaded.toISOString(),
3383
- httpMetadata: obj.httpMetadata?.contentType
3384
- ? { contentType: obj.httpMetadata.contentType }
3385
- : undefined,
3386
- })),
3387
- truncated: listed.truncated,
3388
- cursor: listed.cursor ?? null,
3389
- },
3390
- 200
3391
- );
3370
+ if (!bucket) {
3371
+ return c.json({ error: 'BLOB_STORAGE_NOT_CONFIGURED' }, 501);
3392
3372
  }
3393
- );
3394
3373
 
3395
- routes.get(
3396
- '/storage/:key{.+}/download',
3397
- describeRoute({
3398
- tags: ['console'],
3399
- summary: 'Download a storage item',
3400
- responses: {
3401
- 200: { description: 'Storage item contents' },
3402
- 401: {
3403
- description: 'Unauthenticated',
3404
- content: {
3405
- 'application/json': { schema: resolver(ErrorResponseSchema) },
3406
- },
3374
+ const { prefix, cursor, limit } = c.req.valid('query');
3375
+ const listed = await bucket.list({
3376
+ prefix: prefix || undefined,
3377
+ cursor: cursor || undefined,
3378
+ limit,
3379
+ });
3380
+
3381
+ return c.json(
3382
+ {
3383
+ items: listed.objects.map((obj) => ({
3384
+ key: obj.key,
3385
+ size: obj.size,
3386
+ uploaded: obj.uploaded.toISOString(),
3387
+ httpMetadata: obj.httpMetadata?.contentType
3388
+ ? { contentType: obj.httpMetadata.contentType }
3389
+ : undefined,
3390
+ })),
3391
+ truncated: listed.truncated,
3392
+ cursor: listed.cursor ?? null,
3393
+ },
3394
+ 200
3395
+ );
3396
+ }
3397
+ );
3398
+
3399
+ routes.get(
3400
+ '/storage/:key{.+}/download',
3401
+ describeRoute({
3402
+ tags: ['console'],
3403
+ summary: 'Download a storage item',
3404
+ responses: {
3405
+ 200: { description: 'Storage item contents' },
3406
+ 401: {
3407
+ description: 'Unauthenticated',
3408
+ content: {
3409
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
3407
3410
  },
3408
- 404: {
3409
- description: 'Blob not found',
3410
- content: {
3411
- 'application/json': { schema: resolver(ErrorResponseSchema) },
3412
- },
3411
+ },
3412
+ 404: {
3413
+ description: 'Blob not found',
3414
+ content: {
3415
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
3413
3416
  },
3414
3417
  },
3415
- }),
3416
- async (c) => {
3417
- const auth = await requireAuth(c);
3418
- if (!auth) return c.json({ error: 'UNAUTHENTICATED' }, 401);
3419
-
3420
- const key = decodeURIComponent(c.req.param('key'));
3421
- const object = await bucket.get(key);
3422
- if (!object) {
3423
- return c.json({ error: 'BLOB_NOT_FOUND' }, 404);
3424
- }
3418
+ },
3419
+ }),
3420
+ async (c) => {
3421
+ const auth = await requireAuth(c);
3422
+ if (!auth) return c.json({ error: 'UNAUTHENTICATED' }, 401);
3425
3423
 
3426
- const headers = new Headers();
3427
- headers.set('Content-Length', String(object.size));
3428
- headers.set(
3429
- 'Content-Type',
3430
- object.httpMetadata?.contentType ?? 'application/octet-stream'
3431
- );
3432
- const filename = key.split('/').pop() || key;
3433
- headers.set(
3434
- 'Content-Disposition',
3435
- `attachment; filename="${filename.replace(/"/g, '\\"')}"`
3436
- );
3424
+ if (!bucket) {
3425
+ return c.json({ error: 'BLOB_STORAGE_NOT_CONFIGURED' }, 501);
3426
+ }
3437
3427
 
3438
- return new Response(object.body as ReadableStream, {
3439
- status: 200,
3440
- headers,
3441
- });
3428
+ const key = decodeURIComponent(c.req.param('key'));
3429
+ const object = await bucket.get(key);
3430
+ if (!object) {
3431
+ return c.json({ error: 'BLOB_NOT_FOUND' }, 404);
3442
3432
  }
3443
- );
3444
3433
 
3445
- routes.delete(
3446
- '/storage/:key{.+}',
3447
- describeRoute({
3448
- tags: ['console'],
3449
- summary: 'Delete a storage item',
3450
- responses: {
3451
- 200: {
3452
- description: 'Storage item deleted',
3453
- content: {
3454
- 'application/json': {
3455
- schema: resolver(ConsoleBlobDeleteResponseSchema),
3456
- },
3434
+ const headers = new Headers();
3435
+ headers.set('Content-Length', String(object.size));
3436
+ headers.set(
3437
+ 'Content-Type',
3438
+ object.httpMetadata?.contentType ?? 'application/octet-stream'
3439
+ );
3440
+ const filename = key.split('/').pop() || key;
3441
+ headers.set(
3442
+ 'Content-Disposition',
3443
+ `attachment; filename="${filename.replace(/"/g, '\\"')}"`
3444
+ );
3445
+
3446
+ return new Response(object.body as ReadableStream, {
3447
+ status: 200,
3448
+ headers,
3449
+ });
3450
+ }
3451
+ );
3452
+
3453
+ routes.delete(
3454
+ '/storage/:key{.+}',
3455
+ describeRoute({
3456
+ tags: ['console'],
3457
+ summary: 'Delete a storage item',
3458
+ responses: {
3459
+ 200: {
3460
+ description: 'Storage item deleted',
3461
+ content: {
3462
+ 'application/json': {
3463
+ schema: resolver(ConsoleBlobDeleteResponseSchema),
3457
3464
  },
3458
3465
  },
3459
- 401: {
3460
- description: 'Unauthenticated',
3461
- content: {
3462
- 'application/json': { schema: resolver(ErrorResponseSchema) },
3463
- },
3466
+ },
3467
+ 401: {
3468
+ description: 'Unauthenticated',
3469
+ content: {
3470
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
3464
3471
  },
3465
3472
  },
3466
- }),
3467
- async (c) => {
3468
- const auth = await requireAuth(c);
3469
- if (!auth) return c.json({ error: 'UNAUTHENTICATED' }, 401);
3473
+ },
3474
+ }),
3475
+ async (c) => {
3476
+ const auth = await requireAuth(c);
3477
+ if (!auth) return c.json({ error: 'UNAUTHENTICATED' }, 401);
3470
3478
 
3471
- const key = decodeURIComponent(c.req.param('key'));
3472
- await bucket.delete(key);
3473
- return c.json({ deleted: true }, 200);
3479
+ if (!bucket) {
3480
+ return c.json({ error: 'BLOB_STORAGE_NOT_CONFIGURED' }, 501);
3474
3481
  }
3475
- );
3476
- }
3482
+
3483
+ const key = decodeURIComponent(c.req.param('key'));
3484
+ await bucket.delete(key);
3485
+ return c.json({ deleted: true }, 200);
3486
+ }
3487
+ );
3477
3488
 
3478
3489
  return routes;
3479
3490
  }
@@ -2,6 +2,7 @@ import type {
2
2
  ServerSyncDialect,
3
3
  ServerTableHandler,
4
4
  SyncCoreDb,
5
+ SyncServerAuth,
5
6
  } from '@syncular/server';
6
7
  import type { Context } from 'hono';
7
8
  import type { UpgradeWebSocket } from 'hono/ws';
@@ -90,11 +91,13 @@ export interface ConsoleSharedOptions {
90
91
  blobBucket?: ConsoleBlobBucket;
91
92
  }
92
93
 
93
- export interface CreateConsoleRoutesOptions<DB extends SyncCoreDb = SyncCoreDb>
94
- extends ConsoleSharedOptions {
94
+ export interface CreateConsoleRoutesOptions<
95
+ DB extends SyncCoreDb = SyncCoreDb,
96
+ Auth extends SyncServerAuth = SyncServerAuth,
97
+ > extends ConsoleSharedOptions {
95
98
  db: Kysely<DB>;
96
99
  dialect: ServerSyncDialect;
97
- handlers: ServerTableHandler<DB>[];
100
+ handlers: ServerTableHandler<DB, Auth>[];
98
101
  /**
99
102
  * Authentication function for console requests.
100
103
  * Return null to reject the request.
@@ -2,17 +2,16 @@
2
2
  * Simplified server factory for Hono
3
3
  *
4
4
  * Breaking changes from legacy createSyncRoutes:
5
- * - handlers: array instead of TableRegistry
5
+ * - sync contract instead of top-level handlers/authenticate
6
6
  * - Combined sync + console routes in one call
7
7
  */
8
8
 
9
9
  import type {
10
+ ServerSyncConfig,
10
11
  ServerSyncDialect,
11
- ServerTableHandler,
12
12
  SnapshotChunkStorage,
13
13
  SyncCoreDb,
14
14
  } from '@syncular/server';
15
- import type { Context } from 'hono';
16
15
  import type { UpgradeWebSocket } from 'hono/ws';
17
16
  import type { Kysely } from 'kysely';
18
17
  import {
@@ -31,26 +30,24 @@ import {
31
30
  type SyncRoutesConfigWithRateLimit,
32
31
  } from './routes';
33
32
 
34
- export interface SyncServerOptions<DB extends SyncCoreDb = SyncCoreDb> {
33
+ export interface SyncServerOptions<
34
+ DB extends SyncCoreDb = SyncCoreDb,
35
+ Auth extends SyncAuthResult = SyncAuthResult,
36
+ > {
35
37
  /** Kysely database instance */
36
38
  db: Kysely<DB>;
37
39
 
38
40
  /** Server sync dialect */
39
41
  dialect: ServerSyncDialect;
40
42
 
41
- /**
42
- * Table handlers for sync operations.
43
- */
44
- handlers: ServerTableHandler<DB>[];
45
-
46
- /** Authentication function - returns actorId or null for unauthenticated */
47
- authenticate: (c: Context) => Promise<SyncAuthResult | null>;
43
+ /** Sync contract with auth + table handlers */
44
+ sync: ServerSyncConfig<DB, Auth>;
48
45
 
49
46
  /** Snapshot chunk storage (external body storage, e.g. R2/S3) */
50
47
  chunkStorage?: SnapshotChunkStorage;
51
48
 
52
49
  /** Sync route configuration */
53
- sync?: SyncRoutesConfigWithRateLimit;
50
+ routes?: SyncRoutesConfigWithRateLimit;
54
51
 
55
52
  /** WebSocket upgrader for realtime */
56
53
  upgradeWebSocket?: UpgradeWebSocket;
@@ -81,45 +78,37 @@ export interface SyncServerResult {
81
78
  *
82
79
  * @example
83
80
  * ```typescript
84
- * // With handlers
81
+ * // With sync contract
85
82
  * const { syncRoutes } = createSyncServer({
86
83
  * db,
87
84
  * dialect,
88
- * handlers: [tasksHandler, notesHandler],
89
- * authenticate: async (c) => {
90
- * const userId = c.req.header('x-user-id');
91
- * return userId ? { actorId: userId } : null;
92
- * },
85
+ * sync,
93
86
  * });
94
87
  *
95
88
  * // With custom handlers
96
89
  * const { syncRoutes, consoleRoutes } = createSyncServer({
97
90
  * db,
98
91
  * dialect,
99
- * handlers: [tasksHandler, notesHandler],
100
- * authenticate: async (c) => {
101
- * const userId = c.req.header('x-user-id');
102
- * return userId ? { actorId: userId } : null;
103
- * },
92
+ * sync,
104
93
  * console: { token: process.env.CONSOLE_TOKEN },
105
94
  * });
106
95
  * ```
107
96
  */
108
- export function createSyncServer<DB extends SyncCoreDb = SyncCoreDb>(
109
- options: SyncServerOptions<DB>
110
- ): SyncServerResult {
97
+ export function createSyncServer<
98
+ DB extends SyncCoreDb = SyncCoreDb,
99
+ Auth extends SyncAuthResult = SyncAuthResult,
100
+ >(options: SyncServerOptions<DB, Auth>): SyncServerResult {
111
101
  const {
112
102
  db,
113
103
  dialect,
114
- handlers,
115
- authenticate,
116
- chunkStorage,
117
104
  sync,
105
+ chunkStorage,
106
+ routes,
118
107
  upgradeWebSocket,
119
108
  console: consoleConfig,
120
109
  } = options;
121
110
 
122
- if (handlers.length === 0) {
111
+ if (sync.handlers.length === 0) {
123
112
  throw new Error('At least one handler must be provided');
124
113
  }
125
114
 
@@ -146,24 +135,25 @@ export function createSyncServer<DB extends SyncCoreDb = SyncCoreDb>(
146
135
  const syncRoutes = createSyncRoutes({
147
136
  db,
148
137
  dialect,
149
- handlers,
150
- authenticate,
138
+ handlers: sync.handlers,
139
+ authenticate: async (context): Promise<Auth | null> =>
140
+ sync.authenticate(context.req.raw),
151
141
  chunkStorage,
152
142
  consoleLiveEmitter: consoleEventEmitter,
153
143
  sync: {
154
- ...sync,
144
+ ...routes,
155
145
  websocket: upgradeWebSocket
156
146
  ? {
157
147
  enabled: true,
158
148
  upgradeWebSocket,
159
- ...(sync?.websocket?.heartbeatIntervalMs !== undefined && {
160
- heartbeatIntervalMs: sync.websocket.heartbeatIntervalMs,
149
+ ...(routes?.websocket?.heartbeatIntervalMs !== undefined && {
150
+ heartbeatIntervalMs: routes.websocket.heartbeatIntervalMs,
161
151
  }),
162
- ...(sync?.websocket?.maxConnectionsTotal !== undefined && {
163
- maxConnectionsTotal: sync.websocket.maxConnectionsTotal,
152
+ ...(routes?.websocket?.maxConnectionsTotal !== undefined && {
153
+ maxConnectionsTotal: routes.websocket.maxConnectionsTotal,
164
154
  }),
165
- ...(sync?.websocket?.maxConnectionsPerClient !== undefined && {
166
- maxConnectionsPerClient: sync.websocket.maxConnectionsPerClient,
155
+ ...(routes?.websocket?.maxConnectionsPerClient !== undefined && {
156
+ maxConnectionsPerClient: routes.websocket.maxConnectionsPerClient,
167
157
  }),
168
158
  }
169
159
  : { enabled: false },
@@ -178,7 +168,7 @@ export function createSyncServer<DB extends SyncCoreDb = SyncCoreDb>(
178
168
  const consoleRoutes = createConsoleRoutes({
179
169
  db,
180
170
  dialect,
181
- handlers,
171
+ handlers: sync.handlers,
182
172
  authenticate: createTokenAuthenticator(consoleToken),
183
173
  corsOrigins: resolvedConsoleConfig.corsOrigins ?? '*',
184
174
  eventEmitter: consoleEventEmitter,
@@ -11,7 +11,7 @@ import type {
11
11
  } from '@syncular/core';
12
12
  import type {
13
13
  ExecuteProxyQueryResult,
14
- ProxyTableRegistry,
14
+ ProxyHandlerCollection,
15
15
  ServerSyncDialect,
16
16
  SyncCoreDb,
17
17
  } from '@syncular/server';
@@ -26,8 +26,8 @@ export interface ProxyConnectionManagerConfig<
26
26
  db: Kysely<DB>;
27
27
  /** Server sync dialect */
28
28
  dialect: ServerSyncDialect;
29
- /** Proxy table registry for oplog generation */
30
- handlers: ProxyTableRegistry;
29
+ /** Proxy table handlers for oplog generation */
30
+ handlers: ProxyHandlerCollection;
31
31
  /** Maximum concurrent connections (default: 100) */
32
32
  maxConnections?: number;
33
33
  /** Idle connection timeout in ms (default: 30000) */
@@ -12,7 +12,7 @@ import type {
12
12
  } from '@syncular/core';
13
13
  import { logSyncEvent } from '@syncular/core';
14
14
  import type {
15
- ProxyTableRegistry,
15
+ ProxyHandlerCollection,
16
16
  ServerSyncDialect,
17
17
  SyncCoreDb,
18
18
  } from '@syncular/server';
@@ -48,8 +48,8 @@ interface CreateProxyRoutesConfig<DB extends SyncCoreDb = SyncCoreDb> {
48
48
  db: Kysely<DB>;
49
49
  /** Server sync dialect */
50
50
  dialect: ServerSyncDialect;
51
- /** Proxy table registry for oplog generation */
52
- handlers: ProxyTableRegistry;
51
+ /** Proxy table handlers for oplog generation */
52
+ handlers: ProxyHandlerCollection;
53
53
  /** Authenticate the request and return actor info */
54
54
  authenticate: (c: Context) => Promise<ProxyAuthResult | null>;
55
55
  /** WebSocket upgrade function from Hono */
@@ -75,7 +75,7 @@ interface CreateProxyRoutesConfig<DB extends SyncCoreDb = SyncCoreDb> {
75
75
  *
76
76
  * app.route('/proxy', createProxyRoutes({
77
77
  * db,
78
- * handlers: proxyTableRegistry,
78
+ * handlers: proxyHandlers,
79
79
  * authenticate: async (c) => {
80
80
  * // Verify admin auth
81
81
  * return { actorId: 'admin:123' };
package/src/routes.ts CHANGED
@@ -23,9 +23,11 @@ import type {
23
23
  SyncCoreDb,
24
24
  SyncRealtimeBroadcaster,
25
25
  SyncRealtimeEvent,
26
+ SyncServerAuth,
26
27
  } from '@syncular/server';
27
28
  import {
28
29
  type CompactOptions,
30
+ createServerHandlerCollection,
29
31
  InvalidSubscriptionScopeError,
30
32
  type PruneOptions,
31
33
  type PullResult,
@@ -33,7 +35,6 @@ import {
33
35
  pushCommit,
34
36
  readSnapshotChunk,
35
37
  recordClientCursor,
36
- TableRegistry,
37
38
  } from '@syncular/server';
38
39
  import type { Context, MiddlewareHandler } from 'hono';
39
40
  import { Hono } from 'hono';
@@ -64,10 +65,7 @@ import {
64
65
  const wsConnectionManagerMap = new WeakMap<Hono, WebSocketConnectionManager>();
65
66
  const realtimeUnsubscribeMap = new WeakMap<Hono, () => void>();
66
67
 
67
- export interface SyncAuthResult {
68
- actorId: string;
69
- partitionId?: string;
70
- }
68
+ export interface SyncAuthResult extends SyncServerAuth {}
71
69
 
72
70
  /**
73
71
  * WebSocket configuration for realtime sync.
@@ -162,11 +160,14 @@ export interface SyncRoutesConfigWithRateLimit {
162
160
  };
163
161
  }
164
162
 
165
- export interface CreateSyncRoutesOptions<DB extends SyncCoreDb = SyncCoreDb> {
163
+ export interface CreateSyncRoutesOptions<
164
+ DB extends SyncCoreDb = SyncCoreDb,
165
+ Auth extends SyncAuthResult = SyncAuthResult,
166
+ > {
166
167
  db: Kysely<DB>;
167
168
  dialect: ServerSyncDialect;
168
- handlers: ServerTableHandler<DB>[];
169
- authenticate: (c: Context) => Promise<SyncAuthResult | null>;
169
+ handlers: ServerTableHandler<DB, Auth>[];
170
+ authenticate: (c: Context) => Promise<Auth | null>;
170
171
  sync?: SyncRoutesConfigWithRateLimit;
171
172
  wsConnectionManager?: WebSocketConnectionManager;
172
173
  /**
@@ -430,9 +431,10 @@ function emitConsoleLiveEvent(
430
431
  });
431
432
  }
432
433
 
433
- export function createSyncRoutes<DB extends SyncCoreDb = SyncCoreDb>(
434
- options: CreateSyncRoutesOptions<DB>
435
- ): Hono {
434
+ export function createSyncRoutes<
435
+ DB extends SyncCoreDb = SyncCoreDb,
436
+ Auth extends SyncAuthResult = SyncAuthResult,
437
+ >(options: CreateSyncRoutesOptions<DB, Auth>): Hono {
436
438
  const routes = new Hono();
437
439
  routes.onError((error, c) => {
438
440
  captureSyncException(error, {
@@ -442,10 +444,7 @@ export function createSyncRoutes<DB extends SyncCoreDb = SyncCoreDb>(
442
444
  });
443
445
  return c.text('Internal Server Error', 500);
444
446
  });
445
- const handlerRegistry = new TableRegistry<DB>();
446
- for (const handler of options.handlers) {
447
- handlerRegistry.register(handler);
448
- }
447
+ const handlerRegistry = createServerHandlerCollection(options.handlers);
449
448
  const config = options.sync ?? {};
450
449
  const maxPullLimitCommits = config.maxPullLimitCommits ?? 100;
451
450
  const maxSubscriptionsPerPull = config.maxSubscriptionsPerPull ?? 200;
@@ -631,8 +630,8 @@ export function createSyncRoutes<DB extends SyncCoreDb = SyncCoreDb>(
631
630
  });
632
631
  };
633
632
 
634
- const authCache = new WeakMap<Context, Promise<SyncAuthResult | null>>();
635
- const getAuth = (c: Context): Promise<SyncAuthResult | null> => {
633
+ const authCache = new WeakMap<Context, Promise<Auth | null>>();
634
+ const getAuth = (c: Context): Promise<Auth | null> => {
636
635
  const cached = authCache.get(c);
637
636
  if (cached) return cached;
638
637
  const pending = options.authenticate(c);
@@ -787,8 +786,7 @@ export function createSyncRoutes<DB extends SyncCoreDb = SyncCoreDb>(
787
786
  db: options.db,
788
787
  dialect: options.dialect,
789
788
  handlers: handlerRegistry,
790
- actorId: auth.actorId,
791
- partitionId,
789
+ auth,
792
790
  request: {
793
791
  clientId,
794
792
  clientCommitId: pushBody.clientCommitId,
@@ -979,8 +977,7 @@ export function createSyncRoutes<DB extends SyncCoreDb = SyncCoreDb>(
979
977
  db: options.db,
980
978
  dialect: options.dialect,
981
979
  handlers: handlerRegistry,
982
- actorId: auth.actorId,
983
- partitionId,
980
+ auth,
984
981
  request,
985
982
  chunkStorage: options.chunkStorage,
986
983
  });
@@ -1383,13 +1380,7 @@ export function createSyncRoutes<DB extends SyncCoreDb = SyncCoreDb>(
1383
1380
  if (!msg || typeof msg !== 'object') return;
1384
1381
 
1385
1382
  if (msg.type === 'push') {
1386
- void handleWsPush(
1387
- msg,
1388
- connRef,
1389
- auth.actorId,
1390
- partitionId,
1391
- clientId
1392
- );
1383
+ void handleWsPush(msg, connRef, auth, clientId);
1393
1384
  return;
1394
1385
  }
1395
1386
 
@@ -1481,10 +1472,11 @@ export function createSyncRoutes<DB extends SyncCoreDb = SyncCoreDb>(
1481
1472
  async function handleWsPush(
1482
1473
  msg: Record<string, unknown>,
1483
1474
  conn: WebSocketConnection,
1484
- actorId: string,
1485
- partitionId: string,
1475
+ auth: Auth,
1486
1476
  clientId: string
1487
1477
  ): Promise<void> {
1478
+ const actorId = auth.actorId;
1479
+ const partitionId = auth.partitionId ?? 'default';
1488
1480
  const requestId = typeof msg.requestId === 'string' ? msg.requestId : '';
1489
1481
  if (!requestId) return;
1490
1482
  const traceContext = readTraceContextFromMessage(msg);
@@ -1615,8 +1607,7 @@ export function createSyncRoutes<DB extends SyncCoreDb = SyncCoreDb>(
1615
1607
  db: options.db,
1616
1608
  dialect: options.dialect,
1617
1609
  handlers: handlerRegistry,
1618
- actorId,
1619
- partitionId,
1610
+ auth,
1620
1611
  request: {
1621
1612
  clientId,
1622
1613
  clientCommitId: parsed.data.clientCommitId,