@korajs/server 0.3.2 → 0.4.0

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/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { SchemaDefinition, KoraEventEmitter, Operation, VersionVector } from '@korajs/core';
2
2
  import * as _korajs_sync from '@korajs/sync';
3
- import { SyncStore, MessageSerializer, SyncMessage, ApplyResult } from '@korajs/sync';
3
+ import { SyncStore, MessageSerializer, SyncMessage, AwarenessUpdateMessage, ApplyResult } from '@korajs/sync';
4
4
  import { S as ServerTransport, a as ServerMessageHandler, b as ServerCloseHandler, c as ServerErrorHandler } from './server-transport-CU1BLWgr.cjs';
5
5
  import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
6
6
  import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
@@ -252,6 +252,10 @@ type SessionState = 'connected' | 'authenticated' | 'syncing' | 'streaming' | 'c
252
252
  * Callback invoked when a session has new operations to relay to other sessions.
253
253
  */
254
254
  type RelayCallback = (sourceSessionId: string, operations: Operation[]) => void;
255
+ /**
256
+ * Callback invoked when a session receives an awareness update to relay to other sessions.
257
+ */
258
+ type AwarenessRelayCallback = (sourceSessionId: string, message: AwarenessUpdateMessage) => void;
255
259
  /**
256
260
  * Options for creating a ClientSession.
257
261
  */
@@ -274,6 +278,8 @@ interface ClientSessionOptions {
274
278
  schemaVersion?: number;
275
279
  /** Called when this session has operations to relay to other sessions */
276
280
  onRelay?: RelayCallback;
281
+ /** Called when this session receives an awareness update to broadcast */
282
+ onAwarenessUpdate?: AwarenessRelayCallback;
277
283
  /** Called when this session closes */
278
284
  onClose?: (sessionId: string) => void;
279
285
  }
@@ -304,6 +310,7 @@ declare class ClientSession {
304
310
  private readonly batchSize;
305
311
  private readonly schemaVersion;
306
312
  private readonly onRelay;
313
+ private readonly onAwarenessUpdate;
307
314
  private readonly onClose;
308
315
  constructor(options: ClientSessionOptions);
309
316
  /**
@@ -324,10 +331,16 @@ declare class ClientSession {
324
331
  getClientNodeId(): string | null;
325
332
  getAuthContext(): AuthContext | null;
326
333
  isStreaming(): boolean;
334
+ /**
335
+ * Get the transport for this session.
336
+ * Used by the awareness relay to send messages to this client.
337
+ */
338
+ getTransport(): ServerTransport;
327
339
  private handleMessage;
328
340
  private handleHandshake;
329
341
  private handleOperationBatch;
330
342
  private sendDelta;
343
+ private handleAwarenessUpdate;
331
344
  private sendError;
332
345
  private setSerializerWireFormat;
333
346
  private handleTransportClose;
@@ -372,6 +385,7 @@ declare class KoraSyncServer {
372
385
  private readonly port;
373
386
  private readonly host;
374
387
  private readonly path;
388
+ private readonly awarenessRelay;
375
389
  private readonly sessions;
376
390
  private readonly httpClients;
377
391
  private readonly httpSessionToClient;
@@ -412,6 +426,7 @@ declare class KoraSyncServer {
412
426
  getConnectionCount(): number;
413
427
  private handleRelay;
414
428
  private handleSessionClose;
429
+ private handleAwarenessRelay;
415
430
  private getOrCreateHttpClient;
416
431
  }
417
432
 
@@ -585,6 +600,88 @@ declare class KoraAuthProvider implements AuthProvider {
585
600
  authenticate(token: string): Promise<AuthContext | null>;
586
601
  }
587
602
 
603
+ /**
604
+ * Configuration for creating a MixedAuthProvider.
605
+ */
606
+ interface MixedAuthProviderOptions {
607
+ /**
608
+ * Primary auth provider that validates tokens from authenticated users.
609
+ * Typically a `KoraAuthProvider`, `TokenAuthProvider`, or the result of
610
+ * `authRoutes.toSyncAuthProvider()`.
611
+ */
612
+ primary: AuthProvider;
613
+ /**
614
+ * Scopes to apply to anonymous connections.
615
+ * Each key is a collection name; the value is a filter object
616
+ * (use `{}` for unrestricted access to that collection).
617
+ *
618
+ * @example
619
+ * ```typescript
620
+ * // Anonymous users can only sync the 'responses' collection
621
+ * anonymousScopes: { responses: {} }
622
+ *
623
+ * // Anonymous users can read published forms
624
+ * anonymousScopes: { forms: { status: 'published' } }
625
+ * ```
626
+ */
627
+ anonymousScopes: Record<string, Record<string, unknown>>;
628
+ /**
629
+ * Prefix for generated anonymous user IDs.
630
+ * A unique suffix is appended to each connection.
631
+ * @default 'anon'
632
+ */
633
+ anonymousPrefix?: string;
634
+ }
635
+ /**
636
+ * Auth provider that supports both authenticated and anonymous connections.
637
+ *
638
+ * When a client connects with a valid token, the primary auth provider
639
+ * handles authentication normally. When a client connects without a token
640
+ * (or with an invalid one), the connection is accepted as anonymous with
641
+ * restricted sync scopes.
642
+ *
643
+ * This is the recommended pattern for apps that need public data access
644
+ * alongside authenticated users — for example, a form builder where
645
+ * authenticated users create forms but anyone can submit responses.
646
+ *
647
+ * @example
648
+ * ```typescript
649
+ * import { MixedAuthProvider, KoraAuthProvider } from '@korajs/server'
650
+ *
651
+ * const auth = new MixedAuthProvider({
652
+ * primary: authRoutes.toSyncAuthProvider(),
653
+ * anonymousScopes: {
654
+ * // Anonymous users can only sync the 'responses' collection
655
+ * responses: {},
656
+ * },
657
+ * })
658
+ *
659
+ * const server = new KoraSyncServer({ store, auth })
660
+ * ```
661
+ *
662
+ * @example
663
+ * ```typescript
664
+ * // On the client, return an empty token for unauthenticated users:
665
+ * const app = createApp({
666
+ * schema,
667
+ * sync: {
668
+ * url: 'wss://my-server.com/kora',
669
+ * auth: async () => ({
670
+ * token: (await authClient.getAccessToken()) ?? '',
671
+ * }),
672
+ * },
673
+ * })
674
+ * ```
675
+ */
676
+ declare class MixedAuthProvider implements AuthProvider {
677
+ private readonly primary;
678
+ private readonly anonymousScopes;
679
+ private readonly anonymousPrefix;
680
+ private anonymousCounter;
681
+ constructor(options: MixedAuthProviderOptions);
682
+ authenticate(token: string): Promise<AuthContext>;
683
+ }
684
+
588
685
  /**
589
686
  * In-memory server store for testing and quick prototyping.
590
687
  * Not suitable for production — data does not survive process restart.
@@ -782,6 +879,49 @@ declare class NoAuthProvider implements AuthProvider {
782
879
  authenticate(_token: string): Promise<AuthContext>;
783
880
  }
784
881
 
882
+ /**
883
+ * Server-side awareness relay. Broadcasts ephemeral awareness states
884
+ * (cursor positions, user presence) between connected clients.
885
+ *
886
+ * Awareness data is never persisted. The relay simply forwards awareness
887
+ * updates from one client to all other connected clients, and broadcasts
888
+ * removal notifications when a client disconnects.
889
+ */
890
+ declare class AwarenessRelay {
891
+ private readonly clients;
892
+ /**
893
+ * Register a client for awareness broadcasting.
894
+ *
895
+ * @param sessionId - Unique session identifier
896
+ * @param clientId - Client-assigned awareness ID
897
+ * @param transport - Transport for sending messages to this client
898
+ */
899
+ addClient(sessionId: string, clientId: number, transport: ServerTransport): void;
900
+ /**
901
+ * Remove a client and broadcast its removal to all remaining clients.
902
+ *
903
+ * @param sessionId - Session ID of the disconnecting client
904
+ */
905
+ removeClient(sessionId: string): void;
906
+ /**
907
+ * Handle an incoming awareness update from a client.
908
+ * Stores the state and relays to all other connected clients.
909
+ *
910
+ * @param sessionId - Session ID of the sending client
911
+ * @param message - The awareness update message
912
+ */
913
+ handleUpdate(sessionId: string, message: AwarenessUpdateMessage): void;
914
+ /**
915
+ * Get the number of registered awareness clients.
916
+ */
917
+ getClientCount(): number;
918
+ /**
919
+ * Remove all clients and clear all state.
920
+ */
921
+ clear(): void;
922
+ private broadcastExcept;
923
+ }
924
+
785
925
  /**
786
926
  * Factory function to create a KoraSyncServer.
787
927
  *
@@ -799,4 +939,4 @@ declare class NoAuthProvider implements AuthProvider {
799
939
  */
800
940
  declare function createKoraServer(config: KoraSyncServerConfig): KoraSyncServer;
801
941
 
802
- export { type AuthContext, type AuthProvider, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type MaterializedRecord, MemoryServerStore, NoAuthProvider, PostgresServerStore, type ProductionServer, type ProductionServerConfig, type RelayCallback, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createKoraServer, createPostgresServerStore, createProductionServer, createSqliteServerStore };
942
+ export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, PostgresServerStore, type ProductionServer, type ProductionServerConfig, type RelayCallback, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createKoraServer, createPostgresServerStore, createProductionServer, createSqliteServerStore };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { SchemaDefinition, KoraEventEmitter, Operation, VersionVector } from '@korajs/core';
2
2
  import * as _korajs_sync from '@korajs/sync';
3
- import { SyncStore, MessageSerializer, SyncMessage, ApplyResult } from '@korajs/sync';
3
+ import { SyncStore, MessageSerializer, SyncMessage, AwarenessUpdateMessage, ApplyResult } from '@korajs/sync';
4
4
  import { S as ServerTransport, a as ServerMessageHandler, b as ServerCloseHandler, c as ServerErrorHandler } from './server-transport-CU1BLWgr.js';
5
5
  import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
6
6
  import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
@@ -252,6 +252,10 @@ type SessionState = 'connected' | 'authenticated' | 'syncing' | 'streaming' | 'c
252
252
  * Callback invoked when a session has new operations to relay to other sessions.
253
253
  */
254
254
  type RelayCallback = (sourceSessionId: string, operations: Operation[]) => void;
255
+ /**
256
+ * Callback invoked when a session receives an awareness update to relay to other sessions.
257
+ */
258
+ type AwarenessRelayCallback = (sourceSessionId: string, message: AwarenessUpdateMessage) => void;
255
259
  /**
256
260
  * Options for creating a ClientSession.
257
261
  */
@@ -274,6 +278,8 @@ interface ClientSessionOptions {
274
278
  schemaVersion?: number;
275
279
  /** Called when this session has operations to relay to other sessions */
276
280
  onRelay?: RelayCallback;
281
+ /** Called when this session receives an awareness update to broadcast */
282
+ onAwarenessUpdate?: AwarenessRelayCallback;
277
283
  /** Called when this session closes */
278
284
  onClose?: (sessionId: string) => void;
279
285
  }
@@ -304,6 +310,7 @@ declare class ClientSession {
304
310
  private readonly batchSize;
305
311
  private readonly schemaVersion;
306
312
  private readonly onRelay;
313
+ private readonly onAwarenessUpdate;
307
314
  private readonly onClose;
308
315
  constructor(options: ClientSessionOptions);
309
316
  /**
@@ -324,10 +331,16 @@ declare class ClientSession {
324
331
  getClientNodeId(): string | null;
325
332
  getAuthContext(): AuthContext | null;
326
333
  isStreaming(): boolean;
334
+ /**
335
+ * Get the transport for this session.
336
+ * Used by the awareness relay to send messages to this client.
337
+ */
338
+ getTransport(): ServerTransport;
327
339
  private handleMessage;
328
340
  private handleHandshake;
329
341
  private handleOperationBatch;
330
342
  private sendDelta;
343
+ private handleAwarenessUpdate;
331
344
  private sendError;
332
345
  private setSerializerWireFormat;
333
346
  private handleTransportClose;
@@ -372,6 +385,7 @@ declare class KoraSyncServer {
372
385
  private readonly port;
373
386
  private readonly host;
374
387
  private readonly path;
388
+ private readonly awarenessRelay;
375
389
  private readonly sessions;
376
390
  private readonly httpClients;
377
391
  private readonly httpSessionToClient;
@@ -412,6 +426,7 @@ declare class KoraSyncServer {
412
426
  getConnectionCount(): number;
413
427
  private handleRelay;
414
428
  private handleSessionClose;
429
+ private handleAwarenessRelay;
415
430
  private getOrCreateHttpClient;
416
431
  }
417
432
 
@@ -585,6 +600,88 @@ declare class KoraAuthProvider implements AuthProvider {
585
600
  authenticate(token: string): Promise<AuthContext | null>;
586
601
  }
587
602
 
603
+ /**
604
+ * Configuration for creating a MixedAuthProvider.
605
+ */
606
+ interface MixedAuthProviderOptions {
607
+ /**
608
+ * Primary auth provider that validates tokens from authenticated users.
609
+ * Typically a `KoraAuthProvider`, `TokenAuthProvider`, or the result of
610
+ * `authRoutes.toSyncAuthProvider()`.
611
+ */
612
+ primary: AuthProvider;
613
+ /**
614
+ * Scopes to apply to anonymous connections.
615
+ * Each key is a collection name; the value is a filter object
616
+ * (use `{}` for unrestricted access to that collection).
617
+ *
618
+ * @example
619
+ * ```typescript
620
+ * // Anonymous users can only sync the 'responses' collection
621
+ * anonymousScopes: { responses: {} }
622
+ *
623
+ * // Anonymous users can read published forms
624
+ * anonymousScopes: { forms: { status: 'published' } }
625
+ * ```
626
+ */
627
+ anonymousScopes: Record<string, Record<string, unknown>>;
628
+ /**
629
+ * Prefix for generated anonymous user IDs.
630
+ * A unique suffix is appended to each connection.
631
+ * @default 'anon'
632
+ */
633
+ anonymousPrefix?: string;
634
+ }
635
+ /**
636
+ * Auth provider that supports both authenticated and anonymous connections.
637
+ *
638
+ * When a client connects with a valid token, the primary auth provider
639
+ * handles authentication normally. When a client connects without a token
640
+ * (or with an invalid one), the connection is accepted as anonymous with
641
+ * restricted sync scopes.
642
+ *
643
+ * This is the recommended pattern for apps that need public data access
644
+ * alongside authenticated users — for example, a form builder where
645
+ * authenticated users create forms but anyone can submit responses.
646
+ *
647
+ * @example
648
+ * ```typescript
649
+ * import { MixedAuthProvider, KoraAuthProvider } from '@korajs/server'
650
+ *
651
+ * const auth = new MixedAuthProvider({
652
+ * primary: authRoutes.toSyncAuthProvider(),
653
+ * anonymousScopes: {
654
+ * // Anonymous users can only sync the 'responses' collection
655
+ * responses: {},
656
+ * },
657
+ * })
658
+ *
659
+ * const server = new KoraSyncServer({ store, auth })
660
+ * ```
661
+ *
662
+ * @example
663
+ * ```typescript
664
+ * // On the client, return an empty token for unauthenticated users:
665
+ * const app = createApp({
666
+ * schema,
667
+ * sync: {
668
+ * url: 'wss://my-server.com/kora',
669
+ * auth: async () => ({
670
+ * token: (await authClient.getAccessToken()) ?? '',
671
+ * }),
672
+ * },
673
+ * })
674
+ * ```
675
+ */
676
+ declare class MixedAuthProvider implements AuthProvider {
677
+ private readonly primary;
678
+ private readonly anonymousScopes;
679
+ private readonly anonymousPrefix;
680
+ private anonymousCounter;
681
+ constructor(options: MixedAuthProviderOptions);
682
+ authenticate(token: string): Promise<AuthContext>;
683
+ }
684
+
588
685
  /**
589
686
  * In-memory server store for testing and quick prototyping.
590
687
  * Not suitable for production — data does not survive process restart.
@@ -782,6 +879,49 @@ declare class NoAuthProvider implements AuthProvider {
782
879
  authenticate(_token: string): Promise<AuthContext>;
783
880
  }
784
881
 
882
+ /**
883
+ * Server-side awareness relay. Broadcasts ephemeral awareness states
884
+ * (cursor positions, user presence) between connected clients.
885
+ *
886
+ * Awareness data is never persisted. The relay simply forwards awareness
887
+ * updates from one client to all other connected clients, and broadcasts
888
+ * removal notifications when a client disconnects.
889
+ */
890
+ declare class AwarenessRelay {
891
+ private readonly clients;
892
+ /**
893
+ * Register a client for awareness broadcasting.
894
+ *
895
+ * @param sessionId - Unique session identifier
896
+ * @param clientId - Client-assigned awareness ID
897
+ * @param transport - Transport for sending messages to this client
898
+ */
899
+ addClient(sessionId: string, clientId: number, transport: ServerTransport): void;
900
+ /**
901
+ * Remove a client and broadcast its removal to all remaining clients.
902
+ *
903
+ * @param sessionId - Session ID of the disconnecting client
904
+ */
905
+ removeClient(sessionId: string): void;
906
+ /**
907
+ * Handle an incoming awareness update from a client.
908
+ * Stores the state and relays to all other connected clients.
909
+ *
910
+ * @param sessionId - Session ID of the sending client
911
+ * @param message - The awareness update message
912
+ */
913
+ handleUpdate(sessionId: string, message: AwarenessUpdateMessage): void;
914
+ /**
915
+ * Get the number of registered awareness clients.
916
+ */
917
+ getClientCount(): number;
918
+ /**
919
+ * Remove all clients and clear all state.
920
+ */
921
+ clear(): void;
922
+ private broadcastExcept;
923
+ }
924
+
785
925
  /**
786
926
  * Factory function to create a KoraSyncServer.
787
927
  *
@@ -799,4 +939,4 @@ declare class NoAuthProvider implements AuthProvider {
799
939
  */
800
940
  declare function createKoraServer(config: KoraSyncServerConfig): KoraSyncServer;
801
941
 
802
- export { type AuthContext, type AuthProvider, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type MaterializedRecord, MemoryServerStore, NoAuthProvider, PostgresServerStore, type ProductionServer, type ProductionServerConfig, type RelayCallback, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createKoraServer, createPostgresServerStore, createProductionServer, createSqliteServerStore };
942
+ export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, PostgresServerStore, type ProductionServer, type ProductionServerConfig, type RelayCallback, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createKoraServer, createPostgresServerStore, createProductionServer, createSqliteServerStore };