@cloudflare/workspace 0.0.0-alpha.0

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/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # `@cloudflare/workspace`
2
+
3
+ > [!IMPORTANT]
4
+ > **PREVIEW ONLY** This package is provided as a preview for feedback only.
5
+ > APIs are unstable and the design is subject to change.
6
+ >
7
+ > Suitable for experiments, exploration and prototypes. It is NOT suitable
8
+ > for production use at this time.
9
+ >
10
+ > The specification under [`docs/`](docs/) is forward-looking — read it for
11
+ > intent, not as description of the code today.
12
+
13
+ Durable Object-side facade for a Cloudflare Workspace. Pairs a local
14
+ SQLite-backed VFS (via `@cloudflare/dofs`) with a sync connection to
15
+ a `wsd` instance through a pluggable backend.
16
+
17
+ The public surface lives on three classes:
18
+
19
+ - `Workspace` — the host-side facade. Owns the local store, the
20
+ backend handle, and the push/pull bracket.
21
+ - `WorkspaceStub` — what `workspace.stub()` returns, designed to
22
+ cross the Workers-RPC boundary into another Worker or DO.
23
+ - `WorkspaceShell` / `ExecHandle` — the command-execution half of
24
+ the API.
25
+
26
+ Typical DO-side usage:
27
+
28
+ ```ts
29
+ import { Workspace, CloudflareContainerBackend } from "@cloudflare/workspace";
30
+ import { DurableObject } from "cloudflare:workers";
31
+
32
+ export class WsdContainer extends DurableObject<Env> {
33
+ #workspace = new Workspace({
34
+ storage: this.ctx.storage,
35
+ backends: [
36
+ new CloudflareContainerBackend({
37
+ container: () => this.ctx.container!,
38
+ egress: this.ctx.exports.WsdEgress({ props: { id: this.ctx.id.toString() } }),
39
+ }),
40
+ ],
41
+ });
42
+
43
+ async getWorkspace(): Promise<WorkspaceStub> {
44
+ await this.#workspace.ready();
45
+ return this.#workspace.stub();
46
+ }
47
+ }
48
+ ```
49
+
50
+ Worker-side consumption:
51
+
52
+ ```ts
53
+ export default {
54
+ async fetch(request: Request, env: Env): Promise<Response> {
55
+ const id = env.WSD.idFromName("user-123");
56
+ using ws = await env.WSD.get(id).getWorkspace();
57
+
58
+ await ws.fs.writeFile("/notes.md", "hello");
59
+ using handle = await ws.shell.exec("ls /workspace");
60
+ const { exitCode, stdout } = await handle.result();
61
+
62
+ return new Response(stdout, { status: exitCode === 0 ? 200 : 500 });
63
+ },
64
+ } satisfies ExportedHandler<Env>;
65
+ ```
66
+
67
+ ## Stub disposal
68
+
69
+ capnweb does not garbage-collect remote stubs. On the long-lived
70
+ sessions this package depends on (Worker ↔ DO over Workers RPC,
71
+ DO ↔ wsd over capnweb), undisposed stubs accumulate on the peer
72
+ side until the session ends.
73
+
74
+ The minimum a caller needs to know:
75
+
76
+ - `using` the value returned from `env.WSD.get(id).getWorkspace()`.
77
+ - `using` the handle returned from `ws.shell.exec(...)`.
78
+ - Don't worry about `ws.fs` / `ws.shell` — those are property
79
+ accessors that ride with the parent.
80
+ - Pure-value returns (`readFile` as a string, `stat`, `readdir`,
81
+ etc.) carry no stubs; nothing to dispose.
82
+
83
+ Short-lived single-shot Workers (one `getWorkspace()`, a few calls,
84
+ return a response) tear the session down with the request, so the
85
+ discipline matters most on long-lived isolates that keep grabbing
86
+ fresh `WorkspaceStub`s or on busy `exec` workloads inside a single
87
+ request.
88
+
89
+ The full contract — including the boundary between the driver code
90
+ and direct streaming callers, and how it interacts with hibernation
91
+ and reconnect — is in [`docs/11_lifecycle.md`](../../docs/11_lifecycle.md#stub-disposal-contract).
92
+
93
+ Leak discovery: set `CAPNWEB_TRACK_STUBS=1` and read the snapshot
94
+ via `stubSnapshot()` from `@cloudflare/workspace-rpc/debug`, or
95
+ hit `GET /__wsd/stubs` on a wsd instance. The soak scripts at
96
+ [`script/wsd-stub-soak.mjs`](../../script/wsd-stub-soak.mjs) and
97
+ [`tests/stub-soak.test.ts`](./tests/stub-soak.test.ts) exercise both
98
+ boundaries.
Binary file
package/dist/git.d.ts ADDED
@@ -0,0 +1,119 @@
1
+ import { i as SQLiteWorkspaceProvider } from "./shared-RIdME5uo.js";
2
+
3
+ //#region src/git/adapter.d.ts
4
+ /**
5
+ * The shape isomorphic-git wants as its `fs` argument: an object
6
+ * whose `.promises` is an own, enumerable property and implements
7
+ * the `node:fs/promises` subset listed in `PromiseFsClient`.
8
+ *
9
+ * `promises` is typed as `object` rather than enumerating every
10
+ * method @platformatic/vfs forwards. The full surface is wider
11
+ * than this package consumes and pinning it here would either
12
+ * leak @platformatic/vfs into our public types or force tedious
13
+ * structural shadowing. Callers that want a typed file API can
14
+ * cast `client.promises` to `import('@platformatic/vfs').VirtualFileSystem['promises']`.
15
+ */
16
+ interface IsomorphicGitFSClient {
17
+ promises: object;
18
+ }
19
+ //#endregion
20
+ //#region src/git/clone.d.ts
21
+ type ProgressCallback = (e: {
22
+ phase: string;
23
+ loaded: number;
24
+ total?: number;
25
+ }) => void;
26
+ type MessageCallback = (msg: string) => void;
27
+ interface GitCloneOptions {
28
+ /** Repository URL. HTTPS only — isomorphic-git has no SSH transport. */
29
+ url: string;
30
+ /** Working-tree directory inside the VFS. Defaults to `/`. */
31
+ dir?: string;
32
+ /** Branch, tag, or commit. Defaults to the remote's default branch. */
33
+ ref?: string;
34
+ /**
35
+ * If set, only these paths are written into the working tree.
36
+ * `.git/` is fully populated regardless (subject to `depth`).
37
+ * Globs are not supported; pass directory or file paths.
38
+ */
39
+ paths?: string[];
40
+ /**
41
+ * Shallow-clone depth. Defaults to 1. Pass `0` or `Infinity` for
42
+ * full history. Even with depth=1, every blob reachable from the
43
+ * tip tree is fetched — see the package README for why.
44
+ */
45
+ depth?: number;
46
+ /** Fetch only the requested ref's branch. Default: true. */
47
+ singleBranch?: boolean;
48
+ /** Skip tags. Default: true. */
49
+ noTags?: boolean;
50
+ /** Extra HTTP headers, e.g. `Authorization` for a private repo. */
51
+ headers?: Record<string, string>;
52
+ /** CORS proxy URL. Usually unneeded inside a Worker. */
53
+ corsProxy?: string;
54
+ /** Progress callback. Forwarded to isomorphic-git. */
55
+ onProgress?: ProgressCallback;
56
+ /** Sideband message callback. Forwarded to isomorphic-git. */
57
+ onMessage?: MessageCallback;
58
+ }
59
+ //#endregion
60
+ //#region src/git/diff.d.ts
61
+ /**
62
+ * Status-matrix row as emitted by isomorphic-git's `statusMatrix`:
63
+ * `[filepath, headStatus, workdirStatus, stageStatus]`.
64
+ * - 0 = absent
65
+ * - 1 = same as HEAD
66
+ * - 2 = differs from HEAD
67
+ * - 3 = differs from HEAD and stage (rarely meaningful here)
68
+ */
69
+ type StatusRow = [string, number, number, number];
70
+ interface GitDiffOptions {
71
+ /** Working-tree directory inside the VFS. Defaults to `/`. */
72
+ dir?: string;
73
+ /** Ref to diff against. Defaults to `HEAD`. */
74
+ ref?: string;
75
+ }
76
+ //#endregion
77
+ //#region src/git/index.d.ts
78
+ /** Duck-typed workspace handle. Only `.provider()` is required. */
79
+ interface WorkspaceLike {
80
+ provider(): SQLiteWorkspaceProvider;
81
+ }
82
+ /** Methods returned by `createGitClient`. */
83
+ interface GitClient {
84
+ /** Shallow-clone a remote into the bound workspace. */
85
+ clone(options: GitCloneOptions): Promise<void>;
86
+ /** Unified diff between a ref (default HEAD) and the working tree. */
87
+ diff(options?: GitDiffOptions): Promise<string>;
88
+ }
89
+ interface CreateGitClientOptions {
90
+ /** Workspace whose provider backs the git operations. */
91
+ ws: WorkspaceLike;
92
+ /**
93
+ * Test seam for substituting the @platformatic/vfs adapter.
94
+ * Production callers do not pass this.
95
+ */
96
+ adapter?: (provider: SQLiteWorkspaceProvider) => Promise<IsomorphicGitFSClient>;
97
+ }
98
+ /**
99
+ * Build a git client bound to a workspace.
100
+ *
101
+ * The FsClient is constructed lazily on first use and reused
102
+ * across subsequent calls — `@platformatic/vfs.create()` is cheap
103
+ * but not free, and the workspace provider is stable for the
104
+ * lifetime of the client.
105
+ *
106
+ * An isomorphic-git pack/index cache is also created per client
107
+ * and threaded into every isogit call. Without this, each
108
+ * `readBlob` / `statusMatrix` / `walk` re-parses the packfile
109
+ * from the SQLite-backed VFS — fine for tiny repos, catastrophic
110
+ * for anything with a real history (the difference between a
111
+ * sub-second `diff` and one that hangs for minutes).
112
+ */
113
+ declare function createGitClient({
114
+ ws,
115
+ adapter
116
+ }: CreateGitClientOptions): GitClient;
117
+ //#endregion
118
+ export { CreateGitClientOptions, GitClient, type GitCloneOptions, type GitDiffOptions, type MessageCallback, type ProgressCallback, type StatusRow, WorkspaceLike, createGitClient };
119
+ //# sourceMappingURL=git.d.ts.map
package/dist/git.js ADDED
@@ -0,0 +1,271 @@
1
+ //#region src/git/adapter.ts
2
+ /**
3
+ * Methods on `SQLiteWorkspaceProvider` forwarded through the
4
+ * wrapping `VirtualProvider`. Listed explicitly (rather than walked
5
+ * off the prototype) so the surface is stable across dofs releases:
6
+ * a new method on the provider does not silently widen the wrapper.
7
+ */
8
+ const FORWARDED_METHODS = [
9
+ "open",
10
+ "openSync",
11
+ "stat",
12
+ "statSync",
13
+ "lstat",
14
+ "lstatSync",
15
+ "readdir",
16
+ "readdirSync",
17
+ "mkdir",
18
+ "mkdirSync",
19
+ "rmdir",
20
+ "rmdirSync",
21
+ "unlink",
22
+ "unlinkSync",
23
+ "rename",
24
+ "renameSync",
25
+ "readFile",
26
+ "readFileSync",
27
+ "writeFile",
28
+ "writeFileSync",
29
+ "appendFile",
30
+ "appendFileSync",
31
+ "exists",
32
+ "existsSync",
33
+ "copyFile",
34
+ "copyFileSync",
35
+ "internalModuleStat",
36
+ "realpath",
37
+ "realpathSync",
38
+ "access",
39
+ "accessSync",
40
+ "readlink",
41
+ "readlinkSync",
42
+ "symlink",
43
+ "symlinkSync",
44
+ "watch",
45
+ "watchAsync",
46
+ "watchFile",
47
+ "unwatchFile",
48
+ "closeSync",
49
+ "readSync",
50
+ "writeSync",
51
+ "fstatSync",
52
+ "truncateSync",
53
+ "ftruncateSync"
54
+ ];
55
+ /**
56
+ * Build an isomorphic-git-compatible FsClient from a
57
+ * SQLiteWorkspaceProvider.
58
+ *
59
+ * Lazily imports `@platformatic/vfs`. If the peer dep is missing,
60
+ * the thrown error names the package so the failure mode stays
61
+ * one stack frame away from obvious.
62
+ */
63
+ async function workspaceIsomorphicGitClient(provider) {
64
+ let create;
65
+ let VirtualProvider;
66
+ try {
67
+ ({create, VirtualProvider} = await import("@platformatic/vfs"));
68
+ } catch (cause) {
69
+ throw new Error("@cloudflare/workspace/git requires @platformatic/vfs as an optional peer dependency. Install it, or pass `fs` to clone() explicitly.", { cause });
70
+ }
71
+ class SQLiteVirtualProvider extends VirtualProvider {
72
+ #inner;
73
+ constructor(inner) {
74
+ super();
75
+ this.#inner = inner;
76
+ }
77
+ get readonly() {
78
+ return this.#inner.readonly;
79
+ }
80
+ get supportsSymlinks() {
81
+ return this.#inner.supportsSymlinks;
82
+ }
83
+ get supportsWatch() {
84
+ return this.#inner.supportsWatch;
85
+ }
86
+ /** Used by the forwarding loop below. */
87
+ get inner() {
88
+ return this.#inner;
89
+ }
90
+ }
91
+ for (const name of FORWARDED_METHODS) Object.defineProperty(SQLiteVirtualProvider.prototype, name, {
92
+ value(...args) {
93
+ const inner = this.inner;
94
+ const fn = inner[name];
95
+ if (typeof fn !== "function") throw new Error(`SQLiteWorkspaceProvider.${String(name)} is not a function`);
96
+ return fn.apply(inner, args);
97
+ },
98
+ writable: true,
99
+ configurable: true
100
+ });
101
+ return { promises: create(new SQLiteVirtualProvider(provider), { moduleHooks: false }).promises };
102
+ }
103
+ //#endregion
104
+ //#region src/git/clone.ts
105
+ async function cloneWith(opts) {
106
+ const dir = opts.dir ?? "/";
107
+ const ref = opts.ref;
108
+ const depthRaw = opts.depth ?? 1;
109
+ const depth = depthRaw > 0 && Number.isFinite(depthRaw) ? depthRaw : void 0;
110
+ await opts.git.clone({
111
+ fs: opts.fs,
112
+ http: opts.http,
113
+ dir,
114
+ url: opts.url,
115
+ ref,
116
+ corsProxy: opts.corsProxy,
117
+ headers: opts.headers,
118
+ depth,
119
+ singleBranch: opts.singleBranch ?? true,
120
+ noTags: opts.noTags ?? true,
121
+ noCheckout: true,
122
+ cache: opts.cache,
123
+ onProgress: opts.onProgress,
124
+ onMessage: opts.onMessage
125
+ });
126
+ await opts.git.checkout({
127
+ fs: opts.fs,
128
+ dir,
129
+ ref: ref ?? "HEAD",
130
+ filepaths: opts.paths,
131
+ force: true,
132
+ cache: opts.cache
133
+ });
134
+ }
135
+ //#endregion
136
+ //#region src/git/diff.ts
137
+ async function diffWith(opts) {
138
+ const dir = opts.dir ?? "/";
139
+ const ref = opts.ref ?? "HEAD";
140
+ let head;
141
+ try {
142
+ head = await opts.git.resolveRef({
143
+ fs: opts.fs,
144
+ dir,
145
+ ref
146
+ });
147
+ } catch {
148
+ return "";
149
+ }
150
+ const status = await opts.git.statusMatrix({
151
+ fs: opts.fs,
152
+ dir,
153
+ ref,
154
+ cache: opts.cache
155
+ });
156
+ const chunks = [];
157
+ for (const [filepath, headStatus, workdirStatus] of status) {
158
+ if (workdirStatus === 1) continue;
159
+ const headText = headStatus === 1 ? await readBlobAsText(opts.git, opts.fs, dir, head, filepath, opts.cache) : "";
160
+ const workdirText = workdirStatus === 2 ? await readWorkdirAsText(opts.readFile, dir, filepath) : "";
161
+ const patch = opts.createPatch(filepath, headText, workdirText, "", "");
162
+ if (patch.trim().length > 0) chunks.push(patch);
163
+ }
164
+ return chunks.join("\n");
165
+ }
166
+ async function readBlobAsText(git, fs, dir, oid, filepath, cache) {
167
+ try {
168
+ const { blob } = await git.readBlob({
169
+ fs,
170
+ dir,
171
+ oid,
172
+ filepath,
173
+ cache
174
+ });
175
+ return new TextDecoder("utf-8", {
176
+ fatal: false,
177
+ ignoreBOM: false
178
+ }).decode(blob);
179
+ } catch {
180
+ return "";
181
+ }
182
+ }
183
+ async function readWorkdirAsText(readFile, dir, filepath) {
184
+ try {
185
+ const data = await readFile(`${dir}/${filepath}`);
186
+ if (typeof data === "string") return data;
187
+ return new TextDecoder("utf-8", {
188
+ fatal: false,
189
+ ignoreBOM: false
190
+ }).decode(data);
191
+ } catch {
192
+ return "";
193
+ }
194
+ }
195
+ //#endregion
196
+ //#region src/git/index.ts
197
+ /**
198
+ * Build a git client bound to a workspace.
199
+ *
200
+ * The FsClient is constructed lazily on first use and reused
201
+ * across subsequent calls — `@platformatic/vfs.create()` is cheap
202
+ * but not free, and the workspace provider is stable for the
203
+ * lifetime of the client.
204
+ *
205
+ * An isomorphic-git pack/index cache is also created per client
206
+ * and threaded into every isogit call. Without this, each
207
+ * `readBlob` / `statusMatrix` / `walk` re-parses the packfile
208
+ * from the SQLite-backed VFS — fine for tiny repos, catastrophic
209
+ * for anything with a real history (the difference between a
210
+ * sub-second `diff` and one that hangs for minutes).
211
+ */
212
+ function createGitClient({ ws, adapter = workspaceIsomorphicGitClient }) {
213
+ let fsPromise;
214
+ const fs = () => {
215
+ if (!fsPromise) fsPromise = adapter(ws.provider());
216
+ return fsPromise;
217
+ };
218
+ const cache = {};
219
+ return {
220
+ async clone(options) {
221
+ await cloneWith({
222
+ ...options,
223
+ fs: await fs(),
224
+ git: await loadIsomorphicGit(),
225
+ http: await loadDefaultHTTP(),
226
+ cache
227
+ });
228
+ },
229
+ async diff(options = {}) {
230
+ const f = await fs();
231
+ return diffWith({
232
+ ...options,
233
+ fs: f,
234
+ git: await loadIsomorphicGit(),
235
+ createPatch: await loadCreatePatch(),
236
+ readFile: readFileFrom(f),
237
+ cache
238
+ });
239
+ }
240
+ };
241
+ }
242
+ async function loadIsomorphicGit() {
243
+ try {
244
+ const mod = await import("isomorphic-git");
245
+ return mod.default ?? mod;
246
+ } catch (cause) {
247
+ throw new Error("@cloudflare/workspace/git requires isomorphic-git as an optional peer dependency. Install isomorphic-git.", { cause });
248
+ }
249
+ }
250
+ async function loadDefaultHTTP() {
251
+ try {
252
+ return (await import("isomorphic-git/http/web")).default;
253
+ } catch (cause) {
254
+ throw new Error("Failed to load isomorphic-git/http/web. Install isomorphic-git.", { cause });
255
+ }
256
+ }
257
+ async function loadCreatePatch() {
258
+ try {
259
+ return (await import("./shared-Dj4r_9xb.js")).createPatch;
260
+ } catch (cause) {
261
+ throw new Error("@cloudflare/workspace/git requires `diff` as an optional peer dependency. Install `diff`.", { cause });
262
+ }
263
+ }
264
+ function readFileFrom(fs) {
265
+ const promises = fs.promises;
266
+ return (path) => promises.readFile(path);
267
+ }
268
+ //#endregion
269
+ export { createGitClient };
270
+
271
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","names":["#inner"],"sources":["../src/git/adapter.ts","../src/git/clone.ts","../src/git/diff.ts","../src/git/index.ts"],"sourcesContent":["// Bridge from `@cloudflare/dofs`'s SQLiteWorkspaceProvider to an\n// FsClient that isomorphic-git can consume directly.\n//\n// Mirrors the recipe in `examples/think/src/tools/git/vfs.ts`\n// (which mirrors the trick wsd uses for its FUSE mount):\n//\n// 1. SQLiteWorkspaceProvider already implements the full node:fs\n// surface @platformatic/vfs's VirtualFileSystem needs,\n// including symlink / readlink.\n// 2. It does not extend @platformatic/vfs.VirtualProvider, and\n// `create()` checks `provider instanceof VirtualProvider` —\n// failing silently into an in-memory store on a miss. The\n// dofs provider therefore has to be wrapped in a\n// VirtualProvider subclass with an explicit forwarding list.\n// 3. `vfs.promises` is declared as a class getter. isomorphic-git\n// probes `Object.getOwnPropertyDescriptor(fs, 'promises')`,\n// does not see an enumerable own property, and falls through\n// to its callback-style branch — which then crashes.\n// Re-exposing `promises` as an own property on a plain object\n// fixes the detection.\n//\n// `@platformatic/vfs` is imported lazily so the base\n// `@cloudflare/workspace` bundle has no static reference to it.\n\nimport type { SQLiteWorkspaceProvider } from \"@cloudflare/dofs\";\n\n/**\n * The shape isomorphic-git wants as its `fs` argument: an object\n * whose `.promises` is an own, enumerable property and implements\n * the `node:fs/promises` subset listed in `PromiseFsClient`.\n *\n * `promises` is typed as `object` rather than enumerating every\n * method @platformatic/vfs forwards. The full surface is wider\n * than this package consumes and pinning it here would either\n * leak @platformatic/vfs into our public types or force tedious\n * structural shadowing. Callers that want a typed file API can\n * cast `client.promises` to `import('@platformatic/vfs').VirtualFileSystem['promises']`.\n */\nexport interface IsomorphicGitFSClient {\n promises: object;\n}\n\n/**\n * Methods on `SQLiteWorkspaceProvider` forwarded through the\n * wrapping `VirtualProvider`. Listed explicitly (rather than walked\n * off the prototype) so the surface is stable across dofs releases:\n * a new method on the provider does not silently widen the wrapper.\n */\nconst FORWARDED_METHODS = [\n \"open\",\n \"openSync\",\n \"stat\",\n \"statSync\",\n \"lstat\",\n \"lstatSync\",\n \"readdir\",\n \"readdirSync\",\n \"mkdir\",\n \"mkdirSync\",\n \"rmdir\",\n \"rmdirSync\",\n \"unlink\",\n \"unlinkSync\",\n \"rename\",\n \"renameSync\",\n \"readFile\",\n \"readFileSync\",\n \"writeFile\",\n \"writeFileSync\",\n \"appendFile\",\n \"appendFileSync\",\n \"exists\",\n \"existsSync\",\n \"copyFile\",\n \"copyFileSync\",\n \"internalModuleStat\",\n \"realpath\",\n \"realpathSync\",\n \"access\",\n \"accessSync\",\n \"readlink\",\n \"readlinkSync\",\n \"symlink\",\n \"symlinkSync\",\n \"watch\",\n \"watchAsync\",\n \"watchFile\",\n \"unwatchFile\",\n // fd-style extensions @platformatic/vfs's router uses on\n // read/write paths going through `open`.\n \"closeSync\",\n \"readSync\",\n \"writeSync\",\n \"fstatSync\",\n \"truncateSync\",\n \"ftruncateSync\",\n] as const;\n\n/**\n * Build an isomorphic-git-compatible FsClient from a\n * SQLiteWorkspaceProvider.\n *\n * Lazily imports `@platformatic/vfs`. If the peer dep is missing,\n * the thrown error names the package so the failure mode stays\n * one stack frame away from obvious.\n */\nexport async function workspaceIsomorphicGitClient(\n provider: SQLiteWorkspaceProvider,\n): Promise<IsomorphicGitFSClient> {\n let create: typeof import(\"@platformatic/vfs\").create;\n let VirtualProvider: typeof import(\"@platformatic/vfs\").VirtualProvider;\n try {\n ({ create, VirtualProvider } = await import(\"@platformatic/vfs\"));\n } catch (cause) {\n throw new Error(\n \"@cloudflare/workspace/git requires @platformatic/vfs as an optional peer dependency. \" +\n \"Install it, or pass `fs` to clone() explicitly.\",\n { cause },\n );\n }\n\n class SQLiteVirtualProvider extends VirtualProvider {\n readonly #inner: SQLiteWorkspaceProvider;\n constructor(inner: SQLiteWorkspaceProvider) {\n super();\n this.#inner = inner;\n }\n // VirtualProvider's defaults are false. The dofs provider\n // declares the real values as instance properties; the\n // forwarding loop below skips accessors, so re-expose them.\n override get readonly(): boolean {\n return this.#inner.readonly;\n }\n override get supportsSymlinks(): boolean {\n return this.#inner.supportsSymlinks;\n }\n override get supportsWatch(): boolean {\n return this.#inner.supportsWatch;\n }\n /** Used by the forwarding loop below. */\n get inner(): SQLiteWorkspaceProvider {\n return this.#inner;\n }\n }\n\n for (const name of FORWARDED_METHODS) {\n Object.defineProperty(SQLiteVirtualProvider.prototype, name, {\n value(this: SQLiteVirtualProvider, ...args: unknown[]): unknown {\n // biome-ignore lint/suspicious/noExplicitAny: dispatch table\n const inner = this.inner as any;\n const fn = inner[name];\n if (typeof fn !== \"function\") {\n throw new Error(`SQLiteWorkspaceProvider.${String(name)} is not a function`);\n }\n return fn.apply(inner, args);\n },\n writable: true,\n configurable: true,\n });\n }\n\n // `moduleHooks: false` keeps @platformatic/vfs from installing\n // Node module-resolution hooks, which depend on `node:module` and\n // are pointless inside workerd.\n const vfs = create(new SQLiteVirtualProvider(provider), { moduleHooks: false });\n // Re-expose `.promises` as an enumerable own property so\n // isomorphic-git's FileSystem detection picks the promise branch.\n return { promises: vfs.promises };\n}\n","// Clone a remote repository into a workspace-backed filesystem.\n//\n// Subset-of-files support comes from isomorphic-git's two-step\n// model: clone with `noCheckout: true` to populate `.git/`, then\n// `checkout({ filepaths })` to materialize a chosen subset of the\n// working tree. The wire protocol still ships every blob reachable\n// from the tip tree — isomorphic-git does not speak the v2\n// `filter` capability — but disk writes are limited to `paths`.\n//\n// `cloneWith` is the testable core: it takes a pre-built\n// IsomorphicGitClient and HTTP transport. The public `clone()` in\n// ./index.ts resolves those from dynamic imports of `isomorphic-git`\n// so the peer dep stays optional and the base bundle pays no cost.\n\n/**\n * The subset of `isomorphic-git`'s API consumed here. Declared\n * structurally to avoid pulling the package's `.d.ts` into this\n * package's public types, and to let tests pass plain spies.\n */\nexport interface IsomorphicGitClient {\n clone(args: {\n fs: object;\n http: object;\n dir: string;\n url: string;\n ref?: string;\n corsProxy?: string;\n headers?: Record<string, string>;\n depth?: number;\n singleBranch?: boolean;\n noTags?: boolean;\n noCheckout?: boolean;\n cache?: object;\n onProgress?: ProgressCallback;\n onMessage?: MessageCallback;\n }): Promise<void>;\n\n checkout(args: {\n fs: object;\n dir: string;\n ref: string;\n filepaths?: string[];\n force?: boolean;\n cache?: object;\n }): Promise<void>;\n}\n\nexport type ProgressCallback = (e: { phase: string; loaded: number; total?: number }) => void;\nexport type MessageCallback = (msg: string) => void;\n\nexport interface GitCloneOptions {\n /** Repository URL. HTTPS only — isomorphic-git has no SSH transport. */\n url: string;\n /** Working-tree directory inside the VFS. Defaults to `/`. */\n dir?: string;\n /** Branch, tag, or commit. Defaults to the remote's default branch. */\n ref?: string;\n /**\n * If set, only these paths are written into the working tree.\n * `.git/` is fully populated regardless (subject to `depth`).\n * Globs are not supported; pass directory or file paths.\n */\n paths?: string[];\n /**\n * Shallow-clone depth. Defaults to 1. Pass `0` or `Infinity` for\n * full history. Even with depth=1, every blob reachable from the\n * tip tree is fetched — see the package README for why.\n */\n depth?: number;\n /** Fetch only the requested ref's branch. Default: true. */\n singleBranch?: boolean;\n /** Skip tags. Default: true. */\n noTags?: boolean;\n /** Extra HTTP headers, e.g. `Authorization` for a private repo. */\n headers?: Record<string, string>;\n /** CORS proxy URL. Usually unneeded inside a Worker. */\n corsProxy?: string;\n /** Progress callback. Forwarded to isomorphic-git. */\n onProgress?: ProgressCallback;\n /** Sideband message callback. Forwarded to isomorphic-git. */\n onMessage?: MessageCallback;\n}\n\n/**\n * Dependency-injected form. Used directly by tests and by the\n * public `clone()` wrapper. Splitting this out keeps the import\n * graph clean: this file has no runtime dependency on\n * `isomorphic-git` or `@platformatic/vfs`.\n */\nexport interface CloneWithDeps extends GitCloneOptions {\n git: IsomorphicGitClient;\n http: object;\n fs: object;\n /**\n * isomorphic-git's pack/index cache. Pass the same object to\n * every isogit call against this repo so the packfile is parsed\n * once. Without this, each `readBlob` / `walk` / `statusMatrix`\n * re-parses the pack from the SQLite-backed VFS (~tens of MB),\n * which is the difference between sub-second diffs and minutes.\n */\n cache?: object;\n}\n\nexport async function cloneWith(opts: CloneWithDeps): Promise<void> {\n const dir = opts.dir ?? \"/\";\n const ref = opts.ref;\n const depthRaw = opts.depth ?? 1;\n // depth=0 and depth=Infinity both mean \"no shallow limit\" on this\n // surface. isomorphic-git interprets `undefined` that way.\n const depth = depthRaw > 0 && Number.isFinite(depthRaw) ? depthRaw : undefined;\n\n await opts.git.clone({\n fs: opts.fs,\n http: opts.http,\n dir,\n url: opts.url,\n ref,\n corsProxy: opts.corsProxy,\n headers: opts.headers,\n depth,\n singleBranch: opts.singleBranch ?? true,\n noTags: opts.noTags ?? true,\n noCheckout: true,\n cache: opts.cache,\n onProgress: opts.onProgress,\n onMessage: opts.onMessage,\n });\n\n // The working tree is empty after a noCheckout clone, so `force`\n // is safe — nothing real can conflict — and matches caller intent\n // (\"populate this workspace from the remote\").\n await opts.git.checkout({\n fs: opts.fs,\n dir,\n ref: ref ?? \"HEAD\",\n filepaths: opts.paths,\n force: true,\n cache: opts.cache,\n });\n}\n","// Unified-diff between a git ref (default HEAD) and the working\n// tree, the way `git diff HEAD` would, but in pure JS via\n// isomorphic-git + the `diff` package.\n//\n// `diffWith` is the testable core: takes a pre-built\n// IsomorphicGitDiffClient, an FsClient, a `createPatch` function,\n// and a `readFile` function. The public `diff()` in ./index.ts\n// resolves those from dynamic imports of `isomorphic-git` and\n// `diff` so both stay optional peer deps.\n\nimport type { IsomorphicGitFSClient } from \"./adapter.js\";\n\n/**\n * Status-matrix row as emitted by isomorphic-git's `statusMatrix`:\n * `[filepath, headStatus, workdirStatus, stageStatus]`.\n * - 0 = absent\n * - 1 = same as HEAD\n * - 2 = differs from HEAD\n * - 3 = differs from HEAD and stage (rarely meaningful here)\n */\nexport type StatusRow = [string, number, number, number];\n\n/** Subset of isomorphic-git's API used to compute a working-tree diff. */\nexport interface IsomorphicGitDiffClient {\n resolveRef(args: { fs: object; dir: string; ref: string }): Promise<string>;\n statusMatrix(args: {\n fs: object;\n dir: string;\n ref?: string;\n cache?: object;\n }): Promise<StatusRow[]>;\n readBlob(args: {\n fs: object;\n dir: string;\n oid: string;\n filepath: string;\n cache?: object;\n }): Promise<{ blob: Uint8Array; oid: string }>;\n}\n\n/** Signature compatible with the `diff` package's `createPatch`. */\nexport type CreatePatchFn = (\n fileName: string,\n oldStr: string,\n newStr: string,\n oldHeader?: string,\n newHeader?: string,\n) => string;\n\n/** Signature compatible with `fs.promises.readFile` (binary mode). */\nexport type ReadFileFn = (path: string) => Promise<Uint8Array | string>;\n\nexport interface GitDiffOptions {\n /** Working-tree directory inside the VFS. Defaults to `/`. */\n dir?: string;\n /** Ref to diff against. Defaults to `HEAD`. */\n ref?: string;\n}\n\n/**\n * Dependency-injected form. Used directly by tests and by the\n * public `diff()` wrapper.\n */\nexport interface DiffWithDeps extends GitDiffOptions {\n git: IsomorphicGitDiffClient;\n fs: IsomorphicGitFSClient | object;\n createPatch: CreatePatchFn;\n readFile: ReadFileFn;\n /**\n * isomorphic-git's pack/index cache. Shared with the surrounding\n * GitClient so the packfile is parsed once across clone, diff,\n * and any other isogit call. See clone.ts's CloneWithDeps.cache.\n */\n cache?: object;\n}\n\nexport async function diffWith(opts: DiffWithDeps): Promise<string> {\n const dir = opts.dir ?? \"/\";\n const ref = opts.ref ?? \"HEAD\";\n\n let head: string;\n try {\n head = await opts.git.resolveRef({ fs: opts.fs, dir, ref });\n } catch {\n // Ref unresolvable (e.g. workspace never cloned). Empty\n // string is a more useful signal than an exception for the\n // common \"diff after maybe-no-op\" call site.\n return \"\";\n }\n\n // Pass `ref` through so the matrix is computed against the\n // requested commit rather than always HEAD. Without this the\n // `ref` argument would only affect blob reads, leaving the\n // status walk silently skewed.\n const status = await opts.git.statusMatrix({ fs: opts.fs, dir, ref, cache: opts.cache });\n const chunks: string[] = [];\n for (const [filepath, headStatus, workdirStatus] of status) {\n // workdirStatus: 0 absent, 1 == HEAD, 2 differs. Skip\n // unchanged rows up front to avoid the blob/file reads.\n if (workdirStatus === 1) continue;\n\n const headText =\n headStatus === 1\n ? await readBlobAsText(opts.git, opts.fs, dir, head, filepath, opts.cache)\n : \"\";\n const workdirText =\n workdirStatus === 2 ? await readWorkdirAsText(opts.readFile, dir, filepath) : \"\";\n const patch = opts.createPatch(filepath, headText, workdirText, \"\", \"\");\n if (patch.trim().length > 0) chunks.push(patch);\n }\n return chunks.join(\"\\n\");\n}\n\nasync function readBlobAsText(\n git: IsomorphicGitDiffClient,\n fs: object,\n dir: string,\n oid: string,\n filepath: string,\n cache: object | undefined,\n): Promise<string> {\n try {\n const { blob } = await git.readBlob({ fs, dir, oid, filepath, cache });\n // Best-effort UTF-8 decode. A real diff tool would skip\n // binaries entirely; for the general case here a noisy diff\n // beats a thrown error.\n return new TextDecoder(\"utf-8\", { fatal: false, ignoreBOM: false }).decode(blob);\n } catch {\n return \"\";\n }\n}\n\nasync function readWorkdirAsText(\n readFile: ReadFileFn,\n dir: string,\n filepath: string,\n): Promise<string> {\n try {\n const data = await readFile(`${dir}/${filepath}`);\n if (typeof data === \"string\") return data;\n return new TextDecoder(\"utf-8\", { fatal: false, ignoreBOM: false }).decode(data);\n } catch {\n return \"\";\n }\n}\n","// Public surface of @cloudflare/workspace/git.\n//\n// `createGitClient({ ws })` is the one entry point. It binds a\n// workspace handle once and returns `{ clone, diff }` methods that\n// don't repeat the workspace argument. Internally each method\n// lazy-loads its optional peer deps (`isomorphic-git`, the http\n// transport, and for `diff` the `diff` package) and delegates to\n// `cloneWith` / `diffWith`.\n//\n// `cloneWith` and `diffWith` are still exported for callers that\n// bring their own FsClient — tests, custom adapters, running\n// against node:fs directly — but the workspace-bound path is the\n// only one most consumers need.\n\nimport type { SQLiteWorkspaceProvider } from \"@cloudflare/dofs\";\n\nimport { type IsomorphicGitFSClient, workspaceIsomorphicGitClient } from \"./adapter.js\";\nimport { cloneWith, type GitCloneOptions, type IsomorphicGitClient } from \"./clone.js\";\nimport {\n type CreatePatchFn,\n diffWith,\n type GitDiffOptions,\n type IsomorphicGitDiffClient,\n type ReadFileFn,\n} from \"./diff.js\";\n\nexport type { GitCloneOptions, MessageCallback, ProgressCallback } from \"./clone.js\";\nexport type { GitDiffOptions, StatusRow } from \"./diff.js\";\n\n/** Duck-typed workspace handle. Only `.provider()` is required. */\nexport interface WorkspaceLike {\n provider(): SQLiteWorkspaceProvider;\n}\n\n/** Methods returned by `createGitClient`. */\nexport interface GitClient {\n /** Shallow-clone a remote into the bound workspace. */\n clone(options: GitCloneOptions): Promise<void>;\n /** Unified diff between a ref (default HEAD) and the working tree. */\n diff(options?: GitDiffOptions): Promise<string>;\n}\n\nexport interface CreateGitClientOptions {\n /** Workspace whose provider backs the git operations. */\n ws: WorkspaceLike;\n /**\n * Test seam for substituting the @platformatic/vfs adapter.\n * Production callers do not pass this.\n */\n adapter?: (provider: SQLiteWorkspaceProvider) => Promise<IsomorphicGitFSClient>;\n}\n\n/**\n * Build a git client bound to a workspace.\n *\n * The FsClient is constructed lazily on first use and reused\n * across subsequent calls — `@platformatic/vfs.create()` is cheap\n * but not free, and the workspace provider is stable for the\n * lifetime of the client.\n *\n * An isomorphic-git pack/index cache is also created per client\n * and threaded into every isogit call. Without this, each\n * `readBlob` / `statusMatrix` / `walk` re-parses the packfile\n * from the SQLite-backed VFS — fine for tiny repos, catastrophic\n * for anything with a real history (the difference between a\n * sub-second `diff` and one that hangs for minutes).\n */\nexport function createGitClient({\n ws,\n adapter = workspaceIsomorphicGitClient,\n}: CreateGitClientOptions): GitClient {\n let fsPromise: Promise<IsomorphicGitFSClient> | undefined;\n const fs = () => {\n if (!fsPromise) fsPromise = adapter(ws.provider());\n return fsPromise;\n };\n const cache: Record<string, unknown> = {};\n\n return {\n async clone(options) {\n await cloneWith({\n ...options,\n fs: await fs(),\n git: await loadIsomorphicGit<IsomorphicGitClient>(),\n http: await loadDefaultHTTP(),\n cache,\n });\n },\n async diff(options = {}) {\n const f = await fs();\n return diffWith({\n ...options,\n fs: f,\n git: await loadIsomorphicGit<IsomorphicGitDiffClient>(),\n createPatch: await loadCreatePatch(),\n readFile: readFileFrom(f),\n cache,\n });\n },\n };\n}\n\n// isomorphic-git ships both named exports and a default export\n// (CJS interop). Callers pull the subset they need via the generic.\nasync function loadIsomorphicGit<T>(): Promise<T> {\n try {\n const mod = await import(\"isomorphic-git\");\n return (mod.default ?? mod) as unknown as T;\n } catch (cause) {\n throw new Error(\n \"@cloudflare/workspace/git requires isomorphic-git as an optional peer dependency. \" +\n \"Install isomorphic-git.\",\n { cause },\n );\n }\n}\n\nasync function loadDefaultHTTP(): Promise<object> {\n try {\n const mod = await import(\"isomorphic-git/http/web\");\n return mod.default;\n } catch (cause) {\n throw new Error(\"Failed to load isomorphic-git/http/web. Install isomorphic-git.\", { cause });\n }\n}\n\nasync function loadCreatePatch(): Promise<CreatePatchFn> {\n try {\n const mod = await import(\"diff\");\n return mod.createPatch;\n } catch (cause) {\n throw new Error(\n \"@cloudflare/workspace/git requires `diff` as an optional peer dependency. \" +\n \"Install `diff`.\",\n { cause },\n );\n }\n}\n\nfunction readFileFrom(fs: IsomorphicGitFSClient): ReadFileFn {\n // `fs.promises` is typed as `object` on the public surface; the\n // underlying @platformatic/vfs handle exposes `readFile`. Narrow\n // locally rather than widening the exported type.\n const promises = fs.promises as { readFile: ReadFileFn };\n return (path) => promises.readFile(path);\n}\n"],"mappings":"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"}