@korajs/server 0.3.3 → 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.js CHANGED
@@ -63,9 +63,7 @@ ALTER TABLE ${name} ADD COLUMN ${colDef}`);
63
63
  `CREATE INDEX IF NOT EXISTS idx_${name}_${indexField} ON ${name} (${indexField})`
64
64
  );
65
65
  }
66
- statements.push(
67
- `CREATE INDEX IF NOT EXISTS idx_${name}__deleted ON ${name} (_deleted)`
68
- );
66
+ statements.push(`CREATE INDEX IF NOT EXISTS idx_${name}__deleted ON ${name} (_deleted)`);
69
67
  return statements;
70
68
  }
71
69
  function generateAllCollectionDDL(schema, dialect) {
@@ -180,7 +178,7 @@ var MemoryServerStore = class {
180
178
  if (op.sequenceNumber > currentSeq) {
181
179
  this.versionVector.set(op.nodeId, op.sequenceNumber);
182
180
  }
183
- if (this.schema && this.schema.collections[op.collection]) {
181
+ if (this.schema?.collections[op.collection]) {
184
182
  this.rebuildMaterializedRecord(op.collection, op.recordId);
185
183
  }
186
184
  return "applied";
@@ -197,7 +195,7 @@ var MemoryServerStore = class {
197
195
  }
198
196
  async materializeCollection(collection) {
199
197
  this.assertOpen();
200
- if (this.schema && this.schema.collections[collection]) {
198
+ if (this.schema?.collections[collection]) {
201
199
  return this.queryCollection(collection);
202
200
  }
203
201
  return this.materializeFromOps(collection);
@@ -206,13 +204,14 @@ var MemoryServerStore = class {
206
204
  this.assertOpen();
207
205
  this.assertSchema();
208
206
  this.assertCollection(collection);
207
+ const schema = this.schema;
209
208
  if (options?.where) {
210
209
  for (const key of Object.keys(options.where)) {
211
- validateFieldName(collection, key, this.schema);
210
+ validateFieldName(collection, key, schema);
212
211
  }
213
212
  }
214
213
  if (options?.orderBy) {
215
- validateFieldName(collection, options.orderBy, this.schema);
214
+ validateFieldName(collection, options.orderBy, schema);
216
215
  }
217
216
  const collectionMap = this.materializedRecords.get(collection);
218
217
  if (!collectionMap) return [];
@@ -279,9 +278,10 @@ var MemoryServerStore = class {
279
278
  this.assertOpen();
280
279
  this.assertSchema();
281
280
  this.assertCollection(collection);
281
+ const schema = this.schema;
282
282
  if (where) {
283
283
  for (const key of Object.keys(where)) {
284
- validateFieldName(collection, key, this.schema);
284
+ validateFieldName(collection, key, schema);
285
285
  }
286
286
  }
287
287
  const collectionMap = this.materializedRecords.get(collection);
@@ -317,7 +317,7 @@ var MemoryServerStore = class {
317
317
  // Materialization internals
318
318
  // ---------------------------------------------------------------------------
319
319
  rebuildMaterializedRecord(collection, recordId) {
320
- const collectionDef = this.schema.collections[collection];
320
+ const collectionDef = this.schema?.collections[collection];
321
321
  if (!collectionDef) return;
322
322
  let collectionMap = this.materializedRecords.get(collection);
323
323
  if (!collectionMap) {
@@ -325,8 +325,10 @@ var MemoryServerStore = class {
325
325
  this.materializedRecords.set(collection, collectionMap);
326
326
  }
327
327
  const recordOps = this.operations.filter((op) => op.collection === collection && op.recordId === recordId).sort((a, b) => {
328
- if (a.timestamp.wallTime !== b.timestamp.wallTime) return a.timestamp.wallTime - b.timestamp.wallTime;
329
- if (a.timestamp.logical !== b.timestamp.logical) return a.timestamp.logical - b.timestamp.logical;
328
+ if (a.timestamp.wallTime !== b.timestamp.wallTime)
329
+ return a.timestamp.wallTime - b.timestamp.wallTime;
330
+ if (a.timestamp.logical !== b.timestamp.logical)
331
+ return a.timestamp.logical - b.timestamp.logical;
330
332
  return a.sequenceNumber - b.sequenceNumber;
331
333
  });
332
334
  const parsedOps = recordOps.map((op) => ({
@@ -378,8 +380,10 @@ var MemoryServerStore = class {
378
380
  // ---------------------------------------------------------------------------
379
381
  materializeFromOps(collection) {
380
382
  const collectionOps = this.operations.filter((op) => op.collection === collection).sort((a, b) => {
381
- if (a.timestamp.wallTime !== b.timestamp.wallTime) return a.timestamp.wallTime - b.timestamp.wallTime;
382
- if (a.timestamp.logical !== b.timestamp.logical) return a.timestamp.logical - b.timestamp.logical;
383
+ if (a.timestamp.wallTime !== b.timestamp.wallTime)
384
+ return a.timestamp.wallTime - b.timestamp.wallTime;
385
+ if (a.timestamp.logical !== b.timestamp.logical)
386
+ return a.timestamp.logical - b.timestamp.logical;
383
387
  return a.sequenceNumber - b.sequenceNumber;
384
388
  });
385
389
  const records = /* @__PURE__ */ new Map();
@@ -425,9 +429,10 @@ var MemoryServerStore = class {
425
429
  }
426
430
  }
427
431
  assertCollection(collection) {
428
- if (!this.schema.collections[collection]) {
432
+ const schema = this.schema;
433
+ if (!schema.collections[collection]) {
429
434
  throw new Error(
430
- `Unknown collection "${collection}". Available: ${Object.keys(this.schema.collections).join(", ")}`
435
+ `Unknown collection "${collection}". Available: ${Object.keys(schema.collections).join(", ")}`
431
436
  );
432
437
  }
433
438
  }
@@ -537,7 +542,7 @@ var PostgresServerStore = class {
537
542
  lastSeenAt: sql`${now}`
538
543
  }
539
544
  });
540
- if (this.schema && this.schema.collections[op.collection]) {
545
+ if (this.schema?.collections[op.collection]) {
541
546
  await this.rebuildMaterializedRecord(tx, op.collection, op.recordId);
542
547
  }
543
548
  });
@@ -551,10 +556,7 @@ var PostgresServerStore = class {
551
556
  this.assertOpen();
552
557
  await this.ready;
553
558
  const rows = await this.db.select().from(pgOperations).where(
554
- and(
555
- eq(pgOperations.nodeId, nodeId),
556
- between(pgOperations.sequenceNumber, fromSeq, toSeq)
557
- )
559
+ and(eq(pgOperations.nodeId, nodeId), between(pgOperations.sequenceNumber, fromSeq, toSeq))
558
560
  ).orderBy(asc(pgOperations.sequenceNumber));
559
561
  return rows.map((row) => this.deserializeOperation(row));
560
562
  }
@@ -567,7 +569,7 @@ var PostgresServerStore = class {
567
569
  async materializeCollection(collection) {
568
570
  this.assertOpen();
569
571
  await this.ready;
570
- if (this.schema && this.schema.collections[collection]) {
572
+ if (this.schema?.collections[collection]) {
571
573
  return this.queryCollection(collection);
572
574
  }
573
575
  return this.materializeFromOpsLog(collection);
@@ -577,14 +579,15 @@ var PostgresServerStore = class {
577
579
  await this.ready;
578
580
  this.assertSchema();
579
581
  this.assertCollection(collection);
580
- const collectionDef = this.schema.collections[collection];
582
+ const schema = this.schema;
583
+ const collectionDef = schema.collections[collection];
581
584
  if (options?.where) {
582
585
  for (const key of Object.keys(options.where)) {
583
- validateFieldName(collection, key, this.schema);
586
+ validateFieldName(collection, key, schema);
584
587
  }
585
588
  }
586
589
  if (options?.orderBy) {
587
- validateFieldName(collection, options.orderBy, this.schema);
590
+ validateFieldName(collection, options.orderBy, schema);
588
591
  }
589
592
  const query = this.buildSelectQuery(collection, options);
590
593
  const rows = await this.db.execute(query);
@@ -595,7 +598,8 @@ var PostgresServerStore = class {
595
598
  await this.ready;
596
599
  this.assertSchema();
597
600
  this.assertCollection(collection);
598
- const collectionDef = this.schema.collections[collection];
601
+ const schema = this.schema;
602
+ const collectionDef = schema.collections[collection];
599
603
  const query = sql`SELECT * FROM ${sql.raw(collection)} WHERE id = ${id} AND _deleted = 0`;
600
604
  const rows = await this.db.execute(query);
601
605
  if (rows.length === 0) return null;
@@ -606,9 +610,10 @@ var PostgresServerStore = class {
606
610
  await this.ready;
607
611
  this.assertSchema();
608
612
  this.assertCollection(collection);
613
+ const schema = this.schema;
609
614
  if (where) {
610
615
  for (const key of Object.keys(where)) {
611
- validateFieldName(collection, key, this.schema);
616
+ validateFieldName(collection, key, schema);
612
617
  }
613
618
  }
614
619
  const whereClause = this.buildWhereClause(where ?? {}, false);
@@ -628,18 +633,13 @@ var PostgresServerStore = class {
628
633
  * all operations for that record.
629
634
  */
630
635
  async rebuildMaterializedRecord(txOrDb, collection, recordId) {
631
- const collectionDef = this.schema.collections[collection];
636
+ const collectionDef = this.schema?.collections[collection];
632
637
  if (!collectionDef) return;
633
638
  const ops = await txOrDb.select({
634
639
  type: pgOperations.type,
635
640
  data: pgOperations.data,
636
641
  wallTime: pgOperations.wallTime
637
- }).from(pgOperations).where(
638
- and(
639
- eq(pgOperations.collection, collection),
640
- eq(pgOperations.recordId, recordId)
641
- )
642
- ).orderBy(
642
+ }).from(pgOperations).where(and(eq(pgOperations.collection, collection), eq(pgOperations.recordId, recordId))).orderBy(
643
643
  asc(pgOperations.wallTime),
644
644
  asc(pgOperations.logical),
645
645
  asc(pgOperations.sequenceNumber)
@@ -709,7 +709,7 @@ var PostgresServerStore = class {
709
709
  * Backfill a single collection's materialized table from operations.
710
710
  */
711
711
  async backfillCollection(collectionName) {
712
- const collectionDef = this.schema.collections[collectionName];
712
+ const collectionDef = this.schema?.collections[collectionName];
713
713
  if (!collectionDef) return;
714
714
  const allOps = await this.db.select({
715
715
  recordId: pgOperations.recordId,
@@ -766,9 +766,7 @@ var PostgresServerStore = class {
766
766
  options?.where ?? {},
767
767
  options?.includeDeleted ?? false
768
768
  );
769
- const parts = [
770
- sql`SELECT * FROM ${sql.raw(collection)} WHERE ${whereClause}`
771
- ];
769
+ const parts = [sql`SELECT * FROM ${sql.raw(collection)} WHERE ${whereClause}`];
772
770
  if (options?.orderBy) {
773
771
  const dir = options.orderDirection === "desc" ? "DESC" : "ASC";
774
772
  parts.push(sql.raw(` ORDER BY ${options.orderBy} ${dir}`));
@@ -881,12 +879,8 @@ var PostgresServerStore = class {
881
879
  await this.db.execute(
882
880
  sql`CREATE INDEX IF NOT EXISTS idx_node_seq ON operations (node_id, sequence_number)`
883
881
  );
884
- await this.db.execute(
885
- sql`CREATE INDEX IF NOT EXISTS idx_collection ON operations (collection)`
886
- );
887
- await this.db.execute(
888
- sql`CREATE INDEX IF NOT EXISTS idx_received ON operations (received_at)`
889
- );
882
+ await this.db.execute(sql`CREATE INDEX IF NOT EXISTS idx_collection ON operations (collection)`);
883
+ await this.db.execute(sql`CREATE INDEX IF NOT EXISTS idx_received ON operations (received_at)`);
890
884
  await this.db.execute(
891
885
  sql`CREATE INDEX IF NOT EXISTS idx_collection_record ON operations (collection, record_id)`
892
886
  );
@@ -954,9 +948,10 @@ var PostgresServerStore = class {
954
948
  }
955
949
  }
956
950
  assertCollection(collection) {
957
- if (!this.schema.collections[collection]) {
951
+ const schema = this.schema;
952
+ if (!schema.collections[collection]) {
958
953
  throw new Error(
959
- `Unknown collection "${collection}". Available: ${Object.keys(this.schema.collections).join(", ")}`
954
+ `Unknown collection "${collection}". Available: ${Object.keys(schema.collections).join(", ")}`
960
955
  );
961
956
  }
962
957
  }
@@ -1089,7 +1084,7 @@ var SqliteServerStore = class {
1089
1084
  lastSeenAt: sql2`${now}`
1090
1085
  }
1091
1086
  }).run();
1092
- if (this.schema && this.schema.collections[op.collection]) {
1087
+ if (this.schema?.collections[op.collection]) {
1093
1088
  this.rebuildMaterializedRecord(tx, op.collection, op.recordId);
1094
1089
  }
1095
1090
  return "applied";
@@ -1108,7 +1103,7 @@ var SqliteServerStore = class {
1108
1103
  }
1109
1104
  async materializeCollection(collection) {
1110
1105
  this.assertOpen();
1111
- if (this.schema && this.schema.collections[collection]) {
1106
+ if (this.schema?.collections[collection]) {
1112
1107
  return this.queryCollection(collection);
1113
1108
  }
1114
1109
  return this.materializeFromOpsLog(collection);
@@ -1117,14 +1112,15 @@ var SqliteServerStore = class {
1117
1112
  this.assertOpen();
1118
1113
  this.assertSchema();
1119
1114
  this.assertCollection(collection);
1120
- const collectionDef = this.schema.collections[collection];
1115
+ const schema = this.schema;
1116
+ const collectionDef = schema.collections[collection];
1121
1117
  if (options?.where) {
1122
1118
  for (const key of Object.keys(options.where)) {
1123
- validateFieldName(collection, key, this.schema);
1119
+ validateFieldName(collection, key, schema);
1124
1120
  }
1125
1121
  }
1126
1122
  if (options?.orderBy) {
1127
- validateFieldName(collection, options.orderBy, this.schema);
1123
+ validateFieldName(collection, options.orderBy, schema);
1128
1124
  }
1129
1125
  const query = this.buildSelectQuery(collection, options);
1130
1126
  const rows = this.db.all(query);
@@ -1134,7 +1130,8 @@ var SqliteServerStore = class {
1134
1130
  this.assertOpen();
1135
1131
  this.assertSchema();
1136
1132
  this.assertCollection(collection);
1137
- const collectionDef = this.schema.collections[collection];
1133
+ const schema = this.schema;
1134
+ const collectionDef = schema.collections[collection];
1138
1135
  const query = sql2`SELECT * FROM ${sql2.raw(collection)} WHERE id = ${id} AND _deleted = 0`;
1139
1136
  const rows = this.db.all(query);
1140
1137
  if (rows.length === 0) return null;
@@ -1144,9 +1141,10 @@ var SqliteServerStore = class {
1144
1141
  this.assertOpen();
1145
1142
  this.assertSchema();
1146
1143
  this.assertCollection(collection);
1144
+ const schema = this.schema;
1147
1145
  if (where) {
1148
1146
  for (const key of Object.keys(where)) {
1149
- validateFieldName(collection, key, this.schema);
1147
+ validateFieldName(collection, key, schema);
1150
1148
  }
1151
1149
  }
1152
1150
  const whereClause = this.buildWhereClause(where ?? {}, false);
@@ -1166,18 +1164,13 @@ var SqliteServerStore = class {
1166
1164
  * transaction for atomic dual-write.
1167
1165
  */
1168
1166
  rebuildMaterializedRecord(txOrDb, collection, recordId) {
1169
- const collectionDef = this.schema.collections[collection];
1167
+ const collectionDef = this.schema?.collections[collection];
1170
1168
  if (!collectionDef) return;
1171
1169
  const ops = txOrDb.select({
1172
1170
  type: operations.type,
1173
1171
  data: operations.data,
1174
1172
  wallTime: operations.wallTime
1175
- }).from(operations).where(
1176
- and2(
1177
- eq2(operations.collection, collection),
1178
- eq2(operations.recordId, recordId)
1179
- )
1180
- ).orderBy(asc2(operations.wallTime), asc2(operations.logical), asc2(operations.sequenceNumber)).all();
1173
+ }).from(operations).where(and2(eq2(operations.collection, collection), eq2(operations.recordId, recordId))).orderBy(asc2(operations.wallTime), asc2(operations.logical), asc2(operations.sequenceNumber)).all();
1181
1174
  const parsedOps = ops.map((op) => ({
1182
1175
  type: op.type,
1183
1176
  data: op.data !== null ? JSON.parse(op.data) : null
@@ -1246,7 +1239,7 @@ var SqliteServerStore = class {
1246
1239
  * Backfill a single collection's materialized table from operations.
1247
1240
  */
1248
1241
  backfillCollection(collectionName) {
1249
- const collectionDef = this.schema.collections[collectionName];
1242
+ const collectionDef = this.schema?.collections[collectionName];
1250
1243
  if (!collectionDef) return;
1251
1244
  const allOps = this.db.select({
1252
1245
  recordId: operations.recordId,
@@ -1301,9 +1294,7 @@ var SqliteServerStore = class {
1301
1294
  options?.where ?? {},
1302
1295
  options?.includeDeleted ?? false
1303
1296
  );
1304
- const parts = [
1305
- sql2`SELECT * FROM ${sql2.raw(collection)} WHERE ${whereClause}`
1306
- ];
1297
+ const parts = [sql2`SELECT * FROM ${sql2.raw(collection)} WHERE ${whereClause}`];
1307
1298
  if (options?.orderBy) {
1308
1299
  const dir = options.orderDirection === "desc" ? "DESC" : "ASC";
1309
1300
  parts.push(sql2.raw(` ORDER BY ${options.orderBy} ${dir}`));
@@ -1478,9 +1469,10 @@ var SqliteServerStore = class {
1478
1469
  }
1479
1470
  }
1480
1471
  assertCollection(collection) {
1481
- if (!this.schema.collections[collection]) {
1472
+ const schema = this.schema;
1473
+ if (!schema.collections[collection]) {
1482
1474
  throw new Error(
1483
- `Unknown collection "${collection}". Available: ${Object.keys(this.schema.collections).join(", ")}`
1475
+ `Unknown collection "${collection}". Available: ${Object.keys(schema.collections).join(", ")}`
1484
1476
  );
1485
1477
  }
1486
1478
  }
@@ -1642,11 +1634,7 @@ var WsServerTransport = class {
1642
1634
  // src/session/client-session.ts
1643
1635
  import { generateUUIDv7 as generateUUIDv74 } from "@korajs/core";
1644
1636
  import { topologicalSort } from "@korajs/core/internal";
1645
- import {
1646
- NegotiatedMessageSerializer,
1647
- versionVectorToWire,
1648
- wireToVersionVector
1649
- } from "@korajs/sync";
1637
+ import { NegotiatedMessageSerializer, versionVectorToWire, wireToVersionVector } from "@korajs/sync";
1650
1638
 
1651
1639
  // src/scopes/server-scope-filter.ts
1652
1640
  function operationMatchesScopes(op, scopes) {
@@ -1694,6 +1682,7 @@ var ClientSession = class {
1694
1682
  batchSize;
1695
1683
  schemaVersion;
1696
1684
  onRelay;
1685
+ onAwarenessUpdate;
1697
1686
  onClose;
1698
1687
  constructor(options) {
1699
1688
  this.sessionId = options.sessionId;
@@ -1705,6 +1694,7 @@ var ClientSession = class {
1705
1694
  this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
1706
1695
  this.schemaVersion = options.schemaVersion ?? DEFAULT_SCHEMA_VERSION;
1707
1696
  this.onRelay = options.onRelay ?? null;
1697
+ this.onAwarenessUpdate = options.onAwarenessUpdate ?? null;
1708
1698
  this.onClose = options.onClose ?? null;
1709
1699
  }
1710
1700
  /**
@@ -1767,6 +1757,13 @@ var ClientSession = class {
1767
1757
  isStreaming() {
1768
1758
  return this.state === "streaming";
1769
1759
  }
1760
+ /**
1761
+ * Get the transport for this session.
1762
+ * Used by the awareness relay to send messages to this client.
1763
+ */
1764
+ getTransport() {
1765
+ return this.transport;
1766
+ }
1770
1767
  // --- Private protocol handlers ---
1771
1768
  handleMessage(message) {
1772
1769
  switch (message.type) {
@@ -1781,6 +1778,9 @@ var ClientSession = class {
1781
1778
  break;
1782
1779
  case "error":
1783
1780
  break;
1781
+ case "awareness-update":
1782
+ this.handleAwarenessUpdate(message);
1783
+ break;
1784
1784
  }
1785
1785
  }
1786
1786
  async handleHandshake(msg) {
@@ -1800,6 +1800,19 @@ var ClientSession = class {
1800
1800
  this.authContext = context;
1801
1801
  this.state = "authenticated";
1802
1802
  }
1803
+ if (msg.syncScope) {
1804
+ const mergedScopes = { ...msg.syncScope };
1805
+ if (this.authContext?.scopes) {
1806
+ for (const [collection, authScope] of Object.entries(this.authContext.scopes)) {
1807
+ mergedScopes[collection] = { ...mergedScopes[collection] ?? {}, ...authScope };
1808
+ }
1809
+ }
1810
+ if (this.authContext) {
1811
+ this.authContext = { ...this.authContext, scopes: mergedScopes };
1812
+ } else {
1813
+ this.authContext = { userId: msg.nodeId, scopes: mergedScopes };
1814
+ }
1815
+ }
1803
1816
  const serverVector = this.store.getVersionVector();
1804
1817
  const selectedWireFormat = selectWireFormat(msg.supportedWireFormats);
1805
1818
  this.setSerializerWireFormat(selectedWireFormat);
@@ -1810,7 +1823,10 @@ var ClientSession = class {
1810
1823
  versionVector: versionVectorToWire(serverVector),
1811
1824
  schemaVersion: this.schemaVersion,
1812
1825
  accepted: true,
1813
- selectedWireFormat
1826
+ selectedWireFormat,
1827
+ // Confirm the accepted scope so the client knows what data will be synced.
1828
+ // This may differ from what the client requested if auth scopes are narrower.
1829
+ ...this.authContext?.scopes ? { acceptedScope: this.authContext.scopes } : {}
1814
1830
  };
1815
1831
  this.transport.send(response);
1816
1832
  this.emitter?.emit({ type: "sync:connected", nodeId: msg.nodeId });
@@ -1822,8 +1838,10 @@ var ClientSession = class {
1822
1838
  async handleOperationBatch(msg) {
1823
1839
  const operations2 = msg.operations.map((s) => this.serializer.decodeOperation(s));
1824
1840
  const applied = [];
1841
+ const rejected = [];
1825
1842
  for (const op of operations2) {
1826
1843
  if (!operationMatchesScopes(op, this.authContext?.scopes)) {
1844
+ rejected.push(op);
1827
1845
  continue;
1828
1846
  }
1829
1847
  const result = await this.store.applyRemoteOperation(op);
@@ -1831,6 +1849,15 @@ var ClientSession = class {
1831
1849
  applied.push(op);
1832
1850
  }
1833
1851
  }
1852
+ if (rejected.length > 0) {
1853
+ for (const op of rejected) {
1854
+ this.sendError(
1855
+ "SCOPE_VIOLATION",
1856
+ `Operation "${op.id}" in collection "${op.collection}" is outside the client's sync scope`,
1857
+ false
1858
+ );
1859
+ }
1860
+ }
1834
1861
  if (operations2.length > 0) {
1835
1862
  this.emitter?.emit({
1836
1863
  type: "sync:received",
@@ -1893,6 +1920,9 @@ var ClientSession = class {
1893
1920
  });
1894
1921
  }
1895
1922
  }
1923
+ handleAwarenessUpdate(msg) {
1924
+ this.onAwarenessUpdate?.(this.sessionId, msg);
1925
+ }
1896
1926
  sendError(code, message, retriable) {
1897
1927
  const errorMsg = {
1898
1928
  type: "error",
@@ -1923,8 +1953,107 @@ function selectWireFormat(supportedWireFormats) {
1923
1953
  }
1924
1954
 
1925
1955
  // src/server/kora-sync-server.ts
1926
- import { SyncError as SyncError4, generateUUIDv7 as generateUUIDv75 } from "@korajs/core";
1956
+ import { SyncError as SyncError4, generateUUIDv7 as generateUUIDv76 } from "@korajs/core";
1927
1957
  import { JsonMessageSerializer as JsonMessageSerializer2 } from "@korajs/sync";
1958
+
1959
+ // src/awareness/awareness-relay.ts
1960
+ import { generateUUIDv7 as generateUUIDv75 } from "@korajs/core";
1961
+ var AwarenessRelay = class {
1962
+ clients = /* @__PURE__ */ new Map();
1963
+ /**
1964
+ * Register a client for awareness broadcasting.
1965
+ *
1966
+ * @param sessionId - Unique session identifier
1967
+ * @param clientId - Client-assigned awareness ID
1968
+ * @param transport - Transport for sending messages to this client
1969
+ */
1970
+ addClient(sessionId, clientId, transport) {
1971
+ this.clients.set(sessionId, {
1972
+ sessionId,
1973
+ clientId,
1974
+ transport,
1975
+ state: null
1976
+ });
1977
+ const existingStates = {};
1978
+ let hasStates = false;
1979
+ for (const [, client] of this.clients) {
1980
+ if (client.sessionId === sessionId) continue;
1981
+ if (client.state) {
1982
+ existingStates[String(client.clientId)] = client.state;
1983
+ hasStates = true;
1984
+ }
1985
+ }
1986
+ if (hasStates) {
1987
+ const catchUpMsg = {
1988
+ type: "awareness-update",
1989
+ messageId: generateUUIDv75(),
1990
+ clientId: 0,
1991
+ // Server-sourced
1992
+ states: existingStates
1993
+ };
1994
+ transport.send(catchUpMsg);
1995
+ }
1996
+ }
1997
+ /**
1998
+ * Remove a client and broadcast its removal to all remaining clients.
1999
+ *
2000
+ * @param sessionId - Session ID of the disconnecting client
2001
+ */
2002
+ removeClient(sessionId) {
2003
+ const client = this.clients.get(sessionId);
2004
+ if (!client) return;
2005
+ this.clients.delete(sessionId);
2006
+ if (client.state === null) return;
2007
+ const removalStates = {
2008
+ [String(client.clientId)]: null
2009
+ };
2010
+ const msg = {
2011
+ type: "awareness-update",
2012
+ messageId: generateUUIDv75(),
2013
+ clientId: client.clientId,
2014
+ states: removalStates
2015
+ };
2016
+ this.broadcastExcept(sessionId, msg);
2017
+ }
2018
+ /**
2019
+ * Handle an incoming awareness update from a client.
2020
+ * Stores the state and relays to all other connected clients.
2021
+ *
2022
+ * @param sessionId - Session ID of the sending client
2023
+ * @param message - The awareness update message
2024
+ */
2025
+ handleUpdate(sessionId, message) {
2026
+ const sender = this.clients.get(sessionId);
2027
+ if (!sender) return;
2028
+ const senderState = message.states[String(message.clientId)];
2029
+ if (senderState !== void 0) {
2030
+ sender.state = senderState;
2031
+ }
2032
+ this.broadcastExcept(sessionId, message);
2033
+ }
2034
+ /**
2035
+ * Get the number of registered awareness clients.
2036
+ */
2037
+ getClientCount() {
2038
+ return this.clients.size;
2039
+ }
2040
+ /**
2041
+ * Remove all clients and clear all state.
2042
+ */
2043
+ clear() {
2044
+ this.clients.clear();
2045
+ }
2046
+ // --- Private ---
2047
+ broadcastExcept(excludeSessionId, message) {
2048
+ for (const [, client] of this.clients) {
2049
+ if (client.sessionId === excludeSessionId) continue;
2050
+ if (!client.transport.isConnected()) continue;
2051
+ client.transport.send(message);
2052
+ }
2053
+ }
2054
+ };
2055
+
2056
+ // src/server/kora-sync-server.ts
1928
2057
  var DEFAULT_MAX_CONNECTIONS = 0;
1929
2058
  var DEFAULT_BATCH_SIZE2 = 100;
1930
2059
  var DEFAULT_SCHEMA_VERSION2 = 1;
@@ -1941,6 +2070,7 @@ var KoraSyncServer = class {
1941
2070
  port;
1942
2071
  host;
1943
2072
  path;
2073
+ awarenessRelay = new AwarenessRelay();
1944
2074
  sessions = /* @__PURE__ */ new Map();
1945
2075
  httpClients = /* @__PURE__ */ new Map();
1946
2076
  httpSessionToClient = /* @__PURE__ */ new Map();
@@ -2002,6 +2132,7 @@ var KoraSyncServer = class {
2002
2132
  * Stop the server. Closes all sessions and the WebSocket server.
2003
2133
  */
2004
2134
  async stop() {
2135
+ this.awarenessRelay.clear();
2005
2136
  for (const session of this.sessions.values()) {
2006
2137
  session.close("server shutting down");
2007
2138
  }
@@ -2058,7 +2189,7 @@ var KoraSyncServer = class {
2058
2189
  if (this.maxConnections > 0 && this.sessions.size >= this.maxConnections) {
2059
2190
  transport.send({
2060
2191
  type: "error",
2061
- messageId: generateUUIDv75(),
2192
+ messageId: generateUUIDv76(),
2062
2193
  code: "MAX_CONNECTIONS",
2063
2194
  message: `Server has reached maximum connections (${this.maxConnections})`,
2064
2195
  retriable: true
@@ -2069,7 +2200,7 @@ var KoraSyncServer = class {
2069
2200
  max: this.maxConnections
2070
2201
  });
2071
2202
  }
2072
- const sessionId = generateUUIDv75();
2203
+ const sessionId = generateUUIDv76();
2073
2204
  const session = new ClientSession({
2074
2205
  sessionId,
2075
2206
  transport,
@@ -2082,6 +2213,9 @@ var KoraSyncServer = class {
2082
2213
  onRelay: (sourceSessionId, operations2) => {
2083
2214
  this.handleRelay(sourceSessionId, operations2);
2084
2215
  },
2216
+ onAwarenessUpdate: (sourceSessionId, message) => {
2217
+ this.handleAwarenessRelay(sourceSessionId, message);
2218
+ },
2085
2219
  onClose: (sid) => {
2086
2220
  this.handleSessionClose(sid);
2087
2221
  }
@@ -2115,6 +2249,7 @@ var KoraSyncServer = class {
2115
2249
  }
2116
2250
  }
2117
2251
  handleSessionClose(sessionId) {
2252
+ this.awarenessRelay.removeClient(sessionId);
2118
2253
  this.sessions.delete(sessionId);
2119
2254
  const clientId = this.httpSessionToClient.get(sessionId);
2120
2255
  if (clientId) {
@@ -2122,6 +2257,15 @@ var KoraSyncServer = class {
2122
2257
  this.httpClients.delete(clientId);
2123
2258
  }
2124
2259
  }
2260
+ handleAwarenessRelay(sourceSessionId, message) {
2261
+ const session = this.sessions.get(sourceSessionId);
2262
+ if (!session) return;
2263
+ const transport = session.getTransport();
2264
+ if (!this.awarenessRelay.getClientCount() || !transport) {
2265
+ }
2266
+ this.awarenessRelay.addClient(sourceSessionId, message.clientId, transport);
2267
+ this.awarenessRelay.handleUpdate(sourceSessionId, message);
2268
+ }
2125
2269
  getOrCreateHttpClient(clientId) {
2126
2270
  const existing = this.httpClients.get(clientId);
2127
2271
  if (existing) {
@@ -2203,6 +2347,30 @@ var KoraAuthProvider = class {
2203
2347
  }
2204
2348
  };
2205
2349
 
2350
+ // src/auth/mixed-auth-provider.ts
2351
+ var MixedAuthProvider = class {
2352
+ primary;
2353
+ anonymousScopes;
2354
+ anonymousPrefix;
2355
+ anonymousCounter = 0;
2356
+ constructor(options) {
2357
+ this.primary = options.primary;
2358
+ this.anonymousScopes = options.anonymousScopes;
2359
+ this.anonymousPrefix = options.anonymousPrefix ?? "anon";
2360
+ }
2361
+ async authenticate(token) {
2362
+ if (token) {
2363
+ const ctx = await this.primary.authenticate(token);
2364
+ if (ctx) return ctx;
2365
+ }
2366
+ this.anonymousCounter++;
2367
+ return {
2368
+ userId: `${this.anonymousPrefix}-${Date.now()}-${this.anonymousCounter}`,
2369
+ scopes: this.anonymousScopes
2370
+ };
2371
+ }
2372
+ };
2373
+
2206
2374
  // src/server/create-server.ts
2207
2375
  function createKoraServer(config) {
2208
2376
  return new KoraSyncServer(config);
@@ -2291,7 +2459,7 @@ function createProductionServer(config) {
2291
2459
  }
2292
2460
  });
2293
2461
  return new Promise((resolve2) => {
2294
- httpServer.listen(port, "0.0.0.0", () => {
2462
+ httpServer?.listen(port, "0.0.0.0", () => {
2295
2463
  resolve2(`http://localhost:${port}`);
2296
2464
  });
2297
2465
  });
@@ -2300,7 +2468,7 @@ function createProductionServer(config) {
2300
2468
  await syncServer.stop();
2301
2469
  if (httpServer) {
2302
2470
  await new Promise((resolve) => {
2303
- httpServer.close(() => resolve());
2471
+ httpServer?.close(() => resolve());
2304
2472
  });
2305
2473
  httpServer = null;
2306
2474
  }
@@ -2308,11 +2476,13 @@ function createProductionServer(config) {
2308
2476
  };
2309
2477
  }
2310
2478
  export {
2479
+ AwarenessRelay,
2311
2480
  ClientSession,
2312
2481
  HttpServerTransport,
2313
2482
  KoraAuthProvider,
2314
2483
  KoraSyncServer,
2315
2484
  MemoryServerStore,
2485
+ MixedAuthProvider,
2316
2486
  NoAuthProvider,
2317
2487
  PostgresServerStore,
2318
2488
  SqliteServerStore,