@korajs/server 1.0.0-beta.5 → 1.0.0-beta.7

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
@@ -1051,6 +1051,8 @@ interface ClientSessionOptions {
1051
1051
  serializer?: MessageSerializer;
1052
1052
  /** Event emitter for DevTools integration */
1053
1053
  emitter?: KoraEventEmitter;
1054
+ /** Structured logger used for protocol and persistence failures */
1055
+ logger?: Logger;
1054
1056
  /** Max operations per sync batch */
1055
1057
  batchSize?: number;
1056
1058
  /** Schema version the server expects */
@@ -1155,6 +1157,7 @@ declare class ClientSession {
1155
1157
  private readonly auth;
1156
1158
  private readonly serializer;
1157
1159
  private readonly emitter;
1160
+ private readonly logger;
1158
1161
  private readonly batchSize;
1159
1162
  private readonly schemaVersion;
1160
1163
  private readonly supportedSchemaVersions;
@@ -1289,6 +1292,13 @@ interface ProductionServerConfig {
1289
1292
  syncPath?: string;
1290
1293
  /** Additional KoraSyncServer options */
1291
1294
  syncOptions?: Omit<KoraSyncServerConfig, 'store' | 'port' | 'host' | 'path'>;
1295
+ /**
1296
+ * Cross-Origin-Embedder-Policy for static and route responses. Defaults to
1297
+ * `credentialless`, which keeps third-party embeds usable while allowing
1298
+ * capable browsers to enable cross-origin isolation. Use `require-corp` only
1299
+ * when every embedded resource explicitly opts in with CORP/CORS headers.
1300
+ */
1301
+ crossOriginEmbedderPolicy?: 'credentialless' | 'require-corp' | 'unsafe-none';
1292
1302
  /**
1293
1303
  * Optional HTTP route handlers mounted before static file serving.
1294
1304
  *
package/dist/index.d.ts CHANGED
@@ -1051,6 +1051,8 @@ interface ClientSessionOptions {
1051
1051
  serializer?: MessageSerializer;
1052
1052
  /** Event emitter for DevTools integration */
1053
1053
  emitter?: KoraEventEmitter;
1054
+ /** Structured logger used for protocol and persistence failures */
1055
+ logger?: Logger;
1054
1056
  /** Max operations per sync batch */
1055
1057
  batchSize?: number;
1056
1058
  /** Schema version the server expects */
@@ -1155,6 +1157,7 @@ declare class ClientSession {
1155
1157
  private readonly auth;
1156
1158
  private readonly serializer;
1157
1159
  private readonly emitter;
1160
+ private readonly logger;
1158
1161
  private readonly batchSize;
1159
1162
  private readonly schemaVersion;
1160
1163
  private readonly supportedSchemaVersions;
@@ -1289,6 +1292,13 @@ interface ProductionServerConfig {
1289
1292
  syncPath?: string;
1290
1293
  /** Additional KoraSyncServer options */
1291
1294
  syncOptions?: Omit<KoraSyncServerConfig, 'store' | 'port' | 'host' | 'path'>;
1295
+ /**
1296
+ * Cross-Origin-Embedder-Policy for static and route responses. Defaults to
1297
+ * `credentialless`, which keeps third-party embeds usable while allowing
1298
+ * capable browsers to enable cross-origin isolation. Use `require-corp` only
1299
+ * when every embedded resource explicitly opts in with CORP/CORS headers.
1300
+ */
1301
+ crossOriginEmbedderPolicy?: 'credentialless' | 'require-corp' | 'unsafe-none';
1292
1302
  /**
1293
1303
  * Optional HTTP route handlers mounted before static file serving.
1294
1304
  *
package/dist/index.js CHANGED
@@ -196,6 +196,9 @@ function serializeFieldValue(value, descriptor) {
196
196
  if (value === null || value === void 0) return null;
197
197
  switch (descriptor.kind) {
198
198
  case "array":
199
+ case "object":
200
+ case "json":
201
+ case "blob":
199
202
  return typeof value === "string" ? value : JSON.stringify(value);
200
203
  case "boolean":
201
204
  return value ? 1 : 0;
@@ -209,6 +212,9 @@ function deserializeFieldValue(value, descriptor) {
209
212
  if (value === null || value === void 0) return null;
210
213
  switch (descriptor.kind) {
211
214
  case "array":
215
+ case "object":
216
+ case "json":
217
+ case "blob":
212
218
  return typeof value === "string" ? JSON.parse(value) : value;
213
219
  case "boolean":
214
220
  return value === 1 || value === true;
@@ -2530,6 +2536,20 @@ function warnIfMultiTenantWithoutScopes(auth, resolvedScopes, schema) {
2530
2536
  "[kora] An authenticated session resolved to no sync scopes, so every user will sync every other user's data. Return per-user sync scopes from your auth provider (for example KoraAuthProvider's resolveScopes) to isolate tenants. Note: declaring sync rules in your schema is not enough on its own \u2014 the per-user values come from the auth provider. (This warning is expected for single-tenant apps where all authenticated users are meant to share the same data.)"
2531
2537
  );
2532
2538
  }
2539
+ function stableStringify(value) {
2540
+ if (Array.isArray(value)) {
2541
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
2542
+ }
2543
+ if (value && typeof value === "object") {
2544
+ const record = value;
2545
+ const entries = Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
2546
+ return `{${entries.join(",")}}`;
2547
+ }
2548
+ return JSON.stringify(value);
2549
+ }
2550
+ function sameScopeMap(a, b) {
2551
+ return stableStringify(a ?? null) === stableStringify(b ?? null);
2552
+ }
2533
2553
  var ClientSession = class _ClientSession {
2534
2554
  state = "connected";
2535
2555
  clientNodeId = null;
@@ -2571,6 +2591,7 @@ var ClientSession = class _ClientSession {
2571
2591
  auth;
2572
2592
  serializer;
2573
2593
  emitter;
2594
+ logger;
2574
2595
  batchSize;
2575
2596
  schemaVersion;
2576
2597
  supportedSchemaVersions;
@@ -2595,6 +2616,7 @@ var ClientSession = class _ClientSession {
2595
2616
  this.auth = options.auth ?? null;
2596
2617
  this.serializer = options.serializer ?? new NegotiatedMessageSerializer("json");
2597
2618
  this.emitter = options.emitter ?? null;
2619
+ this.logger = options.logger ?? null;
2598
2620
  this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
2599
2621
  this.schemaVersion = options.schemaVersion ?? DEFAULT_SCHEMA_VERSION;
2600
2622
  const supported = options.supportedSchemaVersions;
@@ -2829,6 +2851,18 @@ var ClientSession = class _ClientSession {
2829
2851
  }
2830
2852
  handleMessageFailure(error) {
2831
2853
  const reason = error instanceof Error ? error.message : "Message handling failed";
2854
+ this.logger?.log({
2855
+ timestamp: Date.now(),
2856
+ level: "error",
2857
+ event: "session.message_failed",
2858
+ sessionId: this.sessionId,
2859
+ nodeId: this.clientNodeId ?? void 0,
2860
+ error: reason,
2861
+ details: {
2862
+ state: this.state,
2863
+ errorName: error instanceof Error ? error.name : typeof error
2864
+ }
2865
+ });
2832
2866
  this.sendError("SYNC_ERROR", reason, true);
2833
2867
  this.close(reason);
2834
2868
  }
@@ -2868,6 +2902,9 @@ var ClientSession = class _ClientSession {
2868
2902
  }
2869
2903
  this.resumeDeltaCursor = msg.deltaCursor ? decodeDeltaCursor(msg.deltaCursor) : null;
2870
2904
  this.clientDeliveryWatermark = msg.lastDeliverySequence ?? null;
2905
+ if (this.clientDeliveryWatermark !== null && !sameScopeMap(msg.syncScope, this.authContext?.scopes)) {
2906
+ this.clientDeliveryWatermark = 0;
2907
+ }
2871
2908
  const clientUsesWatermark = this.clientDeliveryWatermark !== null;
2872
2909
  let serverMaxDelivery = 0;
2873
2910
  if (clientUsesWatermark) {
@@ -2995,20 +3032,17 @@ var ClientSession = class _ClientSession {
2995
3032
  continue;
2996
3033
  }
2997
3034
  }
2998
- try {
2999
- const applyResult = await applyServerOperation(this.store, op);
3000
- if (applyResult.rejection) {
3001
- this.sendError(
3002
- applyResult.rejection.code,
3003
- applyResult.rejection.message,
3004
- applyResult.rejection.retriable
3005
- );
3006
- continue;
3007
- }
3008
- if (applyResult.result === "applied") {
3009
- applied.push(...applyResult.appliedOperations);
3010
- }
3011
- } catch {
3035
+ const applyResult = await applyServerOperation(this.store, op);
3036
+ if (applyResult.rejection) {
3037
+ this.sendError(
3038
+ applyResult.rejection.code,
3039
+ applyResult.rejection.message,
3040
+ applyResult.rejection.retriable
3041
+ );
3042
+ continue;
3043
+ }
3044
+ if (applyResult.result === "applied") {
3045
+ applied.push(...applyResult.appliedOperations);
3012
3046
  }
3013
3047
  }
3014
3048
  if (rejected.length > 0) {
@@ -4406,6 +4440,7 @@ var KoraSyncServer = class _KoraSyncServer {
4406
4440
  auth: this.auth ?? void 0,
4407
4441
  serializer: this.serializer,
4408
4442
  emitter: sessionEmitter,
4443
+ logger: this.logger,
4409
4444
  batchSize: this.batchSize,
4410
4445
  schemaVersion: this.schemaVersion,
4411
4446
  supportedSchemaVersions: this.supportedSchemaVersions,
@@ -4729,6 +4764,7 @@ function createProductionServer(config) {
4729
4764
  const port = config.port ?? (Number(process.env.PORT) || 3001);
4730
4765
  const staticDir = config.staticDir ?? "./dist";
4731
4766
  const syncPath = config.syncPath ?? "/kora-sync";
4767
+ const crossOriginEmbedderPolicy = config.crossOriginEmbedderPolicy ?? "credentialless";
4732
4768
  const syncServer = new KoraSyncServer({
4733
4769
  store: config.store,
4734
4770
  enableDashboard: true,
@@ -4899,7 +4935,7 @@ function createProductionServer(config) {
4899
4935
  });
4900
4936
  async function handleRequest(req, res) {
4901
4937
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
4902
- res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
4938
+ res.setHeader("Cross-Origin-Embedder-Policy", crossOriginEmbedderPolicy);
4903
4939
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
4904
4940
  if (url.pathname === "/health") {
4905
4941
  const status = await syncServer.getStatus();