@smthrs/sync 0.0.0-stage → 1.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +208 -0
- package/LICENSE +21 -0
- package/README.md +208 -2
- package/dist/cjs/BranchCommands.d.ts +228 -0
- package/dist/cjs/BranchCommands.d.ts.map +1 -0
- package/dist/cjs/BranchCommands.js +316 -0
- package/dist/cjs/BranchCommands.js.map +7 -0
- package/dist/cjs/BranchIds.d.ts +65 -0
- package/dist/cjs/BranchIds.d.ts.map +1 -0
- package/dist/cjs/BranchIds.js +53 -0
- package/dist/cjs/BranchIds.js.map +7 -0
- package/dist/cjs/BranchPresence.d.ts +195 -0
- package/dist/cjs/BranchPresence.d.ts.map +1 -0
- package/dist/cjs/BranchPresence.js +276 -0
- package/dist/cjs/BranchPresence.js.map +7 -0
- package/dist/cjs/BranchProjection.d.ts +179 -0
- package/dist/cjs/BranchProjection.d.ts.map +1 -0
- package/dist/cjs/BranchProjection.js +197 -0
- package/dist/cjs/BranchProjection.js.map +7 -0
- package/dist/cjs/BranchProtocol.d.ts +415 -0
- package/dist/cjs/BranchProtocol.d.ts.map +1 -0
- package/dist/cjs/BranchProtocol.js +172 -0
- package/dist/cjs/BranchProtocol.js.map +7 -0
- package/dist/cjs/BranchRpcs.d.ts +177 -0
- package/dist/cjs/BranchRpcs.d.ts.map +1 -0
- package/dist/cjs/BranchRpcs.js +85 -0
- package/dist/cjs/BranchRpcs.js.map +7 -0
- package/dist/cjs/BranchServer.d.ts +55 -0
- package/dist/cjs/BranchServer.d.ts.map +1 -0
- package/dist/cjs/BranchServer.js +147 -0
- package/dist/cjs/BranchServer.js.map +7 -0
- package/dist/cjs/BranchShare.d.ts +159 -0
- package/dist/cjs/BranchShare.d.ts.map +1 -0
- package/dist/cjs/BranchShare.js +147 -0
- package/dist/cjs/BranchShare.js.map +7 -0
- package/dist/cjs/RunCatalog.d.ts +207 -0
- package/dist/cjs/RunCatalog.d.ts.map +1 -0
- package/dist/cjs/RunCatalog.js +122 -0
- package/dist/cjs/RunCatalog.js.map +7 -0
- package/dist/cjs/SyncAuth.d.ts +70 -0
- package/dist/cjs/SyncAuth.d.ts.map +1 -0
- package/dist/cjs/SyncAuth.js +109 -0
- package/dist/cjs/SyncAuth.js.map +7 -0
- package/dist/cjs/SyncClient.d.ts +274 -0
- package/dist/cjs/SyncClient.d.ts.map +1 -0
- package/dist/cjs/SyncClient.js +474 -0
- package/dist/cjs/SyncClient.js.map +7 -0
- package/dist/cjs/SyncError.d.ts +107 -0
- package/dist/cjs/SyncError.d.ts.map +1 -0
- package/dist/cjs/SyncError.js +110 -0
- package/dist/cjs/SyncError.js.map +7 -0
- package/dist/cjs/SyncPrincipal.d.ts +100 -0
- package/dist/cjs/SyncPrincipal.d.ts.map +1 -0
- package/dist/cjs/SyncPrincipal.js +48 -0
- package/dist/cjs/SyncPrincipal.js.map +7 -0
- package/dist/cjs/SyncProtocol.d.ts +592 -0
- package/dist/cjs/SyncProtocol.d.ts.map +1 -0
- package/dist/cjs/SyncProtocol.js +165 -0
- package/dist/cjs/SyncProtocol.js.map +7 -0
- package/dist/cjs/SyncRpcs.d.ts +92 -0
- package/dist/cjs/SyncRpcs.d.ts.map +1 -0
- package/dist/cjs/SyncRpcs.js +35 -0
- package/dist/cjs/SyncRpcs.js.map +7 -0
- package/dist/cjs/SyncServer.d.ts +184 -0
- package/dist/cjs/SyncServer.d.ts.map +1 -0
- package/dist/cjs/SyncServer.js +513 -0
- package/dist/cjs/SyncServer.js.map +7 -0
- package/dist/cjs/WorkspaceShare.d.ts +174 -0
- package/dist/cjs/WorkspaceShare.d.ts.map +1 -0
- package/dist/cjs/WorkspaceShare.js +149 -0
- package/dist/cjs/WorkspaceShare.js.map +7 -0
- package/dist/cjs/index.d.ts +91 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +67 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/Admission.d.ts +43 -0
- package/dist/cjs/internal/Admission.d.ts.map +1 -0
- package/dist/cjs/internal/Admission.js +90 -0
- package/dist/cjs/internal/Admission.js.map +7 -0
- package/dist/cjs/internal/CauseText.d.ts +55 -0
- package/dist/cjs/internal/CauseText.d.ts.map +1 -0
- package/dist/cjs/internal/CauseText.js +38 -0
- package/dist/cjs/internal/CauseText.js.map +7 -0
- package/dist/cjs/internal/JournalErrorCode.d.ts +23 -0
- package/dist/cjs/internal/JournalErrorCode.d.ts.map +1 -0
- package/dist/cjs/internal/JournalErrorCode.js +34 -0
- package/dist/cjs/internal/JournalErrorCode.js.map +7 -0
- package/dist/cjs/internal/PolicyOptions.d.ts +57 -0
- package/dist/cjs/internal/PolicyOptions.d.ts.map +1 -0
- package/dist/cjs/internal/PolicyOptions.js +62 -0
- package/dist/cjs/internal/PolicyOptions.js.map +7 -0
- package/dist/cjs/internal/ProtocolVersion.d.ts +17 -0
- package/dist/cjs/internal/ProtocolVersion.d.ts.map +1 -0
- package/dist/cjs/internal/ProtocolVersion.js +44 -0
- package/dist/cjs/internal/ProtocolVersion.js.map +7 -0
- package/dist/cjs/internal/ShareSigner.d.ts +173 -0
- package/dist/cjs/internal/ShareSigner.d.ts.map +1 -0
- package/dist/cjs/internal/ShareSigner.js +150 -0
- package/dist/cjs/internal/ShareSigner.js.map +7 -0
- package/dist/cjs/internal/SnapshotBoundary.d.ts +17 -0
- package/dist/cjs/internal/SnapshotBoundary.d.ts.map +1 -0
- package/dist/cjs/internal/SnapshotBoundary.js +97 -0
- package/dist/cjs/internal/SnapshotBoundary.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/TestSocket.d.ts +55 -0
- package/dist/cjs/test/TestSocket.d.ts.map +1 -0
- package/dist/cjs/test/TestSocket.js +145 -0
- package/dist/cjs/test/TestSocket.js.map +7 -0
- package/dist/cjs/test/TestSync.d.ts +54 -0
- package/dist/cjs/test/TestSync.d.ts.map +1 -0
- package/dist/cjs/test/TestSync.js +96 -0
- package/dist/cjs/test/TestSync.js.map +7 -0
- package/dist/esm/BranchCommands.d.ts +228 -0
- package/dist/esm/BranchCommands.d.ts.map +1 -0
- package/dist/esm/BranchCommands.js +496 -0
- package/dist/esm/BranchCommands.js.map +1 -0
- package/dist/esm/BranchIds.d.ts +65 -0
- package/dist/esm/BranchIds.d.ts.map +1 -0
- package/dist/esm/BranchIds.js +61 -0
- package/dist/esm/BranchIds.js.map +1 -0
- package/dist/esm/BranchPresence.d.ts +195 -0
- package/dist/esm/BranchPresence.d.ts.map +1 -0
- package/dist/esm/BranchPresence.js +361 -0
- package/dist/esm/BranchPresence.js.map +1 -0
- package/dist/esm/BranchProjection.d.ts +179 -0
- package/dist/esm/BranchProjection.d.ts.map +1 -0
- package/dist/esm/BranchProjection.js +269 -0
- package/dist/esm/BranchProjection.js.map +1 -0
- package/dist/esm/BranchProtocol.d.ts +415 -0
- package/dist/esm/BranchProtocol.d.ts.map +1 -0
- package/dist/esm/BranchProtocol.js +323 -0
- package/dist/esm/BranchProtocol.js.map +1 -0
- package/dist/esm/BranchRpcs.d.ts +177 -0
- package/dist/esm/BranchRpcs.d.ts.map +1 -0
- package/dist/esm/BranchRpcs.js +103 -0
- package/dist/esm/BranchRpcs.js.map +1 -0
- package/dist/esm/BranchServer.d.ts +55 -0
- package/dist/esm/BranchServer.d.ts.map +1 -0
- package/dist/esm/BranchServer.js +168 -0
- package/dist/esm/BranchServer.js.map +1 -0
- package/dist/esm/BranchShare.d.ts +159 -0
- package/dist/esm/BranchShare.d.ts.map +1 -0
- package/dist/esm/BranchShare.js +213 -0
- package/dist/esm/BranchShare.js.map +1 -0
- package/dist/esm/RunCatalog.d.ts +207 -0
- package/dist/esm/RunCatalog.d.ts.map +1 -0
- package/dist/esm/RunCatalog.js +196 -0
- package/dist/esm/RunCatalog.js.map +1 -0
- package/dist/esm/SyncAuth.d.ts +70 -0
- package/dist/esm/SyncAuth.d.ts.map +1 -0
- package/dist/esm/SyncAuth.js +119 -0
- package/dist/esm/SyncAuth.js.map +1 -0
- package/dist/esm/SyncClient.d.ts +274 -0
- package/dist/esm/SyncClient.d.ts.map +1 -0
- package/dist/esm/SyncClient.js +586 -0
- package/dist/esm/SyncClient.js.map +1 -0
- package/dist/esm/SyncError.d.ts +107 -0
- package/dist/esm/SyncError.d.ts.map +1 -0
- package/dist/esm/SyncError.js +133 -0
- package/dist/esm/SyncError.js.map +1 -0
- package/dist/esm/SyncPrincipal.d.ts +100 -0
- package/dist/esm/SyncPrincipal.d.ts.map +1 -0
- package/dist/esm/SyncPrincipal.js +58 -0
- package/dist/esm/SyncPrincipal.js.map +1 -0
- package/dist/esm/SyncProtocol.d.ts +592 -0
- package/dist/esm/SyncProtocol.d.ts.map +1 -0
- package/dist/esm/SyncProtocol.js +434 -0
- package/dist/esm/SyncProtocol.js.map +1 -0
- package/dist/esm/SyncRpcs.d.ts +92 -0
- package/dist/esm/SyncRpcs.d.ts.map +1 -0
- package/dist/esm/SyncRpcs.js +35 -0
- package/dist/esm/SyncRpcs.js.map +1 -0
- package/dist/esm/SyncServer.d.ts +184 -0
- package/dist/esm/SyncServer.d.ts.map +1 -0
- package/dist/esm/SyncServer.js +897 -0
- package/dist/esm/SyncServer.js.map +1 -0
- package/dist/esm/WorkspaceShare.d.ts +174 -0
- package/dist/esm/WorkspaceShare.d.ts.map +1 -0
- package/dist/esm/WorkspaceShare.js +208 -0
- package/dist/esm/WorkspaceShare.js.map +1 -0
- package/dist/esm/index.d.ts +91 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +91 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/Admission.d.ts +43 -0
- package/dist/esm/internal/Admission.d.ts.map +1 -0
- package/dist/esm/internal/Admission.js +80 -0
- package/dist/esm/internal/Admission.js.map +1 -0
- package/dist/esm/internal/CauseText.d.ts +55 -0
- package/dist/esm/internal/CauseText.d.ts.map +1 -0
- package/dist/esm/internal/CauseText.js +67 -0
- package/dist/esm/internal/CauseText.js.map +1 -0
- package/dist/esm/internal/JournalErrorCode.d.ts +23 -0
- package/dist/esm/internal/JournalErrorCode.d.ts.map +1 -0
- package/dist/esm/internal/JournalErrorCode.js +44 -0
- package/dist/esm/internal/JournalErrorCode.js.map +1 -0
- package/dist/esm/internal/PolicyOptions.d.ts +57 -0
- package/dist/esm/internal/PolicyOptions.d.ts.map +1 -0
- package/dist/esm/internal/PolicyOptions.js +70 -0
- package/dist/esm/internal/PolicyOptions.js.map +1 -0
- package/dist/esm/internal/ProtocolVersion.d.ts +17 -0
- package/dist/esm/internal/ProtocolVersion.d.ts.map +1 -0
- package/dist/esm/internal/ProtocolVersion.js +24 -0
- package/dist/esm/internal/ProtocolVersion.js.map +1 -0
- package/dist/esm/internal/ShareSigner.d.ts +173 -0
- package/dist/esm/internal/ShareSigner.d.ts.map +1 -0
- package/dist/esm/internal/ShareSigner.js +219 -0
- package/dist/esm/internal/ShareSigner.js.map +1 -0
- package/dist/esm/internal/SnapshotBoundary.d.ts +17 -0
- package/dist/esm/internal/SnapshotBoundary.d.ts.map +1 -0
- package/dist/esm/internal/SnapshotBoundary.js +68 -0
- package/dist/esm/internal/SnapshotBoundary.js.map +1 -0
- package/dist/esm/test/TestSocket.d.ts +55 -0
- package/dist/esm/test/TestSocket.d.ts.map +1 -0
- package/dist/esm/test/TestSocket.js +128 -0
- package/dist/esm/test/TestSocket.js.map +1 -0
- package/dist/esm/test/TestSync.d.ts +54 -0
- package/dist/esm/test/TestSync.d.ts.map +1 -0
- package/dist/esm/test/TestSync.js +76 -0
- package/dist/esm/test/TestSync.js.map +1 -0
- package/docs/README.md +156 -0
- package/docs/api.md +328 -0
- package/docs/concepts/authorization.md +117 -0
- package/docs/concepts/branches.md +195 -0
- package/docs/concepts/compaction.md +144 -0
- package/docs/concepts/replay-then-follow.md +129 -0
- package/docs/concepts/scopes-and-cursors.md +135 -0
- package/docs/guides/authorize-a-connection.md +174 -0
- package/docs/guides/follow-a-run.md +174 -0
- package/docs/guides/handle-a-compacted-run.md +158 -0
- package/docs/guides/list-workspace-runs.md +120 -0
- package/docs/guides/run-a-long-soak.md +80 -0
- package/docs/guides/serve-the-read-path.md +146 -0
- package/docs/guides/test-a-follower.md +273 -0
- package/docs/installation.md +92 -0
- package/docs/protocol.md +183 -0
- package/docs/quickstart.md +159 -0
- package/docs/troubleshooting.md +234 -0
- package/package.json +304 -3
- package/src/BranchCommands.ts +684 -0
- package/src/BranchIds.ts +76 -0
- package/src/BranchPresence.ts +512 -0
- package/src/BranchProjection.ts +308 -0
- package/src/BranchProtocol.ts +411 -0
- package/src/BranchRpcs.ts +163 -0
- package/src/BranchServer.ts +202 -0
- package/src/BranchShare.ts +297 -0
- package/src/RunCatalog.ts +308 -0
- package/src/SyncAuth.ts +146 -0
- package/src/SyncClient.ts +902 -0
- package/src/SyncError.ts +147 -0
- package/src/SyncPrincipal.ts +115 -0
- package/src/SyncProtocol.ts +594 -0
- package/src/SyncRpcs.ts +40 -0
- package/src/SyncServer.ts +1184 -0
- package/src/WorkspaceShare.ts +292 -0
- package/src/index.ts +107 -0
- package/src/internal/Admission.ts +119 -0
- package/src/internal/CauseText.ts +74 -0
- package/src/internal/JournalErrorCode.ts +46 -0
- package/src/internal/PolicyOptions.ts +97 -0
- package/src/internal/ProtocolVersion.ts +28 -0
- package/src/internal/ShareSigner.ts +300 -0
- package/src/internal/SnapshotBoundary.ts +91 -0
- package/src/test/TestSocket.ts +192 -0
- package/src/test/TestSync.ts +113 -0
|
@@ -0,0 +1,122 @@
|
|
|
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 RunCatalog_exports = {};
|
|
30
|
+
__export(RunCatalog_exports, {
|
|
31
|
+
RunCatalog: () => RunCatalog,
|
|
32
|
+
defaultChangesCapacity: () => defaultChangesCapacity,
|
|
33
|
+
defaultPollIntervalMs: () => defaultPollIntervalMs,
|
|
34
|
+
layerMemory: () => layerMemory,
|
|
35
|
+
layerNoop: () => layerNoop,
|
|
36
|
+
layerPolling: () => layerPolling,
|
|
37
|
+
layerStatic: () => layerStatic,
|
|
38
|
+
makeMemory: () => makeMemory,
|
|
39
|
+
makePolling: () => makePolling
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(RunCatalog_exports);
|
|
42
|
+
var Context = __toESM(require("effect/Context"), 1);
|
|
43
|
+
var Effect = __toESM(require("effect/Effect"), 1);
|
|
44
|
+
var Layer = __toESM(require("effect/Layer"), 1);
|
|
45
|
+
var PubSub = __toESM(require("effect/PubSub"), 1);
|
|
46
|
+
var Ref = __toESM(require("effect/Ref"), 1);
|
|
47
|
+
var Stream = __toESM(require("effect/Stream"), 1);
|
|
48
|
+
var import_PolicyOptions = require("./internal/PolicyOptions.js");
|
|
49
|
+
class RunCatalog extends Context.Service()("@smthrs/sync/RunCatalog") {
|
|
50
|
+
}
|
|
51
|
+
const layerStatic = (ids) => {
|
|
52
|
+
const known = Array.from(new Set(ids));
|
|
53
|
+
return Layer.succeed(
|
|
54
|
+
RunCatalog,
|
|
55
|
+
RunCatalog.of({
|
|
56
|
+
list: Effect.sync(() => [...known]),
|
|
57
|
+
changes: Stream.empty
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
const defaultChangesCapacity = 1024;
|
|
62
|
+
const makeMemory = (options = {}) => Effect.gen(function* () {
|
|
63
|
+
const known = /* @__PURE__ */ new Set();
|
|
64
|
+
const changes = yield* PubSub.sliding(
|
|
65
|
+
yield* (0, import_PolicyOptions.positiveInt)("RunCatalog.MemoryOptions.changesCapacity", options.changesCapacity, defaultChangesCapacity)
|
|
66
|
+
);
|
|
67
|
+
return {
|
|
68
|
+
catalog: RunCatalog.of({
|
|
69
|
+
list: Effect.sync(() => Array.from(known)).pipe(Effect.withSpan("RunCatalog.list")),
|
|
70
|
+
changes: Stream.fromPubSub(changes)
|
|
71
|
+
}),
|
|
72
|
+
register: Effect.fn("RunCatalog.register")(
|
|
73
|
+
(runId) => Effect.suspend(() => {
|
|
74
|
+
if (known.has(runId)) return Effect.void;
|
|
75
|
+
known.add(runId);
|
|
76
|
+
return PubSub.publish(changes, runId).pipe(Effect.asVoid);
|
|
77
|
+
})
|
|
78
|
+
)
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
const defaultPollIntervalMs = 1e3;
|
|
82
|
+
const makePolling = (options) => Effect.gen(function* () {
|
|
83
|
+
const intervalMs = yield* (0, import_PolicyOptions.positiveInt)(
|
|
84
|
+
"RunCatalog.PollingOptions.intervalMs",
|
|
85
|
+
options.intervalMs,
|
|
86
|
+
defaultPollIntervalMs
|
|
87
|
+
);
|
|
88
|
+
const changes = yield* PubSub.sliding(
|
|
89
|
+
yield* (0, import_PolicyOptions.positiveInt)("RunCatalog.PollingOptions.changesCapacity", options.changesCapacity, defaultChangesCapacity)
|
|
90
|
+
);
|
|
91
|
+
const snapshot = yield* Ref.make([]);
|
|
92
|
+
const refresh = Effect.gen(function* () {
|
|
93
|
+
const read = yield* options.read;
|
|
94
|
+
const next = Array.from(new Set(read));
|
|
95
|
+
const previous = new Set(yield* Ref.get(snapshot));
|
|
96
|
+
yield* Ref.set(snapshot, next);
|
|
97
|
+
for (const runId of next) {
|
|
98
|
+
if (!previous.has(runId)) yield* PubSub.publish(changes, runId);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
yield* refresh;
|
|
102
|
+
const consecutiveFailures = yield* Ref.make(0);
|
|
103
|
+
const poll = refresh.pipe(
|
|
104
|
+
Effect.matchCauseEffect({
|
|
105
|
+
onFailure: (cause) => Effect.flatMap(Ref.updateAndGet(consecutiveFailures, (count) => count + 1), (count) => count === 1 ? Effect.logWarning("The workspace run catalog could not be read; the previous view stands", cause) : Effect.void),
|
|
106
|
+
onSuccess: () => Effect.flatMap(Ref.getAndSet(consecutiveFailures, 0), (count) => count === 0 ? Effect.void : Effect.logInfo(`The workspace run catalog is readable again after ${count} failed reads`))
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
yield* poll.pipe(
|
|
110
|
+
Effect.delay(intervalMs),
|
|
111
|
+
Effect.forever,
|
|
112
|
+
Effect.forkScoped
|
|
113
|
+
);
|
|
114
|
+
return RunCatalog.of({
|
|
115
|
+
list: Effect.map(Ref.get(snapshot), (ids) => [...ids]).pipe(Effect.withSpan("RunCatalog.list")),
|
|
116
|
+
changes: Stream.fromPubSub(changes)
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
const layerPolling = (options) => Layer.effect(RunCatalog, makePolling(options));
|
|
120
|
+
const layerMemory = (options = {}) => Layer.effect(RunCatalog, Effect.map(makeMemory(options), ({ catalog }) => catalog));
|
|
121
|
+
const layerNoop = layerStatic([]);
|
|
122
|
+
//# sourceMappingURL=RunCatalog.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/RunCatalog.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Workspace run enumeration for synchronization.\n *\n * @since 0.1.0\n */\n\nimport type { JournalEvent } from \"@smthrs/journal\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as PubSub from \"effect/PubSub\"\nimport * as Ref from \"effect/Ref\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport { positiveInt } from \"./internal/PolicyOptions.ts\"\nimport type { SyncError } from \"./SyncError.ts\"\n\n/**\n * Workspace run enumeration operations.\n *\n * `list` is the AUTHORITATIVE run set and `changes` is only a low-latency\n * wake: a subscriber that misses an announcement re-lists rather than losing\n * state, and a subscriber whose covered set must stay accurate re-lists on a\n * cadence of its own. Every implementation returns a fresh array from `list`,\n * so a caller may retain and sort what it receives without disturbing another\n * reader.\n *\n * `list` names each run AT MOST ONCE. Every implementation here deduplicates,\n * and `SyncServer` deduplicates again at the seam rather than trusting a\n * host's catalog, because both fan-out paths key a run's served position by\n * run id: a run named twice is read twice from the same position and its\n * entries are served twice.\n *\n * The catalog is supplied by the host rather than derived from the journal:\n * `@smthrs/journal` has no workspace-wide list or watch contract, and\n * `@smthrs/engine-store` owns the durable run set that {@link makePolling}\n * reads.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service {\n readonly list: Effect.Effect<ReadonlyArray<JournalEvent.RunId>>\n readonly changes: Stream.Stream<JournalEvent.RunId>\n}\n\n/**\n * Host-provided workspace run catalog.\n *\n * @category services\n * @since 0.1.0\n */\nexport class RunCatalog extends Context.Service<RunCatalog, Service>()(\"@smthrs/sync/RunCatalog\") {}\n\n/**\n * Provides an immutable run catalog.\n *\n * `list` answers with a fresh array each time, matching {@link makeMemory} and\n * {@link makePolling}. Handing every caller the same instance let one reader\n * sort or splice the catalog in place for every other reader, which is a\n * defect a `ReadonlyArray` type hides from TypeScript and not from JavaScript.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerStatic = (\n ids: ReadonlyArray<JournalEvent.RunId>\n): Layer.Layer<RunCatalog> => {\n const known = Array.from(new Set(ids))\n return Layer.succeed(\n RunCatalog,\n RunCatalog.of({\n list: Effect.sync(() => [...known]),\n changes: Stream.empty\n })\n )\n}\n\n/**\n * Announcements a stalled {@link Service.changes} subscriber may fall behind\n * by before the oldest are dropped.\n *\n * `changes` is a notification feed, not a log: a follower reads it to learn\n * that the workspace gained a run and then reads the run itself. Retaining an\n * unbounded backlog for a subscriber that has stopped pulling \u2014 a disconnected\n * tab whose socket has not timed out yet, a follower blocked behind a slow\n * consumer \u2014 is therefore pure cost, and the workspace's whole run history is\n * the upper bound on it. A subscriber that falls further behind than this\n * bound loses the oldest announcements; `list` still names every run, so a\n * follower that reconnects or re-lists converges.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const defaultChangesCapacity = 1024\n\n/**\n * Options for the in-memory run catalog.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface MemoryOptions {\n /**\n * Announcements a stalled subscriber may fall behind by. Defaults to\n * {@link defaultChangesCapacity}.\n */\n readonly changesCapacity?: number | undefined\n}\n\n/**\n * Constructs a mutable in-memory run catalog.\n *\n * A run is published exactly once, when it is first registered. The\n * announcement feed slides: registering never waits on a stalled subscriber,\n * and never grows the process on its behalf.\n *\n * Fails with `invalid_request` when `changesCapacity` is not a positive safe\n * integer, rather than handing it to `PubSub.sliding` as a capacity.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeMemory = (options: MemoryOptions = {}): Effect.Effect<{\n readonly catalog: Service\n readonly register: (runId: JournalEvent.RunId) => Effect.Effect<void>\n}, SyncError> =>\n Effect.gen(function*() {\n const known = new Set<JournalEvent.RunId>()\n const changes = yield* PubSub.sliding<JournalEvent.RunId>(\n yield* positiveInt(\"RunCatalog.MemoryOptions.changesCapacity\", options.changesCapacity, defaultChangesCapacity)\n )\n return {\n catalog: RunCatalog.of({\n list: Effect.sync(() => Array.from(known)).pipe(Effect.withSpan(\"RunCatalog.list\")),\n changes: Stream.fromPubSub(changes)\n }),\n register: Effect.fn(\"RunCatalog.register\")((runId) =>\n Effect.suspend(() => {\n if (known.has(runId)) return Effect.void\n known.add(runId)\n return PubSub.publish(changes, runId).pipe(Effect.asVoid)\n })\n )\n }\n })\n\n/**\n * How often {@link makePolling} re-reads its source when the caller names no\n * interval, in milliseconds.\n *\n * A workspace follower learns of a new run within one interval of it being\n * created, and the read costs one bounded query per interval per composition,\n * not per subscriber. A second is short enough that a run appears in a UI\n * while the operator is still looking at the command that started it, and\n * long enough that an idle workspace is close to free.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const defaultPollIntervalMs = 1000\n\n/**\n * Options for the polling run catalog.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface PollingOptions<E, R> {\n /**\n * Reads the workspace's current run set. `@smthrs/engine-store` supplies\n * the durable one over `flows_runs`; anything that answers the same\n * question works, and the catalog holds no assumption about which.\n */\n readonly read: Effect.Effect<ReadonlyArray<JournalEvent.RunId>, E, R>\n /** Milliseconds between reads. Defaults to {@link defaultPollIntervalMs}. */\n readonly intervalMs?: number | undefined\n /**\n * Announcements a stalled subscriber may fall behind by. Defaults to\n * {@link defaultChangesCapacity}.\n */\n readonly changesCapacity?: number | undefined\n}\n\n/**\n * Constructs a run catalog that polls a durable source.\n *\n * Polling, not waking. rc.0 has no cross-process event delivery, so a\n * follower learns of a run another engine created by asking the workspace\n * again, and the interval is the whole of the policy.\n *\n * The catalog primes itself before it returns, so the first read a follower\n * makes is already current and a workspace that cannot be read at all fails\n * the composition instead of serving an empty run set. After that a failed\n * read is a warning and nothing else: the previous view stands, the interval\n * holds, and no subscription attached to the catalog is torn down for it.\n * The warning is written once per outage, with the first failure's cause, and\n * one info line marks the read that ends it; the polls in between are silent.\n *\n * A run is announced once, when it first appears. A run the read stops\n * naming, which is what retention collecting it looks like, leaves the view;\n * `list` is the workspace's run set, not a log of every run it ever had.\n *\n * `list` answers with a fresh array each time, matching {@link layerStatic} and\n * {@link makeMemory}. Handing back the stored snapshot let a caller sort or\n * splice the authoritative run set in place under the poll fiber that owns it,\n * which is a defect a `ReadonlyArray` type hides from TypeScript and not from\n * JavaScript.\n *\n * The poll fiber belongs to the caller's scope. Closing the scope stops it.\n *\n * Both numeric options are validated here, before the first read: an\n * `intervalMs` of `NaN` or zero reached `Effect.delay` as the whole of the\n * poll cadence, which is a loop with no interval rather than a refusal.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makePolling = <E, R>(\n options: PollingOptions<E, R>\n): Effect.Effect<Service, E | SyncError, R | Scope.Scope> =>\n Effect.gen(function*() {\n const intervalMs = yield* positiveInt(\n \"RunCatalog.PollingOptions.intervalMs\",\n options.intervalMs,\n defaultPollIntervalMs\n )\n const changes = yield* PubSub.sliding<JournalEvent.RunId>(\n yield* positiveInt(\"RunCatalog.PollingOptions.changesCapacity\", options.changesCapacity, defaultChangesCapacity)\n )\n const snapshot = yield* Ref.make<ReadonlyArray<JournalEvent.RunId>>([])\n\n const refresh = Effect.gen(function*() {\n const read = yield* options.read\n const next = Array.from(new Set(read))\n const previous = new Set(yield* Ref.get(snapshot))\n yield* Ref.set(snapshot, next)\n for (const runId of next) {\n // Publishing never waits on a stalled subscriber, and never grows the\n // process on its behalf: the feed slides.\n if (!previous.has(runId)) yield* PubSub.publish(changes, runId)\n }\n })\n\n yield* refresh\n // The log follows the outage's transitions, not the poll cadence. With\n // the default interval a workspace that stays unreadable would otherwise\n // write one identical warning per second per catalog for as long as it\n // lasts, and the line that says it ended would never be written.\n const consecutiveFailures = yield* Ref.make(0)\n const poll = refresh.pipe(\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Effect.flatMap(Ref.updateAndGet(consecutiveFailures, (count) => count + 1), (count) =>\n count === 1\n ? Effect.logWarning(\"The workspace run catalog could not be read; the previous view stands\", cause)\n : Effect.void),\n onSuccess: () =>\n Effect.flatMap(Ref.getAndSet(consecutiveFailures, 0), (count) =>\n count === 0\n ? Effect.void\n : Effect.logInfo(`The workspace run catalog is readable again after ${count} failed reads`))\n })\n )\n yield* poll.pipe(\n Effect.delay(intervalMs),\n Effect.forever,\n Effect.forkScoped\n )\n\n return RunCatalog.of({\n list: Effect.map(Ref.get(snapshot), (ids) => [...ids]).pipe(Effect.withSpan(\"RunCatalog.list\")),\n changes: Stream.fromPubSub(changes)\n })\n })\n\n/**\n * Provides a run catalog that polls a durable source. The poll fiber lives as\n * long as the layer's scope.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerPolling = <E, R>(\n options: PollingOptions<E, R>\n): Layer.Layer<RunCatalog, E | SyncError, R> => Layer.effect(RunCatalog, makePolling(options))\n\n/**\n * Provides the mutable in-memory run catalog, matching how every other\n * implementation here is provided. Fails with `invalid_request` when\n * `changesCapacity` is not a positive safe integer.\n *\n * The layer provides the catalog alone. A composition that also registers runs\n * builds {@link makeMemory} itself and keeps its `register`.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerMemory = (options: MemoryOptions = {}): Layer.Layer<RunCatalog, SyncError> =>\n Layer.effect(RunCatalog, Effect.map(makeMemory(options), ({ catalog }) => catalog))\n\n/**\n * Provides an empty run catalog.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerNoop: Layer.Layer<RunCatalog> = layerStatic([])\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,cAAyB;AACzB,aAAwB;AACxB,YAAuB;AACvB,aAAwB;AACxB,UAAqB;AAErB,aAAwB;AACxB,2BAA4B;AAsCrB,MAAM,mBAAmB,QAAQ,QAA6B,EAAE,yBAAyB,EAAE;AAAC;AAa5F,MAAM,cAAc,CACzB,QAC4B;AAC5B,QAAM,QAAQ,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC;AACrC,SAAO,MAAM;AAAA,IACX;AAAA,IACA,WAAW,GAAG;AAAA,MACZ,MAAM,OAAO,KAAK,MAAM,CAAC,GAAG,KAAK,CAAC;AAAA,MAClC,SAAS,OAAO;AAAA,IAClB,CAAC;AAAA,EACH;AACF;AAkBO,MAAM,yBAAyB;AA6B/B,MAAM,aAAa,CAAC,UAAyB,CAAC,MAInD,OAAO,IAAI,aAAY;AACrB,QAAM,QAAQ,oBAAI,IAAwB;AAC1C,QAAM,UAAU,OAAO,OAAO;AAAA,IAC5B,WAAO,kCAAY,4CAA4C,QAAQ,iBAAiB,sBAAsB;AAAA,EAChH;AACA,SAAO;AAAA,IACL,SAAS,WAAW,GAAG;AAAA,MACrB,MAAM,OAAO,KAAK,MAAM,MAAM,KAAK,KAAK,CAAC,EAAE,KAAK,OAAO,SAAS,iBAAiB,CAAC;AAAA,MAClF,SAAS,OAAO,WAAW,OAAO;AAAA,IACpC,CAAC;AAAA,IACD,UAAU,OAAO,GAAG,qBAAqB;AAAA,MAAE,CAAC,UAC1C,OAAO,QAAQ,MAAM;AACnB,YAAI,MAAM,IAAI,KAAK,EAAG,QAAO,OAAO;AACpC,cAAM,IAAI,KAAK;AACf,eAAO,OAAO,QAAQ,SAAS,KAAK,EAAE,KAAK,OAAO,MAAM;AAAA,MAC1D,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;AAeI,MAAM,wBAAwB;AA0D9B,MAAM,cAAc,CACzB,YAEA,OAAO,IAAI,aAAY;AACrB,QAAM,aAAa,WAAO;AAAA,IACxB;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF;AACA,QAAM,UAAU,OAAO,OAAO;AAAA,IAC5B,WAAO,kCAAY,6CAA6C,QAAQ,iBAAiB,sBAAsB;AAAA,EACjH;AACA,QAAM,WAAW,OAAO,IAAI,KAAwC,CAAC,CAAC;AAEtE,QAAM,UAAU,OAAO,IAAI,aAAY;AACrC,UAAM,OAAO,OAAO,QAAQ;AAC5B,UAAM,OAAO,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC;AACrC,UAAM,WAAW,IAAI,IAAI,OAAO,IAAI,IAAI,QAAQ,CAAC;AACjD,WAAO,IAAI,IAAI,UAAU,IAAI;AAC7B,eAAW,SAAS,MAAM;AAGxB,UAAI,CAAC,SAAS,IAAI,KAAK,EAAG,QAAO,OAAO,QAAQ,SAAS,KAAK;AAAA,IAChE;AAAA,EACF,CAAC;AAED,SAAO;AAKP,QAAM,sBAAsB,OAAO,IAAI,KAAK,CAAC;AAC7C,QAAM,OAAO,QAAQ;AAAA,IACnB,OAAO,iBAAiB;AAAA,MACtB,WAAW,CAAC,UACV,OAAO,QAAQ,IAAI,aAAa,qBAAqB,CAAC,UAAU,QAAQ,CAAC,GAAG,CAAC,UAC3E,UAAU,IACN,OAAO,WAAW,yEAAyE,KAAK,IAChG,OAAO,IAAI;AAAA,MACnB,WAAW,MACT,OAAO,QAAQ,IAAI,UAAU,qBAAqB,CAAC,GAAG,CAAC,UACrD,UAAU,IACN,OAAO,OACP,OAAO,QAAQ,qDAAqD,KAAK,eAAe,CAAC;AAAA,IACnG,CAAC;AAAA,EACH;AACA,SAAO,KAAK;AAAA,IACV,OAAO,MAAM,UAAU;AAAA,IACvB,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAEA,SAAO,WAAW,GAAG;AAAA,IACnB,MAAM,OAAO,IAAI,IAAI,IAAI,QAAQ,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,OAAO,SAAS,iBAAiB,CAAC;AAAA,IAC9F,SAAS,OAAO,WAAW,OAAO;AAAA,EACpC,CAAC;AACH,CAAC;AASI,MAAM,eAAe,CAC1B,YAC8C,MAAM,OAAO,YAAY,YAAY,OAAO,CAAC;AAatF,MAAM,cAAc,CAAC,UAAyB,CAAC,MACpD,MAAM,OAAO,YAAY,OAAO,IAAI,WAAW,OAAO,GAAG,CAAC,EAAE,QAAQ,MAAM,OAAO,CAAC;AAQ7E,MAAM,YAAqC,YAAY,CAAC,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sync read path's connection authentication: implementations of the
|
|
3
|
+
* `SyncRpcs.SyncAuth` RPC middleware.
|
|
4
|
+
*
|
|
5
|
+
* A connection presents a {@link WorkspaceShare.WorkspaceCapability} in the
|
|
6
|
+
* {@link capabilityHeader} request header. {@link layer} verifies it against
|
|
7
|
+
* the workspace's {@link WorkspaceShare.WorkspaceShare} authority and runs
|
|
8
|
+
* the handler as the workspace principal. A request with no header runs as
|
|
9
|
+
* the anonymous principal — branch share capabilities carried in request
|
|
10
|
+
* payloads still authorize branch reads — and the server refuses anonymous
|
|
11
|
+
* access to every non-branch run. A request whose header is present but
|
|
12
|
+
* malformed, forged, expired, or signed by an unknown key is refused
|
|
13
|
+
* outright: presence of a credential is a claim, and a false claim is never
|
|
14
|
+
* downgraded to anonymity.
|
|
15
|
+
*
|
|
16
|
+
* There is deliberately no trusted pass-through implementation here: the only
|
|
17
|
+
* sanctioned bypass is an in-process caller providing `SyncPrincipal` itself,
|
|
18
|
+
* never a transport.
|
|
19
|
+
*
|
|
20
|
+
* @since 0.1.0
|
|
21
|
+
*/
|
|
22
|
+
import * as Effect from "effect/Effect";
|
|
23
|
+
import * as Layer from "effect/Layer";
|
|
24
|
+
import * as RpcMiddleware from "effect/unstable/rpc/RpcMiddleware";
|
|
25
|
+
import { SyncError } from "./SyncError.ts";
|
|
26
|
+
import { SyncAuth } from "./SyncRpcs.ts";
|
|
27
|
+
import * as WorkspaceShare from "./WorkspaceShare.ts";
|
|
28
|
+
/**
|
|
29
|
+
* The request header a connection presents its workspace capability in.
|
|
30
|
+
*
|
|
31
|
+
* @category constants
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
*/
|
|
34
|
+
export declare const capabilityHeader = "flows-sync-workspace";
|
|
35
|
+
/**
|
|
36
|
+
* Renders a workspace capability as its header value: unpadded base64url of
|
|
37
|
+
* the schema-encoded capability JSON.
|
|
38
|
+
*
|
|
39
|
+
* @category encoding
|
|
40
|
+
* @since 0.1.0
|
|
41
|
+
*/
|
|
42
|
+
export declare const encodeCapability: (capability: WorkspaceShare.WorkspaceCapability) => Effect.Effect<string, SyncError>;
|
|
43
|
+
/**
|
|
44
|
+
* Decodes a capability header value. Every malformation folds to the same
|
|
45
|
+
* `unauthorized` refusal so the header is not a parsing oracle.
|
|
46
|
+
*
|
|
47
|
+
* @category encoding
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
*/
|
|
50
|
+
export declare const decodeCapability: (value: string) => Effect.Effect<WorkspaceShare.WorkspaceCapability, SyncError>;
|
|
51
|
+
/**
|
|
52
|
+
* Provides the workspace-capability authentication middleware: no header
|
|
53
|
+
* runs the handler as anonymous, a verified header runs it as the workspace
|
|
54
|
+
* principal, and anything else refuses the request. Verification faults
|
|
55
|
+
* (Web Crypto failing) propagate as their own error rather than as a
|
|
56
|
+
* refusal.
|
|
57
|
+
*
|
|
58
|
+
* @category layers
|
|
59
|
+
* @since 0.1.0
|
|
60
|
+
*/
|
|
61
|
+
export declare const layer: Layer.Layer<SyncAuth, never, WorkspaceShare.WorkspaceShare>;
|
|
62
|
+
/**
|
|
63
|
+
* Stamps every outgoing request from a generated RPC client with the given
|
|
64
|
+
* workspace capability.
|
|
65
|
+
*
|
|
66
|
+
* @category layers
|
|
67
|
+
* @since 0.1.0
|
|
68
|
+
*/
|
|
69
|
+
export declare const layerClient: (capability: WorkspaceShare.WorkspaceCapability) => Layer.Layer<RpcMiddleware.ForClient<SyncAuth>, SyncError>;
|
|
70
|
+
//# sourceMappingURL=SyncAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncAuth.d.ts","sourceRoot":"","sources":["../../src/SyncAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAIrC,OAAO,KAAK,aAAa,MAAM,mCAAmC,CAAA;AAGlE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,yBAAyB,CAAA;AAItD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAC3B,YAAY,cAAc,CAAC,mBAAmB,KAC7C,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAajC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,mBAAmB,EAAE,SAAS,CAe3G,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,cAAc,CAAC,cAAc,CAyB7E,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACtB,YAAY,cAAc,CAAC,mBAAmB,KAC7C,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,SAAS,CAKxD,CAAA"}
|
|
@@ -0,0 +1,109 @@
|
|
|
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 SyncAuth_exports = {};
|
|
30
|
+
__export(SyncAuth_exports, {
|
|
31
|
+
capabilityHeader: () => capabilityHeader,
|
|
32
|
+
decodeCapability: () => decodeCapability,
|
|
33
|
+
encodeCapability: () => encodeCapability,
|
|
34
|
+
layer: () => layer,
|
|
35
|
+
layerClient: () => layerClient
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(SyncAuth_exports);
|
|
38
|
+
var Effect = __toESM(require("effect/Effect"), 1);
|
|
39
|
+
var Encoding = __toESM(require("effect/Encoding"), 1);
|
|
40
|
+
var Layer = __toESM(require("effect/Layer"), 1);
|
|
41
|
+
var Result = __toESM(require("effect/Result"), 1);
|
|
42
|
+
var Schema = __toESM(require("effect/Schema"), 1);
|
|
43
|
+
var Headers = __toESM(require("effect/unstable/http/Headers"), 1);
|
|
44
|
+
var RpcMiddleware = __toESM(require("effect/unstable/rpc/RpcMiddleware"), 1);
|
|
45
|
+
var import_CauseText = require("./internal/CauseText.js");
|
|
46
|
+
var shareSigner = __toESM(require("./internal/ShareSigner.js"), 1);
|
|
47
|
+
var import_SyncError = require("./SyncError.js");
|
|
48
|
+
var SyncPrincipal = __toESM(require("./SyncPrincipal.js"), 1);
|
|
49
|
+
var import_SyncRpcs = require("./SyncRpcs.js");
|
|
50
|
+
var WorkspaceShare = __toESM(require("./WorkspaceShare.js"), 1);
|
|
51
|
+
const capabilityHeader = "flows-sync-workspace";
|
|
52
|
+
const denied = shareSigner.unauthorized;
|
|
53
|
+
const encodeCapability = (capability) => {
|
|
54
|
+
const encoded = Schema.encodeUnknownResult(WorkspaceShare.WorkspaceCapability)(capability);
|
|
55
|
+
return Result.isFailure(encoded) ? Effect.fail(
|
|
56
|
+
new import_SyncError.SyncError({
|
|
57
|
+
code: "invalid_request",
|
|
58
|
+
message: "The workspace capability does not encode",
|
|
59
|
+
// The rendering, never the parse failure itself: a schema issue
|
|
60
|
+
// carries the value it rejected, and the value here is a credential.
|
|
61
|
+
cause: (0, import_CauseText.causeText)(encoded.failure)
|
|
62
|
+
})
|
|
63
|
+
) : Effect.succeed(Encoding.encodeBase64Url(JSON.stringify(encoded.success)));
|
|
64
|
+
};
|
|
65
|
+
const decodeCapability = (value) => {
|
|
66
|
+
const text = Encoding.decodeBase64UrlString(value);
|
|
67
|
+
if (Result.isFailure(text)) {
|
|
68
|
+
return Effect.fail(denied("The workspace capability header is not a capability"));
|
|
69
|
+
}
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(text.success);
|
|
73
|
+
} catch {
|
|
74
|
+
return Effect.fail(denied("The workspace capability header is not a capability"));
|
|
75
|
+
}
|
|
76
|
+
const decoded = Schema.decodeUnknownResult(WorkspaceShare.WorkspaceCapability)(parsed);
|
|
77
|
+
return Result.isFailure(decoded) ? Effect.fail(denied("The workspace capability header is not a capability")) : Effect.succeed(decoded.success);
|
|
78
|
+
};
|
|
79
|
+
const layer = Layer.effect(
|
|
80
|
+
import_SyncRpcs.SyncAuth,
|
|
81
|
+
Effect.gen(function* () {
|
|
82
|
+
const share = yield* WorkspaceShare.WorkspaceShare;
|
|
83
|
+
const middleware = (effect, options) => {
|
|
84
|
+
const header = Headers.get(options.headers, capabilityHeader);
|
|
85
|
+
if (header._tag === "None") {
|
|
86
|
+
return Effect.provideService(effect, SyncPrincipal.SyncPrincipal, SyncPrincipal.anonymous);
|
|
87
|
+
}
|
|
88
|
+
return decodeCapability(header.value).pipe(
|
|
89
|
+
Effect.flatMap((capability) => share.verify(capability, { access: "read" })),
|
|
90
|
+
Effect.flatMap(
|
|
91
|
+
(claims) => Effect.provideService(
|
|
92
|
+
effect,
|
|
93
|
+
SyncPrincipal.SyncPrincipal,
|
|
94
|
+
// The signed expiry travels WITH the identity. A handler that
|
|
95
|
+
// opens a stream authorizes once; reducing the verified claims to
|
|
96
|
+
// a capability id here left the stream with nothing to expire on.
|
|
97
|
+
SyncPrincipal.workspace(claims.capabilityId, claims.expiresAtMs)
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
return middleware;
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
const layerClient = (capability) => RpcMiddleware.layerClient(
|
|
106
|
+
import_SyncRpcs.SyncAuth,
|
|
107
|
+
Effect.map(encodeCapability(capability), (header) => ({ next, request }) => next({ ...request, headers: Headers.set(request.headers, capabilityHeader, header) }))
|
|
108
|
+
);
|
|
109
|
+
//# sourceMappingURL=SyncAuth.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/SyncAuth.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The sync read path's connection authentication: implementations of the\n * `SyncRpcs.SyncAuth` RPC middleware.\n *\n * A connection presents a {@link WorkspaceShare.WorkspaceCapability} in the\n * {@link capabilityHeader} request header. {@link layer} verifies it against\n * the workspace's {@link WorkspaceShare.WorkspaceShare} authority and runs\n * the handler as the workspace principal. A request with no header runs as\n * the anonymous principal \u2014 branch share capabilities carried in request\n * payloads still authorize branch reads \u2014 and the server refuses anonymous\n * access to every non-branch run. A request whose header is present but\n * malformed, forged, expired, or signed by an unknown key is refused\n * outright: presence of a credential is a claim, and a false claim is never\n * downgraded to anonymity.\n *\n * There is deliberately no trusted pass-through implementation here: the only\n * sanctioned bypass is an in-process caller providing `SyncPrincipal` itself,\n * never a transport.\n *\n * @since 0.1.0\n */\n\nimport * as Effect from \"effect/Effect\"\nimport * as Encoding from \"effect/Encoding\"\nimport * as Layer from \"effect/Layer\"\nimport * as Result from \"effect/Result\"\nimport * as Schema from \"effect/Schema\"\nimport * as Headers from \"effect/unstable/http/Headers\"\nimport * as RpcMiddleware from \"effect/unstable/rpc/RpcMiddleware\"\nimport { causeText } from \"./internal/CauseText.ts\"\nimport * as shareSigner from \"./internal/ShareSigner.ts\"\nimport { SyncError } from \"./SyncError.ts\"\nimport * as SyncPrincipal from \"./SyncPrincipal.ts\"\nimport { SyncAuth } from \"./SyncRpcs.ts\"\nimport * as WorkspaceShare from \"./WorkspaceShare.ts\"\n\n/**\n * The request header a connection presents its workspace capability in.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const capabilityHeader = \"flows-sync-workspace\"\n\nconst denied = shareSigner.unauthorized\n\n/**\n * Renders a workspace capability as its header value: unpadded base64url of\n * the schema-encoded capability JSON.\n *\n * @category encoding\n * @since 0.1.0\n */\nexport const encodeCapability = (\n capability: WorkspaceShare.WorkspaceCapability\n): Effect.Effect<string, SyncError> => {\n const encoded = Schema.encodeUnknownResult(WorkspaceShare.WorkspaceCapability)(capability)\n return Result.isFailure(encoded)\n ? Effect.fail(\n new SyncError({\n code: \"invalid_request\",\n message: \"The workspace capability does not encode\",\n // The rendering, never the parse failure itself: a schema issue\n // carries the value it rejected, and the value here is a credential.\n cause: causeText(encoded.failure)\n })\n )\n : Effect.succeed(Encoding.encodeBase64Url(JSON.stringify(encoded.success)))\n}\n\n/**\n * Decodes a capability header value. Every malformation folds to the same\n * `unauthorized` refusal so the header is not a parsing oracle.\n *\n * @category encoding\n * @since 0.1.0\n */\nexport const decodeCapability = (value: string): Effect.Effect<WorkspaceShare.WorkspaceCapability, SyncError> => {\n const text = Encoding.decodeBase64UrlString(value)\n if (Result.isFailure(text)) {\n return Effect.fail(denied(\"The workspace capability header is not a capability\"))\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(text.success)\n } catch {\n return Effect.fail(denied(\"The workspace capability header is not a capability\"))\n }\n const decoded = Schema.decodeUnknownResult(WorkspaceShare.WorkspaceCapability)(parsed)\n return Result.isFailure(decoded)\n ? Effect.fail(denied(\"The workspace capability header is not a capability\"))\n : Effect.succeed(decoded.success)\n}\n\n/**\n * Provides the workspace-capability authentication middleware: no header\n * runs the handler as anonymous, a verified header runs it as the workspace\n * principal, and anything else refuses the request. Verification faults\n * (Web Crypto failing) propagate as their own error rather than as a\n * refusal.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer: Layer.Layer<SyncAuth, never, WorkspaceShare.WorkspaceShare> = Layer.effect(\n SyncAuth,\n Effect.gen(function*() {\n const share = yield* WorkspaceShare.WorkspaceShare\n const middleware: RpcMiddleware.RpcMiddleware<never, SyncError, never> = (effect, options) => {\n const header = Headers.get(options.headers, capabilityHeader)\n if (header._tag === \"None\") {\n return Effect.provideService(effect, SyncPrincipal.SyncPrincipal, SyncPrincipal.anonymous)\n }\n return decodeCapability(header.value).pipe(\n Effect.flatMap((capability) => share.verify(capability, { access: \"read\" })),\n Effect.flatMap((claims) =>\n Effect.provideService(\n effect,\n SyncPrincipal.SyncPrincipal,\n // The signed expiry travels WITH the identity. A handler that\n // opens a stream authorizes once; reducing the verified claims to\n // a capability id here left the stream with nothing to expire on.\n SyncPrincipal.workspace(claims.capabilityId, claims.expiresAtMs)\n )\n )\n )\n }\n return middleware\n })\n)\n\n/**\n * Stamps every outgoing request from a generated RPC client with the given\n * workspace capability.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerClient = (\n capability: WorkspaceShare.WorkspaceCapability\n): Layer.Layer<RpcMiddleware.ForClient<SyncAuth>, SyncError> =>\n RpcMiddleware.layerClient(\n SyncAuth,\n Effect.map(encodeCapability(capability), (header) => ({ next, request }) =>\n next({ ...request, headers: Headers.set(request.headers, capabilityHeader, header) }))\n )\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,aAAwB;AACxB,eAA0B;AAC1B,YAAuB;AACvB,aAAwB;AACxB,aAAwB;AACxB,cAAyB;AACzB,oBAA+B;AAC/B,uBAA0B;AAC1B,kBAA6B;AAC7B,uBAA0B;AAC1B,oBAA+B;AAC/B,sBAAyB;AACzB,qBAAgC;AAQzB,MAAM,mBAAmB;AAEhC,MAAM,SAAS,YAAY;AASpB,MAAM,mBAAmB,CAC9B,eACqC;AACrC,QAAM,UAAU,OAAO,oBAAoB,eAAe,mBAAmB,EAAE,UAAU;AACzF,SAAO,OAAO,UAAU,OAAO,IAC3B,OAAO;AAAA,IACP,IAAI,2BAAU;AAAA,MACZ,MAAM;AAAA,MACN,SAAS;AAAA;AAAA;AAAA,MAGT,WAAO,4BAAU,QAAQ,OAAO;AAAA,IAClC,CAAC;AAAA,EACH,IACE,OAAO,QAAQ,SAAS,gBAAgB,KAAK,UAAU,QAAQ,OAAO,CAAC,CAAC;AAC9E;AASO,MAAM,mBAAmB,CAAC,UAAgF;AAC/G,QAAM,OAAO,SAAS,sBAAsB,KAAK;AACjD,MAAI,OAAO,UAAU,IAAI,GAAG;AAC1B,WAAO,OAAO,KAAK,OAAO,qDAAqD,CAAC;AAAA,EAClF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,KAAK,OAAO;AAAA,EAClC,QAAQ;AACN,WAAO,OAAO,KAAK,OAAO,qDAAqD,CAAC;AAAA,EAClF;AACA,QAAM,UAAU,OAAO,oBAAoB,eAAe,mBAAmB,EAAE,MAAM;AACrF,SAAO,OAAO,UAAU,OAAO,IAC3B,OAAO,KAAK,OAAO,qDAAqD,CAAC,IACzE,OAAO,QAAQ,QAAQ,OAAO;AACpC;AAYO,MAAM,QAAqE,MAAM;AAAA,EACtF;AAAA,EACA,OAAO,IAAI,aAAY;AACrB,UAAM,QAAQ,OAAO,eAAe;AACpC,UAAM,aAAmE,CAAC,QAAQ,YAAY;AAC5F,YAAM,SAAS,QAAQ,IAAI,QAAQ,SAAS,gBAAgB;AAC5D,UAAI,OAAO,SAAS,QAAQ;AAC1B,eAAO,OAAO,eAAe,QAAQ,cAAc,eAAe,cAAc,SAAS;AAAA,MAC3F;AACA,aAAO,iBAAiB,OAAO,KAAK,EAAE;AAAA,QACpC,OAAO,QAAQ,CAAC,eAAe,MAAM,OAAO,YAAY,EAAE,QAAQ,OAAO,CAAC,CAAC;AAAA,QAC3E,OAAO;AAAA,UAAQ,CAAC,WACd,OAAO;AAAA,YACL;AAAA,YACA,cAAc;AAAA;AAAA;AAAA;AAAA,YAId,cAAc,UAAU,OAAO,cAAc,OAAO,WAAW;AAAA,UACjE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;AASO,MAAM,cAAc,CACzB,eAEA,cAAc;AAAA,EACZ;AAAA,EACA,OAAO,IAAI,iBAAiB,UAAU,GAAG,CAAC,WAAW,CAAC,EAAE,MAAM,QAAQ,MACpE,KAAK,EAAE,GAAG,SAAS,SAAS,QAAQ,IAAI,QAAQ,SAAS,kBAAkB,MAAM,EAAE,CAAC,CAAC;AACzF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe client for replaying and following journal entries.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
import type * as JournalEvent from "@smthrs/journal/JournalEvent";
|
|
7
|
+
import * as Context from "effect/Context";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Layer from "effect/Layer";
|
|
10
|
+
import * as Stream from "effect/Stream";
|
|
11
|
+
import * as RpcClient from "effect/unstable/rpc/RpcClient";
|
|
12
|
+
import * as RpcClientError from "effect/unstable/rpc/RpcClientError";
|
|
13
|
+
import type * as RpcGroup from "effect/unstable/rpc/RpcGroup";
|
|
14
|
+
import type { ShareCapability } from "./BranchProtocol.ts";
|
|
15
|
+
import { SyncError, SyncGapError } from "./SyncError.ts";
|
|
16
|
+
import { type Progress, type Resync, RunCursor, type Scope, type Snapshot, type SnapshotRequest, type WorkspaceCursor } from "./SyncProtocol.ts";
|
|
17
|
+
import { SyncRpcs } from "./SyncRpcs.ts";
|
|
18
|
+
/**
|
|
19
|
+
* Frames one subscription round may carry before the follow replenishes it.
|
|
20
|
+
*
|
|
21
|
+
* A subscription's `credit` is a hard frame limit the server enforces with
|
|
22
|
+
* `Stream.take`, so the number the client sends is the size of its window, not
|
|
23
|
+
* a hint. At `credit: 1` the window closed after every single entry and the
|
|
24
|
+
* follow resubscribed: following a run that emits a hundred entries cost a
|
|
25
|
+
* hundred subscribe round trips, each of them a fresh cursor snapshot and a
|
|
26
|
+
* fresh server-side fan-out. Replenishing 256 frames at a time makes the
|
|
27
|
+
* round-trip cost proportional to the window rather than to the traffic, and
|
|
28
|
+
* keeps it bounded — an unbounded window would hand the server one
|
|
29
|
+
* never-closing subscription per follower.
|
|
30
|
+
*
|
|
31
|
+
* @category constants
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
*/
|
|
34
|
+
export declare const defaultCredit = 256;
|
|
35
|
+
/**
|
|
36
|
+
* Entries one catch-up page asks for.
|
|
37
|
+
*
|
|
38
|
+
* Bootstrap is the replay half of replay-then-follow, so the page size is the
|
|
39
|
+
* memory one catch-up costs and the granularity at which its cursor advances.
|
|
40
|
+
* It matches {@link defaultCredit} and stays under
|
|
41
|
+
* `SyncProtocol.maxReadLimit`, which is the ceiling the server enforces.
|
|
42
|
+
*
|
|
43
|
+
* @category constants
|
|
44
|
+
* @since 1.0.0-rc.0
|
|
45
|
+
*/
|
|
46
|
+
export declare const defaultBootstrapLimit = 256;
|
|
47
|
+
/**
|
|
48
|
+
* The policy one sync client runs every operation under.
|
|
49
|
+
*
|
|
50
|
+
* Both fields are validated by {@link makeWith} and {@link layerWith}, so a
|
|
51
|
+
* value that is not a positive safe integer fails the constructor rather than
|
|
52
|
+
* disabling the bound it configures.
|
|
53
|
+
*
|
|
54
|
+
* @category models
|
|
55
|
+
* @since 1.0.0-rc.0
|
|
56
|
+
*/
|
|
57
|
+
export interface Options {
|
|
58
|
+
/**
|
|
59
|
+
* Largest summed encoded-entry size one frame or bootstrap page may carry,
|
|
60
|
+
* in bytes. Defaults to {@link defaultMaxFrameBytes}.
|
|
61
|
+
*/
|
|
62
|
+
readonly maxFrameBytes?: number | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Entries one catch-up page asks for. Defaults to
|
|
65
|
+
* {@link defaultBootstrapLimit}, and may not exceed
|
|
66
|
+
* `SyncProtocol.maxReadLimit`.
|
|
67
|
+
*/
|
|
68
|
+
readonly bootstrapLimit?: number | undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Input accepted by a sync subscription.
|
|
72
|
+
*
|
|
73
|
+
* `capability` authorizes branch reads: following a shared branch's run
|
|
74
|
+
* requires a capability that verifies for that branch on the server.
|
|
75
|
+
*
|
|
76
|
+
* `EApply` and `EResync` are the consumer's own failure types for `apply` and
|
|
77
|
+
* `onResync`. The subscription fails with them unchanged, so an application
|
|
78
|
+
* failure is never dressed as a transport or protocol {@link SyncError}.
|
|
79
|
+
*
|
|
80
|
+
* @category models
|
|
81
|
+
* @since 0.1.0
|
|
82
|
+
*/
|
|
83
|
+
export interface SubscribeOptions<EApply = never, EResync = never> {
|
|
84
|
+
readonly scope: Scope;
|
|
85
|
+
/**
|
|
86
|
+
* Where this subscription starts, per run. The effective position is the
|
|
87
|
+
* LATER of this and the client's matching progress map (applied when an
|
|
88
|
+
* apply callback is present, delivered otherwise), so a caller
|
|
89
|
+
* ahead of the shared view keeps its place and a caller behind it is
|
|
90
|
+
* fast-forwarded. Rebuild from an earlier position with a fresh client.
|
|
91
|
+
*/
|
|
92
|
+
readonly cursors: WorkspaceCursor;
|
|
93
|
+
readonly capability?: ShareCapability;
|
|
94
|
+
/**
|
|
95
|
+
* Frames one subscription round may carry before the follow replenishes the
|
|
96
|
+
* window. Defaults to {@link defaultCredit}, and must be a positive integer
|
|
97
|
+
* no greater than `SyncProtocol.maxSubscribeCredit`.
|
|
98
|
+
*/
|
|
99
|
+
readonly credit?: number | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Applies one entry, and DEFINES what this subscription's cursor means.
|
|
102
|
+
*
|
|
103
|
+
* Without it the cursor advances as each entry is handed to the consumer,
|
|
104
|
+
* so a consumer whose own application then fails has a cursor naming an
|
|
105
|
+
* entry it never materialized. With it the cursor advances only after this
|
|
106
|
+
* effect succeeds, and records it in `Progress.applied`. A failure here
|
|
107
|
+
* fails the subscription with the entry unacknowledged, so the
|
|
108
|
+
* next applying subscription delivers it again; the effect must
|
|
109
|
+
* therefore be idempotent, because a redelivery is exactly what a retry is.
|
|
110
|
+
*/
|
|
111
|
+
readonly apply?: ((entry: JournalEvent.Entry) => Effect.Effect<void, EApply>) | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Restores state when the server refuses a cursor below a compaction floor.
|
|
114
|
+
* Return the exact run and sequence actually restored, after applying it.
|
|
115
|
+
*
|
|
116
|
+
* The entries under the floor are deleted. Fetch an explicitly public
|
|
117
|
+
* projection through `snapshot` or an authorized local source; a
|
|
118
|
+
* projection has a real hole to fill. The returned sequence must cover the
|
|
119
|
+
* requested floor; a newer snapshot resumes at its own sequence, not the
|
|
120
|
+
* older floor. Missing handlers, failures, interruption, and invalid receipts
|
|
121
|
+
* leave the cursor unmoved. There is no implicit skip of deleted history.
|
|
122
|
+
*
|
|
123
|
+
* Durable consumers must commit their restored state and durable cursor in
|
|
124
|
+
* their own transaction and seed a fresh client from that cursor on restart.
|
|
125
|
+
* This client's cursor is in memory, not an atomic cross-store transaction.
|
|
126
|
+
* Recovery must be idempotent, including when a newer compaction requires
|
|
127
|
+
* another restoration before the suffix can be read.
|
|
128
|
+
*/
|
|
129
|
+
readonly onResync?: ((resync: Resync) => Effect.Effect<RunCursor, EResync>) | undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Replicated journal subscription operations.
|
|
133
|
+
*
|
|
134
|
+
* @category models
|
|
135
|
+
* @since 0.1.0
|
|
136
|
+
*/
|
|
137
|
+
export interface Service {
|
|
138
|
+
/** Fetches a public snapshot without applying state or advancing a cursor. */
|
|
139
|
+
readonly snapshot: (request: SnapshotRequest) => Effect.Effect<Snapshot, SyncError>;
|
|
140
|
+
/** Replays then follows `options.scope`, also failing with the callbacks' own errors. */
|
|
141
|
+
readonly subscribe: <EApply = never, EResync = never>(options: SubscribeOptions<EApply, EResync>) => Stream.Stream<JournalEvent.Entry, SyncError | SyncGapError | EApply | EResync>;
|
|
142
|
+
/**
|
|
143
|
+
* Delivery and application positions. `delivered` is a transport bookmark
|
|
144
|
+
* only; `applied` is what the `apply` and `onResync` callbacks acknowledged.
|
|
145
|
+
*/
|
|
146
|
+
readonly progress: Effect.Effect<Progress>;
|
|
147
|
+
}
|
|
148
|
+
declare const SyncClient_base: Context.ServiceClass<SyncClient, "@smthrs/sync/SyncClient", Service>;
|
|
149
|
+
/**
|
|
150
|
+
* The browser-safe journal replication client.
|
|
151
|
+
*
|
|
152
|
+
* @category services
|
|
153
|
+
* @since 1.0.0-rc.0
|
|
154
|
+
*/
|
|
155
|
+
export declare class SyncClient extends SyncClient_base {
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The client tag's former name, kept for one release.
|
|
159
|
+
*
|
|
160
|
+
* @deprecated Use {@link SyncClient}.
|
|
161
|
+
* @category services
|
|
162
|
+
* @since 0.1.0
|
|
163
|
+
*/
|
|
164
|
+
export declare const Sync: typeof SyncClient;
|
|
165
|
+
/**
|
|
166
|
+
* The client tag's former name, kept for one release.
|
|
167
|
+
*
|
|
168
|
+
* @deprecated Use {@link SyncClient}.
|
|
169
|
+
* @category services
|
|
170
|
+
* @since 0.1.0
|
|
171
|
+
*/
|
|
172
|
+
export type Sync = SyncClient;
|
|
173
|
+
/**
|
|
174
|
+
* Constructs a closed sync stub, optionally overriding individual operations.
|
|
175
|
+
*
|
|
176
|
+
* @category constructors
|
|
177
|
+
* @since 0.1.0
|
|
178
|
+
*/
|
|
179
|
+
export declare const makeNoop: (overrides?: Partial<Service>) => Service;
|
|
180
|
+
/**
|
|
181
|
+
* Provides a closed sync stub.
|
|
182
|
+
*
|
|
183
|
+
* @category layers
|
|
184
|
+
* @since 0.1.0
|
|
185
|
+
*/
|
|
186
|
+
export declare const layerNoop: (overrides?: Partial<Service>) => Layer.Layer<SyncClient>;
|
|
187
|
+
type Client = RpcClient.RpcClient<RpcGroup.Rpcs<typeof SyncRpcs>, RpcClientError.RpcClientError>;
|
|
188
|
+
/**
|
|
189
|
+
* Projects a Sync RPC client into the local replication service.
|
|
190
|
+
*
|
|
191
|
+
* Delivered and applied cursors are separate per-service `Ref` maps: concurrent
|
|
192
|
+
* subscriptions for one materialization share them, and each commit advances
|
|
193
|
+
* its matching map monotonically. A
|
|
194
|
+
* cursor advances in the same stream pull that admits each entry to the
|
|
195
|
+
* consumer, or — when the caller supplies `SubscribeOptions.apply` — only
|
|
196
|
+
* after that callback has applied the entry. Interrupted partial pages
|
|
197
|
+
* therefore retain the last admitted entry rather than incorrectly
|
|
198
|
+
* acknowledging the server's whole page.
|
|
199
|
+
*
|
|
200
|
+
* A frame or bootstrap page whose encoded entries exceed `maxFrameBytes` is
|
|
201
|
+
* refused with `frame_too_large`. Out-of-scope entries, repeated or reordered
|
|
202
|
+
* sequences, and missing response generations fail with `protocol_violation`
|
|
203
|
+
* before delivery. Each entry-bearing run in a Read page must have exactly one
|
|
204
|
+
* explicit generation cursor, and every entry must be above the requested
|
|
205
|
+
* cursor. Live frames instead drop already-covered entries and deliver only
|
|
206
|
+
* the suffix above the cursor. An incomplete bootstrap page with no entries
|
|
207
|
+
* or a completed subscription window with no frames fails with
|
|
208
|
+
* `protocol_violation` instead of reopening immediately.
|
|
209
|
+
*
|
|
210
|
+
* A live follow that loses its transport reconnects under
|
|
211
|
+
* {@link reconnectPolicy}, resuming from the acknowledged cursors; the failure
|
|
212
|
+
* cause is logged before the retry folds it. The follow spends a credit window
|
|
213
|
+
* of {@link defaultCredit} frames per subscription round and replenishes it by
|
|
214
|
+
* resubscribing from the cursors it has acknowledged. Already-covered entries
|
|
215
|
+
* are not delivered again; the round-trip cost scales with credit windows.
|
|
216
|
+
*
|
|
217
|
+
* A `compacted` refusal fails closed unless `SubscribeOptions.onResync`
|
|
218
|
+
* restores the missing prefix and returns its exact cursor. Snapshot fetching
|
|
219
|
+
* never applies state or advances cursors. Only a validated receipt for the requested run at or
|
|
220
|
+
* above its compaction floor advances the cursor before reading the suffix.
|
|
221
|
+
*
|
|
222
|
+
* A subscription's effective cursor per run is the LATER of the caller's and
|
|
223
|
+
* the matching progress map: a caller that restored its own progress is never
|
|
224
|
+
* regressed, and a caller asking for history this client already acknowledged
|
|
225
|
+
* is fast-forwarded. Rebuild a projection from an earlier position with a
|
|
226
|
+
* fresh client, whose acknowledged map is empty.
|
|
227
|
+
*
|
|
228
|
+
* This constructor carries the default policy, which is valid by
|
|
229
|
+
* construction and so cannot fail. {@link makeWith} takes an explicit
|
|
230
|
+
* {@link Options} and validates it.
|
|
231
|
+
*
|
|
232
|
+
* @category constructors
|
|
233
|
+
* @since 0.1.0
|
|
234
|
+
*/
|
|
235
|
+
export declare const make: ({ client }: {
|
|
236
|
+
readonly client: Client;
|
|
237
|
+
}) => Effect.Effect<Service>;
|
|
238
|
+
/**
|
|
239
|
+
* Constructs the sync client under an explicit policy.
|
|
240
|
+
*
|
|
241
|
+
* Every option is validated here, once, so a client either exists under a
|
|
242
|
+
* policy it can serve or never exists at all. Validating on each `subscribe`
|
|
243
|
+
* and each `snapshot` instead accepted a `maxFrameBytes` of `NaN` at
|
|
244
|
+
* construction and then refused every operation under it, which reported a
|
|
245
|
+
* composition's mistake as a run-time failure of the follow. This mirrors
|
|
246
|
+
* `SyncServer.makeLiveWith` and `BranchCommands.makeLiveWith`.
|
|
247
|
+
*
|
|
248
|
+
* `SubscribeOptions.credit` is a REQUEST count rather than a client policy, so
|
|
249
|
+
* it is still checked where the request is built.
|
|
250
|
+
*
|
|
251
|
+
* @category constructors
|
|
252
|
+
* @since 1.0.0-rc.0
|
|
253
|
+
*/
|
|
254
|
+
export declare const makeWith: ({ bootstrapLimit, client, maxFrameBytes }: Options & {
|
|
255
|
+
readonly client: Client;
|
|
256
|
+
}) => Effect.Effect<Service, SyncError>;
|
|
257
|
+
/**
|
|
258
|
+
* Provides the sync client from the active RPC protocol.
|
|
259
|
+
*
|
|
260
|
+
* @category layers
|
|
261
|
+
* @since 0.1.0
|
|
262
|
+
*/
|
|
263
|
+
export declare const layer: Layer.Layer<SyncClient, never, RpcClient.Protocol>;
|
|
264
|
+
/**
|
|
265
|
+
* Provides the sync client under an explicit policy. Fails with
|
|
266
|
+
* `invalid_request` when an option is not a positive safe integer, so a bad
|
|
267
|
+
* policy fails the composition rather than every operation under it.
|
|
268
|
+
*
|
|
269
|
+
* @category layers
|
|
270
|
+
* @since 1.0.0-rc.0
|
|
271
|
+
*/
|
|
272
|
+
export declare const layerWith: (options: Options) => Layer.Layer<SyncClient, SyncError, RpcClient.Protocol>;
|
|
273
|
+
export {};
|
|
274
|
+
//# sourceMappingURL=SyncClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncClient.d.ts","sourceRoot":"","sources":["../../src/SyncClient.ts"],"names":[],"mappings":"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"}
|