@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,192 @@
1
+ /**
2
+ * Deterministic in-memory socket transport for sync tests.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+
7
+ import * as Deferred from "effect/Deferred"
8
+ import * as Effect from "effect/Effect"
9
+ import * as Latch from "effect/Latch"
10
+ import * as Queue from "effect/Queue"
11
+ import * as Scope from "effect/Scope"
12
+ import * as Socket from "effect/unstable/socket/Socket"
13
+
14
+ /**
15
+ * A predicate or byte transformation installed between the two socket ends.
16
+ * Returning `false` drops the frame.
17
+ *
18
+ * @category models
19
+ * @since 0.1.0
20
+ */
21
+ export type FrameFilter = (bytes: Uint8Array) => boolean | Uint8Array
22
+
23
+ /**
24
+ * Deterministic transport failures that can be injected into a test socket pair.
25
+ *
26
+ * `dropRange` recognizes the JSON representation of an `Entries` frame without
27
+ * importing the sync protocol. Tests using another wire representation can use
28
+ * `installFilter` directly.
29
+ *
30
+ * @category models
31
+ * @since 0.1.0
32
+ */
33
+ export interface TestFaults {
34
+ readonly installFilter: (filter: FrameFilter) => void
35
+ readonly dropRange: (runId: string, from: number, to: number) => void
36
+ readonly stall: () => void
37
+ readonly resume: () => void
38
+ readonly disconnect: () => Effect.Effect<void>
39
+ }
40
+
41
+ /**
42
+ * The two endpoints and controls of an in-memory socket connection.
43
+ *
44
+ * @category models
45
+ * @since 0.1.0
46
+ */
47
+ export interface Pair {
48
+ readonly client: Socket.Socket
49
+ readonly server: Socket.Socket
50
+ readonly faults: TestFaults
51
+ }
52
+
53
+ const queueCapacity = 16
54
+
55
+ const transportError = () =>
56
+ new Socket.SocketError({
57
+ reason: new Socket.SocketReadError({ cause: new Error("test socket disconnected") })
58
+ })
59
+
60
+ const intersects = (leftFrom: number, leftTo: number, rightFrom: number, rightTo: number) =>
61
+ leftFrom <= rightTo && rightFrom <= leftTo
62
+
63
+ const entriesRange = (
64
+ bytes: Uint8Array
65
+ ): { readonly runId: string; readonly from: number; readonly to: number } | undefined => {
66
+ try {
67
+ const value: unknown = JSON.parse(new TextDecoder().decode(bytes))
68
+ return findEntriesRange(value)
69
+ } catch {
70
+ return undefined
71
+ }
72
+ }
73
+
74
+ const findEntriesRange = (
75
+ value: unknown
76
+ ): { readonly runId: string; readonly from: number; readonly to: number } | undefined => {
77
+ if (typeof value !== "object" || value === null) return undefined
78
+ const record = value as Readonly<Record<string, unknown>>
79
+ if (
80
+ record["_tag"] === "Entries" &&
81
+ typeof record["runId"] === "string" &&
82
+ typeof record["fromSeq"] === "number" &&
83
+ typeof record["toSeq"] === "number"
84
+ ) {
85
+ return { runId: record["runId"], from: record["fromSeq"], to: record["toSeq"] }
86
+ }
87
+ for (const child of Object.values(record)) {
88
+ if (Array.isArray(child)) {
89
+ for (const element of child) {
90
+ const result = findEntriesRange(element)
91
+ if (result !== undefined) return result
92
+ }
93
+ } else {
94
+ const result = findEntriesRange(child)
95
+ if (result !== undefined) return result
96
+ }
97
+ }
98
+ return undefined
99
+ }
100
+
101
+ /**
102
+ * Creates a scoped, bounded, bidirectional in-memory socket connection.
103
+ *
104
+ * Socket runs wait on queue reads and are therefore interrupted with their
105
+ * enclosing scope; no host abort signal or timer is involved.
106
+ *
107
+ * @category constructors
108
+ * @since 0.1.0
109
+ */
110
+ export const makePair = (): Effect.Effect<Pair, never, Scope.Scope> =>
111
+ Effect.gen(function*() {
112
+ const scope = yield* Effect.scope
113
+ const clientIncoming = yield* Queue.bounded<Uint8Array, Socket.SocketError>(queueCapacity)
114
+ const serverIncoming = yield* Queue.bounded<Uint8Array, Socket.SocketError>(queueCapacity)
115
+ yield* Scope.addFinalizer(
116
+ scope,
117
+ Effect.all([Queue.shutdown(clientIncoming), Queue.shutdown(serverIncoming)], { discard: true })
118
+ )
119
+ const delivery = Latch.makeUnsafe(true)
120
+ const filters: Array<FrameFilter> = []
121
+
122
+ const offer = (queue: Queue.Queue<Uint8Array, Socket.SocketError>, bytes: Uint8Array) =>
123
+ Effect.suspend(() => {
124
+ let current = bytes
125
+ for (const filter of filters) {
126
+ const result = filter(current)
127
+ if (typeof result === "boolean") {
128
+ if (result === false) return Effect.void
129
+ } else {
130
+ current = result
131
+ }
132
+ }
133
+ return Queue.offer(queue, current).pipe(Effect.asVoid)
134
+ })
135
+
136
+ const socket = (
137
+ incoming: Queue.Queue<Uint8Array, Socket.SocketError>,
138
+ outgoing: Queue.Queue<Uint8Array, Socket.SocketError>
139
+ ) => {
140
+ const write: Socket.Writer["write"] = (chunk) => {
141
+ if (Socket.isCloseEvent(chunk)) return Effect.void
142
+ const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk
143
+ return offer(outgoing, bytes)
144
+ }
145
+ return Socket.make({
146
+ reader: Effect.gen(function*() {
147
+ const closed = yield* Deferred.make<never, Socket.SocketError>()
148
+ yield* Effect.addFinalizer(() => Deferred.fail(closed, transportError()))
149
+ return {
150
+ pull: Effect.raceFirst(
151
+ Effect.map(delivery.whenOpen(Queue.take(incoming)), (bytes) => [bytes] as const),
152
+ Deferred.await(closed)
153
+ ),
154
+ upgrade: Socket.SocketUpgradeError.unsupported
155
+ }
156
+ }),
157
+ writer: Effect.succeed({
158
+ write,
159
+ writeAll: (chunks) => Effect.forEach(chunks, write, { discard: true })
160
+ })
161
+ })
162
+ }
163
+
164
+ const faults: TestFaults = {
165
+ installFilter: (filter) => {
166
+ filters.push(filter)
167
+ },
168
+ dropRange: (runId, from, to) => {
169
+ filters.push((bytes) => {
170
+ const range = entriesRange(bytes)
171
+ return range === undefined || range.runId !== runId || !intersects(range.from, range.to, from, to)
172
+ })
173
+ },
174
+ stall: () => {
175
+ delivery.closeUnsafe()
176
+ },
177
+ resume: () => {
178
+ delivery.openUnsafe()
179
+ },
180
+ disconnect: () =>
181
+ Effect.andThen(
182
+ Queue.fail(clientIncoming, transportError()),
183
+ Queue.fail(serverIncoming, transportError())
184
+ ).pipe(Effect.asVoid)
185
+ }
186
+
187
+ return {
188
+ client: socket(clientIncoming, serverIncoming),
189
+ server: socket(serverIncoming, clientIncoming),
190
+ faults
191
+ }
192
+ })
@@ -0,0 +1,113 @@
1
+ /**
2
+ * In-process sync service and RPC transport wiring for tests.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+
7
+ import * as TestJournal from "@smthrs/journal/test/TestJournal"
8
+ import * as Effect from "effect/Effect"
9
+ import * as Layer from "effect/Layer"
10
+ import * as NetAddress from "effect/unstable/net/NetAddress"
11
+ import * as RpcClient from "effect/unstable/rpc/RpcClient"
12
+ import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization"
13
+ import * as RpcServer from "effect/unstable/rpc/RpcServer"
14
+ import * as Socket from "effect/unstable/socket/Socket"
15
+ import * as SocketServer from "effect/unstable/socket/SocketServer"
16
+ import * as RunCatalog from "../RunCatalog.ts"
17
+ import * as SyncClient from "../SyncClient.ts"
18
+ import * as SyncPrincipal from "../SyncPrincipal.ts"
19
+ import * as SyncRpcs from "../SyncRpcs.ts"
20
+ import * as SyncServer from "../SyncServer.ts"
21
+ import type { Pair } from "./TestSocket.ts"
22
+
23
+ const layerNoopAuth = Layer.succeed(SyncRpcs.SyncAuth)((effect) => effect)
24
+
25
+ /**
26
+ * An authentication middleware that trusts every connection as the workspace
27
+ * owner. Test-only by construction: replication-mechanics suites use it so
28
+ * they exercise paging and streaming without provisioning capabilities,
29
+ * while the authentication suites use the production `SyncAuth.layer`.
30
+ *
31
+ * Never compose it into a served transport: every network client would read
32
+ * every workspace run as the owner. The name says so at the call site.
33
+ *
34
+ * @category layers
35
+ * @since 1.0.0-rc.1
36
+ */
37
+ export const layerTrustAllAsOwner = Layer.succeed(SyncRpcs.SyncAuth)((effect) =>
38
+ Effect.provideService(effect, SyncPrincipal.SyncPrincipal, SyncPrincipal.workspace("test-workspace-owner"))
39
+ )
40
+
41
+ /**
42
+ * Provides the durable in-memory journal, a mutable empty run catalog, the
43
+ * owner-trusting {@link layerTrustAllAsOwner} middleware, and the production
44
+ * sync server.
45
+ *
46
+ * Tests that need runs in the workspace may register them through their own
47
+ * `RunCatalog.makeMemory` fixture before providing a more specific layer.
48
+ *
49
+ * @category layers
50
+ * @since 0.1.0
51
+ */
52
+ export const layerTest = SyncServer.layer.pipe(
53
+ Layer.provideMerge(
54
+ Layer.mergeAll(
55
+ TestJournal.layer(),
56
+ RunCatalog.layerMemory(),
57
+ layerTrustAllAsOwner
58
+ )
59
+ )
60
+ )
61
+
62
+ /**
63
+ * Provides no-op sync dependencies for consumers that only require the ports.
64
+ *
65
+ * @category layers
66
+ * @since 0.1.0
67
+ */
68
+ export const layerNoop = Layer.mergeAll(
69
+ SyncClient.layerNoop(),
70
+ SyncServer.layerNoop,
71
+ RunCatalog.layerNoop,
72
+ layerNoopAuth
73
+ )
74
+
75
+ /**
76
+ * Starts an RPC server over the pair's server endpoint and returns a sync
77
+ * client over the client endpoint. JSON serialization is deliberately used so
78
+ * frame faults can inspect test traffic without coupling to sync schemas.
79
+ *
80
+ * @category constructors
81
+ * @since 0.1.0
82
+ */
83
+ export const connect = (pair: Pair) =>
84
+ Effect.gen(function*() {
85
+ const sync = yield* SyncServer.SyncServer
86
+ const handlers = SyncServer.layerHandlers.pipe(
87
+ Layer.provide(Layer.succeed(SyncServer.SyncServer, sync))
88
+ )
89
+ // Use the production schema-aware protocol. JSON.stringify on the raw
90
+ // makeNoSerialization responses happened to encode successes, but not
91
+ // Effect's Cause representation: typed RPC failures never reached clients.
92
+ const serverProtocol = yield* RpcServer.makeProtocolSocketServer.pipe(
93
+ Effect.provideService(SocketServer.SocketServer, {
94
+ address: NetAddress.inetAddressFromIpStringUnsafe("127.0.0.1", 0),
95
+ run: (handler) => handler(pair.server).pipe(Effect.orDie, Effect.andThen(Effect.never))
96
+ }),
97
+ Effect.provide(RpcSerialization.layerJson)
98
+ )
99
+ yield* RpcServer.make(SyncRpcs.SyncRpcs, { disableFatalDefects: true }).pipe(
100
+ Effect.provideService(RpcServer.Protocol, serverProtocol),
101
+ Effect.provide(handlers),
102
+ Effect.forkScoped
103
+ )
104
+
105
+ const protocol = yield* RpcClient.makeProtocolSocket().pipe(
106
+ Effect.provideService(Socket.Socket, pair.client),
107
+ Effect.provide(RpcSerialization.layerJson)
108
+ )
109
+ const client = yield* RpcClient.make(SyncRpcs.SyncRpcs).pipe(
110
+ Effect.provideService(RpcClient.Protocol, protocol)
111
+ )
112
+ return yield* SyncClient.make({ client })
113
+ })