@korajs/server 0.6.0 → 1.0.0-beta.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dr. Obed Ehoneah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs CHANGED
@@ -250,10 +250,11 @@ function createDefaultLogger() {
250
250
 
251
251
  // src/store/memory-server-store.ts
252
252
  init_cjs_shims();
253
- var import_core = require("@korajs/core");
253
+ var import_core2 = require("@korajs/core");
254
254
 
255
255
  // src/store/materialization.ts
256
256
  init_cjs_shims();
257
+ var import_core = require("@korajs/core");
257
258
  function fieldTypeToSql(descriptor, dialect) {
258
259
  switch (descriptor.kind) {
259
260
  case "string":
@@ -349,6 +350,52 @@ function replayOperationsForRecord(ops) {
349
350
  }
350
351
  return deleted ? null : record;
351
352
  }
353
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
354
+ var BASE64_REVERSE = new Map(
355
+ Array.from(BASE64_ALPHABET, (char, index3) => [char, index3])
356
+ );
357
+ function base64ToBytes(base64) {
358
+ const cleaned = base64.replace(/=+$/, "");
359
+ const out = new Uint8Array(Math.floor(cleaned.length * 6 / 8));
360
+ let buffer = 0;
361
+ let bits = 0;
362
+ let index3 = 0;
363
+ for (const char of cleaned) {
364
+ const value = BASE64_REVERSE.get(char);
365
+ if (value === void 0) {
366
+ throw new import_core.StorageError(`Invalid base64 character "${char}" in tagged richtext value`, {
367
+ char
368
+ });
369
+ }
370
+ buffer = buffer << 6 | value;
371
+ bits += 6;
372
+ if (bits >= 8) {
373
+ bits -= 8;
374
+ out[index3] = buffer >> bits & 255;
375
+ index3 += 1;
376
+ }
377
+ }
378
+ return out;
379
+ }
380
+ function decodeRichtextColumnValue(value) {
381
+ if (typeof value !== "object" || value === null || ArrayBuffer.isView(value)) {
382
+ return value;
383
+ }
384
+ const record = value;
385
+ if (Object.keys(record).length === 1 && typeof record.$koraBytes === "string") {
386
+ return base64ToBytes(record.$koraBytes);
387
+ }
388
+ const keys = Object.keys(record);
389
+ if (keys.length > 0 && keys.every((k, i) => k === String(i) && typeof record[k] === "number")) {
390
+ const bytes = new Uint8Array(keys.length);
391
+ for (let i = 0; i < keys.length; i++) {
392
+ const byte = record[String(i)];
393
+ bytes[i] = typeof byte === "number" ? byte & 255 : 0;
394
+ }
395
+ return bytes;
396
+ }
397
+ return value;
398
+ }
352
399
  function serializeFieldValue(value, descriptor) {
353
400
  if (value === null || value === void 0) return null;
354
401
  switch (descriptor.kind) {
@@ -356,6 +403,8 @@ function serializeFieldValue(value, descriptor) {
356
403
  return typeof value === "string" ? value : JSON.stringify(value);
357
404
  case "boolean":
358
405
  return value ? 1 : 0;
406
+ case "richtext":
407
+ return decodeRichtextColumnValue(value);
359
408
  default:
360
409
  return value;
361
410
  }
@@ -401,7 +450,7 @@ var MemoryServerStore = class {
401
450
  materializedRecords = /* @__PURE__ */ new Map();
402
451
  closed = false;
403
452
  constructor(nodeId) {
404
- this.nodeId = nodeId ?? (0, import_core.generateUUIDv7)();
453
+ this.nodeId = nodeId ?? (0, import_core2.generateUUIDv7)();
405
454
  }
406
455
  getVersionVector() {
407
456
  return new Map(this.versionVector);
@@ -738,7 +787,7 @@ var MemoryServerStore = class {
738
787
 
739
788
  // src/store/postgres-server-store.ts
740
789
  init_cjs_shims();
741
- var import_core2 = require("@korajs/core");
790
+ var import_core3 = require("@korajs/core");
742
791
  var import_drizzle_orm = require("drizzle-orm");
743
792
 
744
793
  // src/store/drizzle-pg-schema.ts
@@ -787,7 +836,7 @@ var PostgresServerStore = class {
787
836
  closed = false;
788
837
  constructor(db, nodeId) {
789
838
  this.db = db;
790
- this.nodeId = nodeId ?? (0, import_core2.generateUUIDv7)();
839
+ this.nodeId = nodeId ?? (0, import_core3.generateUUIDv7)();
791
840
  this.ready = this.initialize();
792
841
  }
793
842
  getVersionVector() {
@@ -1323,7 +1372,7 @@ async function loadPostgresDeps() {
1323
1372
  // src/store/sqlite-server-store.ts
1324
1373
  init_cjs_shims();
1325
1374
  var import_node_module = require("module");
1326
- var import_core3 = require("@korajs/core");
1375
+ var import_core4 = require("@korajs/core");
1327
1376
  var import_drizzle_orm2 = require("drizzle-orm");
1328
1377
 
1329
1378
  // src/store/drizzle-schema.ts
@@ -1371,7 +1420,7 @@ var SqliteServerStore = class {
1371
1420
  closed = false;
1372
1421
  constructor(db, nodeId) {
1373
1422
  this.db = db;
1374
- this.nodeId = nodeId ?? (0, import_core3.generateUUIDv7)();
1423
+ this.nodeId = nodeId ?? (0, import_core4.generateUUIDv7)();
1375
1424
  this.ensureTables();
1376
1425
  }
1377
1426
  getVersionVector() {
@@ -1869,7 +1918,7 @@ function createSqliteServerStore(options) {
1869
1918
 
1870
1919
  // src/transport/http-server-transport.ts
1871
1920
  init_cjs_shims();
1872
- var import_core4 = require("@korajs/core");
1921
+ var import_core5 = require("@korajs/core");
1873
1922
  var HttpServerTransport = class {
1874
1923
  serializer;
1875
1924
  messageHandler = null;
@@ -1911,7 +1960,7 @@ var HttpServerTransport = class {
1911
1960
  }
1912
1961
  receive(payload) {
1913
1962
  if (!this.connected) {
1914
- throw new import_core4.SyncError("HTTP server transport is closed");
1963
+ throw new import_core5.SyncError("HTTP server transport is closed");
1915
1964
  }
1916
1965
  try {
1917
1966
  const message = this.serializer.decode(payload);
@@ -1951,7 +2000,7 @@ var HttpServerTransport = class {
1951
2000
 
1952
2001
  // src/transport/ws-server-transport.ts
1953
2002
  init_cjs_shims();
1954
- var import_core5 = require("@korajs/core");
2003
+ var import_core6 = require("@korajs/core");
1955
2004
  var import_sync = require("@korajs/sync");
1956
2005
  var WS_OPEN = 1;
1957
2006
  var WsServerTransport = class {
@@ -1967,7 +2016,7 @@ var WsServerTransport = class {
1967
2016
  }
1968
2017
  send(message) {
1969
2018
  if (this.ws.readyState !== WS_OPEN) {
1970
- throw new import_core5.SyncError("Cannot send message: WebSocket is not open", {
2019
+ throw new import_core6.SyncError("Cannot send message: WebSocket is not open", {
1971
2020
  readyState: this.ws.readyState,
1972
2021
  messageType: message.type
1973
2022
  });
@@ -1994,7 +2043,7 @@ var WsServerTransport = class {
1994
2043
  this.ws.on("message", (data) => {
1995
2044
  try {
1996
2045
  if (typeof data !== "string" && !(data instanceof Uint8Array) && !(data instanceof ArrayBuffer)) {
1997
- throw new import_core5.SyncError("Unsupported WebSocket payload type", {
2046
+ throw new import_core6.SyncError("Unsupported WebSocket payload type", {
1998
2047
  payloadType: typeof data
1999
2048
  });
2000
2049
  }
@@ -2015,7 +2064,7 @@ var WsServerTransport = class {
2015
2064
 
2016
2065
  // src/session/client-session.ts
2017
2066
  init_cjs_shims();
2018
- var import_core8 = require("@korajs/core");
2067
+ var import_core10 = require("@korajs/core");
2019
2068
  var import_internal = require("@korajs/core/internal");
2020
2069
  var import_sync2 = require("@korajs/sync");
2021
2070
 
@@ -2107,12 +2156,12 @@ function createServerReferentialContext(store) {
2107
2156
 
2108
2157
  // src/apply/server-side-effect-operation.ts
2109
2158
  init_cjs_shims();
2110
- var import_core6 = require("@korajs/core");
2159
+ var import_core7 = require("@korajs/core");
2111
2160
  async function createServerSideEffectOperation(store, parentOp, effect, schemaVersion, sequenceNumber) {
2112
2161
  const nodeId = store.getNodeId();
2113
- const clock = new import_core6.HybridLogicalClock(nodeId);
2162
+ const clock = new import_core7.HybridLogicalClock(nodeId);
2114
2163
  clock.receive(parentOp.timestamp);
2115
- return (0, import_core6.createOperation)(
2164
+ return (0, import_core7.createOperation)(
2116
2165
  {
2117
2166
  nodeId,
2118
2167
  type: effect.type === "delete" ? "delete" : "update",
@@ -2192,19 +2241,19 @@ async function applyServerOperation(store, op, relationLookup) {
2192
2241
 
2193
2242
  // src/scopes/resolve-session-scopes.ts
2194
2243
  init_cjs_shims();
2195
- var import_core7 = require("@korajs/core");
2244
+ var import_core8 = require("@korajs/core");
2196
2245
  function resolveSessionScopes(schema, options) {
2197
2246
  const { handshakeScope, authScopes, scopeValues } = options;
2198
2247
  let resolved;
2199
2248
  if (schema && scopeValues && Object.keys(scopeValues).length > 0) {
2200
- resolved = (0, import_core7.buildScopeMap)(schema, scopeValues);
2249
+ resolved = (0, import_core8.buildScopeMap)(schema, scopeValues);
2201
2250
  } else if (handshakeScope) {
2202
2251
  resolved = { ...handshakeScope };
2203
2252
  } else if (authScopes) {
2204
2253
  resolved = { ...authScopes };
2205
2254
  }
2206
2255
  if (schema && !resolved && scopeValues) {
2207
- resolved = (0, import_core7.buildScopeMap)(schema, scopeValues);
2256
+ resolved = (0, import_core8.buildScopeMap)(schema, scopeValues);
2208
2257
  }
2209
2258
  if (authScopes) {
2210
2259
  resolved = mergeScopeMaps(resolved, authScopes, "auth");
@@ -2259,9 +2308,17 @@ function asRecord(value) {
2259
2308
 
2260
2309
  // src/session/operation-validation.ts
2261
2310
  init_cjs_shims();
2311
+ var import_core9 = require("@korajs/core");
2262
2312
  var SERVER_MAX_TIMESTAMP_FUTURE_MS = 6e4;
2263
2313
  function isOperationTimestampValid(op, now = Date.now()) {
2264
- return op.timestamp.wallTime <= now + SERVER_MAX_TIMESTAMP_FUTURE_MS;
2314
+ const { wallTime, logical } = op.timestamp;
2315
+ if (!Number.isInteger(wallTime) || wallTime < 0) {
2316
+ return false;
2317
+ }
2318
+ if (!Number.isInteger(logical) || logical < 0 || logical > import_core9.MAX_LOGICAL) {
2319
+ return false;
2320
+ }
2321
+ return wallTime <= now + SERVER_MAX_TIMESTAMP_FUTURE_MS;
2265
2322
  }
2266
2323
 
2267
2324
  // src/session/session-operation-limits.ts
@@ -2379,7 +2436,7 @@ var ClientSession = class {
2379
2436
  const serializedOps = visibleOperations.map((op) => this.serializer.encodeOperation(op));
2380
2437
  const msg = {
2381
2438
  type: "operation-batch",
2382
- messageId: (0, import_core8.generateUUIDv7)(),
2439
+ messageId: (0, import_core10.generateUUIDv7)(),
2383
2440
  operations: serializedOps,
2384
2441
  isFinal: true,
2385
2442
  batchIndex: 0
@@ -2503,14 +2560,15 @@ var ClientSession = class {
2503
2560
  const { min, max } = this.supportedSchemaVersions;
2504
2561
  const response2 = {
2505
2562
  type: "handshake-response",
2506
- messageId: (0, import_core8.generateUUIDv7)(),
2563
+ messageId: (0, import_core10.generateUUIDv7)(),
2507
2564
  nodeId: this.store.getNodeId(),
2508
2565
  versionVector: (0, import_sync2.versionVectorToWire)(serverVector),
2509
2566
  schemaVersion: this.schemaVersion,
2510
2567
  accepted: false,
2511
2568
  rejectReason: `${import_sync2.SCHEMA_MISMATCH_PREFIX}: client schema version ${msg.schemaVersion} not in supported range [${min}, ${max}]`,
2512
2569
  supportedSchemaMin: min,
2513
- supportedSchemaMax: max
2570
+ supportedSchemaMax: max,
2571
+ serverTime: Date.now()
2514
2572
  };
2515
2573
  this.sendToClient(response2);
2516
2574
  this.close("schema version mismatch");
@@ -2518,12 +2576,13 @@ var ClientSession = class {
2518
2576
  }
2519
2577
  const response = {
2520
2578
  type: "handshake-response",
2521
- messageId: (0, import_core8.generateUUIDv7)(),
2579
+ messageId: (0, import_core10.generateUUIDv7)(),
2522
2580
  nodeId: this.store.getNodeId(),
2523
2581
  versionVector: (0, import_sync2.versionVectorToWire)(serverVector),
2524
2582
  schemaVersion: this.schemaVersion,
2525
2583
  accepted: true,
2526
2584
  selectedWireFormat,
2585
+ serverTime: Date.now(),
2527
2586
  // Confirm the accepted scope so the client knows what data will be synced.
2528
2587
  // This may differ from what the client requested if auth scopes are narrower.
2529
2588
  ...this.authContext?.scopes ? { acceptedScope: this.authContext.scopes } : {}
@@ -2600,7 +2659,7 @@ var ClientSession = class {
2600
2659
  const lastOp = operations2[operations2.length - 1];
2601
2660
  const ack = {
2602
2661
  type: "acknowledgment",
2603
- messageId: (0, import_core8.generateUUIDv7)(),
2662
+ messageId: (0, import_core10.generateUUIDv7)(),
2604
2663
  acknowledgedMessageId: msg.messageId,
2605
2664
  lastSequenceNumber: lastOp ? lastOp.sequenceNumber : 0
2606
2665
  };
@@ -2623,7 +2682,7 @@ var ClientSession = class {
2623
2682
  if (missing.length === 0) {
2624
2683
  const emptyBatch = {
2625
2684
  type: "operation-batch",
2626
- messageId: (0, import_core8.generateUUIDv7)(),
2685
+ messageId: (0, import_core10.generateUUIDv7)(),
2627
2686
  operations: [],
2628
2687
  isFinal: true,
2629
2688
  batchIndex: 0,
@@ -2638,7 +2697,7 @@ var ClientSession = class {
2638
2697
  if (afterCursor.length === 0) {
2639
2698
  const emptyBatch = {
2640
2699
  type: "operation-batch",
2641
- messageId: (0, import_core8.generateUUIDv7)(),
2700
+ messageId: (0, import_core10.generateUUIDv7)(),
2642
2701
  operations: [],
2643
2702
  isFinal: true,
2644
2703
  batchIndex: this.resumeDeltaCursor?.batchIndex ?? 0,
@@ -2654,7 +2713,7 @@ var ClientSession = class {
2654
2713
  const batchCursor = (0, import_sync2.createDeltaCursorFromBatch)(batchOps, i);
2655
2714
  const batchMsg = {
2656
2715
  type: "operation-batch",
2657
- messageId: (0, import_core8.generateUUIDv7)(),
2716
+ messageId: (0, import_core10.generateUUIDv7)(),
2658
2717
  operations: serializedOps,
2659
2718
  isFinal: i === totalBatches - 1,
2660
2719
  batchIndex: i,
@@ -2684,7 +2743,7 @@ var ClientSession = class {
2684
2743
  sendError(code, message, retriable) {
2685
2744
  const errorMsg = {
2686
2745
  type: "error",
2687
- messageId: (0, import_core8.generateUUIDv7)(),
2746
+ messageId: (0, import_core10.generateUUIDv7)(),
2688
2747
  code,
2689
2748
  message,
2690
2749
  retriable
@@ -2712,13 +2771,13 @@ function selectWireFormat(supportedWireFormats) {
2712
2771
 
2713
2772
  // src/server/kora-sync-server.ts
2714
2773
  init_cjs_shims();
2715
- var import_core10 = require("@korajs/core");
2774
+ var import_core12 = require("@korajs/core");
2716
2775
  var import_internal2 = require("@korajs/core/internal");
2717
2776
  var import_sync3 = require("@korajs/sync");
2718
2777
 
2719
2778
  // src/awareness/awareness-relay.ts
2720
2779
  init_cjs_shims();
2721
- var import_core9 = require("@korajs/core");
2780
+ var import_core11 = require("@korajs/core");
2722
2781
  var AwarenessRelay = class {
2723
2782
  clients = /* @__PURE__ */ new Map();
2724
2783
  /**
@@ -2747,7 +2806,7 @@ var AwarenessRelay = class {
2747
2806
  if (hasStates) {
2748
2807
  const catchUpMsg = {
2749
2808
  type: "awareness-update",
2750
- messageId: (0, import_core9.generateUUIDv7)(),
2809
+ messageId: (0, import_core11.generateUUIDv7)(),
2751
2810
  clientId: 0,
2752
2811
  // Server-sourced
2753
2812
  states: existingStates
@@ -2770,7 +2829,7 @@ var AwarenessRelay = class {
2770
2829
  };
2771
2830
  const msg = {
2772
2831
  type: "awareness-update",
2773
- messageId: (0, import_core9.generateUUIDv7)(),
2832
+ messageId: (0, import_core11.generateUUIDv7)(),
2774
2833
  clientId: client.clientId,
2775
2834
  states: removalStates
2776
2835
  };
@@ -3084,10 +3143,10 @@ var KoraSyncServer = class {
3084
3143
  */
3085
3144
  async start(wsServerImpl) {
3086
3145
  if (this.running) {
3087
- throw new import_core10.SyncError("Server is already running", { port: this.port });
3146
+ throw new import_core12.SyncError("Server is already running", { port: this.port });
3088
3147
  }
3089
3148
  if (!wsServerImpl && this.port === void 0) {
3090
- throw new import_core10.SyncError(
3149
+ throw new import_core12.SyncError(
3091
3150
  "Port is required for standalone mode. Provide port in config or use handleConnection() for attach mode.",
3092
3151
  {}
3093
3152
  );
@@ -3196,7 +3255,7 @@ var KoraSyncServer = class {
3196
3255
  if (this.maxConnections > 0 && this.sessions.size >= this.maxConnections) {
3197
3256
  transport.send({
3198
3257
  type: "error",
3199
- messageId: (0, import_core10.generateUUIDv7)(),
3258
+ messageId: (0, import_core12.generateUUIDv7)(),
3200
3259
  code: "MAX_CONNECTIONS",
3201
3260
  message: `Server has reached maximum connections (${this.maxConnections})`,
3202
3261
  retriable: true
@@ -3209,12 +3268,12 @@ var KoraSyncServer = class {
3209
3268
  event: "connection.rejected",
3210
3269
  details: { reason: "max_connections", max: this.maxConnections }
3211
3270
  });
3212
- throw new import_core10.SyncError("Maximum connections reached", {
3271
+ throw new import_core12.SyncError("Maximum connections reached", {
3213
3272
  current: this.sessions.size,
3214
3273
  max: this.maxConnections
3215
3274
  });
3216
3275
  }
3217
- const sessionId = (0, import_core10.generateUUIDv7)();
3276
+ const sessionId = (0, import_core12.generateUUIDv7)();
3218
3277
  this.metrics.recordConnection(sessionId);
3219
3278
  const sessionEmitter = new import_internal2.SimpleEventEmitter();
3220
3279
  sessionEmitter.on("sync:connected", (event) => {