@rocicorp/zero 1.8.0-canary.4 → 1.8.0-canary.6

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.
Files changed (88) hide show
  1. package/out/zero/package.js +1 -1
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/custom/fetch.d.ts +6 -1
  4. package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
  5. package/out/zero-cache/src/custom/fetch.js +255 -119
  6. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  7. package/out/zero-cache/src/custom/metrics.d.ts +31 -0
  8. package/out/zero-cache/src/custom/metrics.d.ts.map +1 -0
  9. package/out/zero-cache/src/custom/metrics.js +47 -0
  10. package/out/zero-cache/src/custom/metrics.js.map +1 -0
  11. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
  12. package/out/zero-cache/src/custom-queries/transform-query.js +1 -1
  13. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  14. package/out/zero-cache/src/db/migration-lite.d.ts +0 -6
  15. package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
  16. package/out/zero-cache/src/db/migration-lite.js +2 -22
  17. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  18. package/out/zero-cache/src/observability/metrics.d.ts +3 -0
  19. package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
  20. package/out/zero-cache/src/observability/metrics.js +21 -1
  21. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  22. package/out/zero-cache/src/server/anonymous-otel-start.js +1 -1
  23. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  24. package/out/zero-cache/src/server/main.js +4 -2
  25. package/out/zero-cache/src/server/main.js.map +1 -1
  26. package/out/zero-cache/src/server/otel-start.d.ts.map +1 -1
  27. package/out/zero-cache/src/server/otel-start.js +7 -0
  28. package/out/zero-cache/src/server/otel-start.js.map +1 -1
  29. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  30. package/out/zero-cache/src/services/change-source/common/replica-schema.js +2 -2
  31. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  32. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  33. package/out/zero-cache/src/services/change-source/pg/change-source.js +4 -0
  34. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  35. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.d.ts +15 -0
  36. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.d.ts.map +1 -0
  37. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js +72 -0
  38. package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js.map +1 -0
  39. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +2 -0
  40. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
  41. package/out/zero-cache/src/services/change-streamer/broadcast.js +11 -4
  42. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
  43. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/change-streamer/forwarder.js +43 -2
  45. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  46. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  48. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +8 -2
  49. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  50. package/out/zero-cache/src/services/change-streamer/subscriber.js +112 -12
  51. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  52. package/out/zero-cache/src/services/life-cycle.d.ts +1 -0
  53. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  54. package/out/zero-cache/src/services/life-cycle.js +41 -1
  55. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  56. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  57. package/out/zero-cache/src/services/litestream/commands.js +0 -2
  58. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  59. package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
  60. package/out/zero-cache/src/services/litestream/metrics.js +14 -29
  61. package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
  62. package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +2 -2
  63. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  64. package/out/zero-cache/src/services/mutagen/pusher.js +9 -3
  65. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  66. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js +10 -6
  67. package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
  68. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  69. package/out/zero-cache/src/services/view-syncer/cvr-store.js +48 -11
  70. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  71. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  72. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +12 -0
  73. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  74. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  75. package/out/zero-cache/src/services/view-syncer/view-syncer.js +10 -3
  76. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  77. package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
  78. package/out/zero-cache/src/workers/connection.js +12 -0
  79. package/out/zero-cache/src/workers/connection.js.map +1 -1
  80. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  81. package/out/zero-cache/src/workers/syncer.js +72 -22
  82. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  83. package/out/zero-client/src/client/version.js +1 -1
  84. package/out/zero-protocol/src/error.js +1 -1
  85. package/out/zqlite/src/db.js +2 -2
  86. package/out/zqlite/src/sqlite-cost-model.js +5 -5
  87. package/out/zqlite/src/table-source.js +3 -3
  88. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/litestream/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,6BAA6B,CAAC;AAM5D,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,aAAa,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,iBAAiB,CAAC;CAC/B,CAAC;AAEF,KAAK,8BAA8B,GAAG;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAMF,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,cAAc,EACpB,SAAS,qBAA8B,GACtC,qBAAqB,GAAG,8BAA8B,CAaxD;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,UAAU,GACjB,qBAAqB,CAUvB;AAED,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,UAAU,GACjB,qBAAqB,GAAG,8BAA8B,CAKxD;AAED,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,iBAAiB,EAC7B,IAAI,EAAE,cAAc,GACnB,qBAAqB,CAMvB;AAyBD,wBAAgB,qBAAqB,kFAMpC;AAED,wBAAgB,yBAAyB,kFAMxC;AAED,wBAAgB,yBAAyB,kFAMxC;AAED,wBAAgB,2BAA2B,kFAM1C;AAED,wBAAgB,yBAAyB,8DAKxC;AAED,wBAAgB,6BAA6B,8DAK5C;AAED,wBAAgB,gCAAgC,8DAK/C;AAED,wBAAgB,mCAAmC,8DAKlD;AAED,wBAAgB,+BAA+B,8DAK9C;AAED,wBAAgB,4BAA4B,8DAK3C;AAED,wBAAgB,oCAAoC,8DAKnD;AAED,wBAAgB,qCAAqC,8DAKpD"}
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/litestream/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,6BAA6B,CAAC;AAO5D,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,aAAa,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,iBAAiB,CAAC;CAC/B,CAAC;AAEF,KAAK,8BAA8B,GAAG;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,cAAc,EACpB,SAAS,qBAA8B,GACtC,qBAAqB,GAAG,8BAA8B,CAaxD;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,UAAU,GACjB,qBAAqB,CAUvB;AAED,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,UAAU,GACjB,qBAAqB,GAAG,8BAA8B,CAKxD;AAED,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,iBAAiB,EAC7B,IAAI,EAAE,cAAc,GACnB,qBAAqB,CAMvB;AAyBD,wBAAgB,qBAAqB,kFAMpC;AAED,wBAAgB,yBAAyB,kFAMxC;AAED,wBAAgB,yBAAyB,kFAMxC;AAED,wBAAgB,2BAA2B,kFAM1C;AAED,wBAAgB,yBAAyB,8DAKxC;AAED,wBAAgB,6BAA6B,8DAK5C;AAED,wBAAgB,gCAAgC,8DAK/C;AAED,wBAAgB,mCAAmC,8DAKlD;AAED,wBAAgB,+BAA+B,8DAK9C;AAED,wBAAgB,4BAA4B,8DAK3C;AAED,wBAAgB,oCAAoC,8DAKnD;AAED,wBAAgB,qCAAqC,8DAKpD"}
@@ -1,20 +1,5 @@
1
- import { getOrCreateCounter, getOrCreateHistogram } from "../../observability/metrics.js";
1
+ import { LONG_DURATION_HISTOGRAM_BOUNDARIES_S, getOrCreateCounter, getOrCreateHistogram } from "../../observability/metrics.js";
2
2
  //#region ../zero-cache/src/services/litestream/metrics.ts
3
- var LITESTREAM_DURATION_HISTOGRAM_BOUNDARIES_S = [
4
- 1,
5
- 2,
6
- 5,
7
- 10,
8
- 30,
9
- 60,
10
- 120,
11
- 300,
12
- 600,
13
- 1200,
14
- 2400,
15
- 3600,
16
- 7200
17
- ];
18
3
  function litestreamRestoreMetricAttrs(config, role, backupURL = config.litestream.backupURL) {
19
4
  const { executable, executableV5, restoreUsingV5 } = config.litestream;
20
5
  const selectedExecutable = (restoreUsingV5 ? executableV5 : executable) ?? executable;
@@ -61,49 +46,49 @@ function litestreamMultipartMetricAttrs(config) {
61
46
  };
62
47
  }
63
48
  function litestreamRestoreRuns() {
64
- return getOrCreateCounter("replica", "litestream_restore_runs", "Litestream restore runs, labeled by result.");
49
+ return getOrCreateCounter("replica", "litestream.restore.runs", "Litestream restore runs, labeled by result.");
65
50
  }
66
51
  function litestreamRestoreAttempts() {
67
- return getOrCreateCounter("replica", "litestream_restore_attempts", "Litestream restore subprocess attempts, labeled by result.");
52
+ return getOrCreateCounter("replica", "litestream.restore.attempts", "Litestream restore subprocess attempts, labeled by result.");
68
53
  }
69
54
  function litestreamRestoredDbBytes() {
70
- return getOrCreateCounter("replica", "litestream_restored_db", {
55
+ return getOrCreateCounter("replica", "litestream.restore.db_bytes", {
71
56
  description: "SQLite database bytes restored by successful litestream restores.",
72
57
  unit: "bytes"
73
58
  });
74
59
  }
75
60
  function litestreamBackupProcessRuns() {
76
- return getOrCreateCounter("replica", "litestream_backup_process_runs", "Litestream backup process exits, labeled by result.");
61
+ return getOrCreateCounter("replica", "litestream.backup.process_runs", "Litestream backup process exits, labeled by result.");
77
62
  }
78
63
  function litestreamRestoreDuration() {
79
- return litestreamDurationHistogram("litestream_restore_duration", "Wall-clock duration of a litestream restore run, labeled by result.");
64
+ return litestreamDurationHistogram("litestream.restore.duration", "Wall-clock duration of a litestream restore run, labeled by result.");
80
65
  }
81
66
  function litestreamRestoreWaitDuration() {
82
- return litestreamDurationHistogram("litestream_restore_wait_duration", "Time spent waiting for the replication-manager snapshot status before restoring.");
67
+ return litestreamDurationHistogram("litestream.restore.wait_duration", "Time spent waiting for the replication-manager snapshot status before restoring.");
83
68
  }
84
69
  function litestreamRestoreProcessDuration() {
85
- return litestreamDurationHistogram("litestream_restore_process_duration", "Wall-clock duration of the litestream restore subprocess.");
70
+ return litestreamDurationHistogram("litestream.restore.process_duration", "Wall-clock duration of the litestream restore subprocess.");
86
71
  }
87
72
  function litestreamRestoreValidationDuration() {
88
- return litestreamDurationHistogram("litestream_restore_validation_duration", "Time spent validating a restored replica database.");
73
+ return litestreamDurationHistogram("litestream.restore.validation_duration", "Time spent validating a restored replica database.");
89
74
  }
90
75
  function litestreamBackupProcessDuration() {
91
- return litestreamDurationHistogram("litestream_backup_process_duration", "Runtime duration of the litestream backup subprocess before it exits.");
76
+ return litestreamDurationHistogram("litestream.backup.process_duration", "Runtime duration of the litestream backup subprocess before it exits.");
92
77
  }
93
78
  function litestreamBackupListDuration() {
94
- return litestreamDurationHistogram("litestream_backup_list_duration", "Duration of litestream backup destination listing commands.");
79
+ return litestreamDurationHistogram("litestream.backup.list_duration", "Duration of litestream backup destination listing commands.");
95
80
  }
96
81
  function litestreamBackupVerificationDuration() {
97
- return litestreamDurationHistogram("litestream_backup_verification_duration", "Duration of verifying the actual backup state in the backup destination.");
82
+ return litestreamDurationHistogram("litestream.backup.verification_duration", "Duration of verifying the actual backup state in the backup destination.");
98
83
  }
99
84
  function litestreamSnapshotReservationDuration() {
100
- return litestreamDurationHistogram("litestream_snapshot_reservation_duration", "Duration of a snapshot reservation while a view-syncer restores and subscribes.");
85
+ return litestreamDurationHistogram("litestream.snapshot.reservation_duration", "Duration of a snapshot reservation while a view-syncer restores and subscribes.");
101
86
  }
102
87
  function litestreamDurationHistogram(name, description) {
103
88
  return getOrCreateHistogram("replica", name, {
104
89
  description,
105
90
  unit: "s",
106
- bucketBoundaries: LITESTREAM_DURATION_HISTOGRAM_BOUNDARIES_S
91
+ bucketBoundaries: LONG_DURATION_HISTOGRAM_BOUNDARIES_S
107
92
  });
108
93
  }
109
94
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","names":[],"sources":["../../../../../../zero-cache/src/services/litestream/metrics.ts"],"sourcesContent":["import type {ZeroConfig} from '../../config/zero-config.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\n\nexport type LitestreamRole = 'replication_manager' | 'view_syncer';\nexport type LitestreamVersion = 'legacy' | 'v5';\n\nexport type LitestreamMetricAttrs = {\n role: LitestreamRole;\n backup_scheme: string;\n litestream: LitestreamVersion;\n};\n\ntype LitestreamMultipartMetricAttrs = {\n multipart_concurrency: number;\n multipart_size_mib: number;\n};\n\nconst LITESTREAM_DURATION_HISTOGRAM_BOUNDARIES_S = [\n 1, 2, 5, 10, 30, 60, 120, 300, 600, 1200, 2400, 3600, 7200,\n];\n\nexport function litestreamRestoreMetricAttrs(\n config: ZeroConfig,\n role: LitestreamRole,\n backupURL = config.litestream.backupURL,\n): LitestreamMetricAttrs & LitestreamMultipartMetricAttrs {\n const {executable, executableV5, restoreUsingV5} = config.litestream;\n const selectedExecutable =\n (restoreUsingV5 ? executableV5 : executable) ?? executable;\n return {\n role,\n backup_scheme: litestreamBackupScheme(backupURL),\n litestream:\n executableV5 !== undefined && selectedExecutable === executableV5\n ? 'v5'\n : 'legacy',\n ...litestreamMultipartMetricAttrs(config),\n };\n}\n\nexport function litestreamBackupMetricAttrs(\n config: ZeroConfig,\n): LitestreamMetricAttrs {\n return {\n role: 'replication_manager',\n backup_scheme: litestreamBackupScheme(config.litestream.backupURL),\n litestream:\n config.litestream.executableV5 !== undefined &&\n config.litestream.executable === config.litestream.executableV5\n ? 'v5'\n : 'legacy',\n };\n}\n\nexport function litestreamBackupProcessMetricAttrs(\n config: ZeroConfig,\n): LitestreamMetricAttrs & LitestreamMultipartMetricAttrs {\n return {\n ...litestreamBackupMetricAttrs(config),\n ...litestreamMultipartMetricAttrs(config),\n };\n}\n\nexport function litestreamMonitorMetricAttrs(\n backupURL: string,\n litestream: LitestreamVersion,\n role: LitestreamRole,\n): LitestreamMetricAttrs {\n return {\n role,\n backup_scheme: litestreamBackupScheme(backupURL),\n litestream,\n };\n}\n\nfunction litestreamBackupScheme(backupURL: string | undefined): string {\n if (!backupURL) {\n return 'unknown';\n }\n try {\n const protocol = new URL(backupURL).protocol;\n return protocol.endsWith(':') ? protocol.slice(0, -1) : protocol;\n } catch {\n return 'unknown';\n }\n}\n\nfunction litestreamMultipartMetricAttrs(\n config: ZeroConfig,\n): LitestreamMultipartMetricAttrs {\n return {\n multipart_concurrency: config.litestream.multipartConcurrency,\n multipart_size_mib: Math.round(\n config.litestream.multipartSize / 1024 / 1024,\n ),\n };\n}\n\nexport function litestreamRestoreRuns() {\n return getOrCreateCounter(\n 'replica',\n 'litestream_restore_runs',\n 'Litestream restore runs, labeled by result.',\n );\n}\n\nexport function litestreamRestoreAttempts() {\n return getOrCreateCounter(\n 'replica',\n 'litestream_restore_attempts',\n 'Litestream restore subprocess attempts, labeled by result.',\n );\n}\n\nexport function litestreamRestoredDbBytes() {\n return getOrCreateCounter('replica', 'litestream_restored_db', {\n description:\n 'SQLite database bytes restored by successful litestream restores.',\n unit: 'bytes',\n });\n}\n\nexport function litestreamBackupProcessRuns() {\n return getOrCreateCounter(\n 'replica',\n 'litestream_backup_process_runs',\n 'Litestream backup process exits, labeled by result.',\n );\n}\n\nexport function litestreamRestoreDuration() {\n return litestreamDurationHistogram(\n 'litestream_restore_duration',\n 'Wall-clock duration of a litestream restore run, labeled by result.',\n );\n}\n\nexport function litestreamRestoreWaitDuration() {\n return litestreamDurationHistogram(\n 'litestream_restore_wait_duration',\n 'Time spent waiting for the replication-manager snapshot status before restoring.',\n );\n}\n\nexport function litestreamRestoreProcessDuration() {\n return litestreamDurationHistogram(\n 'litestream_restore_process_duration',\n 'Wall-clock duration of the litestream restore subprocess.',\n );\n}\n\nexport function litestreamRestoreValidationDuration() {\n return litestreamDurationHistogram(\n 'litestream_restore_validation_duration',\n 'Time spent validating a restored replica database.',\n );\n}\n\nexport function litestreamBackupProcessDuration() {\n return litestreamDurationHistogram(\n 'litestream_backup_process_duration',\n 'Runtime duration of the litestream backup subprocess before it exits.',\n );\n}\n\nexport function litestreamBackupListDuration() {\n return litestreamDurationHistogram(\n 'litestream_backup_list_duration',\n 'Duration of litestream backup destination listing commands.',\n );\n}\n\nexport function litestreamBackupVerificationDuration() {\n return litestreamDurationHistogram(\n 'litestream_backup_verification_duration',\n 'Duration of verifying the actual backup state in the backup destination.',\n );\n}\n\nexport function litestreamSnapshotReservationDuration() {\n return litestreamDurationHistogram(\n 'litestream_snapshot_reservation_duration',\n 'Duration of a snapshot reservation while a view-syncer restores and subscribes.',\n );\n}\n\nfunction litestreamDurationHistogram(name: string, description: string) {\n return getOrCreateHistogram('replica', name, {\n description,\n unit: 's',\n bucketBoundaries: LITESTREAM_DURATION_HISTOGRAM_BOUNDARIES_S,\n });\n}\n"],"mappings":";;AAoBA,IAAM,6CAA6C;CACjD;CAAG;CAAG;CAAG;CAAI;CAAI;CAAI;CAAK;CAAK;CAAK;CAAM;CAAM;CAAM;AACxD;AAEA,SAAgB,6BACd,QACA,MACA,YAAY,OAAO,WAAW,WAC0B;CACxD,MAAM,EAAC,YAAY,cAAc,mBAAkB,OAAO;CAC1D,MAAM,sBACH,iBAAiB,eAAe,eAAe;CAClD,OAAO;EACL;EACA,eAAe,uBAAuB,SAAS;EAC/C,YACE,iBAAiB,KAAA,KAAa,uBAAuB,eACjD,OACA;EACN,GAAG,+BAA+B,MAAM;CAC1C;AACF;AAEA,SAAgB,4BACd,QACuB;CACvB,OAAO;EACL,MAAM;EACN,eAAe,uBAAuB,OAAO,WAAW,SAAS;EACjE,YACE,OAAO,WAAW,iBAAiB,KAAA,KACnC,OAAO,WAAW,eAAe,OAAO,WAAW,eAC/C,OACA;CACR;AACF;AAEA,SAAgB,mCACd,QACwD;CACxD,OAAO;EACL,GAAG,4BAA4B,MAAM;EACrC,GAAG,+BAA+B,MAAM;CAC1C;AACF;AAEA,SAAgB,6BACd,WACA,YACA,MACuB;CACvB,OAAO;EACL;EACA,eAAe,uBAAuB,SAAS;EAC/C;CACF;AACF;AAEA,SAAS,uBAAuB,WAAuC;CACrE,IAAI,CAAC,WACH,OAAO;CAET,IAAI;EACF,MAAM,WAAW,IAAI,IAAI,SAAS,EAAE;EACpC,OAAO,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;CAC1D,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,+BACP,QACgC;CAChC,OAAO;EACL,uBAAuB,OAAO,WAAW;EACzC,oBAAoB,KAAK,MACvB,OAAO,WAAW,gBAAgB,OAAO,IAC3C;CACF;AACF;AAEA,SAAgB,wBAAwB;CACtC,OAAO,mBACL,WACA,2BACA,6CACF;AACF;AAEA,SAAgB,4BAA4B;CAC1C,OAAO,mBACL,WACA,+BACA,4DACF;AACF;AAEA,SAAgB,4BAA4B;CAC1C,OAAO,mBAAmB,WAAW,0BAA0B;EAC7D,aACE;EACF,MAAM;CACR,CAAC;AACH;AAEA,SAAgB,8BAA8B;CAC5C,OAAO,mBACL,WACA,kCACA,qDACF;AACF;AAEA,SAAgB,4BAA4B;CAC1C,OAAO,4BACL,+BACA,qEACF;AACF;AAEA,SAAgB,gCAAgC;CAC9C,OAAO,4BACL,oCACA,kFACF;AACF;AAEA,SAAgB,mCAAmC;CACjD,OAAO,4BACL,uCACA,2DACF;AACF;AAEA,SAAgB,sCAAsC;CACpD,OAAO,4BACL,0CACA,oDACF;AACF;AAEA,SAAgB,kCAAkC;CAChD,OAAO,4BACL,sCACA,uEACF;AACF;AAEA,SAAgB,+BAA+B;CAC7C,OAAO,4BACL,mCACA,6DACF;AACF;AAEA,SAAgB,uCAAuC;CACrD,OAAO,4BACL,2CACA,0EACF;AACF;AAEA,SAAgB,wCAAwC;CACtD,OAAO,4BACL,4CACA,iFACF;AACF;AAEA,SAAS,4BAA4B,MAAc,aAAqB;CACtE,OAAO,qBAAqB,WAAW,MAAM;EAC3C;EACA,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH"}
1
+ {"version":3,"file":"metrics.js","names":[],"sources":["../../../../../../zero-cache/src/services/litestream/metrics.ts"],"sourcesContent":["import type {ZeroConfig} from '../../config/zero-config.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n LONG_DURATION_HISTOGRAM_BOUNDARIES_S,\n} from '../../observability/metrics.ts';\n\nexport type LitestreamRole = 'replication_manager' | 'view_syncer';\nexport type LitestreamVersion = 'legacy' | 'v5';\n\nexport type LitestreamMetricAttrs = {\n role: LitestreamRole;\n backup_scheme: string;\n litestream: LitestreamVersion;\n};\n\ntype LitestreamMultipartMetricAttrs = {\n multipart_concurrency: number;\n multipart_size_mib: number;\n};\n\nexport function litestreamRestoreMetricAttrs(\n config: ZeroConfig,\n role: LitestreamRole,\n backupURL = config.litestream.backupURL,\n): LitestreamMetricAttrs & LitestreamMultipartMetricAttrs {\n const {executable, executableV5, restoreUsingV5} = config.litestream;\n const selectedExecutable =\n (restoreUsingV5 ? executableV5 : executable) ?? executable;\n return {\n role,\n backup_scheme: litestreamBackupScheme(backupURL),\n litestream:\n executableV5 !== undefined && selectedExecutable === executableV5\n ? 'v5'\n : 'legacy',\n ...litestreamMultipartMetricAttrs(config),\n };\n}\n\nexport function litestreamBackupMetricAttrs(\n config: ZeroConfig,\n): LitestreamMetricAttrs {\n return {\n role: 'replication_manager',\n backup_scheme: litestreamBackupScheme(config.litestream.backupURL),\n litestream:\n config.litestream.executableV5 !== undefined &&\n config.litestream.executable === config.litestream.executableV5\n ? 'v5'\n : 'legacy',\n };\n}\n\nexport function litestreamBackupProcessMetricAttrs(\n config: ZeroConfig,\n): LitestreamMetricAttrs & LitestreamMultipartMetricAttrs {\n return {\n ...litestreamBackupMetricAttrs(config),\n ...litestreamMultipartMetricAttrs(config),\n };\n}\n\nexport function litestreamMonitorMetricAttrs(\n backupURL: string,\n litestream: LitestreamVersion,\n role: LitestreamRole,\n): LitestreamMetricAttrs {\n return {\n role,\n backup_scheme: litestreamBackupScheme(backupURL),\n litestream,\n };\n}\n\nfunction litestreamBackupScheme(backupURL: string | undefined): string {\n if (!backupURL) {\n return 'unknown';\n }\n try {\n const protocol = new URL(backupURL).protocol;\n return protocol.endsWith(':') ? protocol.slice(0, -1) : protocol;\n } catch {\n return 'unknown';\n }\n}\n\nfunction litestreamMultipartMetricAttrs(\n config: ZeroConfig,\n): LitestreamMultipartMetricAttrs {\n return {\n multipart_concurrency: config.litestream.multipartConcurrency,\n multipart_size_mib: Math.round(\n config.litestream.multipartSize / 1024 / 1024,\n ),\n };\n}\n\nexport function litestreamRestoreRuns() {\n return getOrCreateCounter(\n 'replica',\n 'litestream.restore.runs',\n 'Litestream restore runs, labeled by result.',\n );\n}\n\nexport function litestreamRestoreAttempts() {\n return getOrCreateCounter(\n 'replica',\n 'litestream.restore.attempts',\n 'Litestream restore subprocess attempts, labeled by result.',\n );\n}\n\nexport function litestreamRestoredDbBytes() {\n return getOrCreateCounter('replica', 'litestream.restore.db_bytes', {\n description:\n 'SQLite database bytes restored by successful litestream restores.',\n unit: 'bytes',\n });\n}\n\nexport function litestreamBackupProcessRuns() {\n return getOrCreateCounter(\n 'replica',\n 'litestream.backup.process_runs',\n 'Litestream backup process exits, labeled by result.',\n );\n}\n\nexport function litestreamRestoreDuration() {\n return litestreamDurationHistogram(\n 'litestream.restore.duration',\n 'Wall-clock duration of a litestream restore run, labeled by result.',\n );\n}\n\nexport function litestreamRestoreWaitDuration() {\n return litestreamDurationHistogram(\n 'litestream.restore.wait_duration',\n 'Time spent waiting for the replication-manager snapshot status before restoring.',\n );\n}\n\nexport function litestreamRestoreProcessDuration() {\n return litestreamDurationHistogram(\n 'litestream.restore.process_duration',\n 'Wall-clock duration of the litestream restore subprocess.',\n );\n}\n\nexport function litestreamRestoreValidationDuration() {\n return litestreamDurationHistogram(\n 'litestream.restore.validation_duration',\n 'Time spent validating a restored replica database.',\n );\n}\n\nexport function litestreamBackupProcessDuration() {\n return litestreamDurationHistogram(\n 'litestream.backup.process_duration',\n 'Runtime duration of the litestream backup subprocess before it exits.',\n );\n}\n\nexport function litestreamBackupListDuration() {\n return litestreamDurationHistogram(\n 'litestream.backup.list_duration',\n 'Duration of litestream backup destination listing commands.',\n );\n}\n\nexport function litestreamBackupVerificationDuration() {\n return litestreamDurationHistogram(\n 'litestream.backup.verification_duration',\n 'Duration of verifying the actual backup state in the backup destination.',\n );\n}\n\nexport function litestreamSnapshotReservationDuration() {\n return litestreamDurationHistogram(\n 'litestream.snapshot.reservation_duration',\n 'Duration of a snapshot reservation while a view-syncer restores and subscribes.',\n );\n}\n\nfunction litestreamDurationHistogram(name: string, description: string) {\n return getOrCreateHistogram('replica', name, {\n description,\n unit: 's',\n bucketBoundaries: LONG_DURATION_HISTOGRAM_BOUNDARIES_S,\n });\n}\n"],"mappings":";;AAqBA,SAAgB,6BACd,QACA,MACA,YAAY,OAAO,WAAW,WAC0B;CACxD,MAAM,EAAC,YAAY,cAAc,mBAAkB,OAAO;CAC1D,MAAM,sBACH,iBAAiB,eAAe,eAAe;CAClD,OAAO;EACL;EACA,eAAe,uBAAuB,SAAS;EAC/C,YACE,iBAAiB,KAAA,KAAa,uBAAuB,eACjD,OACA;EACN,GAAG,+BAA+B,MAAM;CAC1C;AACF;AAEA,SAAgB,4BACd,QACuB;CACvB,OAAO;EACL,MAAM;EACN,eAAe,uBAAuB,OAAO,WAAW,SAAS;EACjE,YACE,OAAO,WAAW,iBAAiB,KAAA,KACnC,OAAO,WAAW,eAAe,OAAO,WAAW,eAC/C,OACA;CACR;AACF;AAEA,SAAgB,mCACd,QACwD;CACxD,OAAO;EACL,GAAG,4BAA4B,MAAM;EACrC,GAAG,+BAA+B,MAAM;CAC1C;AACF;AAEA,SAAgB,6BACd,WACA,YACA,MACuB;CACvB,OAAO;EACL;EACA,eAAe,uBAAuB,SAAS;EAC/C;CACF;AACF;AAEA,SAAS,uBAAuB,WAAuC;CACrE,IAAI,CAAC,WACH,OAAO;CAET,IAAI;EACF,MAAM,WAAW,IAAI,IAAI,SAAS,EAAE;EACpC,OAAO,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;CAC1D,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,+BACP,QACgC;CAChC,OAAO;EACL,uBAAuB,OAAO,WAAW;EACzC,oBAAoB,KAAK,MACvB,OAAO,WAAW,gBAAgB,OAAO,IAC3C;CACF;AACF;AAEA,SAAgB,wBAAwB;CACtC,OAAO,mBACL,WACA,2BACA,6CACF;AACF;AAEA,SAAgB,4BAA4B;CAC1C,OAAO,mBACL,WACA,+BACA,4DACF;AACF;AAEA,SAAgB,4BAA4B;CAC1C,OAAO,mBAAmB,WAAW,+BAA+B;EAClE,aACE;EACF,MAAM;CACR,CAAC;AACH;AAEA,SAAgB,8BAA8B;CAC5C,OAAO,mBACL,WACA,kCACA,qDACF;AACF;AAEA,SAAgB,4BAA4B;CAC1C,OAAO,4BACL,+BACA,qEACF;AACF;AAEA,SAAgB,gCAAgC;CAC9C,OAAO,4BACL,oCACA,kFACF;AACF;AAEA,SAAgB,mCAAmC;CACjD,OAAO,4BACL,uCACA,2DACF;AACF;AAEA,SAAgB,sCAAsC;CACpD,OAAO,4BACL,0CACA,oDACF;AACF;AAEA,SAAgB,kCAAkC;CAChD,OAAO,4BACL,sCACA,uEACF;AACF;AAEA,SAAgB,+BAA+B;CAC7C,OAAO,4BACL,mCACA,6DACF;AACF;AAEA,SAAgB,uCAAuC;CACrD,OAAO,4BACL,2CACA,0EACF;AACF;AAEA,SAAgB,wCAAwC;CACtD,OAAO,4BACL,4CACA,iFACF;AACF;AAEA,SAAS,4BAA4B,MAAc,aAAqB;CACtE,OAAO,qBAAqB,WAAW,MAAM;EAC3C;EACA,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH"}
@@ -1,6 +1,6 @@
1
1
  import { parse, valita_exports } from "../../../../shared/src/valita.js";
2
2
  import { Database } from "../../../../zqlite/src/db.js";
3
- import Sqlite3Database from "@rocicorp/zero-sqlite3";
3
+ import SQLite3Database from "@rocicorp/zero-sqlite3";
4
4
  //#region ../zero-cache/src/services/litestream/vfs-watermark-reader.ts
5
5
  var DEFAULT_VFS_DATABASE_URI = "file:zero-backup.db?vfs=litestream&mode=ro";
6
6
  var DEFAULT_VFS_LOG_LEVEL = "info";
@@ -27,7 +27,7 @@ var VfsBackupWatermarkReader = class {
27
27
  env["LITESTREAM_REPLICA_URL"] = options.replicaURL;
28
28
  env["LITESTREAM_LOG_LEVEL"] = (options.logLevel ?? DEFAULT_VFS_LOG_LEVEL).toUpperCase();
29
29
  setOptionalEnv(env, "LITESTREAM_LOG_FILE", options.logFile);
30
- const loaderFactory = deps.loaderFactory ?? (() => new Sqlite3Database(":memory:"));
30
+ const loaderFactory = deps.loaderFactory ?? (() => new SQLite3Database(":memory:"));
31
31
  const databaseFactory = deps.databaseFactory ?? ((lc, uri) => new Database(lc, uri, { readonly: true }));
32
32
  this.#loader = loaderFactory();
33
33
  this.#loader.loadExtension(options.extensionPath);
@@ -1 +1 @@
1
- {"version":3,"file":"pusher.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/mutagen/pusher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pusher.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/mutagen/pusher.ts"],"names":[],"mappings":"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"}
@@ -84,7 +84,10 @@ var PusherService = class {
84
84
  await fetchFromAPIServer(mutateResponseSchema, "push", this.#lc, connCtx, {
85
85
  appID: this.#config.app.id,
86
86
  shardNum: this.#config.shard.num
87
- }, cleanupBody);
87
+ }, cleanupBody, {
88
+ operation: "cleanup",
89
+ cleanupType: "single"
90
+ });
88
91
  } catch (e) {
89
92
  this.#lc.warn?.("Failed to send cleanup mutation", { error: getErrorMessage(e) });
90
93
  }
@@ -121,7 +124,10 @@ var PusherService = class {
121
124
  await fetchFromAPIServer(mutateResponseSchema, "push", this.#lc, connCtx, {
122
125
  appID: this.#config.app.id,
123
126
  shardNum: this.#config.shard.num
124
- }, cleanupBody);
127
+ }, cleanupBody, {
128
+ operation: "cleanup",
129
+ cleanupType: "bulk"
130
+ });
125
131
  } catch (e) {
126
132
  this.#lc.warn?.("Failed to send bulk cleanup mutation", { error: getErrorMessage(e) });
127
133
  }
@@ -286,7 +292,7 @@ var PushWorker = class {
286
292
  const response = await fetchFromAPIServer(mutateResponseSchema, "push", this.#lc, entry.connCtx, {
287
293
  appID: this.#config.app.id,
288
294
  shardNum: this.#config.shard.num
289
- }, entry.push);
295
+ }, entry.push, { operation: "mutate" });
290
296
  if ("kind" in response && response.kind === "PushFailed" || "error" in response) {
291
297
  if (isAuthErrorBody(response)) {
292
298
  this.#lc.warn?.("Push auth failed; invalidating connection", {
@@ -1 +1 @@
1
- {"version":3,"file":"pusher.js","names":["#connContextManager","#pusher","#queue","#config","#lc","#isStopped","#refCount","#stopped","#clients","#customMutations","#pushes","#processPush","#fanOutResponses","#failDownstream"],"sources":["../../../../../../zero-cache/src/services/mutagen/pusher.ts"],"sourcesContent":["import {context, propagation, ROOT_CONTEXT} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {groupBy} from '../../../../shared/src/arrays.ts';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {getErrorMessage} from '../../../../shared/src/error.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {Queue} from '../../../../shared/src/queue.ts';\nimport type {Downstream} from '../../../../zero-protocol/src/down.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../../zero-protocol/src/error-reason.ts';\nimport {\n isProtocolError,\n type PushFailedBody,\n} from '../../../../zero-protocol/src/error.ts';\nimport {\n mutateResponseSchema,\n type MutateResponse,\n} from '../../../../zero-protocol/src/mutate-server.ts';\nimport type {MutationID} from '../../../../zero-protocol/src/mutation-id.ts';\nimport * as MutationType from '../../../../zero-protocol/src/mutation-type-enum.ts';\nimport {CLEANUP_RESULTS_MUTATION_NAME} from '../../../../zero-protocol/src/mutation.ts';\nimport {type PushBody} from '../../../../zero-protocol/src/push.ts';\nimport {authEquals, isAuthErrorBody} from '../../auth/auth.ts';\nimport {type ZeroConfig} from '../../config/zero-config.ts';\nimport {fetchFromAPIServer} from '../../custom/fetch.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport {recordMutation} from '../../server/anonymous-otel-start.ts';\nimport {ProtocolErrorWithLevel} from '../../types/error-with-level.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {Subscription} from '../../types/subscription.ts';\nimport type {HandlerResult, StreamResult} from '../../workers/connection.ts';\nimport type {RefCountedService, Service} from '../service.ts';\nimport type {\n ConnectionContext,\n ConnectionContextManager,\n ConnectionSelector,\n} from '../view-syncer/connection-context-manager.ts';\n\nexport interface Pusher extends RefCountedService {\n initConnection(selector: ConnectionSelector): Source<Downstream>;\n enqueuePush(selector: ConnectionSelector, push: PushBody): HandlerResult;\n ackMutationResponses(\n requester: ConnectionSelector,\n upToID: MutationID,\n ): Promise<void>;\n deleteClientMutations(\n requester: ConnectionSelector,\n clientIDs: string[],\n ): Promise<void>;\n}\n\ntype Config = Pick<ZeroConfig, 'app' | 'shard'>;\n\n/**\n * Receives push messages from zero-client and forwards\n * them the the user's API server.\n *\n * If the user's API server is taking too long to process\n * the push, the PusherService will add the push to a queue\n * and send pushes in bulk the next time the user's API server\n * is available.\n *\n * - One PusherService exists per client group.\n * - Mutations for a given client are always sent in-order\n * - Mutations for different clients in the same group may be interleaved\n */\nexport class PusherService implements Service, Pusher {\n readonly id: string;\n readonly #connContextManager: ConnectionContextManager;\n readonly #pusher: PushWorker;\n readonly #queue: Queue<PusherEntryOrStop>;\n readonly #config: Config;\n readonly #lc: LogContext;\n #stopped: Promise<void> | undefined;\n #refCount = 0;\n #isStopped = false;\n\n constructor(\n appConfig: Config,\n lc: LogContext,\n clientGroupID: string,\n connContextManager: ConnectionContextManager,\n ) {\n this.#connContextManager = connContextManager;\n this.#config = appConfig;\n this.#lc = lc.withContext('component', 'pusherService');\n this.#queue = new Queue();\n this.#pusher = new PushWorker(\n appConfig,\n lc,\n this.#connContextManager,\n this.#queue,\n );\n this.id = clientGroupID;\n }\n\n initConnection(selector: ConnectionSelector) {\n return this.#pusher.initConnection(selector);\n }\n\n enqueuePush(\n selector: ConnectionSelector,\n push: PushBody,\n ): Exclude<HandlerResult, StreamResult> {\n this.#pusher.enqueuePush(\n this.#connContextManager.mustGetConnectionContext(selector),\n push,\n );\n\n return {\n type: 'ok',\n };\n }\n\n async ackMutationResponses(\n requester: ConnectionSelector,\n upToID: MutationID,\n ): Promise<void> {\n const connCtx = this.#connContextManager.getConnectionContext(requester);\n if (!connCtx?.mutateContext?.url) {\n // No push URL configured, skip cleanup\n return;\n }\n\n const cleanupBody: PushBody = {\n clientGroupID: this.id,\n mutations: [\n {\n type: MutationType.Custom,\n id: 0, // Not tracked - this is fire-and-forget\n clientID: upToID.clientID,\n name: CLEANUP_RESULTS_MUTATION_NAME,\n args: [\n {\n type: 'single',\n clientGroupID: this.id,\n clientID: upToID.clientID,\n upToMutationID: upToID.id,\n },\n ],\n timestamp: Date.now(),\n },\n ],\n pushVersion: 1,\n timestamp: Date.now(),\n requestID: `cleanup-${this.id}-${upToID.clientID}-${upToID.id}`,\n };\n\n try {\n await fetchFromAPIServer(\n mutateResponseSchema,\n 'push',\n this.#lc,\n connCtx,\n {appID: this.#config.app.id, shardNum: this.#config.shard.num},\n cleanupBody,\n );\n } catch (e) {\n this.#lc.warn?.('Failed to send cleanup mutation', {\n error: getErrorMessage(e),\n });\n }\n }\n\n /**\n * Bulk cleanup is routed through the requester's push context.\n *\n * This assumes the client group shares a compatible push endpoint/auth\n * context.\n */\n async deleteClientMutations(\n requester: ConnectionSelector,\n clientIDs: string[],\n ): Promise<void> {\n if (clientIDs.length === 0) {\n return;\n }\n\n const connCtx = this.#connContextManager.getConnectionContext(requester);\n if (!connCtx?.mutateContext?.url) {\n // No push URL configured, skip cleanup\n return;\n }\n\n const cleanupBody: PushBody = {\n clientGroupID: this.id,\n mutations: [\n {\n type: MutationType.Custom,\n id: 0, // Not tracked - this is fire-and-forget\n clientID: clientIDs[0], // Use first client as sender\n name: CLEANUP_RESULTS_MUTATION_NAME,\n args: [\n {\n type: 'bulk',\n clientGroupID: this.id,\n clientIDs,\n },\n ],\n timestamp: Date.now(),\n },\n ],\n pushVersion: 1,\n timestamp: Date.now(),\n requestID: `cleanup-bulk-${this.id}-${Date.now()}`,\n };\n\n try {\n await fetchFromAPIServer(\n mutateResponseSchema,\n 'push',\n this.#lc,\n connCtx,\n {appID: this.#config.app.id, shardNum: this.#config.shard.num},\n cleanupBody,\n );\n } catch (e) {\n this.#lc.warn?.('Failed to send bulk cleanup mutation', {\n error: getErrorMessage(e),\n });\n }\n }\n\n ref() {\n assert(!this.#isStopped, 'PusherService is already stopped');\n ++this.#refCount;\n }\n\n unref() {\n assert(!this.#isStopped, 'PusherService is already stopped');\n --this.#refCount;\n if (this.#refCount <= 0) {\n void this.stop();\n }\n }\n\n hasRefs(): boolean {\n return this.#refCount > 0;\n }\n\n run(): Promise<void> {\n this.#stopped = this.#pusher.run();\n return this.#stopped;\n }\n\n stop(): Promise<void> {\n if (this.#isStopped) {\n return must(this.#stopped, 'Stop was called before `run`');\n }\n this.#isStopped = true;\n this.#queue.enqueue('stop');\n return must(this.#stopped, 'Stop was called before `run`');\n }\n}\n\ntype PusherEntry = {\n push: PushBody;\n connCtx: ConnectionContext;\n};\ntype PusherEntryOrStop = PusherEntry | 'stop';\n\n/**\n * Awaits items in the queue then drains and sends them all\n * to the user's API server.\n */\nclass PushWorker {\n readonly #connContextManager: ConnectionContextManager;\n readonly #queue: Queue<PusherEntryOrStop>;\n readonly #lc: LogContext;\n readonly #config: Config;\n readonly #clients: Map<\n string,\n {wsID: string; downstream: Subscription<Downstream>}\n >;\n\n readonly #customMutations = getOrCreateCounter(\n 'mutation',\n 'custom',\n 'Number of custom mutations processed',\n );\n readonly #pushes = getOrCreateCounter(\n 'mutation',\n 'pushes',\n 'Number of pushes processed by the pusher',\n );\n\n constructor(\n config: Config,\n lc: LogContext,\n connContextManager: ConnectionContextManager,\n queue: Queue<PusherEntryOrStop>,\n ) {\n this.#lc = lc.withContext('component', 'pusher');\n this.#connContextManager = connContextManager;\n this.#queue = queue;\n this.#config = config;\n this.#clients = new Map();\n }\n\n /**\n * Returns a new downstream stream if the clientID,wsID pair has not been seen before.\n * If a clientID already exists with a different wsID, that client's downstream is cancelled.\n */\n initConnection(selector: ConnectionSelector) {\n const existing = this.#clients.get(selector.clientID);\n if (existing && existing.wsID === selector.wsID) {\n // already initialized for this socket\n throw new Error('Connection was already initialized');\n }\n\n // client is back on a new connection\n if (existing) {\n existing.downstream.cancel();\n }\n\n const downstream = Subscription.create<Downstream>({\n cleanup: () => {\n this.#clients.delete(selector.clientID);\n },\n });\n this.#clients.set(selector.clientID, {\n wsID: selector.wsID,\n downstream,\n });\n return downstream;\n }\n\n enqueuePush(connCtx: ConnectionContext, push: PushBody) {\n this.#queue.enqueue({\n push,\n connCtx,\n });\n }\n\n async run() {\n for (;;) {\n const task = await this.#queue.dequeue();\n const rest = this.#queue.drain();\n const [pushes, terminate] = combinePushes([task, ...rest]);\n for (const push of pushes) {\n const parentContext = push.push.traceparent\n ? propagation.extract(ROOT_CONTEXT, {\n traceparent: push.push.traceparent,\n })\n : context.active();\n const response = await context.with(parentContext, () =>\n this.#processPush(push),\n );\n await this.#fanOutResponses(response);\n }\n\n if (terminate) {\n break;\n }\n }\n }\n\n /**\n * 1. If the entire `push` fails, we send the error to relevant clients.\n * 2. If the push succeeds, we look for any mutation failure that should cause the connection to terminate\n * and terminate the connection for those clients.\n */\n #fanOutResponses(response: MutateResponse) {\n const connectionTerminations: (() => void)[] = [];\n\n // if the entire push failed, send that to the client.\n if (\n ('kind' in response && response.kind === ErrorKind.PushFailed) ||\n 'error' in response\n ) {\n this.#lc.warn?.(\n 'The server behind ZERO_MUTATE_URL returned a push error.',\n response,\n );\n // TODO(0xcadams): Fanout is keyed only by clientID here. If a response arrives\n // after reconnect or re-auth, `#clients.get(clientID)` may point at a\n // newer wsID/revision and fail the replacement downstream instead.\n const groupedMutationIDs = groupBy(\n response.mutationIDs ?? [],\n m => m.clientID,\n );\n for (const [clientID, mutationIDs] of groupedMutationIDs) {\n const client = this.#clients.get(clientID);\n if (!client) {\n continue;\n }\n\n // We do not resolve mutations on the client if the push fails\n // as those mutations will be retried.\n if ('error' in response) {\n // This error code path will eventually be removed when we\n // no longer support the legacy push error format.\n const pushFailedBody: PushFailedBody =\n response.error === 'http'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.HTTP,\n status: response.status,\n bodyPreview: response.details,\n mutationIDs,\n message: `Fetch from API server returned non-OK status ${response.status}`,\n }\n : response.error === 'unsupportedPushVersion'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.UnsupportedPushVersion,\n mutationIDs,\n message: `Unsupported push version`,\n }\n : {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Internal,\n mutationIDs,\n message:\n response.error === 'zeroPusher'\n ? response.details\n : response.error === 'unsupportedSchemaVersion'\n ? 'Unsupported schema version'\n : 'An unknown error occurred while pushing to the API server',\n };\n\n this.#failDownstream(client.downstream, pushFailedBody);\n } else {\n this.#failDownstream(client.downstream, response);\n }\n }\n } else {\n // Look for mutations results that should cause us to terminate the connection\n // TODO(0xcadams): Same stale-routing issue as above: fatal mutation results are\n // still mapped to the current downstream by clientID only.\n const groupedMutations = groupBy(response.mutations, m => m.id.clientID);\n for (const [clientID, mutations] of groupedMutations) {\n const client = this.#clients.get(clientID);\n if (!client) {\n continue;\n }\n\n let failure: PushFailedBody | undefined;\n let i = 0;\n for (; i < mutations.length; i++) {\n const m = mutations[i];\n if ('error' in m.result) {\n this.#lc.warn?.(\n 'The server behind ZERO_MUTATE_URL returned a mutation error.',\n m.result,\n );\n }\n // This error code path will eventually be removed,\n // keeping this for backwards compatibility, but the server\n // should now return a PushFailedBody with the mutationIDs\n if ('error' in m.result && m.result.error === 'oooMutation') {\n failure = {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.OutOfOrderMutation,\n message: 'mutation was out of order',\n details: m.result.details,\n mutationIDs: mutations.map(m => ({\n clientID: m.id.clientID,\n id: m.id.id,\n })),\n };\n break;\n }\n }\n\n if (failure && i < mutations.length - 1) {\n this.#lc.warn?.(\n 'push-response contains mutations after a mutation which should fatal the connection',\n );\n }\n\n if (failure) {\n connectionTerminations.push(() =>\n this.#failDownstream(client.downstream, failure),\n );\n }\n }\n }\n\n connectionTerminations.forEach(cb => cb());\n }\n\n async #processPush(entry: PusherEntry): Promise<MutateResponse> {\n this.#customMutations.add(entry.push.mutations.length, {\n clientGroupID: entry.push.clientGroupID,\n });\n this.#pushes.add(1, {\n clientGroupID: entry.push.clientGroupID,\n });\n\n // Record custom mutations for telemetry\n recordMutation('custom', entry.push.mutations.length);\n\n const url = must(\n entry.connCtx.mutateContext.url,\n 'ZERO_MUTATE_URL is not set',\n );\n\n this.#lc.debug?.(\n 'pushing to',\n url,\n 'with',\n entry.push.mutations.length,\n 'mutations',\n );\n\n let mutationIDs: MutationID[] = [];\n\n try {\n mutationIDs = entry.push.mutations.map(m => ({\n id: m.id,\n clientID: m.clientID,\n }));\n\n const response = await fetchFromAPIServer(\n mutateResponseSchema,\n 'push',\n this.#lc,\n entry.connCtx,\n {\n appID: this.#config.app.id,\n shardNum: this.#config.shard.num,\n },\n entry.push,\n );\n if (\n ('kind' in response && response.kind === ErrorKind.PushFailed) ||\n 'error' in response\n ) {\n if (isAuthErrorBody(response)) {\n this.#lc.warn?.('Push auth failed; invalidating connection', {\n clientID: entry.connCtx.clientID,\n response: 'kind' in response ? response.message : undefined,\n });\n this.#connContextManager.failConnection(\n entry.connCtx,\n entry.connCtx.revision,\n );\n }\n return response;\n }\n // A successful push also validates this connection's current auth snapshot.\n // That lets later shared work reuse it without trusting stale credentials.\n this.#connContextManager.validateConnection(\n entry.connCtx,\n entry.connCtx.revision,\n 'kind' in response &&\n response.kind === 'MutateResponse' &&\n response?.userID !== undefined\n ? {kind: 'server-validated', validatedUserID: response.userID}\n : {kind: 'client-fallback'},\n );\n return response;\n } catch (e) {\n if (isProtocolError(e) && e.errorBody.kind === ErrorKind.PushFailed) {\n const response = {\n ...e.errorBody,\n mutationIDs,\n } as const satisfies PushFailedBody;\n if (isAuthErrorBody(response)) {\n this.#lc.warn?.('Push auth failed; invalidating connection', {\n clientID: entry.connCtx.clientID,\n response: 'kind' in response ? response.message : undefined,\n });\n this.#connContextManager.failConnection(\n entry.connCtx,\n entry.connCtx.revision,\n );\n }\n return response;\n }\n\n if (isProtocolError(e) && isAuthErrorBody(e.errorBody)) {\n // The push completed far enough for local validation to reject the\n // connection, so invalidate it and surface the result as PushFailed.\n this.#lc.warn?.('Push validation failed; invalidating connection', {\n clientID: entry.connCtx.clientID,\n response: e.message,\n });\n this.#connContextManager.failConnection(\n entry.connCtx,\n entry.connCtx.revision,\n );\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.HTTP,\n message: e.message,\n status: 401,\n mutationIDs,\n } as const satisfies PushFailedBody;\n }\n\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Failed to push: ${getErrorMessage(e)}`,\n mutationIDs,\n } as const satisfies PushFailedBody;\n }\n }\n\n #failDownstream(\n downstream: Subscription<Downstream>,\n errorBody: PushFailedBody,\n ): void {\n downstream.fail(new ProtocolErrorWithLevel(errorBody, 'warn'));\n }\n}\n\n/**\n * Pushes for different clients, sockets, or auth revisions could be interleaved.\n *\n * In order to batch safely, we only combine pushes from the same\n * clientID/wsID/revision snapshot.\n */\nexport function combinePushes(\n entries: readonly (PusherEntryOrStop | undefined)[],\n): [PusherEntry[], boolean] {\n const pushesByConnection = new Map<string, PusherEntry[]>();\n\n function collect() {\n const ret: PusherEntry[] = [];\n for (const entries of pushesByConnection.values()) {\n const composite: PusherEntry = {\n ...entries[0],\n push: {\n ...entries[0].push,\n mutations: [],\n },\n };\n ret.push(composite);\n for (const entry of entries) {\n assertAreCompatiblePushes(composite, entry);\n composite.push.mutations.push(...entry.push.mutations);\n }\n }\n return ret;\n }\n\n for (const entry of entries) {\n if (entry === 'stop' || entry === undefined) {\n return [collect(), true];\n }\n\n const key = `${entry.connCtx.clientID}:${entry.connCtx.wsID}:${entry.connCtx.revision}`;\n const existing = pushesByConnection.get(key);\n if (existing) {\n existing.push(entry);\n } else {\n pushesByConnection.set(key, [entry]);\n }\n }\n\n return [collect(), false] as const;\n}\n\n// These invariants should always be true for a given clientID.\n// If they are not, we have a bug in the code somewhere.\nfunction assertAreCompatiblePushes(left: PusherEntry, right: PusherEntry) {\n assert(\n left.connCtx.clientID === right.connCtx.clientID,\n 'clientID must be the same for all pushes',\n );\n assert(\n left.connCtx.wsID === right.connCtx.wsID,\n 'wsID must be the same for all pushes',\n );\n assert(\n left.connCtx.revision === right.connCtx.revision,\n 'revision must be the same for all pushes',\n );\n assert(\n authEquals(left.connCtx.auth, right.connCtx.auth),\n 'auth must be the same for all pushes with the same clientID',\n );\n assert(\n left.push.schemaVersion === right.push.schemaVersion,\n 'schemaVersion must be the same for all pushes with the same clientID',\n );\n assert(\n left.push.pushVersion === right.push.pushVersion,\n 'pushVersion must be the same for all pushes with the same clientID',\n );\n assert(\n left.connCtx.mutateContext.headerOptions.cookie ===\n right.connCtx.mutateContext.headerOptions.cookie,\n 'httpCookie must be the same for all pushes with the same clientID',\n );\n assert(\n left.connCtx.mutateContext.headerOptions.origin ===\n right.connCtx.mutateContext.headerOptions.origin,\n 'origin must be the same for all pushes with the same clientID',\n );\n assert(\n left.connCtx.user.id === right.connCtx.user.id,\n 'userID must be the same for all pushes with the same clientID',\n );\n assert(\n left.connCtx.mutateContext.url === right.connCtx.mutateContext.url,\n 'userPushURL must be the same for all pushes with the same clientID',\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pusher.js","names":["#connContextManager","#pusher","#queue","#config","#lc","#isStopped","#refCount","#stopped","#clients","#customMutations","#pushes","#processPush","#fanOutResponses","#failDownstream"],"sources":["../../../../../../zero-cache/src/services/mutagen/pusher.ts"],"sourcesContent":["import {context, propagation, ROOT_CONTEXT} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {groupBy} from '../../../../shared/src/arrays.ts';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {getErrorMessage} from '../../../../shared/src/error.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {Queue} from '../../../../shared/src/queue.ts';\nimport type {Downstream} from '../../../../zero-protocol/src/down.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../../zero-protocol/src/error-reason.ts';\nimport {\n isProtocolError,\n type PushFailedBody,\n} from '../../../../zero-protocol/src/error.ts';\nimport {\n mutateResponseSchema,\n type MutateResponse,\n} from '../../../../zero-protocol/src/mutate-server.ts';\nimport type {MutationID} from '../../../../zero-protocol/src/mutation-id.ts';\nimport * as MutationType from '../../../../zero-protocol/src/mutation-type-enum.ts';\nimport {CLEANUP_RESULTS_MUTATION_NAME} from '../../../../zero-protocol/src/mutation.ts';\nimport {type PushBody} from '../../../../zero-protocol/src/push.ts';\nimport {authEquals, isAuthErrorBody} from '../../auth/auth.ts';\nimport {type ZeroConfig} from '../../config/zero-config.ts';\nimport {fetchFromAPIServer} from '../../custom/fetch.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport {recordMutation} from '../../server/anonymous-otel-start.ts';\nimport {ProtocolErrorWithLevel} from '../../types/error-with-level.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {Subscription} from '../../types/subscription.ts';\nimport type {HandlerResult, StreamResult} from '../../workers/connection.ts';\nimport type {RefCountedService, Service} from '../service.ts';\nimport type {\n ConnectionContext,\n ConnectionContextManager,\n ConnectionSelector,\n} from '../view-syncer/connection-context-manager.ts';\n\nexport interface Pusher extends RefCountedService {\n initConnection(selector: ConnectionSelector): Source<Downstream>;\n enqueuePush(selector: ConnectionSelector, push: PushBody): HandlerResult;\n ackMutationResponses(\n requester: ConnectionSelector,\n upToID: MutationID,\n ): Promise<void>;\n deleteClientMutations(\n requester: ConnectionSelector,\n clientIDs: string[],\n ): Promise<void>;\n}\n\ntype Config = Pick<ZeroConfig, 'app' | 'shard'>;\n\n/**\n * Receives push messages from zero-client and forwards\n * them the the user's API server.\n *\n * If the user's API server is taking too long to process\n * the push, the PusherService will add the push to a queue\n * and send pushes in bulk the next time the user's API server\n * is available.\n *\n * - One PusherService exists per client group.\n * - Mutations for a given client are always sent in-order\n * - Mutations for different clients in the same group may be interleaved\n */\nexport class PusherService implements Service, Pusher {\n readonly id: string;\n readonly #connContextManager: ConnectionContextManager;\n readonly #pusher: PushWorker;\n readonly #queue: Queue<PusherEntryOrStop>;\n readonly #config: Config;\n readonly #lc: LogContext;\n #stopped: Promise<void> | undefined;\n #refCount = 0;\n #isStopped = false;\n\n constructor(\n appConfig: Config,\n lc: LogContext,\n clientGroupID: string,\n connContextManager: ConnectionContextManager,\n ) {\n this.#connContextManager = connContextManager;\n this.#config = appConfig;\n this.#lc = lc.withContext('component', 'pusherService');\n this.#queue = new Queue();\n this.#pusher = new PushWorker(\n appConfig,\n lc,\n this.#connContextManager,\n this.#queue,\n );\n this.id = clientGroupID;\n }\n\n initConnection(selector: ConnectionSelector) {\n return this.#pusher.initConnection(selector);\n }\n\n enqueuePush(\n selector: ConnectionSelector,\n push: PushBody,\n ): Exclude<HandlerResult, StreamResult> {\n this.#pusher.enqueuePush(\n this.#connContextManager.mustGetConnectionContext(selector),\n push,\n );\n\n return {\n type: 'ok',\n };\n }\n\n async ackMutationResponses(\n requester: ConnectionSelector,\n upToID: MutationID,\n ): Promise<void> {\n const connCtx = this.#connContextManager.getConnectionContext(requester);\n if (!connCtx?.mutateContext?.url) {\n // No push URL configured, skip cleanup\n return;\n }\n\n const cleanupBody: PushBody = {\n clientGroupID: this.id,\n mutations: [\n {\n type: MutationType.Custom,\n id: 0, // Not tracked - this is fire-and-forget\n clientID: upToID.clientID,\n name: CLEANUP_RESULTS_MUTATION_NAME,\n args: [\n {\n type: 'single',\n clientGroupID: this.id,\n clientID: upToID.clientID,\n upToMutationID: upToID.id,\n },\n ],\n timestamp: Date.now(),\n },\n ],\n pushVersion: 1,\n timestamp: Date.now(),\n requestID: `cleanup-${this.id}-${upToID.clientID}-${upToID.id}`,\n };\n\n try {\n await fetchFromAPIServer(\n mutateResponseSchema,\n 'push',\n this.#lc,\n connCtx,\n {appID: this.#config.app.id, shardNum: this.#config.shard.num},\n cleanupBody,\n {operation: 'cleanup', cleanupType: 'single'},\n );\n } catch (e) {\n this.#lc.warn?.('Failed to send cleanup mutation', {\n error: getErrorMessage(e),\n });\n }\n }\n\n /**\n * Bulk cleanup is routed through the requester's push context.\n *\n * This assumes the client group shares a compatible push endpoint/auth\n * context.\n */\n async deleteClientMutations(\n requester: ConnectionSelector,\n clientIDs: string[],\n ): Promise<void> {\n if (clientIDs.length === 0) {\n return;\n }\n\n const connCtx = this.#connContextManager.getConnectionContext(requester);\n if (!connCtx?.mutateContext?.url) {\n // No push URL configured, skip cleanup\n return;\n }\n\n const cleanupBody: PushBody = {\n clientGroupID: this.id,\n mutations: [\n {\n type: MutationType.Custom,\n id: 0, // Not tracked - this is fire-and-forget\n clientID: clientIDs[0], // Use first client as sender\n name: CLEANUP_RESULTS_MUTATION_NAME,\n args: [\n {\n type: 'bulk',\n clientGroupID: this.id,\n clientIDs,\n },\n ],\n timestamp: Date.now(),\n },\n ],\n pushVersion: 1,\n timestamp: Date.now(),\n requestID: `cleanup-bulk-${this.id}-${Date.now()}`,\n };\n\n try {\n await fetchFromAPIServer(\n mutateResponseSchema,\n 'push',\n this.#lc,\n connCtx,\n {appID: this.#config.app.id, shardNum: this.#config.shard.num},\n cleanupBody,\n {operation: 'cleanup', cleanupType: 'bulk'},\n );\n } catch (e) {\n this.#lc.warn?.('Failed to send bulk cleanup mutation', {\n error: getErrorMessage(e),\n });\n }\n }\n\n ref() {\n assert(!this.#isStopped, 'PusherService is already stopped');\n ++this.#refCount;\n }\n\n unref() {\n assert(!this.#isStopped, 'PusherService is already stopped');\n --this.#refCount;\n if (this.#refCount <= 0) {\n void this.stop();\n }\n }\n\n hasRefs(): boolean {\n return this.#refCount > 0;\n }\n\n run(): Promise<void> {\n this.#stopped = this.#pusher.run();\n return this.#stopped;\n }\n\n stop(): Promise<void> {\n if (this.#isStopped) {\n return must(this.#stopped, 'Stop was called before `run`');\n }\n this.#isStopped = true;\n this.#queue.enqueue('stop');\n return must(this.#stopped, 'Stop was called before `run`');\n }\n}\n\ntype PusherEntry = {\n push: PushBody;\n connCtx: ConnectionContext;\n};\ntype PusherEntryOrStop = PusherEntry | 'stop';\n\n/**\n * Awaits items in the queue then drains and sends them all\n * to the user's API server.\n */\nclass PushWorker {\n readonly #connContextManager: ConnectionContextManager;\n readonly #queue: Queue<PusherEntryOrStop>;\n readonly #lc: LogContext;\n readonly #config: Config;\n readonly #clients: Map<\n string,\n {wsID: string; downstream: Subscription<Downstream>}\n >;\n\n readonly #customMutations = getOrCreateCounter(\n 'mutation',\n 'custom',\n 'Number of custom mutations processed',\n );\n readonly #pushes = getOrCreateCounter(\n 'mutation',\n 'pushes',\n 'Number of pushes processed by the pusher',\n );\n\n constructor(\n config: Config,\n lc: LogContext,\n connContextManager: ConnectionContextManager,\n queue: Queue<PusherEntryOrStop>,\n ) {\n this.#lc = lc.withContext('component', 'pusher');\n this.#connContextManager = connContextManager;\n this.#queue = queue;\n this.#config = config;\n this.#clients = new Map();\n }\n\n /**\n * Returns a new downstream stream if the clientID,wsID pair has not been seen before.\n * If a clientID already exists with a different wsID, that client's downstream is cancelled.\n */\n initConnection(selector: ConnectionSelector) {\n const existing = this.#clients.get(selector.clientID);\n if (existing && existing.wsID === selector.wsID) {\n // already initialized for this socket\n throw new Error('Connection was already initialized');\n }\n\n // client is back on a new connection\n if (existing) {\n existing.downstream.cancel();\n }\n\n const downstream = Subscription.create<Downstream>({\n cleanup: () => {\n this.#clients.delete(selector.clientID);\n },\n });\n this.#clients.set(selector.clientID, {\n wsID: selector.wsID,\n downstream,\n });\n return downstream;\n }\n\n enqueuePush(connCtx: ConnectionContext, push: PushBody) {\n this.#queue.enqueue({\n push,\n connCtx,\n });\n }\n\n async run() {\n for (;;) {\n const task = await this.#queue.dequeue();\n const rest = this.#queue.drain();\n const [pushes, terminate] = combinePushes([task, ...rest]);\n for (const push of pushes) {\n const parentContext = push.push.traceparent\n ? propagation.extract(ROOT_CONTEXT, {\n traceparent: push.push.traceparent,\n })\n : context.active();\n const response = await context.with(parentContext, () =>\n this.#processPush(push),\n );\n await this.#fanOutResponses(response);\n }\n\n if (terminate) {\n break;\n }\n }\n }\n\n /**\n * 1. If the entire `push` fails, we send the error to relevant clients.\n * 2. If the push succeeds, we look for any mutation failure that should cause the connection to terminate\n * and terminate the connection for those clients.\n */\n #fanOutResponses(response: MutateResponse) {\n const connectionTerminations: (() => void)[] = [];\n\n // if the entire push failed, send that to the client.\n if (\n ('kind' in response && response.kind === ErrorKind.PushFailed) ||\n 'error' in response\n ) {\n this.#lc.warn?.(\n 'The server behind ZERO_MUTATE_URL returned a push error.',\n response,\n );\n // TODO(0xcadams): Fanout is keyed only by clientID here. If a response arrives\n // after reconnect or re-auth, `#clients.get(clientID)` may point at a\n // newer wsID/revision and fail the replacement downstream instead.\n const groupedMutationIDs = groupBy(\n response.mutationIDs ?? [],\n m => m.clientID,\n );\n for (const [clientID, mutationIDs] of groupedMutationIDs) {\n const client = this.#clients.get(clientID);\n if (!client) {\n continue;\n }\n\n // We do not resolve mutations on the client if the push fails\n // as those mutations will be retried.\n if ('error' in response) {\n // This error code path will eventually be removed when we\n // no longer support the legacy push error format.\n const pushFailedBody: PushFailedBody =\n response.error === 'http'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.HTTP,\n status: response.status,\n bodyPreview: response.details,\n mutationIDs,\n message: `Fetch from API server returned non-OK status ${response.status}`,\n }\n : response.error === 'unsupportedPushVersion'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.UnsupportedPushVersion,\n mutationIDs,\n message: `Unsupported push version`,\n }\n : {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Internal,\n mutationIDs,\n message:\n response.error === 'zeroPusher'\n ? response.details\n : response.error === 'unsupportedSchemaVersion'\n ? 'Unsupported schema version'\n : 'An unknown error occurred while pushing to the API server',\n };\n\n this.#failDownstream(client.downstream, pushFailedBody);\n } else {\n this.#failDownstream(client.downstream, response);\n }\n }\n } else {\n // Look for mutations results that should cause us to terminate the connection\n // TODO(0xcadams): Same stale-routing issue as above: fatal mutation results are\n // still mapped to the current downstream by clientID only.\n const groupedMutations = groupBy(response.mutations, m => m.id.clientID);\n for (const [clientID, mutations] of groupedMutations) {\n const client = this.#clients.get(clientID);\n if (!client) {\n continue;\n }\n\n let failure: PushFailedBody | undefined;\n let i = 0;\n for (; i < mutations.length; i++) {\n const m = mutations[i];\n if ('error' in m.result) {\n this.#lc.warn?.(\n 'The server behind ZERO_MUTATE_URL returned a mutation error.',\n m.result,\n );\n }\n // This error code path will eventually be removed,\n // keeping this for backwards compatibility, but the server\n // should now return a PushFailedBody with the mutationIDs\n if ('error' in m.result && m.result.error === 'oooMutation') {\n failure = {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.OutOfOrderMutation,\n message: 'mutation was out of order',\n details: m.result.details,\n mutationIDs: mutations.map(m => ({\n clientID: m.id.clientID,\n id: m.id.id,\n })),\n };\n break;\n }\n }\n\n if (failure && i < mutations.length - 1) {\n this.#lc.warn?.(\n 'push-response contains mutations after a mutation which should fatal the connection',\n );\n }\n\n if (failure) {\n connectionTerminations.push(() =>\n this.#failDownstream(client.downstream, failure),\n );\n }\n }\n }\n\n connectionTerminations.forEach(cb => cb());\n }\n\n async #processPush(entry: PusherEntry): Promise<MutateResponse> {\n this.#customMutations.add(entry.push.mutations.length, {\n clientGroupID: entry.push.clientGroupID,\n });\n this.#pushes.add(1, {\n clientGroupID: entry.push.clientGroupID,\n });\n\n // Record custom mutations for telemetry\n recordMutation('custom', entry.push.mutations.length);\n\n const url = must(\n entry.connCtx.mutateContext.url,\n 'ZERO_MUTATE_URL is not set',\n );\n\n this.#lc.debug?.(\n 'pushing to',\n url,\n 'with',\n entry.push.mutations.length,\n 'mutations',\n );\n\n let mutationIDs: MutationID[] = [];\n\n try {\n mutationIDs = entry.push.mutations.map(m => ({\n id: m.id,\n clientID: m.clientID,\n }));\n\n const response = await fetchFromAPIServer(\n mutateResponseSchema,\n 'push',\n this.#lc,\n entry.connCtx,\n {\n appID: this.#config.app.id,\n shardNum: this.#config.shard.num,\n },\n entry.push,\n {operation: 'mutate'},\n );\n if (\n ('kind' in response && response.kind === ErrorKind.PushFailed) ||\n 'error' in response\n ) {\n if (isAuthErrorBody(response)) {\n this.#lc.warn?.('Push auth failed; invalidating connection', {\n clientID: entry.connCtx.clientID,\n response: 'kind' in response ? response.message : undefined,\n });\n this.#connContextManager.failConnection(\n entry.connCtx,\n entry.connCtx.revision,\n );\n }\n return response;\n }\n // A successful push also validates this connection's current auth snapshot.\n // That lets later shared work reuse it without trusting stale credentials.\n this.#connContextManager.validateConnection(\n entry.connCtx,\n entry.connCtx.revision,\n 'kind' in response &&\n response.kind === 'MutateResponse' &&\n response?.userID !== undefined\n ? {kind: 'server-validated', validatedUserID: response.userID}\n : {kind: 'client-fallback'},\n );\n return response;\n } catch (e) {\n if (isProtocolError(e) && e.errorBody.kind === ErrorKind.PushFailed) {\n const response = {\n ...e.errorBody,\n mutationIDs,\n } as const satisfies PushFailedBody;\n if (isAuthErrorBody(response)) {\n this.#lc.warn?.('Push auth failed; invalidating connection', {\n clientID: entry.connCtx.clientID,\n response: 'kind' in response ? response.message : undefined,\n });\n this.#connContextManager.failConnection(\n entry.connCtx,\n entry.connCtx.revision,\n );\n }\n return response;\n }\n\n if (isProtocolError(e) && isAuthErrorBody(e.errorBody)) {\n // The push completed far enough for local validation to reject the\n // connection, so invalidate it and surface the result as PushFailed.\n this.#lc.warn?.('Push validation failed; invalidating connection', {\n clientID: entry.connCtx.clientID,\n response: e.message,\n });\n this.#connContextManager.failConnection(\n entry.connCtx,\n entry.connCtx.revision,\n );\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.HTTP,\n message: e.message,\n status: 401,\n mutationIDs,\n } as const satisfies PushFailedBody;\n }\n\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Failed to push: ${getErrorMessage(e)}`,\n mutationIDs,\n } as const satisfies PushFailedBody;\n }\n }\n\n #failDownstream(\n downstream: Subscription<Downstream>,\n errorBody: PushFailedBody,\n ): void {\n downstream.fail(new ProtocolErrorWithLevel(errorBody, 'warn'));\n }\n}\n\n/**\n * Pushes for different clients, sockets, or auth revisions could be interleaved.\n *\n * In order to batch safely, we only combine pushes from the same\n * clientID/wsID/revision snapshot.\n */\nexport function combinePushes(\n entries: readonly (PusherEntryOrStop | undefined)[],\n): [PusherEntry[], boolean] {\n const pushesByConnection = new Map<string, PusherEntry[]>();\n\n function collect() {\n const ret: PusherEntry[] = [];\n for (const entries of pushesByConnection.values()) {\n const composite: PusherEntry = {\n ...entries[0],\n push: {\n ...entries[0].push,\n mutations: [],\n },\n };\n ret.push(composite);\n for (const entry of entries) {\n assertAreCompatiblePushes(composite, entry);\n composite.push.mutations.push(...entry.push.mutations);\n }\n }\n return ret;\n }\n\n for (const entry of entries) {\n if (entry === 'stop' || entry === undefined) {\n return [collect(), true];\n }\n\n const key = `${entry.connCtx.clientID}:${entry.connCtx.wsID}:${entry.connCtx.revision}`;\n const existing = pushesByConnection.get(key);\n if (existing) {\n existing.push(entry);\n } else {\n pushesByConnection.set(key, [entry]);\n }\n }\n\n return [collect(), false] as const;\n}\n\n// These invariants should always be true for a given clientID.\n// If they are not, we have a bug in the code somewhere.\nfunction assertAreCompatiblePushes(left: PusherEntry, right: PusherEntry) {\n assert(\n left.connCtx.clientID === right.connCtx.clientID,\n 'clientID must be the same for all pushes',\n );\n assert(\n left.connCtx.wsID === right.connCtx.wsID,\n 'wsID must be the same for all pushes',\n );\n assert(\n left.connCtx.revision === right.connCtx.revision,\n 'revision must be the same for all pushes',\n );\n assert(\n authEquals(left.connCtx.auth, right.connCtx.auth),\n 'auth must be the same for all pushes with the same clientID',\n );\n assert(\n left.push.schemaVersion === right.push.schemaVersion,\n 'schemaVersion must be the same for all pushes with the same clientID',\n );\n assert(\n left.push.pushVersion === right.push.pushVersion,\n 'pushVersion must be the same for all pushes with the same clientID',\n );\n assert(\n left.connCtx.mutateContext.headerOptions.cookie ===\n right.connCtx.mutateContext.headerOptions.cookie,\n 'httpCookie must be the same for all pushes with the same clientID',\n );\n assert(\n left.connCtx.mutateContext.headerOptions.origin ===\n right.connCtx.mutateContext.headerOptions.origin,\n 'origin must be the same for all pushes with the same clientID',\n );\n assert(\n left.connCtx.user.id === right.connCtx.user.id,\n 'userID must be the same for all pushes with the same clientID',\n );\n assert(\n left.connCtx.mutateContext.url === right.connCtx.mutateContext.url,\n 'userPushURL must be the same for all pushes with the same clientID',\n );\n}\n"],"mappings":"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"}
@@ -3,8 +3,12 @@ import { getOrCreateCounter, getOrCreateLatencyHistogram } from "../../observabi
3
3
  import { RunningState } from "../running-state.js";
4
4
  import { shadowInitialSync } from "../change-source/pg/initial-sync.js";
5
5
  //#region ../zero-cache/src/services/shadow-sync/shadow-sync-service.ts
6
- var shadowSyncRuns = getOrCreateCounter("replication", "shadow-sync-runs", "Number of shadow initial-sync runs, labeled by result.");
7
- var shadowSyncDuration = getOrCreateLatencyHistogram("replication", "shadow-sync-duration", "Wall-clock duration of a shadow initial-sync run, labeled by result.");
6
+ function shadowSyncRuns() {
7
+ return getOrCreateCounter("replication", "shadow-sync-runs", "Number of shadow initial-sync runs, labeled by result.");
8
+ }
9
+ function shadowSyncDuration() {
10
+ return getOrCreateLatencyHistogram("replication", "shadow-sync-duration", "Wall-clock duration of a shadow initial-sync run, labeled by result.");
11
+ }
8
12
  var ShadowSyncService = class {
9
13
  id = "shadow-syncer";
10
14
  #lc;
@@ -34,13 +38,13 @@ var ShadowSyncService = class {
34
38
  maxRowsPerTable
35
39
  }, this.#context, textCopy !== void 0 ? { textCopy } : void 0);
36
40
  const elapsed = performance.now() - start;
37
- shadowSyncRuns.add(1, { result: "success" });
38
- shadowSyncDuration.recordMs(elapsed, { result: "success" });
41
+ shadowSyncRuns().add(1, { result: "success" });
42
+ shadowSyncDuration().recordMs(elapsed, { result: "success" });
39
43
  this.#lc.info?.(`shadow initial-sync completed (${elapsed.toFixed(0)} ms)`);
40
44
  } catch (e) {
41
45
  const elapsed = performance.now() - start;
42
- shadowSyncRuns.add(1, { result: "error" });
43
- shadowSyncDuration.recordMs(elapsed, { result: "error" });
46
+ shadowSyncRuns().add(1, { result: "error" });
47
+ shadowSyncDuration().recordMs(elapsed, { result: "error" });
44
48
  this.#lc.error?.(`shadow initial-sync failed after ${elapsed.toFixed(0)} ms`, e);
45
49
  }
46
50
  await this.#state.sleep(intervalMs);
@@ -1 +1 @@
1
- {"version":3,"file":"shadow-sync-service.js","names":["#lc","#shard","#upstreamURI","#context","#options","#state"],"sources":["../../../../../../zero-cache/src/services/shadow-sync/shadow-sync-service.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport type {ShardConfig} from '../../types/shards.ts';\nimport type {ServerContext} from '../change-source/pg/initial-sync.ts';\nimport {shadowInitialSync} from '../change-source/pg/initial-sync.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {Service} from '../service.ts';\n\nconst shadowSyncRuns = getOrCreateCounter(\n 'replication',\n 'shadow-sync-runs',\n 'Number of shadow initial-sync runs, labeled by result.',\n);\n\nconst shadowSyncDuration = getOrCreateLatencyHistogram(\n 'replication',\n 'shadow-sync-duration',\n 'Wall-clock duration of a shadow initial-sync run, labeled by result.',\n);\n\nexport type ShadowSyncOptions = {\n intervalMs: number;\n sampleRate: number;\n maxRowsPerTable: number;\n textCopy?: boolean | undefined;\n};\n\nexport class ShadowSyncService implements Service {\n readonly id = 'shadow-syncer';\n\n readonly #lc: LogContext;\n readonly #shard: ShardConfig;\n readonly #upstreamURI: string;\n readonly #context: ServerContext;\n readonly #options: ShadowSyncOptions;\n readonly #state = new RunningState('shadow-syncer');\n\n constructor(\n lc: LogContext,\n shard: ShardConfig,\n upstreamURI: string,\n context: ServerContext,\n options: ShadowSyncOptions,\n ) {\n this.#lc = lc;\n this.#shard = shard;\n this.#upstreamURI = upstreamURI;\n this.#context = context;\n this.#options = options;\n }\n\n async run() {\n const {intervalMs, sampleRate, maxRowsPerTable, textCopy} = this.#options;\n\n // Why: first run fires in [intervalMs * 2/3, intervalMs) — late enough\n // that shadow sync never fires immediately on task startup, but always\n // before one full interval elapses so the canary completes at least once\n // per task lifetime (the replication manager is restarted every ~24h).\n // The last third is randomized so a fleet-wide restart does not cause\n // every task to canary simultaneously.\n const minFirstRunDelay = Math.floor((intervalMs * 2) / 3);\n const firstRunDelay =\n minFirstRunDelay +\n Math.floor(Math.random() * (intervalMs - minFirstRunDelay));\n this.#lc.info?.(\n `shadow-syncer started; first run in ${firstRunDelay} ms, then every ${intervalMs} ms`,\n );\n await this.#state.sleep(firstRunDelay);\n\n while (this.#state.shouldRun()) {\n const start = performance.now();\n try {\n await shadowInitialSync(\n this.#lc,\n this.#shard,\n this.#upstreamURI,\n {sampleRate, maxRowsPerTable},\n this.#context,\n textCopy !== undefined ? {textCopy} : undefined,\n );\n const elapsed = performance.now() - start;\n shadowSyncRuns.add(1, {result: 'success'});\n shadowSyncDuration.recordMs(elapsed, {result: 'success'});\n this.#lc.info?.(\n `shadow initial-sync completed (${elapsed.toFixed(0)} ms)`,\n );\n } catch (e) {\n const elapsed = performance.now() - start;\n shadowSyncRuns.add(1, {result: 'error'});\n shadowSyncDuration.recordMs(elapsed, {result: 'error'});\n this.#lc.error?.(\n `shadow initial-sync failed after ${elapsed.toFixed(0)} ms`,\n e,\n );\n }\n await this.#state.sleep(intervalMs);\n }\n }\n\n stop(): Promise<void> {\n this.#state.stop(this.#lc);\n return promiseVoid;\n }\n}\n"],"mappings":";;;;;AAYA,IAAM,iBAAiB,mBACrB,eACA,oBACA,wDACF;AAEA,IAAM,qBAAqB,4BACzB,eACA,wBACA,sEACF;AASA,IAAa,oBAAb,MAAkD;CAChD,KAAc;CAEd;CACA;CACA;CACA;CACA;CACA,SAAkB,IAAI,aAAa,eAAe;CAElD,YACE,IACA,OACA,aACA,SACA,SACA;EACA,KAAKA,MAAM;EACX,KAAKC,SAAS;EACd,KAAKC,eAAe;EACpB,KAAKC,WAAW;EAChB,KAAKC,WAAW;CAClB;CAEA,MAAM,MAAM;EACV,MAAM,EAAC,YAAY,YAAY,iBAAiB,aAAY,KAAKA;EAQjE,MAAM,mBAAmB,KAAK,MAAO,aAAa,IAAK,CAAC;EACxD,MAAM,gBACJ,mBACA,KAAK,MAAM,KAAK,OAAO,KAAK,aAAa,iBAAiB;EAC5D,KAAKJ,IAAI,OACP,uCAAuC,cAAc,kBAAkB,WAAW,IACpF;EACA,MAAM,KAAKK,OAAO,MAAM,aAAa;EAErC,OAAO,KAAKA,OAAO,UAAU,GAAG;GAC9B,MAAM,QAAQ,YAAY,IAAI;GAC9B,IAAI;IACF,MAAM,kBACJ,KAAKL,KACL,KAAKC,QACL,KAAKC,cACL;KAAC;KAAY;IAAe,GAC5B,KAAKC,UACL,aAAa,KAAA,IAAY,EAAC,SAAQ,IAAI,KAAA,CACxC;IACA,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,eAAe,IAAI,GAAG,EAAC,QAAQ,UAAS,CAAC;IACzC,mBAAmB,SAAS,SAAS,EAAC,QAAQ,UAAS,CAAC;IACxD,KAAKH,IAAI,OACP,kCAAkC,QAAQ,QAAQ,CAAC,EAAE,KACvD;GACF,SAAS,GAAG;IACV,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,eAAe,IAAI,GAAG,EAAC,QAAQ,QAAO,CAAC;IACvC,mBAAmB,SAAS,SAAS,EAAC,QAAQ,QAAO,CAAC;IACtD,KAAKA,IAAI,QACP,oCAAoC,QAAQ,QAAQ,CAAC,EAAE,MACvD,CACF;GACF;GACA,MAAM,KAAKK,OAAO,MAAM,UAAU;EACpC;CACF;CAEA,OAAsB;EACpB,KAAKA,OAAO,KAAK,KAAKL,GAAG;EACzB,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"shadow-sync-service.js","names":["#lc","#shard","#upstreamURI","#context","#options","#state"],"sources":["../../../../../../zero-cache/src/services/shadow-sync/shadow-sync-service.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport type {ShardConfig} from '../../types/shards.ts';\nimport type {ServerContext} from '../change-source/pg/initial-sync.ts';\nimport {shadowInitialSync} from '../change-source/pg/initial-sync.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {Service} from '../service.ts';\n\nfunction shadowSyncRuns() {\n return getOrCreateCounter(\n 'replication',\n 'shadow-sync-runs',\n 'Number of shadow initial-sync runs, labeled by result.',\n );\n}\n\nfunction shadowSyncDuration() {\n return getOrCreateLatencyHistogram(\n 'replication',\n 'shadow-sync-duration',\n 'Wall-clock duration of a shadow initial-sync run, labeled by result.',\n );\n}\n\nexport type ShadowSyncOptions = {\n intervalMs: number;\n sampleRate: number;\n maxRowsPerTable: number;\n textCopy?: boolean | undefined;\n};\n\nexport class ShadowSyncService implements Service {\n readonly id = 'shadow-syncer';\n\n readonly #lc: LogContext;\n readonly #shard: ShardConfig;\n readonly #upstreamURI: string;\n readonly #context: ServerContext;\n readonly #options: ShadowSyncOptions;\n readonly #state = new RunningState('shadow-syncer');\n\n constructor(\n lc: LogContext,\n shard: ShardConfig,\n upstreamURI: string,\n context: ServerContext,\n options: ShadowSyncOptions,\n ) {\n this.#lc = lc;\n this.#shard = shard;\n this.#upstreamURI = upstreamURI;\n this.#context = context;\n this.#options = options;\n }\n\n async run() {\n const {intervalMs, sampleRate, maxRowsPerTable, textCopy} = this.#options;\n\n // Why: first run fires in [intervalMs * 2/3, intervalMs) — late enough\n // that shadow sync never fires immediately on task startup, but always\n // before one full interval elapses so the canary completes at least once\n // per task lifetime (the replication manager is restarted every ~24h).\n // The last third is randomized so a fleet-wide restart does not cause\n // every task to canary simultaneously.\n const minFirstRunDelay = Math.floor((intervalMs * 2) / 3);\n const firstRunDelay =\n minFirstRunDelay +\n Math.floor(Math.random() * (intervalMs - minFirstRunDelay));\n this.#lc.info?.(\n `shadow-syncer started; first run in ${firstRunDelay} ms, then every ${intervalMs} ms`,\n );\n await this.#state.sleep(firstRunDelay);\n\n while (this.#state.shouldRun()) {\n const start = performance.now();\n try {\n await shadowInitialSync(\n this.#lc,\n this.#shard,\n this.#upstreamURI,\n {sampleRate, maxRowsPerTable},\n this.#context,\n textCopy !== undefined ? {textCopy} : undefined,\n );\n const elapsed = performance.now() - start;\n shadowSyncRuns().add(1, {result: 'success'});\n shadowSyncDuration().recordMs(elapsed, {result: 'success'});\n this.#lc.info?.(\n `shadow initial-sync completed (${elapsed.toFixed(0)} ms)`,\n );\n } catch (e) {\n const elapsed = performance.now() - start;\n shadowSyncRuns().add(1, {result: 'error'});\n shadowSyncDuration().recordMs(elapsed, {result: 'error'});\n this.#lc.error?.(\n `shadow initial-sync failed after ${elapsed.toFixed(0)} ms`,\n e,\n );\n }\n await this.#state.sleep(intervalMs);\n }\n }\n\n stop(): Promise<void> {\n this.#state.stop(this.#lc);\n return promiseVoid;\n }\n}\n"],"mappings":";;;;;AAYA,SAAS,iBAAiB;CACxB,OAAO,mBACL,eACA,oBACA,wDACF;AACF;AAEA,SAAS,qBAAqB;CAC5B,OAAO,4BACL,eACA,wBACA,sEACF;AACF;AASA,IAAa,oBAAb,MAAkD;CAChD,KAAc;CAEd;CACA;CACA;CACA;CACA;CACA,SAAkB,IAAI,aAAa,eAAe;CAElD,YACE,IACA,OACA,aACA,SACA,SACA;EACA,KAAKA,MAAM;EACX,KAAKC,SAAS;EACd,KAAKC,eAAe;EACpB,KAAKC,WAAW;EAChB,KAAKC,WAAW;CAClB;CAEA,MAAM,MAAM;EACV,MAAM,EAAC,YAAY,YAAY,iBAAiB,aAAY,KAAKA;EAQjE,MAAM,mBAAmB,KAAK,MAAO,aAAa,IAAK,CAAC;EACxD,MAAM,gBACJ,mBACA,KAAK,MAAM,KAAK,OAAO,KAAK,aAAa,iBAAiB;EAC5D,KAAKJ,IAAI,OACP,uCAAuC,cAAc,kBAAkB,WAAW,IACpF;EACA,MAAM,KAAKK,OAAO,MAAM,aAAa;EAErC,OAAO,KAAKA,OAAO,UAAU,GAAG;GAC9B,MAAM,QAAQ,YAAY,IAAI;GAC9B,IAAI;IACF,MAAM,kBACJ,KAAKL,KACL,KAAKC,QACL,KAAKC,cACL;KAAC;KAAY;IAAe,GAC5B,KAAKC,UACL,aAAa,KAAA,IAAY,EAAC,SAAQ,IAAI,KAAA,CACxC;IACA,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,eAAe,EAAE,IAAI,GAAG,EAAC,QAAQ,UAAS,CAAC;IAC3C,mBAAmB,EAAE,SAAS,SAAS,EAAC,QAAQ,UAAS,CAAC;IAC1D,KAAKH,IAAI,OACP,kCAAkC,QAAQ,QAAQ,CAAC,EAAE,KACvD;GACF,SAAS,GAAG;IACV,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,eAAe,EAAE,IAAI,GAAG,EAAC,QAAQ,QAAO,CAAC;IACzC,mBAAmB,EAAE,SAAS,SAAS,EAAC,QAAQ,QAAO,CAAC;IACxD,KAAKA,IAAI,QACP,oCAAoC,QAAQ,QAAQ,CAAC,EAAE,MACvD,CACF;GACF;GACA,MAAM,KAAKK,OAAO,MAAM,UAAU;EACpC;CACF;CAEA,OAAsB;EACpB,KAAKA,OAAO,KAAK,KAAKL,GAAG;EACzB,OAAO;CACT;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"cvr-store.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cvr-store.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-store.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ import { DEFAULT_TTL_MS, clampTTL } from "../../../../zql/src/query/ttl.js";
11
11
  import { cvrSchema } from "../../types/shards.js";
12
12
  import { startAsyncSpan } from "../../../../otel/src/span.js";
13
13
  import { version } from "../../../../otel/src/version.js";
14
+ import { getOrCreateCounter, getOrCreateLatencyHistogram } from "../../observability/metrics.js";
14
15
  import { rowIDString } from "../../types/row-key.js";
15
16
  import { READONLY, READ_COMMITTED } from "../../db/mode-enum.js";
16
17
  import { runTx } from "../../db/run-transaction.js";
@@ -26,6 +27,9 @@ import { RowRecordCache } from "./row-record-cache.js";
26
27
  import { trace } from "@opentelemetry/api";
27
28
  //#region ../zero-cache/src/services/view-syncer/cvr-store.ts
28
29
  var flushCounter = 0;
30
+ var RESULT_ATTRIBUTE = "result";
31
+ var ERROR_KIND_ATTRIBUTE = "error.kind";
32
+ var CVR_FLUSH_TYPE_ATTRIBUTE = "flush.type";
29
33
  /**
30
34
  * Convert TTL/timestamp values for both old (seconds-based) and new (ms-based) columns.
31
35
  * Old columns: inactivatedAt (TIMESTAMPTZ), ttl (INTERVAL) - need conversion ms->seconds
@@ -89,6 +93,9 @@ var CVRStore = class {
89
93
  #pendingRowRecordUpdates = new CustomKeyMap(rowIDString);
90
94
  #forceUpdates = new CustomKeySet(rowIDString);
91
95
  #rowCache;
96
+ #cvrLoads = getOrCreateCounter("sync", "cvr.load_attempts", "CVR load attempts, labeled by result.");
97
+ #cvrLoadTime = getOrCreateLatencyHistogram("sync", "cvr.load_duration", "Time to load a CVR.");
98
+ #cvrFlushes = getOrCreateCounter("sync", "cvr.flush_attempts", "CVR flush attempts, labeled by result and flush.type.");
92
99
  #loadAttemptIntervalMs;
93
100
  #maxLoadAttempts;
94
101
  #rowCount = 0;
@@ -116,22 +123,36 @@ var CVRStore = class {
116
123
  } : fields);
117
124
  }
118
125
  load(lc, lastConnectTime) {
126
+ const start = performance.now();
119
127
  return startAsyncSpan(tracer, "cvr.load", async () => {
120
- let err;
121
- for (let i = 0; i < this.#maxLoadAttempts; i++) {
122
- if (i > 0) await sleep(this.#loadAttemptIntervalMs);
123
- const result = await this.#load(lc, lastConnectTime);
124
- if (result instanceof RowsVersionBehindError) {
125
- lc.info?.(`attempt ${i + 1}: ${String(result)}`);
126
- err = result;
127
- continue;
128
+ try {
129
+ let err;
130
+ for (let i = 0; i < this.#maxLoadAttempts; i++) {
131
+ if (i > 0) await sleep(this.#loadAttemptIntervalMs);
132
+ const result = await this.#load(lc, lastConnectTime);
133
+ if (result instanceof RowsVersionBehindError) {
134
+ lc.info?.(`attempt ${i + 1}: ${String(result)}`);
135
+ err = result;
136
+ continue;
137
+ }
138
+ this.#recordLoad(performance.now() - start, { [RESULT_ATTRIBUTE]: "success" });
139
+ return result;
128
140
  }
129
- return result;
141
+ assert(err, "Expected error to be set after retry loop exhausted");
142
+ throw new ClientNotFoundError(`max attempts exceeded waiting for CVR@${err.cvrVersion} to catch up from ${err.rowsVersion}`);
143
+ } catch (e) {
144
+ this.#recordLoad(performance.now() - start, {
145
+ [RESULT_ATTRIBUTE]: "error",
146
+ [ERROR_KIND_ATTRIBUTE]: cvrErrorKind(e)
147
+ });
148
+ throw e;
130
149
  }
131
- assert(err, "Expected error to be set after retry loop exhausted");
132
- throw new ClientNotFoundError(`max attempts exceeded waiting for CVR@${err.cvrVersion} to catch up from ${err.rowsVersion}`);
133
150
  });
134
151
  }
152
+ #recordLoad(elapsedMs, attributes) {
153
+ this.#cvrLoads.add(1, attributes);
154
+ this.#cvrLoadTime.recordMs(elapsedMs, attributes);
155
+ }
135
156
  async #load(lc, lastConnectTime) {
136
157
  const start = Date.now();
137
158
  const id = this.#id;
@@ -744,8 +765,17 @@ var CVRStore = class {
744
765
  lc.info?.(`flushed cvr@${versionString(cvr.version)} ${JSON.stringify(stats)} in (${elapsed} ms)`);
745
766
  this.#rowCache.recordSyncFlushStats(stats, elapsed);
746
767
  }
768
+ this.#cvrFlushes.add(1, {
769
+ [RESULT_ATTRIBUTE]: "success",
770
+ [CVR_FLUSH_TYPE_ATTRIBUTE]: stats ? "sync" : "noop"
771
+ });
747
772
  return stats;
748
773
  } catch (e) {
774
+ this.#cvrFlushes.add(1, {
775
+ [RESULT_ATTRIBUTE]: "error",
776
+ [CVR_FLUSH_TYPE_ATTRIBUTE]: "sync",
777
+ [ERROR_KIND_ATTRIBUTE]: cvrErrorKind(e)
778
+ });
749
779
  this.#rowCache.clear();
750
780
  throw e;
751
781
  } finally {
@@ -854,6 +884,13 @@ var InvalidClientSchemaError = class extends ProtocolErrorWithLevel {
854
884
  }, "warn", { cause });
855
885
  }
856
886
  };
887
+ function cvrErrorKind(e) {
888
+ if (e instanceof ClientNotFoundError) return "client_not_found";
889
+ if (e instanceof ConcurrentModificationException) return "concurrent_modification";
890
+ if (e instanceof OwnershipError) return "ownership";
891
+ if (e instanceof InvalidClientSchemaError) return "invalid_client_schema";
892
+ return "error";
893
+ }
857
894
  var RowsVersionBehindError = class extends Error {
858
895
  name = "RowsVersionBehindError";
859
896
  cvrVersion;