@smthrs/sync 0.0.0-stage → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/LICENSE +21 -0
  3. package/README.md +208 -2
  4. package/dist/cjs/BranchCommands.d.ts +228 -0
  5. package/dist/cjs/BranchCommands.d.ts.map +1 -0
  6. package/dist/cjs/BranchCommands.js +316 -0
  7. package/dist/cjs/BranchCommands.js.map +7 -0
  8. package/dist/cjs/BranchIds.d.ts +65 -0
  9. package/dist/cjs/BranchIds.d.ts.map +1 -0
  10. package/dist/cjs/BranchIds.js +53 -0
  11. package/dist/cjs/BranchIds.js.map +7 -0
  12. package/dist/cjs/BranchPresence.d.ts +195 -0
  13. package/dist/cjs/BranchPresence.d.ts.map +1 -0
  14. package/dist/cjs/BranchPresence.js +276 -0
  15. package/dist/cjs/BranchPresence.js.map +7 -0
  16. package/dist/cjs/BranchProjection.d.ts +179 -0
  17. package/dist/cjs/BranchProjection.d.ts.map +1 -0
  18. package/dist/cjs/BranchProjection.js +197 -0
  19. package/dist/cjs/BranchProjection.js.map +7 -0
  20. package/dist/cjs/BranchProtocol.d.ts +415 -0
  21. package/dist/cjs/BranchProtocol.d.ts.map +1 -0
  22. package/dist/cjs/BranchProtocol.js +172 -0
  23. package/dist/cjs/BranchProtocol.js.map +7 -0
  24. package/dist/cjs/BranchRpcs.d.ts +177 -0
  25. package/dist/cjs/BranchRpcs.d.ts.map +1 -0
  26. package/dist/cjs/BranchRpcs.js +85 -0
  27. package/dist/cjs/BranchRpcs.js.map +7 -0
  28. package/dist/cjs/BranchServer.d.ts +55 -0
  29. package/dist/cjs/BranchServer.d.ts.map +1 -0
  30. package/dist/cjs/BranchServer.js +147 -0
  31. package/dist/cjs/BranchServer.js.map +7 -0
  32. package/dist/cjs/BranchShare.d.ts +159 -0
  33. package/dist/cjs/BranchShare.d.ts.map +1 -0
  34. package/dist/cjs/BranchShare.js +147 -0
  35. package/dist/cjs/BranchShare.js.map +7 -0
  36. package/dist/cjs/RunCatalog.d.ts +207 -0
  37. package/dist/cjs/RunCatalog.d.ts.map +1 -0
  38. package/dist/cjs/RunCatalog.js +122 -0
  39. package/dist/cjs/RunCatalog.js.map +7 -0
  40. package/dist/cjs/SyncAuth.d.ts +70 -0
  41. package/dist/cjs/SyncAuth.d.ts.map +1 -0
  42. package/dist/cjs/SyncAuth.js +109 -0
  43. package/dist/cjs/SyncAuth.js.map +7 -0
  44. package/dist/cjs/SyncClient.d.ts +274 -0
  45. package/dist/cjs/SyncClient.d.ts.map +1 -0
  46. package/dist/cjs/SyncClient.js +474 -0
  47. package/dist/cjs/SyncClient.js.map +7 -0
  48. package/dist/cjs/SyncError.d.ts +107 -0
  49. package/dist/cjs/SyncError.d.ts.map +1 -0
  50. package/dist/cjs/SyncError.js +110 -0
  51. package/dist/cjs/SyncError.js.map +7 -0
  52. package/dist/cjs/SyncPrincipal.d.ts +100 -0
  53. package/dist/cjs/SyncPrincipal.d.ts.map +1 -0
  54. package/dist/cjs/SyncPrincipal.js +48 -0
  55. package/dist/cjs/SyncPrincipal.js.map +7 -0
  56. package/dist/cjs/SyncProtocol.d.ts +592 -0
  57. package/dist/cjs/SyncProtocol.d.ts.map +1 -0
  58. package/dist/cjs/SyncProtocol.js +165 -0
  59. package/dist/cjs/SyncProtocol.js.map +7 -0
  60. package/dist/cjs/SyncRpcs.d.ts +92 -0
  61. package/dist/cjs/SyncRpcs.d.ts.map +1 -0
  62. package/dist/cjs/SyncRpcs.js +35 -0
  63. package/dist/cjs/SyncRpcs.js.map +7 -0
  64. package/dist/cjs/SyncServer.d.ts +184 -0
  65. package/dist/cjs/SyncServer.d.ts.map +1 -0
  66. package/dist/cjs/SyncServer.js +513 -0
  67. package/dist/cjs/SyncServer.js.map +7 -0
  68. package/dist/cjs/WorkspaceShare.d.ts +174 -0
  69. package/dist/cjs/WorkspaceShare.d.ts.map +1 -0
  70. package/dist/cjs/WorkspaceShare.js +149 -0
  71. package/dist/cjs/WorkspaceShare.js.map +7 -0
  72. package/dist/cjs/index.d.ts +91 -0
  73. package/dist/cjs/index.d.ts.map +1 -0
  74. package/dist/cjs/index.js +67 -0
  75. package/dist/cjs/index.js.map +7 -0
  76. package/dist/cjs/internal/Admission.d.ts +43 -0
  77. package/dist/cjs/internal/Admission.d.ts.map +1 -0
  78. package/dist/cjs/internal/Admission.js +90 -0
  79. package/dist/cjs/internal/Admission.js.map +7 -0
  80. package/dist/cjs/internal/CauseText.d.ts +55 -0
  81. package/dist/cjs/internal/CauseText.d.ts.map +1 -0
  82. package/dist/cjs/internal/CauseText.js +38 -0
  83. package/dist/cjs/internal/CauseText.js.map +7 -0
  84. package/dist/cjs/internal/JournalErrorCode.d.ts +23 -0
  85. package/dist/cjs/internal/JournalErrorCode.d.ts.map +1 -0
  86. package/dist/cjs/internal/JournalErrorCode.js +34 -0
  87. package/dist/cjs/internal/JournalErrorCode.js.map +7 -0
  88. package/dist/cjs/internal/PolicyOptions.d.ts +57 -0
  89. package/dist/cjs/internal/PolicyOptions.d.ts.map +1 -0
  90. package/dist/cjs/internal/PolicyOptions.js +62 -0
  91. package/dist/cjs/internal/PolicyOptions.js.map +7 -0
  92. package/dist/cjs/internal/ProtocolVersion.d.ts +17 -0
  93. package/dist/cjs/internal/ProtocolVersion.d.ts.map +1 -0
  94. package/dist/cjs/internal/ProtocolVersion.js +44 -0
  95. package/dist/cjs/internal/ProtocolVersion.js.map +7 -0
  96. package/dist/cjs/internal/ShareSigner.d.ts +173 -0
  97. package/dist/cjs/internal/ShareSigner.d.ts.map +1 -0
  98. package/dist/cjs/internal/ShareSigner.js +150 -0
  99. package/dist/cjs/internal/ShareSigner.js.map +7 -0
  100. package/dist/cjs/internal/SnapshotBoundary.d.ts +17 -0
  101. package/dist/cjs/internal/SnapshotBoundary.d.ts.map +1 -0
  102. package/dist/cjs/internal/SnapshotBoundary.js +97 -0
  103. package/dist/cjs/internal/SnapshotBoundary.js.map +7 -0
  104. package/dist/cjs/package.json +1 -0
  105. package/dist/cjs/test/TestSocket.d.ts +55 -0
  106. package/dist/cjs/test/TestSocket.d.ts.map +1 -0
  107. package/dist/cjs/test/TestSocket.js +145 -0
  108. package/dist/cjs/test/TestSocket.js.map +7 -0
  109. package/dist/cjs/test/TestSync.d.ts +54 -0
  110. package/dist/cjs/test/TestSync.d.ts.map +1 -0
  111. package/dist/cjs/test/TestSync.js +96 -0
  112. package/dist/cjs/test/TestSync.js.map +7 -0
  113. package/dist/esm/BranchCommands.d.ts +228 -0
  114. package/dist/esm/BranchCommands.d.ts.map +1 -0
  115. package/dist/esm/BranchCommands.js +496 -0
  116. package/dist/esm/BranchCommands.js.map +1 -0
  117. package/dist/esm/BranchIds.d.ts +65 -0
  118. package/dist/esm/BranchIds.d.ts.map +1 -0
  119. package/dist/esm/BranchIds.js +61 -0
  120. package/dist/esm/BranchIds.js.map +1 -0
  121. package/dist/esm/BranchPresence.d.ts +195 -0
  122. package/dist/esm/BranchPresence.d.ts.map +1 -0
  123. package/dist/esm/BranchPresence.js +361 -0
  124. package/dist/esm/BranchPresence.js.map +1 -0
  125. package/dist/esm/BranchProjection.d.ts +179 -0
  126. package/dist/esm/BranchProjection.d.ts.map +1 -0
  127. package/dist/esm/BranchProjection.js +269 -0
  128. package/dist/esm/BranchProjection.js.map +1 -0
  129. package/dist/esm/BranchProtocol.d.ts +415 -0
  130. package/dist/esm/BranchProtocol.d.ts.map +1 -0
  131. package/dist/esm/BranchProtocol.js +323 -0
  132. package/dist/esm/BranchProtocol.js.map +1 -0
  133. package/dist/esm/BranchRpcs.d.ts +177 -0
  134. package/dist/esm/BranchRpcs.d.ts.map +1 -0
  135. package/dist/esm/BranchRpcs.js +103 -0
  136. package/dist/esm/BranchRpcs.js.map +1 -0
  137. package/dist/esm/BranchServer.d.ts +55 -0
  138. package/dist/esm/BranchServer.d.ts.map +1 -0
  139. package/dist/esm/BranchServer.js +168 -0
  140. package/dist/esm/BranchServer.js.map +1 -0
  141. package/dist/esm/BranchShare.d.ts +159 -0
  142. package/dist/esm/BranchShare.d.ts.map +1 -0
  143. package/dist/esm/BranchShare.js +213 -0
  144. package/dist/esm/BranchShare.js.map +1 -0
  145. package/dist/esm/RunCatalog.d.ts +207 -0
  146. package/dist/esm/RunCatalog.d.ts.map +1 -0
  147. package/dist/esm/RunCatalog.js +196 -0
  148. package/dist/esm/RunCatalog.js.map +1 -0
  149. package/dist/esm/SyncAuth.d.ts +70 -0
  150. package/dist/esm/SyncAuth.d.ts.map +1 -0
  151. package/dist/esm/SyncAuth.js +119 -0
  152. package/dist/esm/SyncAuth.js.map +1 -0
  153. package/dist/esm/SyncClient.d.ts +274 -0
  154. package/dist/esm/SyncClient.d.ts.map +1 -0
  155. package/dist/esm/SyncClient.js +586 -0
  156. package/dist/esm/SyncClient.js.map +1 -0
  157. package/dist/esm/SyncError.d.ts +107 -0
  158. package/dist/esm/SyncError.d.ts.map +1 -0
  159. package/dist/esm/SyncError.js +133 -0
  160. package/dist/esm/SyncError.js.map +1 -0
  161. package/dist/esm/SyncPrincipal.d.ts +100 -0
  162. package/dist/esm/SyncPrincipal.d.ts.map +1 -0
  163. package/dist/esm/SyncPrincipal.js +58 -0
  164. package/dist/esm/SyncPrincipal.js.map +1 -0
  165. package/dist/esm/SyncProtocol.d.ts +592 -0
  166. package/dist/esm/SyncProtocol.d.ts.map +1 -0
  167. package/dist/esm/SyncProtocol.js +434 -0
  168. package/dist/esm/SyncProtocol.js.map +1 -0
  169. package/dist/esm/SyncRpcs.d.ts +92 -0
  170. package/dist/esm/SyncRpcs.d.ts.map +1 -0
  171. package/dist/esm/SyncRpcs.js +35 -0
  172. package/dist/esm/SyncRpcs.js.map +1 -0
  173. package/dist/esm/SyncServer.d.ts +184 -0
  174. package/dist/esm/SyncServer.d.ts.map +1 -0
  175. package/dist/esm/SyncServer.js +897 -0
  176. package/dist/esm/SyncServer.js.map +1 -0
  177. package/dist/esm/WorkspaceShare.d.ts +174 -0
  178. package/dist/esm/WorkspaceShare.d.ts.map +1 -0
  179. package/dist/esm/WorkspaceShare.js +208 -0
  180. package/dist/esm/WorkspaceShare.js.map +1 -0
  181. package/dist/esm/index.d.ts +91 -0
  182. package/dist/esm/index.d.ts.map +1 -0
  183. package/dist/esm/index.js +91 -0
  184. package/dist/esm/index.js.map +1 -0
  185. package/dist/esm/internal/Admission.d.ts +43 -0
  186. package/dist/esm/internal/Admission.d.ts.map +1 -0
  187. package/dist/esm/internal/Admission.js +80 -0
  188. package/dist/esm/internal/Admission.js.map +1 -0
  189. package/dist/esm/internal/CauseText.d.ts +55 -0
  190. package/dist/esm/internal/CauseText.d.ts.map +1 -0
  191. package/dist/esm/internal/CauseText.js +67 -0
  192. package/dist/esm/internal/CauseText.js.map +1 -0
  193. package/dist/esm/internal/JournalErrorCode.d.ts +23 -0
  194. package/dist/esm/internal/JournalErrorCode.d.ts.map +1 -0
  195. package/dist/esm/internal/JournalErrorCode.js +44 -0
  196. package/dist/esm/internal/JournalErrorCode.js.map +1 -0
  197. package/dist/esm/internal/PolicyOptions.d.ts +57 -0
  198. package/dist/esm/internal/PolicyOptions.d.ts.map +1 -0
  199. package/dist/esm/internal/PolicyOptions.js +70 -0
  200. package/dist/esm/internal/PolicyOptions.js.map +1 -0
  201. package/dist/esm/internal/ProtocolVersion.d.ts +17 -0
  202. package/dist/esm/internal/ProtocolVersion.d.ts.map +1 -0
  203. package/dist/esm/internal/ProtocolVersion.js +24 -0
  204. package/dist/esm/internal/ProtocolVersion.js.map +1 -0
  205. package/dist/esm/internal/ShareSigner.d.ts +173 -0
  206. package/dist/esm/internal/ShareSigner.d.ts.map +1 -0
  207. package/dist/esm/internal/ShareSigner.js +219 -0
  208. package/dist/esm/internal/ShareSigner.js.map +1 -0
  209. package/dist/esm/internal/SnapshotBoundary.d.ts +17 -0
  210. package/dist/esm/internal/SnapshotBoundary.d.ts.map +1 -0
  211. package/dist/esm/internal/SnapshotBoundary.js +68 -0
  212. package/dist/esm/internal/SnapshotBoundary.js.map +1 -0
  213. package/dist/esm/test/TestSocket.d.ts +55 -0
  214. package/dist/esm/test/TestSocket.d.ts.map +1 -0
  215. package/dist/esm/test/TestSocket.js +128 -0
  216. package/dist/esm/test/TestSocket.js.map +1 -0
  217. package/dist/esm/test/TestSync.d.ts +54 -0
  218. package/dist/esm/test/TestSync.d.ts.map +1 -0
  219. package/dist/esm/test/TestSync.js +76 -0
  220. package/dist/esm/test/TestSync.js.map +1 -0
  221. package/docs/README.md +156 -0
  222. package/docs/api.md +328 -0
  223. package/docs/concepts/authorization.md +117 -0
  224. package/docs/concepts/branches.md +195 -0
  225. package/docs/concepts/compaction.md +144 -0
  226. package/docs/concepts/replay-then-follow.md +129 -0
  227. package/docs/concepts/scopes-and-cursors.md +135 -0
  228. package/docs/guides/authorize-a-connection.md +174 -0
  229. package/docs/guides/follow-a-run.md +174 -0
  230. package/docs/guides/handle-a-compacted-run.md +158 -0
  231. package/docs/guides/list-workspace-runs.md +120 -0
  232. package/docs/guides/run-a-long-soak.md +80 -0
  233. package/docs/guides/serve-the-read-path.md +146 -0
  234. package/docs/guides/test-a-follower.md +273 -0
  235. package/docs/installation.md +92 -0
  236. package/docs/protocol.md +183 -0
  237. package/docs/quickstart.md +159 -0
  238. package/docs/troubleshooting.md +234 -0
  239. package/package.json +304 -3
  240. package/src/BranchCommands.ts +684 -0
  241. package/src/BranchIds.ts +76 -0
  242. package/src/BranchPresence.ts +512 -0
  243. package/src/BranchProjection.ts +308 -0
  244. package/src/BranchProtocol.ts +411 -0
  245. package/src/BranchRpcs.ts +163 -0
  246. package/src/BranchServer.ts +202 -0
  247. package/src/BranchShare.ts +297 -0
  248. package/src/RunCatalog.ts +308 -0
  249. package/src/SyncAuth.ts +146 -0
  250. package/src/SyncClient.ts +902 -0
  251. package/src/SyncError.ts +147 -0
  252. package/src/SyncPrincipal.ts +115 -0
  253. package/src/SyncProtocol.ts +594 -0
  254. package/src/SyncRpcs.ts +40 -0
  255. package/src/SyncServer.ts +1184 -0
  256. package/src/WorkspaceShare.ts +292 -0
  257. package/src/index.ts +107 -0
  258. package/src/internal/Admission.ts +119 -0
  259. package/src/internal/CauseText.ts +74 -0
  260. package/src/internal/JournalErrorCode.ts +46 -0
  261. package/src/internal/PolicyOptions.ts +97 -0
  262. package/src/internal/ProtocolVersion.ts +28 -0
  263. package/src/internal/ShareSigner.ts +300 -0
  264. package/src/internal/SnapshotBoundary.ts +91 -0
  265. package/src/test/TestSocket.ts +192 -0
  266. package/src/test/TestSync.ts +113 -0
@@ -0,0 +1,145 @@
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 TestSocket_exports = {};
30
+ __export(TestSocket_exports, {
31
+ makePair: () => makePair
32
+ });
33
+ module.exports = __toCommonJS(TestSocket_exports);
34
+ var Deferred = __toESM(require("effect/Deferred"), 1);
35
+ var Effect = __toESM(require("effect/Effect"), 1);
36
+ var Latch = __toESM(require("effect/Latch"), 1);
37
+ var Queue = __toESM(require("effect/Queue"), 1);
38
+ var Scope = __toESM(require("effect/Scope"), 1);
39
+ var Socket = __toESM(require("effect/unstable/socket/Socket"), 1);
40
+ const queueCapacity = 16;
41
+ const transportError = () => new Socket.SocketError({
42
+ reason: new Socket.SocketReadError({ cause: new Error("test socket disconnected") })
43
+ });
44
+ const intersects = (leftFrom, leftTo, rightFrom, rightTo) => leftFrom <= rightTo && rightFrom <= leftTo;
45
+ const entriesRange = (bytes) => {
46
+ try {
47
+ const value = JSON.parse(new TextDecoder().decode(bytes));
48
+ return findEntriesRange(value);
49
+ } catch {
50
+ return void 0;
51
+ }
52
+ };
53
+ const findEntriesRange = (value) => {
54
+ if (typeof value !== "object" || value === null) return void 0;
55
+ const record = value;
56
+ if (record["_tag"] === "Entries" && typeof record["runId"] === "string" && typeof record["fromSeq"] === "number" && typeof record["toSeq"] === "number") {
57
+ return { runId: record["runId"], from: record["fromSeq"], to: record["toSeq"] };
58
+ }
59
+ for (const child of Object.values(record)) {
60
+ if (Array.isArray(child)) {
61
+ for (const element of child) {
62
+ const result = findEntriesRange(element);
63
+ if (result !== void 0) return result;
64
+ }
65
+ } else {
66
+ const result = findEntriesRange(child);
67
+ if (result !== void 0) return result;
68
+ }
69
+ }
70
+ return void 0;
71
+ };
72
+ const makePair = () => Effect.gen(function* () {
73
+ const scope = yield* Effect.scope;
74
+ const clientIncoming = yield* Queue.bounded(queueCapacity);
75
+ const serverIncoming = yield* Queue.bounded(queueCapacity);
76
+ yield* Scope.addFinalizer(
77
+ scope,
78
+ Effect.all([Queue.shutdown(clientIncoming), Queue.shutdown(serverIncoming)], { discard: true })
79
+ );
80
+ const delivery = Latch.makeUnsafe(true);
81
+ const filters = [];
82
+ const offer = (queue, bytes) => Effect.suspend(() => {
83
+ let current = bytes;
84
+ for (const filter of filters) {
85
+ const result = filter(current);
86
+ if (typeof result === "boolean") {
87
+ if (result === false) return Effect.void;
88
+ } else {
89
+ current = result;
90
+ }
91
+ }
92
+ return Queue.offer(queue, current).pipe(Effect.asVoid);
93
+ });
94
+ const socket = (incoming, outgoing) => {
95
+ const write = (chunk) => {
96
+ if (Socket.isCloseEvent(chunk)) return Effect.void;
97
+ const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk;
98
+ return offer(outgoing, bytes);
99
+ };
100
+ return Socket.make({
101
+ reader: Effect.gen(function* () {
102
+ const closed = yield* Deferred.make();
103
+ yield* Effect.addFinalizer(() => Deferred.fail(closed, transportError()));
104
+ return {
105
+ pull: Effect.raceFirst(
106
+ Effect.map(delivery.whenOpen(Queue.take(incoming)), (bytes) => [bytes]),
107
+ Deferred.await(closed)
108
+ ),
109
+ upgrade: Socket.SocketUpgradeError.unsupported
110
+ };
111
+ }),
112
+ writer: Effect.succeed({
113
+ write,
114
+ writeAll: (chunks) => Effect.forEach(chunks, write, { discard: true })
115
+ })
116
+ });
117
+ };
118
+ const faults = {
119
+ installFilter: (filter) => {
120
+ filters.push(filter);
121
+ },
122
+ dropRange: (runId, from, to) => {
123
+ filters.push((bytes) => {
124
+ const range = entriesRange(bytes);
125
+ return range === void 0 || range.runId !== runId || !intersects(range.from, range.to, from, to);
126
+ });
127
+ },
128
+ stall: () => {
129
+ delivery.closeUnsafe();
130
+ },
131
+ resume: () => {
132
+ delivery.openUnsafe();
133
+ },
134
+ disconnect: () => Effect.andThen(
135
+ Queue.fail(clientIncoming, transportError()),
136
+ Queue.fail(serverIncoming, transportError())
137
+ ).pipe(Effect.asVoid)
138
+ };
139
+ return {
140
+ client: socket(clientIncoming, serverIncoming),
141
+ server: socket(serverIncoming, clientIncoming),
142
+ faults
143
+ };
144
+ });
145
+ //# sourceMappingURL=TestSocket.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/test/TestSocket.ts"],
4
+ "sourcesContent": ["/**\n * Deterministic in-memory socket transport for sync tests.\n *\n * @since 0.1.0\n */\n\nimport * as Deferred from \"effect/Deferred\"\nimport * as Effect from \"effect/Effect\"\nimport * as Latch from \"effect/Latch\"\nimport * as Queue from \"effect/Queue\"\nimport * as Scope from \"effect/Scope\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\n\n/**\n * A predicate or byte transformation installed between the two socket ends.\n * Returning `false` drops the frame.\n *\n * @category models\n * @since 0.1.0\n */\nexport type FrameFilter = (bytes: Uint8Array) => boolean | Uint8Array\n\n/**\n * Deterministic transport failures that can be injected into a test socket pair.\n *\n * `dropRange` recognizes the JSON representation of an `Entries` frame without\n * importing the sync protocol. Tests using another wire representation can use\n * `installFilter` directly.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface TestFaults {\n readonly installFilter: (filter: FrameFilter) => void\n readonly dropRange: (runId: string, from: number, to: number) => void\n readonly stall: () => void\n readonly resume: () => void\n readonly disconnect: () => Effect.Effect<void>\n}\n\n/**\n * The two endpoints and controls of an in-memory socket connection.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Pair {\n readonly client: Socket.Socket\n readonly server: Socket.Socket\n readonly faults: TestFaults\n}\n\nconst queueCapacity = 16\n\nconst transportError = () =>\n new Socket.SocketError({\n reason: new Socket.SocketReadError({ cause: new Error(\"test socket disconnected\") })\n })\n\nconst intersects = (leftFrom: number, leftTo: number, rightFrom: number, rightTo: number) =>\n leftFrom <= rightTo && rightFrom <= leftTo\n\nconst entriesRange = (\n bytes: Uint8Array\n): { readonly runId: string; readonly from: number; readonly to: number } | undefined => {\n try {\n const value: unknown = JSON.parse(new TextDecoder().decode(bytes))\n return findEntriesRange(value)\n } catch {\n return undefined\n }\n}\n\nconst findEntriesRange = (\n value: unknown\n): { readonly runId: string; readonly from: number; readonly to: number } | undefined => {\n if (typeof value !== \"object\" || value === null) return undefined\n const record = value as Readonly<Record<string, unknown>>\n if (\n record[\"_tag\"] === \"Entries\" &&\n typeof record[\"runId\"] === \"string\" &&\n typeof record[\"fromSeq\"] === \"number\" &&\n typeof record[\"toSeq\"] === \"number\"\n ) {\n return { runId: record[\"runId\"], from: record[\"fromSeq\"], to: record[\"toSeq\"] }\n }\n for (const child of Object.values(record)) {\n if (Array.isArray(child)) {\n for (const element of child) {\n const result = findEntriesRange(element)\n if (result !== undefined) return result\n }\n } else {\n const result = findEntriesRange(child)\n if (result !== undefined) return result\n }\n }\n return undefined\n}\n\n/**\n * Creates a scoped, bounded, bidirectional in-memory socket connection.\n *\n * Socket runs wait on queue reads and are therefore interrupted with their\n * enclosing scope; no host abort signal or timer is involved.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makePair = (): Effect.Effect<Pair, never, Scope.Scope> =>\n Effect.gen(function*() {\n const scope = yield* Effect.scope\n const clientIncoming = yield* Queue.bounded<Uint8Array, Socket.SocketError>(queueCapacity)\n const serverIncoming = yield* Queue.bounded<Uint8Array, Socket.SocketError>(queueCapacity)\n yield* Scope.addFinalizer(\n scope,\n Effect.all([Queue.shutdown(clientIncoming), Queue.shutdown(serverIncoming)], { discard: true })\n )\n const delivery = Latch.makeUnsafe(true)\n const filters: Array<FrameFilter> = []\n\n const offer = (queue: Queue.Queue<Uint8Array, Socket.SocketError>, bytes: Uint8Array) =>\n Effect.suspend(() => {\n let current = bytes\n for (const filter of filters) {\n const result = filter(current)\n if (typeof result === \"boolean\") {\n if (result === false) return Effect.void\n } else {\n current = result\n }\n }\n return Queue.offer(queue, current).pipe(Effect.asVoid)\n })\n\n const socket = (\n incoming: Queue.Queue<Uint8Array, Socket.SocketError>,\n outgoing: Queue.Queue<Uint8Array, Socket.SocketError>\n ) => {\n const write: Socket.Writer[\"write\"] = (chunk) => {\n if (Socket.isCloseEvent(chunk)) return Effect.void\n const bytes = typeof chunk === \"string\" ? new TextEncoder().encode(chunk) : chunk\n return offer(outgoing, bytes)\n }\n return Socket.make({\n reader: Effect.gen(function*() {\n const closed = yield* Deferred.make<never, Socket.SocketError>()\n yield* Effect.addFinalizer(() => Deferred.fail(closed, transportError()))\n return {\n pull: Effect.raceFirst(\n Effect.map(delivery.whenOpen(Queue.take(incoming)), (bytes) => [bytes] as const),\n Deferred.await(closed)\n ),\n upgrade: Socket.SocketUpgradeError.unsupported\n }\n }),\n writer: Effect.succeed({\n write,\n writeAll: (chunks) => Effect.forEach(chunks, write, { discard: true })\n })\n })\n }\n\n const faults: TestFaults = {\n installFilter: (filter) => {\n filters.push(filter)\n },\n dropRange: (runId, from, to) => {\n filters.push((bytes) => {\n const range = entriesRange(bytes)\n return range === undefined || range.runId !== runId || !intersects(range.from, range.to, from, to)\n })\n },\n stall: () => {\n delivery.closeUnsafe()\n },\n resume: () => {\n delivery.openUnsafe()\n },\n disconnect: () =>\n Effect.andThen(\n Queue.fail(clientIncoming, transportError()),\n Queue.fail(serverIncoming, transportError())\n ).pipe(Effect.asVoid)\n }\n\n return {\n client: socket(clientIncoming, serverIncoming),\n server: socket(serverIncoming, clientIncoming),\n faults\n }\n })\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * In-process sync service and RPC transport wiring for tests.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as Layer from "effect/Layer";
8
+ import * as RunCatalog from "../RunCatalog.ts";
9
+ import * as SyncClient from "../SyncClient.ts";
10
+ import * as SyncRpcs from "../SyncRpcs.ts";
11
+ import * as SyncServer from "../SyncServer.ts";
12
+ import type { Pair } from "./TestSocket.ts";
13
+ /**
14
+ * An authentication middleware that trusts every connection as the workspace
15
+ * owner. Test-only by construction: replication-mechanics suites use it so
16
+ * they exercise paging and streaming without provisioning capabilities,
17
+ * while the authentication suites use the production `SyncAuth.layer`.
18
+ *
19
+ * Never compose it into a served transport: every network client would read
20
+ * every workspace run as the owner. The name says so at the call site.
21
+ *
22
+ * @category layers
23
+ * @since 1.0.0-rc.1
24
+ */
25
+ export declare const layerTrustAllAsOwner: Layer.Layer<SyncRpcs.SyncAuth, never, never>;
26
+ /**
27
+ * Provides the durable in-memory journal, a mutable empty run catalog, the
28
+ * owner-trusting {@link layerTrustAllAsOwner} middleware, and the production
29
+ * sync server.
30
+ *
31
+ * Tests that need runs in the workspace may register them through their own
32
+ * `RunCatalog.makeMemory` fixture before providing a more specific layer.
33
+ *
34
+ * @category layers
35
+ * @since 0.1.0
36
+ */
37
+ export declare const layerTest: Layer.Layer<import("@smthrs/journal/Journal").Journal | SyncRpcs.SyncAuth | RunCatalog.RunCatalog | SyncServer.SyncServer, import("effect/unstable/sql/SqlError").SqlError | import("@smthrs/journal/Journal").JournalError | import("effect/unstable/sql/Migrator").MigrationError | import("../SyncError.ts").SyncError, never>;
38
+ /**
39
+ * Provides no-op sync dependencies for consumers that only require the ports.
40
+ *
41
+ * @category layers
42
+ * @since 0.1.0
43
+ */
44
+ export declare const layerNoop: Layer.Layer<SyncRpcs.SyncAuth | RunCatalog.RunCatalog | SyncClient.SyncClient | SyncServer.SyncServer, never, never>;
45
+ /**
46
+ * Starts an RPC server over the pair's server endpoint and returns a sync
47
+ * client over the client endpoint. JSON serialization is deliberately used so
48
+ * frame faults can inspect test traffic without coupling to sync schemas.
49
+ *
50
+ * @category constructors
51
+ * @since 0.1.0
52
+ */
53
+ export declare const connect: (pair: Pair) => Effect.Effect<SyncClient.Service, never, import("effect/Scope").Scope | SyncRpcs.SyncAuth | SyncServer.SyncServer>;
54
+ //# sourceMappingURL=TestSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestSync.d.ts","sourceRoot":"","sources":["../../../src/test/TestSync.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAOrC,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAE9C,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAA;AAC1C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAI3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,8CAEhC,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,mUAQrB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,sHAKrB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,IAAI,uHA8B9B,CAAA"}
@@ -0,0 +1,96 @@
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 TestSync_exports = {};
30
+ __export(TestSync_exports, {
31
+ connect: () => connect,
32
+ layerNoop: () => layerNoop,
33
+ layerTest: () => layerTest,
34
+ layerTrustAllAsOwner: () => layerTrustAllAsOwner
35
+ });
36
+ module.exports = __toCommonJS(TestSync_exports);
37
+ var TestJournal = __toESM(require("@smthrs/journal/test/TestJournal"), 1);
38
+ var Effect = __toESM(require("effect/Effect"), 1);
39
+ var Layer = __toESM(require("effect/Layer"), 1);
40
+ var NetAddress = __toESM(require("effect/unstable/net/NetAddress"), 1);
41
+ var RpcClient = __toESM(require("effect/unstable/rpc/RpcClient"), 1);
42
+ var RpcSerialization = __toESM(require("effect/unstable/rpc/RpcSerialization"), 1);
43
+ var RpcServer = __toESM(require("effect/unstable/rpc/RpcServer"), 1);
44
+ var Socket = __toESM(require("effect/unstable/socket/Socket"), 1);
45
+ var SocketServer = __toESM(require("effect/unstable/socket/SocketServer"), 1);
46
+ var RunCatalog = __toESM(require("../RunCatalog.js"), 1);
47
+ var SyncClient = __toESM(require("../SyncClient.js"), 1);
48
+ var SyncPrincipal = __toESM(require("../SyncPrincipal.js"), 1);
49
+ var SyncRpcs = __toESM(require("../SyncRpcs.js"), 1);
50
+ var SyncServer = __toESM(require("../SyncServer.js"), 1);
51
+ const layerNoopAuth = Layer.succeed(SyncRpcs.SyncAuth)((effect) => effect);
52
+ const layerTrustAllAsOwner = Layer.succeed(SyncRpcs.SyncAuth)(
53
+ (effect) => Effect.provideService(effect, SyncPrincipal.SyncPrincipal, SyncPrincipal.workspace("test-workspace-owner"))
54
+ );
55
+ const layerTest = SyncServer.layer.pipe(
56
+ Layer.provideMerge(
57
+ Layer.mergeAll(
58
+ TestJournal.layer(),
59
+ RunCatalog.layerMemory(),
60
+ layerTrustAllAsOwner
61
+ )
62
+ )
63
+ );
64
+ const layerNoop = Layer.mergeAll(
65
+ SyncClient.layerNoop(),
66
+ SyncServer.layerNoop,
67
+ RunCatalog.layerNoop,
68
+ layerNoopAuth
69
+ );
70
+ const connect = (pair) => Effect.gen(function* () {
71
+ const sync = yield* SyncServer.SyncServer;
72
+ const handlers = SyncServer.layerHandlers.pipe(
73
+ Layer.provide(Layer.succeed(SyncServer.SyncServer, sync))
74
+ );
75
+ const serverProtocol = yield* RpcServer.makeProtocolSocketServer.pipe(
76
+ Effect.provideService(SocketServer.SocketServer, {
77
+ address: NetAddress.inetAddressFromIpStringUnsafe("127.0.0.1", 0),
78
+ run: (handler) => handler(pair.server).pipe(Effect.orDie, Effect.andThen(Effect.never))
79
+ }),
80
+ Effect.provide(RpcSerialization.layerJson)
81
+ );
82
+ yield* RpcServer.make(SyncRpcs.SyncRpcs, { disableFatalDefects: true }).pipe(
83
+ Effect.provideService(RpcServer.Protocol, serverProtocol),
84
+ Effect.provide(handlers),
85
+ Effect.forkScoped
86
+ );
87
+ const protocol = yield* RpcClient.makeProtocolSocket().pipe(
88
+ Effect.provideService(Socket.Socket, pair.client),
89
+ Effect.provide(RpcSerialization.layerJson)
90
+ );
91
+ const client = yield* RpcClient.make(SyncRpcs.SyncRpcs).pipe(
92
+ Effect.provideService(RpcClient.Protocol, protocol)
93
+ );
94
+ return yield* SyncClient.make({ client });
95
+ });
96
+ //# sourceMappingURL=TestSync.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/test/TestSync.ts"],
4
+ "sourcesContent": ["/**\n * In-process sync service and RPC transport wiring for tests.\n *\n * @since 0.1.0\n */\n\nimport * as TestJournal from \"@smthrs/journal/test/TestJournal\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as NetAddress from \"effect/unstable/net/NetAddress\"\nimport * as RpcClient from \"effect/unstable/rpc/RpcClient\"\nimport * as RpcSerialization from \"effect/unstable/rpc/RpcSerialization\"\nimport * as RpcServer from \"effect/unstable/rpc/RpcServer\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport * as SocketServer from \"effect/unstable/socket/SocketServer\"\nimport * as RunCatalog from \"../RunCatalog.ts\"\nimport * as SyncClient from \"../SyncClient.ts\"\nimport * as SyncPrincipal from \"../SyncPrincipal.ts\"\nimport * as SyncRpcs from \"../SyncRpcs.ts\"\nimport * as SyncServer from \"../SyncServer.ts\"\nimport type { Pair } from \"./TestSocket.ts\"\n\nconst layerNoopAuth = Layer.succeed(SyncRpcs.SyncAuth)((effect) => effect)\n\n/**\n * An authentication middleware that trusts every connection as the workspace\n * owner. Test-only by construction: replication-mechanics suites use it so\n * they exercise paging and streaming without provisioning capabilities,\n * while the authentication suites use the production `SyncAuth.layer`.\n *\n * Never compose it into a served transport: every network client would read\n * every workspace run as the owner. The name says so at the call site.\n *\n * @category layers\n * @since 1.0.0-rc.1\n */\nexport const layerTrustAllAsOwner = Layer.succeed(SyncRpcs.SyncAuth)((effect) =>\n Effect.provideService(effect, SyncPrincipal.SyncPrincipal, SyncPrincipal.workspace(\"test-workspace-owner\"))\n)\n\n/**\n * Provides the durable in-memory journal, a mutable empty run catalog, the\n * owner-trusting {@link layerTrustAllAsOwner} middleware, and the production\n * sync server.\n *\n * Tests that need runs in the workspace may register them through their own\n * `RunCatalog.makeMemory` fixture before providing a more specific layer.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerTest = SyncServer.layer.pipe(\n Layer.provideMerge(\n Layer.mergeAll(\n TestJournal.layer(),\n RunCatalog.layerMemory(),\n layerTrustAllAsOwner\n )\n )\n)\n\n/**\n * Provides no-op sync dependencies for consumers that only require the ports.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerNoop = Layer.mergeAll(\n SyncClient.layerNoop(),\n SyncServer.layerNoop,\n RunCatalog.layerNoop,\n layerNoopAuth\n)\n\n/**\n * Starts an RPC server over the pair's server endpoint and returns a sync\n * client over the client endpoint. JSON serialization is deliberately used so\n * frame faults can inspect test traffic without coupling to sync schemas.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const connect = (pair: Pair) =>\n Effect.gen(function*() {\n const sync = yield* SyncServer.SyncServer\n const handlers = SyncServer.layerHandlers.pipe(\n Layer.provide(Layer.succeed(SyncServer.SyncServer, sync))\n )\n // Use the production schema-aware protocol. JSON.stringify on the raw\n // makeNoSerialization responses happened to encode successes, but not\n // Effect's Cause representation: typed RPC failures never reached clients.\n const serverProtocol = yield* RpcServer.makeProtocolSocketServer.pipe(\n Effect.provideService(SocketServer.SocketServer, {\n address: NetAddress.inetAddressFromIpStringUnsafe(\"127.0.0.1\", 0),\n run: (handler) => handler(pair.server).pipe(Effect.orDie, Effect.andThen(Effect.never))\n }),\n Effect.provide(RpcSerialization.layerJson)\n )\n yield* RpcServer.make(SyncRpcs.SyncRpcs, { disableFatalDefects: true }).pipe(\n Effect.provideService(RpcServer.Protocol, serverProtocol),\n Effect.provide(handlers),\n Effect.forkScoped\n )\n\n const protocol = yield* RpcClient.makeProtocolSocket().pipe(\n Effect.provideService(Socket.Socket, pair.client),\n Effect.provide(RpcSerialization.layerJson)\n )\n const client = yield* RpcClient.make(SyncRpcs.SyncRpcs).pipe(\n Effect.provideService(RpcClient.Protocol, protocol)\n )\n return yield* SyncClient.make({ client })\n })\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,kBAA6B;AAC7B,aAAwB;AACxB,YAAuB;AACvB,iBAA4B;AAC5B,gBAA2B;AAC3B,uBAAkC;AAClC,gBAA2B;AAC3B,aAAwB;AACxB,mBAA8B;AAC9B,iBAA4B;AAC5B,iBAA4B;AAC5B,oBAA+B;AAC/B,eAA0B;AAC1B,iBAA4B;AAG5B,MAAM,gBAAgB,MAAM,QAAQ,SAAS,QAAQ,EAAE,CAAC,WAAW,MAAM;AAclE,MAAM,uBAAuB,MAAM,QAAQ,SAAS,QAAQ;AAAA,EAAE,CAAC,WACpE,OAAO,eAAe,QAAQ,cAAc,eAAe,cAAc,UAAU,sBAAsB,CAAC;AAC5G;AAaO,MAAM,YAAY,WAAW,MAAM;AAAA,EACxC,MAAM;AAAA,IACJ,MAAM;AAAA,MACJ,YAAY,MAAM;AAAA,MAClB,WAAW,YAAY;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF;AAQO,MAAM,YAAY,MAAM;AAAA,EAC7B,WAAW,UAAU;AAAA,EACrB,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AACF;AAUO,MAAM,UAAU,CAAC,SACtB,OAAO,IAAI,aAAY;AACrB,QAAM,OAAO,OAAO,WAAW;AAC/B,QAAM,WAAW,WAAW,cAAc;AAAA,IACxC,MAAM,QAAQ,MAAM,QAAQ,WAAW,YAAY,IAAI,CAAC;AAAA,EAC1D;AAIA,QAAM,iBAAiB,OAAO,UAAU,yBAAyB;AAAA,IAC/D,OAAO,eAAe,aAAa,cAAc;AAAA,MAC/C,SAAS,WAAW,8BAA8B,aAAa,CAAC;AAAA,MAChE,KAAK,CAAC,YAAY,QAAQ,KAAK,MAAM,EAAE,KAAK,OAAO,OAAO,OAAO,QAAQ,OAAO,KAAK,CAAC;AAAA,IACxF,CAAC;AAAA,IACD,OAAO,QAAQ,iBAAiB,SAAS;AAAA,EAC3C;AACA,SAAO,UAAU,KAAK,SAAS,UAAU,EAAE,qBAAqB,KAAK,CAAC,EAAE;AAAA,IACtE,OAAO,eAAe,UAAU,UAAU,cAAc;AAAA,IACxD,OAAO,QAAQ,QAAQ;AAAA,IACvB,OAAO;AAAA,EACT;AAEA,QAAM,WAAW,OAAO,UAAU,mBAAmB,EAAE;AAAA,IACrD,OAAO,eAAe,OAAO,QAAQ,KAAK,MAAM;AAAA,IAChD,OAAO,QAAQ,iBAAiB,SAAS;AAAA,EAC3C;AACA,QAAM,SAAS,OAAO,UAAU,KAAK,SAAS,QAAQ,EAAE;AAAA,IACtD,OAAO,eAAe,UAAU,UAAU,QAAQ;AAAA,EACpD;AACA,SAAO,OAAO,WAAW,KAAK,EAAE,OAAO,CAAC;AAC1C,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Idempotent admission of collaborative commands onto a branch document.
3
+ *
4
+ * Three different things look identical from the server's side — an optimistic
5
+ * client re-sending after a timeout, a reconnecting client flushing its
6
+ * outbox, and two people pressing the same button at once — and all three must
7
+ * produce exactly one durable command. The client-minted `commandId` is the
8
+ * idempotency key for all three.
9
+ *
10
+ * The exactly-once constraint is durable, not process-local: every append
11
+ * carries the producer identity `(branch run, commandSourceId(commandId),
12
+ * commandSourceSeq)`, which the journal enforces inside its own write
13
+ * transaction. Two independently constructed servers that race the same
14
+ * command therefore collide in the journal — one appends, the other receives
15
+ * a duplicate receipt or an idempotency conflict and resolves the canonical
16
+ * sequence by replaying the branch (audit finding F-14). The in-memory
17
+ * ledger, permit, and replay cursor are a fast path only: they answer known
18
+ * duplicates without a journal write and keep a restarted server from
19
+ * re-executing history, but correctness never depends on them.
20
+ *
21
+ * @since 0.1.0
22
+ */
23
+ import { Journal } from "@smthrs/journal";
24
+ import * as Context from "effect/Context";
25
+ import * as Effect from "effect/Effect";
26
+ import * as Layer from "effect/Layer";
27
+ import type * as Scope from "effect/Scope";
28
+ import { type BranchId, type CommandId, CommandReceipt, CommandSubmission, SubmitRequest } from "./BranchProtocol.ts";
29
+ import * as BranchShare from "./BranchShare.ts";
30
+ import { SyncError } from "./SyncError.ts";
31
+ /**
32
+ * Branch command admission operations.
33
+ *
34
+ * @category models
35
+ * @since 0.1.0
36
+ */
37
+ export interface Service {
38
+ readonly submit: (request: SubmitRequest) => Effect.Effect<CommandReceipt, SyncError>;
39
+ }
40
+ declare const BranchCommands_base: Context.ServiceClass<BranchCommands, "@smthrs/sync/BranchCommands", Service>;
41
+ /**
42
+ * The branch command ledger.
43
+ *
44
+ * @category services
45
+ * @since 0.1.0
46
+ */
47
+ export declare class BranchCommands extends BranchCommands_base {
48
+ }
49
+ /**
50
+ * Constructs a command ledger that admits nothing.
51
+ *
52
+ * @category constructors
53
+ * @since 0.1.0
54
+ */
55
+ export declare const makeNoop: (overrides?: Partial<Service>) => Service;
56
+ /**
57
+ * Provides a command ledger that admits nothing.
58
+ *
59
+ * @category layers
60
+ * @since 0.1.0
61
+ */
62
+ export declare const layerNoop: Layer.Layer<BranchCommands>;
63
+ /**
64
+ * Receipts one branch keeps in memory before the oldest are evicted.
65
+ *
66
+ * The ledger is a fast path: it answers a known duplicate without a journal
67
+ * write, and losing an entry costs a round trip, never correctness, because
68
+ * the journal's own producer identity is the durable exactly-once constraint
69
+ * and returns a `Duplicate` receipt for a command already admitted. Retaining
70
+ * one receipt per command forever meant a process serving a hundred branches
71
+ * of a hundred thousand commands held ten million of them, so the fast path
72
+ * is bounded and the durable path is what makes it safe to bound.
73
+ *
74
+ * @category constants
75
+ * @since 1.0.0-rc.0
76
+ */
77
+ export declare const defaultLedgerCapacity = 4096;
78
+ /**
79
+ * Inclusive UTF-8 JSON submission ceiling. The separate sync entry ceiling
80
+ * leaves room for the durable envelope around every admitted command.
81
+ *
82
+ * @category constants
83
+ * @since 1.0.0-rc.0
84
+ */
85
+ export declare const defaultMaxCommandBytes: number;
86
+ /**
87
+ * Milliseconds a branch may have no active submissions before its in-memory state is
88
+ * dropped.
89
+ *
90
+ * The receipt ledger is bounded per branch; this bounds the branches. A
91
+ * dropped branch re-hydrates on its next submission exactly as it does after
92
+ * a restart, so eviction costs a journal read, never correctness. The default
93
+ * is the longest lifetime a branch capability may carry, so a branch is only
94
+ * dropped once every capability minted at its last touch can have expired.
95
+ *
96
+ * @category constants
97
+ * @since 1.0.0-rc.0
98
+ */
99
+ export declare const defaultBranchIdleMs: number;
100
+ /**
101
+ * Entries one branch's first-touch hydration reads before it stops.
102
+ *
103
+ * Hydration is the only full history read that ever sat on the WRITE path: the
104
+ * first submission a process makes to a branch paged that branch's whole
105
+ * durable log, 256 entries at a time, while holding the branch's admission
106
+ * permit, before its own append was attempted. A hundred-thousand-entry branch
107
+ * therefore charged its next writer four hundred journal reads for a fast path
108
+ * that keeps four thousand receipts.
109
+ *
110
+ * The budget defaults to {@link defaultLedgerCapacity} because hydration
111
+ * cannot seed more receipts than the ledger holds: everything a longer walk
112
+ * records displaces something the same walk already recorded. What the extra
113
+ * reads buy is a FRESHER selection — the tail of history rather than its head
114
+ * — and the price is an unbounded wait on somebody's write. This trades that
115
+ * back: the walk stops, the cursor stays where it stopped, and a later replay
116
+ * continues forward from there.
117
+ *
118
+ * A branch longer than the budget starts with a partly cold fast path and
119
+ * nothing else. Correctness never rested on the ledger: the journal's producer
120
+ * identity is the durable exactly-once constraint, an exact retry receives a
121
+ * `Duplicate` receipt carrying the canonical sequence, and an
122
+ * `idempotency_conflict` is resolved by a read of the whole history that the
123
+ * ledger's own bound already made necessary.
124
+ *
125
+ * @category constants
126
+ * @since 1.0.0-rc.0
127
+ */
128
+ export declare const defaultHydrationLimit = 4096;
129
+ /**
130
+ * Admission policy.
131
+ *
132
+ * The default caps one encoded command submission at 1 MiB, one branch's
133
+ * in-memory receipt ledger at {@link defaultLedgerCapacity} commands, and one
134
+ * branch's first-touch hydration at {@link defaultHydrationLimit} entries.
135
+ *
136
+ * @category models
137
+ * @since 0.1.0
138
+ */
139
+ export interface Options {
140
+ /**
141
+ * Largest encoded journal entry followers accept. Defaults to
142
+ * {@link SyncProtocol.defaultMaxFrameBytes}. Configure the server and client
143
+ * with the same or a larger ceiling. Admission reserves the full durable
144
+ * envelope, including generated sequence and timestamp fields, before writing.
145
+ */
146
+ readonly maxFrameBytes?: number | undefined;
147
+ /**
148
+ * Largest encoded command submission admitted, in bytes. Defaults to
149
+ * {@link defaultMaxCommandBytes}.
150
+ */
151
+ readonly maxCommandBytes?: number | undefined;
152
+ /**
153
+ * Receipts one branch keeps in memory before the oldest are evicted.
154
+ * Defaults to {@link defaultLedgerCapacity}.
155
+ */
156
+ readonly ledgerCapacity?: number | undefined;
157
+ /**
158
+ * Entries one branch's first-touch hydration reads before it stops.
159
+ * Defaults to {@link defaultHydrationLimit}.
160
+ */
161
+ readonly hydrationLimit?: number | undefined;
162
+ /**
163
+ * Milliseconds a branch may have no active submissions before its in-memory state is
164
+ * dropped. Defaults to {@link defaultBranchIdleMs}.
165
+ */
166
+ readonly branchIdleMs?: number | undefined;
167
+ }
168
+ /**
169
+ * Constructs the journal-backed branch command ledger under an explicit
170
+ * policy. The caller scope owns cached states and their idle-expiration timers.
171
+ *
172
+ * A submission whose encoded form exceeds the command ceiling is refused with
173
+ * `frame_too_large` before anything is appended, so one oversized `args`
174
+ * cannot enter the branch journal and poison every follower that replays it.
175
+ *
176
+ * First-touch hydration reads at most `hydrationLimit` entries, so the length
177
+ * of a branch's history is not charged to the next writer's latency; see
178
+ * {@link defaultHydrationLimit} for why the fast path may be seeded partially
179
+ * without any effect on what the ledger admits.
180
+ *
181
+ * Every option is validated as a positive safe integer at construction: the
182
+ * TypeScript type says `number`, and `NaN` silently disabled the ceiling it
183
+ * was compared against.
184
+ *
185
+ * @category constructors
186
+ * @since 0.1.0
187
+ */
188
+ export declare const makeLiveWith: (options?: Options) => Effect.Effect<Service, SyncError, Journal.Journal | BranchShare.BranchShare | Scope.Scope>;
189
+ /**
190
+ * Constructs the journal-backed branch command ledger with default policy.
191
+ * The caller scope owns cached states and their idle-expiration timers.
192
+ *
193
+ * @category constructors
194
+ * @since 0.1.0
195
+ */
196
+ export declare const makeLive: Effect.Effect<Service, never, Journal.Journal | BranchShare.BranchShare | Scope.Scope>;
197
+ /**
198
+ * Provides the journal-backed branch command ledger.
199
+ *
200
+ * @category layers
201
+ * @since 0.1.0
202
+ */
203
+ export declare const layer: Layer.Layer<BranchCommands, never, Journal.Journal | BranchShare.BranchShare>;
204
+ /**
205
+ * Provides the journal-backed branch command ledger under an explicit policy.
206
+ * Fails with `invalid_request` when an option is not a positive safe integer.
207
+ *
208
+ * @category layers
209
+ * @since 0.1.0
210
+ */
211
+ export declare const layerWith: (options: Options) => Layer.Layer<BranchCommands, SyncError, Journal.Journal | BranchShare.BranchShare>;
212
+ /**
213
+ * Builds a submission, filling in the fields a plain command never sets, and
214
+ * refusing a field the schema forbids with a `SyncError` rather than a defect.
215
+ *
216
+ * @category constructors
217
+ * @since 0.1.0
218
+ */
219
+ export declare const submission: (fields: {
220
+ readonly branchId: BranchId;
221
+ readonly commandId: CommandId;
222
+ readonly participantId: CommandSubmission["participantId"];
223
+ readonly name: string;
224
+ readonly args?: string;
225
+ readonly target?: string;
226
+ }) => Effect.Effect<CommandSubmission, SyncError>;
227
+ export {};
228
+ //# sourceMappingURL=BranchCommands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BranchCommands.d.ts","sourceRoot":"","sources":["../../src/BranchCommands.ts"],"names":[],"mappings":"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"}