@syncular/server-hono 0.0.6-95 → 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/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.
@@ -212,7 +228,7 @@ const snapshotChunkParamsSchema = z.object({
212
228
  chunkId: z.string().min(1),
213
229
  });
214
230
 
215
- const MAX_REQUEST_PAYLOAD_SNAPSHOT_BYTES = 128 * 1024;
231
+ const DEFAULT_REQUEST_PAYLOAD_SNAPSHOT_MAX_BYTES = 128 * 1024;
216
232
 
217
233
  type TraceContext = {
218
234
  traceId: string | null;
@@ -259,6 +275,19 @@ function parseSentryTraceHeader(
259
275
  return { traceId, spanId };
260
276
  }
261
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
+
262
291
  function readTraceContext(c: Context): TraceContext {
263
292
  const traceparent = parseW3cTraceparent(c.req.header('traceparent'));
264
293
  if (traceparent) return traceparent;
@@ -406,16 +435,16 @@ function countPullRows(response: PullResult['response']): number {
406
435
  }, 0);
407
436
  }
408
437
 
409
- function encodePayloadSnapshot(value: unknown): string {
438
+ function encodePayloadSnapshot(value: unknown, maxBytes: number): string {
410
439
  try {
411
440
  const serialized = JSON.stringify(value);
412
- if (serialized.length <= MAX_REQUEST_PAYLOAD_SNAPSHOT_BYTES) {
441
+ if (serialized.length <= maxBytes) {
413
442
  return serialized;
414
443
  }
415
444
  return JSON.stringify({
416
445
  truncated: true,
417
446
  originalSizeBytes: serialized.length,
418
- preview: serialized.slice(0, MAX_REQUEST_PAYLOAD_SNAPSHOT_BYTES),
447
+ preview: serialized.slice(0, maxBytes),
419
448
  });
420
449
  } catch {
421
450
  return JSON.stringify({
@@ -470,6 +499,13 @@ export function createSyncRoutes<
470
499
  const consoleLiveEmitter = options.consoleLiveEmitter;
471
500
  const shouldEmitConsoleLiveEvents = consoleLiveEmitter !== undefined;
472
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
+ );
473
509
  const consoleSchemaReadyBase = shouldRecordRequestEvents
474
510
  ? (options.consoleSchemaReady ??
475
511
  options.dialect.ensureConsoleSchema?.(options.db) ??
@@ -592,8 +628,14 @@ export function createSyncRoutes<
592
628
  payload_ref, partition_id, request_payload, response_payload, created_at
593
629
  ) VALUES (
594
630
  ${nextPayloadRef}, ${event.partitionId},
595
- ${encodePayloadSnapshot(event.payloadSnapshot.request)},
596
- ${encodePayloadSnapshot(event.payloadSnapshot.response)},
631
+ ${encodePayloadSnapshot(
632
+ event.payloadSnapshot.request,
633
+ requestPayloadSnapshotMaxBytes
634
+ )},
635
+ ${encodePayloadSnapshot(
636
+ event.payloadSnapshot.response,
637
+ requestPayloadSnapshotMaxBytes
638
+ )},
597
639
  ${nowIso}
598
640
  )
599
641
  ON CONFLICT (payload_ref) DO UPDATE SET
@@ -854,15 +896,17 @@ export function createSyncRoutes<
854
896
  commitSeq: pushed.response.commitSeq,
855
897
  operationCount: pushOps.length,
856
898
  tables: pushed.affectedTables,
857
- payloadSnapshot: {
858
- request: {
859
- clientId,
860
- clientCommitId: pushBody.clientCommitId,
861
- schemaVersion: pushBody.schemaVersion,
862
- operations: pushBody.operations,
863
- },
864
- response: pushed.response,
865
- },
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,
866
910
  }));
867
911
  emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
868
912
  partitionId,
@@ -1073,7 +1117,7 @@ export function createSyncRoutes<
1073
1117
  const scopesSummary = shouldRecordRequestEvents
1074
1118
  ? summarizeScopeValues(pullResult.effectiveScopes)
1075
1119
  : null;
1076
- const payloadSnapshot = shouldRecordRequestEvents
1120
+ const payloadSnapshot = shouldCaptureRequestPayloadSnapshots
1077
1121
  ? {
1078
1122
  request: {
1079
1123
  clientId,
@@ -1586,14 +1630,16 @@ export function createSyncRoutes<
1586
1630
  durationMs: invalidDurationMs,
1587
1631
  errorCode: 'INVALID_PUSH_PAYLOAD',
1588
1632
  errorMessage: 'Invalid push payload',
1589
- payloadSnapshot: {
1590
- request: msg,
1591
- response: {
1592
- ok: false,
1593
- status: 'rejected',
1594
- reason: 'invalid_push_payload',
1595
- },
1596
- },
1633
+ payloadSnapshot: shouldCaptureRequestPayloadSnapshots
1634
+ ? {
1635
+ request: msg,
1636
+ response: {
1637
+ ok: false,
1638
+ status: 'rejected',
1639
+ reason: 'invalid_push_payload',
1640
+ },
1641
+ }
1642
+ : null,
1597
1643
  }));
1598
1644
  emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
1599
1645
  partitionId,
@@ -1644,19 +1690,21 @@ export function createSyncRoutes<
1644
1690
  errorCode: 'MAX_OPERATIONS_EXCEEDED',
1645
1691
  errorMessage: `Maximum ${maxOperationsPerPush} operations per push`,
1646
1692
  operationCount: pushOps.length,
1647
- payloadSnapshot: {
1648
- request: {
1649
- clientId,
1650
- clientCommitId: parsed.data.clientCommitId,
1651
- schemaVersion: parsed.data.schemaVersion,
1652
- operations: parsed.data.operations,
1653
- },
1654
- response: {
1655
- ok: false,
1656
- status: 'rejected',
1657
- reason: 'max_operations_exceeded',
1658
- },
1659
- },
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,
1660
1708
  }));
1661
1709
  emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
1662
1710
  partitionId,
@@ -1718,15 +1766,17 @@ export function createSyncRoutes<
1718
1766
  commitSeq: pushed.response.commitSeq,
1719
1767
  operationCount: pushOps.length,
1720
1768
  tables: pushed.affectedTables,
1721
- payloadSnapshot: {
1722
- request: {
1723
- clientId,
1724
- clientCommitId: parsed.data.clientCommitId,
1725
- schemaVersion: parsed.data.schemaVersion,
1726
- operations: parsed.data.operations,
1727
- },
1728
- response: pushed.response,
1729
- },
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,
1730
1780
  }));
1731
1781
  emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
1732
1782
  partitionId,
@@ -1848,15 +1898,17 @@ export function createSyncRoutes<
1848
1898
  durationMs: failedDurationMs,
1849
1899
  errorCode: 'INTERNAL_SERVER_ERROR',
1850
1900
  errorMessage: message,
1851
- payloadSnapshot: {
1852
- request: msg,
1853
- response: {
1854
- ok: false,
1855
- status: 'rejected',
1856
- reason: 'internal_server_error',
1857
- message,
1858
- },
1859
- },
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,
1860
1912
  }));
1861
1913
  emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
1862
1914
  partitionId,