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

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,513 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var SyncServer_exports = {};
30
+ __export(SyncServer_exports, {
31
+ SnapshotSource: () => SnapshotSource,
32
+ SyncServer: () => SyncServer,
33
+ defaultConcurrency: () => defaultConcurrency,
34
+ defaultTailIntervalMs: () => defaultTailIntervalMs,
35
+ layer: () => layer,
36
+ layerHandlers: () => layerHandlers,
37
+ layerNoop: () => layerNoop,
38
+ layerWith: () => layerWith,
39
+ makeLive: () => makeLive,
40
+ makeLiveWith: () => makeLiveWith,
41
+ makeNoop: () => makeNoop
42
+ });
43
+ module.exports = __toCommonJS(SyncServer_exports);
44
+ var import_journal = require("@smthrs/journal");
45
+ var Cause = __toESM(require("effect/Cause"), 1);
46
+ var Clock = __toESM(require("effect/Clock"), 1);
47
+ var Context = __toESM(require("effect/Context"), 1);
48
+ var Effect = __toESM(require("effect/Effect"), 1);
49
+ var Layer = __toESM(require("effect/Layer"), 1);
50
+ var Option = __toESM(require("effect/Option"), 1);
51
+ var PubSub = __toESM(require("effect/PubSub"), 1);
52
+ var Stream = __toESM(require("effect/Stream"), 1);
53
+ var import_BranchProtocol = require("./BranchProtocol.js");
54
+ var BranchShare = __toESM(require("./BranchShare.js"), 1);
55
+ var Admission = __toESM(require("./internal/Admission.js"), 1);
56
+ var import_CauseText = require("./internal/CauseText.js");
57
+ var import_JournalErrorCode = require("./internal/JournalErrorCode.js");
58
+ var import_PolicyOptions = require("./internal/PolicyOptions.js");
59
+ var import_ProtocolVersion = require("./internal/ProtocolVersion.js");
60
+ var SnapshotBoundary = __toESM(require("./internal/SnapshotBoundary.js"), 1);
61
+ var RunCatalog = __toESM(require("./RunCatalog.js"), 1);
62
+ var import_SyncError = require("./SyncError.js");
63
+ var SyncPrincipal = __toESM(require("./SyncPrincipal.js"), 1);
64
+ var SyncProtocol = __toESM(require("./SyncProtocol.js"), 1);
65
+ var import_SyncRpcs = require("./SyncRpcs.js");
66
+ class SyncServer extends Context.Service()("@smthrs/sync/SyncServer") {
67
+ }
68
+ class SnapshotSource extends Context.Service()("@smthrs/sync/SnapshotSource") {
69
+ }
70
+ const makeNoop = (overrides = {}) => SyncServer.of({
71
+ snapshot: () => Effect.fail(new import_SyncError.SyncError({ code: "not_found", message: "Public snapshots are unavailable" })),
72
+ read: Effect.fn("SyncServer.read")(() => Effect.succeed({ entries: [], cursors: [], done: true })),
73
+ subscribe: () => Stream.succeed({ _tag: "Closed", reason: "Sync server is unavailable" }),
74
+ ...overrides
75
+ });
76
+ const layerNoop = Layer.succeed(SyncServer, makeNoop());
77
+ const journalFailure = (runId, generation) => (cause) => cause instanceof import_journal.Journal.JournalError && cause.code === "compacted" && cause.checkpointSeq !== void 0 ? new import_SyncError.SyncError({
78
+ code: "compacted",
79
+ message: `Run ${runId} is compacted above the requested cursor`,
80
+ cause: (0, import_CauseText.causeCode)(cause),
81
+ resync: { runId, checkpointSeq: cause.checkpointSeq, ...generation === void 0 ? {} : { generation } }
82
+ }) : new import_SyncError.SyncError({
83
+ // Constant message, rendered cause. A follower may hold nothing but a
84
+ // branch share link, and the journal's message is the SQLite driver's:
85
+ // it routinely carries SQL text, table and column names, and constraint
86
+ // identifiers. `decodeCapability` already refuses to be a parsing
87
+ // oracle; this is the same rule for the read path. The CODE is a
88
+ // different question from the message: a journal code this boundary
89
+ // also declares crosses as that code, so a shut-down journal is not
90
+ // reported as an unexplained fault.
91
+ code: (0, import_JournalErrorCode.journalErrorCode)(cause),
92
+ message: `Journal read failed for run ${runId}`,
93
+ cause: (0, import_CauseText.causeCode)(cause)
94
+ });
95
+ const expired = new import_SyncError.SyncError({
96
+ code: "unauthorized",
97
+ message: "The capability authorizing this subscription has expired"
98
+ });
99
+ const cursorOf = (cursors, runId) => cursors.find((cursor) => cursor.runId === runId)?.afterSeq;
100
+ const frameOf = (runId, generation, previous, entry) => ({
101
+ _tag: "Entries",
102
+ runId,
103
+ generation,
104
+ fromSeq: previous + 1,
105
+ toSeq: entry.seq,
106
+ entries: [entry]
107
+ });
108
+ const defaultConcurrency = 64;
109
+ const defaultTailIntervalMs = 1e3;
110
+ const tailBatchSize = 256;
111
+ const defaults = {
112
+ maxFrameBytes: SyncProtocol.defaultMaxFrameBytes,
113
+ concurrency: defaultConcurrency,
114
+ tailIntervalMs: defaultTailIntervalMs
115
+ };
116
+ const makeWith = ({ concurrency, maxFrameBytes, tailIntervalMs }) => Effect.gen(function* () {
117
+ const journal = yield* import_journal.Journal.Journal;
118
+ const catalog = yield* RunCatalog.RunCatalog;
119
+ const share = yield* Effect.serviceOption(BranchShare.BranchShare);
120
+ const snapshots = yield* Effect.serviceOption(SnapshotSource);
121
+ const frameTooLarge = (bytes) => Effect.fail(
122
+ new import_SyncError.SyncError({
123
+ code: "frame_too_large",
124
+ message: `Encoded entries of ${bytes} bytes exceed the ${maxFrameBytes}-byte frame ceiling`
125
+ })
126
+ );
127
+ const guardEntryChunk = (chunk) => {
128
+ for (const entry of chunk) {
129
+ const bytes = SyncProtocol.encodedByteLength(entry);
130
+ if (bytes > maxFrameBytes) return frameTooLarge(bytes);
131
+ }
132
+ return Effect.void;
133
+ };
134
+ const covered = Effect.map(catalog.list, (ids) => Array.from(new Set(ids)).sort());
135
+ const requireWorkspace = Effect.flatMap(
136
+ Effect.service(SyncPrincipal.SyncPrincipal),
137
+ (principal) => SyncPrincipal.isWorkspace(principal) ? Effect.succeed(principal.expiresAtMs) : Effect.fail(
138
+ new import_SyncError.SyncError({
139
+ code: "unauthorized",
140
+ message: "Reading workspace runs requires an authenticated workspace principal"
141
+ })
142
+ )
143
+ );
144
+ const branchClaims = (branchId, capability) => Option.isNone(share) || capability === void 0 ? Effect.succeed(null) : share.value.verify(capability, { branchId, access: "read" }).pipe(
145
+ Effect.map((claims) => claims),
146
+ Effect.catch((error) => error.code === "unauthorized" ? Effect.succeed(null) : Effect.fail(error))
147
+ );
148
+ const followUntil = (runId, capability) => {
149
+ const branchId = (0, import_BranchProtocol.branchOfRunId)(runId);
150
+ return branchId === null ? Effect.succeed(Number.POSITIVE_INFINITY) : Effect.map(branchClaims(branchId, capability), (claims) => claims === null ? null : claims.expiresAtMs);
151
+ };
152
+ const runIdsFor = (scope, capability) => scope._tag === "Run" ? Effect.gen(function* () {
153
+ const branchId = (0, import_BranchProtocol.branchOfRunId)(scope.runId);
154
+ if (branchId === null) {
155
+ return { runIds: [scope.runId], expiresAtMs: yield* requireWorkspace };
156
+ }
157
+ const claims = yield* branchClaims(branchId, capability);
158
+ if (claims === null) {
159
+ return yield* Effect.fail(
160
+ new import_SyncError.SyncError({
161
+ code: "unauthorized",
162
+ message: "Reading a shared branch requires a valid share capability"
163
+ })
164
+ );
165
+ }
166
+ return { runIds: [scope.runId], expiresAtMs: claims.expiresAtMs };
167
+ }) : Effect.gen(function* () {
168
+ let expiresAtMs = yield* requireWorkspace;
169
+ const runIds = yield* covered;
170
+ const visible = [];
171
+ for (const runId of runIds) {
172
+ const branchId = (0, import_BranchProtocol.branchOfRunId)(runId);
173
+ if (branchId === null) {
174
+ visible.push(runId);
175
+ continue;
176
+ }
177
+ const claims = yield* branchClaims(branchId, capability);
178
+ if (claims === null) continue;
179
+ expiresAtMs = Math.min(expiresAtMs, claims.expiresAtMs);
180
+ visible.push(runId);
181
+ }
182
+ return { runIds: visible, expiresAtMs };
183
+ });
184
+ const requireUniqueCursors = (cursors) => {
185
+ const duplicate = SyncProtocol.duplicateCursorRunId(cursors);
186
+ return duplicate === void 0 ? Effect.void : Effect.fail(
187
+ new import_SyncError.SyncError({
188
+ code: "invalid_request",
189
+ message: `Cursors name run ${duplicate} more than once`
190
+ })
191
+ );
192
+ };
193
+ const generationOf = (runId, expected) => Effect.gen(function* () {
194
+ const current = journal.generation === void 0 ? { generation: 0, afterSeq: -1 } : yield* journal.generation(runId).pipe(Effect.mapError(journalFailure(runId)));
195
+ if (expected !== void 0 && current.generation !== expected) {
196
+ return yield* Effect.fail(
197
+ new import_SyncError.SyncError({
198
+ code: "lineage_changed",
199
+ message: `Run ${runId} was rewound; rebuild from the archive boundary`,
200
+ rewind: { runId, ...current }
201
+ })
202
+ );
203
+ }
204
+ return current.generation;
205
+ });
206
+ const readPage = (runId, after, expectedGeneration, limit) => Effect.gen(function* () {
207
+ const generation = yield* generationOf(runId, expectedGeneration);
208
+ const page = yield* journal.entries({
209
+ runId,
210
+ ...after === void 0 ? {} : { after },
211
+ limit
212
+ }).pipe(Effect.mapError(journalFailure(runId, generation)));
213
+ yield* generationOf(runId, generation);
214
+ const admitted = yield* Admission.entries(page.entries, runId, after ?? -1);
215
+ return { admitted, generation, hasMore: page.hasMore };
216
+ });
217
+ const snapshot = (input) => Effect.gen(function* () {
218
+ const request = yield* SnapshotBoundary.request(input);
219
+ const { expiresAtMs } = yield* runIdsFor({ _tag: "Run", runId: request.runId }, request.capability);
220
+ if (expiresAtMs <= (yield* Clock.currentTimeMillis)) return yield* Effect.fail(expired);
221
+ if (Option.isNone(snapshots)) {
222
+ return yield* Effect.fail(new import_SyncError.SyncError({ code: "not_found", message: "Public snapshots are unavailable" }));
223
+ }
224
+ const supplied = yield* Effect.suspend(() => snapshots.value.read({ ...request })).pipe(
225
+ Effect.catchCause(
226
+ (cause) => Cause.hasInterrupts(cause) ? Effect.interrupt : Effect.logWarning(
227
+ "The public snapshot provider failed",
228
+ cause
229
+ ).pipe(Effect.andThen(Effect.fail(
230
+ new import_SyncError.SyncError({ code: "not_found", message: "Public snapshot is unavailable" })
231
+ )))
232
+ )
233
+ );
234
+ if (expiresAtMs <= (yield* Clock.currentTimeMillis)) return yield* Effect.fail(expired);
235
+ return yield* SnapshotBoundary.response(request, supplied, maxFrameBytes);
236
+ });
237
+ const read = (input) => Effect.gen(function* () {
238
+ const request = {
239
+ ...input,
240
+ ...yield* Admission.decode(SyncProtocol.RequestEnvelope, input, "invalid_request")
241
+ };
242
+ yield* (0, import_ProtocolVersion.requireVersion)(request.protocolVersion);
243
+ yield* requireUniqueCursors(request.cursors);
244
+ const limit = yield* (0, import_PolicyOptions.requestCount)("A read's limit", request.limit, SyncProtocol.maxReadLimit);
245
+ const { runIds } = yield* runIdsFor(request.scope, request.capability);
246
+ const entries = [];
247
+ const cursors = new Map(request.cursors.map((cursor) => [cursor.runId, cursor.afterSeq]));
248
+ const generations = new Map(request.cursors.map((cursor) => [cursor.runId, cursor.generation ?? 0]));
249
+ let frameBytes = 0;
250
+ const behind = [];
251
+ const serve = (runId, cap) => Effect.gen(function* () {
252
+ const after = cursors.get(runId);
253
+ const { admitted, generation, hasMore } = yield* readPage(runId, after, generations.get(runId), cap);
254
+ generations.set(runId, generation);
255
+ for (const accepted of admitted) {
256
+ const bytes = SyncProtocol.encodedByteLength(accepted);
257
+ if (bytes > maxFrameBytes) return { _tag: "oversized", bytes };
258
+ if (frameBytes + bytes > maxFrameBytes) return { _tag: "truncated" };
259
+ frameBytes += bytes;
260
+ entries.push(accepted);
261
+ cursors.set(runId, accepted.seq);
262
+ }
263
+ return hasMore ? { _tag: "more" } : { _tag: "drained" };
264
+ });
265
+ const pass = (runs, cap, more) => Effect.gen(function* () {
266
+ for (const runId of runs) {
267
+ if (entries.length >= limit) return void 0;
268
+ const outcome = yield* serve(runId, cap());
269
+ if (outcome._tag === "more") more.push(runId);
270
+ if (outcome._tag === "truncated" || outcome._tag === "oversized") return outcome;
271
+ }
272
+ return void 0;
273
+ });
274
+ const pending = [...runIds].sort((left, right) => (cursors.get(left) ?? -1) - (cursors.get(right) ?? -1));
275
+ const share2 = Math.max(1, Math.floor(limit / Math.max(runIds.length, 1)));
276
+ const shares = yield* pass(pending, () => Math.min(share2, limit - entries.length), behind);
277
+ const stop = shares ?? (yield* pass(behind, () => limit - entries.length, []));
278
+ if (stop?._tag === "oversized") return yield* frameTooLarge(stop.bytes);
279
+ return {
280
+ entries,
281
+ cursors: Array.from(cursors, ([runId, afterSeq]) => ({
282
+ runId,
283
+ afterSeq,
284
+ generation: generations.get(runId)
285
+ })),
286
+ // A run that reported more keeps the read unfinished even when the
287
+ // second pass then drained it: the page is a snapshot of a journal
288
+ // that is still being written, and only an untruncated pass over
289
+ // runs that all reported drained can promise there is nothing left.
290
+ done: stop === void 0 && behind.length === 0 && entries.length < limit
291
+ };
292
+ });
293
+ const runStream = (runId, cursors) => Stream.unwrap(Effect.gen(function* () {
294
+ const supplied = cursors.find((cursor) => cursor.runId === runId);
295
+ const after = supplied?.afterSeq;
296
+ const generation = yield* generationOf(runId, supplied === void 0 ? void 0 : supplied.generation ?? 0);
297
+ let admitted = after ?? -1;
298
+ const entries = journal.stream({ runId, ...after === void 0 ? {} : { afterSequence: after } }).pipe(
299
+ Stream.mapError(journalFailure(runId, generation)),
300
+ Stream.chunks,
301
+ Stream.mapEffect(
302
+ (chunk) => Admission.entries(chunk, runId, admitted).pipe(
303
+ Effect.tap(() => generationOf(runId, generation)),
304
+ Effect.tap(guardEntryChunk),
305
+ Effect.map((captured) => {
306
+ for (const entry of captured) admitted = entry.seq;
307
+ return captured;
308
+ })
309
+ )
310
+ ),
311
+ Stream.flattenArray,
312
+ Stream.mapAccum(
313
+ () => after === void 0 ? -1 : after,
314
+ (previous, entry) => [entry.seq, [frameOf(runId, generation, previous, entry)]]
315
+ )
316
+ );
317
+ if (journal.generation === void 0) return entries;
318
+ const guard = Stream.drain(Stream.fromEffectRepeat(
319
+ generationOf(runId, generation).pipe(Effect.delay(tailIntervalMs))
320
+ ));
321
+ return Stream.merge(entries, guard, { haltStrategy: "either" });
322
+ }));
323
+ const workspaceStream = (covering, openedUntil, request) => Stream.unwrap(Effect.gen(function* () {
324
+ const served = /* @__PURE__ */ new Map();
325
+ const generations = new Map(request.cursors.map((cursor) => [cursor.runId, cursor.generation ?? 0]));
326
+ const excluded = /* @__PURE__ */ new Set();
327
+ let visible = new Set(covering);
328
+ let deadline = openedUntil;
329
+ let dirty = /* @__PURE__ */ new Set();
330
+ let announced = false;
331
+ let reconciledAtMs = -Infinity;
332
+ const lowered = yield* PubSub.sliding(1);
333
+ const loweredSignal = yield* PubSub.subscribe(lowered);
334
+ const cover = (runId) => {
335
+ served.set(runId, cursorOf(request.cursors, runId));
336
+ };
337
+ for (const runId of covering) cover(runId);
338
+ const wake = yield* PubSub.sliding(1);
339
+ const woken = yield* PubSub.subscribe(wake);
340
+ const reconcile = Effect.gen(function* () {
341
+ const present = new Set(yield* catalog.list);
342
+ for (const runId of excluded) {
343
+ if (!present.has(runId)) excluded.delete(runId);
344
+ }
345
+ const next = [];
346
+ for (const runId of present) {
347
+ if (excluded.has(runId)) continue;
348
+ if (!served.has(runId)) {
349
+ const until = yield* followUntil(runId, request.capability);
350
+ if (until === null) {
351
+ excluded.add(runId);
352
+ continue;
353
+ }
354
+ if (until < deadline) {
355
+ deadline = until;
356
+ yield* PubSub.publish(lowered, void 0);
357
+ }
358
+ cover(runId);
359
+ }
360
+ next.push(runId);
361
+ }
362
+ reconciledAtMs = yield* Clock.currentTimeMillis;
363
+ visible = new Set(next);
364
+ });
365
+ const tail = (runId) => Stream.unwrap(Effect.gen(function* () {
366
+ const after = served.get(runId);
367
+ const { admitted, generation, hasMore } = yield* readPage(
368
+ runId,
369
+ after,
370
+ generations.get(runId),
371
+ tailBatchSize
372
+ );
373
+ generations.set(runId, generation);
374
+ yield* guardEntryChunk(admitted);
375
+ const frames = [];
376
+ let previous = after === void 0 ? -1 : after;
377
+ for (const accepted of admitted) {
378
+ frames.push(frameOf(runId, generation, previous, accepted));
379
+ previous = accepted.seq;
380
+ }
381
+ const last = admitted.at(-1);
382
+ if (last === void 0) return Stream.empty;
383
+ served.set(runId, last.seq);
384
+ if (hasMore) {
385
+ dirty.add(runId);
386
+ yield* PubSub.publish(wake, void 0);
387
+ }
388
+ return Stream.fromIterable(frames);
389
+ }));
390
+ const fullRound = Stream.unwrap(Effect.map(
391
+ reconcile,
392
+ () => {
393
+ dirty = /* @__PURE__ */ new Set();
394
+ announced = false;
395
+ return Stream.flatMap(Stream.fromIterable(visible), tail, { concurrency });
396
+ }
397
+ ));
398
+ const dirtyRound = Stream.suspend(() => {
399
+ const runs = dirty;
400
+ dirty = /* @__PURE__ */ new Set();
401
+ return Stream.flatMap(
402
+ Stream.fromIterable(Array.from(runs).filter((runId) => visible.has(runId))),
403
+ tail,
404
+ { concurrency }
405
+ );
406
+ });
407
+ const roundFor = (tick) => Effect.map(
408
+ Clock.currentTimeMillis,
409
+ (nowMs) => tick === "interval" || announced || nowMs - reconciledAtMs >= tailIntervalMs ? fullRound : dirtyRound
410
+ );
411
+ const announcements = Stream.drain(Stream.tap(
412
+ catalog.changes,
413
+ () => Effect.suspend(() => {
414
+ announced = true;
415
+ return PubSub.publish(wake, void 0);
416
+ })
417
+ ));
418
+ const commits = yield* journal.changes;
419
+ const appends = Stream.fromSubscription(commits).pipe(
420
+ Stream.filter((entry) => served.has(entry.runId)),
421
+ Stream.tap(
422
+ (entry) => Effect.suspend(() => {
423
+ dirty.add(entry.runId);
424
+ return PubSub.publish(wake, void 0);
425
+ })
426
+ ),
427
+ Stream.drain
428
+ );
429
+ const ticks = Stream.fromEffectRepeat(
430
+ Effect.raceFirst(
431
+ Effect.as(PubSub.take(woken), "wake"),
432
+ Effect.as(Effect.sleep(tailIntervalMs), "interval")
433
+ )
434
+ );
435
+ const untilDeadline = Effect.gen(function* () {
436
+ while (true) {
437
+ const nowMs = yield* Clock.currentTimeMillis;
438
+ if (nowMs >= deadline) {
439
+ return yield* Effect.fail(expired);
440
+ }
441
+ yield* Number.isFinite(deadline) ? Effect.raceFirst(Effect.sleep(deadline - nowMs), PubSub.take(loweredSignal)) : PubSub.take(loweredSignal);
442
+ }
443
+ });
444
+ return Stream.interruptWhen(
445
+ Stream.merge(
446
+ Stream.merge(
447
+ Stream.concat(
448
+ fullRound,
449
+ Stream.flatMap(ticks, (tick) => Stream.unwrap(roundFor(tick)))
450
+ ),
451
+ announcements
452
+ ),
453
+ appends
454
+ ),
455
+ untilDeadline
456
+ );
457
+ }));
458
+ const untilExpiry = (expiresAtMs, stream) => Number.isFinite(expiresAtMs) ? Stream.interruptWhen(
459
+ stream,
460
+ Effect.flatMap(
461
+ Clock.currentTimeMillis,
462
+ (nowMs) => Effect.andThen(Effect.sleep(Math.max(expiresAtMs - nowMs, 0)), Effect.fail(expired))
463
+ )
464
+ ) : stream;
465
+ const subscribe = (input) => Stream.unwrap(
466
+ Effect.gen(function* () {
467
+ const request = {
468
+ ...input,
469
+ ...yield* Admission.decode(SyncProtocol.RequestEnvelope, input, "invalid_request")
470
+ };
471
+ yield* (0, import_ProtocolVersion.requireVersion)(request.protocolVersion);
472
+ yield* requireUniqueCursors(request.cursors);
473
+ const credit = yield* (0, import_PolicyOptions.requestCount)(
474
+ "A subscription's credit",
475
+ request.credit,
476
+ SyncProtocol.maxSubscribeCredit
477
+ );
478
+ const { expiresAtMs, runIds } = yield* runIdsFor(request.scope, request.capability);
479
+ const frames = request.scope._tag === "Run" ? untilExpiry(expiresAtMs, runStream(request.scope.runId, request.cursors)) : workspaceStream(runIds, expiresAtMs, request);
480
+ return Stream.take(frames, credit);
481
+ })
482
+ );
483
+ return SyncServer.of({ snapshot, read, subscribe });
484
+ });
485
+ const makeLive = makeWith(defaults);
486
+ const makeLiveWith = (options = {}) => Effect.flatMap(
487
+ Effect.all({
488
+ maxFrameBytes: (0, import_PolicyOptions.positiveInt)("SyncServer.Options.maxFrameBytes", options.maxFrameBytes, defaults.maxFrameBytes),
489
+ concurrency: (0, import_PolicyOptions.positiveInt)("SyncServer.Options.concurrency", options.concurrency, defaults.concurrency),
490
+ tailIntervalMs: (0, import_PolicyOptions.positiveInt)(
491
+ "SyncServer.Options.tailIntervalMs",
492
+ options.tailIntervalMs,
493
+ defaults.tailIntervalMs
494
+ )
495
+ }),
496
+ makeWith
497
+ );
498
+ const layer = Layer.effect(
499
+ SyncServer,
500
+ makeLive
501
+ );
502
+ const layerWith = (options) => Layer.effect(SyncServer, makeLiveWith(options));
503
+ const layerHandlers = import_SyncRpcs.SyncRpcs.toLayer(
504
+ Effect.gen(function* () {
505
+ const sync = yield* SyncServer;
506
+ return import_SyncRpcs.SyncRpcs.of({
507
+ "Sync.Snapshot": (request) => sync.snapshot(request),
508
+ "Sync.Read": (request) => sync.read(request),
509
+ "Sync.Subscribe": (request) => sync.subscribe(request)
510
+ });
511
+ })
512
+ );
513
+ //# sourceMappingURL=SyncServer.js.map