@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,434 @@
1
+ /**
2
+ * The wire contract shared by the sync server and the browser-safe client.
3
+ *
4
+ * Every message is schema-backed so the same definitions serve the RPC group,
5
+ * the HTTP transport, and replay tooling.
6
+ *
7
+ * @since 0.1.0
8
+ */
9
+ import * as JournalEvent from "@smthrs/journal/JournalEvent";
10
+ import * as Schema from "effect/Schema";
11
+ import { ShareCapability } from "./BranchProtocol.js";
12
+ /**
13
+ * Wire revision requiring explicit generations in server positions.
14
+ * Missing request versions decode so the server can return a typed refusal.
15
+ *
16
+ * @category constants
17
+ * @since 1.0.0-rc.0
18
+ */
19
+ export const protocolVersion = 1;
20
+ /**
21
+ * Selects every run in the workspace.
22
+ *
23
+ * @category models
24
+ * @since 0.1.0
25
+ */
26
+ export const WorkspaceScope = Schema.TaggedStruct("Workspace", {});
27
+ /**
28
+ * Selects a single run.
29
+ *
30
+ * @category models
31
+ * @since 0.1.0
32
+ */
33
+ export const RunScope = Schema.TaggedStruct("Run", { runId: JournalEvent.RunId });
34
+ /**
35
+ * The set of runs a request covers.
36
+ *
37
+ * @category models
38
+ * @since 0.1.0
39
+ */
40
+ export const Scope = Schema.Union([WorkspaceScope, RunScope]);
41
+ /**
42
+ * The last sequence one run has been DELIVERED to a client through.
43
+ *
44
+ * "Delivered", not "applied": {@link SyncClient}'s default follow advances a
45
+ * cursor as it hands the entry to the consumer, so a consumer whose own apply
46
+ * step then fails must not treat the cursor as an acknowledgement of that
47
+ * apply. A consumer that needs the stronger meaning supplies
48
+ * `SubscribeOptions.apply`, which the client runs to success before the cursor
49
+ * moves. `Progress.applied` records that application claim; a bare wire
50
+ * cursor never acknowledges application to the server.
51
+ *
52
+ * `generation` identifies the run's current history after rewinds. Requests may
53
+ * omit it for persisted generation-zero cursors. Server responses must include
54
+ * it in exactly one cursor for every run represented in Read entries. Clients
55
+ * refuse missing cursor rows or generations before delivery; persist a returned
56
+ * generation with its sequence.
57
+ * Sequence ordering is meaningful only within one generation.
58
+ *
59
+ * @category models
60
+ * @since 0.1.0
61
+ */
62
+ export const RunCursor = Schema.Struct({
63
+ runId: JournalEvent.RunId,
64
+ afterSeq: JournalEvent.Seq,
65
+ // Omitted only for generation zero (persisted cursors from before rewind support).
66
+ generation: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))
67
+ });
68
+ /**
69
+ * A client's delivered position across the workspace.
70
+ *
71
+ * At most ONE cursor per run: a request carrying two cursors for the same run
72
+ * is ambiguous about where the read starts, so the server refuses it with
73
+ * `invalid_request` rather than picking one (see {@link duplicateCursorRunId}).
74
+ *
75
+ * @category models
76
+ * @since 0.1.0
77
+ */
78
+ export const WorkspaceCursor = Schema.Array(RunCursor);
79
+ /**
80
+ * One run's delivered position as a SERVER states it.
81
+ *
82
+ * Identical to {@link RunCursor} except that `generation` is required: a
83
+ * request may omit it for a persisted generation-zero cursor, a response may
84
+ * not. Server positions are typed with this shape so an implementation that
85
+ * omits a generation fails to compile rather than being refused at run time by
86
+ * every client that reads it.
87
+ *
88
+ * The wire schemas stay lenient on purpose. {@link ReadResponse} and
89
+ * {@link Frame} decode an absent generation so {@link SyncClient} can answer a
90
+ * non-conforming server with a typed `protocol_violation` naming the missing
91
+ * field, rather than a bare decode failure.
92
+ *
93
+ * @category models
94
+ * @since 1.0.0-rc.0
95
+ */
96
+ export const ServerCursor = Schema.Struct({
97
+ ...RunCursor.fields,
98
+ generation: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
99
+ });
100
+ /**
101
+ * The scope and cursor set every read and subscribe request carries.
102
+ *
103
+ * Server read, server subscribe and client subscribe each admit a
104
+ * caller-built request against this one schema, so an envelope field is
105
+ * declared once and every entry point agrees on what an admitted request is.
106
+ *
107
+ * @category models
108
+ * @since 1.0.0-rc.0
109
+ */
110
+ export const RequestEnvelope = Schema.Struct({ scope: Scope, cursors: WorkspaceCursor });
111
+ /**
112
+ * A client's delivery and application positions, as two cursor sets.
113
+ *
114
+ * `delivered` is transport delivery and never an application acknowledgement.
115
+ * `applied` holds the positions committed after a successful `apply` or
116
+ * `onResync` callback; consumers persist state and these positions in the
117
+ * same transaction. The field names carry the distinction, so neither set is
118
+ * wrapped in a tag.
119
+ *
120
+ * @category models
121
+ * @since 1.0.0-rc.0
122
+ */
123
+ export const Progress = Schema.Struct({ delivered: WorkspaceCursor, applied: WorkspaceCursor });
124
+ /**
125
+ * The first run named twice in a cursor set, or `undefined` when every run is
126
+ * named at most once.
127
+ *
128
+ * The schema cannot express the uniqueness — `Schema.Array` has no such check
129
+ * — so the one rule is stated once here and enforced by both request paths.
130
+ *
131
+ * @category predicates
132
+ * @since 0.1.0
133
+ */
134
+ export const duplicateCursorRunId = (cursors) => {
135
+ const seen = new Set();
136
+ for (const cursor of cursors) {
137
+ if (seen.has(cursor.runId))
138
+ return cursor.runId;
139
+ seen.add(cursor.runId);
140
+ }
141
+ return undefined;
142
+ };
143
+ /**
144
+ * Where a follower must resume after the server refused a cursor that starts
145
+ * below a run's compaction floor.
146
+ *
147
+ * Compaction deletes the entries below a checkpoint, so a cursor under the
148
+ * floor names history that no longer exists. `checkpointSeq` is that floor:
149
+ * the checkpoint's state subsumes every entry at or below it, so a follower
150
+ * must restore a snapshot covering that sequence before reading forward.
151
+ * Its applied cursor names that snapshot, which may be newer than this floor.
152
+ *
153
+ * `generation` is the run's history the floor belongs to. A run rewound
154
+ * before it was compacted is past generation zero, and a fresh follower has
155
+ * no earlier cursor to learn that from, so a recovery receipt that omits its
156
+ * generation is bound to this one. The server's refusal of a read always
157
+ * carries it; a snapshot that falls short of a floor refuses without one.
158
+ *
159
+ * This field carries no replacement state. The client fails closed without
160
+ * an explicit recovery handler returning the cursor it actually restored.
161
+ * `SnapshotRequest` fetches a separately selected public projection. Raw journal
162
+ * checkpoints are unredacted and must not be exposed under history permission.
163
+ *
164
+ * @category models
165
+ * @since 0.1.0
166
+ */
167
+ export const Resync = Schema.Struct({
168
+ runId: JournalEvent.RunId,
169
+ checkpointSeq: JournalEvent.Seq,
170
+ generation: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))
171
+ });
172
+ // `protocolVersion` accepts any integer here for the same reason
173
+ // `ReadRequest` and `SubscribeRequest` do: a mismatch decodes so the boundary
174
+ // can answer it with a typed `protocol_violation` instead of a decode
175
+ // failure, and the accepted number lives only in `protocolVersion`.
176
+ const snapshotIdentity = {
177
+ protocolVersion: Schema.Int,
178
+ runId: JournalEvent.RunId,
179
+ lineageId: Schema.NonEmptyString.check(Schema.isMaxLength(512)),
180
+ projection: Schema.NonEmptyString.check(Schema.isMaxLength(128)),
181
+ projectionVersion: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThan(Number.MAX_SAFE_INTEGER))
182
+ };
183
+ /**
184
+ * Requests an explicitly public projection covering the missing prefix.
185
+ * Identity and version must match exactly; this never requests raw engine state.
186
+ * @category models
187
+ * @since 1.0.0-rc.0
188
+ */
189
+ export const SnapshotRequest = Schema.Struct({
190
+ ...snapshotIdentity,
191
+ atLeastSeq: JournalEvent.Seq,
192
+ capability: Schema.optional(ShareCapability)
193
+ });
194
+ /**
195
+ * A complete public projection through `seq`, inclusive. Apply its state and
196
+ * durable cursor atomically, then read entries strictly after `seq`.
197
+ * @category models
198
+ * @since 1.0.0-rc.0
199
+ */
200
+ export const Snapshot = Schema.Struct({ ...snapshotIdentity, seq: JournalEvent.Seq, state: Schema.Json });
201
+ /**
202
+ * Largest number of entries one {@link ReadRequest} may ask for.
203
+ *
204
+ * `limit` is request-controlled fan-out: the server passes it to the journal
205
+ * as the page size, once per covered run, so an unbounded limit made one
206
+ * workspace read materialize an unbounded number of rows per run. The frame
207
+ * ceiling does not cover it — that drops entries AFTER they have been read.
208
+ * A request above this bound is refused at the wire, and an in-process caller
209
+ * that bypasses the schema is clamped to it.
210
+ *
211
+ * @category limits
212
+ * @since 0.1.0
213
+ */
214
+ export const maxReadLimit = 1024;
215
+ /**
216
+ * Largest number of frames one {@link SubscribeRequest} may hold open.
217
+ *
218
+ * Credit is a hard frame limit, so it is also how long one subscription may
219
+ * pin a server-side fan-out. The bound keeps that lifetime a function of the
220
+ * protocol rather than of what the caller asks for; a follower that wants
221
+ * more resubscribes from the cursors it has, which is what
222
+ * `SyncClient.defaultCredit` already does.
223
+ *
224
+ * @category limits
225
+ * @since 0.1.0
226
+ */
227
+ export const maxSubscribeCredit = 4096;
228
+ /**
229
+ * A durable catch-up request.
230
+ *
231
+ * `capability` authorizes branch runs: a run whose id maps to a shared branch
232
+ * is only read when the capability verifies for that branch. Non-branch runs
233
+ * ignore it.
234
+ *
235
+ * `limit` is bounded on both sides: at least one entry, at most
236
+ * {@link maxReadLimit}.
237
+ *
238
+ * @category models
239
+ * @since 0.1.0
240
+ */
241
+ export const ReadRequest = Schema.Struct({
242
+ protocolVersion: Schema.optional(Schema.Int),
243
+ scope: Scope,
244
+ cursors: WorkspaceCursor,
245
+ limit: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(maxReadLimit)),
246
+ capability: Schema.optional(ShareCapability)
247
+ });
248
+ /**
249
+ * One page of durable catch-up entries.
250
+ *
251
+ * `done` reports that the page reached the durable tail, which is the point at
252
+ * which a client may switch from paging to following.
253
+ *
254
+ * @category models
255
+ * @since 0.1.0
256
+ */
257
+ export const ReadResponse = Schema.Struct({
258
+ entries: Schema.Array(JournalEvent.Entry),
259
+ cursors: WorkspaceCursor,
260
+ done: Schema.Boolean
261
+ });
262
+ /**
263
+ * One page of durable catch-up entries as a SERVER states it.
264
+ *
265
+ * Every cursor carries its generation. {@link SyncServer.Service} answers with
266
+ * this shape; {@link ReadResponse} is the lenient shape a client decodes an
267
+ * untrusted server's page into before refusing a missing generation.
268
+ *
269
+ * @category models
270
+ * @since 1.0.0-rc.0
271
+ */
272
+ export const ServerReadResponse = Schema.Struct({
273
+ ...ReadResponse.fields,
274
+ cursors: Schema.Array(ServerCursor)
275
+ });
276
+ /**
277
+ * A request to follow committed entries.
278
+ *
279
+ * `capability` authorizes branch runs exactly as on {@link ReadRequest}.
280
+ *
281
+ * `credit` is bounded on both sides: at least one frame, at most
282
+ * {@link maxSubscribeCredit}. Zero is refused rather than served as an
283
+ * immediately-empty stream, because the two readings of it — "open a window
284
+ * of nothing" and "a caller computed its window wrong" — are
285
+ * indistinguishable on the wire, and the second one busy-loops a follow that
286
+ * replenishes by resubscribing.
287
+ *
288
+ * @category models
289
+ * @since 0.1.0
290
+ */
291
+ export const SubscribeRequest = Schema.Struct({
292
+ protocolVersion: Schema.optional(Schema.Int),
293
+ scope: Scope,
294
+ cursors: WorkspaceCursor,
295
+ credit: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(maxSubscribeCredit)),
296
+ capability: Schema.optional(ShareCapability)
297
+ });
298
+ /**
299
+ * A contiguous batch of committed entries for one run.
300
+ *
301
+ * `fromSeq` and `toSeq` describe the interval the server covered, not the
302
+ * sequences actually carried: dropped admissions leave legitimate holes.
303
+ * `generation` identifies the history covering the interval and is mandatory
304
+ * on server responses, including zero. Clients refuse its omission before
305
+ * deduplicating by sequence. The optional schema permits a typed refusal.
306
+ *
307
+ * @category models
308
+ * @since 0.1.0
309
+ */
310
+ export const EntriesFrame = Schema.TaggedStruct("Entries", {
311
+ runId: JournalEvent.RunId,
312
+ generation: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
313
+ fromSeq: JournalEvent.Seq,
314
+ toSeq: JournalEvent.Seq,
315
+ entries: Schema.Array(JournalEvent.Entry)
316
+ });
317
+ /**
318
+ * A liveness frame reserved for a future revision, which no rc.0 server emits.
319
+ *
320
+ * No server in this package constructs one at rc.0, and a client must not wait
321
+ * for one: {@link SyncClient} tolerates the variant and ignores it, which is
322
+ * the whole of the contract today. It stays in the {@link Frame} union so
323
+ * adding the frame later is not a wire break, and so a third-party client
324
+ * written against this schema already ignores it.
325
+ *
326
+ * Keepalive is a transport concern at rc.0. Emitting a heartbeat here would
327
+ * spend the subscription's `credit`, which is a hard frame limit, so an idle
328
+ * follow would resubscribe on a timer instead of staying quiet.
329
+ *
330
+ * @category models
331
+ * @since 0.1.0
332
+ */
333
+ export const HeartbeatFrame = Schema.TaggedStruct("Heartbeat", {});
334
+ /**
335
+ * A terminal frame reporting that the server ended the subscription.
336
+ *
337
+ * @category models
338
+ * @since 0.1.0
339
+ */
340
+ export const ClosedFrame = Schema.TaggedStruct("Closed", {
341
+ reason: Schema.optional(Schema.String)
342
+ });
343
+ /**
344
+ * Any frame a subscription may emit.
345
+ *
346
+ * @category models
347
+ * @since 0.1.0
348
+ */
349
+ export const Frame = Schema.Union([EntriesFrame, HeartbeatFrame, ClosedFrame]);
350
+ /**
351
+ * A contiguous batch of committed entries as a SERVER states it.
352
+ *
353
+ * `generation` is required here: a server that covers an interval knows the
354
+ * history it read, so omitting it is a compile error rather than a run-time
355
+ * refusal. {@link EntriesFrame} is the lenient shape a client decodes an
356
+ * untrusted server's frame into.
357
+ *
358
+ * @category models
359
+ * @since 1.0.0-rc.0
360
+ */
361
+ export const ServerEntriesFrame = Schema.TaggedStruct("Entries", {
362
+ runId: JournalEvent.RunId,
363
+ generation: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
364
+ fromSeq: JournalEvent.Seq,
365
+ toSeq: JournalEvent.Seq,
366
+ entries: Schema.Array(JournalEvent.Entry)
367
+ });
368
+ /**
369
+ * Any frame a subscription may emit, as a server states it.
370
+ *
371
+ * @category models
372
+ * @since 1.0.0-rc.0
373
+ */
374
+ export const ServerFrame = Schema.Union([ServerEntriesFrame, HeartbeatFrame, ClosedFrame]);
375
+ /**
376
+ * Whether a scope covers a run.
377
+ *
378
+ * @category predicates
379
+ * @since 0.1.0
380
+ */
381
+ export const covers = (scope, runId) => scope._tag === "Workspace" || scope.runId === runId;
382
+ /**
383
+ * Default ceiling on the encoded entries one frame or page may
384
+ * carry, in bytes.
385
+ *
386
+ * Both ends enforce the sum of encoded journal entries, excluding the outer
387
+ * RPC/frame envelope. Transports must allow their own envelope overhead.
388
+ * Commands default to 1 MiB; the 2 MiB entry ceiling includes their durable
389
+ * journal envelope and keeps already-admitted maximum commands consumable.
390
+ *
391
+ * @category limits
392
+ * @since 0.1.0
393
+ */
394
+ export const defaultMaxFrameBytes = 2 * 1024 * 1024;
395
+ const utf8 = new TextEncoder();
396
+ /** The four bytes `null` occupies, which is what a value with no JSON text
397
+ * costs once it is inside an encoded frame. */
398
+ const nullByteLength = 4;
399
+ /**
400
+ * The wire size of one value: the UTF-8 byte length of its JSON text.
401
+ *
402
+ * Ceilings measure this encoded form rather than in-memory object size, so a
403
+ * limit tracks what a transport actually carries.
404
+ *
405
+ * The function is TOTAL, because every caller is a size guard whose whole job
406
+ * is to produce a typed `frame_too_large` refusal rather than a defect:
407
+ *
408
+ * - A value with no JSON text at all — `undefined`, a function, a symbol —
409
+ * measures {@link nullByteLength}, the bytes it occupies once an enclosing
410
+ * array or object encodes it as `null`. It used to measure zero, which let
411
+ * an unencodable value pass every ceiling.
412
+ * - A value `JSON.stringify` refuses — a cycle, a `bigint`, a throwing
413
+ * `toJSON` — measures `Infinity`, so it trips every ceiling instead of
414
+ * throwing a `TypeError` out of code that only ever fails typed.
415
+ *
416
+ * It measures the JSON text, so a `toJSON` method, a getter, or a proxy trap
417
+ * on the value runs while the size is taken; the read path measures each
418
+ * entry once per end. Entries that arrive over the wire are schema-decoded
419
+ * data and have none of those.
420
+ *
421
+ * @category limits
422
+ * @since 0.1.0
423
+ */
424
+ export const encodedByteLength = (value) => {
425
+ let text;
426
+ try {
427
+ text = JSON.stringify(value);
428
+ }
429
+ catch {
430
+ return Number.POSITIVE_INFINITY;
431
+ }
432
+ return text === undefined ? nullByteLength : utf8.encode(text).length;
433
+ };
434
+ //# sourceMappingURL=SyncProtocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncProtocol.js","sourceRoot":"","sources":["../../src/SyncProtocol.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAA;AAC5D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAA;AAEhC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AAElE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,CAAC,CAAA;AAEjF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAA;AAU7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;IACrC,KAAK,EAAE,YAAY,CAAC,KAAK;IACzB,QAAQ,EAAE,YAAY,CAAC,GAAG;IAC1B,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;CAChF,CAAC,CAAA;AAUF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;AAUtD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,GAAG,SAAS,CAAC,MAAM;IACnB,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;CAC/D,CAAC,CAAA;AAUF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAA;AAUxF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAA;AAQ/F;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,OAAwB,EAAkC,EAAE;IAC/F,MAAM,IAAI,GAAG,IAAI,GAAG,EAAsB,CAAA;IAC1C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC,KAAK,CAAA;QAC/C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACxB,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,YAAY,CAAC,KAAK;IACzB,aAAa,EAAE,YAAY,CAAC,GAAG;IAC/B,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;CAChF,CAAC,CAAA;AAUF,iEAAiE;AACjE,8EAA8E;AAC9E,sEAAsE;AACtE,oEAAoE;AACpE,MAAM,gBAAgB,GAAG;IACvB,eAAe,EAAE,MAAM,CAAC,GAAG;IAC3B,KAAK,EAAE,YAAY,CAAC,KAAK;IACzB,SAAS,EAAE,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC/D,UAAU,EAAE,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAChE,iBAAiB,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;CAClH,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC;IAC3C,GAAG,gBAAgB;IACnB,UAAU,EAAE,YAAY,CAAC,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC;CAC7C,CAAC,CAAA;AAQF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,gBAAgB,EAAE,GAAG,EAAE,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;AAQzG;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAA;AAEhC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAEtC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;IAC5C,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,eAAe;IACxB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACnG,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC;CAC7C,CAAC,CAAA;AAUF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC;IACzC,OAAO,EAAE,eAAe;IACxB,IAAI,EAAE,MAAM,CAAC,OAAO;CACrB,CAAC,CAAA;AAUF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,GAAG,YAAY,CAAC,MAAM;IACtB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC;CACpC,CAAC,CAAA;AAUF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC5C,eAAe,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;IAC5C,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,eAAe;IACxB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;IAC1G,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC;CAC7C,CAAC,CAAA;AAUF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE;IACzD,KAAK,EAAE,YAAY,CAAC,KAAK;IACzB,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,OAAO,EAAE,YAAY,CAAC,GAAG;IACzB,KAAK,EAAE,YAAY,CAAC,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC;CAC1C,CAAC,CAAA;AAUF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AAElE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE;IACvD,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;CACvC,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC,CAAA;AAU9E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE;IAC/D,KAAK,EAAE,YAAY,CAAC,KAAK;IACzB,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAC9D,OAAO,EAAE,YAAY,CAAC,GAAG;IACzB,KAAK,EAAE,YAAY,CAAC,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC;CAC1C,CAAC,CAAA;AAUF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,kBAAkB,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC,CAAA;AAU1F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAY,EAAE,KAAyB,EAAW,EAAE,CACzE,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,CAAA;AAErD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;AAEnD,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAA;AAE9B;+CAC+C;AAC/C,MAAM,cAAc,GAAG,CAAC,CAAA;AAExB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAU,EAAE;IAC1D,IAAI,IAAwB,CAAA;IAC5B,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,iBAAiB,CAAA;IACjC,CAAC;IACD,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAA;AACvE,CAAC,CAAA","sourcesContent":["/**\n * The wire contract shared by the sync server and the browser-safe client.\n *\n * Every message is schema-backed so the same definitions serve the RPC group,\n * the HTTP transport, and replay tooling.\n *\n * @since 0.1.0\n */\n\nimport * as JournalEvent from \"@smthrs/journal/JournalEvent\"\nimport * as Schema from \"effect/Schema\"\nimport { ShareCapability } from \"./BranchProtocol.ts\"\n\n/**\n * Wire revision requiring explicit generations in server positions.\n * Missing request versions decode so the server can return a typed refusal.\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const protocolVersion = 1\n\n/**\n * Selects every run in the workspace.\n *\n * @category models\n * @since 0.1.0\n */\nexport const WorkspaceScope = Schema.TaggedStruct(\"Workspace\", {})\n\n/**\n * Selects a single run.\n *\n * @category models\n * @since 0.1.0\n */\nexport const RunScope = Schema.TaggedStruct(\"Run\", { runId: JournalEvent.RunId })\n\n/**\n * The set of runs a request covers.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Scope = Schema.Union([WorkspaceScope, RunScope])\n\n/**\n * The set of runs a request covers.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Scope = typeof Scope.Type\n\n/**\n * The last sequence one run has been DELIVERED to a client through.\n *\n * \"Delivered\", not \"applied\": {@link SyncClient}'s default follow advances a\n * cursor as it hands the entry to the consumer, so a consumer whose own apply\n * step then fails must not treat the cursor as an acknowledgement of that\n * apply. A consumer that needs the stronger meaning supplies\n * `SubscribeOptions.apply`, which the client runs to success before the cursor\n * moves. `Progress.applied` records that application claim; a bare wire\n * cursor never acknowledges application to the server.\n *\n * `generation` identifies the run's current history after rewinds. Requests may\n * omit it for persisted generation-zero cursors. Server responses must include\n * it in exactly one cursor for every run represented in Read entries. Clients\n * refuse missing cursor rows or generations before delivery; persist a returned\n * generation with its sequence.\n * Sequence ordering is meaningful only within one generation.\n *\n * @category models\n * @since 0.1.0\n */\nexport const RunCursor = Schema.Struct({\n runId: JournalEvent.RunId,\n afterSeq: JournalEvent.Seq,\n // Omitted only for generation zero (persisted cursors from before rewind support).\n generation: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))\n})\n\n/**\n * The last sequence one run has been delivered to a client through.\n *\n * @category models\n * @since 0.1.0\n */\nexport type RunCursor = typeof RunCursor.Type\n\n/**\n * A client's delivered position across the workspace.\n *\n * At most ONE cursor per run: a request carrying two cursors for the same run\n * is ambiguous about where the read starts, so the server refuses it with\n * `invalid_request` rather than picking one (see {@link duplicateCursorRunId}).\n *\n * @category models\n * @since 0.1.0\n */\nexport const WorkspaceCursor = Schema.Array(RunCursor)\n\n/**\n * A client's delivered position across the workspace.\n *\n * @category models\n * @since 0.1.0\n */\nexport type WorkspaceCursor = typeof WorkspaceCursor.Type\n\n/**\n * One run's delivered position as a SERVER states it.\n *\n * Identical to {@link RunCursor} except that `generation` is required: a\n * request may omit it for a persisted generation-zero cursor, a response may\n * not. Server positions are typed with this shape so an implementation that\n * omits a generation fails to compile rather than being refused at run time by\n * every client that reads it.\n *\n * The wire schemas stay lenient on purpose. {@link ReadResponse} and\n * {@link Frame} decode an absent generation so {@link SyncClient} can answer a\n * non-conforming server with a typed `protocol_violation` naming the missing\n * field, rather than a bare decode failure.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const ServerCursor = Schema.Struct({\n ...RunCursor.fields,\n generation: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))\n})\n\n/**\n * One run's delivered position as a server states it.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ServerCursor = typeof ServerCursor.Type\n\n/**\n * The scope and cursor set every read and subscribe request carries.\n *\n * Server read, server subscribe and client subscribe each admit a\n * caller-built request against this one schema, so an envelope field is\n * declared once and every entry point agrees on what an admitted request is.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const RequestEnvelope = Schema.Struct({ scope: Scope, cursors: WorkspaceCursor })\n\n/**\n * The scope and cursor set every read and subscribe request carries.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type RequestEnvelope = typeof RequestEnvelope.Type\n\n/**\n * A client's delivery and application positions, as two cursor sets.\n *\n * `delivered` is transport delivery and never an application acknowledgement.\n * `applied` holds the positions committed after a successful `apply` or\n * `onResync` callback; consumers persist state and these positions in the\n * same transaction. The field names carry the distinction, so neither set is\n * wrapped in a tag.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const Progress = Schema.Struct({ delivered: WorkspaceCursor, applied: WorkspaceCursor })\n\n/** The value form of progress.\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type Progress = typeof Progress.Type\n\n/**\n * The first run named twice in a cursor set, or `undefined` when every run is\n * named at most once.\n *\n * The schema cannot express the uniqueness — `Schema.Array` has no such check\n * — so the one rule is stated once here and enforced by both request paths.\n *\n * @category predicates\n * @since 0.1.0\n */\nexport const duplicateCursorRunId = (cursors: WorkspaceCursor): JournalEvent.RunId | undefined => {\n const seen = new Set<JournalEvent.RunId>()\n for (const cursor of cursors) {\n if (seen.has(cursor.runId)) return cursor.runId\n seen.add(cursor.runId)\n }\n return undefined\n}\n\n/**\n * Where a follower must resume after the server refused a cursor that starts\n * below a run's compaction floor.\n *\n * Compaction deletes the entries below a checkpoint, so a cursor under the\n * floor names history that no longer exists. `checkpointSeq` is that floor:\n * the checkpoint's state subsumes every entry at or below it, so a follower\n * must restore a snapshot covering that sequence before reading forward.\n * Its applied cursor names that snapshot, which may be newer than this floor.\n *\n * `generation` is the run's history the floor belongs to. A run rewound\n * before it was compacted is past generation zero, and a fresh follower has\n * no earlier cursor to learn that from, so a recovery receipt that omits its\n * generation is bound to this one. The server's refusal of a read always\n * carries it; a snapshot that falls short of a floor refuses without one.\n *\n * This field carries no replacement state. The client fails closed without\n * an explicit recovery handler returning the cursor it actually restored.\n * `SnapshotRequest` fetches a separately selected public projection. Raw journal\n * checkpoints are unredacted and must not be exposed under history permission.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Resync = Schema.Struct({\n runId: JournalEvent.RunId,\n checkpointSeq: JournalEvent.Seq,\n generation: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))\n})\n\n/**\n * Where a follower must resume after a compaction refusal.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Resync = typeof Resync.Type\n\n// `protocolVersion` accepts any integer here for the same reason\n// `ReadRequest` and `SubscribeRequest` do: a mismatch decodes so the boundary\n// can answer it with a typed `protocol_violation` instead of a decode\n// failure, and the accepted number lives only in `protocolVersion`.\nconst snapshotIdentity = {\n protocolVersion: Schema.Int,\n runId: JournalEvent.RunId,\n lineageId: Schema.NonEmptyString.check(Schema.isMaxLength(512)),\n projection: Schema.NonEmptyString.check(Schema.isMaxLength(128)),\n projectionVersion: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThan(Number.MAX_SAFE_INTEGER))\n}\n\n/**\n * Requests an explicitly public projection covering the missing prefix.\n * Identity and version must match exactly; this never requests raw engine state.\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const SnapshotRequest = Schema.Struct({\n ...snapshotIdentity,\n atLeastSeq: JournalEvent.Seq,\n capability: Schema.optional(ShareCapability)\n})\n\n/** A decoded request for an explicitly public projection.\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type SnapshotRequest = typeof SnapshotRequest.Type\n\n/**\n * A complete public projection through `seq`, inclusive. Apply its state and\n * durable cursor atomically, then read entries strictly after `seq`.\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const Snapshot = Schema.Struct({ ...snapshotIdentity, seq: JournalEvent.Seq, state: Schema.Json })\n\n/** A complete, versioned public projection at one journal sequence.\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type Snapshot = typeof Snapshot.Type\n\n/**\n * Largest number of entries one {@link ReadRequest} may ask for.\n *\n * `limit` is request-controlled fan-out: the server passes it to the journal\n * as the page size, once per covered run, so an unbounded limit made one\n * workspace read materialize an unbounded number of rows per run. The frame\n * ceiling does not cover it — that drops entries AFTER they have been read.\n * A request above this bound is refused at the wire, and an in-process caller\n * that bypasses the schema is clamped to it.\n *\n * @category limits\n * @since 0.1.0\n */\nexport const maxReadLimit = 1024\n\n/**\n * Largest number of frames one {@link SubscribeRequest} may hold open.\n *\n * Credit is a hard frame limit, so it is also how long one subscription may\n * pin a server-side fan-out. The bound keeps that lifetime a function of the\n * protocol rather than of what the caller asks for; a follower that wants\n * more resubscribes from the cursors it has, which is what\n * `SyncClient.defaultCredit` already does.\n *\n * @category limits\n * @since 0.1.0\n */\nexport const maxSubscribeCredit = 4096\n\n/**\n * A durable catch-up request.\n *\n * `capability` authorizes branch runs: a run whose id maps to a shared branch\n * is only read when the capability verifies for that branch. Non-branch runs\n * ignore it.\n *\n * `limit` is bounded on both sides: at least one entry, at most\n * {@link maxReadLimit}.\n *\n * @category models\n * @since 0.1.0\n */\nexport const ReadRequest = Schema.Struct({\n protocolVersion: Schema.optional(Schema.Int),\n scope: Scope,\n cursors: WorkspaceCursor,\n limit: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(maxReadLimit)),\n capability: Schema.optional(ShareCapability)\n})\n\n/**\n * A durable catch-up request.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ReadRequest = typeof ReadRequest.Type\n\n/**\n * One page of durable catch-up entries.\n *\n * `done` reports that the page reached the durable tail, which is the point at\n * which a client may switch from paging to following.\n *\n * @category models\n * @since 0.1.0\n */\nexport const ReadResponse = Schema.Struct({\n entries: Schema.Array(JournalEvent.Entry),\n cursors: WorkspaceCursor,\n done: Schema.Boolean\n})\n\n/**\n * One page of durable catch-up entries.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ReadResponse = typeof ReadResponse.Type\n\n/**\n * One page of durable catch-up entries as a SERVER states it.\n *\n * Every cursor carries its generation. {@link SyncServer.Service} answers with\n * this shape; {@link ReadResponse} is the lenient shape a client decodes an\n * untrusted server's page into before refusing a missing generation.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const ServerReadResponse = Schema.Struct({\n ...ReadResponse.fields,\n cursors: Schema.Array(ServerCursor)\n})\n\n/**\n * One page of durable catch-up entries as a server states it.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ServerReadResponse = typeof ServerReadResponse.Type\n\n/**\n * A request to follow committed entries.\n *\n * `capability` authorizes branch runs exactly as on {@link ReadRequest}.\n *\n * `credit` is bounded on both sides: at least one frame, at most\n * {@link maxSubscribeCredit}. Zero is refused rather than served as an\n * immediately-empty stream, because the two readings of it — \"open a window\n * of nothing\" and \"a caller computed its window wrong\" — are\n * indistinguishable on the wire, and the second one busy-loops a follow that\n * replenishes by resubscribing.\n *\n * @category models\n * @since 0.1.0\n */\nexport const SubscribeRequest = Schema.Struct({\n protocolVersion: Schema.optional(Schema.Int),\n scope: Scope,\n cursors: WorkspaceCursor,\n credit: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(maxSubscribeCredit)),\n capability: Schema.optional(ShareCapability)\n})\n\n/**\n * A request to follow committed entries.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SubscribeRequest = typeof SubscribeRequest.Type\n\n/**\n * A contiguous batch of committed entries for one run.\n *\n * `fromSeq` and `toSeq` describe the interval the server covered, not the\n * sequences actually carried: dropped admissions leave legitimate holes.\n * `generation` identifies the history covering the interval and is mandatory\n * on server responses, including zero. Clients refuse its omission before\n * deduplicating by sequence. The optional schema permits a typed refusal.\n *\n * @category models\n * @since 0.1.0\n */\nexport const EntriesFrame = Schema.TaggedStruct(\"Entries\", {\n runId: JournalEvent.RunId,\n generation: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),\n fromSeq: JournalEvent.Seq,\n toSeq: JournalEvent.Seq,\n entries: Schema.Array(JournalEvent.Entry)\n})\n\n/**\n * A contiguous batch of committed entries for one run.\n *\n * @category models\n * @since 0.1.0\n */\nexport type EntriesFrame = typeof EntriesFrame.Type\n\n/**\n * A liveness frame reserved for a future revision, which no rc.0 server emits.\n *\n * No server in this package constructs one at rc.0, and a client must not wait\n * for one: {@link SyncClient} tolerates the variant and ignores it, which is\n * the whole of the contract today. It stays in the {@link Frame} union so\n * adding the frame later is not a wire break, and so a third-party client\n * written against this schema already ignores it.\n *\n * Keepalive is a transport concern at rc.0. Emitting a heartbeat here would\n * spend the subscription's `credit`, which is a hard frame limit, so an idle\n * follow would resubscribe on a timer instead of staying quiet.\n *\n * @category models\n * @since 0.1.0\n */\nexport const HeartbeatFrame = Schema.TaggedStruct(\"Heartbeat\", {})\n\n/**\n * A terminal frame reporting that the server ended the subscription.\n *\n * @category models\n * @since 0.1.0\n */\nexport const ClosedFrame = Schema.TaggedStruct(\"Closed\", {\n reason: Schema.optional(Schema.String)\n})\n\n/**\n * Any frame a subscription may emit.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Frame = Schema.Union([EntriesFrame, HeartbeatFrame, ClosedFrame])\n\n/**\n * Any frame a subscription may emit.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Frame = typeof Frame.Type\n\n/**\n * A contiguous batch of committed entries as a SERVER states it.\n *\n * `generation` is required here: a server that covers an interval knows the\n * history it read, so omitting it is a compile error rather than a run-time\n * refusal. {@link EntriesFrame} is the lenient shape a client decodes an\n * untrusted server's frame into.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const ServerEntriesFrame = Schema.TaggedStruct(\"Entries\", {\n runId: JournalEvent.RunId,\n generation: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n fromSeq: JournalEvent.Seq,\n toSeq: JournalEvent.Seq,\n entries: Schema.Array(JournalEvent.Entry)\n})\n\n/**\n * A contiguous batch of committed entries as a server states it.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ServerEntriesFrame = typeof ServerEntriesFrame.Type\n\n/**\n * Any frame a subscription may emit, as a server states it.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const ServerFrame = Schema.Union([ServerEntriesFrame, HeartbeatFrame, ClosedFrame])\n\n/**\n * Any frame a subscription may emit, as a server states it.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ServerFrame = typeof ServerFrame.Type\n\n/**\n * Whether a scope covers a run.\n *\n * @category predicates\n * @since 0.1.0\n */\nexport const covers = (scope: Scope, runId: JournalEvent.RunId): boolean =>\n scope._tag === \"Workspace\" || scope.runId === runId\n\n/**\n * Default ceiling on the encoded entries one frame or page may\n * carry, in bytes.\n *\n * Both ends enforce the sum of encoded journal entries, excluding the outer\n * RPC/frame envelope. Transports must allow their own envelope overhead.\n * Commands default to 1 MiB; the 2 MiB entry ceiling includes their durable\n * journal envelope and keeps already-admitted maximum commands consumable.\n *\n * @category limits\n * @since 0.1.0\n */\nexport const defaultMaxFrameBytes = 2 * 1024 * 1024\n\nconst utf8 = new TextEncoder()\n\n/** The four bytes `null` occupies, which is what a value with no JSON text\n * costs once it is inside an encoded frame. */\nconst nullByteLength = 4\n\n/**\n * The wire size of one value: the UTF-8 byte length of its JSON text.\n *\n * Ceilings measure this encoded form rather than in-memory object size, so a\n * limit tracks what a transport actually carries.\n *\n * The function is TOTAL, because every caller is a size guard whose whole job\n * is to produce a typed `frame_too_large` refusal rather than a defect:\n *\n * - A value with no JSON text at all — `undefined`, a function, a symbol —\n * measures {@link nullByteLength}, the bytes it occupies once an enclosing\n * array or object encodes it as `null`. It used to measure zero, which let\n * an unencodable value pass every ceiling.\n * - A value `JSON.stringify` refuses — a cycle, a `bigint`, a throwing\n * `toJSON` — measures `Infinity`, so it trips every ceiling instead of\n * throwing a `TypeError` out of code that only ever fails typed.\n *\n * It measures the JSON text, so a `toJSON` method, a getter, or a proxy trap\n * on the value runs while the size is taken; the read path measures each\n * entry once per end. Entries that arrive over the wire are schema-decoded\n * data and have none of those.\n *\n * @category limits\n * @since 0.1.0\n */\nexport const encodedByteLength = (value: unknown): number => {\n let text: string | undefined\n try {\n text = JSON.stringify(value)\n } catch {\n return Number.POSITIVE_INFINITY\n }\n return text === undefined ? nullByteLength : utf8.encode(text).length\n}\n"]}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Schema-backed RPC projection of the sync read path.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import { Rpc, RpcGroup, RpcMiddleware } from "effect/unstable/rpc";
7
+ import { SyncError } from "./SyncError.ts";
8
+ declare const SyncAuth_base: RpcMiddleware.ServiceClass<SyncAuth, "@smthrs/sync/SyncAuth", never, typeof SyncError, never, never, false>;
9
+ /**
10
+ * Authentication boundary for the sync RPC group.
11
+ *
12
+ * The middleware authenticates each request to a `SyncPrincipal` before the
13
+ * handler runs. The production implementation is `SyncAuth.layer`, which
14
+ * verifies the workspace capability presented in the request headers and
15
+ * installs the resulting principal; a request without a credential runs as
16
+ * the anonymous principal, which the server refuses for every non-branch
17
+ * read.
18
+ *
19
+ * @category middleware
20
+ * @since 0.1.0
21
+ */
22
+ export declare class SyncAuth extends SyncAuth_base {
23
+ }
24
+ /**
25
+ * The remote procedures of the sync read path.
26
+ *
27
+ * A subscription's `credit` is a hard frame limit. Clients reconnect with
28
+ * their materialized cursor to replenish it; there is no separate
29
+ * acknowledgement channel.
30
+ *
31
+ * @category groups
32
+ * @since 0.1.0
33
+ */
34
+ export declare const SyncRpcs: RpcGroup.RpcGroup<Rpc.Rpc<"Sync.Snapshot", import("effect/Schema").Struct<{
35
+ readonly atLeastSeq: import("effect/Schema").brand<import("effect/Schema").Int, "@smthrs/journal/JournalEvent/Seq">;
36
+ readonly capability: import("effect/Schema").optional<typeof import("./BranchProtocol.ts").ShareCapability>;
37
+ readonly protocolVersion: import("effect/Schema").Int;
38
+ readonly runId: import("effect/Schema").brand<import("effect/Schema").String, "@smthrs/journal/JournalEvent/RunId">;
39
+ readonly lineageId: import("effect/Schema").NonEmptyString;
40
+ readonly projection: import("effect/Schema").NonEmptyString;
41
+ readonly projectionVersion: import("effect/Schema").Int;
42
+ }>, import("effect/Schema").Struct<{
43
+ readonly seq: import("effect/Schema").brand<import("effect/Schema").Int, "@smthrs/journal/JournalEvent/Seq">;
44
+ readonly state: import("effect/Schema").Codec<import("effect/Schema").Json, import("effect/Schema").Json, never, never>;
45
+ readonly protocolVersion: import("effect/Schema").Int;
46
+ readonly runId: import("effect/Schema").brand<import("effect/Schema").String, "@smthrs/journal/JournalEvent/RunId">;
47
+ readonly lineageId: import("effect/Schema").NonEmptyString;
48
+ readonly projection: import("effect/Schema").NonEmptyString;
49
+ readonly projectionVersion: import("effect/Schema").Int;
50
+ }>, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Sync.Read", import("effect/Schema").Struct<{
51
+ readonly protocolVersion: import("effect/Schema").optional<import("effect/Schema").Int>;
52
+ readonly scope: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Workspace", {}>, import("effect/Schema").TaggedStruct<"Run", {
53
+ readonly runId: import("effect/Schema").brand<import("effect/Schema").String, "@smthrs/journal/JournalEvent/RunId">;
54
+ }>]>;
55
+ readonly cursors: import("effect/Schema").$Array<import("effect/Schema").Struct<{
56
+ readonly runId: import("effect/Schema").brand<import("effect/Schema").String, "@smthrs/journal/JournalEvent/RunId">;
57
+ readonly afterSeq: import("effect/Schema").brand<import("effect/Schema").Int, "@smthrs/journal/JournalEvent/Seq">;
58
+ readonly generation: import("effect/Schema").optional<import("effect/Schema").Int>;
59
+ }>>;
60
+ readonly limit: import("effect/Schema").Int;
61
+ readonly capability: import("effect/Schema").optional<typeof import("./BranchProtocol.ts").ShareCapability>;
62
+ }>, import("effect/Schema").Struct<{
63
+ readonly entries: import("effect/Schema").$Array<typeof import("@smthrs/journal/JournalEvent").Entry>;
64
+ readonly cursors: import("effect/Schema").$Array<import("effect/Schema").Struct<{
65
+ readonly runId: import("effect/Schema").brand<import("effect/Schema").String, "@smthrs/journal/JournalEvent/RunId">;
66
+ readonly afterSeq: import("effect/Schema").brand<import("effect/Schema").Int, "@smthrs/journal/JournalEvent/Seq">;
67
+ readonly generation: import("effect/Schema").optional<import("effect/Schema").Int>;
68
+ }>>;
69
+ readonly done: import("effect/Schema").Boolean;
70
+ }>, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Sync.Subscribe", import("effect/Schema").Struct<{
71
+ readonly protocolVersion: import("effect/Schema").optional<import("effect/Schema").Int>;
72
+ readonly scope: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Workspace", {}>, import("effect/Schema").TaggedStruct<"Run", {
73
+ readonly runId: import("effect/Schema").brand<import("effect/Schema").String, "@smthrs/journal/JournalEvent/RunId">;
74
+ }>]>;
75
+ readonly cursors: import("effect/Schema").$Array<import("effect/Schema").Struct<{
76
+ readonly runId: import("effect/Schema").brand<import("effect/Schema").String, "@smthrs/journal/JournalEvent/RunId">;
77
+ readonly afterSeq: import("effect/Schema").brand<import("effect/Schema").Int, "@smthrs/journal/JournalEvent/Seq">;
78
+ readonly generation: import("effect/Schema").optional<import("effect/Schema").Int>;
79
+ }>>;
80
+ readonly credit: import("effect/Schema").Int;
81
+ readonly capability: import("effect/Schema").optional<typeof import("./BranchProtocol.ts").ShareCapability>;
82
+ }>, import("effect/unstable/rpc/RpcSchema").Stream<import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Entries", {
83
+ readonly runId: import("effect/Schema").brand<import("effect/Schema").String, "@smthrs/journal/JournalEvent/RunId">;
84
+ readonly generation: import("effect/Schema").optional<import("effect/Schema").Int>;
85
+ readonly fromSeq: import("effect/Schema").brand<import("effect/Schema").Int, "@smthrs/journal/JournalEvent/Seq">;
86
+ readonly toSeq: import("effect/Schema").brand<import("effect/Schema").Int, "@smthrs/journal/JournalEvent/Seq">;
87
+ readonly entries: import("effect/Schema").$Array<typeof import("@smthrs/journal/JournalEvent").Entry>;
88
+ }>, import("effect/Schema").TaggedStruct<"Heartbeat", {}>, import("effect/Schema").TaggedStruct<"Closed", {
89
+ readonly reason: import("effect/Schema").optional<import("effect/Schema").String>;
90
+ }>]>, typeof SyncError>, import("effect/Schema").Never, typeof SyncAuth, never>>;
91
+ export {};
92
+ //# sourceMappingURL=SyncRpcs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncRpcs.d.ts","sourceRoot":"","sources":["../../src/SyncRpcs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;;AAG1C;;;;;;;;;;;;GAYG;AACH,qBAAa,QAAS,SAAQ,aAAgF;CAAG;AAEjH;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gFAIC,CAAA"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Schema-backed RPC projection of the sync read path.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import { Rpc, RpcGroup, RpcMiddleware } from "effect/unstable/rpc";
7
+ import { SyncError } from "./SyncError.js";
8
+ import { Frame, ReadRequest, ReadResponse, Snapshot, SnapshotRequest, SubscribeRequest } from "./SyncProtocol.js";
9
+ /**
10
+ * Authentication boundary for the sync RPC group.
11
+ *
12
+ * The middleware authenticates each request to a `SyncPrincipal` before the
13
+ * handler runs. The production implementation is `SyncAuth.layer`, which
14
+ * verifies the workspace capability presented in the request headers and
15
+ * installs the resulting principal; a request without a credential runs as
16
+ * the anonymous principal, which the server refuses for every non-branch
17
+ * read.
18
+ *
19
+ * @category middleware
20
+ * @since 0.1.0
21
+ */
22
+ export class SyncAuth extends RpcMiddleware.Service()("@smthrs/sync/SyncAuth", { error: SyncError }) {
23
+ }
24
+ /**
25
+ * The remote procedures of the sync read path.
26
+ *
27
+ * A subscription's `credit` is a hard frame limit. Clients reconnect with
28
+ * their materialized cursor to replenish it; there is no separate
29
+ * acknowledgement channel.
30
+ *
31
+ * @category groups
32
+ * @since 0.1.0
33
+ */
34
+ export const SyncRpcs = RpcGroup.make(Rpc.make("Sync.Snapshot", { payload: SnapshotRequest, success: Snapshot, error: SyncError }), Rpc.make("Sync.Read", { payload: ReadRequest, success: ReadResponse, error: SyncError }), Rpc.make("Sync.Subscribe", { payload: SubscribeRequest, success: Frame, error: SyncError, stream: true })).middleware(SyncAuth);
35
+ //# sourceMappingURL=SyncRpcs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncRpcs.js","sourceRoot":"","sources":["../../src/SyncRpcs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEjH;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,QAAS,SAAQ,aAAa,CAAC,OAAO,EAAY,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;CAAG;AAEjH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CACnC,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAC5F,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EACxF,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAC1G,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA","sourcesContent":["/**\n * Schema-backed RPC projection of the sync read path.\n *\n * @since 0.1.0\n */\n\nimport { Rpc, RpcGroup, RpcMiddleware } from \"effect/unstable/rpc\"\nimport { SyncError } from \"./SyncError.ts\"\nimport { Frame, ReadRequest, ReadResponse, Snapshot, SnapshotRequest, SubscribeRequest } from \"./SyncProtocol.ts\"\n\n/**\n * Authentication boundary for the sync RPC group.\n *\n * The middleware authenticates each request to a `SyncPrincipal` before the\n * handler runs. The production implementation is `SyncAuth.layer`, which\n * verifies the workspace capability presented in the request headers and\n * installs the resulting principal; a request without a credential runs as\n * the anonymous principal, which the server refuses for every non-branch\n * read.\n *\n * @category middleware\n * @since 0.1.0\n */\nexport class SyncAuth extends RpcMiddleware.Service<SyncAuth>()(\"@smthrs/sync/SyncAuth\", { error: SyncError }) {}\n\n/**\n * The remote procedures of the sync read path.\n *\n * A subscription's `credit` is a hard frame limit. Clients reconnect with\n * their materialized cursor to replenish it; there is no separate\n * acknowledgement channel.\n *\n * @category groups\n * @since 0.1.0\n */\nexport const SyncRpcs = RpcGroup.make(\n Rpc.make(\"Sync.Snapshot\", { payload: SnapshotRequest, success: Snapshot, error: SyncError }),\n Rpc.make(\"Sync.Read\", { payload: ReadRequest, success: ReadResponse, error: SyncError }),\n Rpc.make(\"Sync.Subscribe\", { payload: SubscribeRequest, success: Frame, error: SyncError, stream: true })\n).middleware(SyncAuth)\n"]}