@smthrs/sync 0.0.0-stage → 1.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +208 -0
- package/LICENSE +21 -0
- package/README.md +208 -2
- package/dist/cjs/BranchCommands.d.ts +228 -0
- package/dist/cjs/BranchCommands.d.ts.map +1 -0
- package/dist/cjs/BranchCommands.js +316 -0
- package/dist/cjs/BranchCommands.js.map +7 -0
- package/dist/cjs/BranchIds.d.ts +65 -0
- package/dist/cjs/BranchIds.d.ts.map +1 -0
- package/dist/cjs/BranchIds.js +53 -0
- package/dist/cjs/BranchIds.js.map +7 -0
- package/dist/cjs/BranchPresence.d.ts +195 -0
- package/dist/cjs/BranchPresence.d.ts.map +1 -0
- package/dist/cjs/BranchPresence.js +276 -0
- package/dist/cjs/BranchPresence.js.map +7 -0
- package/dist/cjs/BranchProjection.d.ts +179 -0
- package/dist/cjs/BranchProjection.d.ts.map +1 -0
- package/dist/cjs/BranchProjection.js +197 -0
- package/dist/cjs/BranchProjection.js.map +7 -0
- package/dist/cjs/BranchProtocol.d.ts +415 -0
- package/dist/cjs/BranchProtocol.d.ts.map +1 -0
- package/dist/cjs/BranchProtocol.js +172 -0
- package/dist/cjs/BranchProtocol.js.map +7 -0
- package/dist/cjs/BranchRpcs.d.ts +177 -0
- package/dist/cjs/BranchRpcs.d.ts.map +1 -0
- package/dist/cjs/BranchRpcs.js +85 -0
- package/dist/cjs/BranchRpcs.js.map +7 -0
- package/dist/cjs/BranchServer.d.ts +55 -0
- package/dist/cjs/BranchServer.d.ts.map +1 -0
- package/dist/cjs/BranchServer.js +147 -0
- package/dist/cjs/BranchServer.js.map +7 -0
- package/dist/cjs/BranchShare.d.ts +159 -0
- package/dist/cjs/BranchShare.d.ts.map +1 -0
- package/dist/cjs/BranchShare.js +147 -0
- package/dist/cjs/BranchShare.js.map +7 -0
- package/dist/cjs/RunCatalog.d.ts +207 -0
- package/dist/cjs/RunCatalog.d.ts.map +1 -0
- package/dist/cjs/RunCatalog.js +122 -0
- package/dist/cjs/RunCatalog.js.map +7 -0
- package/dist/cjs/SyncAuth.d.ts +70 -0
- package/dist/cjs/SyncAuth.d.ts.map +1 -0
- package/dist/cjs/SyncAuth.js +109 -0
- package/dist/cjs/SyncAuth.js.map +7 -0
- package/dist/cjs/SyncClient.d.ts +274 -0
- package/dist/cjs/SyncClient.d.ts.map +1 -0
- package/dist/cjs/SyncClient.js +474 -0
- package/dist/cjs/SyncClient.js.map +7 -0
- package/dist/cjs/SyncError.d.ts +107 -0
- package/dist/cjs/SyncError.d.ts.map +1 -0
- package/dist/cjs/SyncError.js +110 -0
- package/dist/cjs/SyncError.js.map +7 -0
- package/dist/cjs/SyncPrincipal.d.ts +100 -0
- package/dist/cjs/SyncPrincipal.d.ts.map +1 -0
- package/dist/cjs/SyncPrincipal.js +48 -0
- package/dist/cjs/SyncPrincipal.js.map +7 -0
- package/dist/cjs/SyncProtocol.d.ts +592 -0
- package/dist/cjs/SyncProtocol.d.ts.map +1 -0
- package/dist/cjs/SyncProtocol.js +165 -0
- package/dist/cjs/SyncProtocol.js.map +7 -0
- package/dist/cjs/SyncRpcs.d.ts +92 -0
- package/dist/cjs/SyncRpcs.d.ts.map +1 -0
- package/dist/cjs/SyncRpcs.js +35 -0
- package/dist/cjs/SyncRpcs.js.map +7 -0
- package/dist/cjs/SyncServer.d.ts +184 -0
- package/dist/cjs/SyncServer.d.ts.map +1 -0
- package/dist/cjs/SyncServer.js +513 -0
- package/dist/cjs/SyncServer.js.map +7 -0
- package/dist/cjs/WorkspaceShare.d.ts +174 -0
- package/dist/cjs/WorkspaceShare.d.ts.map +1 -0
- package/dist/cjs/WorkspaceShare.js +149 -0
- package/dist/cjs/WorkspaceShare.js.map +7 -0
- package/dist/cjs/index.d.ts +91 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +67 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/Admission.d.ts +43 -0
- package/dist/cjs/internal/Admission.d.ts.map +1 -0
- package/dist/cjs/internal/Admission.js +90 -0
- package/dist/cjs/internal/Admission.js.map +7 -0
- package/dist/cjs/internal/CauseText.d.ts +55 -0
- package/dist/cjs/internal/CauseText.d.ts.map +1 -0
- package/dist/cjs/internal/CauseText.js +38 -0
- package/dist/cjs/internal/CauseText.js.map +7 -0
- package/dist/cjs/internal/JournalErrorCode.d.ts +23 -0
- package/dist/cjs/internal/JournalErrorCode.d.ts.map +1 -0
- package/dist/cjs/internal/JournalErrorCode.js +34 -0
- package/dist/cjs/internal/JournalErrorCode.js.map +7 -0
- package/dist/cjs/internal/PolicyOptions.d.ts +57 -0
- package/dist/cjs/internal/PolicyOptions.d.ts.map +1 -0
- package/dist/cjs/internal/PolicyOptions.js +62 -0
- package/dist/cjs/internal/PolicyOptions.js.map +7 -0
- package/dist/cjs/internal/ProtocolVersion.d.ts +17 -0
- package/dist/cjs/internal/ProtocolVersion.d.ts.map +1 -0
- package/dist/cjs/internal/ProtocolVersion.js +44 -0
- package/dist/cjs/internal/ProtocolVersion.js.map +7 -0
- package/dist/cjs/internal/ShareSigner.d.ts +173 -0
- package/dist/cjs/internal/ShareSigner.d.ts.map +1 -0
- package/dist/cjs/internal/ShareSigner.js +150 -0
- package/dist/cjs/internal/ShareSigner.js.map +7 -0
- package/dist/cjs/internal/SnapshotBoundary.d.ts +17 -0
- package/dist/cjs/internal/SnapshotBoundary.d.ts.map +1 -0
- package/dist/cjs/internal/SnapshotBoundary.js +97 -0
- package/dist/cjs/internal/SnapshotBoundary.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/TestSocket.d.ts +55 -0
- package/dist/cjs/test/TestSocket.d.ts.map +1 -0
- package/dist/cjs/test/TestSocket.js +145 -0
- package/dist/cjs/test/TestSocket.js.map +7 -0
- package/dist/cjs/test/TestSync.d.ts +54 -0
- package/dist/cjs/test/TestSync.d.ts.map +1 -0
- package/dist/cjs/test/TestSync.js +96 -0
- package/dist/cjs/test/TestSync.js.map +7 -0
- package/dist/esm/BranchCommands.d.ts +228 -0
- package/dist/esm/BranchCommands.d.ts.map +1 -0
- package/dist/esm/BranchCommands.js +496 -0
- package/dist/esm/BranchCommands.js.map +1 -0
- package/dist/esm/BranchIds.d.ts +65 -0
- package/dist/esm/BranchIds.d.ts.map +1 -0
- package/dist/esm/BranchIds.js +61 -0
- package/dist/esm/BranchIds.js.map +1 -0
- package/dist/esm/BranchPresence.d.ts +195 -0
- package/dist/esm/BranchPresence.d.ts.map +1 -0
- package/dist/esm/BranchPresence.js +361 -0
- package/dist/esm/BranchPresence.js.map +1 -0
- package/dist/esm/BranchProjection.d.ts +179 -0
- package/dist/esm/BranchProjection.d.ts.map +1 -0
- package/dist/esm/BranchProjection.js +269 -0
- package/dist/esm/BranchProjection.js.map +1 -0
- package/dist/esm/BranchProtocol.d.ts +415 -0
- package/dist/esm/BranchProtocol.d.ts.map +1 -0
- package/dist/esm/BranchProtocol.js +323 -0
- package/dist/esm/BranchProtocol.js.map +1 -0
- package/dist/esm/BranchRpcs.d.ts +177 -0
- package/dist/esm/BranchRpcs.d.ts.map +1 -0
- package/dist/esm/BranchRpcs.js +103 -0
- package/dist/esm/BranchRpcs.js.map +1 -0
- package/dist/esm/BranchServer.d.ts +55 -0
- package/dist/esm/BranchServer.d.ts.map +1 -0
- package/dist/esm/BranchServer.js +168 -0
- package/dist/esm/BranchServer.js.map +1 -0
- package/dist/esm/BranchShare.d.ts +159 -0
- package/dist/esm/BranchShare.d.ts.map +1 -0
- package/dist/esm/BranchShare.js +213 -0
- package/dist/esm/BranchShare.js.map +1 -0
- package/dist/esm/RunCatalog.d.ts +207 -0
- package/dist/esm/RunCatalog.d.ts.map +1 -0
- package/dist/esm/RunCatalog.js +196 -0
- package/dist/esm/RunCatalog.js.map +1 -0
- package/dist/esm/SyncAuth.d.ts +70 -0
- package/dist/esm/SyncAuth.d.ts.map +1 -0
- package/dist/esm/SyncAuth.js +119 -0
- package/dist/esm/SyncAuth.js.map +1 -0
- package/dist/esm/SyncClient.d.ts +274 -0
- package/dist/esm/SyncClient.d.ts.map +1 -0
- package/dist/esm/SyncClient.js +586 -0
- package/dist/esm/SyncClient.js.map +1 -0
- package/dist/esm/SyncError.d.ts +107 -0
- package/dist/esm/SyncError.d.ts.map +1 -0
- package/dist/esm/SyncError.js +133 -0
- package/dist/esm/SyncError.js.map +1 -0
- package/dist/esm/SyncPrincipal.d.ts +100 -0
- package/dist/esm/SyncPrincipal.d.ts.map +1 -0
- package/dist/esm/SyncPrincipal.js +58 -0
- package/dist/esm/SyncPrincipal.js.map +1 -0
- package/dist/esm/SyncProtocol.d.ts +592 -0
- package/dist/esm/SyncProtocol.d.ts.map +1 -0
- package/dist/esm/SyncProtocol.js +434 -0
- package/dist/esm/SyncProtocol.js.map +1 -0
- package/dist/esm/SyncRpcs.d.ts +92 -0
- package/dist/esm/SyncRpcs.d.ts.map +1 -0
- package/dist/esm/SyncRpcs.js +35 -0
- package/dist/esm/SyncRpcs.js.map +1 -0
- package/dist/esm/SyncServer.d.ts +184 -0
- package/dist/esm/SyncServer.d.ts.map +1 -0
- package/dist/esm/SyncServer.js +897 -0
- package/dist/esm/SyncServer.js.map +1 -0
- package/dist/esm/WorkspaceShare.d.ts +174 -0
- package/dist/esm/WorkspaceShare.d.ts.map +1 -0
- package/dist/esm/WorkspaceShare.js +208 -0
- package/dist/esm/WorkspaceShare.js.map +1 -0
- package/dist/esm/index.d.ts +91 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +91 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/Admission.d.ts +43 -0
- package/dist/esm/internal/Admission.d.ts.map +1 -0
- package/dist/esm/internal/Admission.js +80 -0
- package/dist/esm/internal/Admission.js.map +1 -0
- package/dist/esm/internal/CauseText.d.ts +55 -0
- package/dist/esm/internal/CauseText.d.ts.map +1 -0
- package/dist/esm/internal/CauseText.js +67 -0
- package/dist/esm/internal/CauseText.js.map +1 -0
- package/dist/esm/internal/JournalErrorCode.d.ts +23 -0
- package/dist/esm/internal/JournalErrorCode.d.ts.map +1 -0
- package/dist/esm/internal/JournalErrorCode.js +44 -0
- package/dist/esm/internal/JournalErrorCode.js.map +1 -0
- package/dist/esm/internal/PolicyOptions.d.ts +57 -0
- package/dist/esm/internal/PolicyOptions.d.ts.map +1 -0
- package/dist/esm/internal/PolicyOptions.js +70 -0
- package/dist/esm/internal/PolicyOptions.js.map +1 -0
- package/dist/esm/internal/ProtocolVersion.d.ts +17 -0
- package/dist/esm/internal/ProtocolVersion.d.ts.map +1 -0
- package/dist/esm/internal/ProtocolVersion.js +24 -0
- package/dist/esm/internal/ProtocolVersion.js.map +1 -0
- package/dist/esm/internal/ShareSigner.d.ts +173 -0
- package/dist/esm/internal/ShareSigner.d.ts.map +1 -0
- package/dist/esm/internal/ShareSigner.js +219 -0
- package/dist/esm/internal/ShareSigner.js.map +1 -0
- package/dist/esm/internal/SnapshotBoundary.d.ts +17 -0
- package/dist/esm/internal/SnapshotBoundary.d.ts.map +1 -0
- package/dist/esm/internal/SnapshotBoundary.js +68 -0
- package/dist/esm/internal/SnapshotBoundary.js.map +1 -0
- package/dist/esm/test/TestSocket.d.ts +55 -0
- package/dist/esm/test/TestSocket.d.ts.map +1 -0
- package/dist/esm/test/TestSocket.js +128 -0
- package/dist/esm/test/TestSocket.js.map +1 -0
- package/dist/esm/test/TestSync.d.ts +54 -0
- package/dist/esm/test/TestSync.d.ts.map +1 -0
- package/dist/esm/test/TestSync.js +76 -0
- package/dist/esm/test/TestSync.js.map +1 -0
- package/docs/README.md +156 -0
- package/docs/api.md +328 -0
- package/docs/concepts/authorization.md +117 -0
- package/docs/concepts/branches.md +195 -0
- package/docs/concepts/compaction.md +144 -0
- package/docs/concepts/replay-then-follow.md +129 -0
- package/docs/concepts/scopes-and-cursors.md +135 -0
- package/docs/guides/authorize-a-connection.md +174 -0
- package/docs/guides/follow-a-run.md +174 -0
- package/docs/guides/handle-a-compacted-run.md +158 -0
- package/docs/guides/list-workspace-runs.md +120 -0
- package/docs/guides/run-a-long-soak.md +80 -0
- package/docs/guides/serve-the-read-path.md +146 -0
- package/docs/guides/test-a-follower.md +273 -0
- package/docs/installation.md +92 -0
- package/docs/protocol.md +183 -0
- package/docs/quickstart.md +159 -0
- package/docs/troubleshooting.md +234 -0
- package/package.json +304 -3
- package/src/BranchCommands.ts +684 -0
- package/src/BranchIds.ts +76 -0
- package/src/BranchPresence.ts +512 -0
- package/src/BranchProjection.ts +308 -0
- package/src/BranchProtocol.ts +411 -0
- package/src/BranchRpcs.ts +163 -0
- package/src/BranchServer.ts +202 -0
- package/src/BranchShare.ts +297 -0
- package/src/RunCatalog.ts +308 -0
- package/src/SyncAuth.ts +146 -0
- package/src/SyncClient.ts +902 -0
- package/src/SyncError.ts +147 -0
- package/src/SyncPrincipal.ts +115 -0
- package/src/SyncProtocol.ts +594 -0
- package/src/SyncRpcs.ts +40 -0
- package/src/SyncServer.ts +1184 -0
- package/src/WorkspaceShare.ts +292 -0
- package/src/index.ts +107 -0
- package/src/internal/Admission.ts +119 -0
- package/src/internal/CauseText.ts +74 -0
- package/src/internal/JournalErrorCode.ts +46 -0
- package/src/internal/PolicyOptions.ts +97 -0
- package/src/internal/ProtocolVersion.ts +28 -0
- package/src/internal/ShareSigner.ts +300 -0
- package/src/internal/SnapshotBoundary.ts +91 -0
- package/src/test/TestSocket.ts +192 -0
- 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
|
+
})
|