tensorlake 0.5.77 → 0.5.88
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/dist/index.cjs +704 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +336 -1
- package/dist/index.d.ts +336 -1
- package/dist/index.js +695 -1
- package/dist/index.js.map +1 -1
- package/dist/native/darwin-arm64/tensorlake-node.node +0 -0
- package/dist/native/linux-arm64/tensorlake-node.node +0 -0
- package/dist/native/linux-arm64-musl/tensorlake-node.node +0 -0
- package/dist/native/linux-x64/tensorlake-node.node +0 -0
- package/dist/native/linux-x64-musl/tensorlake-node.node +0 -0
- package/dist/native/win32-x64/tensorlake-node.node +0 -0
- package/dist/sandbox-image.cjs +1 -1
- package/dist/sandbox-image.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
|
|
|
35
35
|
var init_defaults = __esm({
|
|
36
36
|
"src/defaults.ts"() {
|
|
37
37
|
"use strict";
|
|
38
|
-
SDK_VERSION = "0.5.
|
|
38
|
+
SDK_VERSION = "0.5.88";
|
|
39
39
|
API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
|
|
40
40
|
API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
|
|
41
41
|
NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
|
|
@@ -6153,11 +6153,20 @@ var init_sandbox_image = __esm({
|
|
|
6153
6153
|
var index_exports = {};
|
|
6154
6154
|
__export(index_exports, {
|
|
6155
6155
|
APIClient: () => APIClient,
|
|
6156
|
+
CliNotFoundError: () => CliNotFoundError,
|
|
6156
6157
|
CloudClient: () => CloudClient,
|
|
6157
6158
|
ContainerState: () => ContainerState,
|
|
6158
6159
|
Desktop: () => Desktop,
|
|
6160
|
+
FileNotFoundInFilesystemError: () => FileNotFoundInFilesystemError,
|
|
6161
|
+
Filesystem: () => Filesystem,
|
|
6162
|
+
FilesystemAPIError: () => FilesystemAPIError,
|
|
6163
|
+
FilesystemClient: () => FilesystemClient,
|
|
6164
|
+
FilesystemError: () => FilesystemError,
|
|
6165
|
+
FilesystemMount: () => FilesystemMount,
|
|
6166
|
+
FilesystemNotFoundError: () => FilesystemNotFoundError,
|
|
6159
6167
|
Image: () => Image,
|
|
6160
6168
|
ImageBuildOperationType: () => ImageBuildOperationType,
|
|
6169
|
+
MountError: () => MountError,
|
|
6161
6170
|
OutputMode: () => OutputMode,
|
|
6162
6171
|
PoolInUseError: () => PoolInUseError,
|
|
6163
6172
|
PoolNotFoundError: () => PoolNotFoundError,
|
|
@@ -6542,6 +6551,691 @@ async function deleteFileSystem(fileSystemId) {
|
|
|
6542
6551
|
}
|
|
6543
6552
|
}
|
|
6544
6553
|
|
|
6554
|
+
// src/filesystem.ts
|
|
6555
|
+
var import_node_child_process = require("child_process");
|
|
6556
|
+
var import_node_crypto2 = require("crypto");
|
|
6557
|
+
var import_node_fs2 = require("fs");
|
|
6558
|
+
var import_node_os2 = require("os");
|
|
6559
|
+
var import_node_path2 = require("path");
|
|
6560
|
+
var import_node_util2 = require("util");
|
|
6561
|
+
init_defaults();
|
|
6562
|
+
|
|
6563
|
+
// src/filesystem-models.ts
|
|
6564
|
+
var GIT_MODE_DIR = 16384;
|
|
6565
|
+
var GIT_MODE_SYMLINK = 40960;
|
|
6566
|
+
function trimSlashes(path2) {
|
|
6567
|
+
let start = 0;
|
|
6568
|
+
let end = path2.length;
|
|
6569
|
+
while (start < end && path2.charCodeAt(start) === 47) start += 1;
|
|
6570
|
+
while (end > start && path2.charCodeAt(end - 1) === 47) end -= 1;
|
|
6571
|
+
return path2.slice(start, end);
|
|
6572
|
+
}
|
|
6573
|
+
function fileEntryFromWire(entry, dirPath) {
|
|
6574
|
+
const mode = entry.mode ?? 33188;
|
|
6575
|
+
const prefix = trimSlashes(dirPath);
|
|
6576
|
+
return {
|
|
6577
|
+
name: entry.name,
|
|
6578
|
+
path: prefix ? `${prefix}/${entry.name}` : entry.name,
|
|
6579
|
+
contentId: entry.oid ?? "",
|
|
6580
|
+
kind: mode === GIT_MODE_DIR ? "directory" : mode === GIT_MODE_SYMLINK ? "symlink" : "file",
|
|
6581
|
+
executable: mode === 33261,
|
|
6582
|
+
size: entry.size ?? null,
|
|
6583
|
+
isDir: mode === GIT_MODE_DIR,
|
|
6584
|
+
isSymlink: mode === GIT_MODE_SYMLINK
|
|
6585
|
+
};
|
|
6586
|
+
}
|
|
6587
|
+
function mountStatusFromRaw(raw, localPath) {
|
|
6588
|
+
const mounted = "mounted" in raw ? Boolean(raw.mounted) : "active" in raw ? Boolean(raw.active) : true;
|
|
6589
|
+
const path2 = String(raw.path || raw.mount_path || localPath || "");
|
|
6590
|
+
const filesystem = raw.filesystem || raw.file_system || null;
|
|
6591
|
+
return { path: path2, filesystem, mounted, raw };
|
|
6592
|
+
}
|
|
6593
|
+
var FilesystemError = class extends Error {
|
|
6594
|
+
constructor(message) {
|
|
6595
|
+
super(message);
|
|
6596
|
+
this.name = "FilesystemError";
|
|
6597
|
+
}
|
|
6598
|
+
};
|
|
6599
|
+
var FilesystemNotFoundError = class extends FilesystemError {
|
|
6600
|
+
filesystemName;
|
|
6601
|
+
constructor(name) {
|
|
6602
|
+
super(`Filesystem not found: ${name}`);
|
|
6603
|
+
this.name = "FilesystemNotFoundError";
|
|
6604
|
+
this.filesystemName = name;
|
|
6605
|
+
}
|
|
6606
|
+
};
|
|
6607
|
+
var FileNotFoundInFilesystemError = class extends FilesystemError {
|
|
6608
|
+
filesystem;
|
|
6609
|
+
path;
|
|
6610
|
+
constructor(filesystem, path2) {
|
|
6611
|
+
super(`File not found in filesystem ${filesystem}: ${path2}`);
|
|
6612
|
+
this.name = "FileNotFoundInFilesystemError";
|
|
6613
|
+
this.filesystem = filesystem;
|
|
6614
|
+
this.path = path2;
|
|
6615
|
+
}
|
|
6616
|
+
};
|
|
6617
|
+
var FilesystemAPIError = class extends FilesystemError {
|
|
6618
|
+
statusCode;
|
|
6619
|
+
constructor(statusCode, message) {
|
|
6620
|
+
super(`API error (status ${statusCode}): ${message}`);
|
|
6621
|
+
this.name = "FilesystemAPIError";
|
|
6622
|
+
this.statusCode = statusCode;
|
|
6623
|
+
}
|
|
6624
|
+
};
|
|
6625
|
+
var MountError = class extends FilesystemError {
|
|
6626
|
+
constructor(message) {
|
|
6627
|
+
super(message);
|
|
6628
|
+
this.name = "MountError";
|
|
6629
|
+
}
|
|
6630
|
+
};
|
|
6631
|
+
var CliNotFoundError = class extends MountError {
|
|
6632
|
+
constructor(detail) {
|
|
6633
|
+
super(
|
|
6634
|
+
`Mount operations require the Tensorlake CLI (\`tl\`) with \`tl fs\` support: ${detail}. Install or upgrade it with: curl -fsSL https://tensorlake.ai/install.sh | sh`
|
|
6635
|
+
);
|
|
6636
|
+
this.name = "CliNotFoundError";
|
|
6637
|
+
}
|
|
6638
|
+
};
|
|
6639
|
+
|
|
6640
|
+
// src/filesystem.ts
|
|
6641
|
+
init_native_sandbox();
|
|
6642
|
+
init_sandbox_image();
|
|
6643
|
+
var execFileAsync = (0, import_node_util2.promisify)(import_node_child_process.execFile);
|
|
6644
|
+
var FILESYSTEM_REPO_KIND = "filesystem";
|
|
6645
|
+
function translateNativeError2(error, notFound) {
|
|
6646
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6647
|
+
let payload;
|
|
6648
|
+
try {
|
|
6649
|
+
payload = JSON.parse(message);
|
|
6650
|
+
} catch {
|
|
6651
|
+
return error instanceof Error ? error : new FilesystemError(message);
|
|
6652
|
+
}
|
|
6653
|
+
if (!payload || typeof payload.category !== "string") {
|
|
6654
|
+
return error instanceof Error ? error : new FilesystemError(message);
|
|
6655
|
+
}
|
|
6656
|
+
const detail = typeof payload.message === "string" ? payload.message : message;
|
|
6657
|
+
if (payload.category === "connection") {
|
|
6658
|
+
return new FilesystemError(detail);
|
|
6659
|
+
}
|
|
6660
|
+
if (payload.status === 404 && notFound !== void 0) {
|
|
6661
|
+
return notFound;
|
|
6662
|
+
}
|
|
6663
|
+
if (typeof payload.status === "number") {
|
|
6664
|
+
return new FilesystemAPIError(payload.status, detail);
|
|
6665
|
+
}
|
|
6666
|
+
return new FilesystemError(detail);
|
|
6667
|
+
}
|
|
6668
|
+
async function callNative2(fn, notFound) {
|
|
6669
|
+
try {
|
|
6670
|
+
return await fn();
|
|
6671
|
+
} catch (error) {
|
|
6672
|
+
throw translateNativeError2(error, notFound);
|
|
6673
|
+
}
|
|
6674
|
+
}
|
|
6675
|
+
var FsCli = class {
|
|
6676
|
+
binary = null;
|
|
6677
|
+
envOverrides;
|
|
6678
|
+
constructor(envOverrides) {
|
|
6679
|
+
this.envOverrides = envOverrides;
|
|
6680
|
+
}
|
|
6681
|
+
async findCli() {
|
|
6682
|
+
const candidates = [];
|
|
6683
|
+
if (process.env.TENSORLAKE_CLI) {
|
|
6684
|
+
candidates.push(process.env.TENSORLAKE_CLI);
|
|
6685
|
+
}
|
|
6686
|
+
candidates.push("tl");
|
|
6687
|
+
const installed = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".tensorlake", "bin", "tl");
|
|
6688
|
+
try {
|
|
6689
|
+
(0, import_node_fs2.accessSync)(installed, import_node_fs2.constants.F_OK);
|
|
6690
|
+
candidates.push(installed);
|
|
6691
|
+
} catch {
|
|
6692
|
+
}
|
|
6693
|
+
let unsupported = null;
|
|
6694
|
+
for (const candidate of candidates) {
|
|
6695
|
+
try {
|
|
6696
|
+
await execFileAsync(candidate, ["fs", "--help"], { timeout: 15e3 });
|
|
6697
|
+
return candidate;
|
|
6698
|
+
} catch (error) {
|
|
6699
|
+
if (error.code !== "ENOENT") {
|
|
6700
|
+
unsupported = candidate;
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6704
|
+
throw new CliNotFoundError(
|
|
6705
|
+
unsupported ? `\`tl\` at ${unsupported} does not support \`tl fs\` (upgrade required)` : "`tl` was not found on PATH"
|
|
6706
|
+
);
|
|
6707
|
+
}
|
|
6708
|
+
async run(args, timeoutMs = 3e5) {
|
|
6709
|
+
if (this.binary === null) {
|
|
6710
|
+
this.binary = await this.findCli();
|
|
6711
|
+
}
|
|
6712
|
+
try {
|
|
6713
|
+
const { stdout } = await execFileAsync(this.binary, ["fs", ...args], {
|
|
6714
|
+
timeout: timeoutMs,
|
|
6715
|
+
env: { ...process.env, ...this.envOverrides },
|
|
6716
|
+
maxBuffer: 16 * 1024 * 1024
|
|
6717
|
+
});
|
|
6718
|
+
return stdout;
|
|
6719
|
+
} catch (error) {
|
|
6720
|
+
const failure = error;
|
|
6721
|
+
if (failure.killed) {
|
|
6722
|
+
throw new MountError(`\`tl fs ${args[0]}\` timed out after ${timeoutMs}ms`);
|
|
6723
|
+
}
|
|
6724
|
+
const detail = (failure.stderr || failure.stdout || String(error)).trim();
|
|
6725
|
+
throw new MountError(`\`tl fs ${args.join(" ")}\` failed: ${detail}`);
|
|
6726
|
+
}
|
|
6727
|
+
}
|
|
6728
|
+
// Flags always precede a `--` end-of-options separator so caller-supplied
|
|
6729
|
+
// names/paths can never be parsed as CLI flags (e.g. a path literally
|
|
6730
|
+
// named "--discard" must stay a path, not become the destructive flag).
|
|
6731
|
+
async mount(filesystem, localPath, readonly) {
|
|
6732
|
+
const args = ["mount"];
|
|
6733
|
+
if (readonly) args.push("--ro");
|
|
6734
|
+
args.push("--", filesystem, localPath);
|
|
6735
|
+
await this.run(args);
|
|
6736
|
+
}
|
|
6737
|
+
async unmount(localPath, discard) {
|
|
6738
|
+
const args = ["unmount"];
|
|
6739
|
+
if (discard) args.push("--discard");
|
|
6740
|
+
args.push("--", localPath);
|
|
6741
|
+
await this.run(args);
|
|
6742
|
+
}
|
|
6743
|
+
async snapshot(localPath, message) {
|
|
6744
|
+
const args = ["snapshot"];
|
|
6745
|
+
if (message) args.push(`--message=${message}`);
|
|
6746
|
+
args.push("--", localPath);
|
|
6747
|
+
await this.run(args);
|
|
6748
|
+
}
|
|
6749
|
+
async status(localPath) {
|
|
6750
|
+
const args = ["status", "--json"];
|
|
6751
|
+
if (localPath) args.push("--", localPath);
|
|
6752
|
+
const output = await this.run(args, 6e4);
|
|
6753
|
+
try {
|
|
6754
|
+
const payload = JSON.parse(output);
|
|
6755
|
+
return typeof payload === "object" && payload !== null ? payload : { status: payload };
|
|
6756
|
+
} catch {
|
|
6757
|
+
throw new MountError(
|
|
6758
|
+
`\`tl fs status --json\` returned invalid JSON: ${output.slice(0, 200)}`
|
|
6759
|
+
);
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6762
|
+
};
|
|
6763
|
+
var FilesystemClient = class {
|
|
6764
|
+
native;
|
|
6765
|
+
cli;
|
|
6766
|
+
/**
|
|
6767
|
+
* Any option left unset is resolved from the environment
|
|
6768
|
+
* (`TENSORLAKE_API_KEY` / `TENSORLAKE_PAT`, `TENSORLAKE_API_URL`,
|
|
6769
|
+
* `TENSORLAKE_ORGANIZATION_ID`, `TENSORLAKE_PROJECT_ID`).
|
|
6770
|
+
*/
|
|
6771
|
+
constructor(options = {}) {
|
|
6772
|
+
const context = buildContextFromEnv();
|
|
6773
|
+
const token = options.apiKey ?? context.apiKey ?? context.personalAccessToken;
|
|
6774
|
+
if (!token) {
|
|
6775
|
+
throw new FilesystemError(
|
|
6776
|
+
"Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT credentials."
|
|
6777
|
+
);
|
|
6778
|
+
}
|
|
6779
|
+
const organizationId = options.organizationId ?? context.organizationId;
|
|
6780
|
+
const projectId = options.projectId ?? context.projectId;
|
|
6781
|
+
if (!organizationId || !projectId) {
|
|
6782
|
+
throw new FilesystemError(
|
|
6783
|
+
"Filesystem operations require organization and project context (TENSORLAKE_ORGANIZATION_ID and TENSORLAKE_PROJECT_ID)."
|
|
6784
|
+
);
|
|
6785
|
+
}
|
|
6786
|
+
if (options.nativeClient) {
|
|
6787
|
+
this.native = options.nativeClient;
|
|
6788
|
+
} else {
|
|
6789
|
+
const binding = loadNativeSandboxBinding();
|
|
6790
|
+
if (typeof binding.NativeRepositoryClient !== "function") {
|
|
6791
|
+
throw new FilesystemError(
|
|
6792
|
+
"native binding does not export the repository client; rebuild with 'npm run build:native'"
|
|
6793
|
+
);
|
|
6794
|
+
}
|
|
6795
|
+
this.native = new binding.NativeRepositoryClient(
|
|
6796
|
+
options.apiUrl ?? context.apiUrl,
|
|
6797
|
+
token,
|
|
6798
|
+
organizationId,
|
|
6799
|
+
projectId,
|
|
6800
|
+
`tensorlake-typescript-sdk/${SDK_VERSION}`,
|
|
6801
|
+
null
|
|
6802
|
+
);
|
|
6803
|
+
}
|
|
6804
|
+
const envOverrides = {
|
|
6805
|
+
TENSORLAKE_ORGANIZATION_ID: organizationId,
|
|
6806
|
+
TENSORLAKE_PROJECT_ID: projectId,
|
|
6807
|
+
// The CLI must target the same deployment the data plane does, or a
|
|
6808
|
+
// mount could resolve a same-named filesystem in the wrong environment.
|
|
6809
|
+
TENSORLAKE_API_URL: options.apiUrl ?? context.apiUrl
|
|
6810
|
+
};
|
|
6811
|
+
if (options.apiKey) {
|
|
6812
|
+
envOverrides.TENSORLAKE_API_KEY = options.apiKey;
|
|
6813
|
+
}
|
|
6814
|
+
this.cli = new FsCli(envOverrides);
|
|
6815
|
+
}
|
|
6816
|
+
// -- lifecycle --------------------------------------------------------------
|
|
6817
|
+
/** Create a new filesystem and return a handle to it. */
|
|
6818
|
+
async create(name) {
|
|
6819
|
+
if (typeof name !== "string") {
|
|
6820
|
+
throw new TypeError("name must be a string");
|
|
6821
|
+
}
|
|
6822
|
+
if (name.length === 0) {
|
|
6823
|
+
throw new FilesystemError("filesystem name must not be empty");
|
|
6824
|
+
}
|
|
6825
|
+
const raw = await callNative2(() => this.native.createFilesystem(name));
|
|
6826
|
+
const created = JSON.parse(raw);
|
|
6827
|
+
return new Filesystem(
|
|
6828
|
+
this,
|
|
6829
|
+
this.native,
|
|
6830
|
+
this.cli,
|
|
6831
|
+
name,
|
|
6832
|
+
created.default_branch || "main"
|
|
6833
|
+
);
|
|
6834
|
+
}
|
|
6835
|
+
/**
|
|
6836
|
+
* Fork a filesystem at its live head or an explicit retained snapshot.
|
|
6837
|
+
* The server shares immutable content and publishes only metadata.
|
|
6838
|
+
*/
|
|
6839
|
+
async fork(name, baseFilesystem, snapshot) {
|
|
6840
|
+
if (!name || !baseFilesystem) {
|
|
6841
|
+
throw new FilesystemError(
|
|
6842
|
+
"fork and base filesystem names must not be empty"
|
|
6843
|
+
);
|
|
6844
|
+
}
|
|
6845
|
+
await callNative2(
|
|
6846
|
+
() => this.native.forkFilesystem(
|
|
6847
|
+
name,
|
|
6848
|
+
baseFilesystem,
|
|
6849
|
+
snapshot ?? null
|
|
6850
|
+
),
|
|
6851
|
+
new FilesystemNotFoundError(baseFilesystem)
|
|
6852
|
+
);
|
|
6853
|
+
return new Filesystem(this, this.native, this.cli, name, "main");
|
|
6854
|
+
}
|
|
6855
|
+
/** Return a handle to an existing filesystem (verifies it exists). */
|
|
6856
|
+
async get(name) {
|
|
6857
|
+
const traced = await callNative2(
|
|
6858
|
+
() => this.native.filesystemMeta(name),
|
|
6859
|
+
new FilesystemNotFoundError(name)
|
|
6860
|
+
);
|
|
6861
|
+
const meta = JSON.parse(traced.json);
|
|
6862
|
+
if ((meta.kind ?? FILESYSTEM_REPO_KIND) !== FILESYSTEM_REPO_KIND) {
|
|
6863
|
+
throw new FilesystemNotFoundError(name);
|
|
6864
|
+
}
|
|
6865
|
+
return new Filesystem(
|
|
6866
|
+
this,
|
|
6867
|
+
this.native,
|
|
6868
|
+
this.cli,
|
|
6869
|
+
name,
|
|
6870
|
+
meta.default_branch || "main"
|
|
6871
|
+
);
|
|
6872
|
+
}
|
|
6873
|
+
/** List all filesystems in the project. */
|
|
6874
|
+
async list() {
|
|
6875
|
+
const traced = await callNative2(() => this.native.listFilesystems());
|
|
6876
|
+
const page = JSON.parse(traced.json);
|
|
6877
|
+
return (page.repos ?? []).map((repo) => ({
|
|
6878
|
+
name: String(repo.name ?? ""),
|
|
6879
|
+
fullName: String(repo.full_name ?? ""),
|
|
6880
|
+
status: String(repo.status ?? ""),
|
|
6881
|
+
kind: String(repo.kind ?? FILESYSTEM_REPO_KIND)
|
|
6882
|
+
}));
|
|
6883
|
+
}
|
|
6884
|
+
/** Permanently delete a filesystem and all its snapshots. */
|
|
6885
|
+
async delete(name) {
|
|
6886
|
+
await callNative2(
|
|
6887
|
+
() => this.native.deleteFilesystem(name),
|
|
6888
|
+
new FilesystemNotFoundError(name)
|
|
6889
|
+
);
|
|
6890
|
+
}
|
|
6891
|
+
// -- local mounts -------------------------------------------------------------
|
|
6892
|
+
/** Mount a filesystem to a local path (requires the `tl` CLI). */
|
|
6893
|
+
async mount(name, localPath, readonly = false) {
|
|
6894
|
+
await this.cli.mount(name, localPath, readonly);
|
|
6895
|
+
return new FilesystemMount(this, this.cli, name, localPath, readonly);
|
|
6896
|
+
}
|
|
6897
|
+
/**
|
|
6898
|
+
* Unmount a locally mounted filesystem.
|
|
6899
|
+
*
|
|
6900
|
+
* `discard: true` drops local changes that were not yet uploaded.
|
|
6901
|
+
*/
|
|
6902
|
+
async unmount(localPath, discard = false) {
|
|
6903
|
+
await this.cli.unmount(localPath, discard);
|
|
6904
|
+
}
|
|
6905
|
+
/** Status of a local mount (defaults to the mount containing CWD). */
|
|
6906
|
+
async mountStatus(localPath) {
|
|
6907
|
+
return mountStatusFromRaw(await this.cli.status(localPath), localPath);
|
|
6908
|
+
}
|
|
6909
|
+
};
|
|
6910
|
+
function toBuffer(data) {
|
|
6911
|
+
return typeof data === "string" ? Buffer.from(data, "utf-8") : Buffer.from(data);
|
|
6912
|
+
}
|
|
6913
|
+
var Filesystem = class {
|
|
6914
|
+
client;
|
|
6915
|
+
native;
|
|
6916
|
+
cli;
|
|
6917
|
+
name;
|
|
6918
|
+
defaultBranch;
|
|
6919
|
+
/** @internal — obtain instances via `FilesystemClient.create()` / `.get()`. */
|
|
6920
|
+
constructor(client, native, cli, name, defaultBranch = null) {
|
|
6921
|
+
this.client = client;
|
|
6922
|
+
this.native = native;
|
|
6923
|
+
this.cli = cli;
|
|
6924
|
+
this.name = name;
|
|
6925
|
+
this.defaultBranch = defaultBranch;
|
|
6926
|
+
}
|
|
6927
|
+
/**
|
|
6928
|
+
* The filesystem's default branch — the target of every write and the
|
|
6929
|
+
* default version of every read, so writes and `status()` can never
|
|
6930
|
+
* silently disagree on a non-"main" filesystem.
|
|
6931
|
+
*/
|
|
6932
|
+
async branch() {
|
|
6933
|
+
if (!this.defaultBranch) {
|
|
6934
|
+
const traced = await callNative2(
|
|
6935
|
+
() => this.native.filesystemMeta(this.name),
|
|
6936
|
+
new FilesystemNotFoundError(this.name)
|
|
6937
|
+
);
|
|
6938
|
+
const meta = JSON.parse(traced.json);
|
|
6939
|
+
this.defaultBranch = meta.default_branch || "main";
|
|
6940
|
+
}
|
|
6941
|
+
return this.defaultBranch;
|
|
6942
|
+
}
|
|
6943
|
+
// -- writes -------------------------------------------------------------------
|
|
6944
|
+
/** Write one file. Returns the snapshot (version) the write produced. */
|
|
6945
|
+
async writeFile(path2, data, message) {
|
|
6946
|
+
return await this.writeFiles(/* @__PURE__ */ new Map([[path2, data]]), message);
|
|
6947
|
+
}
|
|
6948
|
+
/** Write several files (and/or delete paths) in one atomic snapshot. */
|
|
6949
|
+
async writeFiles(files, message, deletes = []) {
|
|
6950
|
+
const entries = files instanceof Map ? [...files.entries()] : Object.entries(files);
|
|
6951
|
+
if (entries.length === 0 && deletes.length === 0) {
|
|
6952
|
+
throw new FilesystemError("nothing to write: no files or deletions given");
|
|
6953
|
+
}
|
|
6954
|
+
const writes = entries.map(([path2, data]) => ({
|
|
6955
|
+
path: path2,
|
|
6956
|
+
content: toBuffer(data)
|
|
6957
|
+
}));
|
|
6958
|
+
const resolvedMessage = message || `write ${writes.length} file(s) via SDK`;
|
|
6959
|
+
return await this.publishChanges(
|
|
6960
|
+
writes,
|
|
6961
|
+
deletes,
|
|
6962
|
+
[],
|
|
6963
|
+
[],
|
|
6964
|
+
resolvedMessage
|
|
6965
|
+
);
|
|
6966
|
+
}
|
|
6967
|
+
/**
|
|
6968
|
+
* Stream one local file directly to object storage in bounded parts.
|
|
6969
|
+
* This is the memory-bounded API for multi-GiB files.
|
|
6970
|
+
*/
|
|
6971
|
+
async writeFileFromPath(path2, localPath, message) {
|
|
6972
|
+
return await this.writeFilesFromPaths(
|
|
6973
|
+
/* @__PURE__ */ new Map([[path2, localPath]]),
|
|
6974
|
+
message
|
|
6975
|
+
);
|
|
6976
|
+
}
|
|
6977
|
+
/**
|
|
6978
|
+
* Atomically publish local files without loading their complete payloads
|
|
6979
|
+
* into JavaScript or Rust memory.
|
|
6980
|
+
*/
|
|
6981
|
+
async writeFilesFromPaths(files, message) {
|
|
6982
|
+
const entries = files instanceof Map ? [...files.entries()] : Object.entries(files);
|
|
6983
|
+
if (entries.length === 0) {
|
|
6984
|
+
throw new FilesystemError("nothing to write: no local files given");
|
|
6985
|
+
}
|
|
6986
|
+
const writes = entries.map(([path2, localPath]) => ({ path: path2, localPath }));
|
|
6987
|
+
const resolvedMessage = message || `write ${writes.length} local file(s) via SDK`;
|
|
6988
|
+
const branch = await this.branch();
|
|
6989
|
+
const traced = await callNative2(
|
|
6990
|
+
() => this.native.pushFilesystemPaths(
|
|
6991
|
+
this.name,
|
|
6992
|
+
writes,
|
|
6993
|
+
resolvedMessage,
|
|
6994
|
+
branch,
|
|
6995
|
+
(0, import_node_crypto2.randomBytes)(16).toString("hex")
|
|
6996
|
+
),
|
|
6997
|
+
new FilesystemNotFoundError(this.name)
|
|
6998
|
+
);
|
|
6999
|
+
const report = JSON.parse(traced.json);
|
|
7000
|
+
const versionId = String(report.version_id ?? "");
|
|
7001
|
+
const previousVersionId = report.previous_version_id == null ? null : String(report.previous_version_id);
|
|
7002
|
+
return {
|
|
7003
|
+
versionId,
|
|
7004
|
+
previousVersionId,
|
|
7005
|
+
created: previousVersionId !== versionId,
|
|
7006
|
+
message: resolvedMessage
|
|
7007
|
+
};
|
|
7008
|
+
}
|
|
7009
|
+
/**
|
|
7010
|
+
* Move a file or directory without downloading or re-uploading its bytes.
|
|
7011
|
+
* The source removal and destination creation publish as one atomic snapshot.
|
|
7012
|
+
*/
|
|
7013
|
+
async moveFile(from, to, message) {
|
|
7014
|
+
return await this.publishChanges(
|
|
7015
|
+
[],
|
|
7016
|
+
[],
|
|
7017
|
+
[{ from, to }],
|
|
7018
|
+
[],
|
|
7019
|
+
message || `move ${from} to ${to} via SDK`
|
|
7020
|
+
);
|
|
7021
|
+
}
|
|
7022
|
+
/**
|
|
7023
|
+
* Copy a file or directory by reusing immutable content references. No
|
|
7024
|
+
* payload bytes traverse the client, API server, or object store.
|
|
7025
|
+
*/
|
|
7026
|
+
async copyFile(from, to, message) {
|
|
7027
|
+
return await this.publishChanges(
|
|
7028
|
+
[],
|
|
7029
|
+
[],
|
|
7030
|
+
[],
|
|
7031
|
+
[{ from, to }],
|
|
7032
|
+
message || `copy ${from} to ${to} via SDK`
|
|
7033
|
+
);
|
|
7034
|
+
}
|
|
7035
|
+
async publishChanges(writes, deletes, moves, copies, message) {
|
|
7036
|
+
const branch = await this.branch();
|
|
7037
|
+
const traced = await callNative2(
|
|
7038
|
+
() => this.native.pushFilesystemFiles(
|
|
7039
|
+
this.name,
|
|
7040
|
+
writes,
|
|
7041
|
+
deletes,
|
|
7042
|
+
moves,
|
|
7043
|
+
copies,
|
|
7044
|
+
message,
|
|
7045
|
+
branch,
|
|
7046
|
+
// One key per logical write: a retried submit reattaches to the
|
|
7047
|
+
// same durable commit job instead of double-committing.
|
|
7048
|
+
(0, import_node_crypto2.randomBytes)(16).toString("hex")
|
|
7049
|
+
),
|
|
7050
|
+
new FilesystemNotFoundError(this.name)
|
|
7051
|
+
);
|
|
7052
|
+
const report = JSON.parse(traced.json);
|
|
7053
|
+
const versionId = String(report.version_id ?? "");
|
|
7054
|
+
const previousVersionId = report.previous_version_id == null ? null : String(report.previous_version_id);
|
|
7055
|
+
return {
|
|
7056
|
+
versionId,
|
|
7057
|
+
previousVersionId,
|
|
7058
|
+
created: previousVersionId !== versionId,
|
|
7059
|
+
message
|
|
7060
|
+
};
|
|
7061
|
+
}
|
|
7062
|
+
/** Delete one file. Returns the durable version the deletion produced. */
|
|
7063
|
+
async deleteFile(path2, message) {
|
|
7064
|
+
return await this.writeFiles(
|
|
7065
|
+
/* @__PURE__ */ new Map(),
|
|
7066
|
+
message || `delete ${path2} via SDK`,
|
|
7067
|
+
[path2]
|
|
7068
|
+
);
|
|
7069
|
+
}
|
|
7070
|
+
/**
|
|
7071
|
+
* Return the filesystem's current version as a snapshot.
|
|
7072
|
+
*
|
|
7073
|
+
* Writes publish durable live versions. This retains the current head
|
|
7074
|
+
* permanently in one metadata-only server operation without changing or
|
|
7075
|
+
* copying any content.
|
|
7076
|
+
*/
|
|
7077
|
+
async snapshot(message = "snapshot via SDK") {
|
|
7078
|
+
const resolvedMessage = message || "snapshot via SDK";
|
|
7079
|
+
const traced = await callNative2(
|
|
7080
|
+
() => this.native.retainFilesystemSnapshot(
|
|
7081
|
+
this.name,
|
|
7082
|
+
resolvedMessage,
|
|
7083
|
+
(0, import_node_crypto2.randomBytes)(16).toString("hex")
|
|
7084
|
+
),
|
|
7085
|
+
new FilesystemNotFoundError(this.name)
|
|
7086
|
+
);
|
|
7087
|
+
const retained = JSON.parse(traced.json);
|
|
7088
|
+
return {
|
|
7089
|
+
id: String(retained.snapshot_id ?? ""),
|
|
7090
|
+
message: retained.message || resolvedMessage
|
|
7091
|
+
};
|
|
7092
|
+
}
|
|
7093
|
+
/** List explicitly retained snapshots without reading filesystem contents. */
|
|
7094
|
+
async listSnapshots() {
|
|
7095
|
+
const traced = await callNative2(
|
|
7096
|
+
() => this.native.listFilesystemSnapshots(this.name),
|
|
7097
|
+
new FilesystemNotFoundError(this.name)
|
|
7098
|
+
);
|
|
7099
|
+
const payload = JSON.parse(traced.json);
|
|
7100
|
+
return (payload.snapshots ?? []).map((snapshot) => ({
|
|
7101
|
+
id: String(snapshot.snapshot_id ?? ""),
|
|
7102
|
+
createdAt: new Date(Number(snapshot.created_at_ms ?? 0)),
|
|
7103
|
+
message: String(snapshot.message ?? "")
|
|
7104
|
+
}));
|
|
7105
|
+
}
|
|
7106
|
+
/** Delete one permanent retention point; shared/head-reachable bytes remain. */
|
|
7107
|
+
async deleteSnapshot(snapshot) {
|
|
7108
|
+
await callNative2(
|
|
7109
|
+
() => this.native.deleteFilesystemSnapshot(this.name, snapshot),
|
|
7110
|
+
new FilesystemNotFoundError(this.name)
|
|
7111
|
+
);
|
|
7112
|
+
}
|
|
7113
|
+
// -- reads --------------------------------------------------------------------
|
|
7114
|
+
/**
|
|
7115
|
+
* Read a file's bytes at `version` (the current `main` head or a retained
|
|
7116
|
+
* snapshot id; defaults to the filesystem's current head).
|
|
7117
|
+
*/
|
|
7118
|
+
async readFile(path2, version) {
|
|
7119
|
+
return (await this.readFileWithMetadata(path2, { version })).data;
|
|
7120
|
+
}
|
|
7121
|
+
/**
|
|
7122
|
+
* Read bytes and immutable metadata in one request. A range is executed by
|
|
7123
|
+
* Artifact Storage, so callers never download the unrequested remainder.
|
|
7124
|
+
*/
|
|
7125
|
+
async readFileWithMetadata(path2, options = {}) {
|
|
7126
|
+
if (trimSlashes(path2) === "") {
|
|
7127
|
+
throw new FilesystemError("file path must not be empty");
|
|
7128
|
+
}
|
|
7129
|
+
const range = options.range;
|
|
7130
|
+
if (range !== void 0 && (!Number.isSafeInteger(range.offset) || range.offset < 0 || !Number.isSafeInteger(range.length) || range.length <= 0)) {
|
|
7131
|
+
throw new FilesystemError(
|
|
7132
|
+
"range requires a non-negative integer offset and positive integer length"
|
|
7133
|
+
);
|
|
7134
|
+
}
|
|
7135
|
+
const resolvedVersion = options.version || await this.branch();
|
|
7136
|
+
const traced = await callNative2(
|
|
7137
|
+
() => this.native.readFilesystemFile(
|
|
7138
|
+
this.name,
|
|
7139
|
+
path2,
|
|
7140
|
+
resolvedVersion,
|
|
7141
|
+
range?.offset,
|
|
7142
|
+
range?.length
|
|
7143
|
+
),
|
|
7144
|
+
new FileNotFoundInFilesystemError(this.name, path2)
|
|
7145
|
+
);
|
|
7146
|
+
const fullSize = traced.fullSize;
|
|
7147
|
+
if (!traced.contentId || fullSize === void 0 || !Number.isSafeInteger(fullSize) || fullSize < 0 || traced.data.byteLength > fullSize) {
|
|
7148
|
+
throw new FilesystemError(
|
|
7149
|
+
"filesystem read response omitted content identity or total size"
|
|
7150
|
+
);
|
|
7151
|
+
}
|
|
7152
|
+
return {
|
|
7153
|
+
data: new Uint8Array(traced.data),
|
|
7154
|
+
contentId: traced.contentId,
|
|
7155
|
+
size: fullSize
|
|
7156
|
+
};
|
|
7157
|
+
}
|
|
7158
|
+
/** Read a file as UTF-8 text at `version`. Throws on non-UTF-8 content. */
|
|
7159
|
+
async readText(path2, version) {
|
|
7160
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
|
7161
|
+
await this.readFile(path2, version)
|
|
7162
|
+
);
|
|
7163
|
+
}
|
|
7164
|
+
/** List one directory (non-recursive) at `version`. */
|
|
7165
|
+
async listFiles(dirPath = "", version) {
|
|
7166
|
+
const resolvedVersion = version || await this.branch();
|
|
7167
|
+
const traced = await callNative2(
|
|
7168
|
+
() => this.native.listFilesystemTree(this.name, dirPath, resolvedVersion),
|
|
7169
|
+
new FileNotFoundInFilesystemError(this.name, dirPath || "/")
|
|
7170
|
+
);
|
|
7171
|
+
const page = JSON.parse(traced.json);
|
|
7172
|
+
return (page.entries ?? []).map((entry) => fileEntryFromWire(entry, dirPath));
|
|
7173
|
+
}
|
|
7174
|
+
// -- status -------------------------------------------------------------------
|
|
7175
|
+
/** Remote status: identity plus the current native version. */
|
|
7176
|
+
async status() {
|
|
7177
|
+
const metaTraced = await callNative2(
|
|
7178
|
+
() => this.native.filesystemMeta(this.name),
|
|
7179
|
+
new FilesystemNotFoundError(this.name)
|
|
7180
|
+
);
|
|
7181
|
+
const meta = JSON.parse(metaTraced.json);
|
|
7182
|
+
const defaultBranch = String(meta.default_branch || "main");
|
|
7183
|
+
this.defaultBranch = defaultBranch;
|
|
7184
|
+
let versionId = null;
|
|
7185
|
+
let generation = null;
|
|
7186
|
+
try {
|
|
7187
|
+
const refTraced = await callNative2(
|
|
7188
|
+
() => this.native.filesystemRefStatus(this.name, defaultBranch)
|
|
7189
|
+
);
|
|
7190
|
+
const ref = JSON.parse(refTraced.json);
|
|
7191
|
+
versionId = ref.resolved_commit || ref.oid || null;
|
|
7192
|
+
generation = typeof ref.generation === "number" ? ref.generation : null;
|
|
7193
|
+
} catch (error) {
|
|
7194
|
+
if (!(error instanceof FilesystemAPIError) || error.statusCode !== 404) {
|
|
7195
|
+
throw error;
|
|
7196
|
+
}
|
|
7197
|
+
}
|
|
7198
|
+
return {
|
|
7199
|
+
name: this.name,
|
|
7200
|
+
status: String(meta.status ?? ""),
|
|
7201
|
+
versionId,
|
|
7202
|
+
generation
|
|
7203
|
+
};
|
|
7204
|
+
}
|
|
7205
|
+
// -- mounts -------------------------------------------------------------------
|
|
7206
|
+
/** Mount this filesystem to a local path (requires the `tl` CLI). */
|
|
7207
|
+
async mount(localPath, readonly = false) {
|
|
7208
|
+
return await this.client.mount(this.name, localPath, readonly);
|
|
7209
|
+
}
|
|
7210
|
+
};
|
|
7211
|
+
var FilesystemMount = class {
|
|
7212
|
+
client;
|
|
7213
|
+
cli;
|
|
7214
|
+
filesystem;
|
|
7215
|
+
path;
|
|
7216
|
+
readonly;
|
|
7217
|
+
/** @internal — obtain instances via `FilesystemClient.mount()`. */
|
|
7218
|
+
constructor(client, cli, filesystem, path2, readonly) {
|
|
7219
|
+
this.client = client;
|
|
7220
|
+
this.cli = cli;
|
|
7221
|
+
this.filesystem = filesystem;
|
|
7222
|
+
this.path = path2;
|
|
7223
|
+
this.readonly = readonly;
|
|
7224
|
+
}
|
|
7225
|
+
/** Flush pending local changes into a durable snapshot. */
|
|
7226
|
+
async snapshot(message) {
|
|
7227
|
+
await this.cli.snapshot(this.path, message);
|
|
7228
|
+
}
|
|
7229
|
+
/** Local mount status as reported by the mount daemon. */
|
|
7230
|
+
async status() {
|
|
7231
|
+
return await this.client.mountStatus(this.path);
|
|
7232
|
+
}
|
|
7233
|
+
/** Unmount; `discard: true` drops changes not yet uploaded. */
|
|
7234
|
+
async unmount(discard = false) {
|
|
7235
|
+
await this.client.unmount(this.path, discard);
|
|
7236
|
+
}
|
|
7237
|
+
};
|
|
7238
|
+
|
|
6545
7239
|
// src/index.ts
|
|
6546
7240
|
init_image();
|
|
6547
7241
|
init_url();
|
|
@@ -6550,11 +7244,20 @@ init_models();
|
|
|
6550
7244
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6551
7245
|
0 && (module.exports = {
|
|
6552
7246
|
APIClient,
|
|
7247
|
+
CliNotFoundError,
|
|
6553
7248
|
CloudClient,
|
|
6554
7249
|
ContainerState,
|
|
6555
7250
|
Desktop,
|
|
7251
|
+
FileNotFoundInFilesystemError,
|
|
7252
|
+
Filesystem,
|
|
7253
|
+
FilesystemAPIError,
|
|
7254
|
+
FilesystemClient,
|
|
7255
|
+
FilesystemError,
|
|
7256
|
+
FilesystemMount,
|
|
7257
|
+
FilesystemNotFoundError,
|
|
6556
7258
|
Image,
|
|
6557
7259
|
ImageBuildOperationType,
|
|
7260
|
+
MountError,
|
|
6558
7261
|
OutputMode,
|
|
6559
7262
|
PoolInUseError,
|
|
6560
7263
|
PoolNotFoundError,
|