@jini-ai/platform 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/asset-cache.d.ts +12 -1
- package/dist/asset-cache.js +9 -5
- package/dist/child-environment.d.ts +18 -0
- package/dist/child-environment.js +26 -0
- package/dist/fs/boot-identity.d.ts +10 -0
- package/dist/fs/boot-identity.js +34 -0
- package/dist/fs/directory-home.d.ts +15 -0
- package/dist/fs/directory-home.js +17 -0
- package/dist/fs/directory-registry.d.ts +50 -0
- package/dist/fs/directory-registry.js +99 -0
- package/dist/fs/directory-running-lock.d.ts +43 -0
- package/dist/fs/directory-running-lock.js +66 -0
- package/dist/fs/directory-sync.d.ts +20 -0
- package/dist/fs/directory-sync.js +87 -0
- package/dist/fs/directory-user-config.d.ts +79 -0
- package/dist/fs/directory-user-config.js +183 -0
- package/dist/fs/directory-workspace.d.ts +132 -0
- package/dist/fs/directory-workspace.js +266 -0
- package/dist/fs/durable-json.d.ts +21 -0
- package/dist/fs/durable-json.js +59 -4
- package/dist/fs/file-lock.d.ts +3 -0
- package/dist/fs/file-lock.js +16 -7
- package/dist/fs/index.d.ts +6 -0
- package/dist/fs/index.js +6 -0
- package/dist/fs/strict-containment.d.ts +1 -1
- package/dist/fs/strict-containment.js +1 -1
- package/dist/fs/user-data-directories.d.ts +28 -0
- package/dist/fs/user-data-directories.js +53 -0
- package/dist/fs/verified-directory-move.d.ts +37 -0
- package/dist/fs/verified-directory-move.js +138 -0
- package/dist/fs/verified-file-relocation.d.ts +27 -0
- package/dist/fs/verified-file-relocation.js +115 -0
- package/dist/fs/workspace-directory.d.ts +19 -0
- package/dist/fs/workspace-directory.js +23 -0
- package/dist/http/guarded/transport.fetch.d.ts +13 -1
- package/dist/http/guarded/transport.fetch.js +9 -3
- package/dist/index.d.ts +6 -54
- package/dist/index.js +6 -54
- package/dist/net/address.d.ts +1 -8
- package/dist/net/address.js +2 -89
- package/dist/net/endpoint-policy.d.ts +9 -0
- package/dist/net/endpoint-policy.js +98 -0
- package/dist/secrets/credential-sets/index.d.ts +5 -0
- package/dist/secrets/credential-sets/index.js +5 -0
- package/dist/secrets/credential-sets/repo.memory.d.ts +18 -10
- package/dist/secrets/credential-sets/repo.memory.js +18 -10
- package/dist/secrets/credential-sets/vault.aad.d.ts +4 -0
- package/dist/secrets/credential-sets/vault.aad.js +8 -0
- package/dist/secrets/credential-sets/vault.meta.d.ts +10 -0
- package/dist/secrets/credential-sets/vault.meta.js +52 -0
- package/dist/secrets/credential-sets/vault.repo.memory.d.ts +51 -0
- package/dist/secrets/credential-sets/vault.repo.memory.js +148 -0
- package/dist/secrets/credential-sets/vault.service.d.ts +130 -0
- package/dist/secrets/credential-sets/vault.service.js +310 -0
- package/dist/secrets/credential-sets/vault.types.d.ts +150 -0
- package/dist/secrets/credential-sets/vault.types.js +28 -0
- package/dist/secrets/credential-token.d.ts +64 -0
- package/dist/secrets/credential-token.js +75 -0
- package/dist/secrets/index.d.ts +1 -0
- package/dist/secrets/index.js +1 -0
- package/dist/secrets/secret-sealer.aesgcm.d.ts +6 -0
- package/dist/secrets/secret-sealer.aesgcm.js +9 -2
- package/dist/web/html-document.d.ts +70 -0
- package/dist/web/html-document.js +268 -0
- package/dist/web/html-resource.d.ts +15 -0
- package/dist/web/html-resource.js +12 -0
- package/dist/web/html-to-markdown.d.ts +11 -0
- package/dist/web/html-to-markdown.js +245 -0
- package/dist/web/index.d.ts +5 -0
- package/dist/web/index.js +5 -0
- package/dist/web/web-page.d.ts +157 -0
- package/dist/web/web-page.js +310 -0
- package/package.json +26 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { stageVerifiedFileRelocation } from "./verified-file-relocation.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
/** Fingerprint relative names, entry kinds, symlink targets and file bytes without following links.
|
|
6
|
+
* This is verification, never a log of file contents. Lease files are excluded by the host.
|
|
7
|
+
* @complexity O(bytes + entries log entries), O(entries + a fixed 64 KiB read buffer) space.
|
|
8
|
+
*/
|
|
9
|
+
export function directoryFingerprint({ dir }, { excluded = [], filesystem = fs } = {}) {
|
|
10
|
+
const digest = createHash("sha256");
|
|
11
|
+
function visit(relative) {
|
|
12
|
+
for (const entry of filesystem.readdirSync(path.join(dir, relative), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13
|
+
if (!relative && excluded.includes(entry.name))
|
|
14
|
+
continue;
|
|
15
|
+
const name = path.join(relative, entry.name), absolute = path.join(dir, name);
|
|
16
|
+
const stat = filesystem.lstatSync(absolute);
|
|
17
|
+
const kind = stat.isSymbolicLink() ? "link" : stat.isDirectory() ? "directory" : stat.isFile() ? "file" : "unsupported";
|
|
18
|
+
if (kind === "unsupported")
|
|
19
|
+
throw new Error(`Cannot move special filesystem entry: ${name}`);
|
|
20
|
+
digest.update(JSON.stringify([name, kind, stat.mode & 0o777]));
|
|
21
|
+
if (kind === "directory")
|
|
22
|
+
visit(name);
|
|
23
|
+
else if (kind === "link")
|
|
24
|
+
digest.update(JSON.stringify(filesystem.readlinkSync(absolute)));
|
|
25
|
+
else {
|
|
26
|
+
const fileDigest = createHash("sha256"), buffer = Buffer.allocUnsafe(64 * 1024);
|
|
27
|
+
const descriptor = filesystem.openSync(absolute, "r");
|
|
28
|
+
try {
|
|
29
|
+
let bytes;
|
|
30
|
+
while ((bytes = filesystem.readSync(descriptor, buffer, 0, buffer.length, null)) > 0)
|
|
31
|
+
fileDigest.update(buffer.subarray(0, bytes));
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
filesystem.closeSync(descriptor);
|
|
35
|
+
}
|
|
36
|
+
digest.update(fileDigest.digest());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
visit("");
|
|
41
|
+
return digest.digest("hex");
|
|
42
|
+
}
|
|
43
|
+
/** Move only into an absent target. Verify before registry commit or source deletion.
|
|
44
|
+
* EXDEV uses copy/verify/retire/commit; failure restores the source and removes only our new copy.
|
|
45
|
+
* Same-filesystem failures roll the rename back. Commit must atomically update the host registry.
|
|
46
|
+
* @complexity O(bytes + entries log entries), with two verification passes.
|
|
47
|
+
*/
|
|
48
|
+
export function moveDirectoryVerified({ from, to, commit }, { excluded = [], filesystem = fs, prepareDestination, relocateFiles, verifyFile, backupId = randomUUID } = {}) {
|
|
49
|
+
const source = filesystem.realpathSync(from);
|
|
50
|
+
const target = path.resolve(to);
|
|
51
|
+
if (target === source || target.startsWith(source + path.sep))
|
|
52
|
+
throw new Error("A directory cannot be moved into itself.");
|
|
53
|
+
try {
|
|
54
|
+
filesystem.lstatSync(target);
|
|
55
|
+
throw new Error(`Move target already exists: ${target}`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.code !== "ENOENT")
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
const before = directoryFingerprint({ dir: source }, { excluded, filesystem });
|
|
62
|
+
let copied = false;
|
|
63
|
+
try {
|
|
64
|
+
filesystem.renameSync(source, target);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code !== "EXDEV")
|
|
68
|
+
throw error;
|
|
69
|
+
filesystem.mkdirSync(target);
|
|
70
|
+
copied = true;
|
|
71
|
+
try {
|
|
72
|
+
// The host may reserve the empty destination before any commit marker or database is copied.
|
|
73
|
+
prepareDestination?.({ path: target });
|
|
74
|
+
filesystem.cpSync(source, target, { recursive: true, force: false, errorOnExist: true, preserveTimestamps: true, verbatimSymlinks: true,
|
|
75
|
+
filter: (candidate) => candidate === source || !excluded.includes(path.relative(source, candidate).split(path.sep)[0]),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (failure) {
|
|
79
|
+
filesystem.rmSync(target, { recursive: true });
|
|
80
|
+
throw failure;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
let retiredSource;
|
|
84
|
+
let staged;
|
|
85
|
+
try {
|
|
86
|
+
if (directoryFingerprint({ dir: target }, { excluded, filesystem }) !== before)
|
|
87
|
+
throw new Error("Moved directory verification failed.");
|
|
88
|
+
if (relocateFiles) {
|
|
89
|
+
if (!verifyFile)
|
|
90
|
+
throw new Error("File relocation requires a verification port.");
|
|
91
|
+
staged = stageVerifiedFileRelocation({ groups: relocateFiles({ source, target }), verify: verifyFile }, { filesystem, backupId, backupCovered: ({ from }) => copied && path.resolve(from).startsWith(target + path.sep) });
|
|
92
|
+
}
|
|
93
|
+
if (copied) {
|
|
94
|
+
const backupRoot = path.join(path.dirname(source), `.relocation-${backupId()}-backup`);
|
|
95
|
+
const backup = path.join(backupRoot, path.basename(source));
|
|
96
|
+
if (filesystem.existsSync(backup))
|
|
97
|
+
throw new Error(`Move backup target already exists: ${backup}`);
|
|
98
|
+
// The EXDEV original becomes a recovery folder before catalog commit, never a second live
|
|
99
|
+
// site at the old path if cleanup fails. An interrupted apply leaves explicit backups.
|
|
100
|
+
filesystem.mkdirSync(backupRoot);
|
|
101
|
+
filesystem.renameSync(source, backup);
|
|
102
|
+
retiredSource = backup;
|
|
103
|
+
const fd = filesystem.openSync(backupRoot, "r");
|
|
104
|
+
try {
|
|
105
|
+
filesystem.fsyncSync(fd);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
filesystem.closeSync(fd);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Persist directory-name changes for same-filesystem renames as well as EXDEV retirement.
|
|
112
|
+
for (const directory of new Set([path.dirname(source), path.dirname(target)])) {
|
|
113
|
+
const fd = filesystem.openSync(directory, "r");
|
|
114
|
+
try {
|
|
115
|
+
filesystem.fsyncSync(fd);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
filesystem.closeSync(fd);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
commit();
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (retiredSource)
|
|
125
|
+
filesystem.renameSync(retiredSource, source);
|
|
126
|
+
staged?.rollback();
|
|
127
|
+
if (copied)
|
|
128
|
+
filesystem.rmSync(target, { recursive: true });
|
|
129
|
+
else
|
|
130
|
+
filesystem.renameSync(target, source);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
// If deletion fails after commit, the verified target and registry remain usable; old DB live
|
|
134
|
+
// names were already retired before commit. Never roll back a registry whose commit succeeded.
|
|
135
|
+
staged?.finalize();
|
|
136
|
+
// Keep the retired EXDEV recovery folder; it is never a registered/live site path.
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=verified-directory-move.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
export interface VerifiedFileGroup {
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
sidecars: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
/** Stage related files together while both directory identities are reserved by the host. Raw
|
|
8
|
+
* sources remain intact through inspection so even WAL/SHM bytes are retained. Verification opens only
|
|
9
|
+
* stopped databases through an injected read-only inspector; the filesystem owner never uses SQL.
|
|
10
|
+
* @complexity O(total bytes + verification cost), bounded memory via native file copies.
|
|
11
|
+
*/
|
|
12
|
+
export declare function stageVerifiedFileRelocation({ groups, verify }: {
|
|
13
|
+
groups: readonly VerifiedFileGroup[];
|
|
14
|
+
verify: (required: {
|
|
15
|
+
path: string;
|
|
16
|
+
}) => string;
|
|
17
|
+
}, { filesystem, backupCovered, backupId }?: {
|
|
18
|
+
filesystem?: typeof fs;
|
|
19
|
+
backupCovered?: (required: {
|
|
20
|
+
from: string;
|
|
21
|
+
}) => boolean;
|
|
22
|
+
backupId?: () => string;
|
|
23
|
+
}): {
|
|
24
|
+
rollback: () => void;
|
|
25
|
+
finalize: () => void;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=verified-file-relocation.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
/** Stage related files together while both directory identities are reserved by the host. Raw
|
|
5
|
+
* sources remain intact through inspection so even WAL/SHM bytes are retained. Verification opens only
|
|
6
|
+
* stopped databases through an injected read-only inspector; the filesystem owner never uses SQL.
|
|
7
|
+
* @complexity O(total bytes + verification cost), bounded memory via native file copies.
|
|
8
|
+
*/
|
|
9
|
+
export function stageVerifiedFileRelocation({ groups, verify }, { filesystem = fs, backupCovered = () => false, backupId = randomUUID } = {}) {
|
|
10
|
+
const created = [], retired = [], markers = [], redundantBackups = [];
|
|
11
|
+
const backupSuffix = `.relocation-${backupId()}-backup`;
|
|
12
|
+
const copyExclusive = (from, to) => {
|
|
13
|
+
// Reserve before copying so a disk-full failure is still ours to clean up. COPYFILE_EXCL
|
|
14
|
+
// alone may create a partial file before throwing, which cannot be tracked after the fact.
|
|
15
|
+
const fd = filesystem.openSync(to, "wx");
|
|
16
|
+
created.push(to);
|
|
17
|
+
filesystem.closeSync(fd);
|
|
18
|
+
filesystem.copyFileSync(from, to);
|
|
19
|
+
const copied = filesystem.openSync(to, "r+");
|
|
20
|
+
try {
|
|
21
|
+
filesystem.fsyncSync(copied);
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
filesystem.closeSync(copied);
|
|
25
|
+
}
|
|
26
|
+
syncParent(to);
|
|
27
|
+
};
|
|
28
|
+
const syncParent = (file) => {
|
|
29
|
+
// Directory fsync persists retirement/marker ordering before the host commits its catalog.
|
|
30
|
+
const fd = filesystem.openSync(path.dirname(file), "r");
|
|
31
|
+
try {
|
|
32
|
+
filesystem.fsyncSync(fd);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
filesystem.closeSync(fd);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const rollback = () => {
|
|
39
|
+
for (const item of [...retired].reverse()) {
|
|
40
|
+
filesystem.renameSync(item.backup, item.from);
|
|
41
|
+
syncParent(item.from);
|
|
42
|
+
}
|
|
43
|
+
for (const file of [...created].reverse()) {
|
|
44
|
+
filesystem.rmSync(file, { force: true });
|
|
45
|
+
syncParent(file);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
try {
|
|
49
|
+
for (const group of groups) {
|
|
50
|
+
const pairs = ["", ...group.sidecars].filter((suffix) => filesystem.existsSync(group.from + suffix))
|
|
51
|
+
.map((suffix) => ({ from: group.from + suffix, to: group.to + suffix }));
|
|
52
|
+
for (const pair of pairs)
|
|
53
|
+
if (!filesystem.lstatSync(pair.from).isFile())
|
|
54
|
+
throw new Error(`Database move requires a regular file: ${pair.from}`);
|
|
55
|
+
filesystem.mkdirSync(path.dirname(group.to), { recursive: true });
|
|
56
|
+
const moving = path.resolve(group.from) !== path.resolve(group.to);
|
|
57
|
+
// A directory rename already carries unchanged DB paths atomically; verify those bytes
|
|
58
|
+
// without copying them. For a file move the original inode becomes the sole backup after
|
|
59
|
+
// verification: another full destination backup doubles storage without rollback benefit.
|
|
60
|
+
// Cross-volume directory moves already retain the untouched original directory. A copied
|
|
61
|
+
// DB inside that destination needs no second full backup of the same bytes.
|
|
62
|
+
if (moving) {
|
|
63
|
+
const marker = group.from + ".home2-relocation.json";
|
|
64
|
+
const fd = filesystem.openSync(marker, "wx");
|
|
65
|
+
created.push(marker);
|
|
66
|
+
markers.push(marker);
|
|
67
|
+
try {
|
|
68
|
+
filesystem.writeFileSync(fd, JSON.stringify({ to: group.to, backup: group.from + backupSuffix }));
|
|
69
|
+
filesystem.fsyncSync(fd);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
filesystem.closeSync(fd);
|
|
73
|
+
}
|
|
74
|
+
syncParent(marker);
|
|
75
|
+
}
|
|
76
|
+
const before = verify({ path: group.from });
|
|
77
|
+
for (const pair of pairs) {
|
|
78
|
+
if (path.resolve(pair.from) === path.resolve(pair.to))
|
|
79
|
+
continue;
|
|
80
|
+
// A destination collision aborts; never overwrite another site's data or an old move.
|
|
81
|
+
copyExclusive(pair.from, pair.to);
|
|
82
|
+
}
|
|
83
|
+
if (verify({ path: group.to }) !== before)
|
|
84
|
+
throw new Error(`Database row counts changed while moving ${path.basename(group.to)}.`);
|
|
85
|
+
// Retire old live names BEFORE commit. Finalization removes recovery markers and redundant copies covered by the retained original: a crash
|
|
86
|
+
// or cleanup failure cannot leave two independently writable database names after commit.
|
|
87
|
+
if (moving)
|
|
88
|
+
for (const pair of pairs) {
|
|
89
|
+
const backup = pair.from + backupSuffix;
|
|
90
|
+
if (filesystem.existsSync(backup))
|
|
91
|
+
throw new Error(`Database backup target already exists: ${backup}`);
|
|
92
|
+
filesystem.renameSync(pair.from, backup);
|
|
93
|
+
retired.push({ from: pair.from, backup });
|
|
94
|
+
if (backupCovered({ from: pair.from }))
|
|
95
|
+
redundantBackups.push(backup);
|
|
96
|
+
syncParent(pair.from);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
rollback();
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
return { rollback, finalize: () => {
|
|
105
|
+
for (const backup of redundantBackups) {
|
|
106
|
+
filesystem.rmSync(backup);
|
|
107
|
+
syncParent(backup);
|
|
108
|
+
}
|
|
109
|
+
for (const marker of markers) {
|
|
110
|
+
filesystem.rmSync(marker);
|
|
111
|
+
syncParent(marker);
|
|
112
|
+
}
|
|
113
|
+
} };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=verified-file-relocation.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DirectoryRecord } from "./directory-registry.js";
|
|
2
|
+
/** Shared selection policy for CLI/dev hosts. A registry ID disambiguates adopted paths;
|
|
3
|
+
* a basename selects the home first. No initialized home site preserves the supplied fallback.
|
|
4
|
+
* @complexity O(n) registered/child directories and validation probes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function selectWorkspaceDirectory({ home, entries, fallback, selected, roots, listDirectories, isInitialized }: {
|
|
7
|
+
home: string;
|
|
8
|
+
entries: readonly DirectoryRecord[];
|
|
9
|
+
fallback: string;
|
|
10
|
+
selected?: string | undefined;
|
|
11
|
+
roots?: readonly string[];
|
|
12
|
+
listDirectories: (required: {
|
|
13
|
+
root: string;
|
|
14
|
+
}) => readonly string[];
|
|
15
|
+
isInitialized: (required: {
|
|
16
|
+
path: string;
|
|
17
|
+
}) => boolean;
|
|
18
|
+
}, _options?: {}): string;
|
|
19
|
+
//# sourceMappingURL=workspace-directory.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/** Shared selection policy for CLI/dev hosts. A registry ID disambiguates adopted paths;
|
|
3
|
+
* a basename selects the home first. No initialized home site preserves the supplied fallback.
|
|
4
|
+
* @complexity O(n) registered/child directories and validation probes.
|
|
5
|
+
*/
|
|
6
|
+
export function selectWorkspaceDirectory({ home, entries, fallback, selected, roots = [home], listDirectories, isInitialized }, _options = {}) {
|
|
7
|
+
if (selected !== undefined) {
|
|
8
|
+
const byId = entries.find((entry) => entry.id === selected);
|
|
9
|
+
if (byId)
|
|
10
|
+
return byId.path;
|
|
11
|
+
const inHome = path.join(home, selected);
|
|
12
|
+
if (isInitialized({ path: inHome }))
|
|
13
|
+
return inHome;
|
|
14
|
+
const matches = [...new Set([...entries.filter((entry) => path.basename(entry.path) === selected).map((entry) => entry.path),
|
|
15
|
+
...roots.filter((root) => root !== home).map((root) => path.join(root, selected)).filter((dir) => isInitialized({ path: dir }))])];
|
|
16
|
+
if (matches.length > 1)
|
|
17
|
+
throw new Error("More than one registered directory has this name. Select its registry ID.");
|
|
18
|
+
return matches[0] ?? path.join(path.dirname(fallback), selected);
|
|
19
|
+
}
|
|
20
|
+
const candidates = [...entries.filter((entry) => path.dirname(entry.path) === home).sort((a, b) => (b.lastOpened ? Date.parse(b.lastOpened) : 0) - (a.lastOpened ? Date.parse(a.lastOpened) : 0)).map((entry) => entry.path), ...roots.flatMap((root) => listDirectories({ root }))];
|
|
21
|
+
return candidates.find((dir) => isInitialized({ path: dir })) ?? fallback;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=workspace-directory.js.map
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
import { type ClientRequest, type IncomingMessage } from "node:http";
|
|
2
|
+
import { type RequestOptions } from "node:https";
|
|
1
3
|
import type { HttpRequest, HttpResponse } from "@jini-ai/core/primitives";
|
|
2
4
|
import type { PinnedPeer } from "./types.js";
|
|
3
5
|
import type { HttpTransportAdapter } from "./ports.js";
|
|
6
|
+
/** Native request effect port; peer pinning and TLS options remain owned by this adapter. */
|
|
7
|
+
export interface NativeHttpTransportOptions {
|
|
8
|
+
/** Like node:http, invoke onResponse asynchronously, after returning the request handle. */
|
|
9
|
+
request?: (input: {
|
|
10
|
+
secure: boolean;
|
|
11
|
+
options: RequestOptions;
|
|
12
|
+
onResponse: (response: IncomingMessage) => void;
|
|
13
|
+
}, optional?: Record<string, never>) => ClientRequest;
|
|
14
|
+
}
|
|
4
15
|
export declare class FetchHttpTransportAdapter implements HttpTransportAdapter {
|
|
5
|
-
|
|
16
|
+
private readonly optional;
|
|
17
|
+
constructor(_required: Record<string, never>, optional?: NativeHttpTransportOptions);
|
|
6
18
|
requestPinned({ request: req, peer }: {
|
|
7
19
|
request: HttpRequest;
|
|
8
20
|
peer: PinnedPeer;
|
|
@@ -11,7 +11,10 @@ import { createBrotliDecompress, createGunzip, createInflate } from "node:zlib";
|
|
|
11
11
|
/** Last-resort decoded-byte bound independent of any caller's response policy. */
|
|
12
12
|
const ABSOLUTE_MAX_BYTES = 100 * 1024 * 1024;
|
|
13
13
|
export class FetchHttpTransportAdapter {
|
|
14
|
-
|
|
14
|
+
optional;
|
|
15
|
+
constructor(_required, optional = {}) {
|
|
16
|
+
this.optional = optional;
|
|
17
|
+
}
|
|
15
18
|
async requestPinned({ request: req, peer }) {
|
|
16
19
|
const url = new URL(req.url);
|
|
17
20
|
const isHttps = url.protocol === "https:";
|
|
@@ -31,7 +34,7 @@ export class FetchHttpTransportAdapter {
|
|
|
31
34
|
...(isHttps && peer.tlsServerName ? { servername: peer.tlsServerName } : {}),
|
|
32
35
|
};
|
|
33
36
|
return new Promise((resolve, reject) => {
|
|
34
|
-
const
|
|
37
|
+
const onResponse = (res) => {
|
|
35
38
|
// HEAD can advertise GET's compression but has no body to decompress.
|
|
36
39
|
const decoded = req.method === "HEAD" ? res : decodeBody(res.headers["content-encoding"], res);
|
|
37
40
|
const chunks = [];
|
|
@@ -74,7 +77,10 @@ export class FetchHttpTransportAdapter {
|
|
|
74
77
|
});
|
|
75
78
|
decoded.on("end", () => finish(false));
|
|
76
79
|
decoded.on("error", reject);
|
|
77
|
-
}
|
|
80
|
+
};
|
|
81
|
+
const clientRequest = this.optional.request
|
|
82
|
+
? this.optional.request({ secure: isHttps, options, onResponse }, {})
|
|
83
|
+
: requestFn(options, onResponse);
|
|
78
84
|
clientRequest.on("timeout", () => {
|
|
79
85
|
clientRequest.destroy(new Error(`request timed out after ${idleTimeoutMs}ms`));
|
|
80
86
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - `fs` — filesystem containment, atomic copy, removal, log tails.
|
|
11
11
|
* - `http` — HTTP readiness polling.
|
|
12
12
|
* - `fetch-with-timeout` — the shared `AbortSignal`-timeout wrapper every outbound `fetch()` call
|
|
13
|
-
* in this monorepo is meant to go through
|
|
13
|
+
* in this monorepo is meant to go through.
|
|
14
14
|
* - `toolchain` — user-level toolchain bin discovery.
|
|
15
15
|
* - `asset-cache` — SSRF-safe same-origin cache/proxy for sandboxed content's
|
|
16
16
|
* external media references.
|
|
@@ -26,62 +26,14 @@
|
|
|
26
26
|
* - `blob-storage` — a backend-agnostic blob storage port + local-disk and
|
|
27
27
|
* S3-compatible implementations.
|
|
28
28
|
*
|
|
29
|
-
* The set of names exported here is intentionally identical to the pre-split
|
|
30
|
-
* public surface; importers see no change.
|
|
31
29
|
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* The task brief asked to verify the recon's "leans OD" flag on
|
|
36
|
-
* `project-storage.ts` firsthand and either drop it or extract a generic core
|
|
37
|
-
* with OD's project-specific parts as an adapter, "depending on what you
|
|
38
|
-
* actually find." Read in full: the `ProjectStorage` interface,
|
|
39
|
-
* `LocalProjectStorage`, and `S3ProjectStorage` classes carry no OD nouns in
|
|
40
|
-
* their own logic — they're a backend-agnostic blob CRUD port (read/write/
|
|
41
|
-
* list/delete/stat under a scoping key) plus a local-disk and an
|
|
42
|
-
* S3-compatible implementation, both traversal-guarded. The **only** OD
|
|
43
|
-
* coupling in the file is the bottom `resolveProjectStorage()` factory
|
|
44
|
-
* function, which reads `OD_PROJECT_STORAGE`/`OD_S3_BUCKET`/`OD_S3_REGION`/
|
|
45
|
-
* `OD_S3_PREFIX`/`OD_S3_ENDPOINT`/`OD_S3_ACCESS_KEY_ID`/
|
|
46
|
-
* `OD_S3_SECRET_ACCESS_KEY`/`OD_S3_SESSION_TOKEN` directly from `process.env`.
|
|
47
|
-
*
|
|
48
|
-
* **Extracted, not dropped:** the interface + both implementations, ported to
|
|
49
|
-
* `src/blob-storage.ts` as `BlobStorage`/`BlobFileMeta`/`LocalBlobStorage`/
|
|
50
|
-
* `S3BlobStorage`/`S3BlobStorageOptions`/`StorageError`, with every
|
|
51
|
-
* `projectId: string` parameter renamed to `namespace: string` — per
|
|
52
|
-
* extraction-plan.md's Task-4 Port-2 finding ("OD's 'project' is a product
|
|
53
|
-
* model; engine needs a generic workspace/session store interface") and §2.1's
|
|
54
|
-
* "Runs key on an opaque `contextRef`, never `projectId`" convention, "project"
|
|
55
|
-
* specifically is flagged elsewhere in this porting effort as OD's noun, not
|
|
56
|
-
* the engine's — `namespace` carries no domain meaning here beyond "a
|
|
57
|
-
* top-level grouping/scoping key," which is what the parameter actually is
|
|
58
|
-
* structurally (nothing in `LocalBlobStorage`/`S3BlobStorage`'s logic assumes
|
|
59
|
-
* it means a design project).
|
|
60
|
-
*
|
|
61
|
-
* **Dropped, not ported:** `resolveProjectStorage()`. This is OD adapter
|
|
62
|
-
* wiring — a specific env-var-naming convention and the choice to read
|
|
63
|
-
* `process.env` directly inside the engine layer — not generic engine
|
|
64
|
-
* behavior. It has no equivalent anywhere else in this porting session's
|
|
65
|
-
* established pattern (`createSqliteEventLog`, `LocalBlobStorage`,
|
|
66
|
-
* `S3BlobStorage` are all called with explicit constructor arguments; none of
|
|
67
|
-
* `@jini/*`'s existing adapters read `process.env` inside their own
|
|
68
|
-
* constructors). A Jini host application composes its own equivalent
|
|
69
|
-
* (`new LocalBlobStorage(root)` or `new S3BlobStorage({ bucket, region,
|
|
70
|
-
* credentials, ... })` called directly, with whatever env-var convention that
|
|
71
|
-
* host prefers) rather than inheriting OD's `OD_S3_*` names or its
|
|
72
|
-
* env-selection shape.
|
|
73
|
-
*
|
|
74
|
-
* Landed in `@jini/platform`, not `@jini/sqlite`: this is a filesystem/network
|
|
75
|
-
* blob-storage primitive with no SQL involvement, parallel to this package's
|
|
76
|
-
* existing `fs.ts` (path containment / atomic copy) and `http.ts` (readiness
|
|
77
|
-
* polling) roles — `@jini/sqlite` is reserved for the `EventLog` port adapter
|
|
78
|
-
* and `better-sqlite3`-specific helpers (`db-inspect.ts`,
|
|
79
|
-
* `backend-config.ts`), none of which this needs. `aws-sigv4.ts` lives
|
|
80
|
-
* alongside it in the same package since it's `S3BlobStorage`'s only
|
|
81
|
-
* dependency and has no other consumer yet.
|
|
30
|
+
* Blob storage uses opaque namespace scoping rather than a product's project model. Hosts supply
|
|
31
|
+
* configuration/credentials explicitly instead of inheriting product-prefixed environment names.
|
|
32
|
+
* Filesystem/network storage and its SigV4 signing belong here, with no kernel SQL-port dependency.
|
|
82
33
|
*/
|
|
83
34
|
export type { CommandInvocation, CommandInvocationRequest } from "./command.js";
|
|
84
35
|
export { createCommandInvocation, createPackageManagerInvocation } from "./command.js";
|
|
36
|
+
export { omitEnvironmentKeys } from "./child-environment.js";
|
|
85
37
|
export type { ResolveSystemProxyEnvOptions, SystemProxyCommandRunner } from "./proxy-env.js";
|
|
86
38
|
export { mergeProxyAwareEnv, parseMacosScutilProxyOutput, parseWindowsInternetSettingsProxyOutput, resolveSystemProxyEnv, } from "./proxy-env.js";
|
|
87
39
|
export type { ProcessSnapshot, ProcessStampContract, ProcessStampField, ProcessStampShape, SpawnProcessRequest, StampedProcessMatchCriteria, StopProcessesResult, } from "./process.js";
|
|
@@ -97,7 +49,7 @@ export { wellKnownUserToolchainBins } from "./toolchain.js";
|
|
|
97
49
|
export type { BufferedCommandResult } from "./shell.js";
|
|
98
50
|
export { execCommandViaLoginShell, execFileBuffered } from "./shell.js";
|
|
99
51
|
export type { AssetCache, AssetCacheOptions, AssetCacheResult } from "./asset-cache.js";
|
|
100
|
-
export { AssetCacheError, assertSafePublicUrl, assetCacheKey, assetCacheRewriteUrl, createAssetCache, createValidatingLookup, isCacheableExternalUrl, } from "./asset-cache.js";
|
|
52
|
+
export { AssetCacheError, assertSafePublicUrl, assetCacheKey, assetCacheRewriteUrl, createAssetCache, createAssetCacheDispatcher, createValidatingLookup, isCacheableExternalUrl, } from "./asset-cache.js";
|
|
101
53
|
export { expandHomePrefix, resolveProjectRelativePath } from "./home-expansion.js";
|
|
102
54
|
export type { SandboxEnvConfig, SandboxRuntimeConfig, SandboxRuntimeRoots } from "./sandbox-env.js";
|
|
103
55
|
export { applySandboxRuntimeEnv, ensureSandboxRuntimeDirs, isSandboxImportedProjectRootAllowed, isSandboxModeEnabled, resolveSandboxRuntimeConfig, resolveSandboxRuntimeConfigFromEnv, sandboxAgentProfilesConfigPath, sandboxImportAllowedRoots, sandboxImportedProjectRootUnavailableReason, } from "./sandbox-env.js";
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - `fs` — filesystem containment, atomic copy, removal, log tails.
|
|
11
11
|
* - `http` — HTTP readiness polling.
|
|
12
12
|
* - `fetch-with-timeout` — the shared `AbortSignal`-timeout wrapper every outbound `fetch()` call
|
|
13
|
-
* in this monorepo is meant to go through
|
|
13
|
+
* in this monorepo is meant to go through.
|
|
14
14
|
* - `toolchain` — user-level toolchain bin discovery.
|
|
15
15
|
* - `asset-cache` — SSRF-safe same-origin cache/proxy for sandboxed content's
|
|
16
16
|
* external media references.
|
|
@@ -26,61 +26,13 @@
|
|
|
26
26
|
* - `blob-storage` — a backend-agnostic blob storage port + local-disk and
|
|
27
27
|
* S3-compatible implementations.
|
|
28
28
|
*
|
|
29
|
-
* The set of names exported here is intentionally identical to the pre-split
|
|
30
|
-
* public surface; importers see no change.
|
|
31
29
|
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* The task brief asked to verify the recon's "leans OD" flag on
|
|
36
|
-
* `project-storage.ts` firsthand and either drop it or extract a generic core
|
|
37
|
-
* with OD's project-specific parts as an adapter, "depending on what you
|
|
38
|
-
* actually find." Read in full: the `ProjectStorage` interface,
|
|
39
|
-
* `LocalProjectStorage`, and `S3ProjectStorage` classes carry no OD nouns in
|
|
40
|
-
* their own logic — they're a backend-agnostic blob CRUD port (read/write/
|
|
41
|
-
* list/delete/stat under a scoping key) plus a local-disk and an
|
|
42
|
-
* S3-compatible implementation, both traversal-guarded. The **only** OD
|
|
43
|
-
* coupling in the file is the bottom `resolveProjectStorage()` factory
|
|
44
|
-
* function, which reads `OD_PROJECT_STORAGE`/`OD_S3_BUCKET`/`OD_S3_REGION`/
|
|
45
|
-
* `OD_S3_PREFIX`/`OD_S3_ENDPOINT`/`OD_S3_ACCESS_KEY_ID`/
|
|
46
|
-
* `OD_S3_SECRET_ACCESS_KEY`/`OD_S3_SESSION_TOKEN` directly from `process.env`.
|
|
47
|
-
*
|
|
48
|
-
* **Extracted, not dropped:** the interface + both implementations, ported to
|
|
49
|
-
* `src/blob-storage.ts` as `BlobStorage`/`BlobFileMeta`/`LocalBlobStorage`/
|
|
50
|
-
* `S3BlobStorage`/`S3BlobStorageOptions`/`StorageError`, with every
|
|
51
|
-
* `projectId: string` parameter renamed to `namespace: string` — per
|
|
52
|
-
* extraction-plan.md's Task-4 Port-2 finding ("OD's 'project' is a product
|
|
53
|
-
* model; engine needs a generic workspace/session store interface") and §2.1's
|
|
54
|
-
* "Runs key on an opaque `contextRef`, never `projectId`" convention, "project"
|
|
55
|
-
* specifically is flagged elsewhere in this porting effort as OD's noun, not
|
|
56
|
-
* the engine's — `namespace` carries no domain meaning here beyond "a
|
|
57
|
-
* top-level grouping/scoping key," which is what the parameter actually is
|
|
58
|
-
* structurally (nothing in `LocalBlobStorage`/`S3BlobStorage`'s logic assumes
|
|
59
|
-
* it means a design project).
|
|
60
|
-
*
|
|
61
|
-
* **Dropped, not ported:** `resolveProjectStorage()`. This is OD adapter
|
|
62
|
-
* wiring — a specific env-var-naming convention and the choice to read
|
|
63
|
-
* `process.env` directly inside the engine layer — not generic engine
|
|
64
|
-
* behavior. It has no equivalent anywhere else in this porting session's
|
|
65
|
-
* established pattern (`createSqliteEventLog`, `LocalBlobStorage`,
|
|
66
|
-
* `S3BlobStorage` are all called with explicit constructor arguments; none of
|
|
67
|
-
* `@jini/*`'s existing adapters read `process.env` inside their own
|
|
68
|
-
* constructors). A Jini host application composes its own equivalent
|
|
69
|
-
* (`new LocalBlobStorage(root)` or `new S3BlobStorage({ bucket, region,
|
|
70
|
-
* credentials, ... })` called directly, with whatever env-var convention that
|
|
71
|
-
* host prefers) rather than inheriting OD's `OD_S3_*` names or its
|
|
72
|
-
* env-selection shape.
|
|
73
|
-
*
|
|
74
|
-
* Landed in `@jini/platform`, not `@jini/sqlite`: this is a filesystem/network
|
|
75
|
-
* blob-storage primitive with no SQL involvement, parallel to this package's
|
|
76
|
-
* existing `fs.ts` (path containment / atomic copy) and `http.ts` (readiness
|
|
77
|
-
* polling) roles — `@jini/sqlite` is reserved for the `EventLog` port adapter
|
|
78
|
-
* and `better-sqlite3`-specific helpers (`db-inspect.ts`,
|
|
79
|
-
* `backend-config.ts`), none of which this needs. `aws-sigv4.ts` lives
|
|
80
|
-
* alongside it in the same package since it's `S3BlobStorage`'s only
|
|
81
|
-
* dependency and has no other consumer yet.
|
|
30
|
+
* Blob storage uses opaque namespace scoping rather than a product's project model. Hosts supply
|
|
31
|
+
* configuration/credentials explicitly instead of inheriting product-prefixed environment names.
|
|
32
|
+
* Filesystem/network storage and its SigV4 signing belong here, with no kernel SQL-port dependency.
|
|
82
33
|
*/
|
|
83
34
|
export { createCommandInvocation, createPackageManagerInvocation } from "./command.js";
|
|
35
|
+
export { omitEnvironmentKeys } from "./child-environment.js";
|
|
84
36
|
export { mergeProxyAwareEnv, parseMacosScutilProxyOutput, parseWindowsInternetSettingsProxyOutput, resolveSystemProxyEnv, } from "./proxy-env.js";
|
|
85
37
|
export { collectProcessTreePids, createProcessStampArgs, isProcessAlive, listProcessSnapshots, matchesProcessStamp, matchesStampedProcess, readFlagValue, readProcessStamp, readProcessStampFromCommand, spawnBackgroundProcess, spawnLoggedProcess, stopProcesses, waitForProcessExit, } from "./process.js";
|
|
86
38
|
export { atomicCopyFile, pathContains, readLogTail, removePathBestEffort } from "./fs.js";
|
|
@@ -88,7 +40,7 @@ export { waitForHttpOk } from "./http.js";
|
|
|
88
40
|
export { FETCH_TIMEOUT_MS, FetchTimeoutError, fetchWithTimeout } from "./fetch-with-timeout.js";
|
|
89
41
|
export { wellKnownUserToolchainBins } from "./toolchain.js";
|
|
90
42
|
export { execCommandViaLoginShell, execFileBuffered } from "./shell.js";
|
|
91
|
-
export { AssetCacheError, assertSafePublicUrl, assetCacheKey, assetCacheRewriteUrl, createAssetCache, createValidatingLookup, isCacheableExternalUrl, } from "./asset-cache.js";
|
|
43
|
+
export { AssetCacheError, assertSafePublicUrl, assetCacheKey, assetCacheRewriteUrl, createAssetCache, createAssetCacheDispatcher, createValidatingLookup, isCacheableExternalUrl, } from "./asset-cache.js";
|
|
92
44
|
export { expandHomePrefix, resolveProjectRelativePath } from "./home-expansion.js";
|
|
93
45
|
export { applySandboxRuntimeEnv, ensureSandboxRuntimeDirs, isSandboxImportedProjectRootAllowed, isSandboxModeEnabled, resolveSandboxRuntimeConfig, resolveSandboxRuntimeConfigFromEnv, sandboxAgentProfilesConfigPath, sandboxImportAllowedRoots, sandboxImportedProjectRootUnavailableReason, } from "./sandbox-env.js";
|
|
94
46
|
export { resolveDaemonCliPath, resolveDaemonPluginPreviewsDir, resolveDaemonResourceDir, resolveDaemonResourceRoot, resolveDataDir, resolveProcessResourcesPath, } from "./resource-paths.js";
|
package/dist/net/address.d.ts
CHANGED
|
@@ -23,14 +23,7 @@ export declare function expandIpv6({ address: addr }: {
|
|
|
23
23
|
export declare function isPrivateAddress({ address: addr }: {
|
|
24
24
|
address: string;
|
|
25
25
|
}): boolean;
|
|
26
|
-
|
|
27
|
-
export declare function isLoopbackApiHost({ hostname }: {
|
|
28
|
-
hostname: string;
|
|
29
|
-
}): boolean;
|
|
30
|
-
/** True for RFC1918/link-local/CGNAT/multicast/unspecified/unique-local-IPv6 addresses — private network space a public caller should never be steered into. */
|
|
31
|
-
export declare function isBlockedExternalApiHostname({ hostname }: {
|
|
32
|
-
hostname: string;
|
|
33
|
-
}): boolean;
|
|
26
|
+
export { isLoopbackApiHost, isBlockedExternalApiHostname } from './endpoint-policy.js';
|
|
34
27
|
/** True when `hostname` is `localhost`, an IPv6 loopback literal, or a `127.0.0.0/8` IPv4 literal. */
|
|
35
28
|
export declare function isLoopbackHostname({ hostname }: {
|
|
36
29
|
readonly hostname: unknown;
|