@smthrs/artifacts 0.0.0-stage → 1.0.0-rc.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 +77 -0
- package/LICENSE +21 -0
- package/README.md +173 -2
- package/dist/cjs/ArtifactBackupLease.d.ts +34 -0
- package/dist/cjs/ArtifactBackupLease.d.ts.map +1 -0
- package/dist/cjs/ArtifactBackupLease.js +175 -0
- package/dist/cjs/ArtifactBackupLease.js.map +7 -0
- package/dist/cjs/ArtifactStore.d.ts +129 -0
- package/dist/cjs/ArtifactStore.d.ts.map +1 -0
- package/dist/cjs/ArtifactStore.js +73 -0
- package/dist/cjs/ArtifactStore.js.map +7 -0
- package/dist/cjs/ArtifactStoreError.d.ts +82 -0
- package/dist/cjs/ArtifactStoreError.d.ts.map +1 -0
- package/dist/cjs/ArtifactStoreError.js +72 -0
- package/dist/cjs/ArtifactStoreError.js.map +7 -0
- package/dist/cjs/ArtifactStoreMetrics.d.ts +70 -0
- package/dist/cjs/ArtifactStoreMetrics.d.ts.map +1 -0
- package/dist/cjs/ArtifactStoreMetrics.js +51 -0
- package/dist/cjs/ArtifactStoreMetrics.js.map +7 -0
- package/dist/cjs/ArtifactSweep.d.ts +185 -0
- package/dist/cjs/ArtifactSweep.d.ts.map +1 -0
- package/dist/cjs/ArtifactSweep.js +163 -0
- package/dist/cjs/ArtifactSweep.js.map +7 -0
- package/dist/cjs/CombinedArtifacts.d.ts +103 -0
- package/dist/cjs/CombinedArtifacts.d.ts.map +1 -0
- package/dist/cjs/CombinedArtifacts.js +189 -0
- package/dist/cjs/CombinedArtifacts.js.map +7 -0
- package/dist/cjs/FileLease.d.ts +113 -0
- package/dist/cjs/FileLease.d.ts.map +1 -0
- package/dist/cjs/FileLease.js +201 -0
- package/dist/cjs/FileLease.js.map +7 -0
- package/dist/cjs/FileSystemArtifactStore.d.ts +93 -0
- package/dist/cjs/FileSystemArtifactStore.d.ts.map +1 -0
- package/dist/cjs/FileSystemArtifactStore.js +276 -0
- package/dist/cjs/FileSystemArtifactStore.js.map +7 -0
- package/dist/cjs/MemoryArtifactStore.d.ts +25 -0
- package/dist/cjs/MemoryArtifactStore.d.ts.map +1 -0
- package/dist/cjs/MemoryArtifactStore.js +84 -0
- package/dist/cjs/MemoryArtifactStore.js.map +7 -0
- package/dist/cjs/NoopArtifactStore.d.ts +17 -0
- package/dist/cjs/NoopArtifactStore.d.ts.map +1 -0
- package/dist/cjs/NoopArtifactStore.js +46 -0
- package/dist/cjs/NoopArtifactStore.js.map +7 -0
- package/dist/cjs/RemoteArtifacts.d.ts +237 -0
- package/dist/cjs/RemoteArtifacts.d.ts.map +1 -0
- package/dist/cjs/RemoteArtifacts.js +405 -0
- package/dist/cjs/RemoteArtifacts.js.map +7 -0
- package/dist/cjs/index.d.ts +65 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/ArtifactLocks.d.ts +30 -0
- package/dist/cjs/internal/ArtifactLocks.d.ts.map +1 -0
- package/dist/cjs/internal/ArtifactLocks.js +93 -0
- package/dist/cjs/internal/ArtifactLocks.js.map +7 -0
- package/dist/cjs/internal/ArtifactPath.d.ts +19 -0
- package/dist/cjs/internal/ArtifactPath.d.ts.map +1 -0
- package/dist/cjs/internal/ArtifactPath.js +74 -0
- package/dist/cjs/internal/ArtifactPath.js.map +7 -0
- package/dist/cjs/measureBytes.d.ts +18 -0
- package/dist/cjs/measureBytes.d.ts.map +1 -0
- package/dist/cjs/measureBytes.js +43 -0
- package/dist/cjs/measureBytes.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/snapshotBytes.d.ts +19 -0
- package/dist/cjs/snapshotBytes.d.ts.map +1 -0
- package/dist/cjs/snapshotBytes.js +41 -0
- package/dist/cjs/snapshotBytes.js.map +7 -0
- package/dist/cjs/validateDigest.d.ts +22 -0
- package/dist/cjs/validateDigest.d.ts.map +1 -0
- package/dist/cjs/validateDigest.js +42 -0
- package/dist/cjs/validateDigest.js.map +7 -0
- package/dist/esm/ArtifactBackupLease.d.ts +34 -0
- package/dist/esm/ArtifactBackupLease.d.ts.map +1 -0
- package/dist/esm/ArtifactBackupLease.js +117 -0
- package/dist/esm/ArtifactBackupLease.js.map +1 -0
- package/dist/esm/ArtifactStore.d.ts +129 -0
- package/dist/esm/ArtifactStore.d.ts.map +1 -0
- package/dist/esm/ArtifactStore.js +83 -0
- package/dist/esm/ArtifactStore.js.map +1 -0
- package/dist/esm/ArtifactStoreError.d.ts +82 -0
- package/dist/esm/ArtifactStoreError.d.ts.map +1 -0
- package/dist/esm/ArtifactStoreError.js +77 -0
- package/dist/esm/ArtifactStoreError.js.map +1 -0
- package/dist/esm/ArtifactStoreMetrics.d.ts +70 -0
- package/dist/esm/ArtifactStoreMetrics.d.ts.map +1 -0
- package/dist/esm/ArtifactStoreMetrics.js +77 -0
- package/dist/esm/ArtifactStoreMetrics.js.map +1 -0
- package/dist/esm/ArtifactSweep.d.ts +185 -0
- package/dist/esm/ArtifactSweep.d.ts.map +1 -0
- package/dist/esm/ArtifactSweep.js +202 -0
- package/dist/esm/ArtifactSweep.js.map +1 -0
- package/dist/esm/CombinedArtifacts.d.ts +103 -0
- package/dist/esm/CombinedArtifacts.d.ts.map +1 -0
- package/dist/esm/CombinedArtifacts.js +214 -0
- package/dist/esm/CombinedArtifacts.js.map +1 -0
- package/dist/esm/FileLease.d.ts +113 -0
- package/dist/esm/FileLease.d.ts.map +1 -0
- package/dist/esm/FileLease.js +239 -0
- package/dist/esm/FileLease.js.map +1 -0
- package/dist/esm/FileSystemArtifactStore.d.ts +93 -0
- package/dist/esm/FileSystemArtifactStore.d.ts.map +1 -0
- package/dist/esm/FileSystemArtifactStore.js +336 -0
- package/dist/esm/FileSystemArtifactStore.js.map +1 -0
- package/dist/esm/MemoryArtifactStore.d.ts +25 -0
- package/dist/esm/MemoryArtifactStore.d.ts.map +1 -0
- package/dist/esm/MemoryArtifactStore.js +67 -0
- package/dist/esm/MemoryArtifactStore.js.map +1 -0
- package/dist/esm/NoopArtifactStore.d.ts +17 -0
- package/dist/esm/NoopArtifactStore.d.ts.map +1 -0
- package/dist/esm/NoopArtifactStore.js +27 -0
- package/dist/esm/NoopArtifactStore.js.map +1 -0
- package/dist/esm/RemoteArtifacts.d.ts +237 -0
- package/dist/esm/RemoteArtifacts.d.ts.map +1 -0
- package/dist/esm/RemoteArtifacts.js +554 -0
- package/dist/esm/RemoteArtifacts.js.map +1 -0
- package/dist/esm/index.d.ts +65 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/ArtifactLocks.d.ts +30 -0
- package/dist/esm/internal/ArtifactLocks.d.ts.map +1 -0
- package/dist/esm/internal/ArtifactLocks.js +86 -0
- package/dist/esm/internal/ArtifactLocks.js.map +1 -0
- package/dist/esm/internal/ArtifactPath.d.ts +19 -0
- package/dist/esm/internal/ArtifactPath.d.ts.map +1 -0
- package/dist/esm/internal/ArtifactPath.js +59 -0
- package/dist/esm/internal/ArtifactPath.js.map +1 -0
- package/dist/esm/measureBytes.d.ts +18 -0
- package/dist/esm/measureBytes.d.ts.map +1 -0
- package/dist/esm/measureBytes.js +22 -0
- package/dist/esm/measureBytes.js.map +1 -0
- package/dist/esm/snapshotBytes.d.ts +19 -0
- package/dist/esm/snapshotBytes.d.ts.map +1 -0
- package/dist/esm/snapshotBytes.js +30 -0
- package/dist/esm/snapshotBytes.js.map +1 -0
- package/dist/esm/validateDigest.d.ts +22 -0
- package/dist/esm/validateDigest.d.ts.map +1 -0
- package/dist/esm/validateDigest.js +26 -0
- package/dist/esm/validateDigest.js.map +1 -0
- package/docs/README.md +200 -0
- package/docs/api.md +474 -0
- package/docs/concepts/content-addressing.md +174 -0
- package/docs/concepts/coordination.md +141 -0
- package/docs/concepts/tiers.md +147 -0
- package/docs/guides/fence-a-backup.md +123 -0
- package/docs/guides/reclaim-disk-space.md +143 -0
- package/docs/guides/serve-the-artifact-protocol.md +140 -0
- package/docs/guides/share-artifacts-across-machines.md +180 -0
- package/docs/guides/test-against-an-artifact-store.md +158 -0
- package/docs/installation.md +98 -0
- package/docs/quickstart.md +182 -0
- package/docs/troubleshooting.md +232 -0
- package/package.json +179 -3
- package/src/ArtifactBackupLease.ts +213 -0
- package/src/ArtifactStore.ts +152 -0
- package/src/ArtifactStoreError.ts +96 -0
- package/src/ArtifactStoreMetrics.ts +83 -0
- package/src/ArtifactSweep.ts +336 -0
- package/src/CombinedArtifacts.ts +369 -0
- package/src/FileLease.ts +380 -0
- package/src/FileSystemArtifactStore.ts +459 -0
- package/src/MemoryArtifactStore.ts +83 -0
- package/src/NoopArtifactStore.ts +30 -0
- package/src/RemoteArtifacts.ts +809 -0
- package/src/index.ts +71 -0
- package/src/internal/ArtifactLocks.ts +110 -0
- package/src/internal/ArtifactPath.ts +72 -0
- package/src/measureBytes.ts +28 -0
- package/src/snapshotBytes.ts +34 -0
- package/src/validateDigest.ts +31 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cross-process lease on one lock file.
|
|
3
|
+
*
|
|
4
|
+
* The lock is a file created with `wx` (exclusive create) that holds a unique
|
|
5
|
+
* owner token. The holder refreshes its mtime every `heartbeatEvery`; a lock
|
|
6
|
+
* whose mtime is older than `staleAfterMs` belongs to a crashed or hard-killed
|
|
7
|
+
* owner and is reclaimed. Reclaimers of one lock generation race for a `wx`
|
|
8
|
+
* claim file first, so a stale generation is moved away at most once and a
|
|
9
|
+
* fresh lock is never displaced. Acquisition is bounded by `acquireWithin`,
|
|
10
|
+
* so a waiter fails with a typed error instead of waiting forever on a lock
|
|
11
|
+
* nothing will release.
|
|
12
|
+
*
|
|
13
|
+
* | Setting | Default |
|
|
14
|
+
* | ---------------- | ----------- |
|
|
15
|
+
* | `heartbeatEvery` | 10 seconds |
|
|
16
|
+
* | `staleAfterMs` | 60 000 ms |
|
|
17
|
+
* | `acquireWithin` | 2 minutes |
|
|
18
|
+
* | `retryEvery` | 25 millis |
|
|
19
|
+
*
|
|
20
|
+
* The fence is bounded, not absolute: a holder stalled past `staleAfterMs`
|
|
21
|
+
* (a suspended laptop, a stopped debugger) is reaped while it still runs. Its
|
|
22
|
+
* heartbeat then logs `<label> was reclaimed while its holder was still
|
|
23
|
+
* running`, and the protected effect continues unfenced.
|
|
24
|
+
*
|
|
25
|
+
* A directory at the lock path, left by a release that used an exclusive
|
|
26
|
+
* `makeDirectory` as its lock, is treated as a lock with no readable owner:
|
|
27
|
+
* it is reclaimed once its mtime is past `staleAfterMs`, and waited on
|
|
28
|
+
* before that.
|
|
29
|
+
*
|
|
30
|
+
* The host needs exclusive `wx` file creation, `readFileString`, `stat` with
|
|
31
|
+
* an mtime, `utimes`, `rename`, and `remove`. The claim file and the tombstone
|
|
32
|
+
* a reclaim renames the stale lock to are siblings of the lock:
|
|
33
|
+
* `<lockPath>.reclaim-<token>` and `<lockPath>.stale-<token>`.
|
|
34
|
+
*
|
|
35
|
+
* @since 1.0.0-rc.1
|
|
36
|
+
*/
|
|
37
|
+
import * as Clock from "effect/Clock";
|
|
38
|
+
import * as Deferred from "effect/Deferred";
|
|
39
|
+
import * as Effect from "effect/Effect";
|
|
40
|
+
import * as Option from "effect/Option";
|
|
41
|
+
import * as PlatformError from "effect/PlatformError";
|
|
42
|
+
import * as Random from "effect/Random";
|
|
43
|
+
/**
|
|
44
|
+
* The default acquisition deadline.
|
|
45
|
+
*
|
|
46
|
+
* @category constants
|
|
47
|
+
* @since 1.0.0-rc.1
|
|
48
|
+
*/
|
|
49
|
+
export const defaultAcquireWithin = "2 minutes";
|
|
50
|
+
/**
|
|
51
|
+
* The default stale bound, in milliseconds.
|
|
52
|
+
*
|
|
53
|
+
* @category constants
|
|
54
|
+
* @since 1.0.0-rc.1
|
|
55
|
+
*/
|
|
56
|
+
export const defaultStaleAfterMs = 60_000;
|
|
57
|
+
const isReason = (cause, tag) => cause instanceof PlatformError.PlatformError && cause.reason._tag === tag;
|
|
58
|
+
/** The generation name of a lock with no readable owner: a directory. */
|
|
59
|
+
const directoryGeneration = "legacy-directory";
|
|
60
|
+
const token = Effect.gen(function* () {
|
|
61
|
+
const first = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true });
|
|
62
|
+
const second = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true });
|
|
63
|
+
return `${first.toString(36)}-${second.toString(36)}-${yield* Clock.currentTimeMillis}`;
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* Runs `effect` while holding the lease at `lockPath`, with no acquisition
|
|
67
|
+
* deadline. Use it when the caller already bounds a wider wait (an
|
|
68
|
+
* in-process semaphore plus this lease) with its own deadline; otherwise use
|
|
69
|
+
* {@link withLease}.
|
|
70
|
+
*
|
|
71
|
+
* @category combinators
|
|
72
|
+
* @since 1.0.0-rc.1
|
|
73
|
+
*/
|
|
74
|
+
export const hold = (fs, lockPath, effect, failure, options = {}) => Effect.gen(function* () {
|
|
75
|
+
const label = options.label ?? "File lease";
|
|
76
|
+
const heartbeatEvery = options.heartbeatEvery ?? "10 seconds";
|
|
77
|
+
const staleAfterMs = options.staleAfterMs ?? defaultStaleAfterMs;
|
|
78
|
+
const retryEvery = options.retryEvery ?? "25 millis";
|
|
79
|
+
const owner = yield* token;
|
|
80
|
+
/**
|
|
81
|
+
* Whether this call created the lock file, and therefore owes a release.
|
|
82
|
+
* It is set in the same uninterruptible step that creates the file, so
|
|
83
|
+
* interruption striking between the two cannot leak a lock nothing
|
|
84
|
+
* releases — the only party that would ever reclaim it is a later
|
|
85
|
+
* acquirer of the same path, which may never come.
|
|
86
|
+
*/
|
|
87
|
+
let acquired = false;
|
|
88
|
+
const guard = options.guard === undefined ? Effect.void : options.guard.pipe(Effect.mapError(failure));
|
|
89
|
+
const guarded = (mutation) => Effect.andThen(guard, mutation);
|
|
90
|
+
const statIfPresent = (path) => fs.stat(path).pipe(Effect.map(Option.some), Effect.catch((cause) => isReason(cause, "NotFound") ? Effect.succeed(Option.none()) : Effect.fail(failure(cause))));
|
|
91
|
+
const refuse = (cause) => Effect.fail(failure(cause));
|
|
92
|
+
const readOwner = (path, onRefused = refuse) => fs.readFileString(path).pipe(Effect.map(Option.some), Effect.catch((cause) => isReason(cause, "NotFound") ? Effect.succeed(Option.none()) : onRefused(cause)));
|
|
93
|
+
/**
|
|
94
|
+
* Names the lock generation at `path`: its owner token, or
|
|
95
|
+
* `directoryGeneration` for a directory, which has no readable owner.
|
|
96
|
+
* A read refused for any other reason is propagated, not guessed at.
|
|
97
|
+
*/
|
|
98
|
+
const observe = (path) => readOwner(path, (cause) => fs.stat(path).pipe(Effect.matchEffect({
|
|
99
|
+
onFailure: () => refuse(cause),
|
|
100
|
+
onSuccess: (info) => info.type === "Directory" ? Effect.succeed(Option.some(directoryGeneration)) : refuse(cause)
|
|
101
|
+
})));
|
|
102
|
+
const isStale = (path) => Effect.gen(function* () {
|
|
103
|
+
const info = yield* statIfPresent(path);
|
|
104
|
+
const modified = Option.isSome(info) ? Option.getOrUndefined(info.value.mtime) : undefined;
|
|
105
|
+
return modified !== undefined && (yield* Clock.currentTimeMillis) - modified.getTime() > staleAfterMs;
|
|
106
|
+
});
|
|
107
|
+
const removeIfOwnedBy = (path, expected) => fs.readFileString(path).pipe(Effect.flatMap((found) => found === expected ? guarded(fs.remove(path)) : Effect.void), Effect.ignore);
|
|
108
|
+
const acquire = Effect.gen(function* () {
|
|
109
|
+
while (true) {
|
|
110
|
+
const created = yield* Effect.uninterruptible(guarded(fs.writeFileString(lockPath, owner, { flag: "wx", mode: 0o600 })).pipe(Effect.andThen(Effect.sync(() => {
|
|
111
|
+
acquired = true;
|
|
112
|
+
return true;
|
|
113
|
+
})), Effect.catch((cause) => isReason(cause, "AlreadyExists") ? Effect.succeed(false) : Effect.fail(failure(cause)))));
|
|
114
|
+
if (created)
|
|
115
|
+
return;
|
|
116
|
+
// The owner is read before the age, so a stale verdict always
|
|
117
|
+
// belongs to the lock generation this read named.
|
|
118
|
+
const observed = yield* observe(lockPath);
|
|
119
|
+
if (Option.isNone(observed))
|
|
120
|
+
continue;
|
|
121
|
+
if (yield* isStale(lockPath)) {
|
|
122
|
+
const reclaimed = yield* reclaim(observed.value);
|
|
123
|
+
if (reclaimed)
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
yield* Effect.sleep(retryEvery);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
/**
|
|
130
|
+
* Moves away the stale lock generation `observed` names, at most once.
|
|
131
|
+
*
|
|
132
|
+
* Measuring a lock and renaming it are two steps, so two contenders
|
|
133
|
+
* that both measure the same generation as stale would otherwise both
|
|
134
|
+
* rename, and the second would move away the fresh lock the first just
|
|
135
|
+
* took. Reclaimers of one generation therefore race for a `wx` claim
|
|
136
|
+
* named after its owner token first. Only the winner renames, and only
|
|
137
|
+
* after re-reading that the path still holds that stale generation. A
|
|
138
|
+
* loser, or a winner whose generation is already gone, moves nothing.
|
|
139
|
+
* Owner tokens are unique per acquisition, so a claim never outlives
|
|
140
|
+
* the generation it names in any way that matters.
|
|
141
|
+
*/
|
|
142
|
+
const reclaim = (observed) => {
|
|
143
|
+
const claimPath = `${lockPath}.reclaim-${observed.replace(/[^0-9A-Za-z-]/g, "_").slice(0, 96)}`;
|
|
144
|
+
return Effect.acquireUseRelease(guarded(fs.writeFileString(claimPath, owner, { flag: "wx", mode: 0o600 })).pipe(Effect.as(true), Effect.catch((cause) => isReason(cause, "AlreadyExists") ? Effect.succeed(false) : Effect.fail(failure(cause)))), (claimed) => Effect.gen(function* () {
|
|
145
|
+
if (!claimed) {
|
|
146
|
+
// A claimant that died mid-reclaim leaves its claim behind.
|
|
147
|
+
// It ages out on the same bound as a lock.
|
|
148
|
+
const holder = yield* readOwner(claimPath);
|
|
149
|
+
if (Option.isSome(holder) && (yield* isStale(claimPath))) {
|
|
150
|
+
yield* removeIfOwnedBy(claimPath, holder.value);
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const current = yield* observe(lockPath);
|
|
155
|
+
if (Option.isNone(current) || current.value !== observed || !(yield* isStale(lockPath)))
|
|
156
|
+
return true;
|
|
157
|
+
const tombstone = `${lockPath}.stale-${owner}`;
|
|
158
|
+
const moved = yield* guarded(fs.rename(lockPath, tombstone)).pipe(Effect.as(true), Effect.catch((cause) => isReason(cause, "NotFound") ? Effect.succeed(false) : Effect.fail(failure(cause))));
|
|
159
|
+
if (!moved)
|
|
160
|
+
return true;
|
|
161
|
+
// Only a stalled owner releasing between the re-read and the
|
|
162
|
+
// rename, and a new owner acquiring in that gap, puts a live
|
|
163
|
+
// lock here. Hand it back with an atomic create, which cannot
|
|
164
|
+
// displace anyone who took the path since.
|
|
165
|
+
const displaced = yield* observe(tombstone);
|
|
166
|
+
if (Option.isSome(displaced) && displaced.value !== observed && displaced.value !== directoryGeneration) {
|
|
167
|
+
yield* guarded(fs.writeFileString(lockPath, displaced.value, { flag: "wx", mode: 0o600 })).pipe(Effect.ignore);
|
|
168
|
+
}
|
|
169
|
+
yield* guarded(fs.remove(tombstone, { recursive: true })).pipe(Effect.ignore);
|
|
170
|
+
return true;
|
|
171
|
+
}), (claimed) => claimed ? removeIfOwnedBy(claimPath, owner) : Effect.void);
|
|
172
|
+
};
|
|
173
|
+
const release = fs.readFileString(lockPath).pipe(Effect.flatMap((found) => found === owner ? guarded(fs.remove(lockPath)) : Effect.void),
|
|
174
|
+
// A concurrent stale-lock reaper can win release. `NotFound`
|
|
175
|
+
// means no lock remains for this owner to release or leak.
|
|
176
|
+
Effect.catch((cause) => isReason(cause, "NotFound") ? Effect.void : Effect.fail(failure(cause))));
|
|
177
|
+
// The finalizer is attached around acquisition itself, not just around
|
|
178
|
+
// the protected effect, so a lock created moments before an interruption
|
|
179
|
+
// is still released. A call that never created one owes nothing and must
|
|
180
|
+
// not touch a file another owner holds.
|
|
181
|
+
return yield* acquire.pipe(Effect.andThen(options.onAcquired ?? Effect.void), Effect.andThen(Effect.scoped(Effect.gen(function* () {
|
|
182
|
+
yield* Effect.forkScoped(Effect.gen(function* () {
|
|
183
|
+
while (true) {
|
|
184
|
+
yield* Effect.sleep(heartbeatEvery);
|
|
185
|
+
// Freshen only a lock this call still owns, the same comparison
|
|
186
|
+
// the release below makes. A holder stalled past the stale
|
|
187
|
+
// bound has already been reaped and replaced, and touching that
|
|
188
|
+
// pathname anyway would hold a stranger's lock fresh forever: a
|
|
189
|
+
// replacement that is then hard-killed could never be judged
|
|
190
|
+
// stale while this zombie ran, so every later acquirer would
|
|
191
|
+
// burn the full acquisition deadline and fail.
|
|
192
|
+
const beat = yield* fs.readFileString(lockPath).pipe(Effect.map((found) => found === owner ? "own" : "foreign"), Effect.catch((cause) => Effect.succeed(isReason(cause, "NotFound") ? "gone" : "unreadable")));
|
|
193
|
+
if (beat === "own") {
|
|
194
|
+
const timestamp = new Date(yield* Clock.currentTimeMillis);
|
|
195
|
+
yield* Effect.ignore(guarded(fs.utimes(lockPath, timestamp, timestamp)));
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
// A read the host refused for any other reason is no evidence
|
|
199
|
+
// either way, so this beat is skipped and the next one retries.
|
|
200
|
+
// Ending the heartbeat on it would retire a lock this call
|
|
201
|
+
// still holds: the file goes stale within the minute, another
|
|
202
|
+
// process reaps it, and this holder keeps working unfenced.
|
|
203
|
+
if (beat === "unreadable")
|
|
204
|
+
continue;
|
|
205
|
+
// `gone` and `foreign` are the two states worth an operator
|
|
206
|
+
// signal: this call's lock was reaped, and possibly already
|
|
207
|
+
// replaced, while the effect it fences is still running, so
|
|
208
|
+
// the rest of that effect proceeds unfenced against every other
|
|
209
|
+
// process. The heartbeat is the only party that ever observes
|
|
210
|
+
// it — the protected effect is not interrupted and the caller
|
|
211
|
+
// is not failed — so retiring the fiber silently would leave no
|
|
212
|
+
// trace at all.
|
|
213
|
+
yield* Effect.logWarning(`${label} was reclaimed while its holder was still running`, { ...options.annotations, state: beat });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}));
|
|
217
|
+
return yield* effect;
|
|
218
|
+
}))), Effect.onExit(() => acquired ? release : Effect.void));
|
|
219
|
+
});
|
|
220
|
+
/**
|
|
221
|
+
* Runs `effect` while holding the lease at `lockPath`.
|
|
222
|
+
*
|
|
223
|
+
* Acquisition fails with `failure(TimeoutError)` once `acquireWithin` passes
|
|
224
|
+
* without the lease; every other host refusal reaches `failure` with the
|
|
225
|
+
* refusing `PlatformError`. The deadline ends when the lease is held: the
|
|
226
|
+
* protected effect and its release are never timed out.
|
|
227
|
+
*
|
|
228
|
+
* @category combinators
|
|
229
|
+
* @since 1.0.0-rc.1
|
|
230
|
+
*/
|
|
231
|
+
export const withLease = (fs, lockPath, effect, failure, options = {}) => Effect.suspend(() => {
|
|
232
|
+
const ready = Deferred.makeUnsafe();
|
|
233
|
+
const deadline = Deferred.await(ready).pipe(Effect.timeout(options.acquireWithin ?? defaultAcquireWithin), Effect.catchTag("TimeoutError", (cause) => Effect.fail(failure(cause))), Effect.andThen(Effect.never));
|
|
234
|
+
return hold(fs, lockPath, effect, failure, {
|
|
235
|
+
...options,
|
|
236
|
+
onAcquired: Deferred.succeed(ready, undefined).pipe(Effect.asVoid)
|
|
237
|
+
}).pipe(Effect.raceFirst(deadline));
|
|
238
|
+
});
|
|
239
|
+
//# sourceMappingURL=FileLease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileLease.js","sourceRoot":"","sources":["../../src/FileLease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAE3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAyCvC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAmB,WAAW,CAAA;AAE/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAA;AAEzC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAE,GAAiC,EAAW,EAAE,CAC9E,KAAK,YAAY,aAAa,CAAC,aAAa,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,GAAG,CAAA;AAE3E,yEAAyE;AACzE,MAAM,mBAAmB,GAAG,kBAAkB,CAAA;AAE9C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1F,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3F,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAA;AACzF,CAAC,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,EAAyB,EACzB,QAAgB,EAChB,MAA8B,EAC9B,OAA+B,EAC/B,UAAuB,EAAE,EACI,EAAE,CAC/B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAA;IAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,YAAY,CAAA;IAC7D,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,mBAAmB,CAAA;IAChE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,WAAW,CAAA;IACpD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAA;IAC1B;;;;;;OAMG;IACH,IAAI,QAAQ,GAAG,KAAK,CAAA;IAEpB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;IACtG,MAAM,OAAO,GAAG,CAAU,QAAgC,EAA+B,EAAE,CACzF,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IAEjC,MAAM,aAAa,GAAG,CAAC,IAAY,EAA0D,EAAE,CAC7F,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAChB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EACvB,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA0D,EAAE,CAC7E,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAC1F,CACF,CAAA;IAEH,MAAM,MAAM,GAAG,CAAC,KAAc,EAA4B,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IAExF,MAAM,SAAS,GAAG,CAChB,IAAY,EACZ,YAA0E,MAAM,EACtC,EAAE,CAC5C,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAC1B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EACvB,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CACxG,CAAA;IAEH;;;;OAIG;IACH,MAAM,OAAO,GAAG,CAAC,IAAY,EAA4C,EAAE,CACzE,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACxB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAChB,MAAM,CAAC,WAAW,CAAC;QACjB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;QAC9B,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAClB,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KAC/F,CAAC,CACH,CAAC,CAAA;IAEN,MAAM,OAAO,GAAG,CAAC,IAAY,EAA8B,EAAE,CAC3D,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;QACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAC1F,OAAO,QAAQ,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,YAAY,CAAA;IACvG,CAAC,CAAC,CAAA;IAEJ,MAAM,eAAe,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAuB,EAAE,CAC9E,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAC1B,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EACtF,MAAM,CAAC,MAAM,CACd,CAAA;IAEH,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClC,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,eAAe,CAC3C,OAAO,CAAC,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAC5E,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC9B,QAAQ,GAAG,IAAI,CAAA;gBACf,OAAO,IAAI,CAAA;YACb,CAAC,CAAC,CAAC,EACH,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA8B,EAAE,CACjD,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACvF,CACF,CACF,CAAA;YACD,IAAI,OAAO;gBAAE,OAAM;YAEnB,8DAA8D;YAC9D,kDAAkD;YAClD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACzC,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;gBAAE,SAAQ;YACrC,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBAChD,IAAI,SAAS;oBAAE,SAAQ;YACzB,CAAC;YACD,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;QACjC,CAAC;IACH,CAAC,CAAC,CAAA;IAEF;;;;;;;;;;;;OAYG;IACH,MAAM,OAAO,GAAG,CAAC,QAAgB,EAA8B,EAAE;QAC/D,MAAM,SAAS,GAAG,GAAG,QAAQ,YAAY,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAA;QAC/F,OAAO,MAAM,CAAC,iBAAiB,CAC7B,OAAO,CAAC,EAAE,CAAC,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAC7E,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EACf,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA8B,EAAE,CACjD,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACvF,CACF,EACD,CAAC,OAAO,EAAE,EAAE,CACV,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,4DAA4D;gBAC5D,2CAA2C;gBAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;gBAC1C,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;oBACzD,KAAK,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;gBACjD,CAAC;gBACD,OAAO,KAAK,CAAA;YACd,CAAC;YACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACxC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAA;YACpG,MAAM,SAAS,GAAG,GAAG,QAAQ,UAAU,KAAK,EAAE,CAAA;YAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAC/D,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EACf,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA8B,EAAE,CACjD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAClF,CACF,CAAA;YACD,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAA;YACvB,6DAA6D;YAC7D,6DAA6D;YAC7D,8DAA8D;YAC9D,2CAA2C;YAC3C,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YAC3C,IACE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,KAAK,KAAK,QAAQ,IAAI,SAAS,CAAC,KAAK,KAAK,mBAAmB,EACnG,CAAC;gBACD,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAC7F,MAAM,CAAC,MAAM,CACd,CAAA;YACH,CAAC;YACD,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC7E,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,EACJ,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CACvE,CAAA;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,IAAI,CAC9C,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IACvF,6DAA6D;IAC7D,2DAA2D;IAC3D,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA2B,EAAE,CAC9C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACxE,CACF,CAAA;IAED,uEAAuE;IACvE,yEAAyE;IACzE,yEAAyE;IACzE,wCAAwC;IACxC,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CACxB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,EACjD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC/C,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,OAAO,IAAI,EAAE,CAAC;gBACZ,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;gBACnC,gEAAgE;gBAChE,2DAA2D;gBAC3D,gEAAgE;gBAChE,gEAAgE;gBAChE,6DAA6D;gBAC7D,6DAA6D;gBAC7D,+CAA+C;gBAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,IAAI,CAClD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,KAAc,CAAC,CAAC,CAAC,SAAkB,CAAC,EAC5E,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACrB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAe,CAAC,CAAC,CAAC,YAAqB,CAAC,CACtF,CACF,CAAA;gBACD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;oBACnB,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;oBAC1D,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;oBACxE,SAAQ;gBACV,CAAC;gBACD,8DAA8D;gBAC9D,gEAAgE;gBAChE,2DAA2D;gBAC3D,8DAA8D;gBAC9D,4DAA4D;gBAC5D,IAAI,IAAI,KAAK,YAAY;oBAAE,SAAQ;gBACnC,4DAA4D;gBAC5D,4DAA4D;gBAC5D,4DAA4D;gBAC5D,gEAAgE;gBAChE,8DAA8D;gBAC9D,8DAA8D;gBAC9D,gEAAgE;gBAChE,gBAAgB;gBAChB,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,GAAG,KAAK,mDAAmD,EAC3D,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,CACxC,CAAA;gBACD,OAAM;YACR,CAAC;QACH,CAAC,CAAC,CACH,CAAA;QACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAA;IACtB,CAAC,CAAC,CAAC,CAAC,EACJ,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CACtD,CAAA;AACH,CAAC,CAAC,CAAA;AAEJ;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,EAAyB,EACzB,QAAgB,EAChB,MAA8B,EAC9B,OAA+B,EAC/B,UAAmB,EAAE,EACQ,EAAE,CAC/B,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;IAClB,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,EAAQ,CAAA;IACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CACzC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,IAAI,oBAAoB,CAAC,EAC7D,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EACvE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAC7B,CAAA;IACD,OAAO,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QACzC,GAAG,OAAO;QACV,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;KACnE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAA;AACrC,CAAC,CAAC,CAAA","sourcesContent":["/**\n * A cross-process lease on one lock file.\n *\n * The lock is a file created with `wx` (exclusive create) that holds a unique\n * owner token. The holder refreshes its mtime every `heartbeatEvery`; a lock\n * whose mtime is older than `staleAfterMs` belongs to a crashed or hard-killed\n * owner and is reclaimed. Reclaimers of one lock generation race for a `wx`\n * claim file first, so a stale generation is moved away at most once and a\n * fresh lock is never displaced. Acquisition is bounded by `acquireWithin`,\n * so a waiter fails with a typed error instead of waiting forever on a lock\n * nothing will release.\n *\n * | Setting | Default |\n * | ---------------- | ----------- |\n * | `heartbeatEvery` | 10 seconds |\n * | `staleAfterMs` | 60 000 ms |\n * | `acquireWithin` | 2 minutes |\n * | `retryEvery` | 25 millis |\n *\n * The fence is bounded, not absolute: a holder stalled past `staleAfterMs`\n * (a suspended laptop, a stopped debugger) is reaped while it still runs. Its\n * heartbeat then logs `<label> was reclaimed while its holder was still\n * running`, and the protected effect continues unfenced.\n *\n * A directory at the lock path, left by a release that used an exclusive\n * `makeDirectory` as its lock, is treated as a lock with no readable owner:\n * it is reclaimed once its mtime is past `staleAfterMs`, and waited on\n * before that.\n *\n * The host needs exclusive `wx` file creation, `readFileString`, `stat` with\n * an mtime, `utimes`, `rename`, and `remove`. The claim file and the tombstone\n * a reclaim renames the stale lock to are siblings of the lock:\n * `<lockPath>.reclaim-<token>` and `<lockPath>.stale-<token>`.\n *\n * @since 1.0.0-rc.1\n */\n\nimport * as Clock from \"effect/Clock\"\nimport * as Deferred from \"effect/Deferred\"\nimport type * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport type * as FileSystem from \"effect/FileSystem\"\nimport * as Option from \"effect/Option\"\nimport * as PlatformError from \"effect/PlatformError\"\nimport * as Random from \"effect/Random\"\n\n/**\n * Tuning for one lease.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Options {\n /** Names the lock in the reclaimed-holder warning. Defaults to `\"File lease\"`. */\n readonly label?: string | undefined\n /** Extra fields logged with the reclaimed-holder warning. */\n readonly annotations?: Readonly<Record<string, unknown>> | undefined\n /** How often the holder refreshes the lock's mtime. Defaults to 10 seconds. */\n readonly heartbeatEvery?: Duration.Input | undefined\n /** Age past which a lock or claim is stale. Defaults to 60 000 ms. */\n readonly staleAfterMs?: number | undefined\n /** How long acquisition may wait. Defaults to 2 minutes. */\n readonly acquireWithin?: Duration.Input | undefined\n /** How long a waiter sleeps between attempts. Defaults to 25 millis. */\n readonly retryEvery?: Duration.Input | undefined\n}\n\n/**\n * {@link Options} for {@link hold}, which leaves the deadline to its caller.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface HoldOptions extends Omit<Options, \"acquireWithin\"> {\n /** Runs once, the moment the lease is held and before `effect` starts. */\n readonly onAcquired?: Effect.Effect<void> | undefined\n /**\n * Runs before every create, heartbeat, rename, and remove of the lock, its\n * claim, or its tombstone. A failure refuses that step through `failure`,\n * so a caller can re-check that the lock directory was not replaced (for\n * example by a symlink) after it was created.\n */\n readonly guard?: Effect.Effect<void, unknown> | undefined\n}\n\n/**\n * The default acquisition deadline.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const defaultAcquireWithin: Duration.Input = \"2 minutes\"\n\n/**\n * The default stale bound, in milliseconds.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const defaultStaleAfterMs = 60_000\n\nconst isReason = (cause: unknown, tag: PlatformError.SystemErrorTag): boolean =>\n cause instanceof PlatformError.PlatformError && cause.reason._tag === tag\n\n/** The generation name of a lock with no readable owner: a directory. */\nconst directoryGeneration = \"legacy-directory\"\n\nconst token = Effect.gen(function*() {\n const first = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true })\n const second = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true })\n return `${first.toString(36)}-${second.toString(36)}-${yield* Clock.currentTimeMillis}`\n})\n\n/**\n * Runs `effect` while holding the lease at `lockPath`, with no acquisition\n * deadline. Use it when the caller already bounds a wider wait (an\n * in-process semaphore plus this lease) with its own deadline; otherwise use\n * {@link withLease}.\n *\n * @category combinators\n * @since 1.0.0-rc.1\n */\nexport const hold = <A, E, R, E2>(\n fs: FileSystem.FileSystem,\n lockPath: string,\n effect: Effect.Effect<A, E, R>,\n failure: (cause: unknown) => E2,\n options: HoldOptions = {}\n): Effect.Effect<A, E | E2, R> =>\n Effect.gen(function*() {\n const label = options.label ?? \"File lease\"\n const heartbeatEvery = options.heartbeatEvery ?? \"10 seconds\"\n const staleAfterMs = options.staleAfterMs ?? defaultStaleAfterMs\n const retryEvery = options.retryEvery ?? \"25 millis\"\n const owner = yield* token\n /**\n * Whether this call created the lock file, and therefore owes a release.\n * It is set in the same uninterruptible step that creates the file, so\n * interruption striking between the two cannot leak a lock nothing\n * releases — the only party that would ever reclaim it is a later\n * acquirer of the same path, which may never come.\n */\n let acquired = false\n\n const guard = options.guard === undefined ? Effect.void : options.guard.pipe(Effect.mapError(failure))\n const guarded = <B, F, S>(mutation: Effect.Effect<B, F, S>): Effect.Effect<B, F | E2, S> =>\n Effect.andThen(guard, mutation)\n\n const statIfPresent = (path: string): Effect.Effect<Option.Option<FileSystem.File.Info>, E2> =>\n fs.stat(path).pipe(\n Effect.map(Option.some),\n Effect.catch((cause): Effect.Effect<Option.Option<FileSystem.File.Info>, E2> =>\n isReason(cause, \"NotFound\") ? Effect.succeed(Option.none()) : Effect.fail(failure(cause))\n )\n )\n\n const refuse = (cause: unknown): Effect.Effect<never, E2> => Effect.fail(failure(cause))\n\n const readOwner = (\n path: string,\n onRefused: (cause: unknown) => Effect.Effect<Option.Option<string>, E2> = refuse\n ): Effect.Effect<Option.Option<string>, E2> =>\n fs.readFileString(path).pipe(\n Effect.map(Option.some),\n Effect.catch((cause) => isReason(cause, \"NotFound\") ? Effect.succeed(Option.none()) : onRefused(cause))\n )\n\n /**\n * Names the lock generation at `path`: its owner token, or\n * `directoryGeneration` for a directory, which has no readable owner.\n * A read refused for any other reason is propagated, not guessed at.\n */\n const observe = (path: string): Effect.Effect<Option.Option<string>, E2> =>\n readOwner(path, (cause) =>\n fs.stat(path).pipe(\n Effect.matchEffect({\n onFailure: () => refuse(cause),\n onSuccess: (info) =>\n info.type === \"Directory\" ? Effect.succeed(Option.some(directoryGeneration)) : refuse(cause)\n })\n ))\n\n const isStale = (path: string): Effect.Effect<boolean, E2> =>\n Effect.gen(function*() {\n const info = yield* statIfPresent(path)\n const modified = Option.isSome(info) ? Option.getOrUndefined(info.value.mtime) : undefined\n return modified !== undefined && (yield* Clock.currentTimeMillis) - modified.getTime() > staleAfterMs\n })\n\n const removeIfOwnedBy = (path: string, expected: string): Effect.Effect<void> =>\n fs.readFileString(path).pipe(\n Effect.flatMap((found) => found === expected ? guarded(fs.remove(path)) : Effect.void),\n Effect.ignore\n )\n\n const acquire = Effect.gen(function*() {\n while (true) {\n const created = yield* Effect.uninterruptible(\n guarded(fs.writeFileString(lockPath, owner, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.andThen(Effect.sync(() => {\n acquired = true\n return true\n })),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"AlreadyExists\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n )\n )\n if (created) return\n\n // The owner is read before the age, so a stale verdict always\n // belongs to the lock generation this read named.\n const observed = yield* observe(lockPath)\n if (Option.isNone(observed)) continue\n if (yield* isStale(lockPath)) {\n const reclaimed = yield* reclaim(observed.value)\n if (reclaimed) continue\n }\n yield* Effect.sleep(retryEvery)\n }\n })\n\n /**\n * Moves away the stale lock generation `observed` names, at most once.\n *\n * Measuring a lock and renaming it are two steps, so two contenders\n * that both measure the same generation as stale would otherwise both\n * rename, and the second would move away the fresh lock the first just\n * took. Reclaimers of one generation therefore race for a `wx` claim\n * named after its owner token first. Only the winner renames, and only\n * after re-reading that the path still holds that stale generation. A\n * loser, or a winner whose generation is already gone, moves nothing.\n * Owner tokens are unique per acquisition, so a claim never outlives\n * the generation it names in any way that matters.\n */\n const reclaim = (observed: string): Effect.Effect<boolean, E2> => {\n const claimPath = `${lockPath}.reclaim-${observed.replace(/[^0-9A-Za-z-]/g, \"_\").slice(0, 96)}`\n return Effect.acquireUseRelease(\n guarded(fs.writeFileString(claimPath, owner, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.as(true),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"AlreadyExists\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n ),\n (claimed) =>\n Effect.gen(function*() {\n if (!claimed) {\n // A claimant that died mid-reclaim leaves its claim behind.\n // It ages out on the same bound as a lock.\n const holder = yield* readOwner(claimPath)\n if (Option.isSome(holder) && (yield* isStale(claimPath))) {\n yield* removeIfOwnedBy(claimPath, holder.value)\n }\n return false\n }\n const current = yield* observe(lockPath)\n if (Option.isNone(current) || current.value !== observed || !(yield* isStale(lockPath))) return true\n const tombstone = `${lockPath}.stale-${owner}`\n const moved = yield* guarded(fs.rename(lockPath, tombstone)).pipe(\n Effect.as(true),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"NotFound\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n )\n if (!moved) return true\n // Only a stalled owner releasing between the re-read and the\n // rename, and a new owner acquiring in that gap, puts a live\n // lock here. Hand it back with an atomic create, which cannot\n // displace anyone who took the path since.\n const displaced = yield* observe(tombstone)\n if (\n Option.isSome(displaced) && displaced.value !== observed && displaced.value !== directoryGeneration\n ) {\n yield* guarded(fs.writeFileString(lockPath, displaced.value, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.ignore\n )\n }\n yield* guarded(fs.remove(tombstone, { recursive: true })).pipe(Effect.ignore)\n return true\n }),\n (claimed) => claimed ? removeIfOwnedBy(claimPath, owner) : Effect.void\n )\n }\n\n const release = fs.readFileString(lockPath).pipe(\n Effect.flatMap((found) => found === owner ? guarded(fs.remove(lockPath)) : Effect.void),\n // A concurrent stale-lock reaper can win release. `NotFound`\n // means no lock remains for this owner to release or leak.\n Effect.catch((cause): Effect.Effect<void, E2> =>\n isReason(cause, \"NotFound\") ? Effect.void : Effect.fail(failure(cause))\n )\n )\n\n // The finalizer is attached around acquisition itself, not just around\n // the protected effect, so a lock created moments before an interruption\n // is still released. A call that never created one owes nothing and must\n // not touch a file another owner holds.\n return yield* acquire.pipe(\n Effect.andThen(options.onAcquired ?? Effect.void),\n Effect.andThen(Effect.scoped(Effect.gen(function*() {\n yield* Effect.forkScoped(\n Effect.gen(function*() {\n while (true) {\n yield* Effect.sleep(heartbeatEvery)\n // Freshen only a lock this call still owns, the same comparison\n // the release below makes. A holder stalled past the stale\n // bound has already been reaped and replaced, and touching that\n // pathname anyway would hold a stranger's lock fresh forever: a\n // replacement that is then hard-killed could never be judged\n // stale while this zombie ran, so every later acquirer would\n // burn the full acquisition deadline and fail.\n const beat = yield* fs.readFileString(lockPath).pipe(\n Effect.map((found) => found === owner ? \"own\" as const : \"foreign\" as const),\n Effect.catch((cause) =>\n Effect.succeed(isReason(cause, \"NotFound\") ? \"gone\" as const : \"unreadable\" as const)\n )\n )\n if (beat === \"own\") {\n const timestamp = new Date(yield* Clock.currentTimeMillis)\n yield* Effect.ignore(guarded(fs.utimes(lockPath, timestamp, timestamp)))\n continue\n }\n // A read the host refused for any other reason is no evidence\n // either way, so this beat is skipped and the next one retries.\n // Ending the heartbeat on it would retire a lock this call\n // still holds: the file goes stale within the minute, another\n // process reaps it, and this holder keeps working unfenced.\n if (beat === \"unreadable\") continue\n // `gone` and `foreign` are the two states worth an operator\n // signal: this call's lock was reaped, and possibly already\n // replaced, while the effect it fences is still running, so\n // the rest of that effect proceeds unfenced against every other\n // process. The heartbeat is the only party that ever observes\n // it — the protected effect is not interrupted and the caller\n // is not failed — so retiring the fiber silently would leave no\n // trace at all.\n yield* Effect.logWarning(\n `${label} was reclaimed while its holder was still running`,\n { ...options.annotations, state: beat }\n )\n return\n }\n })\n )\n return yield* effect\n }))),\n Effect.onExit(() => acquired ? release : Effect.void)\n )\n })\n\n/**\n * Runs `effect` while holding the lease at `lockPath`.\n *\n * Acquisition fails with `failure(TimeoutError)` once `acquireWithin` passes\n * without the lease; every other host refusal reaches `failure` with the\n * refusing `PlatformError`. The deadline ends when the lease is held: the\n * protected effect and its release are never timed out.\n *\n * @category combinators\n * @since 1.0.0-rc.1\n */\nexport const withLease = <A, E, R, E2>(\n fs: FileSystem.FileSystem,\n lockPath: string,\n effect: Effect.Effect<A, E, R>,\n failure: (cause: unknown) => E2,\n options: Options = {}\n): Effect.Effect<A, E | E2, R> =>\n Effect.suspend(() => {\n const ready = Deferred.makeUnsafe<void>()\n const deadline = Deferred.await(ready).pipe(\n Effect.timeout(options.acquireWithin ?? defaultAcquireWithin),\n Effect.catchTag(\"TimeoutError\", (cause) => Effect.fail(failure(cause))),\n Effect.andThen(Effect.never)\n )\n return hold(fs, lockPath, effect, failure, {\n ...options,\n onAcquired: Deferred.succeed(ready, undefined).pipe(Effect.asVoid)\n }).pipe(Effect.raceFirst(deadline))\n })\n"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The filesystem-backed artifact store: blobs in a workspace-relative objects
|
|
3
|
+
* directory under a two-hex-prefix fanout, published atomically, coordinated
|
|
4
|
+
* across processes, and digest-verified on every read.
|
|
5
|
+
*
|
|
6
|
+
* @since 1.0.0-rc.0
|
|
7
|
+
*/
|
|
8
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import type * as Path from "effect/Path";
|
|
10
|
+
import type { Service } from "./ArtifactStore.ts";
|
|
11
|
+
/**
|
|
12
|
+
* Where the filesystem-backed store keeps its blobs.
|
|
13
|
+
*
|
|
14
|
+
* The directory is workspace-relative rather than absolute so a workspace can
|
|
15
|
+
* be moved or copied whole and still resolve its own artifacts.
|
|
16
|
+
*
|
|
17
|
+
* @category models
|
|
18
|
+
* @since 1.0.0-rc.0
|
|
19
|
+
* @slop
|
|
20
|
+
*/
|
|
21
|
+
export interface FileSystemOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Where blobs are stored, content-addressed by digest. Workspace-relative;
|
|
24
|
+
* defaults to {@link defaultDirectory}. An `ArtifactSweep` over the same
|
|
25
|
+
* store must be built with this same directory, or it enumerates somewhere
|
|
26
|
+
* the store never publishes.
|
|
27
|
+
*/
|
|
28
|
+
readonly directory?: string | undefined;
|
|
29
|
+
/** New payload mode, default `0600`, restricted by the umask. Existing blobs are unchanged. */
|
|
30
|
+
readonly fileMode?: number | undefined;
|
|
31
|
+
/** New objects and fanout directory mode, default `0700`. Existing directories are unchanged. */
|
|
32
|
+
readonly directoryMode?: number | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* `required` reports success only after syncing the blob, its fanout,
|
|
35
|
+
* the objects directory, and every ancestor. `best-effort` is the explicit weaker
|
|
36
|
+
* capability for hosts that cannot sync file or directory handles.
|
|
37
|
+
* Both modes require exclusive writable handles and symlink inspection.
|
|
38
|
+
*/
|
|
39
|
+
readonly durability?: "required" | "best-effort" | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* `required` uses an atomic lock file to coordinate writers and sweepers
|
|
42
|
+
* across processes. `process` is the explicit weaker browser/test mode.
|
|
43
|
+
*
|
|
44
|
+
* The fence bounds the race rather than eliminating it: the lock is
|
|
45
|
+
* heartbeated every 10 seconds, another process reclaims it once it is 60
|
|
46
|
+
* seconds stale, and acquisition itself gives up after 2 minutes. A holder
|
|
47
|
+
* whose host stalls past the stale bound can therefore be reaped while it is
|
|
48
|
+
* still running.
|
|
49
|
+
*
|
|
50
|
+
* Contenders that measure the same lock as stale race for a claim file named
|
|
51
|
+
* after its owner token, so each lock generation is moved away at most once
|
|
52
|
+
* and a fresh replacement is never displaced by a late stale verdict.
|
|
53
|
+
* Neither the mtime check nor the backup lease independently protects
|
|
54
|
+
* against a reaped stalled holder: the check precedes a separate delete, and
|
|
55
|
+
* the gate uses this same protocol.
|
|
56
|
+
*
|
|
57
|
+
* It also only fences parties that agree. An `ArtifactSweep` over the same
|
|
58
|
+
* directory must be built with the same `coordination`: a store on `process`
|
|
59
|
+
* paired with a sweep on `required` takes lock files no writer observes, so
|
|
60
|
+
* the fence reads as armed and protects nothing.
|
|
61
|
+
*/
|
|
62
|
+
readonly coordination?: "required" | "process" | undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The default objects directory. Workspace-relative, so a workspace carries
|
|
66
|
+
* its own artifacts and a sandbox that mounts the workspace inherits them.
|
|
67
|
+
*
|
|
68
|
+
* Exported because the store and its sweep must name the same directory, and a
|
|
69
|
+
* second private copy of the literal is exactly how they would drift apart.
|
|
70
|
+
*
|
|
71
|
+
* @category constants
|
|
72
|
+
* @since 1.0.0-rc.0
|
|
73
|
+
*/
|
|
74
|
+
export declare const defaultDirectory = ".flows/objects";
|
|
75
|
+
/**
|
|
76
|
+
* Builds the filesystem-backed artifact store.
|
|
77
|
+
*
|
|
78
|
+
* Host access arrives through Effect's `FileSystem` tag. Persistence needs a
|
|
79
|
+
* trusted host with exclusive writable handles, symlink inspection and, for
|
|
80
|
+
* required durability, file and directory syncing. Build this service before
|
|
81
|
+
* decorating the workflow filesystem with the capability kernel, as native
|
|
82
|
+
* `layerHost` does. The guarded native filesystem deliberately refuses open
|
|
83
|
+
* handles and cannot back this store; best-effort durability does not lift
|
|
84
|
+
* that requirement. A browser host can instead use the memory store.
|
|
85
|
+
* `platformPath` is the host's `Path` service; the directory fsync walks ancestors with it,
|
|
86
|
+
* so a Windows drive directory syncs its real parents.
|
|
87
|
+
*
|
|
88
|
+
* @category constructors
|
|
89
|
+
* @since 1.0.0-rc.0
|
|
90
|
+
* @slop
|
|
91
|
+
*/
|
|
92
|
+
export declare const makeFileSystem: (fs: FileSystem.FileSystem, platformPath: Path.Path, options?: FileSystemOptions) => Service;
|
|
93
|
+
//# sourceMappingURL=FileSystemArtifactStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileSystemArtifactStore.d.ts","sourceRoot":"","sources":["../../src/FileSystemArtifactStore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAGpD,OAAO,KAAK,KAAK,IAAI,MAAM,aAAa,CAAA;AAGxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAgBjD;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,iGAAiG;IACjG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,aAAa,GAAG,SAAS,CAAA;IAC5D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAA;CAC3D;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,mBAAmB,CAAA;AAgChD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GACzB,IAAI,UAAU,CAAC,UAAU,EACzB,cAAc,IAAI,CAAC,IAAI,EACvB,UAAS,iBAAsB,KAC9B,OAqTF,CAAA"}
|