@korajs/server 0.1.7 → 0.3.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
@@ -420,6 +420,100 @@ declare class TokenAuthProvider implements AuthProvider {
420
420
  authenticate(token: string): Promise<AuthContext | null>;
421
421
  }
422
422
 
423
+ /**
424
+ * Validates a Kora auth JWT and returns identity claims.
425
+ * This interface matches the signature of TokenManager.validateToken()
426
+ * from @korajs/auth without requiring a direct import.
427
+ */
428
+ interface TokenValidator {
429
+ validateToken(token: string): {
430
+ sub: string;
431
+ dev: string;
432
+ type: string;
433
+ } | null;
434
+ }
435
+ /**
436
+ * Looks up a user by ID. Returns null if the user doesn't exist.
437
+ * This interface matches InMemoryUserStore.findById() from @korajs/auth
438
+ * without requiring a direct import.
439
+ */
440
+ interface UserLookup {
441
+ findById(userId: string): Promise<{
442
+ id: string;
443
+ email: string;
444
+ name: string;
445
+ } | null>;
446
+ }
447
+ /**
448
+ * Optional device touch callback for updating last-seen timestamps.
449
+ */
450
+ interface DeviceToucher {
451
+ touchDevice(deviceId: string): Promise<void>;
452
+ }
453
+ /**
454
+ * Configuration for creating a KoraAuthProvider.
455
+ */
456
+ interface KoraAuthProviderOptions {
457
+ /**
458
+ * Token validator that verifies JWT signatures and returns claims.
459
+ * Typically a `TokenManager` instance from `@korajs/auth/server`.
460
+ */
461
+ tokenValidator: TokenValidator;
462
+ /**
463
+ * User lookup for verifying the user still exists.
464
+ * Typically an `InMemoryUserStore` instance from `@korajs/auth/server`.
465
+ */
466
+ userLookup: UserLookup;
467
+ /**
468
+ * Optional device tracker for updating last-seen timestamps.
469
+ * Typically the same `InMemoryUserStore` if it implements `touchDevice`.
470
+ */
471
+ deviceTracker?: DeviceToucher;
472
+ /**
473
+ * Optional scope resolver. Called with the user ID to determine
474
+ * which collections/records the user can sync.
475
+ */
476
+ resolveScopes?: (userId: string) => Promise<Record<string, Record<string, unknown>>>;
477
+ }
478
+ /**
479
+ * Auth provider that bridges `@korajs/auth` token management with the
480
+ * sync server's authentication layer.
481
+ *
482
+ * Validates access tokens issued by `@korajs/auth`'s `TokenManager`,
483
+ * verifies the user still exists, and optionally computes per-user
484
+ * sync scopes. This is the recommended way to connect @korajs/auth
485
+ * to @korajs/server.
486
+ *
487
+ * @example
488
+ * ```typescript
489
+ * import { TokenManager, InMemoryUserStore } from '@korajs/auth/server'
490
+ * import { KoraAuthProvider, KoraSyncServer } from '@korajs/server'
491
+ *
492
+ * const tokenManager = new TokenManager({ secret: 'my-secret' })
493
+ * const userStore = new InMemoryUserStore()
494
+ *
495
+ * const auth = new KoraAuthProvider({
496
+ * tokenValidator: tokenManager,
497
+ * userLookup: userStore,
498
+ * deviceTracker: userStore,
499
+ * resolveScopes: async (userId) => ({
500
+ * forms: { userId },
501
+ * responses: { formOwnerId: userId },
502
+ * }),
503
+ * })
504
+ *
505
+ * const server = new KoraSyncServer({ store, auth })
506
+ * ```
507
+ */
508
+ declare class KoraAuthProvider implements AuthProvider {
509
+ private readonly tokenValidator;
510
+ private readonly userLookup;
511
+ private readonly deviceTracker;
512
+ private readonly resolveScopes;
513
+ constructor(options: KoraAuthProviderOptions);
514
+ authenticate(token: string): Promise<AuthContext | null>;
515
+ }
516
+
423
517
  /**
424
518
  * In-memory server store for testing and quick prototyping.
425
519
  * Not suitable for production — data does not survive process restart.
@@ -557,4 +651,4 @@ declare class NoAuthProvider implements AuthProvider {
557
651
  */
558
652
  declare function createKoraServer(config: KoraSyncServerConfig): KoraSyncServer;
559
653
 
560
- export { type AuthContext, type AuthProvider, ClientSession, type ClientSessionOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraSyncServer, type KoraSyncServerConfig, 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 };
654
+ export { type AuthContext, type AuthProvider, ClientSession, type ClientSessionOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, 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 };
package/dist/index.d.ts CHANGED
@@ -420,6 +420,100 @@ declare class TokenAuthProvider implements AuthProvider {
420
420
  authenticate(token: string): Promise<AuthContext | null>;
421
421
  }
422
422
 
423
+ /**
424
+ * Validates a Kora auth JWT and returns identity claims.
425
+ * This interface matches the signature of TokenManager.validateToken()
426
+ * from @korajs/auth without requiring a direct import.
427
+ */
428
+ interface TokenValidator {
429
+ validateToken(token: string): {
430
+ sub: string;
431
+ dev: string;
432
+ type: string;
433
+ } | null;
434
+ }
435
+ /**
436
+ * Looks up a user by ID. Returns null if the user doesn't exist.
437
+ * This interface matches InMemoryUserStore.findById() from @korajs/auth
438
+ * without requiring a direct import.
439
+ */
440
+ interface UserLookup {
441
+ findById(userId: string): Promise<{
442
+ id: string;
443
+ email: string;
444
+ name: string;
445
+ } | null>;
446
+ }
447
+ /**
448
+ * Optional device touch callback for updating last-seen timestamps.
449
+ */
450
+ interface DeviceToucher {
451
+ touchDevice(deviceId: string): Promise<void>;
452
+ }
453
+ /**
454
+ * Configuration for creating a KoraAuthProvider.
455
+ */
456
+ interface KoraAuthProviderOptions {
457
+ /**
458
+ * Token validator that verifies JWT signatures and returns claims.
459
+ * Typically a `TokenManager` instance from `@korajs/auth/server`.
460
+ */
461
+ tokenValidator: TokenValidator;
462
+ /**
463
+ * User lookup for verifying the user still exists.
464
+ * Typically an `InMemoryUserStore` instance from `@korajs/auth/server`.
465
+ */
466
+ userLookup: UserLookup;
467
+ /**
468
+ * Optional device tracker for updating last-seen timestamps.
469
+ * Typically the same `InMemoryUserStore` if it implements `touchDevice`.
470
+ */
471
+ deviceTracker?: DeviceToucher;
472
+ /**
473
+ * Optional scope resolver. Called with the user ID to determine
474
+ * which collections/records the user can sync.
475
+ */
476
+ resolveScopes?: (userId: string) => Promise<Record<string, Record<string, unknown>>>;
477
+ }
478
+ /**
479
+ * Auth provider that bridges `@korajs/auth` token management with the
480
+ * sync server's authentication layer.
481
+ *
482
+ * Validates access tokens issued by `@korajs/auth`'s `TokenManager`,
483
+ * verifies the user still exists, and optionally computes per-user
484
+ * sync scopes. This is the recommended way to connect @korajs/auth
485
+ * to @korajs/server.
486
+ *
487
+ * @example
488
+ * ```typescript
489
+ * import { TokenManager, InMemoryUserStore } from '@korajs/auth/server'
490
+ * import { KoraAuthProvider, KoraSyncServer } from '@korajs/server'
491
+ *
492
+ * const tokenManager = new TokenManager({ secret: 'my-secret' })
493
+ * const userStore = new InMemoryUserStore()
494
+ *
495
+ * const auth = new KoraAuthProvider({
496
+ * tokenValidator: tokenManager,
497
+ * userLookup: userStore,
498
+ * deviceTracker: userStore,
499
+ * resolveScopes: async (userId) => ({
500
+ * forms: { userId },
501
+ * responses: { formOwnerId: userId },
502
+ * }),
503
+ * })
504
+ *
505
+ * const server = new KoraSyncServer({ store, auth })
506
+ * ```
507
+ */
508
+ declare class KoraAuthProvider implements AuthProvider {
509
+ private readonly tokenValidator;
510
+ private readonly userLookup;
511
+ private readonly deviceTracker;
512
+ private readonly resolveScopes;
513
+ constructor(options: KoraAuthProviderOptions);
514
+ authenticate(token: string): Promise<AuthContext | null>;
515
+ }
516
+
423
517
  /**
424
518
  * In-memory server store for testing and quick prototyping.
425
519
  * Not suitable for production — data does not survive process restart.
@@ -557,4 +651,4 @@ declare class NoAuthProvider implements AuthProvider {
557
651
  */
558
652
  declare function createKoraServer(config: KoraSyncServerConfig): KoraSyncServer;
559
653
 
560
- export { type AuthContext, type AuthProvider, ClientSession, type ClientSessionOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraSyncServer, type KoraSyncServerConfig, 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 };
654
+ export { type AuthContext, type AuthProvider, ClientSession, type ClientSessionOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, 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 };
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ import { generateUUIDv7 as generateUUIDv72 } from "@korajs/core";
60
60
  import { and, asc, between, count, eq, sql } from "drizzle-orm";
61
61
 
62
62
  // src/store/drizzle-pg-schema.ts
63
- import { index, integer, pgTable, text } from "drizzle-orm/pg-core";
63
+ import { bigint, index, integer, pgTable, text } from "drizzle-orm/pg-core";
64
64
  var pgOperations = pgTable(
65
65
  "operations",
66
66
  {
@@ -73,14 +73,14 @@ var pgOperations = pgTable(
73
73
  // JSON-serialized, null for deletes
74
74
  previousData: text("previous_data"),
75
75
  // JSON-serialized, null for insert/delete
76
- wallTime: integer("wall_time").notNull(),
76
+ wallTime: bigint("wall_time", { mode: "number" }).notNull(),
77
77
  logical: integer("logical").notNull(),
78
78
  timestampNodeId: text("timestamp_node_id").notNull(),
79
79
  sequenceNumber: integer("sequence_number").notNull(),
80
80
  causalDeps: text("causal_deps").notNull().default("[]"),
81
81
  // JSON array of op IDs
82
82
  schemaVersion: integer("schema_version").notNull(),
83
- receivedAt: integer("received_at").notNull()
83
+ receivedAt: bigint("received_at", { mode: "number" }).notNull()
84
84
  },
85
85
  (table) => ({
86
86
  nodeSeqIdx: index("idx_pg_node_seq").on(table.nodeId, table.sequenceNumber),
@@ -91,7 +91,7 @@ var pgOperations = pgTable(
91
91
  var pgSyncState = pgTable("sync_state", {
92
92
  nodeId: text("node_id").primaryKey(),
93
93
  maxSequenceNumber: integer("max_sequence_number").notNull(),
94
- lastSeenAt: integer("last_seen_at").notNull()
94
+ lastSeenAt: bigint("last_seen_at", { mode: "number" }).notNull()
95
95
  });
96
96
 
97
97
  // src/store/postgres-server-store.ts
@@ -186,13 +186,13 @@ var PostgresServerStore = class {
186
186
  record_id TEXT NOT NULL,
187
187
  data TEXT,
188
188
  previous_data TEXT,
189
- wall_time INTEGER NOT NULL,
189
+ wall_time BIGINT NOT NULL,
190
190
  logical INTEGER NOT NULL,
191
191
  timestamp_node_id TEXT NOT NULL,
192
192
  sequence_number INTEGER NOT NULL,
193
193
  causal_deps TEXT NOT NULL DEFAULT '[]',
194
194
  schema_version INTEGER NOT NULL,
195
- received_at INTEGER NOT NULL
195
+ received_at BIGINT NOT NULL
196
196
  )
197
197
  `);
198
198
  await this.db.execute(
@@ -208,7 +208,7 @@ var PostgresServerStore = class {
208
208
  CREATE TABLE IF NOT EXISTS sync_state (
209
209
  node_id TEXT PRIMARY KEY,
210
210
  max_sequence_number INTEGER NOT NULL,
211
- last_seen_at INTEGER NOT NULL
211
+ last_seen_at BIGINT NOT NULL
212
212
  )
213
213
  `);
214
214
  }
@@ -1138,6 +1138,46 @@ var TokenAuthProvider = class {
1138
1138
  }
1139
1139
  };
1140
1140
 
1141
+ // src/auth/kora-auth-provider.ts
1142
+ var KoraAuthProvider = class {
1143
+ tokenValidator;
1144
+ userLookup;
1145
+ deviceTracker;
1146
+ resolveScopes;
1147
+ constructor(options) {
1148
+ this.tokenValidator = options.tokenValidator;
1149
+ this.userLookup = options.userLookup;
1150
+ this.deviceTracker = options.deviceTracker;
1151
+ this.resolveScopes = options.resolveScopes;
1152
+ }
1153
+ async authenticate(token) {
1154
+ const payload = this.tokenValidator.validateToken(token);
1155
+ if (payload === null) {
1156
+ return null;
1157
+ }
1158
+ if (payload.type !== "access") {
1159
+ return null;
1160
+ }
1161
+ const user = await this.userLookup.findById(payload.sub);
1162
+ if (user === null) {
1163
+ return null;
1164
+ }
1165
+ if (this.deviceTracker) {
1166
+ await this.deviceTracker.touchDevice(payload.dev);
1167
+ }
1168
+ const scopes = this.resolveScopes ? await this.resolveScopes(payload.sub) : void 0;
1169
+ return {
1170
+ userId: payload.sub,
1171
+ scopes,
1172
+ metadata: {
1173
+ deviceId: payload.dev,
1174
+ email: user.email,
1175
+ name: user.name
1176
+ }
1177
+ };
1178
+ }
1179
+ };
1180
+
1141
1181
  // src/server/create-server.ts
1142
1182
  function createKoraServer(config) {
1143
1183
  return new KoraSyncServer(config);
@@ -1178,6 +1218,11 @@ function createProductionServer(config) {
1178
1218
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
1179
1219
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
1180
1220
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
1221
+ if (url.pathname === "/health") {
1222
+ res.writeHead(200, { "Content-Type": "application/json" });
1223
+ res.end(JSON.stringify({ status: "ok", timestamp: Date.now() }));
1224
+ return;
1225
+ }
1181
1226
  let filePath = join(distDir, url.pathname);
1182
1227
  if (!extname(filePath)) {
1183
1228
  const indexPath = join(filePath, "index.html");
@@ -1240,6 +1285,7 @@ function createProductionServer(config) {
1240
1285
  export {
1241
1286
  ClientSession,
1242
1287
  HttpServerTransport,
1288
+ KoraAuthProvider,
1243
1289
  KoraSyncServer,
1244
1290
  MemoryServerStore,
1245
1291
  NoAuthProvider,