@superblocksteam/workspace-sync 0.0.0-stage → 2.0.178-next.1
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/LICENSE.txt +87 -0
- package/README.md +43 -2
- package/dist/atomic-write.d.ts +7 -0
- package/dist/atomic-write.d.ts.map +1 -0
- package/dist/atomic-write.js +33 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/branch-write-guard.d.ts +32 -0
- package/dist/branch-write-guard.d.ts.map +1 -0
- package/dist/branch-write-guard.js +38 -0
- package/dist/branch-write-guard.js.map +1 -0
- package/dist/control.d.ts +237 -0
- package/dist/control.d.ts.map +1 -0
- package/dist/control.js +242 -0
- package/dist/control.js.map +1 -0
- package/dist/dbfs-transport.d.ts +52 -0
- package/dist/dbfs-transport.d.ts.map +1 -0
- package/dist/dbfs-transport.js +21 -0
- package/dist/dbfs-transport.js.map +1 -0
- package/dist/download.d.ts +54 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +475 -0
- package/dist/download.js.map +1 -0
- package/dist/draft-helpers.d.ts +114 -0
- package/dist/draft-helpers.d.ts.map +1 -0
- package/dist/draft-helpers.js +760 -0
- package/dist/draft-helpers.js.map +1 -0
- package/dist/draft-journal-store.d.ts +30 -0
- package/dist/draft-journal-store.d.ts.map +1 -0
- package/dist/draft-journal-store.js +7 -0
- package/dist/draft-journal-store.js.map +1 -0
- package/dist/draft-state-report-metrics.d.ts +5 -0
- package/dist/draft-state-report-metrics.d.ts.map +1 -0
- package/dist/draft-state-report-metrics.js +36 -0
- package/dist/draft-state-report-metrics.js.map +1 -0
- package/dist/file-draft-journal-store.d.ts +26 -0
- package/dist/file-draft-journal-store.d.ts.map +1 -0
- package/dist/file-draft-journal-store.js +123 -0
- package/dist/file-draft-journal-store.js.map +1 -0
- package/dist/flush-scheduler.d.ts +24 -0
- package/dist/flush-scheduler.d.ts.map +1 -0
- package/dist/flush-scheduler.js +36 -0
- package/dist/flush-scheduler.js.map +1 -0
- package/dist/hash-cache.d.ts +16 -0
- package/dist/hash-cache.d.ts.map +1 -0
- package/dist/hash-cache.js +62 -0
- package/dist/hash-cache.js.map +1 -0
- package/dist/hash-dir-tree.d.ts +10 -0
- package/dist/hash-dir-tree.d.ts.map +1 -0
- package/dist/hash-dir-tree.js +53 -0
- package/dist/hash-dir-tree.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/list-dir.d.ts +31 -0
- package/dist/list-dir.d.ts.map +1 -0
- package/dist/list-dir.js +153 -0
- package/dist/list-dir.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +16 -0
- package/dist/logger.js.map +1 -0
- package/dist/native-draft-session.d.ts +28 -0
- package/dist/native-draft-session.d.ts.map +1 -0
- package/dist/native-draft-session.js +154 -0
- package/dist/native-draft-session.js.map +1 -0
- package/dist/rpc-client.d.ts +7 -0
- package/dist/rpc-client.d.ts.map +1 -0
- package/dist/rpc-client.js +2 -0
- package/dist/rpc-client.js.map +1 -0
- package/dist/safe-directory-reader.d.ts +28 -0
- package/dist/safe-directory-reader.d.ts.map +1 -0
- package/dist/safe-directory-reader.js +136 -0
- package/dist/safe-directory-reader.js.map +1 -0
- package/dist/server-tree.d.ts +56 -0
- package/dist/server-tree.d.ts.map +1 -0
- package/dist/server-tree.js +179 -0
- package/dist/server-tree.js.map +1 -0
- package/dist/session-draft-journal-store.d.ts +42 -0
- package/dist/session-draft-journal-store.d.ts.map +1 -0
- package/dist/session-draft-journal-store.js +177 -0
- package/dist/session-draft-journal-store.js.map +1 -0
- package/dist/snapshot/take-snapshot.d.ts +41 -0
- package/dist/snapshot/take-snapshot.d.ts.map +1 -0
- package/dist/snapshot/take-snapshot.js +244 -0
- package/dist/snapshot/take-snapshot.js.map +1 -0
- package/dist/snapshot/types.d.ts +25 -0
- package/dist/snapshot/types.d.ts.map +1 -0
- package/dist/snapshot/types.js +3 -0
- package/dist/snapshot/types.js.map +1 -0
- package/dist/snapshot/upload-snapshot.d.ts +33 -0
- package/dist/snapshot/upload-snapshot.d.ts.map +1 -0
- package/dist/snapshot/upload-snapshot.js +113 -0
- package/dist/snapshot/upload-snapshot.js.map +1 -0
- package/dist/socket-dbfs-transport.d.ts +32 -0
- package/dist/socket-dbfs-transport.d.ts.map +1 -0
- package/dist/socket-dbfs-transport.js +126 -0
- package/dist/socket-dbfs-transport.js.map +1 -0
- package/dist/sync-credential-provider.d.ts +27 -0
- package/dist/sync-credential-provider.d.ts.map +1 -0
- package/dist/sync-credential-provider.js +22 -0
- package/dist/sync-credential-provider.js.map +1 -0
- package/dist/sync-engine.d.ts +119 -0
- package/dist/sync-engine.d.ts.map +1 -0
- package/dist/sync-engine.js +358 -0
- package/dist/sync-engine.js.map +1 -0
- package/dist/sync-paths.d.ts +61 -0
- package/dist/sync-paths.d.ts.map +1 -0
- package/dist/sync-paths.js +107 -0
- package/dist/sync-paths.js.map +1 -0
- package/dist/sync-telemetry.d.ts +12 -0
- package/dist/sync-telemetry.d.ts.map +1 -0
- package/dist/sync-telemetry.js +33 -0
- package/dist/sync-telemetry.js.map +1 -0
- package/dist/testing.d.ts +66 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +180 -0
- package/dist/testing.js.map +1 -0
- package/dist/workspace-state-store.d.ts +52 -0
- package/dist/workspace-state-store.d.ts.map +1 -0
- package/dist/workspace-state-store.js +45 -0
- package/dist/workspace-state-store.js.map +1 -0
- package/package.json +59 -3
package/dist/control.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract between the three parties of an AgentCore workspace:
|
|
3
|
+
*
|
|
4
|
+
* - the AI service, which opens the VM and learns its session endpoint URL;
|
|
5
|
+
* - the workspace edge in the live-edit pod, the browser's front door, which
|
|
6
|
+
* signs and forwards traffic to that URL and owns lock, activity and the
|
|
7
|
+
* sync credential;
|
|
8
|
+
* - the workspace ingress in the VM, the one listener the session endpoint
|
|
9
|
+
* reaches.
|
|
10
|
+
*
|
|
11
|
+
* Constants, the shared bearer token and message schemas only: no IO, so any
|
|
12
|
+
* party can be replaced as long as it speaks this file. Import it as
|
|
13
|
+
* `@superblocksteam/workspace-sync/control`.
|
|
14
|
+
*
|
|
15
|
+
* The edge and the VM image ship separately, so the schemas strip unknown
|
|
16
|
+
* fields instead of rejecting them: either side may add a field first.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Ports, paths and headers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* The port the VM's ingress listens on. The builder runtime's `allowedPorts`
|
|
25
|
+
* must include it (AgentCore allows 8000-12000).
|
|
26
|
+
*/
|
|
27
|
+
export const WORKSPACE_INGRESS_PORT = 8090;
|
|
28
|
+
/**
|
|
29
|
+
* Every control path on either side lives under this prefix. The edge never
|
|
30
|
+
* forwards a browser request under it to the VM: forwarded requests carry the
|
|
31
|
+
* workspace token, so a forwarded control path would be authorized.
|
|
32
|
+
*/
|
|
33
|
+
export const WORKSPACE_CONTROL_PREFIX = "/_sb_workspace/";
|
|
34
|
+
/** VM ingress, edge → VM: replace the VM's copy of the edge's state. */
|
|
35
|
+
export const WORKSPACE_STATE_PATH = "/_sb_workspace/state";
|
|
36
|
+
/** VM ingress, edge → VM: commit the tree to DBFS now and report the result. */
|
|
37
|
+
export const WORKSPACE_COMMIT_PATH = "/_sb_workspace/commit";
|
|
38
|
+
/** Edge, AI service → edge: the VM's current session endpoint. */
|
|
39
|
+
export const WORKSPACE_ENDPOINT_PATH = "/_sb_workspace/endpoint";
|
|
40
|
+
/** Edge, VM → edge: read-only package registry proxy (prefix). */
|
|
41
|
+
export const WORKSPACE_REGISTRY_PATH = "/_sb_workspace/registry/";
|
|
42
|
+
/**
|
|
43
|
+
* Carries the workspace token on every request between the parties. Not
|
|
44
|
+
* `Authorization`: SigV4 owns that header on the edge → VM hop.
|
|
45
|
+
*/
|
|
46
|
+
export const WORKSPACE_TOKEN_HEADER = "x-sb-workspace-token";
|
|
47
|
+
/**
|
|
48
|
+
* The browser's Host. The edge → VM hop must send the session endpoint's own
|
|
49
|
+
* Host (it is signed), so the edge moves the original here and the VM ingress
|
|
50
|
+
* restores it before Vite sees the request.
|
|
51
|
+
*/
|
|
52
|
+
export const WORKSPACE_ORIGINAL_HOST_HEADER = "x-sb-original-host";
|
|
53
|
+
/**
|
|
54
|
+
* The browser's `Authorization`. SigV4 owns `Authorization` on the edge → VM
|
|
55
|
+
* hop, so the edge moves the browser's value here (dropping any inbound copy)
|
|
56
|
+
* and the VM ingress restores it before the workspace host checks it.
|
|
57
|
+
*/
|
|
58
|
+
export const WORKSPACE_ORIGINAL_AUTHORIZATION_HEADER = "x-sb-original-authorization";
|
|
59
|
+
/** True when `pathname` is a control path that must not be forwarded. */
|
|
60
|
+
export function isWorkspaceControlPath(pathname) {
|
|
61
|
+
return pathname.startsWith(WORKSPACE_CONTROL_PREFIX);
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Token
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* The bearer token for one workspace session: the hex HMAC-SHA256 of
|
|
68
|
+
* `sessionKey` keyed by `secret`. Every party that holds the secret derives
|
|
69
|
+
* the same token, and a token derived for one session opens no other.
|
|
70
|
+
*/
|
|
71
|
+
export function deriveWorkspaceToken(secret, sessionKey) {
|
|
72
|
+
if (!secret)
|
|
73
|
+
throw new Error("deriveWorkspaceToken requires a secret");
|
|
74
|
+
if (!sessionKey)
|
|
75
|
+
throw new Error("deriveWorkspaceToken requires a sessionKey");
|
|
76
|
+
return createHmac("sha256", secret).update(sessionKey).digest("hex");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The token that authorizes endpoint reports (`PUT`
|
|
80
|
+
* {@link WORKSPACE_ENDPOINT_PATH}) for one session. Only parties outside the
|
|
81
|
+
* VM derive it: the VM holds the {@link deriveWorkspaceToken workspace
|
|
82
|
+
* token}, and that token must not be able to retarget the edge.
|
|
83
|
+
*/
|
|
84
|
+
export function deriveWorkspaceEndpointToken(secret, sessionKey) {
|
|
85
|
+
if (!secret)
|
|
86
|
+
throw new Error("deriveWorkspaceEndpointToken requires a secret");
|
|
87
|
+
if (!sessionKey)
|
|
88
|
+
throw new Error("deriveWorkspaceEndpointToken requires a sessionKey");
|
|
89
|
+
return createHmac("sha256", secret)
|
|
90
|
+
.update(`workspace-endpoint\n${sessionKey}`)
|
|
91
|
+
.digest("hex");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* True when `url` is an AgentCore session endpoint in `region`: https on the
|
|
95
|
+
* default port, no credentials, and a host under
|
|
96
|
+
* `runtime.bedrock-agentcore.<region>.on.aws`.
|
|
97
|
+
*/
|
|
98
|
+
export function isAgentCoreSessionEndpoint(url, region) {
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = new URL(url);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return (/^[a-z]{2}(-[a-z]+)+-\d+$/.test(region) &&
|
|
107
|
+
parsed.protocol === "https:" &&
|
|
108
|
+
parsed.port === "" &&
|
|
109
|
+
parsed.username === "" &&
|
|
110
|
+
parsed.password === "" &&
|
|
111
|
+
parsed.hostname.endsWith(`.runtime.bedrock-agentcore.${region}.on.aws`));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Constant-time string comparison. Both sides are hashed first so inputs of
|
|
115
|
+
* different lengths compare in constant time too.
|
|
116
|
+
*/
|
|
117
|
+
export function tokensEqual(a, b) {
|
|
118
|
+
return timingSafeEqual(createHash("sha256").update(a).digest(), createHash("sha256").update(b).digest());
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether `headers` carry exactly `expected` in {@link WORKSPACE_TOKEN_HEADER}.
|
|
122
|
+
* A missing, repeated or empty header is rejected.
|
|
123
|
+
*/
|
|
124
|
+
export function hasWorkspaceToken(headers, expected) {
|
|
125
|
+
const value = headers[WORKSPACE_TOKEN_HEADER];
|
|
126
|
+
if (typeof value !== "string" || !value || !expected)
|
|
127
|
+
return false;
|
|
128
|
+
return tokensEqual(value, expected);
|
|
129
|
+
}
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Edge → VM: state snapshot
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
export const workspaceSyncCredentialSchema = z.object({
|
|
134
|
+
token: z.string().min(1).max(8192),
|
|
135
|
+
/** Epoch milliseconds after which the server rejects the token. */
|
|
136
|
+
expiresAt: z.number().int().positive(),
|
|
137
|
+
});
|
|
138
|
+
/**
|
|
139
|
+
* Everything the VM needs from the edge, sent whole every time
|
|
140
|
+
* (`PUT` {@link WORKSPACE_STATE_PATH}). The edge sends it on every change and
|
|
141
|
+
* on a timer, so a VM that restarted inside the same session is back in sync
|
|
142
|
+
* within one interval without the edge noticing the restart. The VM replaces
|
|
143
|
+
* its copy; it never merges.
|
|
144
|
+
*/
|
|
145
|
+
export const workspaceStateSchema = z.object({
|
|
146
|
+
/** The edge holds the branch lock; without it the VM must not commit. */
|
|
147
|
+
lockHeld: z.boolean(),
|
|
148
|
+
/** A user was recently active in the editor (feeds the VM's busy policy). */
|
|
149
|
+
userActive: z.boolean(),
|
|
150
|
+
/** The edge is shutting down; going quiet after this is not a failure. */
|
|
151
|
+
draining: z.boolean(),
|
|
152
|
+
/**
|
|
153
|
+
* Absent until the edge has minted one. A snapshot without one leaves the
|
|
154
|
+
* VM's last credential in place; the server enforces its expiry.
|
|
155
|
+
*/
|
|
156
|
+
credential: workspaceSyncCredentialSchema.optional(),
|
|
157
|
+
});
|
|
158
|
+
/**
|
|
159
|
+
* The VM's answer to a state snapshot. `bootId` is random per VM process, for
|
|
160
|
+
* logs and metrics; the edge does not branch on it.
|
|
161
|
+
*/
|
|
162
|
+
export const workspaceStatusSchema = z.object({
|
|
163
|
+
bootId: z.string().min(1),
|
|
164
|
+
devServer: z.enum(["booting", "ready", "failed"]),
|
|
165
|
+
/** Local changes not yet committed to DBFS. */
|
|
166
|
+
unsynced: z.boolean(),
|
|
167
|
+
dbfsHash: z.string().optional(),
|
|
168
|
+
});
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Edge → VM: commit
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
/**
|
|
173
|
+
* The response body of `POST` {@link WORKSPACE_COMMIT_PATH}, always with
|
|
174
|
+
* status 200 so the edge reads one shape. `not_ready` means the workspace is
|
|
175
|
+
* not open or has no state snapshot yet; the edge reports it like an error.
|
|
176
|
+
*/
|
|
177
|
+
export const workspaceCommitResultSchema = z.discriminatedUnion("outcome", [
|
|
178
|
+
z.object({
|
|
179
|
+
outcome: z.literal("committed"),
|
|
180
|
+
dbfsHash: z.string().optional(),
|
|
181
|
+
}),
|
|
182
|
+
z.object({
|
|
183
|
+
outcome: z.literal("not_ready"),
|
|
184
|
+
reason: z.string().max(512),
|
|
185
|
+
}),
|
|
186
|
+
z.object({
|
|
187
|
+
outcome: z.literal("error"),
|
|
188
|
+
error: z.string().max(512),
|
|
189
|
+
}),
|
|
190
|
+
]);
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// AI service → edge: session endpoint
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
const endpointUrl = z
|
|
195
|
+
.string()
|
|
196
|
+
.url()
|
|
197
|
+
.refine((value) => /^https?:\/\//.test(value), "must be http(s)");
|
|
198
|
+
/**
|
|
199
|
+
* Where the VM's ingress is reachable and how the edge authenticates to it
|
|
200
|
+
* (`PUT` {@link WORKSPACE_ENDPOINT_PATH}). Each `workspace.open` that returns
|
|
201
|
+
* a new URL sends a new one; the edge then pushes its state there.
|
|
202
|
+
*
|
|
203
|
+
* - `sigv4`: an AgentCore session endpoint. The edge signs each request and
|
|
204
|
+
* WebSocket handshake for `bedrock-agentcore` in `region`.
|
|
205
|
+
* - `none`: a local builder runtime reached directly, for development.
|
|
206
|
+
*/
|
|
207
|
+
export const workspaceEndpointSchema = z.discriminatedUnion("auth", [
|
|
208
|
+
z.object({
|
|
209
|
+
auth: z.literal("sigv4"),
|
|
210
|
+
url: endpointUrl,
|
|
211
|
+
region: z.string().min(1).max(64),
|
|
212
|
+
sessionKey: z.string().min(1).max(1024),
|
|
213
|
+
}),
|
|
214
|
+
z.object({
|
|
215
|
+
auth: z.literal("none"),
|
|
216
|
+
url: endpointUrl,
|
|
217
|
+
sessionKey: z.string().min(1).max(1024),
|
|
218
|
+
}),
|
|
219
|
+
]);
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Edge health, read by the AI service
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
/**
|
|
224
|
+
* The edge's view of the VM, reported in its health response as
|
|
225
|
+
* `workspace.ingress`. The AI service reopens the workspace when it stays
|
|
226
|
+
* `unreachable`.
|
|
227
|
+
*
|
|
228
|
+
* - `waiting`: no endpoint yet.
|
|
229
|
+
* - `reachable`: the last state push succeeded and the VM reports a booting
|
|
230
|
+
* or ready workspace host.
|
|
231
|
+
* - `unreachable`: pushes to the current endpoint are failing, or the VM
|
|
232
|
+
* answers them but reports its workspace host `failed` (it cannot serve
|
|
233
|
+
* until the workspace is reopened).
|
|
234
|
+
* - `closed`: the edge is shutting down.
|
|
235
|
+
*/
|
|
236
|
+
export const workspaceIngressStateSchema = z.enum([
|
|
237
|
+
"waiting",
|
|
238
|
+
"reachable",
|
|
239
|
+
"unreachable",
|
|
240
|
+
"closed",
|
|
241
|
+
]);
|
|
242
|
+
//# sourceMappingURL=control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control.js","sourceRoot":"","sources":["../src/control.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ApplicationHashChangeSource, DirectoryEntry } from "@superblocksteam/shared";
|
|
2
|
+
import type { DraftJournalBlobStore } from "./session-draft-journal-store.js";
|
|
3
|
+
import type { PutManyItems } from "./snapshot/upload-snapshot.js";
|
|
4
|
+
export interface SetHashRequest {
|
|
5
|
+
hash: string;
|
|
6
|
+
/**
|
|
7
|
+
* The hash this commit was made on top of: the last hash this workspace
|
|
8
|
+
* committed or loaded. Undefined when the workspace has no stored hash.
|
|
9
|
+
*/
|
|
10
|
+
baseHash: string | undefined;
|
|
11
|
+
source: ApplicationHashChangeSource;
|
|
12
|
+
}
|
|
13
|
+
export interface SetHashResult {
|
|
14
|
+
hash: string;
|
|
15
|
+
commitId?: string;
|
|
16
|
+
}
|
|
17
|
+
/** A file to read out of a stored tree. */
|
|
18
|
+
export interface DbfsFileRef {
|
|
19
|
+
/** Slash-separated path from the tree root. */
|
|
20
|
+
path: string;
|
|
21
|
+
/** Content hash, checked against what comes back. */
|
|
22
|
+
hash: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Everything the sync engine needs from DBFS and the application's hash
|
|
26
|
+
* pointer, for one application branch. The transport is the only part of the
|
|
27
|
+
* engine that talks to the server, so tests swap it for `FakeDbfs`.
|
|
28
|
+
*/
|
|
29
|
+
export interface DbfsTransport {
|
|
30
|
+
/** Rejects when the server would refuse any of these directory listings. */
|
|
31
|
+
validateMany(items: PutManyItems): Promise<void>;
|
|
32
|
+
/** Stores blobs and listings; returns their hashes in request order. */
|
|
33
|
+
putMany(items: PutManyItems): Promise<string[]>;
|
|
34
|
+
/** One directory listing by hash. */
|
|
35
|
+
getDirectory(hash: string): Promise<DirectoryEntry[]>;
|
|
36
|
+
/** File contents by path inside the tree `rootHash`, in request order. */
|
|
37
|
+
getContents(rootHash: string, files: DbfsFileRef[]): Promise<Buffer[]>;
|
|
38
|
+
/** One blob by content hash. */
|
|
39
|
+
getBlob(hash: string): Promise<Buffer>;
|
|
40
|
+
/** The branch's current working-copy hash. */
|
|
41
|
+
getServerHash(): Promise<string>;
|
|
42
|
+
/** Moves the branch's working-copy hash. */
|
|
43
|
+
setHash(request: SetHashRequest): Promise<SetHashResult>;
|
|
44
|
+
/** Reports whether the workspace has unaccepted drafts. */
|
|
45
|
+
setDraftState(hasDraft: boolean): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Stores native draft journal contents as DBFS blobs through a transport, for
|
|
49
|
+
* `SessionDraftJournalStore`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function journalBlobStoreFromTransport(transport: Pick<DbfsTransport, "putMany" | "getBlob">): DraftJournalBlobStore;
|
|
52
|
+
//# sourceMappingURL=dbfs-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dbfs-transport.d.ts","sourceRoot":"","sources":["../src/dbfs-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,cAAc,EACf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,2BAA2B,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,wEAAwE;IACxE,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAChD,qCAAqC;IACrC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACtD,0EAA0E;IAC1E,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvE,gCAAgC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,8CAA8C;IAC9C,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,4CAA4C;IAC5C,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACzD,2DAA2D;IAC3D,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAID;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,SAAS,CAAC,GACpD,qBAAqB,CAkBvB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const BLOB_BATCH = 64;
|
|
2
|
+
/**
|
|
3
|
+
* Stores native draft journal contents as DBFS blobs through a transport, for
|
|
4
|
+
* `SessionDraftJournalStore`.
|
|
5
|
+
*/
|
|
6
|
+
export function journalBlobStoreFromTransport(transport) {
|
|
7
|
+
return {
|
|
8
|
+
async put(contents) {
|
|
9
|
+
const hashes = [];
|
|
10
|
+
for (let idx = 0; idx < contents.length; idx += BLOB_BATCH) {
|
|
11
|
+
hashes.push(...(await transport.putMany(contents.slice(idx, idx + BLOB_BATCH).map((bytes) => ({
|
|
12
|
+
type: "fileContents",
|
|
13
|
+
contents: bytes.toString("base64"),
|
|
14
|
+
})))));
|
|
15
|
+
}
|
|
16
|
+
return hashes;
|
|
17
|
+
},
|
|
18
|
+
get: (hash) => transport.getBlob(hash),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=dbfs-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dbfs-transport.js","sourceRoot":"","sources":["../src/dbfs-transport.ts"],"names":[],"mappings":"AAuDA,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAC3C,SAAqD;IAErD,OAAO;QACL,KAAK,CAAC,GAAG,CAAC,QAAQ;YAChB,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,IAAI,UAAU,EAAE,CAAC;gBAC3D,MAAM,CAAC,IAAI,CACT,GAAG,CAAC,MAAM,SAAS,CAAC,OAAO,CACzB,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;oBACpD,IAAI,EAAE,cAAuB;oBAC7B,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;iBACnC,CAAC,CAAC,CACJ,CAAC,CACH,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;KACvC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { DraftRestoreOutcome, DraftRestorePhase } from "./draft-state-report-metrics.js";
|
|
2
|
+
import type { HashCache } from "./hash-cache.js";
|
|
3
|
+
import type { StdISocketRPCClient } from "./rpc-client.js";
|
|
4
|
+
import type { DirectoryState } from "./snapshot/types.js";
|
|
5
|
+
type DirectoryHashResult = {
|
|
6
|
+
hash: string;
|
|
7
|
+
latestNonDraftCommitId?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function downloadApplicationDirectory({ rpcClient, applicationId, branch, localDirectoryPath, hashCache, }: {
|
|
10
|
+
rpcClient: StdISocketRPCClient;
|
|
11
|
+
applicationId: string;
|
|
12
|
+
branch: string | undefined;
|
|
13
|
+
localDirectoryPath: string;
|
|
14
|
+
hashCache: HashCache;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
directoryHash: string;
|
|
17
|
+
latestNonDraftCommitId?: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Incrementally download the directory contents, skipping any files that are already
|
|
21
|
+
* present locally, based on the old local directory state.
|
|
22
|
+
* Returns the new local directory state.
|
|
23
|
+
*
|
|
24
|
+
* @param rpcClient - The RPC client to use to get the directory contents from the server.
|
|
25
|
+
* @param directoryHash - The hash of the directory to download.
|
|
26
|
+
* @param oldLocalDirState - The old local directory state.
|
|
27
|
+
* @param localDirectoryPath - The path to the local directory.
|
|
28
|
+
* @returns The new local directory state.
|
|
29
|
+
*/
|
|
30
|
+
export declare function doDownloadApplicationDirectory(rpcClient: StdISocketRPCClient, directoryHash: string, oldLocalDirState: DirectoryState | undefined, localDirectoryPath: string): Promise<DirectoryState>;
|
|
31
|
+
export type DraftRestoreResult = {
|
|
32
|
+
outcome: Exclude<DraftRestoreOutcome, "error">;
|
|
33
|
+
edits: number;
|
|
34
|
+
additions: number;
|
|
35
|
+
deletions: number;
|
|
36
|
+
};
|
|
37
|
+
export declare class DraftRestoreError extends Error {
|
|
38
|
+
readonly phase: DraftRestorePhase;
|
|
39
|
+
constructor(phase: DraftRestorePhase, cause: unknown);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Rebuilds the `.sbswap` markers that a replaced pod lost: every file that
|
|
43
|
+
* differs from the last checkpoint becomes a draft, so Accept and Reject work.
|
|
44
|
+
* The markers do not change the upload hash, because snapshots skip them.
|
|
45
|
+
*/
|
|
46
|
+
export declare function restoreDraftMarkersFromCheckpoint({ rpcClient, applicationId, branch, appRootDirPath, }: {
|
|
47
|
+
rpcClient: StdISocketRPCClient;
|
|
48
|
+
applicationId: string;
|
|
49
|
+
branch: string | undefined;
|
|
50
|
+
appRootDirPath: string;
|
|
51
|
+
}): Promise<DraftRestoreResult>;
|
|
52
|
+
export declare function getDraftOrLiveDirectoryHash(rpcClient: StdISocketRPCClient, applicationId: string, branch: string | undefined): Promise<DirectoryHashResult>;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,qBAAqB,CAAC;AAK7B,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IAGb,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AA0BF,wBAAsB,4BAA4B,CAAC,EACjD,SAAS,EACT,aAAa,EACb,MAAM,EACN,kBAAkB,EAClB,SAAS,GACV,EAAE;IACD,SAAS,EAAE,mBAAmB,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,SAAS,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAqBtE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,mBAAmB,EAC9B,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,cAAc,GAAG,SAAS,EAC5C,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,cAAc,CAAC,CAoMzB;AAQD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,OAAO,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,KAAK,EAAE,iBAAiB;IADnC,YACW,KAAK,EAAE,iBAAiB,EACjC,KAAK,EAAE,OAAO,EAGf;CACF;AAaD;;;;GAIG;AACH,wBAAsB,iCAAiC,CAAC,EACtD,SAAS,EACT,aAAa,EACb,MAAM,EACN,cAAc,GACf,EAAE;IACD,SAAS,EAAE,mBAAmB,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoG9B;AAgND,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,mBAAmB,EAC9B,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAqC9B"}
|