@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.
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,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"}