swarm-deploy 0.0.0 → 0.2.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/CHANGELOG.md +76 -0
- package/LICENSE.md +201 -0
- package/NOTICE.md +13 -0
- package/README.md +1283 -0
- package/RELEASING.md +71 -0
- package/SECURITY.md +40 -0
- package/dist/abort.d.ts +16 -0
- package/dist/abort.js +52 -0
- package/dist/abort.js.map +1 -0
- package/dist/allowlist.d.ts +2 -0
- package/dist/allowlist.js +21 -0
- package/dist/allowlist.js.map +1 -0
- package/dist/bin/swarm-deploy.d.ts +2 -0
- package/dist/bin/swarm-deploy.js +41 -0
- package/dist/bin/swarm-deploy.js.map +1 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.js +751 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +111 -0
- package/dist/client.js +276 -0
- package/dist/client.js.map +1 -0
- package/dist/direct-dht.d.ts +81 -0
- package/dist/direct-dht.js +254 -0
- package/dist/direct-dht.js.map +1 -0
- package/dist/error-code.d.ts +15 -0
- package/dist/error-code.js +30 -0
- package/dist/error-code.js.map +1 -0
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/events-node.d.ts +2 -0
- package/dist/events-node.js +7 -0
- package/dist/events-node.js.map +1 -0
- package/dist/files.d.ts +32 -0
- package/dist/files.js +85 -0
- package/dist/files.js.map +1 -0
- package/dist/fs-node.d.ts +2 -0
- package/dist/fs-node.js +7 -0
- package/dist/fs-node.js.map +1 -0
- package/dist/hooks-module.d.ts +9 -0
- package/dist/hooks-module.js +118 -0
- package/dist/hooks-module.js.map +1 -0
- package/dist/hooks.d.ts +69 -0
- package/dist/hooks.js +88 -0
- package/dist/hooks.js.map +1 -0
- package/dist/identity.d.ts +7 -0
- package/dist/identity.js +50 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/os-node.d.ts +2 -0
- package/dist/os-node.js +7 -0
- package/dist/os-node.js.map +1 -0
- package/dist/path-node.d.ts +2 -0
- package/dist/path-node.js +7 -0
- package/dist/path-node.js.map +1 -0
- package/dist/process-node.d.ts +2 -0
- package/dist/process-node.js +7 -0
- package/dist/process-node.js.map +1 -0
- package/dist/release.d.ts +26 -0
- package/dist/release.js +419 -0
- package/dist/release.js.map +1 -0
- package/dist/server.d.ts +239 -0
- package/dist/server.js +928 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/atomic-file.d.ts +15 -0
- package/dist/storage/atomic-file.js +172 -0
- package/dist/storage/atomic-file.js.map +1 -0
- package/dist/storage/commit-journal.d.ts +120 -0
- package/dist/storage/commit-journal.js +417 -0
- package/dist/storage/commit-journal.js.map +1 -0
- package/dist/storage/commit-store.d.ts +307 -0
- package/dist/storage/commit-store.js +1691 -0
- package/dist/storage/commit-store.js.map +1 -0
- package/dist/storage/layout.d.ts +16 -0
- package/dist/storage/layout.js +409 -0
- package/dist/storage/layout.js.map +1 -0
- package/dist/storage/link-store.d.ts +70 -0
- package/dist/storage/link-store.js +363 -0
- package/dist/storage/link-store.js.map +1 -0
- package/dist/storage/recovery.d.ts +100 -0
- package/dist/storage/recovery.js +260 -0
- package/dist/storage/recovery.js.map +1 -0
- package/dist/storage/retention.d.ts +183 -0
- package/dist/storage/retention.js +619 -0
- package/dist/storage/retention.js.map +1 -0
- package/dist/storage/root-coordinator.d.ts +7 -0
- package/dist/storage/root-coordinator.js +27 -0
- package/dist/storage/root-coordinator.js.map +1 -0
- package/dist/storage/session-store.d.ts +2 -0
- package/dist/storage/session-store.js +7 -0
- package/dist/storage/session-store.js.map +1 -0
- package/dist/storage/tar-session-store.d.ts +147 -0
- package/dist/storage/tar-session-store.js +1044 -0
- package/dist/storage/tar-session-store.js.map +1 -0
- package/dist/storage/tree-fs.d.ts +44 -0
- package/dist/storage/tree-fs.js +580 -0
- package/dist/storage/tree-fs.js.map +1 -0
- package/dist/storage/tree-staging.d.ts +9 -0
- package/dist/storage/tree-staging.js +64 -0
- package/dist/storage/tree-staging.js.map +1 -0
- package/dist/storage/types.d.ts +77 -0
- package/dist/storage/types.js +3 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/symlinks.d.ts +36 -0
- package/dist/symlinks.js +128 -0
- package/dist/symlinks.js.map +1 -0
- package/dist/tar-protocol/controls.d.ts +73 -0
- package/dist/tar-protocol/controls.js +291 -0
- package/dist/tar-protocol/controls.js.map +1 -0
- package/dist/tar-protocol/direct-wire.d.ts +43 -0
- package/dist/tar-protocol/direct-wire.js +194 -0
- package/dist/tar-protocol/direct-wire.js.map +1 -0
- package/dist/tar-protocol/extract.d.ts +18 -0
- package/dist/tar-protocol/extract.js +124 -0
- package/dist/tar-protocol/extract.js.map +1 -0
- package/dist/tar-protocol/hash.d.ts +10 -0
- package/dist/tar-protocol/hash.js +44 -0
- package/dist/tar-protocol/hash.js.map +1 -0
- package/dist/tar-protocol/lifecycle.d.ts +13 -0
- package/dist/tar-protocol/lifecycle.js +90 -0
- package/dist/tar-protocol/lifecycle.js.map +1 -0
- package/dist/tar-protocol/manifest.d.ts +58 -0
- package/dist/tar-protocol/manifest.js +312 -0
- package/dist/tar-protocol/manifest.js.map +1 -0
- package/dist/tar-protocol/tree-extract.d.ts +37 -0
- package/dist/tar-protocol/tree-extract.js +240 -0
- package/dist/tar-protocol/tree-extract.js.map +1 -0
- package/dist/tar-protocol/tree-manifest.d.ts +45 -0
- package/dist/tar-protocol/tree-manifest.js +277 -0
- package/dist/tar-protocol/tree-manifest.js.map +1 -0
- package/dist/tar-protocol/tree-path.d.ts +15 -0
- package/dist/tar-protocol/tree-path.js +68 -0
- package/dist/tar-protocol/tree-path.js.map +1 -0
- package/dist/tar-protocol/tree.d.ts +68 -0
- package/dist/tar-protocol/tree.js +269 -0
- package/dist/tar-protocol/tree.js.map +1 -0
- package/dist/tar-protocol/ustar.d.ts +36 -0
- package/dist/tar-protocol/ustar.js +167 -0
- package/dist/tar-protocol/ustar.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +4 -0
- package/dist/validation.js +26 -0
- package/dist/validation.js.map +1 -0
- package/package.json +103 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { DesiredLink } from '../symlinks.js';
|
|
2
|
+
import type { ArtifactKind } from '../types.js';
|
|
3
|
+
import type { StorageAdapter, StorageLayout, SymlinkCapableStorage } from './types.js';
|
|
4
|
+
export interface ManagedSymlinkRecord {
|
|
5
|
+
version: 1;
|
|
6
|
+
name: string;
|
|
7
|
+
target: string;
|
|
8
|
+
transferId: string;
|
|
9
|
+
targetKind: ArtifactKind;
|
|
10
|
+
}
|
|
11
|
+
export interface LinkReconcileResult {
|
|
12
|
+
created: string[];
|
|
13
|
+
updated: string[];
|
|
14
|
+
removed: string[];
|
|
15
|
+
unchanged: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface LinkReconcileOptions {
|
|
18
|
+
/** Managed artifact basenames used to prove a crash-residual symlink target. */
|
|
19
|
+
managedArtifactNames?: ReadonlySet<string>;
|
|
20
|
+
}
|
|
21
|
+
export interface LinkStoreOptions {
|
|
22
|
+
layout: StorageLayout;
|
|
23
|
+
/** Must provide `symlink` and `readlink`, or construction fails closed. */
|
|
24
|
+
storage?: StorageAdapter;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Owns every server-managed symlink in the storage root.
|
|
28
|
+
*
|
|
29
|
+
* A destination is replaceable only when a valid ownership record exists, the
|
|
30
|
+
* visible destination is a symbolic link, and `readlink()` returns either the
|
|
31
|
+
* recorded target or the desired target. Anything else is unmanaged: it is left
|
|
32
|
+
* untouched and the operation fails closed. A link target is always exactly a
|
|
33
|
+
* sibling basename, and `readlink()` output is only ever compared, never used
|
|
34
|
+
* to build a path.
|
|
35
|
+
*/
|
|
36
|
+
export declare class LinkStore {
|
|
37
|
+
readonly layout: StorageLayout;
|
|
38
|
+
readonly storage: SymlinkCapableStorage;
|
|
39
|
+
constructor({ layout, storage }: LinkStoreOptions);
|
|
40
|
+
recordPath(name: string): string;
|
|
41
|
+
private finalPath;
|
|
42
|
+
read(name: string): Promise<ManagedSymlinkRecord | null>;
|
|
43
|
+
/** Every persisted ownership record, keyed by its proven link name. */
|
|
44
|
+
list(): Promise<ManagedSymlinkRecord[]>;
|
|
45
|
+
private readRecord;
|
|
46
|
+
private listRecords;
|
|
47
|
+
private classify;
|
|
48
|
+
private writeRecord;
|
|
49
|
+
private removeRecord;
|
|
50
|
+
/**
|
|
51
|
+
* Creates a link where nothing exists. The ownership record is durable first,
|
|
52
|
+
* so a crash after the link appears never leaves an unrecorded link; the
|
|
53
|
+
* link itself is created without overwriting, so a path that appeared since
|
|
54
|
+
* classification is never replaced and its record is withdrawn.
|
|
55
|
+
*/
|
|
56
|
+
private createLink;
|
|
57
|
+
/** Removes a temporary symlink with `lstat`/`unlink`, never a regular-file helper. */
|
|
58
|
+
private removeTemporary;
|
|
59
|
+
/** Atomically swaps an owned link by renaming a private temporary link over it. */
|
|
60
|
+
private replaceLink;
|
|
61
|
+
private removeLink;
|
|
62
|
+
/**
|
|
63
|
+
* Converges every desired link and removes the record and visible link of
|
|
64
|
+
* every name no rule owns. Level-triggered: the same input always produces
|
|
65
|
+
* the same output, whatever the previous attempt's crash point.
|
|
66
|
+
*/
|
|
67
|
+
reconcile(desired: readonly DesiredLink[], ruleNames: ReadonlySet<string>, options?: LinkReconcileOptions): Promise<LinkReconcileResult>;
|
|
68
|
+
private sweepTemporaryLinks;
|
|
69
|
+
private converge;
|
|
70
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LinkStore = void 0;
|
|
7
|
+
const b4a_1 = __importDefault(require("b4a"));
|
|
8
|
+
const error_code_js_1 = require("../error-code.js");
|
|
9
|
+
const _fs_1 = __importDefault(require("#fs"));
|
|
10
|
+
const _path_1 = __importDefault(require("#path"));
|
|
11
|
+
const sodium_native_1 = __importDefault(require("sodium-native"));
|
|
12
|
+
const errors_js_1 = require("../errors.js");
|
|
13
|
+
const files_js_1 = require("../files.js");
|
|
14
|
+
const hash_js_1 = require("../tar-protocol/hash.js");
|
|
15
|
+
const atomic_file_js_1 = require("./atomic-file.js");
|
|
16
|
+
const layout_js_1 = require("./layout.js");
|
|
17
|
+
const tree_fs_js_1 = require("./tree-fs.js");
|
|
18
|
+
const MAX_LINK_RECORD_BYTES = 4 * 1024;
|
|
19
|
+
const RECORD_FILENAME = /^[0-9a-f]{64}\.json$/;
|
|
20
|
+
const RECORD_KEYS = 'name,target,targetKind,transferId,version';
|
|
21
|
+
function conflict(message) {
|
|
22
|
+
return new errors_js_1.SwarmDeployError(errors_js_1.ERRORS.LINK_CONFLICT, message);
|
|
23
|
+
}
|
|
24
|
+
function failure(message, cause = null) {
|
|
25
|
+
return new errors_js_1.SwarmDeployError(errors_js_1.ERRORS.LINK_FAILED, message, cause);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Maps every failure to a stable link error. The raw error, which may carry an
|
|
29
|
+
* errno or an absolute path, is kept only as the `cause`, never in the message.
|
|
30
|
+
*/
|
|
31
|
+
function asLinkError(error) {
|
|
32
|
+
if (error instanceof errors_js_1.SwarmDeployError &&
|
|
33
|
+
(error.code === errors_js_1.ERRORS.LINK_CONFLICT ||
|
|
34
|
+
error.code === errors_js_1.ERRORS.LINK_FAILED ||
|
|
35
|
+
error.code === errors_js_1.ERRORS.UNSUPPORTED_STORAGE)) {
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
return failure('Managed link operation failed', error);
|
|
39
|
+
}
|
|
40
|
+
/** A link or target name: one safe basename, never reserved, never the internal directory. */
|
|
41
|
+
function assertSafeName(value, message) {
|
|
42
|
+
if (typeof value !== 'string' ||
|
|
43
|
+
value.includes('/') ||
|
|
44
|
+
value.includes('\\') ||
|
|
45
|
+
value.includes('\u0000') ||
|
|
46
|
+
value === '.' ||
|
|
47
|
+
value === '..' ||
|
|
48
|
+
value === '.swarm-deploy') {
|
|
49
|
+
throw failure(message);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
(0, files_js_1.validateBasename)(value);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw failure(message, error);
|
|
56
|
+
}
|
|
57
|
+
if ((0, files_js_1.isReservedHistoryName)(value))
|
|
58
|
+
throw failure(message);
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function assertRecord(value, expectedName) {
|
|
62
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
63
|
+
throw failure('Invalid managed link record');
|
|
64
|
+
}
|
|
65
|
+
const candidate = value;
|
|
66
|
+
if (Object.keys(candidate).sort().join(',') !== RECORD_KEYS ||
|
|
67
|
+
candidate.version !== 1 ||
|
|
68
|
+
candidate.name !== expectedName ||
|
|
69
|
+
(candidate.targetKind !== 'file' && candidate.targetKind !== 'directory') ||
|
|
70
|
+
typeof candidate.transferId !== 'string' ||
|
|
71
|
+
!/^[0-9a-f]{64}$/.test(candidate.transferId)) {
|
|
72
|
+
throw failure('Invalid managed link record');
|
|
73
|
+
}
|
|
74
|
+
assertSafeName(candidate.name, 'Invalid managed link record');
|
|
75
|
+
assertSafeName(candidate.target, 'Invalid managed link record');
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
function randomSuffix() {
|
|
79
|
+
const bytes = b4a_1.default.allocUnsafe(16);
|
|
80
|
+
sodium_native_1.default.randombytes_buf(bytes);
|
|
81
|
+
return b4a_1.default.toString(bytes, 'hex');
|
|
82
|
+
}
|
|
83
|
+
async function syncDirectory(directory, storage) {
|
|
84
|
+
const handle = await storage.open(directory, 'r');
|
|
85
|
+
try {
|
|
86
|
+
await handle.sync();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await handle.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Owns every server-managed symlink in the storage root.
|
|
94
|
+
*
|
|
95
|
+
* A destination is replaceable only when a valid ownership record exists, the
|
|
96
|
+
* visible destination is a symbolic link, and `readlink()` returns either the
|
|
97
|
+
* recorded target or the desired target. Anything else is unmanaged: it is left
|
|
98
|
+
* untouched and the operation fails closed. A link target is always exactly a
|
|
99
|
+
* sibling basename, and `readlink()` output is only ever compared, never used
|
|
100
|
+
* to build a path.
|
|
101
|
+
*/
|
|
102
|
+
class LinkStore {
|
|
103
|
+
layout;
|
|
104
|
+
storage;
|
|
105
|
+
constructor({ layout, storage = _fs_1.default.promises }) {
|
|
106
|
+
if (!layout || typeof layout !== 'object')
|
|
107
|
+
throw failure('Invalid storage layout');
|
|
108
|
+
(0, tree_fs_js_1.assertSymlinkCapable)(storage);
|
|
109
|
+
this.layout = layout;
|
|
110
|
+
this.storage = storage;
|
|
111
|
+
}
|
|
112
|
+
recordPath(name) {
|
|
113
|
+
return _path_1.default.join(this.layout.links, `${b4a_1.default.toString((0, hash_js_1.sodiumSha256)(b4a_1.default.from(name)), 'hex')}.json`);
|
|
114
|
+
}
|
|
115
|
+
finalPath(name) {
|
|
116
|
+
return _path_1.default.join(this.layout.root, name);
|
|
117
|
+
}
|
|
118
|
+
async read(name) {
|
|
119
|
+
try {
|
|
120
|
+
return await this.readRecord(assertSafeName(name, 'Invalid managed link name'));
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
throw asLinkError(error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Every persisted ownership record, keyed by its proven link name. */
|
|
127
|
+
async list() {
|
|
128
|
+
try {
|
|
129
|
+
return await this.listRecords();
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
throw asLinkError(error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async readRecord(name) {
|
|
136
|
+
try {
|
|
137
|
+
return assertRecord(await (0, atomic_file_js_1.readJson)(this.recordPath(name), this.storage, MAX_LINK_RECORD_BYTES), name);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if ((0, error_code_js_1.isMissing)(error))
|
|
141
|
+
return null;
|
|
142
|
+
if (error instanceof atomic_file_js_1.MetadataFormatError) {
|
|
143
|
+
throw failure('Malformed managed link record', error);
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async listRecords() {
|
|
149
|
+
const filenames = await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.links, this.storage, () => this.storage.readdir(this.layout.links));
|
|
150
|
+
const records = [];
|
|
151
|
+
for (const filename of filenames.sort()) {
|
|
152
|
+
if (!RECORD_FILENAME.test(filename))
|
|
153
|
+
continue;
|
|
154
|
+
let parsed;
|
|
155
|
+
try {
|
|
156
|
+
parsed = await (0, atomic_file_js_1.readJson)(_path_1.default.join(this.layout.links, filename), this.storage, MAX_LINK_RECORD_BYTES);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw failure('Malformed managed link record', error);
|
|
160
|
+
}
|
|
161
|
+
const record = assertRecord(parsed, typeof parsed.name === 'string' ? parsed.name : '');
|
|
162
|
+
if (_path_1.default.basename(this.recordPath(record.name)) !== filename) {
|
|
163
|
+
throw failure('Foreign managed link record');
|
|
164
|
+
}
|
|
165
|
+
records.push(record);
|
|
166
|
+
}
|
|
167
|
+
return records;
|
|
168
|
+
}
|
|
169
|
+
classify(name) {
|
|
170
|
+
const finalPath = this.finalPath(name);
|
|
171
|
+
return (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.root, this.storage, async () => {
|
|
172
|
+
let stat;
|
|
173
|
+
try {
|
|
174
|
+
stat = await this.storage.lstat(finalPath);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if ((0, error_code_js_1.isMissing)(error))
|
|
178
|
+
return { state: 'MISSING' };
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
if (!stat.isSymbolicLink())
|
|
182
|
+
return { state: 'UNMANAGED' };
|
|
183
|
+
return { state: 'SYMLINK', target: await this.storage.readlink(finalPath) };
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async writeRecord(link) {
|
|
187
|
+
const record = {
|
|
188
|
+
version: 1,
|
|
189
|
+
name: link.name,
|
|
190
|
+
target: link.target,
|
|
191
|
+
transferId: link.transferId,
|
|
192
|
+
targetKind: link.targetKind
|
|
193
|
+
};
|
|
194
|
+
assertRecord(record, link.name);
|
|
195
|
+
await (0, atomic_file_js_1.writeAtomic)(this.recordPath(link.name), b4a_1.default.from(JSON.stringify(record)), this.storage);
|
|
196
|
+
}
|
|
197
|
+
async removeRecord(name) {
|
|
198
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.links, this.storage, async () => {
|
|
199
|
+
try {
|
|
200
|
+
await this.storage.unlink(this.recordPath(name));
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (!(0, error_code_js_1.isMissing)(error))
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.links, this.storage, () => syncDirectory(this.layout.links, this.storage));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Creates a link where nothing exists. The ownership record is durable first,
|
|
211
|
+
* so a crash after the link appears never leaves an unrecorded link; the
|
|
212
|
+
* link itself is created without overwriting, so a path that appeared since
|
|
213
|
+
* classification is never replaced and its record is withdrawn.
|
|
214
|
+
*/
|
|
215
|
+
async createLink(link) {
|
|
216
|
+
await this.writeRecord(link);
|
|
217
|
+
try {
|
|
218
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.root, this.storage, () => this.storage.symlink(link.target, this.finalPath(link.name)));
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
await this.removeRecord(link.name).catch(() => { });
|
|
222
|
+
if ((0, error_code_js_1.errorCode)(error) === 'EEXIST') {
|
|
223
|
+
throw conflict('Unmanaged path at a configured managed link name');
|
|
224
|
+
}
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.root, this.storage, () => syncDirectory(this.layout.root, this.storage));
|
|
228
|
+
}
|
|
229
|
+
/** Removes a temporary symlink with `lstat`/`unlink`, never a regular-file helper. */
|
|
230
|
+
async removeTemporary(temporary) {
|
|
231
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.publications, this.storage, async () => {
|
|
232
|
+
try {
|
|
233
|
+
await this.storage.lstat(temporary);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if ((0, error_code_js_1.isMissing)(error))
|
|
237
|
+
return;
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
await this.storage.unlink(temporary);
|
|
241
|
+
}).catch(() => { });
|
|
242
|
+
}
|
|
243
|
+
/** Atomically swaps an owned link by renaming a private temporary link over it. */
|
|
244
|
+
async replaceLink(link) {
|
|
245
|
+
const temporary = _path_1.default.join(this.layout.publications, `.link-${randomSuffix()}`);
|
|
246
|
+
try {
|
|
247
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.publications, this.storage, () => this.storage.symlink(link.target, temporary));
|
|
248
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.publications, this.storage, () => syncDirectory(this.layout.publications, this.storage));
|
|
249
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.root, this.storage, () => (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.publications, this.storage, () => this.storage.rename(temporary, this.finalPath(link.name))));
|
|
250
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.root, this.storage, () => syncDirectory(this.layout.root, this.storage));
|
|
251
|
+
await this.writeRecord(link);
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
await this.removeTemporary(temporary);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async removeLink(record, desiredTarget) {
|
|
258
|
+
const destination = await this.classify(record.name);
|
|
259
|
+
const allowed = new Set([record.target]);
|
|
260
|
+
if (desiredTarget)
|
|
261
|
+
allowed.add(desiredTarget);
|
|
262
|
+
if (destination.state === 'SYMLINK' && allowed.has(destination.target)) {
|
|
263
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.root, this.storage, () => this.storage.unlink(this.finalPath(record.name)));
|
|
264
|
+
await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.root, this.storage, () => syncDirectory(this.layout.root, this.storage));
|
|
265
|
+
}
|
|
266
|
+
else if (destination.state !== 'MISSING') {
|
|
267
|
+
throw conflict('Unmanaged path at a removed managed link name');
|
|
268
|
+
}
|
|
269
|
+
await this.removeRecord(record.name);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Converges every desired link and removes the record and visible link of
|
|
273
|
+
* every name no rule owns. Level-triggered: the same input always produces
|
|
274
|
+
* the same output, whatever the previous attempt's crash point.
|
|
275
|
+
*/
|
|
276
|
+
async reconcile(desired, ruleNames, options = {}) {
|
|
277
|
+
try {
|
|
278
|
+
return await this.converge(desired, ruleNames, options);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
throw asLinkError(error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async sweepTemporaryLinks() {
|
|
285
|
+
const names = await (0, layout_js_1.withSafeDirectoryIdentity)(this.layout.publications, this.storage, () => this.storage.readdir(this.layout.publications));
|
|
286
|
+
for (const name of names) {
|
|
287
|
+
if (!/^\.link-[0-9a-f]{32}$/.test(name))
|
|
288
|
+
continue;
|
|
289
|
+
await this.removeTemporary(_path_1.default.join(this.layout.publications, name));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async converge(desired, ruleNames, { managedArtifactNames } = {}) {
|
|
293
|
+
await this.sweepTemporaryLinks();
|
|
294
|
+
const result = { created: [], updated: [], removed: [], unchanged: [] };
|
|
295
|
+
const desiredByName = new Map(desired.map((link) => [link.name, link]));
|
|
296
|
+
for (const record of await this.listRecords()) {
|
|
297
|
+
if (ruleNames.has(record.name)) {
|
|
298
|
+
if (!desiredByName.has(record.name)) {
|
|
299
|
+
const destination = await this.classify(record.name);
|
|
300
|
+
const desiredTarget = destination.state === 'SYMLINK' && destination.target !== record.target
|
|
301
|
+
? destination.target
|
|
302
|
+
: undefined;
|
|
303
|
+
await this.removeLink(record, desiredTarget);
|
|
304
|
+
result.removed.push(record.name);
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const destination = await this.classify(record.name);
|
|
309
|
+
let desiredTarget;
|
|
310
|
+
if (destination.state === 'SYMLINK' && destination.target !== record.target) {
|
|
311
|
+
const pending = desiredByName.get(record.name);
|
|
312
|
+
if (pending)
|
|
313
|
+
desiredTarget = pending.target;
|
|
314
|
+
else if (managedArtifactNames?.has(destination.target)) {
|
|
315
|
+
desiredTarget = destination.target;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
await this.removeLink(record, desiredTarget);
|
|
319
|
+
result.removed.push(record.name);
|
|
320
|
+
}
|
|
321
|
+
for (const link of desired) {
|
|
322
|
+
if (!ruleNames.has(link.name))
|
|
323
|
+
throw failure('Desired link is not a configured rule');
|
|
324
|
+
assertSafeName(link.name, 'Invalid managed link name');
|
|
325
|
+
assertSafeName(link.target, 'Invalid managed link target');
|
|
326
|
+
if (link.target === link.name)
|
|
327
|
+
throw failure('Managed link cannot target itself');
|
|
328
|
+
const record = await this.readRecord(link.name);
|
|
329
|
+
const destination = await this.classify(link.name);
|
|
330
|
+
if (destination.state === 'UNMANAGED') {
|
|
331
|
+
throw conflict('Unmanaged path at a configured managed link name');
|
|
332
|
+
}
|
|
333
|
+
if (destination.state === 'MISSING') {
|
|
334
|
+
await this.createLink(link);
|
|
335
|
+
result.created.push(link.name);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (record === null)
|
|
339
|
+
throw conflict('Unrecorded symlink at a configured managed link name');
|
|
340
|
+
// Ownership proof: a record exists and the visible link is either the
|
|
341
|
+
// recorded target or the desired one (a crash after the visible swap).
|
|
342
|
+
if (destination.target !== record.target && destination.target !== link.target) {
|
|
343
|
+
throw conflict('Managed link does not match its ownership record');
|
|
344
|
+
}
|
|
345
|
+
if (destination.target === link.target) {
|
|
346
|
+
if (record.target === link.target &&
|
|
347
|
+
record.transferId === link.transferId &&
|
|
348
|
+
record.targetKind === link.targetKind) {
|
|
349
|
+
result.unchanged.push(link.name);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
await this.writeRecord(link);
|
|
353
|
+
result.updated.push(link.name);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
await this.replaceLink(link);
|
|
357
|
+
result.updated.push(link.name);
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
exports.LinkStore = LinkStore;
|
|
363
|
+
//# sourceMappingURL=link-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-store.js","sourceRoot":"","sources":["../../src/storage/link-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { CommitRecord } from './commit-journal.js';
|
|
2
|
+
import type { StorageAdapter, StorageLayout } from './types.js';
|
|
3
|
+
interface Logger {
|
|
4
|
+
info?: (message: string, details: Record<string, unknown>) => void;
|
|
5
|
+
warn?: (message: string, details: Record<string, unknown>) => void;
|
|
6
|
+
}
|
|
7
|
+
interface SessionStore {
|
|
8
|
+
initialized?: boolean;
|
|
9
|
+
closed?: boolean;
|
|
10
|
+
readVerified(transferId: Uint8Array): Promise<{
|
|
11
|
+
id: string;
|
|
12
|
+
transferId: Uint8Array;
|
|
13
|
+
ownerKey: Uint8Array;
|
|
14
|
+
name: string;
|
|
15
|
+
size: number;
|
|
16
|
+
digest: Uint8Array;
|
|
17
|
+
tarSize: number;
|
|
18
|
+
tarDigest: Uint8Array;
|
|
19
|
+
state: string;
|
|
20
|
+
}>;
|
|
21
|
+
expire(ttl: number, shouldExpire: (session: {
|
|
22
|
+
state: string;
|
|
23
|
+
}) => boolean): Promise<number>;
|
|
24
|
+
delete?(transferId: Uint8Array): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
interface RecoveryResult {
|
|
27
|
+
status: 'MISSING' | 'FILE_EXISTS' | 'COMMITTED' | 'ABORTED' | 'RESUMABLE' | 'CORRUPT';
|
|
28
|
+
record?: CommitRecord;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
interface CommitStore {
|
|
32
|
+
storage?: StorageAdapter;
|
|
33
|
+
_readJournal(id: string): Promise<unknown>;
|
|
34
|
+
retireCorruptAttempt(id: string): Promise<{
|
|
35
|
+
retired: boolean;
|
|
36
|
+
removedStaging: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
recoverJournal(id: string, sessionStore: SessionStore, options: {
|
|
39
|
+
isAuthorized: ((ownerKey: Uint8Array) => boolean) | null;
|
|
40
|
+
}): Promise<RecoveryResult>;
|
|
41
|
+
list(): Promise<CommitRecord[]>;
|
|
42
|
+
scrubRecords(): Promise<{
|
|
43
|
+
records: CommitRecord[];
|
|
44
|
+
deleted: number;
|
|
45
|
+
}>;
|
|
46
|
+
delete(record: CommitRecord): Promise<boolean>;
|
|
47
|
+
purge(record: CommitRecord, options?: {
|
|
48
|
+
preservePath?: boolean;
|
|
49
|
+
}): Promise<false | {
|
|
50
|
+
purged: true;
|
|
51
|
+
preservedPath: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
sweepTrash(): Promise<number>;
|
|
54
|
+
}
|
|
55
|
+
type RecoveryEvent = {
|
|
56
|
+
type: 'recovery';
|
|
57
|
+
status: 'CORRUPT';
|
|
58
|
+
phase: 'classification';
|
|
59
|
+
transfer: string;
|
|
60
|
+
} | {
|
|
61
|
+
type: 'recovery';
|
|
62
|
+
status: 'failed';
|
|
63
|
+
phase: 'classification' | 'journal';
|
|
64
|
+
transfer?: string;
|
|
65
|
+
reason: string;
|
|
66
|
+
} | {
|
|
67
|
+
type: 'recovery';
|
|
68
|
+
status: 'MISSING' | 'FILE_EXISTS' | 'COMMITTED' | 'ABORTED' | 'RESUMABLE' | 'CORRUPT';
|
|
69
|
+
transfer: string;
|
|
70
|
+
} | {
|
|
71
|
+
type: 'cleanup';
|
|
72
|
+
transfer: string;
|
|
73
|
+
name: null;
|
|
74
|
+
reason: 'corrupt-journal' | 'trash-residue';
|
|
75
|
+
} | {
|
|
76
|
+
type: 'scrub';
|
|
77
|
+
status: 'started';
|
|
78
|
+
} | {
|
|
79
|
+
type: 'scrub';
|
|
80
|
+
status: 'completed';
|
|
81
|
+
deleted: number;
|
|
82
|
+
unknownCount: number;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'scrub';
|
|
85
|
+
status: 'failed';
|
|
86
|
+
reason: string;
|
|
87
|
+
};
|
|
88
|
+
interface PrepareStorageRecoveryOptions {
|
|
89
|
+
layout: StorageLayout;
|
|
90
|
+
commitStore: CommitStore;
|
|
91
|
+
logger?: Logger | null;
|
|
92
|
+
onEvent?: ((event: RecoveryEvent) => void) | null;
|
|
93
|
+
}
|
|
94
|
+
interface RecoverStorageOptions extends PrepareStorageRecoveryOptions {
|
|
95
|
+
sessionStore: SessionStore;
|
|
96
|
+
isAuthorized?: ((ownerKey: Uint8Array) => boolean) | null;
|
|
97
|
+
}
|
|
98
|
+
declare function prepareStorageRecovery({ layout, commitStore, logger, onEvent }: PrepareStorageRecoveryOptions): Promise<number>;
|
|
99
|
+
declare function recoverStorage({ layout, sessionStore, commitStore, logger, isAuthorized, onEvent }: RecoverStorageOptions): Promise<RecoveryResult[]>;
|
|
100
|
+
export { prepareStorageRecovery, recoverStorage };
|