@smthrs/sync 0.0.0-stage → 1.0.0-rc.4

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 (266) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/LICENSE +21 -0
  3. package/README.md +208 -2
  4. package/dist/cjs/BranchCommands.d.ts +228 -0
  5. package/dist/cjs/BranchCommands.d.ts.map +1 -0
  6. package/dist/cjs/BranchCommands.js +316 -0
  7. package/dist/cjs/BranchCommands.js.map +7 -0
  8. package/dist/cjs/BranchIds.d.ts +65 -0
  9. package/dist/cjs/BranchIds.d.ts.map +1 -0
  10. package/dist/cjs/BranchIds.js +53 -0
  11. package/dist/cjs/BranchIds.js.map +7 -0
  12. package/dist/cjs/BranchPresence.d.ts +195 -0
  13. package/dist/cjs/BranchPresence.d.ts.map +1 -0
  14. package/dist/cjs/BranchPresence.js +276 -0
  15. package/dist/cjs/BranchPresence.js.map +7 -0
  16. package/dist/cjs/BranchProjection.d.ts +179 -0
  17. package/dist/cjs/BranchProjection.d.ts.map +1 -0
  18. package/dist/cjs/BranchProjection.js +197 -0
  19. package/dist/cjs/BranchProjection.js.map +7 -0
  20. package/dist/cjs/BranchProtocol.d.ts +415 -0
  21. package/dist/cjs/BranchProtocol.d.ts.map +1 -0
  22. package/dist/cjs/BranchProtocol.js +172 -0
  23. package/dist/cjs/BranchProtocol.js.map +7 -0
  24. package/dist/cjs/BranchRpcs.d.ts +177 -0
  25. package/dist/cjs/BranchRpcs.d.ts.map +1 -0
  26. package/dist/cjs/BranchRpcs.js +85 -0
  27. package/dist/cjs/BranchRpcs.js.map +7 -0
  28. package/dist/cjs/BranchServer.d.ts +55 -0
  29. package/dist/cjs/BranchServer.d.ts.map +1 -0
  30. package/dist/cjs/BranchServer.js +147 -0
  31. package/dist/cjs/BranchServer.js.map +7 -0
  32. package/dist/cjs/BranchShare.d.ts +159 -0
  33. package/dist/cjs/BranchShare.d.ts.map +1 -0
  34. package/dist/cjs/BranchShare.js +147 -0
  35. package/dist/cjs/BranchShare.js.map +7 -0
  36. package/dist/cjs/RunCatalog.d.ts +207 -0
  37. package/dist/cjs/RunCatalog.d.ts.map +1 -0
  38. package/dist/cjs/RunCatalog.js +122 -0
  39. package/dist/cjs/RunCatalog.js.map +7 -0
  40. package/dist/cjs/SyncAuth.d.ts +70 -0
  41. package/dist/cjs/SyncAuth.d.ts.map +1 -0
  42. package/dist/cjs/SyncAuth.js +109 -0
  43. package/dist/cjs/SyncAuth.js.map +7 -0
  44. package/dist/cjs/SyncClient.d.ts +274 -0
  45. package/dist/cjs/SyncClient.d.ts.map +1 -0
  46. package/dist/cjs/SyncClient.js +474 -0
  47. package/dist/cjs/SyncClient.js.map +7 -0
  48. package/dist/cjs/SyncError.d.ts +107 -0
  49. package/dist/cjs/SyncError.d.ts.map +1 -0
  50. package/dist/cjs/SyncError.js +110 -0
  51. package/dist/cjs/SyncError.js.map +7 -0
  52. package/dist/cjs/SyncPrincipal.d.ts +100 -0
  53. package/dist/cjs/SyncPrincipal.d.ts.map +1 -0
  54. package/dist/cjs/SyncPrincipal.js +48 -0
  55. package/dist/cjs/SyncPrincipal.js.map +7 -0
  56. package/dist/cjs/SyncProtocol.d.ts +592 -0
  57. package/dist/cjs/SyncProtocol.d.ts.map +1 -0
  58. package/dist/cjs/SyncProtocol.js +165 -0
  59. package/dist/cjs/SyncProtocol.js.map +7 -0
  60. package/dist/cjs/SyncRpcs.d.ts +92 -0
  61. package/dist/cjs/SyncRpcs.d.ts.map +1 -0
  62. package/dist/cjs/SyncRpcs.js +35 -0
  63. package/dist/cjs/SyncRpcs.js.map +7 -0
  64. package/dist/cjs/SyncServer.d.ts +184 -0
  65. package/dist/cjs/SyncServer.d.ts.map +1 -0
  66. package/dist/cjs/SyncServer.js +513 -0
  67. package/dist/cjs/SyncServer.js.map +7 -0
  68. package/dist/cjs/WorkspaceShare.d.ts +174 -0
  69. package/dist/cjs/WorkspaceShare.d.ts.map +1 -0
  70. package/dist/cjs/WorkspaceShare.js +149 -0
  71. package/dist/cjs/WorkspaceShare.js.map +7 -0
  72. package/dist/cjs/index.d.ts +91 -0
  73. package/dist/cjs/index.d.ts.map +1 -0
  74. package/dist/cjs/index.js +67 -0
  75. package/dist/cjs/index.js.map +7 -0
  76. package/dist/cjs/internal/Admission.d.ts +43 -0
  77. package/dist/cjs/internal/Admission.d.ts.map +1 -0
  78. package/dist/cjs/internal/Admission.js +90 -0
  79. package/dist/cjs/internal/Admission.js.map +7 -0
  80. package/dist/cjs/internal/CauseText.d.ts +55 -0
  81. package/dist/cjs/internal/CauseText.d.ts.map +1 -0
  82. package/dist/cjs/internal/CauseText.js +38 -0
  83. package/dist/cjs/internal/CauseText.js.map +7 -0
  84. package/dist/cjs/internal/JournalErrorCode.d.ts +23 -0
  85. package/dist/cjs/internal/JournalErrorCode.d.ts.map +1 -0
  86. package/dist/cjs/internal/JournalErrorCode.js +34 -0
  87. package/dist/cjs/internal/JournalErrorCode.js.map +7 -0
  88. package/dist/cjs/internal/PolicyOptions.d.ts +57 -0
  89. package/dist/cjs/internal/PolicyOptions.d.ts.map +1 -0
  90. package/dist/cjs/internal/PolicyOptions.js +62 -0
  91. package/dist/cjs/internal/PolicyOptions.js.map +7 -0
  92. package/dist/cjs/internal/ProtocolVersion.d.ts +17 -0
  93. package/dist/cjs/internal/ProtocolVersion.d.ts.map +1 -0
  94. package/dist/cjs/internal/ProtocolVersion.js +44 -0
  95. package/dist/cjs/internal/ProtocolVersion.js.map +7 -0
  96. package/dist/cjs/internal/ShareSigner.d.ts +173 -0
  97. package/dist/cjs/internal/ShareSigner.d.ts.map +1 -0
  98. package/dist/cjs/internal/ShareSigner.js +150 -0
  99. package/dist/cjs/internal/ShareSigner.js.map +7 -0
  100. package/dist/cjs/internal/SnapshotBoundary.d.ts +17 -0
  101. package/dist/cjs/internal/SnapshotBoundary.d.ts.map +1 -0
  102. package/dist/cjs/internal/SnapshotBoundary.js +97 -0
  103. package/dist/cjs/internal/SnapshotBoundary.js.map +7 -0
  104. package/dist/cjs/package.json +1 -0
  105. package/dist/cjs/test/TestSocket.d.ts +55 -0
  106. package/dist/cjs/test/TestSocket.d.ts.map +1 -0
  107. package/dist/cjs/test/TestSocket.js +145 -0
  108. package/dist/cjs/test/TestSocket.js.map +7 -0
  109. package/dist/cjs/test/TestSync.d.ts +54 -0
  110. package/dist/cjs/test/TestSync.d.ts.map +1 -0
  111. package/dist/cjs/test/TestSync.js +96 -0
  112. package/dist/cjs/test/TestSync.js.map +7 -0
  113. package/dist/esm/BranchCommands.d.ts +228 -0
  114. package/dist/esm/BranchCommands.d.ts.map +1 -0
  115. package/dist/esm/BranchCommands.js +496 -0
  116. package/dist/esm/BranchCommands.js.map +1 -0
  117. package/dist/esm/BranchIds.d.ts +65 -0
  118. package/dist/esm/BranchIds.d.ts.map +1 -0
  119. package/dist/esm/BranchIds.js +61 -0
  120. package/dist/esm/BranchIds.js.map +1 -0
  121. package/dist/esm/BranchPresence.d.ts +195 -0
  122. package/dist/esm/BranchPresence.d.ts.map +1 -0
  123. package/dist/esm/BranchPresence.js +361 -0
  124. package/dist/esm/BranchPresence.js.map +1 -0
  125. package/dist/esm/BranchProjection.d.ts +179 -0
  126. package/dist/esm/BranchProjection.d.ts.map +1 -0
  127. package/dist/esm/BranchProjection.js +269 -0
  128. package/dist/esm/BranchProjection.js.map +1 -0
  129. package/dist/esm/BranchProtocol.d.ts +415 -0
  130. package/dist/esm/BranchProtocol.d.ts.map +1 -0
  131. package/dist/esm/BranchProtocol.js +323 -0
  132. package/dist/esm/BranchProtocol.js.map +1 -0
  133. package/dist/esm/BranchRpcs.d.ts +177 -0
  134. package/dist/esm/BranchRpcs.d.ts.map +1 -0
  135. package/dist/esm/BranchRpcs.js +103 -0
  136. package/dist/esm/BranchRpcs.js.map +1 -0
  137. package/dist/esm/BranchServer.d.ts +55 -0
  138. package/dist/esm/BranchServer.d.ts.map +1 -0
  139. package/dist/esm/BranchServer.js +168 -0
  140. package/dist/esm/BranchServer.js.map +1 -0
  141. package/dist/esm/BranchShare.d.ts +159 -0
  142. package/dist/esm/BranchShare.d.ts.map +1 -0
  143. package/dist/esm/BranchShare.js +213 -0
  144. package/dist/esm/BranchShare.js.map +1 -0
  145. package/dist/esm/RunCatalog.d.ts +207 -0
  146. package/dist/esm/RunCatalog.d.ts.map +1 -0
  147. package/dist/esm/RunCatalog.js +196 -0
  148. package/dist/esm/RunCatalog.js.map +1 -0
  149. package/dist/esm/SyncAuth.d.ts +70 -0
  150. package/dist/esm/SyncAuth.d.ts.map +1 -0
  151. package/dist/esm/SyncAuth.js +119 -0
  152. package/dist/esm/SyncAuth.js.map +1 -0
  153. package/dist/esm/SyncClient.d.ts +274 -0
  154. package/dist/esm/SyncClient.d.ts.map +1 -0
  155. package/dist/esm/SyncClient.js +586 -0
  156. package/dist/esm/SyncClient.js.map +1 -0
  157. package/dist/esm/SyncError.d.ts +107 -0
  158. package/dist/esm/SyncError.d.ts.map +1 -0
  159. package/dist/esm/SyncError.js +133 -0
  160. package/dist/esm/SyncError.js.map +1 -0
  161. package/dist/esm/SyncPrincipal.d.ts +100 -0
  162. package/dist/esm/SyncPrincipal.d.ts.map +1 -0
  163. package/dist/esm/SyncPrincipal.js +58 -0
  164. package/dist/esm/SyncPrincipal.js.map +1 -0
  165. package/dist/esm/SyncProtocol.d.ts +592 -0
  166. package/dist/esm/SyncProtocol.d.ts.map +1 -0
  167. package/dist/esm/SyncProtocol.js +434 -0
  168. package/dist/esm/SyncProtocol.js.map +1 -0
  169. package/dist/esm/SyncRpcs.d.ts +92 -0
  170. package/dist/esm/SyncRpcs.d.ts.map +1 -0
  171. package/dist/esm/SyncRpcs.js +35 -0
  172. package/dist/esm/SyncRpcs.js.map +1 -0
  173. package/dist/esm/SyncServer.d.ts +184 -0
  174. package/dist/esm/SyncServer.d.ts.map +1 -0
  175. package/dist/esm/SyncServer.js +897 -0
  176. package/dist/esm/SyncServer.js.map +1 -0
  177. package/dist/esm/WorkspaceShare.d.ts +174 -0
  178. package/dist/esm/WorkspaceShare.d.ts.map +1 -0
  179. package/dist/esm/WorkspaceShare.js +208 -0
  180. package/dist/esm/WorkspaceShare.js.map +1 -0
  181. package/dist/esm/index.d.ts +91 -0
  182. package/dist/esm/index.d.ts.map +1 -0
  183. package/dist/esm/index.js +91 -0
  184. package/dist/esm/index.js.map +1 -0
  185. package/dist/esm/internal/Admission.d.ts +43 -0
  186. package/dist/esm/internal/Admission.d.ts.map +1 -0
  187. package/dist/esm/internal/Admission.js +80 -0
  188. package/dist/esm/internal/Admission.js.map +1 -0
  189. package/dist/esm/internal/CauseText.d.ts +55 -0
  190. package/dist/esm/internal/CauseText.d.ts.map +1 -0
  191. package/dist/esm/internal/CauseText.js +67 -0
  192. package/dist/esm/internal/CauseText.js.map +1 -0
  193. package/dist/esm/internal/JournalErrorCode.d.ts +23 -0
  194. package/dist/esm/internal/JournalErrorCode.d.ts.map +1 -0
  195. package/dist/esm/internal/JournalErrorCode.js +44 -0
  196. package/dist/esm/internal/JournalErrorCode.js.map +1 -0
  197. package/dist/esm/internal/PolicyOptions.d.ts +57 -0
  198. package/dist/esm/internal/PolicyOptions.d.ts.map +1 -0
  199. package/dist/esm/internal/PolicyOptions.js +70 -0
  200. package/dist/esm/internal/PolicyOptions.js.map +1 -0
  201. package/dist/esm/internal/ProtocolVersion.d.ts +17 -0
  202. package/dist/esm/internal/ProtocolVersion.d.ts.map +1 -0
  203. package/dist/esm/internal/ProtocolVersion.js +24 -0
  204. package/dist/esm/internal/ProtocolVersion.js.map +1 -0
  205. package/dist/esm/internal/ShareSigner.d.ts +173 -0
  206. package/dist/esm/internal/ShareSigner.d.ts.map +1 -0
  207. package/dist/esm/internal/ShareSigner.js +219 -0
  208. package/dist/esm/internal/ShareSigner.js.map +1 -0
  209. package/dist/esm/internal/SnapshotBoundary.d.ts +17 -0
  210. package/dist/esm/internal/SnapshotBoundary.d.ts.map +1 -0
  211. package/dist/esm/internal/SnapshotBoundary.js +68 -0
  212. package/dist/esm/internal/SnapshotBoundary.js.map +1 -0
  213. package/dist/esm/test/TestSocket.d.ts +55 -0
  214. package/dist/esm/test/TestSocket.d.ts.map +1 -0
  215. package/dist/esm/test/TestSocket.js +128 -0
  216. package/dist/esm/test/TestSocket.js.map +1 -0
  217. package/dist/esm/test/TestSync.d.ts +54 -0
  218. package/dist/esm/test/TestSync.d.ts.map +1 -0
  219. package/dist/esm/test/TestSync.js +76 -0
  220. package/dist/esm/test/TestSync.js.map +1 -0
  221. package/docs/README.md +156 -0
  222. package/docs/api.md +328 -0
  223. package/docs/concepts/authorization.md +117 -0
  224. package/docs/concepts/branches.md +195 -0
  225. package/docs/concepts/compaction.md +144 -0
  226. package/docs/concepts/replay-then-follow.md +129 -0
  227. package/docs/concepts/scopes-and-cursors.md +135 -0
  228. package/docs/guides/authorize-a-connection.md +174 -0
  229. package/docs/guides/follow-a-run.md +174 -0
  230. package/docs/guides/handle-a-compacted-run.md +158 -0
  231. package/docs/guides/list-workspace-runs.md +120 -0
  232. package/docs/guides/run-a-long-soak.md +80 -0
  233. package/docs/guides/serve-the-read-path.md +146 -0
  234. package/docs/guides/test-a-follower.md +273 -0
  235. package/docs/installation.md +92 -0
  236. package/docs/protocol.md +183 -0
  237. package/docs/quickstart.md +159 -0
  238. package/docs/troubleshooting.md +234 -0
  239. package/package.json +304 -3
  240. package/src/BranchCommands.ts +684 -0
  241. package/src/BranchIds.ts +76 -0
  242. package/src/BranchPresence.ts +512 -0
  243. package/src/BranchProjection.ts +308 -0
  244. package/src/BranchProtocol.ts +411 -0
  245. package/src/BranchRpcs.ts +163 -0
  246. package/src/BranchServer.ts +202 -0
  247. package/src/BranchShare.ts +297 -0
  248. package/src/RunCatalog.ts +308 -0
  249. package/src/SyncAuth.ts +146 -0
  250. package/src/SyncClient.ts +902 -0
  251. package/src/SyncError.ts +147 -0
  252. package/src/SyncPrincipal.ts +115 -0
  253. package/src/SyncProtocol.ts +594 -0
  254. package/src/SyncRpcs.ts +40 -0
  255. package/src/SyncServer.ts +1184 -0
  256. package/src/WorkspaceShare.ts +292 -0
  257. package/src/index.ts +107 -0
  258. package/src/internal/Admission.ts +119 -0
  259. package/src/internal/CauseText.ts +74 -0
  260. package/src/internal/JournalErrorCode.ts +46 -0
  261. package/src/internal/PolicyOptions.ts +97 -0
  262. package/src/internal/ProtocolVersion.ts +28 -0
  263. package/src/internal/ShareSigner.ts +300 -0
  264. package/src/internal/SnapshotBoundary.ts +91 -0
  265. package/src/test/TestSocket.ts +192 -0
  266. package/src/test/TestSync.ts +113 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BranchShare.js","sourceRoot":"","sources":["../../src/BranchShare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,SAAS,MAAM,yBAAyB,CAAA;AACpD,OAAO,KAAK,WAAW,MAAM,2BAA2B,CAAA;AAcxD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IACvC,QAAQ,EAAE,QAAQ;IAClB,YAAY,EAAE,MAAM,CAAC,cAAc;IACnC,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAChD,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;CACvF,CAAC,CAAA;AA4BF;;;;;GAKG;AACH,MAAM,OAAO,WAAY,SAAQ,OAAO,CAAC,OAAO,EAAwB,CAAC,0BAA0B,CAAC;CAAG;AAEvG,MAAM,MAAM,GAAG,WAAW,CAAC,YAAY,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,YAA8B,EAAE,EAAW,EAAE,CACpE,WAAW,CAAC,EAAE,CAAC;IACb,uEAAuE;IACvE,oEAAoE;IACpE,sEAAsE;IACtE,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,+BAA+B,CAAC,CAAC;IAChE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,+BAA+B,CAAC,CAAC;IAClE,GAAG,SAAS;CACb,CAAC,CAAA;AAEJ;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAA6B,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAA;AAEzF;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,6BAA6B,CAAA;AAEjD,uEAAuE;AACvE,MAAM,SAAS,GAAG,CAAC,MAAmB,EAAU,EAAE,CAChD,WAAW,CAAC,cAAc,CAAC;IACzB,WAAW;IACX,MAAM,CAAC,GAAG;IACV,MAAM,CAAC,QAAQ;IACf,MAAM,CAAC,YAAY;IACnB,MAAM,CAAC,MAAM;IACb,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;IACzB,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;CAC3B,CAAC,CAAA;AAEJ;;;;;;;;GAQG;AACH,MAAM,QAAQ,GAAG,CAAC,MAAmB,EAAe,EAAE,CACpD,IAAI,WAAW,CAAC;IACd,GAAG,EAAE,MAAM,CAAC,GAAG;IACf,QAAQ,EAAE,MAAM,CAAC,QAAQ;IACzB,YAAY,EAAE,MAAM,CAAC,YAAY;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU;IAC7B,WAAW,EAAE,MAAM,CAAC,WAAW;CAChC,CAAC,CAAA;AAsBJ;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,OAAgB,EAAqC,EAAE,CAC9E,MAAM,CAAC,GAAG,CACR,WAAW,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,EAC5C,CAAC,IAAI,EAAE,EAAE;IACP,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,QAAQ,CAAC,EAAC,OAAoB;QACvE,iEAAiE;QACjE,oEAAoE;QACpE,mEAAmE;QACnE,mEAAmE;QACnE,8DAA8D;QAC9D,uDAAuD;QACvD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAA;QACjF,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;QAC3F,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAA;QACjD,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,QAAQ,CAAA;QAC1D,IAAI,UAAU,IAAI,cAAc,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,kCAAkC,CAAC,CAAC,CAAA;QACvE,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC;YAC7B,GAAG,EAAE,IAAI,CAAC,SAAS;YACnB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU;YACV,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;SACnE,CAAC,CAAA;QACF,OAAO,IAAI,eAAe,CAAC;YACzB,MAAM;YACN,SAAS,EAAE,KAAK,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;SACvE,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EACtD,UAA2B,EAC3B,OAAyB;QAEzB,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;QACzF,oEAAoE;QACpE,sEAAsE;QACtE,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QAC1C,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,CAAA;QACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC7C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,mDAAmD,CAAC,CAAC,CAAA;QACxF,CAAC;QACD,KAAK,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC;YAC9B,GAAG;YACH,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC;YAC5B,SAAS;YACT,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,OAAO,EAAE,MAAM,CAAC,MAAM;YACtB,SAAS,EAAE,OAAO,CAAC,MAAM;YACzB,OAAO,EAAE,sBAAsB;YAC/B,kEAAkE;YAClE,oEAAoE;YACpE,+BAA+B;YAC/B,KAAK,EAAE,MAAM,CAAC,QAAQ,KAAK,QAAQ;gBACjC,CAAC,CAAC,MAAM,CAAC,IAAI;gBACb,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,4CAA4C,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;SACvF,CAAC,CAAA;QACF,OAAO,MAAM,CAAA;IACf,CAAC,CAAC,CAAA;IAEF,OAAO,WAAW,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;AACzC,CAAC,CACF,CAAA;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAgB,EAAuC,EAAE,CACjF,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;AAE9C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,WAAW,GAA6D,KAAK,CAAC,MAAM,CAC/F,WAAW,EACX,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,6BAA6B,CAAC,CAAA;IACpE,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,6BAA6B,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAA;IACnG,OAAO,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAA;AACrE,CAAC,CAAC,CACH,CAAA","sourcesContent":["/**\n * Share capabilities: the authorization boundary of a shared branch.\n *\n * A share link carries a capability, not a session. The capability names\n * exactly one branch, one access level, and one expiry, and it is signed, so\n * the holder cannot widen it. Every branch operation authorizes through\n * `verify`, which is therefore the single place cross-branch access and\n * expired links are refused.\n *\n * The signature is HMAC-SHA-256 over a length-prefixed encoding of the claims,\n * led by a scheme label. Length prefixes matter: without them a branch id\n * ending in the separator could be re-cut into a different, still-validly-\n * signed claim set. The label matters for the same reason across schemes: a\n * branch capability can never verify as a workspace capability even if one\n * secret is misconfigured into both authorities. Web Crypto is used directly\n * so the same module runs in the browser and on node.\n *\n * A `kid` inside the claims names the key that signed them, so a branch secret\n * rotates the way the workspace secret already did: the new key signs, the\n * retired key stays in the verification keyring, and the share links already\n * out keep working until they expire.\n *\n * @since 0.1.0\n */\n\nimport * as Clock from \"effect/Clock\"\nimport * as Config from \"effect/Config\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\nimport { Access, BranchId, ShareCapability, ShareClaims } from \"./BranchProtocol.ts\"\nimport * as Admission from \"./internal/Admission.ts\"\nimport * as shareSigner from \"./internal/ShareSigner.ts\"\nimport type { SyncError } from \"./SyncError.ts\"\n\n/**\n * The branch and access one authorization request needs.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface AuthorizeRequest {\n readonly branchId: BranchId\n readonly access: Access\n}\n\n/**\n * What a freshly minted capability grants.\n *\n * @category models\n * @since 0.1.0\n */\nexport const MintRequest = Schema.Struct({\n branchId: BranchId,\n capabilityId: Schema.NonEmptyString,\n access: Access,\n ttlMs: Schema.Int.check(Schema.isGreaterThan(0)),\n /** Absolute expiry ceiling for a capability delegated from a parent. */\n maxExpiresAtMs: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))\n})\n\n/**\n * What a freshly minted capability grants.\n *\n * @category models\n * @since 0.1.0\n */\nexport type MintRequest = typeof MintRequest.Type\n\n/**\n * Share capability operations.\n *\n * `mint` fails with a `SyncError` when the Web Crypto signing operation\n * rejects or the absolute expiry ceiling has elapsed; `verify` fails with a\n * `SyncError` when signing rejects or the capability is refused.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service {\n readonly mint: (request: MintRequest) => Effect.Effect<ShareCapability, SyncError>\n readonly verify: (\n capability: ShareCapability,\n request: AuthorizeRequest\n ) => Effect.Effect<ShareClaims, SyncError>\n}\n\n/**\n * The branch share-capability authority.\n *\n * @category services\n * @since 0.1.0\n */\nexport class BranchShare extends Context.Service<BranchShare, Service>()(\"@smthrs/sync/BranchShare\") {}\n\nconst denied = shareSigner.unauthorized\n\n/**\n * Constructs a share authority that mints nothing and trusts nothing.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeNoop = (overrides: Partial<Service> = {}): Service =>\n BranchShare.of({\n // Both operations FAIL. `mint` used to die, which contradicted its own\n // declared `Effect<ShareCapability, SyncError>` and left a consumer\n // wired to this authority unable to tell \"sharing is off\" from a bug.\n mint: () => Effect.fail(denied(\"Branch sharing is unavailable\")),\n verify: () => Effect.fail(denied(\"Branch sharing is unavailable\")),\n ...overrides\n })\n\n/**\n * Provides a share authority that refuses every capability.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerNoop: Layer.Layer<BranchShare> = Layer.succeed(BranchShare, makeNoop())\n\n/**\n * Domain separation: the label leads the signed encoding, so a branch\n * signature can never be replayed as a workspace signature (or any later\n * scheme's) under a shared secret. `WorkspaceShare` has always led with its\n * own label; a one-sided label protects one direction, and the branch side is\n * the one anonymous share-link holders reach.\n */\nconst schemeLabel = \"@smthrs/sync/BranchShare/v2\"\n\n/** Length-prefixed so no two distinct claim sets share an encoding. */\nconst canonical = (claims: ShareClaims): string =>\n shareSigner.lengthPrefixed([\n schemeLabel,\n claims.kid,\n claims.branchId,\n claims.capabilityId,\n claims.access,\n String(claims.issuedAtMs),\n String(claims.expiresAtMs)\n ])\n\n/**\n * The claim fields, copied out of the capability the caller owns.\n *\n * `Schema.Class` instances are not frozen, and `verify` awaits Web Crypto\n * between signing the claims and authorizing them. Reading the caller's object\n * again after the await let an in-process holder of the same instance widen\n * `access` — or move `expiresAtMs` — between the signature that was checked\n * and the checks that follow it.\n */\nconst snapshot = (claims: ShareClaims): ShareClaims =>\n new ShareClaims({\n kid: claims.kid,\n branchId: claims.branchId,\n capabilityId: claims.capabilityId,\n access: claims.access,\n issuedAtMs: claims.issuedAtMs,\n expiresAtMs: claims.expiresAtMs\n })\n\n/**\n * One named signing key. The secret is `Redacted` so a keyring never renders\n * it into logs or inspection output.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type Key = shareSigner.Key\n\n/**\n * A keyring: the key that signs new capabilities plus every key still\n * accepted for verification. Rotation adds a new active key and keeps the\n * retired one in `keys` until its outstanding links expire, so rotating the\n * branch secret no longer breaks every share link that is already out.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type Keyring = shareSigner.Keyring\n\n/**\n * Constructs the HMAC-SHA-256 share authority over a keyring.\n *\n * Every key is imported up front, so a misconfigured keyring — an unknown\n * active kid, a duplicate kid, or a key Web Crypto refuses — fails at\n * construction rather than at the first request. Secrets enter as `Redacted`\n * values: an authority never holds a plain string that a log, a span, or an\n * inspection of the keyring could render.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeHmac = (keyring: Keyring): Effect.Effect<Service, SyncError> =>\n Effect.map(\n shareSigner.importKeyring(keyring, \"branch\"),\n (ring) => {\n const mint = Effect.fn(\"BranchShare.mint\")(function*(request: MintRequest) {\n // Decoded, not read: the declared parameter type admits an empty\n // branch id, an empty capability id and a non-positive ttl that the\n // schema forbids, and reading them straight into `new ShareClaims`\n // turned a caller's bad argument into a defect out of an operation\n // whose type promises a `SyncError`. A `ttlMs` of 0 minted an\n // already-expired capability instead of being refused.\n const admitted = yield* Admission.decode(MintRequest, request, \"invalid_request\")\n yield* Effect.annotateCurrentSpan({ branchId: admitted.branchId, access: admitted.access })\n const issuedAtMs = yield* Clock.currentTimeMillis\n const maxExpiresAtMs = admitted.maxExpiresAtMs ?? Infinity\n if (issuedAtMs >= maxExpiresAtMs) {\n return yield* Effect.fail(denied(\"The share capability has expired\"))\n }\n const claims = new ShareClaims({\n kid: ring.activeKid,\n branchId: admitted.branchId,\n capabilityId: admitted.capabilityId,\n access: admitted.access,\n issuedAtMs,\n expiresAtMs: Math.min(issuedAtMs + admitted.ttlMs, maxExpiresAtMs)\n })\n return new ShareCapability({\n claims,\n signature: yield* shareSigner.signHmac(ring.active, canonical(claims))\n })\n })\n\n const verify = Effect.fn(\"BranchShare.verify\")(function*(\n capability: ShareCapability,\n request: AuthorizeRequest\n ) {\n yield* Effect.annotateCurrentSpan({ branchId: request.branchId, access: request.access })\n // Everything authorized below is read from these locals, never from\n // the caller's objects, which may change while Web Crypto is awaited.\n const claims = snapshot(capability.claims)\n const signature = capability.signature\n const branchId = request.branchId\n const key = ring.verification.get(claims.kid)\n if (key === undefined) {\n return yield* Effect.fail(denied(\"The share capability names an unknown signing key\"))\n }\n yield* shareSigner.verifyClaims({\n key,\n canonical: canonical(claims),\n signature,\n expiresAtMs: claims.expiresAtMs,\n granted: claims.access,\n requested: request.access,\n subject: \"The share capability\",\n // The one check this authority adds, run after the signature so a\n // forged claim set is refused as a bad signature and never reported\n // back as the branch it names.\n scope: claims.branchId === branchId\n ? Effect.void\n : Effect.fail(denied(`The share capability is scoped to branch ${claims.branchId}`))\n })\n return claims\n })\n\n return BranchShare.of({ mint, verify })\n }\n )\n\n/**\n * Provides the HMAC-SHA-256 share authority over a keyring.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerHmac = (keyring: Keyring): Layer.Layer<BranchShare, SyncError> =>\n Layer.effect(BranchShare, makeHmac(keyring))\n\n/**\n * Provides the branch share authority from configuration: the secret from\n * `SMITHERS_SYNC_BRANCH_SECRET` (read as `Redacted`, never logged) and the key\n * name from `SMITHERS_SYNC_BRANCH_KEY_ID`, defaulting to `primary`. Rotation\n * beyond one configured key uses {@link layerHmac} with an explicit keyring.\n *\n * The names are the branch authority's own, not the workspace's: the scheme\n * label makes one secret in both authorities survivable, not advisable, and a\n * deployment that rotates one credential should not silently invalidate the\n * other's outstanding capabilities.\n *\n * There is deliberately no default secret: a deployment that configures\n * neither name fails to construct the authority, and every branch operation\n * stays closed.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerConfig: Layer.Layer<BranchShare, SyncError | Config.ConfigError> = Layer.effect(\n BranchShare,\n Effect.gen(function*() {\n const secret = yield* Config.Redacted(\"SMITHERS_SYNC_BRANCH_SECRET\")\n const kid = yield* Config.String(\"SMITHERS_SYNC_BRANCH_KEY_ID\").pipe(Config.withDefault(\"primary\"))\n return yield* makeHmac({ activeKid: kid, keys: [{ kid, secret }] })\n })\n)\n"]}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Workspace run enumeration for synchronization.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import type { JournalEvent } from "@smthrs/journal";
7
+ import * as Context from "effect/Context";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Layer from "effect/Layer";
10
+ import type * as Scope from "effect/Scope";
11
+ import * as Stream from "effect/Stream";
12
+ import type { SyncError } from "./SyncError.ts";
13
+ /**
14
+ * Workspace run enumeration operations.
15
+ *
16
+ * `list` is the AUTHORITATIVE run set and `changes` is only a low-latency
17
+ * wake: a subscriber that misses an announcement re-lists rather than losing
18
+ * state, and a subscriber whose covered set must stay accurate re-lists on a
19
+ * cadence of its own. Every implementation returns a fresh array from `list`,
20
+ * so a caller may retain and sort what it receives without disturbing another
21
+ * reader.
22
+ *
23
+ * `list` names each run AT MOST ONCE. Every implementation here deduplicates,
24
+ * and `SyncServer` deduplicates again at the seam rather than trusting a
25
+ * host's catalog, because both fan-out paths key a run's served position by
26
+ * run id: a run named twice is read twice from the same position and its
27
+ * entries are served twice.
28
+ *
29
+ * The catalog is supplied by the host rather than derived from the journal:
30
+ * `@smthrs/journal` has no workspace-wide list or watch contract, and
31
+ * `@smthrs/engine-store` owns the durable run set that {@link makePolling}
32
+ * reads.
33
+ *
34
+ * @category models
35
+ * @since 0.1.0
36
+ */
37
+ export interface Service {
38
+ readonly list: Effect.Effect<ReadonlyArray<JournalEvent.RunId>>;
39
+ readonly changes: Stream.Stream<JournalEvent.RunId>;
40
+ }
41
+ declare const RunCatalog_base: Context.ServiceClass<RunCatalog, "@smthrs/sync/RunCatalog", Service>;
42
+ /**
43
+ * Host-provided workspace run catalog.
44
+ *
45
+ * @category services
46
+ * @since 0.1.0
47
+ */
48
+ export declare class RunCatalog extends RunCatalog_base {
49
+ }
50
+ /**
51
+ * Provides an immutable run catalog.
52
+ *
53
+ * `list` answers with a fresh array each time, matching {@link makeMemory} and
54
+ * {@link makePolling}. Handing every caller the same instance let one reader
55
+ * sort or splice the catalog in place for every other reader, which is a
56
+ * defect a `ReadonlyArray` type hides from TypeScript and not from JavaScript.
57
+ *
58
+ * @category layers
59
+ * @since 0.1.0
60
+ */
61
+ export declare const layerStatic: (ids: ReadonlyArray<JournalEvent.RunId>) => Layer.Layer<RunCatalog>;
62
+ /**
63
+ * Announcements a stalled {@link Service.changes} subscriber may fall behind
64
+ * by before the oldest are dropped.
65
+ *
66
+ * `changes` is a notification feed, not a log: a follower reads it to learn
67
+ * that the workspace gained a run and then reads the run itself. Retaining an
68
+ * unbounded backlog for a subscriber that has stopped pulling — a disconnected
69
+ * tab whose socket has not timed out yet, a follower blocked behind a slow
70
+ * consumer — is therefore pure cost, and the workspace's whole run history is
71
+ * the upper bound on it. A subscriber that falls further behind than this
72
+ * bound loses the oldest announcements; `list` still names every run, so a
73
+ * follower that reconnects or re-lists converges.
74
+ *
75
+ * @category constants
76
+ * @since 0.1.0
77
+ */
78
+ export declare const defaultChangesCapacity = 1024;
79
+ /**
80
+ * Options for the in-memory run catalog.
81
+ *
82
+ * @category models
83
+ * @since 0.1.0
84
+ */
85
+ export interface MemoryOptions {
86
+ /**
87
+ * Announcements a stalled subscriber may fall behind by. Defaults to
88
+ * {@link defaultChangesCapacity}.
89
+ */
90
+ readonly changesCapacity?: number | undefined;
91
+ }
92
+ /**
93
+ * Constructs a mutable in-memory run catalog.
94
+ *
95
+ * A run is published exactly once, when it is first registered. The
96
+ * announcement feed slides: registering never waits on a stalled subscriber,
97
+ * and never grows the process on its behalf.
98
+ *
99
+ * Fails with `invalid_request` when `changesCapacity` is not a positive safe
100
+ * integer, rather than handing it to `PubSub.sliding` as a capacity.
101
+ *
102
+ * @category constructors
103
+ * @since 0.1.0
104
+ */
105
+ export declare const makeMemory: (options?: MemoryOptions) => Effect.Effect<{
106
+ readonly catalog: Service;
107
+ readonly register: (runId: JournalEvent.RunId) => Effect.Effect<void>;
108
+ }, SyncError>;
109
+ /**
110
+ * How often {@link makePolling} re-reads its source when the caller names no
111
+ * interval, in milliseconds.
112
+ *
113
+ * A workspace follower learns of a new run within one interval of it being
114
+ * created, and the read costs one bounded query per interval per composition,
115
+ * not per subscriber. A second is short enough that a run appears in a UI
116
+ * while the operator is still looking at the command that started it, and
117
+ * long enough that an idle workspace is close to free.
118
+ *
119
+ * @category constants
120
+ * @since 0.1.0
121
+ */
122
+ export declare const defaultPollIntervalMs = 1000;
123
+ /**
124
+ * Options for the polling run catalog.
125
+ *
126
+ * @category models
127
+ * @since 0.1.0
128
+ */
129
+ export interface PollingOptions<E, R> {
130
+ /**
131
+ * Reads the workspace's current run set. `@smthrs/engine-store` supplies
132
+ * the durable one over `flows_runs`; anything that answers the same
133
+ * question works, and the catalog holds no assumption about which.
134
+ */
135
+ readonly read: Effect.Effect<ReadonlyArray<JournalEvent.RunId>, E, R>;
136
+ /** Milliseconds between reads. Defaults to {@link defaultPollIntervalMs}. */
137
+ readonly intervalMs?: number | undefined;
138
+ /**
139
+ * Announcements a stalled subscriber may fall behind by. Defaults to
140
+ * {@link defaultChangesCapacity}.
141
+ */
142
+ readonly changesCapacity?: number | undefined;
143
+ }
144
+ /**
145
+ * Constructs a run catalog that polls a durable source.
146
+ *
147
+ * Polling, not waking. rc.0 has no cross-process event delivery, so a
148
+ * follower learns of a run another engine created by asking the workspace
149
+ * again, and the interval is the whole of the policy.
150
+ *
151
+ * The catalog primes itself before it returns, so the first read a follower
152
+ * makes is already current and a workspace that cannot be read at all fails
153
+ * the composition instead of serving an empty run set. After that a failed
154
+ * read is a warning and nothing else: the previous view stands, the interval
155
+ * holds, and no subscription attached to the catalog is torn down for it.
156
+ * The warning is written once per outage, with the first failure's cause, and
157
+ * one info line marks the read that ends it; the polls in between are silent.
158
+ *
159
+ * A run is announced once, when it first appears. A run the read stops
160
+ * naming, which is what retention collecting it looks like, leaves the view;
161
+ * `list` is the workspace's run set, not a log of every run it ever had.
162
+ *
163
+ * `list` answers with a fresh array each time, matching {@link layerStatic} and
164
+ * {@link makeMemory}. Handing back the stored snapshot let a caller sort or
165
+ * splice the authoritative run set in place under the poll fiber that owns it,
166
+ * which is a defect a `ReadonlyArray` type hides from TypeScript and not from
167
+ * JavaScript.
168
+ *
169
+ * The poll fiber belongs to the caller's scope. Closing the scope stops it.
170
+ *
171
+ * Both numeric options are validated here, before the first read: an
172
+ * `intervalMs` of `NaN` or zero reached `Effect.delay` as the whole of the
173
+ * poll cadence, which is a loop with no interval rather than a refusal.
174
+ *
175
+ * @category constructors
176
+ * @since 0.1.0
177
+ */
178
+ export declare const makePolling: <E, R>(options: PollingOptions<E, R>) => Effect.Effect<Service, E | SyncError, R | Scope.Scope>;
179
+ /**
180
+ * Provides a run catalog that polls a durable source. The poll fiber lives as
181
+ * long as the layer's scope.
182
+ *
183
+ * @category layers
184
+ * @since 0.1.0
185
+ */
186
+ export declare const layerPolling: <E, R>(options: PollingOptions<E, R>) => Layer.Layer<RunCatalog, E | SyncError, R>;
187
+ /**
188
+ * Provides the mutable in-memory run catalog, matching how every other
189
+ * implementation here is provided. Fails with `invalid_request` when
190
+ * `changesCapacity` is not a positive safe integer.
191
+ *
192
+ * The layer provides the catalog alone. A composition that also registers runs
193
+ * builds {@link makeMemory} itself and keeps its `register`.
194
+ *
195
+ * @category layers
196
+ * @since 1.0.0-rc.0
197
+ */
198
+ export declare const layerMemory: (options?: MemoryOptions) => Layer.Layer<RunCatalog, SyncError>;
199
+ /**
200
+ * Provides an empty run catalog.
201
+ *
202
+ * @category layers
203
+ * @since 0.1.0
204
+ */
205
+ export declare const layerNoop: Layer.Layer<RunCatalog>;
206
+ export {};
207
+ //# sourceMappingURL=RunCatalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RunCatalog.d.ts","sourceRoot":"","sources":["../../src/RunCatalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Workspace run enumeration for synchronization.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import * as Context from "effect/Context";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Layer from "effect/Layer";
9
+ import * as PubSub from "effect/PubSub";
10
+ import * as Ref from "effect/Ref";
11
+ import * as Stream from "effect/Stream";
12
+ import { positiveInt } from "./internal/PolicyOptions.js";
13
+ /**
14
+ * Host-provided workspace run catalog.
15
+ *
16
+ * @category services
17
+ * @since 0.1.0
18
+ */
19
+ export class RunCatalog extends Context.Service()("@smthrs/sync/RunCatalog") {
20
+ }
21
+ /**
22
+ * Provides an immutable run catalog.
23
+ *
24
+ * `list` answers with a fresh array each time, matching {@link makeMemory} and
25
+ * {@link makePolling}. Handing every caller the same instance let one reader
26
+ * sort or splice the catalog in place for every other reader, which is a
27
+ * defect a `ReadonlyArray` type hides from TypeScript and not from JavaScript.
28
+ *
29
+ * @category layers
30
+ * @since 0.1.0
31
+ */
32
+ export const layerStatic = (ids) => {
33
+ const known = Array.from(new Set(ids));
34
+ return Layer.succeed(RunCatalog, RunCatalog.of({
35
+ list: Effect.sync(() => [...known]),
36
+ changes: Stream.empty
37
+ }));
38
+ };
39
+ /**
40
+ * Announcements a stalled {@link Service.changes} subscriber may fall behind
41
+ * by before the oldest are dropped.
42
+ *
43
+ * `changes` is a notification feed, not a log: a follower reads it to learn
44
+ * that the workspace gained a run and then reads the run itself. Retaining an
45
+ * unbounded backlog for a subscriber that has stopped pulling — a disconnected
46
+ * tab whose socket has not timed out yet, a follower blocked behind a slow
47
+ * consumer — is therefore pure cost, and the workspace's whole run history is
48
+ * the upper bound on it. A subscriber that falls further behind than this
49
+ * bound loses the oldest announcements; `list` still names every run, so a
50
+ * follower that reconnects or re-lists converges.
51
+ *
52
+ * @category constants
53
+ * @since 0.1.0
54
+ */
55
+ export const defaultChangesCapacity = 1024;
56
+ /**
57
+ * Constructs a mutable in-memory run catalog.
58
+ *
59
+ * A run is published exactly once, when it is first registered. The
60
+ * announcement feed slides: registering never waits on a stalled subscriber,
61
+ * and never grows the process on its behalf.
62
+ *
63
+ * Fails with `invalid_request` when `changesCapacity` is not a positive safe
64
+ * integer, rather than handing it to `PubSub.sliding` as a capacity.
65
+ *
66
+ * @category constructors
67
+ * @since 0.1.0
68
+ */
69
+ export const makeMemory = (options = {}) => Effect.gen(function* () {
70
+ const known = new Set();
71
+ const changes = yield* PubSub.sliding(yield* positiveInt("RunCatalog.MemoryOptions.changesCapacity", options.changesCapacity, defaultChangesCapacity));
72
+ return {
73
+ catalog: RunCatalog.of({
74
+ list: Effect.sync(() => Array.from(known)).pipe(Effect.withSpan("RunCatalog.list")),
75
+ changes: Stream.fromPubSub(changes)
76
+ }),
77
+ register: Effect.fn("RunCatalog.register")((runId) => Effect.suspend(() => {
78
+ if (known.has(runId))
79
+ return Effect.void;
80
+ known.add(runId);
81
+ return PubSub.publish(changes, runId).pipe(Effect.asVoid);
82
+ }))
83
+ };
84
+ });
85
+ /**
86
+ * How often {@link makePolling} re-reads its source when the caller names no
87
+ * interval, in milliseconds.
88
+ *
89
+ * A workspace follower learns of a new run within one interval of it being
90
+ * created, and the read costs one bounded query per interval per composition,
91
+ * not per subscriber. A second is short enough that a run appears in a UI
92
+ * while the operator is still looking at the command that started it, and
93
+ * long enough that an idle workspace is close to free.
94
+ *
95
+ * @category constants
96
+ * @since 0.1.0
97
+ */
98
+ export const defaultPollIntervalMs = 1000;
99
+ /**
100
+ * Constructs a run catalog that polls a durable source.
101
+ *
102
+ * Polling, not waking. rc.0 has no cross-process event delivery, so a
103
+ * follower learns of a run another engine created by asking the workspace
104
+ * again, and the interval is the whole of the policy.
105
+ *
106
+ * The catalog primes itself before it returns, so the first read a follower
107
+ * makes is already current and a workspace that cannot be read at all fails
108
+ * the composition instead of serving an empty run set. After that a failed
109
+ * read is a warning and nothing else: the previous view stands, the interval
110
+ * holds, and no subscription attached to the catalog is torn down for it.
111
+ * The warning is written once per outage, with the first failure's cause, and
112
+ * one info line marks the read that ends it; the polls in between are silent.
113
+ *
114
+ * A run is announced once, when it first appears. A run the read stops
115
+ * naming, which is what retention collecting it looks like, leaves the view;
116
+ * `list` is the workspace's run set, not a log of every run it ever had.
117
+ *
118
+ * `list` answers with a fresh array each time, matching {@link layerStatic} and
119
+ * {@link makeMemory}. Handing back the stored snapshot let a caller sort or
120
+ * splice the authoritative run set in place under the poll fiber that owns it,
121
+ * which is a defect a `ReadonlyArray` type hides from TypeScript and not from
122
+ * JavaScript.
123
+ *
124
+ * The poll fiber belongs to the caller's scope. Closing the scope stops it.
125
+ *
126
+ * Both numeric options are validated here, before the first read: an
127
+ * `intervalMs` of `NaN` or zero reached `Effect.delay` as the whole of the
128
+ * poll cadence, which is a loop with no interval rather than a refusal.
129
+ *
130
+ * @category constructors
131
+ * @since 0.1.0
132
+ */
133
+ export const makePolling = (options) => Effect.gen(function* () {
134
+ const intervalMs = yield* positiveInt("RunCatalog.PollingOptions.intervalMs", options.intervalMs, defaultPollIntervalMs);
135
+ const changes = yield* PubSub.sliding(yield* positiveInt("RunCatalog.PollingOptions.changesCapacity", options.changesCapacity, defaultChangesCapacity));
136
+ const snapshot = yield* Ref.make([]);
137
+ const refresh = Effect.gen(function* () {
138
+ const read = yield* options.read;
139
+ const next = Array.from(new Set(read));
140
+ const previous = new Set(yield* Ref.get(snapshot));
141
+ yield* Ref.set(snapshot, next);
142
+ for (const runId of next) {
143
+ // Publishing never waits on a stalled subscriber, and never grows the
144
+ // process on its behalf: the feed slides.
145
+ if (!previous.has(runId))
146
+ yield* PubSub.publish(changes, runId);
147
+ }
148
+ });
149
+ yield* refresh;
150
+ // The log follows the outage's transitions, not the poll cadence. With
151
+ // the default interval a workspace that stays unreadable would otherwise
152
+ // write one identical warning per second per catalog for as long as it
153
+ // lasts, and the line that says it ended would never be written.
154
+ const consecutiveFailures = yield* Ref.make(0);
155
+ const poll = refresh.pipe(Effect.matchCauseEffect({
156
+ onFailure: (cause) => Effect.flatMap(Ref.updateAndGet(consecutiveFailures, (count) => count + 1), (count) => count === 1
157
+ ? Effect.logWarning("The workspace run catalog could not be read; the previous view stands", cause)
158
+ : Effect.void),
159
+ onSuccess: () => Effect.flatMap(Ref.getAndSet(consecutiveFailures, 0), (count) => count === 0
160
+ ? Effect.void
161
+ : Effect.logInfo(`The workspace run catalog is readable again after ${count} failed reads`))
162
+ }));
163
+ yield* poll.pipe(Effect.delay(intervalMs), Effect.forever, Effect.forkScoped);
164
+ return RunCatalog.of({
165
+ list: Effect.map(Ref.get(snapshot), (ids) => [...ids]).pipe(Effect.withSpan("RunCatalog.list")),
166
+ changes: Stream.fromPubSub(changes)
167
+ });
168
+ });
169
+ /**
170
+ * Provides a run catalog that polls a durable source. The poll fiber lives as
171
+ * long as the layer's scope.
172
+ *
173
+ * @category layers
174
+ * @since 0.1.0
175
+ */
176
+ export const layerPolling = (options) => Layer.effect(RunCatalog, makePolling(options));
177
+ /**
178
+ * Provides the mutable in-memory run catalog, matching how every other
179
+ * implementation here is provided. Fails with `invalid_request` when
180
+ * `changesCapacity` is not a positive safe integer.
181
+ *
182
+ * The layer provides the catalog alone. A composition that also registers runs
183
+ * builds {@link makeMemory} itself and keeps its `register`.
184
+ *
185
+ * @category layers
186
+ * @since 1.0.0-rc.0
187
+ */
188
+ export const layerMemory = (options = {}) => Layer.effect(RunCatalog, Effect.map(makeMemory(options), ({ catalog }) => catalog));
189
+ /**
190
+ * Provides an empty run catalog.
191
+ *
192
+ * @category layers
193
+ * @since 0.1.0
194
+ */
195
+ export const layerNoop = layerStatic([]);
196
+ //# sourceMappingURL=RunCatalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RunCatalog.js","sourceRoot":"","sources":["../../src/RunCatalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Workspace run enumeration for synchronization.\n *\n * @since 0.1.0\n */\n\nimport type { JournalEvent } from \"@smthrs/journal\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as PubSub from \"effect/PubSub\"\nimport * as Ref from \"effect/Ref\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport { positiveInt } from \"./internal/PolicyOptions.ts\"\nimport type { SyncError } from \"./SyncError.ts\"\n\n/**\n * Workspace run enumeration operations.\n *\n * `list` is the AUTHORITATIVE run set and `changes` is only a low-latency\n * wake: a subscriber that misses an announcement re-lists rather than losing\n * state, and a subscriber whose covered set must stay accurate re-lists on a\n * cadence of its own. Every implementation returns a fresh array from `list`,\n * so a caller may retain and sort what it receives without disturbing another\n * reader.\n *\n * `list` names each run AT MOST ONCE. Every implementation here deduplicates,\n * and `SyncServer` deduplicates again at the seam rather than trusting a\n * host's catalog, because both fan-out paths key a run's served position by\n * run id: a run named twice is read twice from the same position and its\n * entries are served twice.\n *\n * The catalog is supplied by the host rather than derived from the journal:\n * `@smthrs/journal` has no workspace-wide list or watch contract, and\n * `@smthrs/engine-store` owns the durable run set that {@link makePolling}\n * reads.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service {\n readonly list: Effect.Effect<ReadonlyArray<JournalEvent.RunId>>\n readonly changes: Stream.Stream<JournalEvent.RunId>\n}\n\n/**\n * Host-provided workspace run catalog.\n *\n * @category services\n * @since 0.1.0\n */\nexport class RunCatalog extends Context.Service<RunCatalog, Service>()(\"@smthrs/sync/RunCatalog\") {}\n\n/**\n * Provides an immutable run catalog.\n *\n * `list` answers with a fresh array each time, matching {@link makeMemory} and\n * {@link makePolling}. Handing every caller the same instance let one reader\n * sort or splice the catalog in place for every other reader, which is a\n * defect a `ReadonlyArray` type hides from TypeScript and not from JavaScript.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerStatic = (\n ids: ReadonlyArray<JournalEvent.RunId>\n): Layer.Layer<RunCatalog> => {\n const known = Array.from(new Set(ids))\n return Layer.succeed(\n RunCatalog,\n RunCatalog.of({\n list: Effect.sync(() => [...known]),\n changes: Stream.empty\n })\n )\n}\n\n/**\n * Announcements a stalled {@link Service.changes} subscriber may fall behind\n * by before the oldest are dropped.\n *\n * `changes` is a notification feed, not a log: a follower reads it to learn\n * that the workspace gained a run and then reads the run itself. Retaining an\n * unbounded backlog for a subscriber that has stopped pulling — a disconnected\n * tab whose socket has not timed out yet, a follower blocked behind a slow\n * consumer — is therefore pure cost, and the workspace's whole run history is\n * the upper bound on it. A subscriber that falls further behind than this\n * bound loses the oldest announcements; `list` still names every run, so a\n * follower that reconnects or re-lists converges.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const defaultChangesCapacity = 1024\n\n/**\n * Options for the in-memory run catalog.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface MemoryOptions {\n /**\n * Announcements a stalled subscriber may fall behind by. Defaults to\n * {@link defaultChangesCapacity}.\n */\n readonly changesCapacity?: number | undefined\n}\n\n/**\n * Constructs a mutable in-memory run catalog.\n *\n * A run is published exactly once, when it is first registered. The\n * announcement feed slides: registering never waits on a stalled subscriber,\n * and never grows the process on its behalf.\n *\n * Fails with `invalid_request` when `changesCapacity` is not a positive safe\n * integer, rather than handing it to `PubSub.sliding` as a capacity.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeMemory = (options: MemoryOptions = {}): Effect.Effect<{\n readonly catalog: Service\n readonly register: (runId: JournalEvent.RunId) => Effect.Effect<void>\n}, SyncError> =>\n Effect.gen(function*() {\n const known = new Set<JournalEvent.RunId>()\n const changes = yield* PubSub.sliding<JournalEvent.RunId>(\n yield* positiveInt(\"RunCatalog.MemoryOptions.changesCapacity\", options.changesCapacity, defaultChangesCapacity)\n )\n return {\n catalog: RunCatalog.of({\n list: Effect.sync(() => Array.from(known)).pipe(Effect.withSpan(\"RunCatalog.list\")),\n changes: Stream.fromPubSub(changes)\n }),\n register: Effect.fn(\"RunCatalog.register\")((runId) =>\n Effect.suspend(() => {\n if (known.has(runId)) return Effect.void\n known.add(runId)\n return PubSub.publish(changes, runId).pipe(Effect.asVoid)\n })\n )\n }\n })\n\n/**\n * How often {@link makePolling} re-reads its source when the caller names no\n * interval, in milliseconds.\n *\n * A workspace follower learns of a new run within one interval of it being\n * created, and the read costs one bounded query per interval per composition,\n * not per subscriber. A second is short enough that a run appears in a UI\n * while the operator is still looking at the command that started it, and\n * long enough that an idle workspace is close to free.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const defaultPollIntervalMs = 1000\n\n/**\n * Options for the polling run catalog.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface PollingOptions<E, R> {\n /**\n * Reads the workspace's current run set. `@smthrs/engine-store` supplies\n * the durable one over `flows_runs`; anything that answers the same\n * question works, and the catalog holds no assumption about which.\n */\n readonly read: Effect.Effect<ReadonlyArray<JournalEvent.RunId>, E, R>\n /** Milliseconds between reads. Defaults to {@link defaultPollIntervalMs}. */\n readonly intervalMs?: number | undefined\n /**\n * Announcements a stalled subscriber may fall behind by. Defaults to\n * {@link defaultChangesCapacity}.\n */\n readonly changesCapacity?: number | undefined\n}\n\n/**\n * Constructs a run catalog that polls a durable source.\n *\n * Polling, not waking. rc.0 has no cross-process event delivery, so a\n * follower learns of a run another engine created by asking the workspace\n * again, and the interval is the whole of the policy.\n *\n * The catalog primes itself before it returns, so the first read a follower\n * makes is already current and a workspace that cannot be read at all fails\n * the composition instead of serving an empty run set. After that a failed\n * read is a warning and nothing else: the previous view stands, the interval\n * holds, and no subscription attached to the catalog is torn down for it.\n * The warning is written once per outage, with the first failure's cause, and\n * one info line marks the read that ends it; the polls in between are silent.\n *\n * A run is announced once, when it first appears. A run the read stops\n * naming, which is what retention collecting it looks like, leaves the view;\n * `list` is the workspace's run set, not a log of every run it ever had.\n *\n * `list` answers with a fresh array each time, matching {@link layerStatic} and\n * {@link makeMemory}. Handing back the stored snapshot let a caller sort or\n * splice the authoritative run set in place under the poll fiber that owns it,\n * which is a defect a `ReadonlyArray` type hides from TypeScript and not from\n * JavaScript.\n *\n * The poll fiber belongs to the caller's scope. Closing the scope stops it.\n *\n * Both numeric options are validated here, before the first read: an\n * `intervalMs` of `NaN` or zero reached `Effect.delay` as the whole of the\n * poll cadence, which is a loop with no interval rather than a refusal.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makePolling = <E, R>(\n options: PollingOptions<E, R>\n): Effect.Effect<Service, E | SyncError, R | Scope.Scope> =>\n Effect.gen(function*() {\n const intervalMs = yield* positiveInt(\n \"RunCatalog.PollingOptions.intervalMs\",\n options.intervalMs,\n defaultPollIntervalMs\n )\n const changes = yield* PubSub.sliding<JournalEvent.RunId>(\n yield* positiveInt(\"RunCatalog.PollingOptions.changesCapacity\", options.changesCapacity, defaultChangesCapacity)\n )\n const snapshot = yield* Ref.make<ReadonlyArray<JournalEvent.RunId>>([])\n\n const refresh = Effect.gen(function*() {\n const read = yield* options.read\n const next = Array.from(new Set(read))\n const previous = new Set(yield* Ref.get(snapshot))\n yield* Ref.set(snapshot, next)\n for (const runId of next) {\n // Publishing never waits on a stalled subscriber, and never grows the\n // process on its behalf: the feed slides.\n if (!previous.has(runId)) yield* PubSub.publish(changes, runId)\n }\n })\n\n yield* refresh\n // The log follows the outage's transitions, not the poll cadence. With\n // the default interval a workspace that stays unreadable would otherwise\n // write one identical warning per second per catalog for as long as it\n // lasts, and the line that says it ended would never be written.\n const consecutiveFailures = yield* Ref.make(0)\n const poll = refresh.pipe(\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Effect.flatMap(Ref.updateAndGet(consecutiveFailures, (count) => count + 1), (count) =>\n count === 1\n ? Effect.logWarning(\"The workspace run catalog could not be read; the previous view stands\", cause)\n : Effect.void),\n onSuccess: () =>\n Effect.flatMap(Ref.getAndSet(consecutiveFailures, 0), (count) =>\n count === 0\n ? Effect.void\n : Effect.logInfo(`The workspace run catalog is readable again after ${count} failed reads`))\n })\n )\n yield* poll.pipe(\n Effect.delay(intervalMs),\n Effect.forever,\n Effect.forkScoped\n )\n\n return RunCatalog.of({\n list: Effect.map(Ref.get(snapshot), (ids) => [...ids]).pipe(Effect.withSpan(\"RunCatalog.list\")),\n changes: Stream.fromPubSub(changes)\n })\n })\n\n/**\n * Provides a run catalog that polls a durable source. The poll fiber lives as\n * long as the layer's scope.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerPolling = <E, R>(\n options: PollingOptions<E, R>\n): Layer.Layer<RunCatalog, E | SyncError, R> => Layer.effect(RunCatalog, makePolling(options))\n\n/**\n * Provides the mutable in-memory run catalog, matching how every other\n * implementation here is provided. Fails with `invalid_request` when\n * `changesCapacity` is not a positive safe integer.\n *\n * The layer provides the catalog alone. A composition that also registers runs\n * builds {@link makeMemory} itself and keeps its `register`.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerMemory = (options: MemoryOptions = {}): Layer.Layer<RunCatalog, SyncError> =>\n Layer.effect(RunCatalog, Effect.map(makeMemory(options), ({ catalog }) => catalog))\n\n/**\n * Provides an empty run catalog.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerNoop: Layer.Layer<RunCatalog> = layerStatic([])\n"]}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The sync read path's connection authentication: implementations of the
3
+ * `SyncRpcs.SyncAuth` RPC middleware.
4
+ *
5
+ * A connection presents a {@link WorkspaceShare.WorkspaceCapability} in the
6
+ * {@link capabilityHeader} request header. {@link layer} verifies it against
7
+ * the workspace's {@link WorkspaceShare.WorkspaceShare} authority and runs
8
+ * the handler as the workspace principal. A request with no header runs as
9
+ * the anonymous principal — branch share capabilities carried in request
10
+ * payloads still authorize branch reads — and the server refuses anonymous
11
+ * access to every non-branch run. A request whose header is present but
12
+ * malformed, forged, expired, or signed by an unknown key is refused
13
+ * outright: presence of a credential is a claim, and a false claim is never
14
+ * downgraded to anonymity.
15
+ *
16
+ * There is deliberately no trusted pass-through implementation here: the only
17
+ * sanctioned bypass is an in-process caller providing `SyncPrincipal` itself,
18
+ * never a transport.
19
+ *
20
+ * @since 0.1.0
21
+ */
22
+ import * as Effect from "effect/Effect";
23
+ import * as Layer from "effect/Layer";
24
+ import * as RpcMiddleware from "effect/unstable/rpc/RpcMiddleware";
25
+ import { SyncError } from "./SyncError.ts";
26
+ import { SyncAuth } from "./SyncRpcs.ts";
27
+ import * as WorkspaceShare from "./WorkspaceShare.ts";
28
+ /**
29
+ * The request header a connection presents its workspace capability in.
30
+ *
31
+ * @category constants
32
+ * @since 0.1.0
33
+ */
34
+ export declare const capabilityHeader = "flows-sync-workspace";
35
+ /**
36
+ * Renders a workspace capability as its header value: unpadded base64url of
37
+ * the schema-encoded capability JSON.
38
+ *
39
+ * @category encoding
40
+ * @since 0.1.0
41
+ */
42
+ export declare const encodeCapability: (capability: WorkspaceShare.WorkspaceCapability) => Effect.Effect<string, SyncError>;
43
+ /**
44
+ * Decodes a capability header value. Every malformation folds to the same
45
+ * `unauthorized` refusal so the header is not a parsing oracle.
46
+ *
47
+ * @category encoding
48
+ * @since 0.1.0
49
+ */
50
+ export declare const decodeCapability: (value: string) => Effect.Effect<WorkspaceShare.WorkspaceCapability, SyncError>;
51
+ /**
52
+ * Provides the workspace-capability authentication middleware: no header
53
+ * runs the handler as anonymous, a verified header runs it as the workspace
54
+ * principal, and anything else refuses the request. Verification faults
55
+ * (Web Crypto failing) propagate as their own error rather than as a
56
+ * refusal.
57
+ *
58
+ * @category layers
59
+ * @since 0.1.0
60
+ */
61
+ export declare const layer: Layer.Layer<SyncAuth, never, WorkspaceShare.WorkspaceShare>;
62
+ /**
63
+ * Stamps every outgoing request from a generated RPC client with the given
64
+ * workspace capability.
65
+ *
66
+ * @category layers
67
+ * @since 0.1.0
68
+ */
69
+ export declare const layerClient: (capability: WorkspaceShare.WorkspaceCapability) => Layer.Layer<RpcMiddleware.ForClient<SyncAuth>, SyncError>;
70
+ //# sourceMappingURL=SyncAuth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncAuth.d.ts","sourceRoot":"","sources":["../../src/SyncAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAIrC,OAAO,KAAK,aAAa,MAAM,mCAAmC,CAAA;AAGlE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,yBAAyB,CAAA;AAItD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAC3B,YAAY,cAAc,CAAC,mBAAmB,KAC7C,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAajC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,mBAAmB,EAAE,SAAS,CAe3G,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,cAAc,CAAC,cAAc,CAyB7E,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACtB,YAAY,cAAc,CAAC,mBAAmB,KAC7C,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,SAAS,CAKxD,CAAA"}