@syncular/server-hono 0.0.6-93 → 0.0.6-96
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/console/routes.d.ts.map +1 -1
- package/dist/console/routes.js +212 -40
- package/dist/console/routes.js.map +1 -1
- package/dist/console/types.d.ts +38 -0
- package/dist/console/types.d.ts.map +1 -1
- package/dist/create-server.d.ts.map +1 -1
- package/dist/create-server.js +7 -1
- package/dist/create-server.js.map +1 -1
- package/dist/routes.d.ts +21 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +92 -56
- package/dist/routes.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/console-routes.test.ts +155 -5
- package/src/__tests__/create-server.test.ts +147 -4
- package/src/console/routes.ts +280 -48
- package/src/console/types.ts +39 -0
- package/src/create-server.ts +8 -1
- package/src/routes.ts +134 -63
package/src/routes.ts
CHANGED
|
@@ -121,6 +121,22 @@ export interface SyncRoutesConfigWithRateLimit {
|
|
|
121
121
|
* Default: 200
|
|
122
122
|
*/
|
|
123
123
|
maxOperationsPerPush?: number;
|
|
124
|
+
/**
|
|
125
|
+
* Request/response payload snapshots recorded for console inspection.
|
|
126
|
+
*/
|
|
127
|
+
requestPayloadSnapshots?: {
|
|
128
|
+
/**
|
|
129
|
+
* Enable payload snapshot storage in `sync_request_payloads`.
|
|
130
|
+
* Default: true when console event recording is enabled.
|
|
131
|
+
*/
|
|
132
|
+
enabled?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Max serialized payload size in bytes per request/response snapshot.
|
|
135
|
+
* Larger payloads are truncated with metadata.
|
|
136
|
+
* Default: 128 KiB.
|
|
137
|
+
*/
|
|
138
|
+
maxBytes?: number;
|
|
139
|
+
};
|
|
124
140
|
/**
|
|
125
141
|
* Rate limiting configuration.
|
|
126
142
|
* Set to false to disable all rate limiting.
|
|
@@ -197,6 +213,11 @@ export interface CreateSyncRoutesOptions<
|
|
|
197
213
|
data: Record<string, unknown>;
|
|
198
214
|
}): void;
|
|
199
215
|
};
|
|
216
|
+
/**
|
|
217
|
+
* Optional console schema readiness promise.
|
|
218
|
+
* When provided, request-event recording waits for this promise before writing.
|
|
219
|
+
*/
|
|
220
|
+
consoleSchemaReady?: Promise<void>;
|
|
200
221
|
}
|
|
201
222
|
|
|
202
223
|
// ============================================================================
|
|
@@ -207,7 +228,7 @@ const snapshotChunkParamsSchema = z.object({
|
|
|
207
228
|
chunkId: z.string().min(1),
|
|
208
229
|
});
|
|
209
230
|
|
|
210
|
-
const
|
|
231
|
+
const DEFAULT_REQUEST_PAYLOAD_SNAPSHOT_MAX_BYTES = 128 * 1024;
|
|
211
232
|
|
|
212
233
|
type TraceContext = {
|
|
213
234
|
traceId: string | null;
|
|
@@ -254,6 +275,19 @@ function parseSentryTraceHeader(
|
|
|
254
275
|
return { traceId, spanId };
|
|
255
276
|
}
|
|
256
277
|
|
|
278
|
+
function readPositiveInteger(
|
|
279
|
+
value: number | undefined,
|
|
280
|
+
fallback: number
|
|
281
|
+
): number {
|
|
282
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
283
|
+
return fallback;
|
|
284
|
+
}
|
|
285
|
+
if (value <= 0) {
|
|
286
|
+
return fallback;
|
|
287
|
+
}
|
|
288
|
+
return Math.floor(value);
|
|
289
|
+
}
|
|
290
|
+
|
|
257
291
|
function readTraceContext(c: Context): TraceContext {
|
|
258
292
|
const traceparent = parseW3cTraceparent(c.req.header('traceparent'));
|
|
259
293
|
if (traceparent) return traceparent;
|
|
@@ -401,16 +435,16 @@ function countPullRows(response: PullResult['response']): number {
|
|
|
401
435
|
}, 0);
|
|
402
436
|
}
|
|
403
437
|
|
|
404
|
-
function encodePayloadSnapshot(value: unknown): string {
|
|
438
|
+
function encodePayloadSnapshot(value: unknown, maxBytes: number): string {
|
|
405
439
|
try {
|
|
406
440
|
const serialized = JSON.stringify(value);
|
|
407
|
-
if (serialized.length <=
|
|
441
|
+
if (serialized.length <= maxBytes) {
|
|
408
442
|
return serialized;
|
|
409
443
|
}
|
|
410
444
|
return JSON.stringify({
|
|
411
445
|
truncated: true,
|
|
412
446
|
originalSizeBytes: serialized.length,
|
|
413
|
-
preview: serialized.slice(0,
|
|
447
|
+
preview: serialized.slice(0, maxBytes),
|
|
414
448
|
});
|
|
415
449
|
} catch {
|
|
416
450
|
return JSON.stringify({
|
|
@@ -465,6 +499,25 @@ export function createSyncRoutes<
|
|
|
465
499
|
const consoleLiveEmitter = options.consoleLiveEmitter;
|
|
466
500
|
const shouldEmitConsoleLiveEvents = consoleLiveEmitter !== undefined;
|
|
467
501
|
const shouldRecordRequestEvents = shouldEmitConsoleLiveEvents;
|
|
502
|
+
const shouldCaptureRequestPayloadSnapshots =
|
|
503
|
+
shouldRecordRequestEvents &&
|
|
504
|
+
config.requestPayloadSnapshots?.enabled !== false;
|
|
505
|
+
const requestPayloadSnapshotMaxBytes = readPositiveInteger(
|
|
506
|
+
config.requestPayloadSnapshots?.maxBytes,
|
|
507
|
+
DEFAULT_REQUEST_PAYLOAD_SNAPSHOT_MAX_BYTES
|
|
508
|
+
);
|
|
509
|
+
const consoleSchemaReadyBase = shouldRecordRequestEvents
|
|
510
|
+
? (options.consoleSchemaReady ??
|
|
511
|
+
options.dialect.ensureConsoleSchema?.(options.db) ??
|
|
512
|
+
Promise.resolve())
|
|
513
|
+
: Promise.resolve();
|
|
514
|
+
const consoleSchemaReady = consoleSchemaReadyBase.catch((error) => {
|
|
515
|
+
logSyncEvent({
|
|
516
|
+
event: 'sync.console_schema_ready_failed',
|
|
517
|
+
error: error instanceof Error ? error.message : String(error),
|
|
518
|
+
});
|
|
519
|
+
throw error;
|
|
520
|
+
});
|
|
468
521
|
|
|
469
522
|
// -------------------------------------------------------------------------
|
|
470
523
|
// Optional WebSocket manager (scope-key based wake-ups)
|
|
@@ -575,8 +628,14 @@ export function createSyncRoutes<
|
|
|
575
628
|
payload_ref, partition_id, request_payload, response_payload, created_at
|
|
576
629
|
) VALUES (
|
|
577
630
|
${nextPayloadRef}, ${event.partitionId},
|
|
578
|
-
${encodePayloadSnapshot(
|
|
579
|
-
|
|
631
|
+
${encodePayloadSnapshot(
|
|
632
|
+
event.payloadSnapshot.request,
|
|
633
|
+
requestPayloadSnapshotMaxBytes
|
|
634
|
+
)},
|
|
635
|
+
${encodePayloadSnapshot(
|
|
636
|
+
event.payloadSnapshot.response,
|
|
637
|
+
requestPayloadSnapshotMaxBytes
|
|
638
|
+
)},
|
|
580
639
|
${nowIso}
|
|
581
640
|
)
|
|
582
641
|
ON CONFLICT (payload_ref) DO UPDATE SET
|
|
@@ -630,15 +689,17 @@ export function createSyncRoutes<
|
|
|
630
689
|
|
|
631
690
|
const resolvedEvent = typeof event === 'function' ? event() : event;
|
|
632
691
|
|
|
633
|
-
void
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
692
|
+
void consoleSchemaReady
|
|
693
|
+
.then(() => recordRequestEvent(resolvedEvent))
|
|
694
|
+
.catch((error) => {
|
|
695
|
+
logAsyncFailureOnce('sync.request_event_record_failed', {
|
|
696
|
+
event: 'sync.request_event_record_failed',
|
|
697
|
+
userId: resolvedEvent.actorId,
|
|
698
|
+
clientId: resolvedEvent.clientId,
|
|
699
|
+
requestEventType: resolvedEvent.eventType,
|
|
700
|
+
error: error instanceof Error ? error.message : String(error),
|
|
701
|
+
});
|
|
640
702
|
});
|
|
641
|
-
});
|
|
642
703
|
};
|
|
643
704
|
|
|
644
705
|
const authCache = new WeakMap<Context, Promise<Auth | null>>();
|
|
@@ -835,15 +896,17 @@ export function createSyncRoutes<
|
|
|
835
896
|
commitSeq: pushed.response.commitSeq,
|
|
836
897
|
operationCount: pushOps.length,
|
|
837
898
|
tables: pushed.affectedTables,
|
|
838
|
-
payloadSnapshot:
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
899
|
+
payloadSnapshot: shouldCaptureRequestPayloadSnapshots
|
|
900
|
+
? {
|
|
901
|
+
request: {
|
|
902
|
+
clientId,
|
|
903
|
+
clientCommitId: pushBody.clientCommitId,
|
|
904
|
+
schemaVersion: pushBody.schemaVersion,
|
|
905
|
+
operations: pushBody.operations,
|
|
906
|
+
},
|
|
907
|
+
response: pushed.response,
|
|
908
|
+
}
|
|
909
|
+
: null,
|
|
847
910
|
}));
|
|
848
911
|
emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
|
|
849
912
|
partitionId,
|
|
@@ -1054,7 +1117,7 @@ export function createSyncRoutes<
|
|
|
1054
1117
|
const scopesSummary = shouldRecordRequestEvents
|
|
1055
1118
|
? summarizeScopeValues(pullResult.effectiveScopes)
|
|
1056
1119
|
: null;
|
|
1057
|
-
const payloadSnapshot =
|
|
1120
|
+
const payloadSnapshot = shouldCaptureRequestPayloadSnapshots
|
|
1058
1121
|
? {
|
|
1059
1122
|
request: {
|
|
1060
1123
|
clientId,
|
|
@@ -1567,14 +1630,16 @@ export function createSyncRoutes<
|
|
|
1567
1630
|
durationMs: invalidDurationMs,
|
|
1568
1631
|
errorCode: 'INVALID_PUSH_PAYLOAD',
|
|
1569
1632
|
errorMessage: 'Invalid push payload',
|
|
1570
|
-
payloadSnapshot:
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1633
|
+
payloadSnapshot: shouldCaptureRequestPayloadSnapshots
|
|
1634
|
+
? {
|
|
1635
|
+
request: msg,
|
|
1636
|
+
response: {
|
|
1637
|
+
ok: false,
|
|
1638
|
+
status: 'rejected',
|
|
1639
|
+
reason: 'invalid_push_payload',
|
|
1640
|
+
},
|
|
1641
|
+
}
|
|
1642
|
+
: null,
|
|
1578
1643
|
}));
|
|
1579
1644
|
emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
|
|
1580
1645
|
partitionId,
|
|
@@ -1625,19 +1690,21 @@ export function createSyncRoutes<
|
|
|
1625
1690
|
errorCode: 'MAX_OPERATIONS_EXCEEDED',
|
|
1626
1691
|
errorMessage: `Maximum ${maxOperationsPerPush} operations per push`,
|
|
1627
1692
|
operationCount: pushOps.length,
|
|
1628
|
-
payloadSnapshot:
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1693
|
+
payloadSnapshot: shouldCaptureRequestPayloadSnapshots
|
|
1694
|
+
? {
|
|
1695
|
+
request: {
|
|
1696
|
+
clientId,
|
|
1697
|
+
clientCommitId: parsed.data.clientCommitId,
|
|
1698
|
+
schemaVersion: parsed.data.schemaVersion,
|
|
1699
|
+
operations: parsed.data.operations,
|
|
1700
|
+
},
|
|
1701
|
+
response: {
|
|
1702
|
+
ok: false,
|
|
1703
|
+
status: 'rejected',
|
|
1704
|
+
reason: 'max_operations_exceeded',
|
|
1705
|
+
},
|
|
1706
|
+
}
|
|
1707
|
+
: null,
|
|
1641
1708
|
}));
|
|
1642
1709
|
emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
|
|
1643
1710
|
partitionId,
|
|
@@ -1699,15 +1766,17 @@ export function createSyncRoutes<
|
|
|
1699
1766
|
commitSeq: pushed.response.commitSeq,
|
|
1700
1767
|
operationCount: pushOps.length,
|
|
1701
1768
|
tables: pushed.affectedTables,
|
|
1702
|
-
payloadSnapshot:
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1769
|
+
payloadSnapshot: shouldCaptureRequestPayloadSnapshots
|
|
1770
|
+
? {
|
|
1771
|
+
request: {
|
|
1772
|
+
clientId,
|
|
1773
|
+
clientCommitId: parsed.data.clientCommitId,
|
|
1774
|
+
schemaVersion: parsed.data.schemaVersion,
|
|
1775
|
+
operations: parsed.data.operations,
|
|
1776
|
+
},
|
|
1777
|
+
response: pushed.response,
|
|
1778
|
+
}
|
|
1779
|
+
: null,
|
|
1711
1780
|
}));
|
|
1712
1781
|
emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
|
|
1713
1782
|
partitionId,
|
|
@@ -1829,15 +1898,17 @@ export function createSyncRoutes<
|
|
|
1829
1898
|
durationMs: failedDurationMs,
|
|
1830
1899
|
errorCode: 'INTERNAL_SERVER_ERROR',
|
|
1831
1900
|
errorMessage: message,
|
|
1832
|
-
payloadSnapshot:
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1901
|
+
payloadSnapshot: shouldCaptureRequestPayloadSnapshots
|
|
1902
|
+
? {
|
|
1903
|
+
request: msg,
|
|
1904
|
+
response: {
|
|
1905
|
+
ok: false,
|
|
1906
|
+
status: 'rejected',
|
|
1907
|
+
reason: 'internal_server_error',
|
|
1908
|
+
message,
|
|
1909
|
+
},
|
|
1910
|
+
}
|
|
1911
|
+
: null,
|
|
1841
1912
|
}));
|
|
1842
1913
|
emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
|
|
1843
1914
|
partitionId,
|