@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.cjs +51 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +51 -15
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
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",
|
|
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();
|