@rocicorp/zero 1.5.0-canary.3 → 1.6.0-canary.0

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 (172) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/replicache/src/btree/node.d.ts +3 -0
  4. package/out/replicache/src/btree/node.d.ts.map +1 -1
  5. package/out/replicache/src/btree/node.js +114 -1
  6. package/out/replicache/src/btree/node.js.map +1 -1
  7. package/out/replicache/src/btree/write.d.ts +7 -0
  8. package/out/replicache/src/btree/write.d.ts.map +1 -1
  9. package/out/replicache/src/btree/write.js +50 -0
  10. package/out/replicache/src/btree/write.js.map +1 -1
  11. package/out/replicache/src/db/write.d.ts +8 -0
  12. package/out/replicache/src/db/write.d.ts.map +1 -1
  13. package/out/replicache/src/db/write.js +15 -0
  14. package/out/replicache/src/db/write.js.map +1 -1
  15. package/out/replicache/src/kv/sqlite-store.d.ts +2 -5
  16. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  17. package/out/replicache/src/kv/sqlite-store.js +21 -24
  18. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  19. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  20. package/out/replicache/src/replicache-impl.js.map +1 -1
  21. package/out/replicache/src/sync/patch.d.ts +15 -0
  22. package/out/replicache/src/sync/patch.d.ts.map +1 -1
  23. package/out/replicache/src/sync/patch.js +85 -26
  24. package/out/replicache/src/sync/patch.js.map +1 -1
  25. package/out/shared/src/testing.d.ts +3 -0
  26. package/out/shared/src/testing.d.ts.map +1 -0
  27. package/out/zero/package.js +5 -6
  28. package/out/zero/package.js.map +1 -1
  29. package/out/zero-cache/src/auth/write-authorizer.js +1 -1
  30. package/out/zero-cache/src/config/zero-config.d.ts +4 -0
  31. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  32. package/out/zero-cache/src/config/zero-config.js +8 -0
  33. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  34. package/out/zero-cache/src/server/inspector-delegate.d.ts +3 -2
  35. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  36. package/out/zero-cache/src/server/inspector-delegate.js +19 -9
  37. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  38. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  39. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  40. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  41. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +7 -6
  42. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  43. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/change-source/pg/change-source.js +49 -66
  45. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  46. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +0 -8
  47. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +22 -52
  49. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  50. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +57 -0
  51. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -0
  52. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +162 -0
  53. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -0
  54. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/change-source/pg/schema/init.js +18 -0
  56. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  57. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +17 -3
  58. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +43 -16
  60. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  61. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -3
  62. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +5 -5
  64. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  65. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +10 -1
  66. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  67. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +13 -3
  68. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  69. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +6 -11
  70. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/change-streamer/change-streamer.js +0 -1
  72. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  73. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/change-streamer/forwarder.js +2 -2
  75. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  76. package/out/zero-cache/src/services/change-streamer/storer.d.ts +12 -5
  77. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  78. package/out/zero-cache/src/services/change-streamer/storer.js +43 -21
  79. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  80. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +4 -5
  81. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  82. package/out/zero-cache/src/services/change-streamer/subscriber.js +18 -16
  83. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  84. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  85. package/out/zero-cache/src/services/litestream/commands.js +3 -2
  86. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  87. package/out/zero-cache/src/services/litestream/config.yml +1 -0
  88. package/out/zero-cache/src/services/mutagen/pusher.d.ts +2 -2
  89. package/out/zero-cache/src/services/view-syncer/cvr-store.js +2 -2
  90. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  91. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +1 -1
  92. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  93. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  94. package/out/zero-cache/src/services/view-syncer/view-syncer.js +5 -6
  95. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  96. package/out/zero-cache/src/types/streams.d.ts +4 -0
  97. package/out/zero-cache/src/types/streams.d.ts.map +1 -1
  98. package/out/zero-cache/src/types/streams.js +13 -10
  99. package/out/zero-cache/src/types/streams.js.map +1 -1
  100. package/out/zero-cache/src/workers/connection.js +5 -5
  101. package/out/zero-cache/src/workers/connection.js.map +1 -1
  102. package/out/zero-client/src/client/inspector/inspector.d.ts.map +1 -1
  103. package/out/zero-client/src/client/inspector/inspector.js +15 -2
  104. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  105. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts +9 -3
  106. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
  107. package/out/zero-client/src/client/inspector/lazy-inspector.js +27 -6
  108. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  109. package/out/zero-client/src/client/inspector/query.d.ts.map +1 -1
  110. package/out/zero-client/src/client/inspector/query.js +3 -3
  111. package/out/zero-client/src/client/inspector/query.js.map +1 -1
  112. package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
  113. package/out/zero-client/src/client/ivm-branch.js +16 -2
  114. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  115. package/out/zero-client/src/client/options.d.ts +12 -4
  116. package/out/zero-client/src/client/options.d.ts.map +1 -1
  117. package/out/zero-client/src/client/options.js.map +1 -1
  118. package/out/zero-client/src/client/query-manager.d.ts +8 -1
  119. package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
  120. package/out/zero-client/src/client/query-manager.js +28 -3
  121. package/out/zero-client/src/client/query-manager.js.map +1 -1
  122. package/out/zero-client/src/client/version.js +1 -1
  123. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  124. package/out/zero-client/src/client/zero.js +12 -11
  125. package/out/zero-client/src/client/zero.js.map +1 -1
  126. package/out/zero-protocol/src/down.d.ts +1 -1
  127. package/out/zero-protocol/src/inspect-down.d.ts +15 -4
  128. package/out/zero-protocol/src/inspect-down.d.ts.map +1 -1
  129. package/out/zero-protocol/src/inspect-down.js +11 -1
  130. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  131. package/out/zero-protocol/src/protocol-version.d.ts +1 -1
  132. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  133. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  134. package/out/zero-react/src/use-query.d.ts.map +1 -1
  135. package/out/zero-react/src/use-query.js.map +1 -1
  136. package/out/zero-react/src/zero-provider.d.ts +6 -0
  137. package/out/zero-react/src/zero-provider.d.ts.map +1 -1
  138. package/out/zero-react/src/zero-provider.js +21 -1
  139. package/out/zero-react/src/zero-provider.js.map +1 -1
  140. package/out/zero-solid/src/use-zero.d.ts +6 -0
  141. package/out/zero-solid/src/use-zero.d.ts.map +1 -1
  142. package/out/zero-solid/src/use-zero.js +24 -4
  143. package/out/zero-solid/src/use-zero.js.map +1 -1
  144. package/out/zql/src/builder/builder.d.ts.map +1 -1
  145. package/out/zql/src/builder/builder.js +18 -8
  146. package/out/zql/src/builder/builder.js.map +1 -1
  147. package/out/zql/src/ivm/cap.d.ts +32 -0
  148. package/out/zql/src/ivm/cap.d.ts.map +1 -0
  149. package/out/zql/src/ivm/cap.js +205 -0
  150. package/out/zql/src/ivm/cap.js.map +1 -0
  151. package/out/zql/src/ivm/constraint.d.ts.map +1 -1
  152. package/out/zql/src/ivm/constraint.js.map +1 -1
  153. package/out/zql/src/ivm/flipped-join.d.ts +9 -0
  154. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  155. package/out/zql/src/ivm/flipped-join.js +56 -69
  156. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  157. package/out/zql/src/ivm/memory-source.d.ts +24 -3
  158. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  159. package/out/zql/src/ivm/memory-source.js +162 -7
  160. package/out/zql/src/ivm/memory-source.js.map +1 -1
  161. package/out/zql/src/ivm/operator.d.ts +26 -0
  162. package/out/zql/src/ivm/operator.d.ts.map +1 -1
  163. package/out/zql/src/ivm/operator.js.map +1 -1
  164. package/out/zql/src/ivm/take.js +2 -2
  165. package/out/zqlite/src/query-builder.d.ts +14 -2
  166. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  167. package/out/zqlite/src/query-builder.js +32 -1
  168. package/out/zqlite/src/query-builder.js.map +1 -1
  169. package/out/zqlite/src/table-source.d.ts.map +1 -1
  170. package/out/zqlite/src/table-source.js +4 -4
  171. package/out/zqlite/src/table-source.js.map +1 -1
  172. package/package.json +5 -6
@@ -48,6 +48,10 @@ type PipeOptions<T> = {
48
48
  };
49
49
  export declare function pipe<T>({ source, sink, parse, bufferMessages }: PipeOptions<T>): void;
50
50
  export declare function streamOut<T extends JSONValue>(lc: LogContext, source: Source<T>, sink: WebSocket): Promise<void>;
51
+ /**
52
+ * Streams out a `Source` for which messages are already stringified JSON.
53
+ */
54
+ export declare function streamOutStringified(lc: LogContext, source: Source<string>, sink: WebSocket): Promise<void>;
51
55
  export declare function streamIn<T extends JSONValue>(lc: LogContext, source: WebSocket, schema: v.Type<T>): Promise<Source<T>>;
52
56
  export {};
53
57
  //# sourceMappingURL=streams.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/streams.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAGR,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,IAAI,CAAC;AAEZ,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAE9E,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAAC,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,mBAAmB,CAAC;AAW7D,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG;IACzC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAE9B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAC,CAAC,GAAG,SAAS,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI;IACpB,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF;;;GAGG;AAIH,wBAAgB,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,SAAS,EAChE,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,SAAS,EACb,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EACpB,UAAU,GAAE,OAAO,CAAC,GAAG,CAAM,EAC7B,SAAS,GAAE,OAAO,CAAC,EAAE,CAAM,EAC3B,aAAa,GAAE,aAAkB,GAChC;IAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;CAAC,CAwE9C;AAED,KAAK,WAAW,CAAC,CAAC,IAAI;IACpB,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IACtB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,IAAI,CAAC,CAAC,EAAE,EAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAC,EAAE,WAAW,CAAC,CAAC,CAAC,QAsD5E;AAkBD,wBAAsB,SAAS,CAAC,CAAC,SAAS,SAAS,EACjD,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EACjB,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED,wBAAsB,QAAQ,CAAC,CAAC,SAAS,SAAS,EAChD,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAChB,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAqCpB"}
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/streams.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAGR,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,IAAI,CAAC;AAEZ,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAE9E,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAAC,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,mBAAmB,CAAC;AAW7D,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG;IACzC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAE9B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAC,CAAC,GAAG,SAAS,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI;IACpB,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF;;;GAGG;AAIH,wBAAgB,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,SAAS,EAChE,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,SAAS,EACb,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EACpB,UAAU,GAAE,OAAO,CAAC,GAAG,CAAM,EAC7B,SAAS,GAAE,OAAO,CAAC,EAAE,CAAM,EAC3B,aAAa,GAAE,aAAkB,GAChC;IAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;CAAC,CAwE9C;AAED,KAAK,WAAW,CAAC,CAAC,IAAI;IACpB,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IACtB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,IAAI,CAAC,CAAC,EAAE,EAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAC,EAAE,WAAW,CAAC,CAAC,CAAC,QAsD5E;AAkBD,wBAAgB,SAAS,CAAC,CAAC,SAAS,SAAS,EAC3C,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EACjB,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EACtB,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC,CAEf;AAmED,wBAAsB,QAAQ,CAAC,CAAC,SAAS,SAAS,EAChD,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAChB,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAqCpB"}
@@ -102,7 +102,16 @@ function ensureError(err) {
102
102
  return err instanceof Error ? err : new Error(String(err));
103
103
  }
104
104
  var ackSchema = valita_exports.object({ ack: valita_exports.number() });
105
- async function streamOut(lc, source, sink) {
105
+ function streamOut(lc, source, sink) {
106
+ return streamOutInternal(lc, source, sink, BigIntJSON.stringify);
107
+ }
108
+ /**
109
+ * Streams out a `Source` for which messages are already stringified JSON.
110
+ */
111
+ function streamOutStringified(lc, source, sink) {
112
+ return streamOutInternal(lc, source, sink, (json) => json);
113
+ }
114
+ async function streamOutInternal(lc, source, sink, stringify) {
106
115
  sendPingsForLiveness(lc, sink, PING_INTERVAL_MS);
107
116
  const closer = WebSocketCloser.forSource(lc, sink, source);
108
117
  const acks = new Queue();
@@ -122,10 +131,7 @@ async function streamOut(lc, source, sink) {
122
131
  lc.debug?.(`started pipelined outbound stream`);
123
132
  for await (const { value: msg, consumed } of pipeline) {
124
133
  const id = ++nextID;
125
- const data = BigIntJSON.stringify({
126
- msg,
127
- id
128
- });
134
+ const data = `{"id":${id},"msg":${stringify(msg)}}`;
129
135
  sink.send(data);
130
136
  (async () => {
131
137
  const { ack } = await acks.dequeue();
@@ -137,10 +143,7 @@ async function streamOut(lc, source, sink) {
137
143
  lc.debug?.(`started synchronous outbound stream`);
138
144
  for await (const msg of source) {
139
145
  const id = ++nextID;
140
- const data = BigIntJSON.stringify({
141
- msg,
142
- id
143
- });
146
+ const data = `{"id":${id},"msg":${stringify(msg)}}`;
144
147
  sink.send(data);
145
148
  const { ack } = await acks.dequeue();
146
149
  if (ack !== id) throw new Error(`Unexpected ack for ${id}: ${ack}`);
@@ -243,6 +246,6 @@ var WebSocketCloser = class WebSocketCloser {
243
246
  }
244
247
  };
245
248
  //#endregion
246
- export { pipe, stream, streamIn, streamOut };
249
+ export { pipe, stream, streamIn, streamOut, streamOutStringified };
247
250
 
248
251
  //# sourceMappingURL=streams.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"streams.js","names":["#lc","#ws","#closeStream","#messageHandler","#connected","#handleOpen","#handleClose","#handleError","#conn"],"sources":["../../../../../zero-cache/src/types/streams.ts"],"sourcesContent":["import {\n pipeline,\n Readable,\n Transform,\n Writable,\n type DuplexOptions,\n} from 'node:stream';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {\n createWebSocketStream,\n type CloseEvent,\n type ErrorEvent,\n type MessageEvent,\n type WebSocket,\n} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Subscription, type Options} from './subscription.ts';\nimport {\n closeWithError,\n expectPingsForLiveness,\n sendPingsForLiveness,\n} from './ws.ts';\n\n// Consistent with Postgres keepalives, and shorter than the\n// commonly used default idle timeout of 1 minute.\nconst PING_INTERVAL_MS = 30_000;\n\nexport type Source<T> = AsyncIterable<T> & {\n /**\n * Immediately terminates all current iterations (i.e. {@link AsyncIterator.next next()})\n * will return `{value: undefined, done: true}`), and prevents any subsequent iterations\n * from yielding any values.\n *\n * @param err Terminate the iteration by throwing the `err` instead.\n */\n cancel: (err?: Error) => void;\n\n /**\n * The presence of a `pipeline` iterable allows the usual \"consumed-on-iterate\" semantics\n * to be overridden.\n *\n * This is suitable for transport layers that serialize messages across processes, such\n * as the {@link streamOut()} method; pipelining allows the transport to send messages\n * as they arrive without waiting for the previous message to be acked, streaming\n * them to the receiving process where they are presumably queued and processed without\n * a per-message ack delay. The receiving end of the transport then responds with acks\n * asynchronously as the receiving end processes the messages.\n */\n pipeline?: AsyncIterable<{value: T; consumed: () => void}> | undefined;\n};\n\nexport type Sink<T> = {\n push(message: T): void;\n};\n\n/**\n * Back-pressure-aware transformation of a WebSocket into\n * upstream and downstream {@link Subscription} objects.\n */\n// TODO: Change {@link streamIn} and {@link streamOut} to use this\n// under the covers so that internal communication is also\n// responsive to backpressure.\nexport function stream<In extends JSONValue, Out extends JSONValue>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In>,\n outOptions: Options<Out> = {},\n inOptions: Options<In> = {},\n streamOptions: DuplexOptions = {},\n): {outstream: Sink<Out>; instream: Source<In>} {\n const endpoint = ws.url ?? 'client';\n function close(err?: unknown) {\n if (ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {\n if (err) {\n closeWithError(lc, ws, err);\n } else {\n lc.info?.(`closing connection to ${endpoint}`);\n ws.close();\n }\n }\n }\n\n const instream = Subscription.create<In>({\n ...inOptions,\n cleanup: (unconsumed, err) => {\n inOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n const outstream = Subscription.create<Out>({\n ...outOptions,\n cleanup: (unconsumed, err) => {\n outOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n\n const duplex = createWebSocketStream(ws, {\n ...streamOptions,\n decodeStrings: false,\n });\n\n // Outgoing transform.\n function streamOut() {\n // Mainly used for verifying that back-pressure kicks in tests.\n duplex.on('drain', () => lc.debug?.(`drained messages to ${endpoint}`));\n\n pipeline(\n Readable.from(outstream),\n new Transform({\n objectMode: true,\n transform: (msg, _encoding, callback) =>\n callback(null, BigIntJSON.stringify(msg)),\n }),\n duplex,\n err => (err ? outstream.fail(err) : outstream.cancel()),\n );\n }\n\n if (ws.readyState === ws.CONNECTING) {\n ws.on('open', () => {\n lc.info?.(`connected to ${endpoint}`);\n streamOut();\n });\n } else {\n streamOut();\n }\n\n // Incoming transform.\n pipe({\n source: duplex,\n sink: instream,\n parse: chunk => {\n const json = BigIntJSON.parse(chunk.toString());\n return v.parse(json, inSchema, 'passthrough');\n },\n });\n\n sendPingsForLiveness(lc, ws, PING_INTERVAL_MS);\n\n return {outstream, instream};\n}\n\ntype PipeOptions<T> = {\n source: Readable;\n sink: Subscription<T>;\n parse: (buffer: Buffer) => T | null;\n bufferMessages?: number;\n};\n\nexport function pipe<T>({source, sink, parse, bufferMessages}: PipeOptions<T>) {\n bufferMessages ??= 0;\n assert(bufferMessages >= 0, 'bufferMessages must be non-negative');\n const pending: Promise<unknown>[] = [];\n\n pipeline(\n source,\n new Writable({\n decodeStrings: false,\n write: (chunk, _encoding, callback) => {\n let msg: T | null;\n try {\n if ((msg = parse(chunk)) === null) {\n callback();\n return;\n }\n } catch (err) {\n callback(ensureError(err));\n return;\n }\n // Inbound backpressure is exerted by unconsumed messages in the\n // subscription. A buffer can be used to allow messages to queue up in\n // in the Subscription object, which allows the consumer to \"peek\" at\n // whether there are more messages immediately available\n // (via {@link Subscription.queued}.\n const {result} = sink.push(msg);\n pending.push(result);\n void result.then(() => pending.shift());\n\n if (pending.length <= bufferMessages) {\n // immediately allow more messages\n callback();\n } else {\n // wait for the oldest result in the pending queue\n pending[0].then(\n () => callback(),\n err => callback(ensureError(err)),\n );\n }\n },\n destroy: (err, callback) => {\n if (err) {\n sink.fail(ensureError(err));\n }\n // Otherwise, final will handle the cancel.\n callback();\n },\n final: callback => {\n sink.cancel();\n callback();\n },\n }),\n err => (err ? sink.fail(err) : sink.cancel()),\n );\n}\n\nfunction ensureError(err: unknown) {\n return err instanceof Error ? err : new Error(String(err));\n}\n\nconst ackSchema = v.object({ack: v.number()});\n\ntype Ack = v.Infer<typeof ackSchema>;\n\ntype Streamed<T> = {\n /** Application-level message. */\n msg: T;\n\n /** ID used for the Ack message. */\n id: number;\n};\n\nexport async function streamOut<T extends JSONValue>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n): Promise<void> {\n sendPingsForLiveness(lc, sink, PING_INTERVAL_MS);\n\n const closer = WebSocketCloser.forSource(lc, sink, source);\n\n const acks = new Queue<Ack>();\n sink.addEventListener('message', ({data}) => {\n try {\n if (typeof data !== 'string') {\n throw new Error('Expected string message');\n }\n acks.enqueue(v.parse(JSON.parse(data), ackSchema));\n } catch (e) {\n lc.error?.(`error parsing ack`, e);\n closer.close(e);\n }\n });\n\n try {\n let nextID = 0;\n const {pipeline} = source;\n if (pipeline) {\n lc.debug?.(`started pipelined outbound stream`);\n for await (const {value: msg, consumed} of pipeline) {\n const id = ++nextID;\n const data = BigIntJSON.stringify({msg, id} satisfies Streamed<T>);\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`pipelining`, data);\n sink.send(data);\n\n void (async () => {\n const {ack} = await acks.dequeue();\n // lc.debug?.(`received ack`, ack);\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })();\n }\n } else {\n lc.debug?.(`started synchronous outbound stream`);\n for await (const msg of source) {\n const id = ++nextID;\n const data = BigIntJSON.stringify({msg, id} satisfies Streamed<T>);\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`sending`, data);\n sink.send(data);\n\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n }\n }\n closer.close();\n } catch (e) {\n closer.close(e);\n }\n}\n\nexport async function streamIn<T extends JSONValue>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n): Promise<Source<T>> {\n expectPingsForLiveness(lc, source, PING_INTERVAL_MS);\n\n const streamedSchema = v.object({\n msg: schema,\n id: v.number(),\n });\n\n const sink: Subscription<T, Streamed<T>> = new Subscription<T, Streamed<T>>(\n {\n consumed: ({id}) => source.send(JSON.stringify({ack: id} satisfies Ack)),\n cleanup: () => closer.close(),\n },\n ({msg}) => msg,\n );\n\n const closer = WebSocketCloser.forSink(lc, source, sink, handleMessage);\n\n function handleMessage(event: MessageEvent) {\n const data = event.data.toString();\n if (!sink.active) {\n lc.warn?.('dropping ws message received after close', data);\n return;\n }\n try {\n const value = BigIntJSON.parse(data);\n const msg = v.parse(value, streamedSchema, 'passthrough');\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`received`, data);\n sink.push(msg);\n } catch (e) {\n closer.close(e);\n }\n }\n\n await closer.connected;\n return sink;\n}\n\nclass WebSocketCloser {\n readonly #lc: LogContext;\n readonly #ws: WebSocket;\n readonly #closeStream: () => void;\n readonly #messageHandler: ((e: MessageEvent) => void | undefined) | null;\n readonly #connected = resolver();\n\n get connected(): Promise<void> {\n return this.#connected.promise;\n }\n\n static forSource<T>(lc: LogContext, ws: WebSocket, stream: Source<T>) {\n // If the websocket is closed, call cancel() to notify the Source of\n // any unconsumed messages.\n return new WebSocketCloser(lc, ws, () => stream.cancel());\n }\n\n static forSink<T>(\n lc: LogContext,\n ws: WebSocket,\n stream: Subscription<T, Streamed<T>>,\n messageHandler: (e: MessageEvent) => void | undefined,\n ) {\n // If the websocket is closed, call end() to allow the downstream Sink\n // to process any pending messages before closing the stream.\n return new WebSocketCloser(lc, ws, () => stream.end(), messageHandler);\n }\n\n private constructor(\n lc: LogContext,\n ws: WebSocket,\n closeStream: () => void,\n messageHandler?: (e: MessageEvent) => void | undefined,\n ) {\n this.#lc = lc;\n this.#ws = ws;\n this.#closeStream = closeStream;\n this.#messageHandler = messageHandler ?? null;\n\n ws.addEventListener('open', this.#handleOpen);\n ws.addEventListener('close', this.#handleClose);\n ws.addEventListener('error', this.#handleError);\n if (this.#messageHandler) {\n ws.addEventListener('message', this.#messageHandler);\n }\n\n switch (ws.readyState) {\n case ws.CONNECTING:\n break; // expected for new connections. resolve or reject in handlers.\n case ws.OPEN:\n this.#connected.resolve();\n break;\n default:\n this.#connected.reject(\n new Error(`websocket already in state ${ws.readyState}`),\n );\n break;\n }\n }\n\n get #conn(): string {\n return 'connection' + (this.#ws.url ? ` to ${this.#ws.url}` : '');\n }\n\n #handleOpen = () => {\n this.#lc.info?.(`${this.#conn} established`);\n this.#connected.resolve();\n };\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.#lc.info?.(`${this.#conn} closed`, {\n code,\n reason,\n wasClean,\n });\n this.close();\n this.#connected.reject(`${this.#conn} closed with code ${code}`);\n };\n\n #handleError = ({message, error}: ErrorEvent) => {\n if (this.#ws.readyState === this.#ws.OPEN) {\n this.#lc.error?.(`error in ${this.#conn}`, message, error);\n }\n this.#connected.reject(error);\n };\n\n close(err?: unknown) {\n if (err) {\n this.#lc.error?.(`closing stream with error`, err);\n }\n this.#closeStream();\n if (!this.closed()) {\n this.#ws.close();\n }\n }\n\n closed() {\n return (\n this.#ws.readyState === this.#ws.CLOSED ||\n this.#ws.readyState === this.#ws.CLOSING\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"streams.js","names":["#lc","#ws","#closeStream","#messageHandler","#connected","#handleOpen","#handleClose","#handleError","#conn"],"sources":["../../../../../zero-cache/src/types/streams.ts"],"sourcesContent":["import {\n pipeline,\n Readable,\n Transform,\n Writable,\n type DuplexOptions,\n} from 'node:stream';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {\n createWebSocketStream,\n type CloseEvent,\n type ErrorEvent,\n type MessageEvent,\n type WebSocket,\n} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Subscription, type Options} from './subscription.ts';\nimport {\n closeWithError,\n expectPingsForLiveness,\n sendPingsForLiveness,\n} from './ws.ts';\n\n// Consistent with Postgres keepalives, and shorter than the\n// commonly used default idle timeout of 1 minute.\nconst PING_INTERVAL_MS = 30_000;\n\nexport type Source<T> = AsyncIterable<T> & {\n /**\n * Immediately terminates all current iterations (i.e. {@link AsyncIterator.next next()})\n * will return `{value: undefined, done: true}`), and prevents any subsequent iterations\n * from yielding any values.\n *\n * @param err Terminate the iteration by throwing the `err` instead.\n */\n cancel: (err?: Error) => void;\n\n /**\n * The presence of a `pipeline` iterable allows the usual \"consumed-on-iterate\" semantics\n * to be overridden.\n *\n * This is suitable for transport layers that serialize messages across processes, such\n * as the {@link streamOut()} method; pipelining allows the transport to send messages\n * as they arrive without waiting for the previous message to be acked, streaming\n * them to the receiving process where they are presumably queued and processed without\n * a per-message ack delay. The receiving end of the transport then responds with acks\n * asynchronously as the receiving end processes the messages.\n */\n pipeline?: AsyncIterable<{value: T; consumed: () => void}> | undefined;\n};\n\nexport type Sink<T> = {\n push(message: T): void;\n};\n\n/**\n * Back-pressure-aware transformation of a WebSocket into\n * upstream and downstream {@link Subscription} objects.\n */\n// TODO: Change {@link streamIn} and {@link streamOut} to use this\n// under the covers so that internal communication is also\n// responsive to backpressure.\nexport function stream<In extends JSONValue, Out extends JSONValue>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In>,\n outOptions: Options<Out> = {},\n inOptions: Options<In> = {},\n streamOptions: DuplexOptions = {},\n): {outstream: Sink<Out>; instream: Source<In>} {\n const endpoint = ws.url ?? 'client';\n function close(err?: unknown) {\n if (ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {\n if (err) {\n closeWithError(lc, ws, err);\n } else {\n lc.info?.(`closing connection to ${endpoint}`);\n ws.close();\n }\n }\n }\n\n const instream = Subscription.create<In>({\n ...inOptions,\n cleanup: (unconsumed, err) => {\n inOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n const outstream = Subscription.create<Out>({\n ...outOptions,\n cleanup: (unconsumed, err) => {\n outOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n\n const duplex = createWebSocketStream(ws, {\n ...streamOptions,\n decodeStrings: false,\n });\n\n // Outgoing transform.\n function streamOut() {\n // Mainly used for verifying that back-pressure kicks in tests.\n duplex.on('drain', () => lc.debug?.(`drained messages to ${endpoint}`));\n\n pipeline(\n Readable.from(outstream),\n new Transform({\n objectMode: true,\n transform: (msg, _encoding, callback) =>\n callback(null, BigIntJSON.stringify(msg)),\n }),\n duplex,\n err => (err ? outstream.fail(err) : outstream.cancel()),\n );\n }\n\n if (ws.readyState === ws.CONNECTING) {\n ws.on('open', () => {\n lc.info?.(`connected to ${endpoint}`);\n streamOut();\n });\n } else {\n streamOut();\n }\n\n // Incoming transform.\n pipe({\n source: duplex,\n sink: instream,\n parse: chunk => {\n const json = BigIntJSON.parse(chunk.toString());\n return v.parse(json, inSchema, 'passthrough');\n },\n });\n\n sendPingsForLiveness(lc, ws, PING_INTERVAL_MS);\n\n return {outstream, instream};\n}\n\ntype PipeOptions<T> = {\n source: Readable;\n sink: Subscription<T>;\n parse: (buffer: Buffer) => T | null;\n bufferMessages?: number;\n};\n\nexport function pipe<T>({source, sink, parse, bufferMessages}: PipeOptions<T>) {\n bufferMessages ??= 0;\n assert(bufferMessages >= 0, 'bufferMessages must be non-negative');\n const pending: Promise<unknown>[] = [];\n\n pipeline(\n source,\n new Writable({\n decodeStrings: false,\n write: (chunk, _encoding, callback) => {\n let msg: T | null;\n try {\n if ((msg = parse(chunk)) === null) {\n callback();\n return;\n }\n } catch (err) {\n callback(ensureError(err));\n return;\n }\n // Inbound backpressure is exerted by unconsumed messages in the\n // subscription. A buffer can be used to allow messages to queue up in\n // in the Subscription object, which allows the consumer to \"peek\" at\n // whether there are more messages immediately available\n // (via {@link Subscription.queued}.\n const {result} = sink.push(msg);\n pending.push(result);\n void result.then(() => pending.shift());\n\n if (pending.length <= bufferMessages) {\n // immediately allow more messages\n callback();\n } else {\n // wait for the oldest result in the pending queue\n pending[0].then(\n () => callback(),\n err => callback(ensureError(err)),\n );\n }\n },\n destroy: (err, callback) => {\n if (err) {\n sink.fail(ensureError(err));\n }\n // Otherwise, final will handle the cancel.\n callback();\n },\n final: callback => {\n sink.cancel();\n callback();\n },\n }),\n err => (err ? sink.fail(err) : sink.cancel()),\n );\n}\n\nfunction ensureError(err: unknown) {\n return err instanceof Error ? err : new Error(String(err));\n}\n\nconst ackSchema = v.object({ack: v.number()});\n\ntype Ack = v.Infer<typeof ackSchema>;\n\ntype Streamed<T> = {\n /** Application-level message. */\n msg: T;\n\n /** ID used for the Ack message. */\n id: number;\n};\n\nexport function streamOut<T extends JSONValue>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n): Promise<void> {\n return streamOutInternal(lc, source, sink, BigIntJSON.stringify);\n}\n\n/**\n * Streams out a `Source` for which messages are already stringified JSON.\n */\nexport function streamOutStringified(\n lc: LogContext,\n source: Source<string>,\n sink: WebSocket,\n): Promise<void> {\n return streamOutInternal(lc, source, sink, json => json);\n}\n\nasync function streamOutInternal<T extends JSONValue>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n stringify: (payload: T) => string,\n): Promise<void> {\n sendPingsForLiveness(lc, sink, PING_INTERVAL_MS);\n\n const closer = WebSocketCloser.forSource(lc, sink, source);\n\n const acks = new Queue<Ack>();\n sink.addEventListener('message', ({data}) => {\n try {\n if (typeof data !== 'string') {\n throw new Error('Expected string message');\n }\n acks.enqueue(v.parse(JSON.parse(data), ackSchema));\n } catch (e) {\n lc.error?.(`error parsing ack`, e);\n closer.close(e);\n }\n });\n\n try {\n let nextID = 0;\n const {pipeline} = source;\n if (pipeline) {\n lc.debug?.(`started pipelined outbound stream`);\n for await (const {value: msg, consumed} of pipeline) {\n const id = ++nextID;\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`pipelining`, data);\n sink.send(data);\n\n void (async () => {\n const {ack} = await acks.dequeue();\n // lc.debug?.(`received ack`, ack);\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })();\n }\n } else {\n lc.debug?.(`started synchronous outbound stream`);\n for await (const msg of source) {\n const id = ++nextID;\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`sending`, data);\n sink.send(data);\n\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n }\n }\n closer.close();\n } catch (e) {\n closer.close(e);\n }\n}\n\nexport async function streamIn<T extends JSONValue>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n): Promise<Source<T>> {\n expectPingsForLiveness(lc, source, PING_INTERVAL_MS);\n\n const streamedSchema = v.object({\n msg: schema,\n id: v.number(),\n });\n\n const sink: Subscription<T, Streamed<T>> = new Subscription<T, Streamed<T>>(\n {\n consumed: ({id}) => source.send(JSON.stringify({ack: id} satisfies Ack)),\n cleanup: () => closer.close(),\n },\n ({msg}) => msg,\n );\n\n const closer = WebSocketCloser.forSink(lc, source, sink, handleMessage);\n\n function handleMessage(event: MessageEvent) {\n const data = event.data.toString();\n if (!sink.active) {\n lc.warn?.('dropping ws message received after close', data);\n return;\n }\n try {\n const value = BigIntJSON.parse(data);\n const msg = v.parse(value, streamedSchema, 'passthrough');\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`received`, data);\n sink.push(msg);\n } catch (e) {\n closer.close(e);\n }\n }\n\n await closer.connected;\n return sink;\n}\n\nclass WebSocketCloser {\n readonly #lc: LogContext;\n readonly #ws: WebSocket;\n readonly #closeStream: () => void;\n readonly #messageHandler: ((e: MessageEvent) => void | undefined) | null;\n readonly #connected = resolver();\n\n get connected(): Promise<void> {\n return this.#connected.promise;\n }\n\n static forSource<T>(lc: LogContext, ws: WebSocket, stream: Source<T>) {\n // If the websocket is closed, call cancel() to notify the Source of\n // any unconsumed messages.\n return new WebSocketCloser(lc, ws, () => stream.cancel());\n }\n\n static forSink<T>(\n lc: LogContext,\n ws: WebSocket,\n stream: Subscription<T, Streamed<T>>,\n messageHandler: (e: MessageEvent) => void | undefined,\n ) {\n // If the websocket is closed, call end() to allow the downstream Sink\n // to process any pending messages before closing the stream.\n return new WebSocketCloser(lc, ws, () => stream.end(), messageHandler);\n }\n\n private constructor(\n lc: LogContext,\n ws: WebSocket,\n closeStream: () => void,\n messageHandler?: (e: MessageEvent) => void | undefined,\n ) {\n this.#lc = lc;\n this.#ws = ws;\n this.#closeStream = closeStream;\n this.#messageHandler = messageHandler ?? null;\n\n ws.addEventListener('open', this.#handleOpen);\n ws.addEventListener('close', this.#handleClose);\n ws.addEventListener('error', this.#handleError);\n if (this.#messageHandler) {\n ws.addEventListener('message', this.#messageHandler);\n }\n\n switch (ws.readyState) {\n case ws.CONNECTING:\n break; // expected for new connections. resolve or reject in handlers.\n case ws.OPEN:\n this.#connected.resolve();\n break;\n default:\n this.#connected.reject(\n new Error(`websocket already in state ${ws.readyState}`),\n );\n break;\n }\n }\n\n get #conn(): string {\n return 'connection' + (this.#ws.url ? ` to ${this.#ws.url}` : '');\n }\n\n #handleOpen = () => {\n this.#lc.info?.(`${this.#conn} established`);\n this.#connected.resolve();\n };\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.#lc.info?.(`${this.#conn} closed`, {\n code,\n reason,\n wasClean,\n });\n this.close();\n this.#connected.reject(`${this.#conn} closed with code ${code}`);\n };\n\n #handleError = ({message, error}: ErrorEvent) => {\n if (this.#ws.readyState === this.#ws.OPEN) {\n this.#lc.error?.(`error in ${this.#conn}`, message, error);\n }\n this.#connected.reject(error);\n };\n\n close(err?: unknown) {\n if (err) {\n this.#lc.error?.(`closing stream with error`, err);\n }\n this.#closeStream();\n if (!this.closed()) {\n this.#ws.close();\n }\n }\n\n closed() {\n return (\n this.#ws.readyState === this.#ws.CLOSED ||\n this.#ws.readyState === this.#ws.CLOSING\n );\n }\n}\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ import { isProtocolError } from "../../../zero-protocol/src/error.js";
6
6
  import "../../../zero-protocol/src/protocol-version.js";
7
7
  import { ProtocolErrorWithLevel, getLogLevel, wrapWithProtocolError } from "../types/error-with-level.js";
8
8
  import { upstreamSchema } from "../../../zero-protocol/src/up.js";
9
- import WebSocket$1, { createWebSocketStream } from "ws";
9
+ import WebSocket, { createWebSocketStream } from "ws";
10
10
  import { Readable, Writable, pipeline } from "node:stream";
11
11
  //#region ../zero-cache/src/workers/connection.ts
12
12
  var DOWNSTREAM_MSG_INTERVAL_MS = 6e3;
@@ -51,9 +51,9 @@ var Connection = class {
51
51
  * will only parse messages with schema(s) of supported protocol versions.
52
52
  */
53
53
  init() {
54
- if (this.#protocolVersion > 50 || this.#protocolVersion < 30) this.#closeWithError({
54
+ if (this.#protocolVersion > 51 || this.#protocolVersion < 30) this.#closeWithError({
55
55
  kind: VersionNotSupported,
56
- message: `server is at sync protocol v50 and does not support v${this.#protocolVersion}. The ${this.#protocolVersion > 50 ? "server" : "client"} must be updated to a newer release.`,
56
+ message: `server is at sync protocol v51 and does not support v${this.#protocolVersion}. The ${this.#protocolVersion > 51 ? "server" : "client"} must be updated to a newer release.`,
57
57
  origin: ZeroCache
58
58
  });
59
59
  else {
@@ -143,7 +143,7 @@ var Connection = class {
143
143
  });
144
144
  };
145
145
  #handleError = (e) => {
146
- this.#lc.error?.("WebSocket error event", e.message, e.error);
146
+ this.#lc.warn?.("WebSocket error event", e.message, e.error);
147
147
  };
148
148
  #proxyInbound() {
149
149
  pipeline(createWebSocketStream(this.#ws), new Writable({ write: (data, _encoding, callback) => {
@@ -180,7 +180,7 @@ var Connection = class {
180
180
  }
181
181
  };
182
182
  function send(lc, ws, data, callback) {
183
- if (ws.readyState === WebSocket$1.OPEN) ws.send(JSON.stringify(data), callback === "ignore-backpressure" ? void 0 : callback);
183
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(data), callback === "ignore-backpressure" ? void 0 : callback);
184
184
  else {
185
185
  lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, { dropped: data });
186
186
  if (callback !== "ignore-backpressure") callback(new ProtocolErrorWithLevel({
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","names":["#ws","#wsID","#protocolVersion","#lc","#onClose","#messageHandler","#downstreamMsgTimer","#handleClose","#handleError","#proxyInbound","#maybeSendPong","#closeWithError","#closed","#viewSyncerOutboundStream","#pusherOutboundStream","#handleMessage","#handleMessageResult","#closeWithThrown","#proxyOutbound","#lastDownstreamMsgTime"],"sources":["../../../../../zero-cache/src/workers/connection.ts"],"sourcesContent":["import {pipeline, Readable, Writable} from 'node:stream';\nimport type {LogContext, LogLevel} from '@rocicorp/logger';\nimport type {CloseEvent, Data, ErrorEvent} from 'ws';\nimport WebSocket, {createWebSocketStream} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport type {ConnectedMessage} from '../../../zero-protocol/src/connect.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 type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport {\n isProtocolError,\n type ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {\n MIN_SERVER_SUPPORTED_SYNC_PROTOCOL,\n PROTOCOL_VERSION,\n} from '../../../zero-protocol/src/protocol-version.ts';\nimport {upstreamSchema, type Upstream} from '../../../zero-protocol/src/up.ts';\nimport {\n ProtocolErrorWithLevel,\n getLogLevel,\n wrapWithProtocolError,\n} from '../types/error-with-level.ts';\nimport type {Source} from '../types/streams.ts';\nimport type {ConnectParams} from './connect-params.ts';\n\nexport type HandlerResult =\n | {\n type: 'ok';\n }\n | {\n type: 'fatal';\n error: ErrorBody;\n }\n | {\n type: 'transient';\n errors: ErrorBody[];\n }\n | StreamResult;\n\nexport type StreamResult = {\n type: 'stream';\n source: 'viewSyncer' | 'pusher';\n stream: Source<Downstream>;\n};\n\nexport interface MessageHandler {\n handleMessage(msg: Upstream): Promise<HandlerResult[]>;\n}\n\n// Ensures that a downstream message is sent at least every interval, sending a\n// 'pong' if necessary. This is set to be slightly longer than the client-side\n// PING_INTERVAL of 5 seconds, so that in the common case, 'pong's are sent in\n// response to client-initiated 'ping's. However, if the inbound stream is\n// backed up because a command is taking a long time to process, the pings\n// will be stuck in the queue (i.e. back-pressured), in which case pongs will\n// be manually sent to notify the client of server liveness.\n//\n// This is equivalent to what is done for Postgres keepalives on the\n// replication stream (which can similarly be back-pressured):\n// https://github.com/rocicorp/mono/blob/f98cb369a2dbb15650328859c732db358f187ef0/packages/zero-cache/src/services/change-source/pg/logical-replication/stream.ts#L21\nconst DOWNSTREAM_MSG_INTERVAL_MS = 6_000;\n\n/**\n * Represents a connection between the client and server.\n *\n * Handles incoming messages on the connection and dispatches\n * them to the correct service.\n *\n * Listens to the ViewSyncer and sends messages to the client.\n */\nexport class Connection {\n readonly #ws: WebSocket;\n readonly #wsID: string;\n readonly #protocolVersion: number;\n readonly #lc: LogContext;\n readonly #onClose: () => void;\n readonly #messageHandler: MessageHandler;\n readonly #downstreamMsgTimer: NodeJS.Timeout | undefined;\n\n #viewSyncerOutboundStream: Source<Downstream> | undefined;\n #pusherOutboundStream: Source<Downstream> | undefined;\n #closed = false;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n ws: WebSocket,\n messageHandler: MessageHandler,\n onClose: () => void,\n ) {\n const {clientGroupID, clientID, wsID, protocolVersion} = connectParams;\n this.#messageHandler = messageHandler;\n\n this.#ws = ws;\n this.#wsID = wsID;\n this.#protocolVersion = protocolVersion;\n\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#lc.debug?.('new connection');\n this.#onClose = onClose;\n\n this.#ws.addEventListener('close', this.#handleClose);\n this.#ws.addEventListener('error', this.#handleError);\n\n this.#proxyInbound();\n this.#downstreamMsgTimer = setInterval(\n this.#maybeSendPong,\n DOWNSTREAM_MSG_INTERVAL_MS / 2,\n );\n }\n\n /**\n * Checks the protocol version and errors for unsupported protocols,\n * sending the initial `connected` response on success.\n *\n * This is early in the connection lifecycle because {@link #handleMessage}\n * will only parse messages with schema(s) of supported protocol versions.\n */\n init(): boolean {\n if (\n this.#protocolVersion > PROTOCOL_VERSION ||\n this.#protocolVersion < MIN_SERVER_SUPPORTED_SYNC_PROTOCOL\n ) {\n this.#closeWithError({\n kind: ErrorKind.VersionNotSupported,\n message: `server is at sync protocol v${PROTOCOL_VERSION} and does not support v${\n this.#protocolVersion\n }. The ${\n this.#protocolVersion > PROTOCOL_VERSION ? 'server' : 'client'\n } must be updated to a newer release.`,\n origin: ErrorOrigin.ZeroCache,\n });\n } else {\n const connectedMessage: ConnectedMessage = [\n 'connected',\n {wsid: this.#wsID, timestamp: Date.now()},\n ];\n this.send(connectedMessage, 'ignore-backpressure');\n return true;\n }\n return false;\n }\n\n close(reason: string, ...args: unknown[]) {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#lc.info?.(`closing connection: ${reason}`, ...args);\n this.#ws.removeEventListener('close', this.#handleClose);\n this.#ws.removeEventListener('error', this.#handleError);\n this.#viewSyncerOutboundStream?.cancel();\n this.#viewSyncerOutboundStream = undefined;\n this.#pusherOutboundStream?.cancel();\n this.#pusherOutboundStream = undefined;\n this.#onClose();\n if (this.#ws.readyState !== this.#ws.CLOSED) {\n this.#ws.close();\n }\n clearTimeout(this.#downstreamMsgTimer);\n\n // spin down services if we have\n // no more client connections for the client group?\n }\n\n handleInitConnection(initConnectionMsg: string) {\n return this.#handleMessage({data: initConnectionMsg});\n }\n\n #handleMessage = async (event: {data: Data}) => {\n const data = event.data.toString();\n if (this.#closed) {\n this.#lc.debug?.('Ignoring message received after closed', data);\n return;\n }\n\n let msg;\n try {\n const value = JSON.parse(data);\n msg = valita.parse(value, upstreamSchema);\n } catch (e) {\n const errorBody = {\n kind: ErrorKind.InvalidMessage,\n message: String(e),\n origin: ErrorOrigin.ZeroCache,\n } as const;\n this.#closeWithError(\n errorBody,\n new ProtocolErrorWithLevel(errorBody, 'warn'),\n );\n return;\n }\n\n try {\n const msgType = msg[0];\n if (msgType === 'ping') {\n this.send(['pong', {}], 'ignore-backpressure');\n return;\n }\n\n const result = await this.#messageHandler.handleMessage(msg);\n for (const r of result) {\n this.#handleMessageResult(r);\n }\n } catch (e) {\n this.#closeWithThrown(e);\n }\n };\n\n #handleMessageResult(result: HandlerResult): void {\n switch (result.type) {\n case 'fatal':\n this.#closeWithError(result.error);\n break;\n case 'ok':\n break;\n case 'stream': {\n switch (result.source) {\n case 'viewSyncer':\n assert(\n this.#viewSyncerOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#viewSyncerOutboundStream = result.stream;\n break;\n case 'pusher':\n assert(\n this.#pusherOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#pusherOutboundStream = result.stream;\n break;\n }\n this.#proxyOutbound(result.stream);\n break;\n }\n case 'transient': {\n for (const error of result.errors) {\n this.sendError(error);\n }\n }\n }\n }\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.close('WebSocket close event', {code, reason, wasClean});\n };\n\n #handleError = (e: ErrorEvent) => {\n this.#lc.error?.('WebSocket error event', e.message, e.error);\n };\n\n #proxyInbound() {\n pipeline(\n createWebSocketStream(this.#ws),\n new Writable({\n write: (data, _encoding, callback) => {\n this.#handleMessage({data}).then(() => callback(), callback);\n },\n }),\n // The done callback is not used, as #handleClose and #handleError,\n // configured on the underlying WebSocket, provide more complete\n // information.\n () => {},\n );\n }\n\n #proxyOutbound(outboundStream: Source<Downstream>) {\n // Note: createWebSocketStream() is avoided here in order to control\n // exception handling with #closeWithThrown(). If the Writable\n // from createWebSocketStream() were instead used, exceptions\n // from the outboundStream result in the Writable closing the\n // the websocket before the error message can be sent.\n pipeline(\n Readable.from(outboundStream),\n new Writable({\n objectMode: true,\n write: (downstream: Downstream, _encoding, callback) =>\n this.send(downstream, callback),\n }),\n e =>\n e\n ? this.#closeWithThrown(e)\n : this.close(`downstream closed by ViewSyncer`),\n );\n }\n\n #closeWithThrown(e: unknown) {\n const errorBody =\n findProtocolError(e)?.errorBody ?? wrapWithProtocolError(e).errorBody;\n\n this.#closeWithError(errorBody, e);\n }\n\n #closeWithError(errorBody: ErrorBody, thrown?: unknown) {\n this.sendError(errorBody, thrown);\n this.close(\n `${errorBody.kind} (${errorBody.origin}): ${errorBody.message}`,\n errorBody,\n );\n }\n\n #lastDownstreamMsgTime = Date.now();\n\n #maybeSendPong = () => {\n if (Date.now() - this.#lastDownstreamMsgTime > DOWNSTREAM_MSG_INTERVAL_MS) {\n this.#lc.debug?.('manually sending pong');\n this.send(['pong', {}], 'ignore-backpressure');\n }\n };\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n ) {\n this.#lastDownstreamMsgTime = Date.now();\n return send(this.#lc, this.#ws, data, callback);\n }\n\n sendError(errorBody: ErrorBody, thrown?: unknown) {\n sendError(this.#lc, this.#ws, errorBody, thrown);\n }\n}\n\nexport type WebSocketLike = Pick<WebSocket, 'readyState'> & {\n send(data: string, cb?: (err?: Error) => void): void;\n};\n\n// Exported for testing purposes.\nexport function send(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(\n JSON.stringify(data),\n callback === 'ignore-backpressure' ? undefined : callback,\n );\n } else {\n lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, {\n dropped: data,\n });\n if (callback !== 'ignore-backpressure') {\n callback(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }\n }\n}\n\nexport function sendError(\n lc: LogContext,\n ws: WebSocket,\n errorBody: ErrorBody,\n thrown?: unknown,\n) {\n lc = lc.withContext('errorKind', errorBody.kind);\n\n let logLevel: LogLevel;\n\n // If the thrown error is a ProtocolErrorWithLevel, its explicit logLevel takes precedence\n if (thrown instanceof ProtocolErrorWithLevel) {\n logLevel = thrown.logLevel;\n }\n // Errors with errno or transient socket codes are low-level, transient I/O issues\n // (e.g., EPIPE, ECONNRESET) and should be warnings, not errors\n else if (\n hasErrno(thrown) ||\n hasTransientSocketCode(thrown) ||\n isTransientSocketMessage(errorBody.message)\n ) {\n logLevel = 'warn';\n }\n // Fallback: check errorBody.kind for errors that weren't thrown as ProtocolErrorWithLevel\n else if (\n errorBody.kind === ErrorKind.ClientNotFound ||\n errorBody.kind === ErrorKind.TransformFailed\n ) {\n logLevel = 'warn';\n } else {\n logLevel = thrown ? getLogLevel(thrown) : 'info';\n }\n\n lc[logLevel]?.('Sending error on WebSocket', errorBody, thrown ?? '');\n send(lc, ws, ['error', errorBody], 'ignore-backpressure');\n}\n\nexport function findProtocolError(error: unknown): ProtocolError | undefined {\n if (isProtocolError(error)) {\n return error;\n }\n if (error instanceof Error && error.cause) {\n return findProtocolError(error.cause);\n }\n return undefined;\n}\n\nfunction hasErrno(error: unknown): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'errno' in error &&\n typeof (error as {errno: unknown}).errno !== 'undefined',\n );\n}\n\n// System error codes that indicate transient socket conditions.\n// These are checked via the `code` property on errors.\nconst TRANSIENT_SOCKET_ERROR_CODES = new Set([\n 'EPIPE',\n 'ECONNRESET',\n 'ECANCELED',\n]);\n\n// Error messages that indicate transient socket conditions but don't have\n// standard error codes (e.g., WebSocket library errors).\nconst TRANSIENT_SOCKET_MESSAGE_PATTERNS = [\n 'socket was closed while data was being compressed',\n];\n\nfunction hasTransientSocketCode(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const maybeCode =\n 'code' in error ? String((error as {code?: unknown}).code) : undefined;\n return Boolean(\n maybeCode && TRANSIENT_SOCKET_ERROR_CODES.has(maybeCode.toUpperCase()),\n );\n}\n\nfunction isTransientSocketMessage(message: string | undefined): boolean {\n if (!message) {\n return false;\n }\n const lower = message.toLowerCase();\n return TRANSIENT_SOCKET_MESSAGE_PATTERNS.some(pattern =>\n lower.includes(pattern),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"connection.js","names":["#ws","#wsID","#protocolVersion","#lc","#onClose","#messageHandler","#downstreamMsgTimer","#handleClose","#handleError","#proxyInbound","#maybeSendPong","#closeWithError","#closed","#viewSyncerOutboundStream","#pusherOutboundStream","#handleMessage","#handleMessageResult","#closeWithThrown","#proxyOutbound","#lastDownstreamMsgTime"],"sources":["../../../../../zero-cache/src/workers/connection.ts"],"sourcesContent":["import {pipeline, Readable, Writable} from 'node:stream';\nimport type {LogContext, LogLevel} from '@rocicorp/logger';\nimport type {CloseEvent, Data, ErrorEvent} from 'ws';\nimport WebSocket, {createWebSocketStream} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport type {ConnectedMessage} from '../../../zero-protocol/src/connect.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 type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport {\n isProtocolError,\n type ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {\n MIN_SERVER_SUPPORTED_SYNC_PROTOCOL,\n PROTOCOL_VERSION,\n} from '../../../zero-protocol/src/protocol-version.ts';\nimport {upstreamSchema, type Upstream} from '../../../zero-protocol/src/up.ts';\nimport {\n ProtocolErrorWithLevel,\n getLogLevel,\n wrapWithProtocolError,\n} from '../types/error-with-level.ts';\nimport type {Source} from '../types/streams.ts';\nimport type {ConnectParams} from './connect-params.ts';\n\nexport type HandlerResult =\n | {\n type: 'ok';\n }\n | {\n type: 'fatal';\n error: ErrorBody;\n }\n | {\n type: 'transient';\n errors: ErrorBody[];\n }\n | StreamResult;\n\nexport type StreamResult = {\n type: 'stream';\n source: 'viewSyncer' | 'pusher';\n stream: Source<Downstream>;\n};\n\nexport interface MessageHandler {\n handleMessage(msg: Upstream): Promise<HandlerResult[]>;\n}\n\n// Ensures that a downstream message is sent at least every interval, sending a\n// 'pong' if necessary. This is set to be slightly longer than the client-side\n// PING_INTERVAL of 5 seconds, so that in the common case, 'pong's are sent in\n// response to client-initiated 'ping's. However, if the inbound stream is\n// backed up because a command is taking a long time to process, the pings\n// will be stuck in the queue (i.e. back-pressured), in which case pongs will\n// be manually sent to notify the client of server liveness.\n//\n// This is equivalent to what is done for Postgres keepalives on the\n// replication stream (which can similarly be back-pressured):\n// https://github.com/rocicorp/mono/blob/f98cb369a2dbb15650328859c732db358f187ef0/packages/zero-cache/src/services/change-source/pg/logical-replication/stream.ts#L21\nconst DOWNSTREAM_MSG_INTERVAL_MS = 6_000;\n\n/**\n * Represents a connection between the client and server.\n *\n * Handles incoming messages on the connection and dispatches\n * them to the correct service.\n *\n * Listens to the ViewSyncer and sends messages to the client.\n */\nexport class Connection {\n readonly #ws: WebSocket;\n readonly #wsID: string;\n readonly #protocolVersion: number;\n readonly #lc: LogContext;\n readonly #onClose: () => void;\n readonly #messageHandler: MessageHandler;\n readonly #downstreamMsgTimer: NodeJS.Timeout | undefined;\n\n #viewSyncerOutboundStream: Source<Downstream> | undefined;\n #pusherOutboundStream: Source<Downstream> | undefined;\n #closed = false;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n ws: WebSocket,\n messageHandler: MessageHandler,\n onClose: () => void,\n ) {\n const {clientGroupID, clientID, wsID, protocolVersion} = connectParams;\n this.#messageHandler = messageHandler;\n\n this.#ws = ws;\n this.#wsID = wsID;\n this.#protocolVersion = protocolVersion;\n\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#lc.debug?.('new connection');\n this.#onClose = onClose;\n\n this.#ws.addEventListener('close', this.#handleClose);\n this.#ws.addEventListener('error', this.#handleError);\n\n this.#proxyInbound();\n this.#downstreamMsgTimer = setInterval(\n this.#maybeSendPong,\n DOWNSTREAM_MSG_INTERVAL_MS / 2,\n );\n }\n\n /**\n * Checks the protocol version and errors for unsupported protocols,\n * sending the initial `connected` response on success.\n *\n * This is early in the connection lifecycle because {@link #handleMessage}\n * will only parse messages with schema(s) of supported protocol versions.\n */\n init(): boolean {\n if (\n this.#protocolVersion > PROTOCOL_VERSION ||\n this.#protocolVersion < MIN_SERVER_SUPPORTED_SYNC_PROTOCOL\n ) {\n this.#closeWithError({\n kind: ErrorKind.VersionNotSupported,\n message: `server is at sync protocol v${PROTOCOL_VERSION} and does not support v${\n this.#protocolVersion\n }. The ${\n this.#protocolVersion > PROTOCOL_VERSION ? 'server' : 'client'\n } must be updated to a newer release.`,\n origin: ErrorOrigin.ZeroCache,\n });\n } else {\n const connectedMessage: ConnectedMessage = [\n 'connected',\n {wsid: this.#wsID, timestamp: Date.now()},\n ];\n this.send(connectedMessage, 'ignore-backpressure');\n return true;\n }\n return false;\n }\n\n close(reason: string, ...args: unknown[]) {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#lc.info?.(`closing connection: ${reason}`, ...args);\n this.#ws.removeEventListener('close', this.#handleClose);\n this.#ws.removeEventListener('error', this.#handleError);\n this.#viewSyncerOutboundStream?.cancel();\n this.#viewSyncerOutboundStream = undefined;\n this.#pusherOutboundStream?.cancel();\n this.#pusherOutboundStream = undefined;\n this.#onClose();\n if (this.#ws.readyState !== this.#ws.CLOSED) {\n this.#ws.close();\n }\n clearTimeout(this.#downstreamMsgTimer);\n\n // spin down services if we have\n // no more client connections for the client group?\n }\n\n handleInitConnection(initConnectionMsg: string) {\n return this.#handleMessage({data: initConnectionMsg});\n }\n\n #handleMessage = async (event: {data: Data}) => {\n const data = event.data.toString();\n if (this.#closed) {\n this.#lc.debug?.('Ignoring message received after closed', data);\n return;\n }\n\n let msg;\n try {\n const value = JSON.parse(data);\n msg = valita.parse(value, upstreamSchema);\n } catch (e) {\n const errorBody = {\n kind: ErrorKind.InvalidMessage,\n message: String(e),\n origin: ErrorOrigin.ZeroCache,\n } as const;\n this.#closeWithError(\n errorBody,\n new ProtocolErrorWithLevel(errorBody, 'warn'),\n );\n return;\n }\n\n try {\n const msgType = msg[0];\n if (msgType === 'ping') {\n this.send(['pong', {}], 'ignore-backpressure');\n return;\n }\n\n const result = await this.#messageHandler.handleMessage(msg);\n for (const r of result) {\n this.#handleMessageResult(r);\n }\n } catch (e) {\n this.#closeWithThrown(e);\n }\n };\n\n #handleMessageResult(result: HandlerResult): void {\n switch (result.type) {\n case 'fatal':\n this.#closeWithError(result.error);\n break;\n case 'ok':\n break;\n case 'stream': {\n switch (result.source) {\n case 'viewSyncer':\n assert(\n this.#viewSyncerOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#viewSyncerOutboundStream = result.stream;\n break;\n case 'pusher':\n assert(\n this.#pusherOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#pusherOutboundStream = result.stream;\n break;\n }\n this.#proxyOutbound(result.stream);\n break;\n }\n case 'transient': {\n for (const error of result.errors) {\n this.sendError(error);\n }\n }\n }\n }\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.close('WebSocket close event', {code, reason, wasClean});\n };\n\n #handleError = (e: ErrorEvent) => {\n this.#lc.warn?.('WebSocket error event', e.message, e.error);\n };\n\n #proxyInbound() {\n pipeline(\n createWebSocketStream(this.#ws),\n new Writable({\n write: (data, _encoding, callback) => {\n this.#handleMessage({data}).then(() => callback(), callback);\n },\n }),\n // The done callback is not used, as #handleClose and #handleError,\n // configured on the underlying WebSocket, provide more complete\n // information.\n () => {},\n );\n }\n\n #proxyOutbound(outboundStream: Source<Downstream>) {\n // Note: createWebSocketStream() is avoided here in order to control\n // exception handling with #closeWithThrown(). If the Writable\n // from createWebSocketStream() were instead used, exceptions\n // from the outboundStream result in the Writable closing the\n // the websocket before the error message can be sent.\n pipeline(\n Readable.from(outboundStream),\n new Writable({\n objectMode: true,\n write: (downstream: Downstream, _encoding, callback) =>\n this.send(downstream, callback),\n }),\n e =>\n e\n ? this.#closeWithThrown(e)\n : this.close(`downstream closed by ViewSyncer`),\n );\n }\n\n #closeWithThrown(e: unknown) {\n const errorBody =\n findProtocolError(e)?.errorBody ?? wrapWithProtocolError(e).errorBody;\n\n this.#closeWithError(errorBody, e);\n }\n\n #closeWithError(errorBody: ErrorBody, thrown?: unknown) {\n this.sendError(errorBody, thrown);\n this.close(\n `${errorBody.kind} (${errorBody.origin}): ${errorBody.message}`,\n errorBody,\n );\n }\n\n #lastDownstreamMsgTime = Date.now();\n\n #maybeSendPong = () => {\n if (Date.now() - this.#lastDownstreamMsgTime > DOWNSTREAM_MSG_INTERVAL_MS) {\n this.#lc.debug?.('manually sending pong');\n this.send(['pong', {}], 'ignore-backpressure');\n }\n };\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n ) {\n this.#lastDownstreamMsgTime = Date.now();\n return send(this.#lc, this.#ws, data, callback);\n }\n\n sendError(errorBody: ErrorBody, thrown?: unknown) {\n sendError(this.#lc, this.#ws, errorBody, thrown);\n }\n}\n\nexport type WebSocketLike = Pick<WebSocket, 'readyState'> & {\n send(data: string, cb?: (err?: Error) => void): void;\n};\n\n// Exported for testing purposes.\nexport function send(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(\n JSON.stringify(data),\n callback === 'ignore-backpressure' ? undefined : callback,\n );\n } else {\n lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, {\n dropped: data,\n });\n if (callback !== 'ignore-backpressure') {\n callback(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }\n }\n}\n\nexport function sendError(\n lc: LogContext,\n ws: WebSocket,\n errorBody: ErrorBody,\n thrown?: unknown,\n) {\n lc = lc.withContext('errorKind', errorBody.kind);\n\n let logLevel: LogLevel;\n\n // If the thrown error is a ProtocolErrorWithLevel, its explicit logLevel takes precedence\n if (thrown instanceof ProtocolErrorWithLevel) {\n logLevel = thrown.logLevel;\n }\n // Errors with errno or transient socket codes are low-level, transient I/O issues\n // (e.g., EPIPE, ECONNRESET) and should be warnings, not errors\n else if (\n hasErrno(thrown) ||\n hasTransientSocketCode(thrown) ||\n isTransientSocketMessage(errorBody.message)\n ) {\n logLevel = 'warn';\n }\n // Fallback: check errorBody.kind for errors that weren't thrown as ProtocolErrorWithLevel\n else if (\n errorBody.kind === ErrorKind.ClientNotFound ||\n errorBody.kind === ErrorKind.TransformFailed\n ) {\n logLevel = 'warn';\n } else {\n logLevel = thrown ? getLogLevel(thrown) : 'info';\n }\n\n lc[logLevel]?.('Sending error on WebSocket', errorBody, thrown ?? '');\n send(lc, ws, ['error', errorBody], 'ignore-backpressure');\n}\n\nexport function findProtocolError(error: unknown): ProtocolError | undefined {\n if (isProtocolError(error)) {\n return error;\n }\n if (error instanceof Error && error.cause) {\n return findProtocolError(error.cause);\n }\n return undefined;\n}\n\nfunction hasErrno(error: unknown): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'errno' in error &&\n typeof (error as {errno: unknown}).errno !== 'undefined',\n );\n}\n\n// System error codes that indicate transient socket conditions.\n// These are checked via the `code` property on errors.\nconst TRANSIENT_SOCKET_ERROR_CODES = new Set([\n 'EPIPE',\n 'ECONNRESET',\n 'ECANCELED',\n]);\n\n// Error messages that indicate transient socket conditions but don't have\n// standard error codes (e.g., WebSocket library errors).\nconst TRANSIENT_SOCKET_MESSAGE_PATTERNS = [\n 'socket was closed while data was being compressed',\n];\n\nfunction hasTransientSocketCode(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const maybeCode =\n 'code' in error ? String((error as {code?: unknown}).code) : undefined;\n return Boolean(\n maybeCode && TRANSIENT_SOCKET_ERROR_CODES.has(maybeCode.toUpperCase()),\n );\n}\n\nfunction isTransientSocketMessage(message: string | undefined): boolean {\n if (!message) {\n return false;\n }\n const lower = message.toLowerCase();\n return TRANSIENT_SOCKET_MESSAGE_PATTERNS.some(pattern =>\n lower.includes(pattern),\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../../../../../zero-client/src/client/inspector/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,sCAAsC,CAAC;AAC9D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,6CAA6C,CAAC;AAErF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6CAA6C,CAAC;AAC/E,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AACjE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,EAEV,iBAAiB,EACjB,OAAO,EACP,GAAG,EACJ,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAC,iBAAiB,EAAC,CAAC;AAGhC,MAAM,MAAM,IAAI,GAAG,cAAc,qBAAqB,CAAC,CAAC;AAExD,qBAAa,SAAS;;IAEpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;gBAGhC,GAAG,EAAE,GAAG,EACR,iBAAiB,EAAE,iBAAiB,EACpC,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC;IAqB/B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAI3B,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI5B,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAMvC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAIhC,YAAY,CAChB,KAAK,EAAE,QAAQ,EACf,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAQ9B;;;;OAIG;IACG,gBAAgB,CACpB,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAQ9B;;;OAGG;IACG,iBAAiB,CACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,CAAC,iBAAiB,CAAC,EACtC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAS9B;;;;;;;;;OASG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM;CAG1D"}
1
+ {"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../../../../../zero-client/src/client/inspector/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,sCAAsC,CAAC;AAC9D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,6CAA6C,CAAC;AAErF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6CAA6C,CAAC;AAC/E,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AACjE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,EAEV,iBAAiB,EACjB,OAAO,EAEP,GAAG,EACJ,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAC,iBAAiB,EAAC,CAAC;AAGhC,MAAM,MAAM,IAAI,GAAG,cAAc,qBAAqB,CAAC,CAAC;AAExD,qBAAa,SAAS;;IAMpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;gBAGhC,GAAG,EAAE,GAAG,EACR,iBAAiB,EAAE,iBAAiB,EACpC,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC;IAkC/B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAI3B,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI5B,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAMvC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAIhC,YAAY,CAChB,KAAK,EAAE,QAAQ,EACf,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAQ9B;;;;OAIG;IACG,gBAAgB,CACpB,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAQ9B;;;OAGG;IACG,iBAAiB,CACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,CAAC,iBAAiB,CAAC,EACtC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAS9B;;;;;;;;;OASG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM;CAG1D"}
@@ -3,16 +3,29 @@ import { Client } from "./client.js";
3
3
  //#region ../zero-client/src/client/inspector/inspector.ts
4
4
  var Inspector = class {
5
5
  #delegate;
6
+ #deletedQueries = /* @__PURE__ */ new Map();
6
7
  client;
7
8
  clientGroup;
8
9
  constructor(rep, inspectorDelegate, queryDelegate, getSocket) {
10
+ const deletedQueries = this.#deletedQueries;
11
+ inspectorDelegate.setQueryEvictedCallback((hash, ast, metrics) => {
12
+ deletedQueries.set(hash, {
13
+ ast,
14
+ metrics
15
+ });
16
+ });
9
17
  this.#delegate = {
10
- getQueryMetrics: inspectorDelegate.getQueryMetrics.bind(inspectorDelegate),
11
- getAST: inspectorDelegate.getAST.bind(inspectorDelegate),
18
+ getQueryMetrics(hash) {
19
+ return inspectorDelegate.getQueryMetrics(hash) ?? deletedQueries.get(hash)?.metrics;
20
+ },
21
+ getAST(queryID) {
22
+ return inspectorDelegate.getAST(queryID) ?? deletedQueries.get(queryID)?.ast;
23
+ },
12
24
  mapClientASTToServer: inspectorDelegate.mapClientASTToServer.bind(inspectorDelegate),
13
25
  get metrics() {
14
26
  return inspectorDelegate.metrics;
15
27
  },
28
+ setQueryEvictedCallback() {},
16
29
  queryDelegate,
17
30
  rep,
18
31
  getSocket,
@@ -1 +1 @@
1
- {"version":3,"file":"inspector.js","names":["#delegate"],"sources":["../../../../../../zero-client/src/client/inspector/inspector.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../../../shared/src/json.ts';\nimport type {\n AnalyzeQueryResult,\n PlanDebugEventJSON,\n} from '../../../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../../../zero-protocol/src/ast.ts';\nimport type {AnalyzeQueryOptions} from '../../../../zero-protocol/src/inspect-up.ts';\nimport {formatPlannerEvents} from '../../../../zql/src/planner/planner-debug.ts';\nimport type {QueryDelegate} from '../../../../zql/src/query/query-delegate.ts';\nimport type {AnyQuery} from '../../../../zql/src/query/query.ts';\nimport type {ClientGroup} from './client-group.ts';\nimport {Client} from './client.ts';\nimport type {\n ExtendedInspectorDelegate,\n InspectorDelegate,\n Metrics,\n Rep,\n} from './lazy-inspector.ts';\n\nexport type {InspectorDelegate};\n\n// oxlint-disable-next-line consistent-type-imports\nexport type Lazy = typeof import('./lazy-inspector.ts');\n\nexport class Inspector {\n readonly #delegate: ExtendedInspectorDelegate;\n readonly client: Client;\n readonly clientGroup: ClientGroup;\n\n constructor(\n rep: Rep,\n inspectorDelegate: InspectorDelegate,\n queryDelegate: QueryDelegate,\n getSocket: () => Promise<WebSocket>,\n ) {\n this.#delegate = {\n getQueryMetrics:\n inspectorDelegate.getQueryMetrics.bind(inspectorDelegate),\n getAST: inspectorDelegate.getAST.bind(inspectorDelegate),\n mapClientASTToServer:\n inspectorDelegate.mapClientASTToServer.bind(inspectorDelegate),\n get metrics() {\n return inspectorDelegate.metrics;\n },\n queryDelegate,\n rep,\n getSocket,\n lazy: import('./lazy-inspector.ts'),\n };\n\n this.client = new Client(this.#delegate, rep.clientID, rep.clientGroupID);\n this.clientGroup = this.client.clientGroup;\n }\n\n async metrics(): Promise<Metrics> {\n return (await this.#delegate.lazy).inspectorMetrics(this.#delegate);\n }\n\n async clients(): Promise<Client[]> {\n return (await this.#delegate.lazy).inspectorClients(this.#delegate);\n }\n\n async clientsWithQueries(): Promise<Client[]> {\n return (await this.#delegate.lazy).inspectorClientsWithQueries(\n this.#delegate,\n );\n }\n\n async serverVersion(): Promise<string> {\n return (await this.#delegate.lazy).serverVersion(this.#delegate);\n }\n\n async analyzeQuery(\n query: AnyQuery,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeQuery(\n this.#delegate,\n query,\n options,\n );\n }\n\n /**\n * Analyze a query specified by a server-side AST. Unlike {@link analyzeQuery}\n * the AST is sent to the server verbatim with no client-to-server name\n * mapping; callers should provide an AST already in the server shape.\n */\n async analyzeServerAST(\n ast: AST,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeServerAST(\n this.#delegate,\n ast,\n options,\n );\n }\n\n /**\n * Analyze a server-registered named (custom) query. The server resolves\n * the name and args to an AST using its registered custom-query handler.\n */\n async analyzeNamedQuery(\n name: string,\n args: ReadonlyArray<ReadonlyJSONValue>,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeNamedQuery(\n this.#delegate,\n name,\n args,\n options,\n );\n }\n\n /**\n * Authenticate with the server's admin password. Other inspector RPCs\n * (e.g. {@link analyzeQuery}) fall back to an interactive HTML password\n * prompt when authentication is needed, which is unavailable in non-DOM\n * environments. Call this first from Node contexts to establish the\n * session.\n *\n * Returns `true` if the password is accepted (or the server runs in a\n * development mode that bypasses the check), `false` otherwise.\n */\n async authenticate(password: string): Promise<boolean> {\n return (await this.#delegate.lazy).authenticate(this.#delegate, password);\n }\n\n /**\n * Format planner debug events as a human-readable string.\n */\n formatPlannerEvents(events: PlanDebugEventJSON[]): string {\n return formatPlannerEvents(events);\n }\n}\n"],"mappings":";;;AAwBA,IAAa,YAAb,MAAuB;CACrB;CACA;CACA;CAEA,YACE,KACA,mBACA,eACA,WACA;AACA,QAAA,WAAiB;GACf,iBACE,kBAAkB,gBAAgB,KAAK,kBAAkB;GAC3D,QAAQ,kBAAkB,OAAO,KAAK,kBAAkB;GACxD,sBACE,kBAAkB,qBAAqB,KAAK,kBAAkB;GAChE,IAAI,UAAU;AACZ,WAAO,kBAAkB;;GAE3B;GACA;GACA;GACA,MAAM,OAAO;GACd;AAED,OAAK,SAAS,IAAI,OAAO,MAAA,UAAgB,IAAI,UAAU,IAAI,cAAc;AACzE,OAAK,cAAc,KAAK,OAAO;;CAGjC,MAAM,UAA4B;AAChC,UAAQ,MAAM,MAAA,SAAe,MAAM,iBAAiB,MAAA,SAAe;;CAGrE,MAAM,UAA6B;AACjC,UAAQ,MAAM,MAAA,SAAe,MAAM,iBAAiB,MAAA,SAAe;;CAGrE,MAAM,qBAAwC;AAC5C,UAAQ,MAAM,MAAA,SAAe,MAAM,4BACjC,MAAA,SACD;;CAGH,MAAM,gBAAiC;AACrC,UAAQ,MAAM,MAAA,SAAe,MAAM,cAAc,MAAA,SAAe;;CAGlE,MAAM,aACJ,OACA,SAC6B;AAC7B,UAAQ,MAAM,MAAA,SAAe,MAAM,aACjC,MAAA,UACA,OACA,QACD;;;;;;;CAQH,MAAM,iBACJ,KACA,SAC6B;AAC7B,UAAQ,MAAM,MAAA,SAAe,MAAM,iBACjC,MAAA,UACA,KACA,QACD;;;;;;CAOH,MAAM,kBACJ,MACA,MACA,SAC6B;AAC7B,UAAQ,MAAM,MAAA,SAAe,MAAM,kBACjC,MAAA,UACA,MACA,MACA,QACD;;;;;;;;;;;;CAaH,MAAM,aAAa,UAAoC;AACrD,UAAQ,MAAM,MAAA,SAAe,MAAM,aAAa,MAAA,UAAgB,SAAS;;;;;CAM3E,oBAAoB,QAAsC;AACxD,SAAO,oBAAoB,OAAO"}
1
+ {"version":3,"file":"inspector.js","names":["#delegate","#deletedQueries"],"sources":["../../../../../../zero-client/src/client/inspector/inspector.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../../../shared/src/json.ts';\nimport type {\n AnalyzeQueryResult,\n PlanDebugEventJSON,\n} from '../../../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../../../zero-protocol/src/ast.ts';\nimport type {AnalyzeQueryOptions} from '../../../../zero-protocol/src/inspect-up.ts';\nimport {formatPlannerEvents} from '../../../../zql/src/planner/planner-debug.ts';\nimport type {QueryDelegate} from '../../../../zql/src/query/query-delegate.ts';\nimport type {AnyQuery} from '../../../../zql/src/query/query.ts';\nimport type {ClientGroup} from './client-group.ts';\nimport {Client} from './client.ts';\nimport type {\n ExtendedInspectorDelegate,\n InspectorDelegate,\n Metrics,\n QueryClientMetrics,\n Rep,\n} from './lazy-inspector.ts';\n\nexport type {InspectorDelegate};\n\n// oxlint-disable-next-line consistent-type-imports\nexport type Lazy = typeof import('./lazy-inspector.ts');\n\nexport class Inspector {\n readonly #delegate: ExtendedInspectorDelegate;\n readonly #deletedQueries = new Map<\n string,\n {readonly ast: AST; readonly metrics: QueryClientMetrics}\n >();\n readonly client: Client;\n readonly clientGroup: ClientGroup;\n\n constructor(\n rep: Rep,\n inspectorDelegate: InspectorDelegate,\n queryDelegate: QueryDelegate,\n getSocket: () => Promise<WebSocket>,\n ) {\n const deletedQueries = this.#deletedQueries;\n inspectorDelegate.setQueryEvictedCallback((hash, ast, metrics) => {\n deletedQueries.set(hash, {ast, metrics});\n });\n this.#delegate = {\n getQueryMetrics(hash) {\n return (\n inspectorDelegate.getQueryMetrics(hash) ??\n deletedQueries.get(hash)?.metrics\n );\n },\n getAST(queryID) {\n return (\n inspectorDelegate.getAST(queryID) ?? deletedQueries.get(queryID)?.ast\n );\n },\n mapClientASTToServer:\n inspectorDelegate.mapClientASTToServer.bind(inspectorDelegate),\n get metrics() {\n return inspectorDelegate.metrics;\n },\n setQueryEvictedCallback() {},\n queryDelegate,\n rep,\n getSocket,\n lazy: import('./lazy-inspector.ts'),\n };\n\n this.client = new Client(this.#delegate, rep.clientID, rep.clientGroupID);\n this.clientGroup = this.client.clientGroup;\n }\n\n async metrics(): Promise<Metrics> {\n return (await this.#delegate.lazy).inspectorMetrics(this.#delegate);\n }\n\n async clients(): Promise<Client[]> {\n return (await this.#delegate.lazy).inspectorClients(this.#delegate);\n }\n\n async clientsWithQueries(): Promise<Client[]> {\n return (await this.#delegate.lazy).inspectorClientsWithQueries(\n this.#delegate,\n );\n }\n\n async serverVersion(): Promise<string> {\n return (await this.#delegate.lazy).serverVersion(this.#delegate);\n }\n\n async analyzeQuery(\n query: AnyQuery,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeQuery(\n this.#delegate,\n query,\n options,\n );\n }\n\n /**\n * Analyze a query specified by a server-side AST. Unlike {@link analyzeQuery}\n * the AST is sent to the server verbatim with no client-to-server name\n * mapping; callers should provide an AST already in the server shape.\n */\n async analyzeServerAST(\n ast: AST,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeServerAST(\n this.#delegate,\n ast,\n options,\n );\n }\n\n /**\n * Analyze a server-registered named (custom) query. The server resolves\n * the name and args to an AST using its registered custom-query handler.\n */\n async analyzeNamedQuery(\n name: string,\n args: ReadonlyArray<ReadonlyJSONValue>,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeNamedQuery(\n this.#delegate,\n name,\n args,\n options,\n );\n }\n\n /**\n * Authenticate with the server's admin password. Other inspector RPCs\n * (e.g. {@link analyzeQuery}) fall back to an interactive HTML password\n * prompt when authentication is needed, which is unavailable in non-DOM\n * environments. Call this first from Node contexts to establish the\n * session.\n *\n * Returns `true` if the password is accepted (or the server runs in a\n * development mode that bypasses the check), `false` otherwise.\n */\n async authenticate(password: string): Promise<boolean> {\n return (await this.#delegate.lazy).authenticate(this.#delegate, password);\n }\n\n /**\n * Format planner debug events as a human-readable string.\n */\n formatPlannerEvents(events: PlanDebugEventJSON[]): string {\n return formatPlannerEvents(events);\n }\n}\n"],"mappings":";;;AAyBA,IAAa,YAAb,MAAuB;CACrB;CACA,kCAA2B,IAAI,KAG5B;CACH;CACA;CAEA,YACE,KACA,mBACA,eACA,WACA;EACA,MAAM,iBAAiB,MAAA;AACvB,oBAAkB,yBAAyB,MAAM,KAAK,YAAY;AAChE,kBAAe,IAAI,MAAM;IAAC;IAAK;IAAQ,CAAC;IACxC;AACF,QAAA,WAAiB;GACf,gBAAgB,MAAM;AACpB,WACE,kBAAkB,gBAAgB,KAAK,IACvC,eAAe,IAAI,KAAK,EAAE;;GAG9B,OAAO,SAAS;AACd,WACE,kBAAkB,OAAO,QAAQ,IAAI,eAAe,IAAI,QAAQ,EAAE;;GAGtE,sBACE,kBAAkB,qBAAqB,KAAK,kBAAkB;GAChE,IAAI,UAAU;AACZ,WAAO,kBAAkB;;GAE3B,0BAA0B;GAC1B;GACA;GACA;GACA,MAAM,OAAO;GACd;AAED,OAAK,SAAS,IAAI,OAAO,MAAA,UAAgB,IAAI,UAAU,IAAI,cAAc;AACzE,OAAK,cAAc,KAAK,OAAO;;CAGjC,MAAM,UAA4B;AAChC,UAAQ,MAAM,MAAA,SAAe,MAAM,iBAAiB,MAAA,SAAe;;CAGrE,MAAM,UAA6B;AACjC,UAAQ,MAAM,MAAA,SAAe,MAAM,iBAAiB,MAAA,SAAe;;CAGrE,MAAM,qBAAwC;AAC5C,UAAQ,MAAM,MAAA,SAAe,MAAM,4BACjC,MAAA,SACD;;CAGH,MAAM,gBAAiC;AACrC,UAAQ,MAAM,MAAA,SAAe,MAAM,cAAc,MAAA,SAAe;;CAGlE,MAAM,aACJ,OACA,SAC6B;AAC7B,UAAQ,MAAM,MAAA,SAAe,MAAM,aACjC,MAAA,UACA,OACA,QACD;;;;;;;CAQH,MAAM,iBACJ,KACA,SAC6B;AAC7B,UAAQ,MAAM,MAAA,SAAe,MAAM,iBACjC,MAAA,UACA,KACA,QACD;;;;;;CAOH,MAAM,kBACJ,MACA,MACA,SAC6B;AAC7B,UAAQ,MAAM,MAAA,SAAe,MAAM,kBACjC,MAAA,UACA,MACA,MACA,QACD;;;;;;;;;;;;CAaH,MAAM,aAAa,UAAoC;AACrD,UAAQ,MAAM,MAAA,SAAe,MAAM,aAAa,MAAA,UAAgB,SAAS;;;;;CAM3E,oBAAoB,QAAsC;AACxD,SAAO,oBAAoB,OAAO"}
@@ -5,7 +5,7 @@ import * as valita from '../../../../shared/src/valita.ts';
5
5
  import type { AnalyzeQueryResult } from '../../../../zero-protocol/src/analyze-query-result.ts';
6
6
  import type { AST } from '../../../../zero-protocol/src/ast.ts';
7
7
  import type { Row } from '../../../../zero-protocol/src/data.ts';
8
- import { type InspectDownBody, type ServerMetrics as ServerMetricsJSON } from '../../../../zero-protocol/src/inspect-down.ts';
8
+ import { type InspectDownBody, type QueryServerMetrics as QueryServerMetricsJSON } from '../../../../zero-protocol/src/inspect-down.ts';
9
9
  import type { AnalyzeQueryOptions, InspectUpBody } from '../../../../zero-protocol/src/inspect-up.ts';
10
10
  import type { ClientMetricMap, ServerMetricMap } from '../../../../zql/src/query/metrics-delegate.ts';
11
11
  import type { QueryDelegate } from '../../../../zql/src/query/query-delegate.ts';
@@ -20,7 +20,7 @@ export type Metrics = {
20
20
  };
21
21
  type DistributiveOmit<T, K extends string> = T extends object ? Omit<T, K> : never;
22
22
  export declare function rpc<T extends InspectDownBody>(socket: WebSocket, arg: DistributiveOmit<InspectUpBody, 'id'>, downSchema: valita.Type<T>): Promise<T['value']>;
23
- export declare function mergeMetrics(clientMetrics: ClientMetrics | undefined, serverMetrics: ServerMetricsJSON | null | undefined): ClientMetrics & ServerMetrics;
23
+ export declare function mergeMetrics(clientMetrics: QueryClientMetrics | undefined, serverMetrics: QueryServerMetricsJSON | null | undefined): ClientMetrics & ServerMetrics;
24
24
  export declare function inspectorMetrics(delegate: ExtendedInspectorDelegate): Promise<Metrics>;
25
25
  export declare function inspectorClients(delegate: ExtendedInspectorDelegate): Promise<Client[]>;
26
26
  export declare function inspectorClientsWithQueries(delegate: ExtendedInspectorDelegate): Promise<Client[]>;
@@ -42,10 +42,11 @@ export declare function analyzeNamedQuery(delegate: ExtendedInspectorDelegate, n
42
42
  */
43
43
  export declare function authenticate(delegate: ExtendedInspectorDelegate, password: string): Promise<boolean>;
44
44
  export interface InspectorDelegate {
45
- getQueryMetrics(hash: string): ClientMetrics | undefined;
45
+ getQueryMetrics(hash: string): QueryClientMetrics | undefined;
46
46
  getAST(queryID: string): AST | undefined;
47
47
  readonly metrics: ClientMetrics;
48
48
  mapClientASTToServer(ast: AST): AST;
49
+ setQueryEvictedCallback(cb: (hash: string, ast: AST, metrics: QueryClientMetrics) => void): void;
49
50
  }
50
51
  export interface ExtendedInspectorDelegate extends InspectorDelegate {
51
52
  readonly rep: Rep;
@@ -54,6 +55,11 @@ export interface ExtendedInspectorDelegate extends InspectorDelegate {
54
55
  lazy: Promise<Lazy>;
55
56
  }
56
57
  export type Rep = ReplicacheImpl<MutatorDefs>;
58
+ export type QueryClientMetrics = {
59
+ readonly 'query-materialization-client': number | undefined;
60
+ readonly 'query-materialization-end-to-end': number | undefined;
61
+ readonly 'query-update-client': ReadonlyTDigest;
62
+ };
57
63
  export type ClientMetrics = {
58
64
  readonly [K in keyof ClientMetricMap]: ReadonlyTDigest;
59
65
  };
@@ -1 +1 @@
1
- {"version":3,"file":"lazy-inspector.d.ts","sourceRoot":"","sources":["../../../../../../zero-client/src/client/inspector/lazy-inspector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lazy-inspector.d.ts","sourceRoot":"","sources":["../../../../../../zero-client/src/client/inspector/lazy-inspector.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,6 @@ import { readFromHash } from "../../../../replicache/src/db/read.js";
4
4
  import { withRead } from "../../../../replicache/src/with-transactions.js";
5
5
  import { getClientGroup } from "../../../../replicache/src/persist/client-groups.js";
6
6
  import { getClient, getClients } from "../../../../replicache/src/persist/clients.js";
7
- import { mapValues } from "../../../../shared/src/objects.js";
8
7
  import { asQueryInternals } from "../../../../zql/src/query/query-internals.js";
9
8
  import { inspectAnalyzeQueryDownSchema, inspectAuthenticatedDownSchema, inspectMetricsDownSchema, inspectQueriesDownSchema, inspectVersionDownSchema } from "../../../../zero-protocol/src/inspect-down.js";
10
9
  import { nanoid } from "../../util/nanoid.js";
@@ -63,17 +62,28 @@ function rpcNoAuthTry(socket, arg, downSchema) {
63
62
  }
64
63
  function mergeMetrics(clientMetrics, serverMetrics) {
65
64
  return {
66
- ...clientMetrics ?? newClientMetrics(),
65
+ ...convertClientMetrics(clientMetrics ?? newClientMetrics()),
67
66
  ...serverMetrics ? convertServerMetrics(serverMetrics) : newServerMetrics()
68
67
  };
69
68
  }
70
69
  function newClientMetrics() {
71
70
  return {
72
- "query-materialization-client": new TDigest(),
73
- "query-materialization-end-to-end": new TDigest(),
71
+ "query-materialization-client": void 0,
72
+ "query-materialization-end-to-end": void 0,
74
73
  "query-update-client": new TDigest()
75
74
  };
76
75
  }
76
+ function convertClientMetrics(metrics) {
77
+ const hydrateDigest = new TDigest();
78
+ if (metrics["query-materialization-client"] !== void 0) hydrateDigest.add(metrics["query-materialization-client"]);
79
+ const totalDigest = new TDigest();
80
+ if (metrics["query-materialization-end-to-end"] !== void 0) totalDigest.add(metrics["query-materialization-end-to-end"]);
81
+ return {
82
+ "query-materialization-client": hydrateDigest,
83
+ "query-materialization-end-to-end": totalDigest,
84
+ "query-update-client": metrics["query-update-client"]
85
+ };
86
+ }
77
87
  function newServerMetrics() {
78
88
  return {
79
89
  "query-materialization-server": new TDigest(),
@@ -81,11 +91,22 @@ function newServerMetrics() {
81
91
  };
82
92
  }
83
93
  function convertServerMetrics(metrics) {
84
- return mapValues(metrics, (v) => TDigest.fromJSON(v));
94
+ const hydrateMs = metrics["query-hydration-server-ms"];
95
+ const hydrateDigest = new TDigest();
96
+ if (hydrateMs !== void 0) hydrateDigest.add(hydrateMs);
97
+ return {
98
+ "query-materialization-server": hydrateDigest,
99
+ "query-update-server": TDigest.fromJSON(metrics["query-update-server"])
100
+ };
85
101
  }
86
102
  async function inspectorMetrics(delegate) {
87
103
  const clientMetrics = delegate.metrics;
88
- return mergeMetrics(clientMetrics, await rpc(await delegate.getSocket(), { op: "metrics" }, inspectMetricsDownSchema));
104
+ const serverMetricsJSON = await rpc(await delegate.getSocket(), { op: "metrics" }, inspectMetricsDownSchema);
105
+ return {
106
+ ...clientMetrics ?? newClientMetrics(),
107
+ "query-materialization-server": TDigest.fromJSON(serverMetricsJSON["query-materialization-server"]),
108
+ "query-update-server": TDigest.fromJSON(serverMetricsJSON["query-update-server"])
109
+ };
89
110
  }
90
111
  function inspectorClients(delegate) {
91
112
  return withDagRead(delegate, (dagRead) => clients(delegate, dagRead));