@uns-kit/core 3.0.44 → 3.0.46

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/MIGRATIONS.md CHANGED
@@ -9,6 +9,37 @@ Agents must inspect the application's existing ownership and shutdown flow
9
9
  before editing it. The examples below describe the intended behavior, not a
10
10
  mechanical search-and-replace operation.
11
11
 
12
+ ## 3.0.46 - Verified retained-store copy and recovery
13
+
14
+ When crossing `<3.0.46` to `>=3.0.46`, existing rename and unbound consumers
15
+ remain compatible. An explicit
16
+ `transfer: "copy-verified"` adds stopped-source review and bounded asynchronous
17
+ opaque copying between local mounts. Use the same operation/checksum after
18
+ interruption; return current work and retain the original copy. Full Runtime
19
+ recovery requires the matching verified offline retained root before RTT owners
20
+ can be activated. See `docs/retained-store-adoption.md`, bundled as the core
21
+ retained-store guide. No application-specific Controller logic, queue conversion,
22
+ new default handover protocol or automatic disk persistence is introduced.
23
+
24
+ ## 3.0.45 - Opt-in retained-store storage protocol
25
+
26
+ When crossing `<3.0.45` to `>=3.0.45`, ordinary applications keep their current
27
+ behavior. Applications adopting bound
28
+ retained stores must declare `storageProtocol: 1`, resolve only the shared resource
29
+ paths, acquire `openRetainedStoreLease(binding)` before opening payload writers,
30
+ and release it only after final writer cleanup. Do not infer support from a Kit
31
+ version or default an old omitted capability to supported. Classify manifest
32
+ resources explicitly as backup payload or metadata when needed.
33
+
34
+ Read the [bundled guide](dist/runtime/retained-store-guide.md) and the two
35
+ unrelated manifests in `dist/runtime/retained-store-examples/`. The source guide
36
+ is `docs/retained-store-adoption.md` in the SDK repository.
37
+ Managed use requires the Runtime's external retained-store root. Older local
38
+ candidate bindings/layouts need reviewed initialization/relocation; the SDK never
39
+ silently changes their absolute roots or converts application data. Snapshot
40
+ requires stopped owners. Preserve original volumes and handle unknown outcomes
41
+ through the same operation ID before restarting any owner.
42
+
12
43
  ## 3.0.44 - Bounded operator JWKS recovery
13
44
 
14
45
  When crossing `<3.0.44` to `>=3.0.44`, ordinary application handlers, standalone
package/README.md CHANGED
@@ -29,6 +29,20 @@ npm install @uns-kit/core
29
29
  - **MQTT helpers** — resilient publishers, topic builders, throttled queues, and handover support.
30
30
  - **gRPC gateway** — infrastructure to bridge Python workers into the UNS message fabric.
31
31
 
32
+ ## Optional retained stores (3.0.45)
33
+
34
+ Applications can explicitly declare stable opaque resources for reviewed
35
+ same-host adoption, offline backup and recovery. Import the shared contract from
36
+ `@uns-kit/core/runtime/retained-store.js`; hold its store lease before opening
37
+ payload writers. Ordinary applications retain their existing behavior.
38
+
39
+ Read the [bundled guide](dist/runtime/retained-store-guide.md) and manifests in
40
+ `dist/runtime/retained-store-examples/`, or the
41
+ [source guide](https://github.com/uns-openhub/uns-kit/blob/master/docs/retained-store-adoption.md).
42
+ Runtime mounts, Controller orchestration and helper releases must also support
43
+ the declared capability. The SDK version alone grants no permissions and does
44
+ not move data, enable queues or change the handover protocol.
45
+
32
46
  ## Basic usage
33
47
 
34
48
  ```ts
@@ -0,0 +1,3 @@
1
+ export type RetainedCopyFingerprint = { digest: string; bytes: number; entries: number };
2
+ export function inspectRetainedCopy(root: string, kind: "file" | "directory", limits: unknown): RetainedCopyFingerprint;
3
+ export function applyRetainedCopy(plan: unknown, direction: "adopt" | "return", limits: unknown, afterMove?: (id: string) => void, onFile?: (file: string) => void): void;
@@ -0,0 +1,133 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { safePath, owned, sha, sync, entry, relative, checkLimit } from "./retained-store-storage.mjs";
5
+
6
+ function inventory(root, kind, limits) {
7
+ checkLimit(limits);
8
+ const entries = [];
9
+ let bytes = 0;
10
+ function visit(file, rel, depth) {
11
+ if (depth > limits.maximumDepth || (rel && !relative(rel)) || entries.length >= limits.maximumEntries) throw Error("Retained copy path/count limit");
12
+ const stat = entry(file);
13
+ if (!stat) throw Error("Retained copy source is missing");
14
+ if (stat.isDirectory()) {
15
+ owned(file, true);
16
+ entries.push({ path: rel, type: "directory" });
17
+ for (const name of fs.readdirSync(file).sort()) visit(path.join(file, name), rel ? rel + "/" + name : name, depth + 1);
18
+ } else {
19
+ const value = owned(file);
20
+ bytes += value.size;
21
+ if (bytes > limits.maximumBytes || value.size > limits.maximumFileBytes) throw Error("Retained copy byte limit");
22
+ entries.push({ path: rel, type: "file", size: value.size, sha256: sha(file) });
23
+ }
24
+ }
25
+ safePath(root);
26
+ if ((kind === "directory") !== !!entry(root)?.isDirectory()) throw Error("Retained copy resource kind differs");
27
+ visit(root, "", 0);
28
+ return { entries, fingerprint: { digest: createHash("sha256").update(JSON.stringify(entries)).digest("hex"), bytes, entries: entries.length } };
29
+ }
30
+ export function inspectRetainedCopy(root, kind, limits) { return inventory(root, kind, limits).fingerprint; }
31
+ function same(a, b) { return a?.digest === b?.digest && a?.bytes === b?.bytes && a?.entries === b?.entries; }
32
+ function assertFingerprint(file, move, limits) {
33
+ const stat = entry(file);
34
+ if (!stat || stat.dev !== move.device || stat.ino !== move.inode || !same(inspectRetainedCopy(file, move.kind, limits), move.content)) throw Error("Retained copy source changed after review");
35
+ }
36
+ function privateStage(stage) {
37
+ if (!entry(stage)) { fs.mkdirSync(stage, { mode: 0o700 }); sync(path.dirname(stage)); }
38
+ if (owned(stage, true).mode & 0o077) throw Error("Retained copy staging must be private");
39
+ }
40
+ function checkStage(stage, entries) {
41
+ const allowed = new Set(["payload", ...entries.map(e => e.path ? "payload/" + e.path : "payload")]);
42
+ function visit(dir, prefix) {
43
+ for (const name of fs.readdirSync(dir)) {
44
+ const rel = prefix ? prefix + "/" + name : name, file = path.join(dir, name);
45
+ if (!allowed.has(rel)) throw Error("Unexpected retained copy staging entry");
46
+ const stat = entry(file);
47
+ owned(file, !!stat?.isDirectory());
48
+ if (stat.isDirectory()) visit(file, rel);
49
+ }
50
+ }
51
+ visit(stage, "");
52
+ }
53
+ function copyFile(source, destination, expected) {
54
+ if (entry(destination)) owned(destination);
55
+ const input = fs.openSync(source, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
56
+ let output;
57
+ try {
58
+ output = fs.openSync(destination, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_NOFOLLOW, 0o600);
59
+ const h = createHash("sha256"), buffer = Buffer.alloc(65536);
60
+ let count = 0, size;
61
+ while ((size = fs.readSync(input, buffer, 0, buffer.length, null))) {
62
+ count += size;
63
+ if (count > expected.size) throw Error("Retained copy source grew");
64
+ h.update(buffer.subarray(0, size));
65
+ let offset = 0;
66
+ while (offset < size) offset += fs.writeSync(output, buffer, offset, size - offset);
67
+ }
68
+ fs.fchmodSync(output, 0o600); fs.fsyncSync(output);
69
+ if (count !== expected.size || h.digest("hex") !== expected.sha256) throw Error("Retained copy source changed while reading");
70
+ } finally { fs.closeSync(input); if(output!==undefined)fs.closeSync(output); }
71
+ }
72
+ function copyClosed(source, destination, stage, move, limits, onFile) {
73
+ assertFingerprint(source, move, limits);
74
+ const snapshot = inventory(source, move.kind, limits);
75
+ if (entry(destination)) {
76
+ if (!same(inspectRetainedCopy(destination, move.kind, limits), move.content)) throw Error("Published retained copy differs; preserve for review");
77
+ if (entry(stage)) { checkStage(stage, snapshot.entries); fs.rmSync(stage, { recursive: true }); sync(path.dirname(stage)); }
78
+ return;
79
+ }
80
+ privateStage(stage); checkStage(stage, snapshot.entries);
81
+ const payload = path.join(stage, "payload");
82
+ for (const item of snapshot.entries) {
83
+ const from = item.path ? path.join(source, item.path) : source, to = item.path ? path.join(payload, item.path) : payload;
84
+ if (item.type === "directory") {
85
+ if (!entry(to)) fs.mkdirSync(to, { mode: 0o700 });
86
+ if (owned(to, true).mode & 0o077) throw Error("Retained stage directory must be private");
87
+ } else {
88
+ if (entry(to) && owned(to).size === item.size && sha(to) === item.sha256) continue;
89
+ copyFile(from, to, item); onFile?.(item.path);
90
+ }
91
+ }
92
+ assertFingerprint(source, move, limits);
93
+ if (!same(inspectRetainedCopy(payload, move.kind, limits), move.content)) throw Error("Retained staged copy differs");
94
+ for (const item of [...snapshot.entries].reverse()) if (item.type === "directory") sync(item.path ? path.join(payload, item.path) : payload);
95
+ fs.renameSync(payload, destination); sync(stage); sync(path.dirname(destination));
96
+ fs.rmdirSync(stage); sync(path.dirname(stage));
97
+ }
98
+ export function applyRetainedCopy(plan, direction, limits, afterMove, onFile) {
99
+ checkLimit(limits); safePath(plan.source); safePath(plan.directory);
100
+ for (const move of plan.moves) {
101
+ const legacy = path.join(plan.source, move.relative), stable = path.join(plan.directory, move.id);
102
+ const stage = direction === "adopt" ? path.join(plan.directory, ".retained-copy-" + move.id) : path.join(plan.source, ".retained-return-" + plan.storeId + "-" + move.id);
103
+ if (direction === "adopt") copyClosed(legacy, stable, stage, move, limits, onFile);
104
+ else {
105
+ assertFingerprint(stable, move, limits);
106
+ const original = plan.originalMoves?.find(m => m.id === move.id), saved = path.join(plan.source, ".retained-original-" + plan.storeId + "-" + move.id);
107
+ if (entry(legacy) && !same(inspectRetainedCopy(legacy, move.kind, limits), move.content)) {
108
+ if (!original || entry(saved)) throw Error("Return would overwrite unreviewed legacy work");
109
+ assertFingerprint(legacy, original, limits);
110
+ fs.renameSync(legacy, saved); sync(path.dirname(legacy));
111
+ }
112
+ if (entry(saved)) {
113
+ if (!original) throw Error("Unreviewed original retained copy");
114
+ assertFingerprint(saved, original, limits);
115
+ }
116
+ copyClosed(stable, legacy, stage, move, limits, onFile);
117
+ }
118
+ afterMove?.(move.id);
119
+ }
120
+ if (direction === "return") {
121
+ for (const original of plan.originalMoves ?? []) {
122
+ const stage = path.join(plan.directory, ".retained-copy-" + original.id);
123
+ if (!entry(stage)) continue;
124
+ const legacy = path.join(plan.source, original.relative);
125
+ if (!plan.moves.some(m => m.id === original.id)) assertFingerprint(legacy, original, limits);
126
+ const reference = plan.moves.some(m => m.id === original.id) ? path.join(plan.directory, original.id) : legacy;
127
+ const snapshot = inventory(reference, original.kind, limits);
128
+ checkStage(stage, snapshot.entries);
129
+ // Only incomplete scratch is discarded; all original bytes remain proven.
130
+ fs.rmSync(stage, { recursive: true }); sync(plan.directory);
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "protocol": 1,
3
+ "format": "text-index-v1",
4
+ "resources": [
5
+ {
6
+ "id": "queue",
7
+ "path": "index-data",
8
+ "kind": "directory",
9
+ "required": true,
10
+ "backup": "payload"
11
+ },
12
+ {
13
+ "id": "replay",
14
+ "path": "maintenance.json",
15
+ "kind": "file",
16
+ "required": false,
17
+ "backup": "metadata"
18
+ }
19
+ ],
20
+ "legacyVersions": [],
21
+ "storageProtocol": 1
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "protocol": 1,
3
+ "format": "measurement-journal-v1",
4
+ "resources": [
5
+ {
6
+ "id": "queue",
7
+ "path": "pending-events",
8
+ "kind": "directory",
9
+ "required": true,
10
+ "backup": "payload"
11
+ },
12
+ {
13
+ "id": "policy",
14
+ "path": "destination-policy.json",
15
+ "kind": "file",
16
+ "required": false,
17
+ "backup": "metadata"
18
+ }
19
+ ],
20
+ "legacyVersions": ["1.0.0"],
21
+ "storageProtocol": 1
22
+ }
@@ -0,0 +1,164 @@
1
+ # Retained stores: opt-in same-host adoption
2
+
3
+ Retained-store adoption starts in UNS Kit 3.0.45. The 3.0.46 extension adds
4
+ explicit copying between local mounts and verification of recovered stores.
5
+ Managed activation requires coordinated Controller and Runtime releases. Existing applications do not change behavior.
6
+ This is a filesystem capability, separate from the small JSON
7
+ handover-state contract and MQTT output persistence. It does not change the
8
+ default handover protocol, enable disk queues, or transfer data between hosts.
9
+
10
+ ## Ownership
11
+
12
+ The SDK owns the versioned manifest, binding, safe path validation and resumable
13
+ same-filesystem rename helpers. Controller owns authenticated review, launch
14
+ checks, reservations, durable operation state and managed lifecycle fences.
15
+ The application owns data schema compatibility, its exclusive writer/lease,
16
+ maintenance semantics, replay and database acknowledgements. Runtime/tools own
17
+ durable mounts and backup/restore coverage. No Controller package-name branches
18
+ or execution of application code are required.
19
+
20
+ Publish `retained-store.manifest.json` at the installed package root. Give each
21
+ opaque resource a stable ID, a relative legacy path, kind and required flag.
22
+ Declare a format only when both versions can safely open the same data. An
23
+ explicit legacy version list permits sources without a manifest; it is a
24
+ compatibility promise by the application, not an automatic schema conversion.
25
+ Keep keys, tokens and startup configuration outside resource declarations.
26
+
27
+ The two example manifests in `examples/retained-store/` deliberately describe
28
+ different applications/formats. Whole data directories include SQLite WAL/SHM
29
+ sidecars without the Controller reading SQLite or interpreting payloads.
30
+ The npm core package includes this guide at `dist/runtime/retained-store-guide.md`
31
+ and the same manifests at `dist/runtime/retained-store-examples/`.
32
+
33
+ ```ts
34
+ import {
35
+ readRetainedStoreManifest,
36
+ resolveRetainedStoreResources,
37
+ openRetainedStoreLease,
38
+ } from '@uns-kit/core/runtime/retained-store.js';
39
+
40
+ const resolved = resolveRetainedStoreResources(instanceDirectory,
41
+ readRetainedStoreManifest(packageDirectory),
42
+ { rttNode: packageName, version: packageVersion, instanceId });
43
+ const lease = resolved.binding ? openRetainedStoreLease(resolved.binding) : undefined;
44
+ // Now open resolved payload resources and acquire application database leases.
45
+ // Keep the shared lease until writers close; call lease?.close() on shutdown.
46
+ // An attached store enters application maintenance before replay.
47
+ const dataDirectory = resolved.resources.queue;
48
+ ```
49
+
50
+ A binding is private and names an opaque store UUID, generation, operation,
51
+ owner, reviewed configuration digest and stable directory. The shared store
52
+ metadata must match it exactly. A stale owner or fenced instance fails closed.
53
+ The helper does not acquire the application's database lock or authorize an
54
+ operator. Do not bypass either check with an application-specific fallback.
55
+
56
+ ## Host procedure
57
+
58
+ 1. Resolve installed package/instance IDs server-side. Require matching portable
59
+ configuration, compatible declarations, exact compiled artifact fingerprints,
60
+ an unoccupied target and one filesystem. Preview never creates storage.
61
+ 2. Persist the reviewed selection/digest and, before moving, commit operation
62
+ intent. Require the reviewed source launch's clean physical exit according to
63
+ the supervisor; target must also be stopped. Reserve both managed instances.
64
+ 3. Use `prepareRetainedStoreMove` and `applyRetainedStoreMove(plan, 'adopt')`.
65
+ Persist the complete plan before applying it. Moves are atomic per resource,
66
+ fsynced, inode/device pinned and resumable; there is no copy or overwrite
67
+ fallback. Several resources do not form one filesystem transaction.
68
+ 4. Commit the binding/current owner and durable outcome before releasing the
69
+ receiving instance fence. Start only the new owner, initially in maintenance.
70
+ 5. After stopping that owner, capture `prepareRetainedStoreReturn(plan)` and
71
+ persist return intent. Return the *current* resources, including new optional
72
+ or atomically replaced control files. Never restore an old queue snapshot.
73
+
74
+ Reconcile a timeout by reading/repeating the same operation UUID. A different
75
+ request is not a substitute for recovery. Keep the old-side fence after success.
76
+ A direct manual launch of an older binary outside Controller can ignore a fence;
77
+ this protocol protects managed lifecycle and participating SDK consumers.
78
+
79
+ ## Deliberate limits
80
+
81
+ POSIX same-user, same-filesystem storage only. No cross-host transport, resource
82
+ merging, schema conversion, automatic deletion, retention finalization, or
83
+ exactly-once destination guarantee. Unknown write acknowledgements keep their
84
+ existing application semantics. Launches, configurations and artifacts changed
85
+ after review are rejected. An absent target requires the existing installation
86
+ workflow first.
87
+
88
+ Before production release, Runtime and backup tools must explicitly cover the
89
+ stable root, including a queue-excluding backup policy and full restore proof.
90
+ Moving a directory outside the previous archive layout is not backup coverage.
91
+ Consumers must be checked again against published, versioned registry packages;
92
+ a local file tarball is development evidence only.
93
+
94
+ ## Runtime storage and backup extension
95
+
96
+ A bound application must now explicitly declare `storageProtocol: 1` in its
97
+ manifest and acquire `openRetainedStoreLease(binding)` before opening payloads.
98
+ Keep that handle until final writer shutdown and close it then; the SDK also pins
99
+ live leases process-wide, bounded to32. This is a participation contract, not an
100
+ inference from an installed SDK version. Older unbound applications keep their
101
+ legacy behavior. Bound sources/targets lacking the declaration are rejected by
102
+ managed startup/adoption instead of being guessed safe for offline capture.
103
+ The storage program has been tested on Node 24 with POSIX local filesystems;
104
+ application payload formats remain opaque to it.
105
+
106
+ The trusted Runtime root is `UNS_RETAINED_STORE_ROOT`; managed adoption requires
107
+ it, and an existing binding must match it exactly. No automatic rebasing of old
108
+ absolute bindings is performed. `store-resources.json` records the declaration,
109
+ including optional per-resource `backup: "payload" | "metadata"` (default payload).
110
+ An incomplete store operation or root restore blocks startup. A returned legacy
111
+ layout leaves a validated empty-store retirement marker, rather than making old
112
+ bindings appear live.
113
+
114
+ The shared `retained-store-storage.mjs` program supplies leases, offline capture
115
+ and retryable restoration without importing application modules. Runtime tools
116
+ vendor its exact bytes/limits with provenance. Capture requires all bound owners
117
+ stopped; output/input journals retain their existing distinct online protocols.
118
+ The full RTT backup includes the extra capsule inside its existing signed,
119
+ age-encrypted payload. Queue-excluding capture reports excluded payloads and
120
+ cannot be used as a complete queue restore. Restore retains store/owner/generation
121
+ identity at the same logical root on another offline volume, and leaves an applying
122
+ fence until complete. Known private partial staging files may be re-created only
123
+ from the verified snapshot; differing published work is never overwritten.
124
+
125
+ ## Cross-mount transfer and recovery verification in 3.0.46
126
+
127
+ Same-filesystem rename remains the default.
128
+ For initial relocation to a separately mounted Runtime volume, the host may
129
+ explicitly select `transfer: "copy-verified"`. Stop the source cleanly **before**
130
+ reviewing this transport. Review pins content hashes, aggregate byte/file counts,
131
+ resource identities, both installed artifacts, configuration and supervisor epoch.
132
+ There is no automatic `EXDEV` fallback or caller-selected filesystem path.
133
+
134
+ The host uses `prepareRetainedStoreMoveAsync`, `applyRetainedStoreMoveAsync` and
135
+ `prepareRetainedStoreReturnAsync`. Bounded worker jobs perform opaque hashing and
136
+ copying while the host event loop can serve health and recovery reads. Persist the
137
+ complete plan before mutation and hold the existing maintenance/lifecycle fences
138
+ through the durable outcome. Jobs have a 128MiB old-generation heap and a 300s
139
+ bound; uncertainty always requires reconciliation of the same operation.
140
+
141
+ Copy stages and fsyncs each declared resource on its receiving mount, verifies
142
+ its exact contents and atomically publishes it. Source files remain unchanged and
143
+ fenced; allow space for both copies. Known incomplete scratch can be resumed from
144
+ the unchanged reviewed source. Unknown entries, links, changed source bytes and
145
+ differing published files are preserved and refused. The existing limits remain
146
+ 32 stores, 100,000 entries, 64GiB aggregate files and 32GiB per file.
147
+
148
+ Return copies the **current** stopped store back. If current work differs from
149
+ the original, the original is first preserved as
150
+ `.retained-original-<store UUID>-<resource ID>` beside its legacy path. The retired
151
+ stable copy remains inert and is included by full backup; it cannot acquire a
152
+ runtime lease. No cleanup, resource merge, conversion or cross-host transfer is
153
+ implied. Runtime disk capacity and the application's destination acknowledgement
154
+ semantics remain independent operational requirements.
155
+
156
+ Offline restore now retains a verified local snapshot manifest. The data-only
157
+ `verify-restored` / `verify-root` actions rehash exact declared payloads under an
158
+ exclusive root lease before Runtime activation. A completed restore retry never
159
+ replaces newer application data. Full RTT stage/activation/completion binds the
160
+ verified root, signed backup digest and local manifest digest into its protected
161
+ receipts. Missing, excluded, unfinished or altered storage cannot enable bound
162
+ owners. After application work advances, do not activate an old snapshot: preserve
163
+ current storage and use a fresh reviewed owner transition. Historic operation
164
+ plans pin original physical files and are not rebased after a full restore.
@@ -0,0 +1 @@
1
+ export function retainedStoreJob(job: unknown, onProgress?: (id: string) => void): Promise<unknown>;
@@ -0,0 +1,28 @@
1
+ import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
2
+ import { inspectRetainedCopy, applyRetainedCopy } from "./retained-store-copy.mjs";
3
+ if (!isMainThread) {
4
+ const job = workerData;
5
+ if (job.action === "inspect") parentPort.postMessage({ done: true, result: inspectRetainedCopy(job.path, job.kind, job.limits) });
6
+ else if (job.action === "apply") {
7
+ applyRetainedCopy(job.plan, job.direction, job.limits, id => parentPort.postMessage({ progress: id }));
8
+ parentPort.postMessage({ done: true });
9
+ } else throw Error("Unknown retained storage job");
10
+ }
11
+ export function retainedStoreJob(job, onProgress) {
12
+ if (!isMainThread) throw Error("Nested retained storage job");
13
+ return new Promise((resolve, reject) => {
14
+ const worker = new Worker(new URL(import.meta.url), { workerData: job, execArgv: [], env: { NODE_NO_WARNINGS: "1" }, resourceLimits: { maxOldGenerationSizeMb: 128 } });
15
+ let done = false;
16
+ const timer = setTimeout(() => finish(Error("Retained storage job timed out; reconcile the same operation")), 300000);
17
+ const finish = (error, result) => {
18
+ if (done) return; done = true; clearTimeout(timer);
19
+ void worker.terminate().finally(() => error ? reject(error) : resolve(result));
20
+ };
21
+ worker.on("message", message => {
22
+ if (message.progress) { try { onProgress?.(message.progress); } catch (error) { finish(error); } }
23
+ if (message.done) finish(undefined, message.result);
24
+ });
25
+ worker.once("error", error => finish(error));
26
+ worker.once("exit", () => { if (!done) finish(Error("Retained storage job exited without confirmation; reconcile the same operation")); });
27
+ });
28
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "maximumStores": 32,
4
+ "maximumEntries": 100000,
5
+ "maximumBytes": 68719476736,
6
+ "maximumManifestBytes": 33554432,
7
+ "maximumFileBytes": 34359738368,
8
+ "maximumDepth": 32
9
+ }
@@ -0,0 +1,24 @@
1
+ export function rootLease(root: string, exclusive?: boolean, restoring?: boolean): () => void;
2
+ export function maintenanceLease(directory: string): () => void;
3
+ export function runtimeLease(binding: unknown): { close: () => void };
4
+ export function captureRetained(
5
+ root: string,
6
+ destination: string,
7
+ mode: string,
8
+ limits: unknown,
9
+ ): { state: string; stores: number; files: number; payloadBytes: number };
10
+ export function restoreRetained(
11
+ source: string,
12
+ root: string,
13
+ backupDigest: string,
14
+ limits: unknown,
15
+ afterPublish?: (id: string) => void,
16
+ ): { state: string; stores: number };
17
+ export function available(root: string): void;
18
+ export function owned(path: string, directory?: boolean): import("node:fs").Stats;
19
+ export function sha(path: string): string;
20
+ export function sync(path: string): void;
21
+ export function relative(path: string): boolean;
22
+ export function checkLimit(limits: unknown): void;
23
+ export function verifyRestoredRetained(source:string,root:string,backupDigest:string,limits:unknown):{state:string;stores:number;backupDigest:string;manifestDigest:string;root:string};
24
+ export function verifyRetainedRecoveryRoot(root:string,backupDigest:string,manifestDigest:string,limits:unknown):{state:string;stores:number;backupDigest:string;manifestDigest:string;root:string};