@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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/internal/ProtocolVersion.ts"],
|
|
4
|
+
"sourcesContent": ["/** The one protocol-version comparison every sync boundary answers with.\n * @since 1.0.0-rc.0\n */\n\nimport * as Effect from \"effect/Effect\"\nimport { SyncError } from \"../SyncError.ts\"\nimport { protocolVersion } from \"../SyncProtocol.ts\"\n\n/** Refuse a version this revision does not speak, on every path, with one code.\n *\n * Read, subscribe and snapshot all decode a mismatched version so the refusal\n * is this typed `protocol_violation` rather than three different decode\n * failures, and the accepted number is only ever\n * {@link SyncProtocol.protocolVersion}.\n *\n * @category validation\n * @since 1.0.0-rc.0\n */\nexport const requireVersion = (version: number | undefined): Effect.Effect<void, SyncError> =>\n version === protocolVersion\n ? Effect.void\n : Effect.fail(\n new SyncError({\n code: \"protocol_violation\",\n message: `Expected sync protocol version ${protocolVersion}; received ${version}`,\n cause: \"protocol_version_mismatch\"\n })\n )\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,aAAwB;AACxB,uBAA0B;AAC1B,0BAAgC;AAYzB,MAAM,iBAAiB,CAAC,YAC7B,YAAY,sCACR,OAAO,OACP,OAAO;AAAA,EACP,IAAI,2BAAU;AAAA,IACZ,MAAM;AAAA,IACN,SAAS,kCAAkC,mCAAe,cAAc,OAAO;AAAA,IAC/E,OAAO;AAAA,EACT,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMAC-SHA-256 signing primitives shared by the branch and workspace share
|
|
3
|
+
* authorities.
|
|
4
|
+
*
|
|
5
|
+
* Both authorities sign a length-prefixed encoding of their claims: without
|
|
6
|
+
* length prefixes a field ending in the separator could be re-cut into a
|
|
7
|
+
* different, still-validly-signed claim set. Web Crypto is used directly so
|
|
8
|
+
* the same module runs in the browser and on node, and signature comparison
|
|
9
|
+
* is length-independent so a check leaks no prefix length.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
*/
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as Redacted from "effect/Redacted";
|
|
15
|
+
import type { Access } from "../BranchProtocol.ts";
|
|
16
|
+
import { SyncError } from "../SyncError.ts";
|
|
17
|
+
/**
|
|
18
|
+
* Length-prefixes each field so no two distinct field sequences share an
|
|
19
|
+
* encoding.
|
|
20
|
+
*
|
|
21
|
+
* The prefix counts UTF-8 BYTES, not UTF-16 code units, because the bytes are
|
|
22
|
+
* what {@link signHmac} covers. Counting code units let a two-byte field claim
|
|
23
|
+
* length one, so the prefix no longer separated the fields the signature was
|
|
24
|
+
* taken over.
|
|
25
|
+
*
|
|
26
|
+
* @category encoding
|
|
27
|
+
* @since 0.1.0
|
|
28
|
+
*/
|
|
29
|
+
export declare const lengthPrefixed: (fields: ReadonlyArray<string>) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Whether a string is exactly what its own UTF-8 bytes decode back to.
|
|
32
|
+
*
|
|
33
|
+
* `TextEncoder` replaces every unpaired surrogate with U+FFFD, so two claim
|
|
34
|
+
* sets differing only in a lone surrogate sign to identical bytes: a
|
|
35
|
+
* capability minted for a lone-surrogate branch id verifies after its
|
|
36
|
+
* `branchId` is rewritten to U+FFFD, which names a different branch. A length
|
|
37
|
+
* prefix cannot separate them either, because both encode to the same three
|
|
38
|
+
* bytes. The only correct answer is to refuse such a claim set before it is
|
|
39
|
+
* signed, and the round trip is the exact question — does this string survive
|
|
40
|
+
* the encoding the signature is taken over — rather than a proxy for it.
|
|
41
|
+
*
|
|
42
|
+
* @category encoding
|
|
43
|
+
* @since 1.0.0-rc.0
|
|
44
|
+
*/
|
|
45
|
+
export declare const utf8RoundTrips: (value: string, bytes: Uint8Array) => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Renders signature bytes as lowercase hex.
|
|
48
|
+
*
|
|
49
|
+
* @category encoding
|
|
50
|
+
* @since 0.1.0
|
|
51
|
+
*/
|
|
52
|
+
export declare const hex: (bytes: Uint8Array) => string;
|
|
53
|
+
/**
|
|
54
|
+
* Length-independent comparison, so a signature check leaks no prefix length.
|
|
55
|
+
*
|
|
56
|
+
* @category comparison
|
|
57
|
+
* @since 0.1.0
|
|
58
|
+
*/
|
|
59
|
+
export declare const constantTimeEquals: (left: string, right: string) => boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The fewest UTF-8 bytes a signing secret may carry: the HMAC-SHA-256 output
|
|
62
|
+
* length, the minimum RFC 2104 section 3 recommends.
|
|
63
|
+
*
|
|
64
|
+
* Every capability carries its claims and their signature in the clear, so one
|
|
65
|
+
* observed capability is an offline oracle for the secret. A short secret such
|
|
66
|
+
* as `a` falls to exhaustive search in seconds, and the attacker then mints
|
|
67
|
+
* branch and workspace capabilities of their own.
|
|
68
|
+
*
|
|
69
|
+
* @category constants
|
|
70
|
+
* @since 1.0.0-rc.1
|
|
71
|
+
*/
|
|
72
|
+
export declare const minSecretBytes = 32;
|
|
73
|
+
/**
|
|
74
|
+
* Imports a raw secret as a non-extractable Web Crypto HMAC-SHA-256 signing
|
|
75
|
+
* key. Fails with `invalid_request` when the secret does not survive UTF-8
|
|
76
|
+
* (an unpaired surrogate) or is shorter than {@link minSecretBytes}, and with
|
|
77
|
+
* a `SyncError` carrying the rejection as `cause` when Web Crypto refuses the
|
|
78
|
+
* import.
|
|
79
|
+
*
|
|
80
|
+
* @category crypto
|
|
81
|
+
* @since 0.1.0
|
|
82
|
+
*/
|
|
83
|
+
export declare const importHmacKey: (secret: string) => Effect.Effect<CryptoKey, SyncError>;
|
|
84
|
+
/**
|
|
85
|
+
* Signs a canonical claim encoding, returning the signature as lowercase hex.
|
|
86
|
+
*
|
|
87
|
+
* @category crypto
|
|
88
|
+
* @since 0.1.0
|
|
89
|
+
*/
|
|
90
|
+
export declare const signHmac: (key: CryptoKey, canonical: string) => Effect.Effect<string, SyncError>;
|
|
91
|
+
/**
|
|
92
|
+
* The refusal both authorities issue. Declared once so a message added to one
|
|
93
|
+
* authority's verification cannot land under a different error code in the
|
|
94
|
+
* other.
|
|
95
|
+
*
|
|
96
|
+
* @category errors
|
|
97
|
+
* @since 1.0.0-rc.0
|
|
98
|
+
*/
|
|
99
|
+
export declare const unauthorized: (message: string) => SyncError;
|
|
100
|
+
/**
|
|
101
|
+
* One named signing key. The secret is `Redacted` so a keyring never renders
|
|
102
|
+
* it into logs or inspection output.
|
|
103
|
+
*
|
|
104
|
+
* @category models
|
|
105
|
+
* @since 1.0.0-rc.0
|
|
106
|
+
*/
|
|
107
|
+
export interface Key {
|
|
108
|
+
readonly kid: string;
|
|
109
|
+
readonly secret: Redacted.Redacted<string>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A keyring: the key that signs new capabilities plus every key still
|
|
113
|
+
* accepted for verification. Rotation adds a new active key and keeps the
|
|
114
|
+
* retired one in `keys` until its outstanding capabilities expire.
|
|
115
|
+
*
|
|
116
|
+
* @category models
|
|
117
|
+
* @since 1.0.0-rc.0
|
|
118
|
+
*/
|
|
119
|
+
export interface Keyring {
|
|
120
|
+
readonly activeKid: string;
|
|
121
|
+
readonly keys: ReadonlyArray<Key>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A keyring whose every secret is already an imported Web Crypto key: the
|
|
125
|
+
* signer for new capabilities, and the map a verifier selects a retired key
|
|
126
|
+
* from by `kid`.
|
|
127
|
+
*
|
|
128
|
+
* @category models
|
|
129
|
+
* @since 1.0.0-rc.0
|
|
130
|
+
*/
|
|
131
|
+
export interface ImportedKeyring {
|
|
132
|
+
readonly activeKid: string;
|
|
133
|
+
readonly active: CryptoKey;
|
|
134
|
+
readonly verification: ReadonlyMap<string, CryptoKey>;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Imports every key in a keyring, so a misconfigured ring — an unknown active
|
|
138
|
+
* kid, a duplicate kid, or a key Web Crypto refuses — fails at construction
|
|
139
|
+
* rather than at the first request. `subject` names the authority in the
|
|
140
|
+
* refusal ("branch", "workspace").
|
|
141
|
+
*
|
|
142
|
+
* @category crypto
|
|
143
|
+
* @since 1.0.0-rc.0
|
|
144
|
+
*/
|
|
145
|
+
export declare const importKeyring: (keyring: Keyring, subject: string) => Effect.Effect<ImportedKeyring, SyncError>;
|
|
146
|
+
/**
|
|
147
|
+
* The authorization both capability authorities run once their signing key is
|
|
148
|
+
* selected: the signature, then the expiry, then the requested access.
|
|
149
|
+
*
|
|
150
|
+
* The order is the contract. A capability is authenticated before anything it
|
|
151
|
+
* claims is reported back, so a forged claim set is refused as a bad signature
|
|
152
|
+
* and never as a scope or an expiry. `scope` is the one check an authority
|
|
153
|
+
* adds for itself — the branch authority's cross-branch refusal — and it runs
|
|
154
|
+
* after the signature for exactly that reason.
|
|
155
|
+
*
|
|
156
|
+
* Both authorities called this sequence out by hand, which left a fix to the
|
|
157
|
+
* expiry boundary or the read-only refusal landing in one and not the other.
|
|
158
|
+
*
|
|
159
|
+
* @category crypto
|
|
160
|
+
* @since 1.0.0-rc.0
|
|
161
|
+
*/
|
|
162
|
+
export declare const verifyClaims: (options: {
|
|
163
|
+
readonly key: CryptoKey;
|
|
164
|
+
readonly canonical: string;
|
|
165
|
+
readonly signature: string;
|
|
166
|
+
readonly expiresAtMs: number;
|
|
167
|
+
readonly granted: Access;
|
|
168
|
+
readonly requested: Access;
|
|
169
|
+
/** The refused thing, named as it appears in the message: "The share capability". */
|
|
170
|
+
readonly subject: string;
|
|
171
|
+
readonly scope?: Effect.Effect<void, SyncError>;
|
|
172
|
+
}) => Effect.Effect<void, SyncError>;
|
|
173
|
+
//# sourceMappingURL=ShareSigner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShareSigner.d.ts","sourceRoot":"","sources":["../../../src/internal/ShareSigner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAK3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,aAAa,CAAC,MAAM,CAAC,KAAG,MACa,CAAA;AAI5E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,OAAO,UAAU,KAAG,OAA0C,CAAA;AAE5G;;;;;GAKG;AACH,eAAO,MAAM,GAAG,GAAI,OAAO,UAAU,KAAG,MACkC,CAAA;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,OAQhE,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,KAAK,CAAA;AAEhC;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAiChF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,SAAS,EAAE,WAAW,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAsB3F,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,SAA6D,CAAA;AAE5G;;;;;;GAMG;AACH,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;CAC3C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,CAAA;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;IAC1B,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;CACtD;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO,EAAE,SAAS,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,SAAS,CA6BtG,CAAA;AAEJ;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS;IACpC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;CAChD,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAc7B,CAAA"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var ShareSigner_exports = {};
|
|
30
|
+
__export(ShareSigner_exports, {
|
|
31
|
+
constantTimeEquals: () => constantTimeEquals,
|
|
32
|
+
hex: () => hex,
|
|
33
|
+
importHmacKey: () => importHmacKey,
|
|
34
|
+
importKeyring: () => importKeyring,
|
|
35
|
+
lengthPrefixed: () => lengthPrefixed,
|
|
36
|
+
minSecretBytes: () => minSecretBytes,
|
|
37
|
+
signHmac: () => signHmac,
|
|
38
|
+
unauthorized: () => unauthorized,
|
|
39
|
+
utf8RoundTrips: () => utf8RoundTrips,
|
|
40
|
+
verifyClaims: () => verifyClaims
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(ShareSigner_exports);
|
|
43
|
+
var Clock = __toESM(require("effect/Clock"), 1);
|
|
44
|
+
var Effect = __toESM(require("effect/Effect"), 1);
|
|
45
|
+
var Redacted = __toESM(require("effect/Redacted"), 1);
|
|
46
|
+
var import_SyncError = require("../SyncError.js");
|
|
47
|
+
var import_CauseText = require("./CauseText.js");
|
|
48
|
+
const encoder = new TextEncoder();
|
|
49
|
+
const lengthPrefixed = (fields) => fields.map((field) => `${encoder.encode(field).length}:${field}`).join("");
|
|
50
|
+
const decoder = new TextDecoder();
|
|
51
|
+
const utf8RoundTrips = (value, bytes) => decoder.decode(bytes) === value;
|
|
52
|
+
const hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
53
|
+
const constantTimeEquals = (left, right) => {
|
|
54
|
+
let difference = left.length ^ right.length;
|
|
55
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
56
|
+
difference |= (left.charCodeAt(index) | 0) ^ (right.charCodeAt(index) | 0);
|
|
57
|
+
}
|
|
58
|
+
return difference === 0;
|
|
59
|
+
};
|
|
60
|
+
const minSecretBytes = 32;
|
|
61
|
+
const importHmacKey = (secret) => {
|
|
62
|
+
const bytes = encoder.encode(secret);
|
|
63
|
+
if (!utf8RoundTrips(secret, bytes)) {
|
|
64
|
+
return Effect.fail(
|
|
65
|
+
new import_SyncError.SyncError({
|
|
66
|
+
code: "invalid_request",
|
|
67
|
+
message: "The HMAC signing secret carries an unpaired surrogate"
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (bytes.length < minSecretBytes) {
|
|
72
|
+
return Effect.fail(
|
|
73
|
+
new import_SyncError.SyncError({
|
|
74
|
+
code: "invalid_request",
|
|
75
|
+
message: `The HMAC signing secret must be at least ${minSecretBytes} bytes of UTF-8`
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return Effect.tryPromise({
|
|
80
|
+
try: () => crypto.subtle.importKey("raw", bytes, { name: "HMAC", hash: "SHA-256" }, false, [
|
|
81
|
+
"sign"
|
|
82
|
+
]),
|
|
83
|
+
catch: (cause) => new import_SyncError.SyncError({
|
|
84
|
+
code: "unknown",
|
|
85
|
+
message: "Web Crypto could not import the HMAC signing key",
|
|
86
|
+
cause: (0, import_CauseText.causeText)(cause)
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const signHmac = (key, canonical) => {
|
|
91
|
+
const bytes = encoder.encode(canonical);
|
|
92
|
+
if (!utf8RoundTrips(canonical, bytes)) {
|
|
93
|
+
return Effect.fail(
|
|
94
|
+
new import_SyncError.SyncError({
|
|
95
|
+
code: "invalid_request",
|
|
96
|
+
message: "Share claims carry an unpaired surrogate and cannot be signed"
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return Effect.map(
|
|
101
|
+
Effect.tryPromise({
|
|
102
|
+
try: () => crypto.subtle.sign("HMAC", key, bytes),
|
|
103
|
+
catch: (cause) => new import_SyncError.SyncError({
|
|
104
|
+
code: "unknown",
|
|
105
|
+
message: "Web Crypto could not sign the share claims",
|
|
106
|
+
cause: (0, import_CauseText.causeText)(cause)
|
|
107
|
+
})
|
|
108
|
+
}),
|
|
109
|
+
(signature) => hex(new Uint8Array(signature))
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
const unauthorized = (message) => new import_SyncError.SyncError({ code: "unauthorized", message });
|
|
113
|
+
const importKeyring = (keyring, subject) => Effect.gen(function* () {
|
|
114
|
+
const activeKid = keyring.activeKid;
|
|
115
|
+
const keys = keyring.keys.map((key) => ({ kid: key.kid, secret: key.secret }));
|
|
116
|
+
const verification = /* @__PURE__ */ new Map();
|
|
117
|
+
for (const key of keys) {
|
|
118
|
+
if (verification.has(key.kid)) {
|
|
119
|
+
return yield* Effect.fail(
|
|
120
|
+
new import_SyncError.SyncError({ code: "invalid_request", message: `The ${subject} keyring names kid ${key.kid} twice` })
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
verification.set(key.kid, yield* importHmacKey(Redacted.value(key.secret)));
|
|
124
|
+
}
|
|
125
|
+
const active = verification.get(activeKid);
|
|
126
|
+
if (active === void 0) {
|
|
127
|
+
return yield* Effect.fail(
|
|
128
|
+
new import_SyncError.SyncError({
|
|
129
|
+
code: "invalid_request",
|
|
130
|
+
message: `The ${subject} keyring's active kid names no key in the ring`
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return { activeKid, active, verification };
|
|
135
|
+
});
|
|
136
|
+
const verifyClaims = (options) => Effect.gen(function* () {
|
|
137
|
+
const expected = yield* signHmac(options.key, options.canonical);
|
|
138
|
+
if (!constantTimeEquals(expected, options.signature)) {
|
|
139
|
+
return yield* Effect.fail(unauthorized(`${options.subject} signature is invalid`));
|
|
140
|
+
}
|
|
141
|
+
if (options.scope !== void 0) yield* options.scope;
|
|
142
|
+
const nowMs = yield* Clock.currentTimeMillis;
|
|
143
|
+
if (nowMs >= options.expiresAtMs) {
|
|
144
|
+
return yield* Effect.fail(unauthorized(`${options.subject} has expired`));
|
|
145
|
+
}
|
|
146
|
+
if (options.requested === "write" && options.granted !== "write") {
|
|
147
|
+
return yield* Effect.fail(unauthorized(`${options.subject} is read-only`));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
//# sourceMappingURL=ShareSigner.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/internal/ShareSigner.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * HMAC-SHA-256 signing primitives shared by the branch and workspace share\n * authorities.\n *\n * Both authorities sign a length-prefixed encoding of their claims: without\n * length prefixes a field ending in the separator could be re-cut into a\n * different, still-validly-signed claim set. Web Crypto is used directly so\n * the same module runs in the browser and on node, and signature comparison\n * is length-independent so a check leaks no prefix length.\n *\n * @since 0.1.0\n */\n\nimport * as Clock from \"effect/Clock\"\nimport * as Effect from \"effect/Effect\"\nimport * as Redacted from \"effect/Redacted\"\nimport type { Access } from \"../BranchProtocol.ts\"\nimport { SyncError } from \"../SyncError.ts\"\nimport { causeText } from \"./CauseText.ts\"\n\nconst encoder = new TextEncoder()\n\n/**\n * Length-prefixes each field so no two distinct field sequences share an\n * encoding.\n *\n * The prefix counts UTF-8 BYTES, not UTF-16 code units, because the bytes are\n * what {@link signHmac} covers. Counting code units let a two-byte field claim\n * length one, so the prefix no longer separated the fields the signature was\n * taken over.\n *\n * @category encoding\n * @since 0.1.0\n */\nexport const lengthPrefixed = (fields: ReadonlyArray<string>): string =>\n fields.map((field) => `${encoder.encode(field).length}:${field}`).join(\"\")\n\nconst decoder = new TextDecoder()\n\n/**\n * Whether a string is exactly what its own UTF-8 bytes decode back to.\n *\n * `TextEncoder` replaces every unpaired surrogate with U+FFFD, so two claim\n * sets differing only in a lone surrogate sign to identical bytes: a\n * capability minted for a lone-surrogate branch id verifies after its\n * `branchId` is rewritten to U+FFFD, which names a different branch. A length\n * prefix cannot separate them either, because both encode to the same three\n * bytes. The only correct answer is to refuse such a claim set before it is\n * signed, and the round trip is the exact question \u2014 does this string survive\n * the encoding the signature is taken over \u2014 rather than a proxy for it.\n *\n * @category encoding\n * @since 1.0.0-rc.0\n */\nexport const utf8RoundTrips = (value: string, bytes: Uint8Array): boolean => decoder.decode(bytes) === value\n\n/**\n * Renders signature bytes as lowercase hex.\n *\n * @category encoding\n * @since 0.1.0\n */\nexport const hex = (bytes: Uint8Array): string =>\n Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\")\n\n/**\n * Length-independent comparison, so a signature check leaks no prefix length.\n *\n * @category comparison\n * @since 0.1.0\n */\nexport const constantTimeEquals = (left: string, right: string): boolean => {\n let difference = left.length ^ right.length\n // `charCodeAt` past the end is NaN, and `NaN | 0` is 0, so the loop reads\n // both strings to the longer length without an early return.\n for (let index = 0; index < Math.max(left.length, right.length); index += 1) {\n difference |= (left.charCodeAt(index) | 0) ^ (right.charCodeAt(index) | 0)\n }\n return difference === 0\n}\n\n/**\n * The fewest UTF-8 bytes a signing secret may carry: the HMAC-SHA-256 output\n * length, the minimum RFC 2104 section 3 recommends.\n *\n * Every capability carries its claims and their signature in the clear, so one\n * observed capability is an offline oracle for the secret. A short secret such\n * as `a` falls to exhaustive search in seconds, and the attacker then mints\n * branch and workspace capabilities of their own.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const minSecretBytes = 32\n\n/**\n * Imports a raw secret as a non-extractable Web Crypto HMAC-SHA-256 signing\n * key. Fails with `invalid_request` when the secret does not survive UTF-8\n * (an unpaired surrogate) or is shorter than {@link minSecretBytes}, and with\n * a `SyncError` carrying the rejection as `cause` when Web Crypto refuses the\n * import.\n *\n * @category crypto\n * @since 0.1.0\n */\nexport const importHmacKey = (secret: string): Effect.Effect<CryptoKey, SyncError> => {\n // An unpaired surrogate encodes to U+FFFD's bytes, so two distinct\n // secrets would import the same key and rotating between them revokes\n // nothing. Refused here, as `signHmac` refuses such claims.\n const bytes = encoder.encode(secret)\n if (!utf8RoundTrips(secret, bytes)) {\n return Effect.fail(\n new SyncError({\n code: \"invalid_request\",\n message: \"The HMAC signing secret carries an unpaired surrogate\"\n })\n )\n }\n if (bytes.length < minSecretBytes) {\n return Effect.fail(\n new SyncError({\n code: \"invalid_request\",\n message: `The HMAC signing secret must be at least ${minSecretBytes} bytes of UTF-8`\n })\n )\n }\n return Effect.tryPromise({\n try: () =>\n crypto.subtle.importKey(\"raw\", bytes, { name: \"HMAC\", hash: \"SHA-256\" }, false, [\n \"sign\"\n ]),\n catch: (cause) =>\n new SyncError({\n code: \"unknown\",\n message: \"Web Crypto could not import the HMAC signing key\",\n cause: causeText(cause)\n })\n })\n}\n\n/**\n * Signs a canonical claim encoding, returning the signature as lowercase hex.\n *\n * @category crypto\n * @since 0.1.0\n */\nexport const signHmac = (key: CryptoKey, canonical: string): Effect.Effect<string, SyncError> => {\n const bytes = encoder.encode(canonical)\n if (!utf8RoundTrips(canonical, bytes)) {\n return Effect.fail(\n new SyncError({\n code: \"invalid_request\",\n message: \"Share claims carry an unpaired surrogate and cannot be signed\"\n })\n )\n }\n return Effect.map(\n Effect.tryPromise({\n try: () => crypto.subtle.sign(\"HMAC\", key, bytes),\n catch: (cause) =>\n new SyncError({\n code: \"unknown\",\n message: \"Web Crypto could not sign the share claims\",\n cause: causeText(cause)\n })\n }),\n (signature) => hex(new Uint8Array(signature))\n )\n}\n\n/**\n * The refusal both authorities issue. Declared once so a message added to one\n * authority's verification cannot land under a different error code in the\n * other.\n *\n * @category errors\n * @since 1.0.0-rc.0\n */\nexport const unauthorized = (message: string): SyncError => new SyncError({ code: \"unauthorized\", message })\n\n/**\n * One named signing key. The secret is `Redacted` so a keyring never renders\n * it into logs or inspection output.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Key {\n readonly kid: string\n readonly secret: Redacted.Redacted<string>\n}\n\n/**\n * A keyring: the key that signs new capabilities plus every key still\n * accepted for verification. Rotation adds a new active key and keeps the\n * retired one in `keys` until its outstanding capabilities expire.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Keyring {\n readonly activeKid: string\n readonly keys: ReadonlyArray<Key>\n}\n\n/**\n * A keyring whose every secret is already an imported Web Crypto key: the\n * signer for new capabilities, and the map a verifier selects a retired key\n * from by `kid`.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface ImportedKeyring {\n readonly activeKid: string\n readonly active: CryptoKey\n readonly verification: ReadonlyMap<string, CryptoKey>\n}\n\n/**\n * Imports every key in a keyring, so a misconfigured ring \u2014 an unknown active\n * kid, a duplicate kid, or a key Web Crypto refuses \u2014 fails at construction\n * rather than at the first request. `subject` names the authority in the\n * refusal (\"branch\", \"workspace\").\n *\n * @category crypto\n * @since 1.0.0-rc.0\n */\nexport const importKeyring = (keyring: Keyring, subject: string): Effect.Effect<ImportedKeyring, SyncError> =>\n Effect.gen(function*() {\n // Copied BEFORE the first import, for the reason snapshots exist on the\n // verify path: the keyring belongs to the caller and every import below is\n // an await, so a `for..of` over the caller's own array re-reads it between\n // them. A splice landing in that window changed which keys the authority\n // ended up holding, and reading `activeKid` only after the loop let it name\n // a key the ring was validated with rather than the one the caller passed.\n const activeKid = keyring.activeKid\n const keys = keyring.keys.map((key): Key => ({ kid: key.kid, secret: key.secret }))\n const verification = new Map<string, CryptoKey>()\n for (const key of keys) {\n if (verification.has(key.kid)) {\n return yield* Effect.fail(\n new SyncError({ code: \"invalid_request\", message: `The ${subject} keyring names kid ${key.kid} twice` })\n )\n }\n verification.set(key.kid, yield* importHmacKey(Redacted.value(key.secret)))\n }\n const active = verification.get(activeKid)\n if (active === undefined) {\n return yield* Effect.fail(\n new SyncError({\n code: \"invalid_request\",\n message: `The ${subject} keyring's active kid names no key in the ring`\n })\n )\n }\n return { activeKid, active, verification }\n })\n\n/**\n * The authorization both capability authorities run once their signing key is\n * selected: the signature, then the expiry, then the requested access.\n *\n * The order is the contract. A capability is authenticated before anything it\n * claims is reported back, so a forged claim set is refused as a bad signature\n * and never as a scope or an expiry. `scope` is the one check an authority\n * adds for itself \u2014 the branch authority's cross-branch refusal \u2014 and it runs\n * after the signature for exactly that reason.\n *\n * Both authorities called this sequence out by hand, which left a fix to the\n * expiry boundary or the read-only refusal landing in one and not the other.\n *\n * @category crypto\n * @since 1.0.0-rc.0\n */\nexport const verifyClaims = (options: {\n readonly key: CryptoKey\n readonly canonical: string\n readonly signature: string\n readonly expiresAtMs: number\n readonly granted: Access\n readonly requested: Access\n /** The refused thing, named as it appears in the message: \"The share capability\". */\n readonly subject: string\n readonly scope?: Effect.Effect<void, SyncError>\n}): Effect.Effect<void, SyncError> =>\n Effect.gen(function*() {\n const expected = yield* signHmac(options.key, options.canonical)\n if (!constantTimeEquals(expected, options.signature)) {\n return yield* Effect.fail(unauthorized(`${options.subject} signature is invalid`))\n }\n if (options.scope !== undefined) yield* options.scope\n const nowMs = yield* Clock.currentTimeMillis\n if (nowMs >= options.expiresAtMs) {\n return yield* Effect.fail(unauthorized(`${options.subject} has expired`))\n }\n if (options.requested === \"write\" && options.granted !== \"write\") {\n return yield* Effect.fail(unauthorized(`${options.subject} is read-only`))\n }\n })\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Snapshot validation shared by the hosted and browser-side boundaries.
|
|
2
|
+
* @since 1.0.0-rc.0
|
|
3
|
+
*/
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { SyncError } from "../SyncError.ts";
|
|
6
|
+
import * as Protocol from "../SyncProtocol.ts";
|
|
7
|
+
/** Decode a detached request before callbacks can mutate its expected identity.
|
|
8
|
+
* @category validation
|
|
9
|
+
* @since 1.0.0-rc.0
|
|
10
|
+
*/
|
|
11
|
+
export declare const request: (value: unknown) => Effect.Effect<Protocol.SnapshotRequest, SyncError>;
|
|
12
|
+
/** Copy JSON bytes once, bound them, then check the exact requested projection.
|
|
13
|
+
* @category validation
|
|
14
|
+
* @since 1.0.0-rc.0
|
|
15
|
+
*/
|
|
16
|
+
export declare const response: (expected: Protocol.SnapshotRequest, value: unknown, maxBytes: number) => Effect.Effect<Protocol.Snapshot, SyncError>;
|
|
17
|
+
//# sourceMappingURL=SnapshotBoundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SnapshotBoundary.d.ts","sourceRoot":"","sources":["../../../src/internal/SnapshotBoundary.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,QAAQ,MAAM,oBAAoB,CAAA;AAI9C;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAUvF,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,QAAQ,GACnB,UAAU,QAAQ,CAAC,eAAe,EAClC,OAAO,OAAO,EACd,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAuDzC,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var SnapshotBoundary_exports = {};
|
|
30
|
+
__export(SnapshotBoundary_exports, {
|
|
31
|
+
request: () => request,
|
|
32
|
+
response: () => response
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(SnapshotBoundary_exports);
|
|
35
|
+
var Effect = __toESM(require("effect/Effect"), 1);
|
|
36
|
+
var Schema = __toESM(require("effect/Schema"), 1);
|
|
37
|
+
var import_SyncError = require("../SyncError.js");
|
|
38
|
+
var Protocol = __toESM(require("../SyncProtocol.js"), 1);
|
|
39
|
+
var import_CauseText = require("./CauseText.js");
|
|
40
|
+
var import_ProtocolVersion = require("./ProtocolVersion.js");
|
|
41
|
+
const request = (value) => Effect.try({
|
|
42
|
+
try: () => Schema.decodeUnknownSync(Protocol.SnapshotRequest)(value, { onExcessProperty: "error" }),
|
|
43
|
+
catch: (cause) => new import_SyncError.SyncError({ code: "invalid_request", message: "Invalid public snapshot request", cause: (0, import_CauseText.causeCode)(cause) })
|
|
44
|
+
}).pipe(
|
|
45
|
+
// The schema admits any integer version so this refusal, not a decode
|
|
46
|
+
// failure, is what a version mismatch produces here as on every other RPC.
|
|
47
|
+
Effect.tap((decoded) => (0, import_ProtocolVersion.requireVersion)(decoded.protocolVersion)),
|
|
48
|
+
Effect.map((decoded) => ({ ...decoded }))
|
|
49
|
+
);
|
|
50
|
+
const response = (expected, value, maxBytes) => Effect.gen(function* () {
|
|
51
|
+
const text = yield* Effect.try({
|
|
52
|
+
try: () => JSON.stringify(value),
|
|
53
|
+
catch: (cause) => new import_SyncError.SyncError({
|
|
54
|
+
code: "decode_failed",
|
|
55
|
+
message: "Public snapshot is not JSON encodable",
|
|
56
|
+
cause: (0, import_CauseText.causeCode)(cause)
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
if (text === void 0) {
|
|
60
|
+
return yield* Effect.fail(
|
|
61
|
+
new import_SyncError.SyncError({ code: "decode_failed", message: "Public snapshot is missing", cause: "undefined" })
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (new TextEncoder().encode(text).length > maxBytes) {
|
|
65
|
+
return yield* Effect.fail(
|
|
66
|
+
new import_SyncError.SyncError({ code: "frame_too_large", message: "Public snapshot exceeds the response byte limit" })
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const snapshot = yield* Effect.try({
|
|
70
|
+
try: () => {
|
|
71
|
+
Schema.decodeUnknownSync(Protocol.Snapshot)(value, { onExcessProperty: "error" });
|
|
72
|
+
return Schema.decodeUnknownSync(Protocol.Snapshot)(JSON.parse(text), { onExcessProperty: "error" });
|
|
73
|
+
},
|
|
74
|
+
catch: (cause) => new import_SyncError.SyncError({ code: "decode_failed", message: "Invalid public snapshot response", cause: (0, import_CauseText.causeCode)(cause) })
|
|
75
|
+
});
|
|
76
|
+
yield* (0, import_ProtocolVersion.requireVersion)(snapshot.protocolVersion);
|
|
77
|
+
if (snapshot.runId !== expected.runId || snapshot.lineageId !== expected.lineageId || snapshot.projection !== expected.projection || snapshot.projectionVersion !== expected.projectionVersion) {
|
|
78
|
+
return yield* Effect.fail(
|
|
79
|
+
new import_SyncError.SyncError({
|
|
80
|
+
code: "protocol_violation",
|
|
81
|
+
message: "Public snapshot identity does not match the request",
|
|
82
|
+
cause: "snapshot_identity_mismatch"
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (snapshot.seq < expected.atLeastSeq) {
|
|
87
|
+
return yield* Effect.fail(
|
|
88
|
+
new import_SyncError.SyncError({
|
|
89
|
+
code: "compacted",
|
|
90
|
+
message: "Public snapshot does not cover the required history",
|
|
91
|
+
resync: { runId: expected.runId, checkpointSeq: expected.atLeastSeq }
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return snapshot;
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=SnapshotBoundary.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/internal/SnapshotBoundary.ts"],
|
|
4
|
+
"sourcesContent": ["/** Snapshot validation shared by the hosted and browser-side boundaries.\n * @since 1.0.0-rc.0\n */\n\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport { SyncError } from \"../SyncError.ts\"\nimport * as Protocol from \"../SyncProtocol.ts\"\nimport { causeCode } from \"./CauseText.ts\"\nimport { requireVersion } from \"./ProtocolVersion.ts\"\n\n/** Decode a detached request before callbacks can mutate its expected identity.\n * @category validation\n * @since 1.0.0-rc.0\n */\nexport const request = (value: unknown): Effect.Effect<Protocol.SnapshotRequest, SyncError> =>\n Effect.try({\n try: () => Schema.decodeUnknownSync(Protocol.SnapshotRequest)(value, { onExcessProperty: \"error\" }),\n catch: (cause) =>\n new SyncError({ code: \"invalid_request\", message: \"Invalid public snapshot request\", cause: causeCode(cause) })\n }).pipe(\n // The schema admits any integer version so this refusal, not a decode\n // failure, is what a version mismatch produces here as on every other RPC.\n Effect.tap((decoded) => requireVersion(decoded.protocolVersion)),\n Effect.map((decoded) => ({ ...decoded }))\n )\n\n/** Copy JSON bytes once, bound them, then check the exact requested projection.\n * @category validation\n * @since 1.0.0-rc.0\n */\nexport const response = (\n expected: Protocol.SnapshotRequest,\n value: unknown,\n maxBytes: number\n): Effect.Effect<Protocol.Snapshot, SyncError> =>\n Effect.gen(function*() {\n const text = yield* Effect.try({\n try: () => JSON.stringify(value),\n catch: (cause) =>\n new SyncError({\n code: \"decode_failed\",\n message: \"Public snapshot is not JSON encodable\",\n cause: causeCode(cause)\n })\n })\n if (text === undefined) {\n return yield* Effect.fail(\n new SyncError({ code: \"decode_failed\", message: \"Public snapshot is missing\", cause: \"undefined\" })\n )\n }\n if (new TextEncoder().encode(text).length > maxBytes) {\n return yield* Effect.fail(\n new SyncError({ code: \"frame_too_large\", message: \"Public snapshot exceeds the response byte limit\" })\n )\n }\n const snapshot = yield* Effect.try({\n try: () => {\n // JSON.stringify alone silently drops undefined/functions and turns\n // non-finite numbers into null. Reject such provider values rather than\n // returning a different state under the same checkpoint sequence.\n Schema.decodeUnknownSync(Protocol.Snapshot)(value, { onExcessProperty: \"error\" })\n return Schema.decodeUnknownSync(Protocol.Snapshot)(JSON.parse(text), { onExcessProperty: \"error\" })\n },\n catch: (cause) =>\n new SyncError({ code: \"decode_failed\", message: \"Invalid public snapshot response\", cause: causeCode(cause) })\n })\n yield* requireVersion(snapshot.protocolVersion)\n if (\n snapshot.runId !== expected.runId || snapshot.lineageId !== expected.lineageId ||\n snapshot.projection !== expected.projection || snapshot.projectionVersion !== expected.projectionVersion\n ) {\n return yield* Effect.fail(\n new SyncError({\n code: \"protocol_violation\",\n message: \"Public snapshot identity does not match the request\",\n cause: \"snapshot_identity_mismatch\"\n })\n )\n }\n if (snapshot.seq < expected.atLeastSeq) {\n return yield* Effect.fail(\n new SyncError({\n code: \"compacted\",\n message: \"Public snapshot does not cover the required history\",\n resync: { runId: expected.runId, checkpointSeq: expected.atLeastSeq }\n })\n )\n }\n return snapshot\n })\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,aAAwB;AACxB,aAAwB;AACxB,uBAA0B;AAC1B,eAA0B;AAC1B,uBAA0B;AAC1B,6BAA+B;AAMxB,MAAM,UAAU,CAAC,UACtB,OAAO,IAAI;AAAA,EACT,KAAK,MAAM,OAAO,kBAAkB,SAAS,eAAe,EAAE,OAAO,EAAE,kBAAkB,QAAQ,CAAC;AAAA,EAClG,OAAO,CAAC,UACN,IAAI,2BAAU,EAAE,MAAM,mBAAmB,SAAS,mCAAmC,WAAO,4BAAU,KAAK,EAAE,CAAC;AAClH,CAAC,EAAE;AAAA;AAAA;AAAA,EAGD,OAAO,IAAI,CAAC,gBAAY,uCAAe,QAAQ,eAAe,CAAC;AAAA,EAC/D,OAAO,IAAI,CAAC,aAAa,EAAE,GAAG,QAAQ,EAAE;AAC1C;AAMK,MAAM,WAAW,CACtB,UACA,OACA,aAEA,OAAO,IAAI,aAAY;AACrB,QAAM,OAAO,OAAO,OAAO,IAAI;AAAA,IAC7B,KAAK,MAAM,KAAK,UAAU,KAAK;AAAA,IAC/B,OAAO,CAAC,UACN,IAAI,2BAAU;AAAA,MACZ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,WAAO,4BAAU,KAAK;AAAA,IACxB,CAAC;AAAA,EACL,CAAC;AACD,MAAI,SAAS,QAAW;AACtB,WAAO,OAAO,OAAO;AAAA,MACnB,IAAI,2BAAU,EAAE,MAAM,iBAAiB,SAAS,8BAA8B,OAAO,YAAY,CAAC;AAAA,IACpG;AAAA,EACF;AACA,MAAI,IAAI,YAAY,EAAE,OAAO,IAAI,EAAE,SAAS,UAAU;AACpD,WAAO,OAAO,OAAO;AAAA,MACnB,IAAI,2BAAU,EAAE,MAAM,mBAAmB,SAAS,kDAAkD,CAAC;AAAA,IACvG;AAAA,EACF;AACA,QAAM,WAAW,OAAO,OAAO,IAAI;AAAA,IACjC,KAAK,MAAM;AAIT,aAAO,kBAAkB,SAAS,QAAQ,EAAE,OAAO,EAAE,kBAAkB,QAAQ,CAAC;AAChF,aAAO,OAAO,kBAAkB,SAAS,QAAQ,EAAE,KAAK,MAAM,IAAI,GAAG,EAAE,kBAAkB,QAAQ,CAAC;AAAA,IACpG;AAAA,IACA,OAAO,CAAC,UACN,IAAI,2BAAU,EAAE,MAAM,iBAAiB,SAAS,oCAAoC,WAAO,4BAAU,KAAK,EAAE,CAAC;AAAA,EACjH,CAAC;AACD,aAAO,uCAAe,SAAS,eAAe;AAC9C,MACE,SAAS,UAAU,SAAS,SAAS,SAAS,cAAc,SAAS,aACrE,SAAS,eAAe,SAAS,cAAc,SAAS,sBAAsB,SAAS,mBACvF;AACA,WAAO,OAAO,OAAO;AAAA,MACnB,IAAI,2BAAU;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,SAAS,MAAM,SAAS,YAAY;AACtC,WAAO,OAAO,OAAO;AAAA,MACnB,IAAI,2BAAU;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,QAAQ,EAAE,OAAO,SAAS,OAAO,eAAe,SAAS,WAAW;AAAA,MACtE,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic in-memory socket transport for sync tests.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Scope from "effect/Scope";
|
|
8
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
9
|
+
/**
|
|
10
|
+
* A predicate or byte transformation installed between the two socket ends.
|
|
11
|
+
* Returning `false` drops the frame.
|
|
12
|
+
*
|
|
13
|
+
* @category models
|
|
14
|
+
* @since 0.1.0
|
|
15
|
+
*/
|
|
16
|
+
export type FrameFilter = (bytes: Uint8Array) => boolean | Uint8Array;
|
|
17
|
+
/**
|
|
18
|
+
* Deterministic transport failures that can be injected into a test socket pair.
|
|
19
|
+
*
|
|
20
|
+
* `dropRange` recognizes the JSON representation of an `Entries` frame without
|
|
21
|
+
* importing the sync protocol. Tests using another wire representation can use
|
|
22
|
+
* `installFilter` directly.
|
|
23
|
+
*
|
|
24
|
+
* @category models
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
*/
|
|
27
|
+
export interface TestFaults {
|
|
28
|
+
readonly installFilter: (filter: FrameFilter) => void;
|
|
29
|
+
readonly dropRange: (runId: string, from: number, to: number) => void;
|
|
30
|
+
readonly stall: () => void;
|
|
31
|
+
readonly resume: () => void;
|
|
32
|
+
readonly disconnect: () => Effect.Effect<void>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The two endpoints and controls of an in-memory socket connection.
|
|
36
|
+
*
|
|
37
|
+
* @category models
|
|
38
|
+
* @since 0.1.0
|
|
39
|
+
*/
|
|
40
|
+
export interface Pair {
|
|
41
|
+
readonly client: Socket.Socket;
|
|
42
|
+
readonly server: Socket.Socket;
|
|
43
|
+
readonly faults: TestFaults;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Creates a scoped, bounded, bidirectional in-memory socket connection.
|
|
47
|
+
*
|
|
48
|
+
* Socket runs wait on queue reads and are therefore interrupted with their
|
|
49
|
+
* enclosing scope; no host abort signal or timer is involved.
|
|
50
|
+
*
|
|
51
|
+
* @category constructors
|
|
52
|
+
* @since 0.1.0
|
|
53
|
+
*/
|
|
54
|
+
export declare const makePair: () => Effect.Effect<Pair, never, Scope.Scope>;
|
|
55
|
+
//# sourceMappingURL=TestSocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestSocket.d.ts","sourceRoot":"","sources":["../../../src/test/TestSocket.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,+BAA+B,CAAA;AAEvD;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,GAAG,UAAU,CAAA;AAErE;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;IACrD,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAC/C;AAED;;;;;GAKG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAC5B;AAkDD;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ,QAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAkF9D,CAAA"}
|