@korajs/server 1.0.0-beta.0 → 1.0.0-beta.1

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
- import { SchemaDefinition, KoraEventEmitter, Operation, VersionVector } from '@korajs/core';
1
+ import { SchemaDefinition, KoraEventEmitter, Operation, BlobRef, VersionVector } from '@korajs/core';
2
2
  import * as _korajs_sync from '@korajs/sync';
3
- import { SyncStore, MessageSerializer, SyncMessage, AwarenessUpdateMessage, YjsDocUpdateMessage, ApplyResult } from '@korajs/sync';
3
+ import { SyncStore, MessageSerializer, SyncMessage, AwarenessUpdateMessage, YjsDocUpdateMessage, BlobChunkRequestMessage, BlobChunkResponseMessage, 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';
@@ -286,6 +286,23 @@ interface KoraSyncServerConfig {
286
286
  metricsCollector?: ServerMetricsCollector;
287
287
  /** Enable built-in dashboard and metrics endpoints. Defaults to true. */
288
288
  enableDashboard?: boolean;
289
+ /**
290
+ * Resolve a blob chunk by content hash from server-side storage. Optional.
291
+ * When provided, the server answers blob chunk requests directly from its own
292
+ * store; when absent, the server relays chunk requests among connected peers
293
+ * without storing any blob bytes itself.
294
+ */
295
+ resolveBlobChunk?: (hash: string) => Promise<Uint8Array | null>;
296
+ /**
297
+ * Persist a client-uploaded blob chunk (or manifest) centrally, keyed by its
298
+ * content hash. Optional. When provided, the server advertises central blob
299
+ * storage at handshake, clients upload the bytes behind their `blob` fields,
300
+ * and those bytes remain available to other devices after the authoring device
301
+ * goes offline. Pair with `resolveBlobChunk` (both backed by the same store)
302
+ * so uploaded blobs can then be served. `toServerBlobCallbacks` in
303
+ * `@korajs/store` derives both from a `ContentAddressedBlobStore`.
304
+ */
305
+ persistBlobChunk?: (hash: string, bytes: Uint8Array) => Promise<void> | void;
289
306
  }
290
307
  /**
291
308
  * Request envelope for the server-side HTTP sync endpoint.
@@ -439,6 +456,20 @@ type AwarenessRelayCallback = (sourceSessionId: string, message: AwarenessUpdate
439
456
  * Callback invoked when a session receives a Yjs doc channel update to relay.
440
457
  */
441
458
  type YjsDocRelayCallback = (sourceSessionId: string, message: YjsDocUpdateMessage) => void;
459
+ /**
460
+ * Callback invoked when a session receives a blob chunk request to route.
461
+ */
462
+ type BlobChunkRequestCallback = (sourceSessionId: string, message: BlobChunkRequestMessage) => void;
463
+ /**
464
+ * Callback invoked when a session receives a blob chunk response to route back.
465
+ */
466
+ type BlobChunkResponseCallback = (sourceSessionId: string, message: BlobChunkResponseMessage) => void;
467
+ /**
468
+ * Persist a blob chunk (or manifest) uploaded by a client, keyed by its content
469
+ * hash. Provided by the server operator; enables central blob storage so bytes
470
+ * survive the authoring device going offline.
471
+ */
472
+ type PersistBlobChunk = (hash: string, bytes: Uint8Array) => Promise<void> | void;
442
473
  /**
443
474
  * Options for creating a ClientSession.
444
475
  */
@@ -470,6 +501,12 @@ interface ClientSessionOptions {
470
501
  onAwarenessUpdate?: AwarenessRelayCallback;
471
502
  /** Called when this session receives a Yjs doc channel update to broadcast */
472
503
  onYjsDocUpdate?: YjsDocRelayCallback;
504
+ /** Called when this session receives a blob chunk request to route */
505
+ onBlobChunkRequest?: BlobChunkRequestCallback;
506
+ /** Called when this session receives a blob chunk response to route back */
507
+ onBlobChunkResponse?: BlobChunkResponseCallback;
508
+ /** Persist a client-uploaded blob chunk centrally (keyed by content hash) */
509
+ persistBlobChunk?: PersistBlobChunk;
473
510
  /** Called when this session closes */
474
511
  onClose?: (sessionId: string) => void;
475
512
  /** Maximum serialized operation size in bytes. Defaults to 256 KiB. */
@@ -509,6 +546,9 @@ declare class ClientSession {
509
546
  private readonly onRelay;
510
547
  private readonly onAwarenessUpdate;
511
548
  private readonly onYjsDocUpdate;
549
+ private readonly onBlobChunkRequest;
550
+ private readonly onBlobChunkResponse;
551
+ private readonly persistBlobChunk;
512
552
  private readonly onClose;
513
553
  private readonly maxOperationBytes;
514
554
  private readonly maxOpsPerMinute;
@@ -542,6 +582,12 @@ declare class ClientSession {
542
582
  private sendToClient;
543
583
  private enqueueMessage;
544
584
  private handleMessageAsync;
585
+ /**
586
+ * Persist a client-uploaded blob chunk (or manifest) centrally. The bytes are
587
+ * verified to hash to the declared hash before storing, so a corrupt or
588
+ * mislabeled upload is rejected rather than served later as trusted content.
589
+ */
590
+ private handleBlobChunkPush;
545
591
  private handleMessageFailure;
546
592
  private handleHandshake;
547
593
  private handleOperationBatch;
@@ -554,6 +600,18 @@ declare class ClientSession {
554
600
  private handleTransportClose;
555
601
  }
556
602
 
603
+ interface ApplyServerOperationResult {
604
+ /** Result of applying the primary operation */
605
+ result: ApplyResult;
606
+ /** Primary op when applied, plus any server-generated side-effect operations */
607
+ appliedOperations: Operation[];
608
+ /** Rejection reason when the operation was not applied */
609
+ rejection?: {
610
+ code: string;
611
+ message: string;
612
+ };
613
+ }
614
+
557
615
  /**
558
616
  * Minimal interface for a ws.WebSocketServer instance.
559
617
  * Allows dependency injection for testing without importing ws directly.
@@ -598,6 +656,8 @@ declare class KoraSyncServer {
598
656
  private readonly metrics;
599
657
  private readonly awarenessRelay;
600
658
  private readonly yjsDocRelay;
659
+ private readonly blobChunkRelay;
660
+ private readonly persistBlobChunk;
601
661
  private readonly sessions;
602
662
  private readonly httpClients;
603
663
  private readonly httpSessionToClient;
@@ -646,6 +706,32 @@ declare class KoraSyncServer {
646
706
  * Get the current server status.
647
707
  */
648
708
  getStatus(): Promise<ServerStatus>;
709
+ /**
710
+ * Apply a server-originated operation (for example one created by a custom
711
+ * HTTP route) through the same validated pipeline that incoming client
712
+ * operations use — Tier 2 constraints, referential integrity, and cascade
713
+ * side effects — then relay every applied operation to connected clients.
714
+ *
715
+ * Because the operation did not come from a client session, it is relayed to
716
+ * ALL sessions (there is no source session to exclude). Each session still
717
+ * applies its own per-scope visibility filter in `relayOperations`, so a
718
+ * client only receives the operation if it falls within that client's scope.
719
+ *
720
+ * @param op - A fully-formed, server-originated operation to apply
721
+ * @returns The apply result, including any server-generated side-effect ops
722
+ */
723
+ applyLocalOperation(op: Operation): Promise<ApplyServerOperationResult>;
724
+ /**
725
+ * Collect every blob reference still reachable from live records on the server.
726
+ *
727
+ * This is the live set for garbage-collecting the server's central blob store:
728
+ * pass the result to `collectBlobGarbage(blobStore, liveRefs)` from
729
+ * `@korajs/store` to reclaim bytes no record references any more. Only
730
+ * collections that declare a `blob` field are scanned.
731
+ *
732
+ * @returns Every live blob reference across all collections
733
+ */
734
+ getLiveBlobRefs(): Promise<BlobRef[]>;
649
735
  /**
650
736
  * Get the number of currently connected clients.
651
737
  */
@@ -657,6 +743,65 @@ declare class KoraSyncServer {
657
743
  private getOrCreateHttpClient;
658
744
  }
659
745
 
746
+ /**
747
+ * Per-collection scope map from auth context.
748
+ */
749
+ type ScopeMap = Record<string, Record<string, unknown>>;
750
+
751
+ /**
752
+ * A mutation a custom HTTP route asks the server to apply on the data plane.
753
+ *
754
+ * `recordId` is optional for inserts (a UUID v7 is generated when omitted) and
755
+ * required for updates and deletes. For updates, `data` holds only the changed
756
+ * fields; the server reads the current record to capture the previous values so
757
+ * the change three-way merges correctly with concurrent client edits.
758
+ */
759
+ interface RouteMutation {
760
+ collection: string;
761
+ type: 'insert' | 'update' | 'delete';
762
+ recordId?: string;
763
+ data?: Record<string, unknown> | null;
764
+ }
765
+ /**
766
+ * Scope the route derived from the request's authenticated actor. When present,
767
+ * it is enforced exactly like a sync session's scope: `apply()` rejects a
768
+ * mutation whose resulting record falls outside the scope, and `query()` /
769
+ * `findById()` only return records inside it. Omit it for genuinely public
770
+ * routes (for example a signup endpoint) where no per-actor isolation applies.
771
+ */
772
+ interface RouteScopeOptions {
773
+ scope?: ScopeMap;
774
+ }
775
+ /** Result of {@link ProductionHttpRouteContext.apply}. */
776
+ type RouteApplyResult = {
777
+ ok: true;
778
+ operation: Operation;
779
+ record: MaterializedRecord | null;
780
+ } | {
781
+ ok: false;
782
+ code: string;
783
+ message: string;
784
+ };
785
+ /**
786
+ * Scoped, validated data-plane access handed to custom HTTP route handlers.
787
+ *
788
+ * Every mutation goes through the same pipeline as sync (Tier 2 constraints,
789
+ * referential integrity, materialization, and fan-out to connected clients), so
790
+ * REST endpoints cannot accidentally bypass validation, constraints, or tenant
791
+ * isolation the way hand-rolled store writes can.
792
+ */
793
+ interface ProductionHttpRouteContext {
794
+ /**
795
+ * Apply a mutation through the validated server pipeline and relay the
796
+ * resulting operation(s) to connected clients.
797
+ */
798
+ apply(mutation: RouteMutation, options?: RouteScopeOptions): Promise<RouteApplyResult>;
799
+ /** Read materialized records from a collection, optionally scoped. */
800
+ query(collection: string, options?: CollectionQueryOptions & RouteScopeOptions): Promise<MaterializedRecord[]>;
801
+ /** Read a single materialized record by id, optionally scoped. */
802
+ findById(collection: string, id: string, options?: RouteScopeOptions): Promise<MaterializedRecord | null>;
803
+ }
804
+
660
805
  /**
661
806
  * Configuration for the production server that serves both
662
807
  * static files and WebSocket sync on a single port.
@@ -704,6 +849,13 @@ interface ProductionHttpRouteRequest {
704
849
  headers?: Record<string, string | string[] | undefined>;
705
850
  query?: Record<string, string | string[] | undefined>;
706
851
  ip?: string;
852
+ /**
853
+ * Scoped, validated data-plane access. Use `kora.apply()` to mutate through
854
+ * the same pipeline as sync (constraints, referential integrity, fan-out) and
855
+ * `kora.query()` / `kora.findById()` to read materialized state, instead of
856
+ * writing to the store directly and bypassing those guarantees.
857
+ */
858
+ kora: ProductionHttpRouteContext;
707
859
  }
708
860
  interface ProductionHttpRouteResponse {
709
861
  status: number;
@@ -1232,4 +1384,4 @@ declare class AwarenessRelay {
1232
1384
  */
1233
1385
  declare function createKoraServer(config: KoraSyncServerConfig): KoraSyncServer;
1234
1386
 
1235
- export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, type ClientMetrics, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type LogEntry, type LogLevel, type Logger, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, PostgresServerStore, type ProductionHttpRoute, type ProductionHttpRouteRequest, type ProductionHttpRouteResponse, type ProductionServer, type ProductionServerConfig, type RelayCallback, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, ServerMetricsCollector, type ServerMetricsSnapshot, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createDefaultLogger, createJsonLogger, createKoraServer, createPostgresServerStore, createPrettyLogger, createProductionServer, createSilentLogger, createSqliteServerStore };
1387
+ export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, type ClientMetrics, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type LogEntry, type LogLevel, type Logger, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, PostgresServerStore, type ProductionHttpRoute, type ProductionHttpRouteContext, type ProductionHttpRouteRequest, type ProductionHttpRouteResponse, type ProductionServer, type ProductionServerConfig, type RelayCallback, type RouteApplyResult, type RouteMutation, type RouteScopeOptions, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, ServerMetricsCollector, type ServerMetricsSnapshot, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createDefaultLogger, createJsonLogger, createKoraServer, createPostgresServerStore, createPrettyLogger, createProductionServer, createSilentLogger, createSqliteServerStore };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { SchemaDefinition, KoraEventEmitter, Operation, VersionVector } from '@korajs/core';
1
+ import { SchemaDefinition, KoraEventEmitter, Operation, BlobRef, VersionVector } from '@korajs/core';
2
2
  import * as _korajs_sync from '@korajs/sync';
3
- import { SyncStore, MessageSerializer, SyncMessage, AwarenessUpdateMessage, YjsDocUpdateMessage, ApplyResult } from '@korajs/sync';
3
+ import { SyncStore, MessageSerializer, SyncMessage, AwarenessUpdateMessage, YjsDocUpdateMessage, BlobChunkRequestMessage, BlobChunkResponseMessage, 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';
@@ -286,6 +286,23 @@ interface KoraSyncServerConfig {
286
286
  metricsCollector?: ServerMetricsCollector;
287
287
  /** Enable built-in dashboard and metrics endpoints. Defaults to true. */
288
288
  enableDashboard?: boolean;
289
+ /**
290
+ * Resolve a blob chunk by content hash from server-side storage. Optional.
291
+ * When provided, the server answers blob chunk requests directly from its own
292
+ * store; when absent, the server relays chunk requests among connected peers
293
+ * without storing any blob bytes itself.
294
+ */
295
+ resolveBlobChunk?: (hash: string) => Promise<Uint8Array | null>;
296
+ /**
297
+ * Persist a client-uploaded blob chunk (or manifest) centrally, keyed by its
298
+ * content hash. Optional. When provided, the server advertises central blob
299
+ * storage at handshake, clients upload the bytes behind their `blob` fields,
300
+ * and those bytes remain available to other devices after the authoring device
301
+ * goes offline. Pair with `resolveBlobChunk` (both backed by the same store)
302
+ * so uploaded blobs can then be served. `toServerBlobCallbacks` in
303
+ * `@korajs/store` derives both from a `ContentAddressedBlobStore`.
304
+ */
305
+ persistBlobChunk?: (hash: string, bytes: Uint8Array) => Promise<void> | void;
289
306
  }
290
307
  /**
291
308
  * Request envelope for the server-side HTTP sync endpoint.
@@ -439,6 +456,20 @@ type AwarenessRelayCallback = (sourceSessionId: string, message: AwarenessUpdate
439
456
  * Callback invoked when a session receives a Yjs doc channel update to relay.
440
457
  */
441
458
  type YjsDocRelayCallback = (sourceSessionId: string, message: YjsDocUpdateMessage) => void;
459
+ /**
460
+ * Callback invoked when a session receives a blob chunk request to route.
461
+ */
462
+ type BlobChunkRequestCallback = (sourceSessionId: string, message: BlobChunkRequestMessage) => void;
463
+ /**
464
+ * Callback invoked when a session receives a blob chunk response to route back.
465
+ */
466
+ type BlobChunkResponseCallback = (sourceSessionId: string, message: BlobChunkResponseMessage) => void;
467
+ /**
468
+ * Persist a blob chunk (or manifest) uploaded by a client, keyed by its content
469
+ * hash. Provided by the server operator; enables central blob storage so bytes
470
+ * survive the authoring device going offline.
471
+ */
472
+ type PersistBlobChunk = (hash: string, bytes: Uint8Array) => Promise<void> | void;
442
473
  /**
443
474
  * Options for creating a ClientSession.
444
475
  */
@@ -470,6 +501,12 @@ interface ClientSessionOptions {
470
501
  onAwarenessUpdate?: AwarenessRelayCallback;
471
502
  /** Called when this session receives a Yjs doc channel update to broadcast */
472
503
  onYjsDocUpdate?: YjsDocRelayCallback;
504
+ /** Called when this session receives a blob chunk request to route */
505
+ onBlobChunkRequest?: BlobChunkRequestCallback;
506
+ /** Called when this session receives a blob chunk response to route back */
507
+ onBlobChunkResponse?: BlobChunkResponseCallback;
508
+ /** Persist a client-uploaded blob chunk centrally (keyed by content hash) */
509
+ persistBlobChunk?: PersistBlobChunk;
473
510
  /** Called when this session closes */
474
511
  onClose?: (sessionId: string) => void;
475
512
  /** Maximum serialized operation size in bytes. Defaults to 256 KiB. */
@@ -509,6 +546,9 @@ declare class ClientSession {
509
546
  private readonly onRelay;
510
547
  private readonly onAwarenessUpdate;
511
548
  private readonly onYjsDocUpdate;
549
+ private readonly onBlobChunkRequest;
550
+ private readonly onBlobChunkResponse;
551
+ private readonly persistBlobChunk;
512
552
  private readonly onClose;
513
553
  private readonly maxOperationBytes;
514
554
  private readonly maxOpsPerMinute;
@@ -542,6 +582,12 @@ declare class ClientSession {
542
582
  private sendToClient;
543
583
  private enqueueMessage;
544
584
  private handleMessageAsync;
585
+ /**
586
+ * Persist a client-uploaded blob chunk (or manifest) centrally. The bytes are
587
+ * verified to hash to the declared hash before storing, so a corrupt or
588
+ * mislabeled upload is rejected rather than served later as trusted content.
589
+ */
590
+ private handleBlobChunkPush;
545
591
  private handleMessageFailure;
546
592
  private handleHandshake;
547
593
  private handleOperationBatch;
@@ -554,6 +600,18 @@ declare class ClientSession {
554
600
  private handleTransportClose;
555
601
  }
556
602
 
603
+ interface ApplyServerOperationResult {
604
+ /** Result of applying the primary operation */
605
+ result: ApplyResult;
606
+ /** Primary op when applied, plus any server-generated side-effect operations */
607
+ appliedOperations: Operation[];
608
+ /** Rejection reason when the operation was not applied */
609
+ rejection?: {
610
+ code: string;
611
+ message: string;
612
+ };
613
+ }
614
+
557
615
  /**
558
616
  * Minimal interface for a ws.WebSocketServer instance.
559
617
  * Allows dependency injection for testing without importing ws directly.
@@ -598,6 +656,8 @@ declare class KoraSyncServer {
598
656
  private readonly metrics;
599
657
  private readonly awarenessRelay;
600
658
  private readonly yjsDocRelay;
659
+ private readonly blobChunkRelay;
660
+ private readonly persistBlobChunk;
601
661
  private readonly sessions;
602
662
  private readonly httpClients;
603
663
  private readonly httpSessionToClient;
@@ -646,6 +706,32 @@ declare class KoraSyncServer {
646
706
  * Get the current server status.
647
707
  */
648
708
  getStatus(): Promise<ServerStatus>;
709
+ /**
710
+ * Apply a server-originated operation (for example one created by a custom
711
+ * HTTP route) through the same validated pipeline that incoming client
712
+ * operations use — Tier 2 constraints, referential integrity, and cascade
713
+ * side effects — then relay every applied operation to connected clients.
714
+ *
715
+ * Because the operation did not come from a client session, it is relayed to
716
+ * ALL sessions (there is no source session to exclude). Each session still
717
+ * applies its own per-scope visibility filter in `relayOperations`, so a
718
+ * client only receives the operation if it falls within that client's scope.
719
+ *
720
+ * @param op - A fully-formed, server-originated operation to apply
721
+ * @returns The apply result, including any server-generated side-effect ops
722
+ */
723
+ applyLocalOperation(op: Operation): Promise<ApplyServerOperationResult>;
724
+ /**
725
+ * Collect every blob reference still reachable from live records on the server.
726
+ *
727
+ * This is the live set for garbage-collecting the server's central blob store:
728
+ * pass the result to `collectBlobGarbage(blobStore, liveRefs)` from
729
+ * `@korajs/store` to reclaim bytes no record references any more. Only
730
+ * collections that declare a `blob` field are scanned.
731
+ *
732
+ * @returns Every live blob reference across all collections
733
+ */
734
+ getLiveBlobRefs(): Promise<BlobRef[]>;
649
735
  /**
650
736
  * Get the number of currently connected clients.
651
737
  */
@@ -657,6 +743,65 @@ declare class KoraSyncServer {
657
743
  private getOrCreateHttpClient;
658
744
  }
659
745
 
746
+ /**
747
+ * Per-collection scope map from auth context.
748
+ */
749
+ type ScopeMap = Record<string, Record<string, unknown>>;
750
+
751
+ /**
752
+ * A mutation a custom HTTP route asks the server to apply on the data plane.
753
+ *
754
+ * `recordId` is optional for inserts (a UUID v7 is generated when omitted) and
755
+ * required for updates and deletes. For updates, `data` holds only the changed
756
+ * fields; the server reads the current record to capture the previous values so
757
+ * the change three-way merges correctly with concurrent client edits.
758
+ */
759
+ interface RouteMutation {
760
+ collection: string;
761
+ type: 'insert' | 'update' | 'delete';
762
+ recordId?: string;
763
+ data?: Record<string, unknown> | null;
764
+ }
765
+ /**
766
+ * Scope the route derived from the request's authenticated actor. When present,
767
+ * it is enforced exactly like a sync session's scope: `apply()` rejects a
768
+ * mutation whose resulting record falls outside the scope, and `query()` /
769
+ * `findById()` only return records inside it. Omit it for genuinely public
770
+ * routes (for example a signup endpoint) where no per-actor isolation applies.
771
+ */
772
+ interface RouteScopeOptions {
773
+ scope?: ScopeMap;
774
+ }
775
+ /** Result of {@link ProductionHttpRouteContext.apply}. */
776
+ type RouteApplyResult = {
777
+ ok: true;
778
+ operation: Operation;
779
+ record: MaterializedRecord | null;
780
+ } | {
781
+ ok: false;
782
+ code: string;
783
+ message: string;
784
+ };
785
+ /**
786
+ * Scoped, validated data-plane access handed to custom HTTP route handlers.
787
+ *
788
+ * Every mutation goes through the same pipeline as sync (Tier 2 constraints,
789
+ * referential integrity, materialization, and fan-out to connected clients), so
790
+ * REST endpoints cannot accidentally bypass validation, constraints, or tenant
791
+ * isolation the way hand-rolled store writes can.
792
+ */
793
+ interface ProductionHttpRouteContext {
794
+ /**
795
+ * Apply a mutation through the validated server pipeline and relay the
796
+ * resulting operation(s) to connected clients.
797
+ */
798
+ apply(mutation: RouteMutation, options?: RouteScopeOptions): Promise<RouteApplyResult>;
799
+ /** Read materialized records from a collection, optionally scoped. */
800
+ query(collection: string, options?: CollectionQueryOptions & RouteScopeOptions): Promise<MaterializedRecord[]>;
801
+ /** Read a single materialized record by id, optionally scoped. */
802
+ findById(collection: string, id: string, options?: RouteScopeOptions): Promise<MaterializedRecord | null>;
803
+ }
804
+
660
805
  /**
661
806
  * Configuration for the production server that serves both
662
807
  * static files and WebSocket sync on a single port.
@@ -704,6 +849,13 @@ interface ProductionHttpRouteRequest {
704
849
  headers?: Record<string, string | string[] | undefined>;
705
850
  query?: Record<string, string | string[] | undefined>;
706
851
  ip?: string;
852
+ /**
853
+ * Scoped, validated data-plane access. Use `kora.apply()` to mutate through
854
+ * the same pipeline as sync (constraints, referential integrity, fan-out) and
855
+ * `kora.query()` / `kora.findById()` to read materialized state, instead of
856
+ * writing to the store directly and bypassing those guarantees.
857
+ */
858
+ kora: ProductionHttpRouteContext;
707
859
  }
708
860
  interface ProductionHttpRouteResponse {
709
861
  status: number;
@@ -1232,4 +1384,4 @@ declare class AwarenessRelay {
1232
1384
  */
1233
1385
  declare function createKoraServer(config: KoraSyncServerConfig): KoraSyncServer;
1234
1386
 
1235
- export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, type ClientMetrics, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type LogEntry, type LogLevel, type Logger, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, PostgresServerStore, type ProductionHttpRoute, type ProductionHttpRouteRequest, type ProductionHttpRouteResponse, type ProductionServer, type ProductionServerConfig, type RelayCallback, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, ServerMetricsCollector, type ServerMetricsSnapshot, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createDefaultLogger, createJsonLogger, createKoraServer, createPostgresServerStore, createPrettyLogger, createProductionServer, createSilentLogger, createSqliteServerStore };
1387
+ export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, type ClientMetrics, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type LogEntry, type LogLevel, type Logger, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, PostgresServerStore, type ProductionHttpRoute, type ProductionHttpRouteContext, type ProductionHttpRouteRequest, type ProductionHttpRouteResponse, type ProductionServer, type ProductionServerConfig, type RelayCallback, type RouteApplyResult, type RouteMutation, type RouteScopeOptions, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, ServerMetricsCollector, type ServerMetricsSnapshot, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createDefaultLogger, createJsonLogger, createKoraServer, createPostgresServerStore, createPrettyLogger, createProductionServer, createSilentLogger, createSqliteServerStore };