@graftland/hmr 0.1.0 → 0.2.0-beta.1
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/README.md +195 -2
- package/README.zh-CN.md +137 -0
- package/README.zh-TW.md +137 -0
- package/dist/index.d.ts +39 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +250 -48
- package/dist/index.js.map +1 -1
- package/dist/internal/changes.js +92 -0
- package/dist/internal/changes.js.map +1 -0
- package/dist/internal/digest.js +6 -0
- package/dist/internal/digest.js.map +1 -0
- package/dist/internal/modules.js +237 -39
- package/dist/internal/modules.js.map +1 -1
- package/package.json +7 -5
- package/src/index.ts +632 -108
- package/src/internal/changes.ts +274 -0
- package/src/internal/digest.ts +5 -0
- package/src/internal/modules.ts +403 -57
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { changes } from "./internal/changes.js";
|
|
1
2
|
import { RestartRequired, isMissingEntry, modules } from "./internal/modules.js";
|
|
3
|
+
import { realpath } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
5
|
import { Graft, Plugin } from "@graftland/graft";
|
|
3
|
-
import { Array, Effect, Exit, FiberSet,
|
|
6
|
+
import { Array, Cause, Data, Deferred, Effect, Exit, FiberSet, HashMap, HashSet, Option, Ref, Result, Schema, Scope, Semaphore, Stream, String } from "effect";
|
|
4
7
|
// src/index.ts
|
|
5
8
|
/**
|
|
6
9
|
* Hot module replacement for Graft plugins on Node.js.
|
|
@@ -18,6 +21,7 @@ var HmrError = class extends Schema.TaggedError()("HmrError", {
|
|
|
18
21
|
return `HMR ${this.stage} failed for ${this.specifier || "the closed session"}`;
|
|
19
22
|
}
|
|
20
23
|
};
|
|
24
|
+
const WatchResult = Data.taggedEnum();
|
|
21
25
|
/**
|
|
22
26
|
* Installs Node module hooks until the enclosing scope closes. Module evaluation
|
|
23
27
|
* must be side-effect free. Versioned ESM cache entries are not reclaimed.
|
|
@@ -25,20 +29,78 @@ var HmrError = class extends Schema.TaggedError()("HmrError", {
|
|
|
25
29
|
const make = (options) => Effect.gen(function* () {
|
|
26
30
|
const graft = yield* Graft.Graft;
|
|
27
31
|
const semaphore = yield* Semaphore.make(1);
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const owners = yield* Ref.make(HashMap.empty());
|
|
33
|
+
const closed = yield* Ref.make(false);
|
|
34
|
+
const session = yield* Effect.scope;
|
|
35
|
+
const preserveExit = (effect) => Effect.gen(function* () {
|
|
36
|
+
const completed = yield* Deferred.make();
|
|
37
|
+
return yield* effect.pipe(Effect.exit, Effect.tap((exit) => Deferred.succeed(completed, exit)), Effect.uninterruptible, Effect.flatten, Effect.onExit((exit) => Effect.gen(function* () {
|
|
38
|
+
if (Exit.isSuccess(exit) || !Cause.hasInterrupts(exit.cause)) return;
|
|
39
|
+
const saved = yield* Deferred.poll(completed);
|
|
40
|
+
if (Option.isNone(saved)) return;
|
|
41
|
+
const original = yield* saved.value;
|
|
42
|
+
if (Exit.isFailure(original)) yield* Effect.logError("HMR operation failed during cancellation", original.cause);
|
|
43
|
+
})));
|
|
44
|
+
});
|
|
45
|
+
const serial = (effect) => effect.pipe(preserveExit, semaphore.withPermits(1));
|
|
46
|
+
const hooks = yield* Effect.acquireRelease(Effect.sync(() => modules(options.base, options.include ?? ((url) => !String.includes("/node_modules/")(new URL(url).pathname)))), (hooks) => serial(Ref.set(closed, true).pipe(Effect.andThen(Effect.sync(hooks.close)))));
|
|
47
|
+
const cached = yield* Ref.make(HashMap.empty());
|
|
48
|
+
const retryDirty = yield* Ref.make(HashSet.empty());
|
|
49
|
+
const retryAll = yield* Ref.make(false);
|
|
50
|
+
const observations = Ref.get(owners).pipe(Effect.map((owners) => Array.flatMap(HashMap.toValues(owners), (owner) => Array.getSomes([owner.active, owner.pending]))));
|
|
51
|
+
const collect = Effect.gen(function* () {
|
|
52
|
+
const attempts = yield* observations;
|
|
53
|
+
const currentCache = yield* Ref.get(cached);
|
|
54
|
+
hooks.collect([...attempts, ...HashMap.toValues(currentCache)]);
|
|
55
|
+
const files = hooks.files(attempts);
|
|
56
|
+
yield* Ref.update(retryDirty, HashSet.filter((url) => HashMap.has(files, url)));
|
|
57
|
+
if (attempts.length === 0) yield* Ref.set(retryAll, false);
|
|
58
|
+
});
|
|
59
|
+
const state = yield* Ref.make({
|
|
60
|
+
generation: hooks.prepare(),
|
|
61
|
+
entries: HashMap.empty()
|
|
62
|
+
});
|
|
63
|
+
const imports = yield* Scope.fork(session);
|
|
36
64
|
const run = yield* FiberSet.makeRuntimePromise();
|
|
37
|
-
const
|
|
65
|
+
const closedError = () => HmrError.make({
|
|
38
66
|
stage: "closed",
|
|
39
67
|
specifier: "",
|
|
40
68
|
cause: "HMR scope is closed"
|
|
41
|
-
})
|
|
69
|
+
});
|
|
70
|
+
const checkOpen = Ref.get(closed).pipe(Effect.flatMap((closed) => closed ? Effect.fail(closedError()) : Effect.void));
|
|
71
|
+
const physicalTarget = (request) => Effect.tryPromise({
|
|
72
|
+
try: () => realpath(fileURLToPath(request)),
|
|
73
|
+
catch: (cause) => HmrError.make({
|
|
74
|
+
stage: "import",
|
|
75
|
+
specifier: request,
|
|
76
|
+
cause
|
|
77
|
+
})
|
|
78
|
+
}).pipe(Effect.map((path) => pathToFileURL(path).href), Effect.catchIf((error) => isMissingEntry(request, error.cause), () => Effect.undefined));
|
|
79
|
+
const changedResolution = Effect.fnUntraced(function* (attempt) {
|
|
80
|
+
if (attempt.request === void 0) return Option.none();
|
|
81
|
+
const request = attempt.request;
|
|
82
|
+
const expected = pathToFileURL(fileURLToPath(attempt.url)).href;
|
|
83
|
+
const actual = yield* physicalTarget(request);
|
|
84
|
+
if (actual === expected) return Option.none();
|
|
85
|
+
if (actual === void 0) {
|
|
86
|
+
if ((yield* physicalTarget(attempt.url)) === void 0) return Option.none();
|
|
87
|
+
}
|
|
88
|
+
return Option.some(request);
|
|
89
|
+
});
|
|
90
|
+
const changedResolutions = Effect.gen(function* () {
|
|
91
|
+
const current = yield* Ref.get(owners);
|
|
92
|
+
const attempts = hooks.resolutions(Array.flatMap(HashMap.toValues(current), (owner) => Array.getSomes([owner.active, owner.pending])));
|
|
93
|
+
const requests = HashSet.fromIterable(Array.filterMap(attempts, (attempt) => attempt.request === void 0 ? Result.fail(void 0) : Result.succeed(attempt.request)));
|
|
94
|
+
const resolveAll = (requests) => Effect.forEach(requests, (request) => physicalTarget(request).pipe(Effect.map((target) => [request, target])), { concurrency: 8 }).pipe(Effect.map(HashMap.fromIterable));
|
|
95
|
+
const actual = yield* resolveAll(requests);
|
|
96
|
+
const remaining = yield* resolveAll(HashSet.fromIterable(Array.filterMap(attempts, (attempt) => attempt.request !== void 0 && Option.getOrUndefined(HashMap.get(actual, attempt.request)) === void 0 && !HashMap.has(actual, attempt.url) ? Result.succeed(attempt.url) : Result.fail(void 0))));
|
|
97
|
+
const targets = HashMap.union(actual, remaining);
|
|
98
|
+
return Array.fromIterable(HashSet.fromIterable(Array.filterMap(attempts, (attempt) => {
|
|
99
|
+
if (attempt.request === void 0) return Result.fail(void 0);
|
|
100
|
+
const actual = Option.getOrUndefined(HashMap.get(targets, attempt.request));
|
|
101
|
+
return actual === pathToFileURL(fileURLToPath(attempt.url)).href || actual === void 0 && Option.getOrUndefined(HashMap.get(targets, attempt.url)) === void 0 ? Result.fail(void 0) : Result.succeed(attempt.request);
|
|
102
|
+
})));
|
|
103
|
+
});
|
|
42
104
|
const load = Effect.fnUntraced(function* (specifier) {
|
|
43
105
|
const namespace = yield* Effect.tryPromise({
|
|
44
106
|
try: () => import(
|
|
@@ -57,59 +119,199 @@ const make = (options) => Effect.gen(function* () {
|
|
|
57
119
|
specifier,
|
|
58
120
|
cause: "Expected a default or named plugin export"
|
|
59
121
|
});
|
|
122
|
+
const url = yield* Effect.fromOption(hooks.resolved()).pipe(Effect.mapError((cause) => HmrError.make({
|
|
123
|
+
stage: "import",
|
|
124
|
+
specifier,
|
|
125
|
+
cause
|
|
126
|
+
})));
|
|
127
|
+
const request = hooks.fileRequest(specifier);
|
|
128
|
+
const attempt = {
|
|
129
|
+
url,
|
|
130
|
+
...Option.match(request, {
|
|
131
|
+
onNone: () => ({}),
|
|
132
|
+
onSome: (request) => ({ request })
|
|
133
|
+
})
|
|
134
|
+
};
|
|
135
|
+
const changed = yield* changedResolution(attempt);
|
|
136
|
+
if (Option.isSome(changed)) return yield* HmrError.make({
|
|
137
|
+
stage: "import",
|
|
138
|
+
specifier,
|
|
139
|
+
cause: RestartRequired.make({ url: changed.value })
|
|
140
|
+
});
|
|
60
141
|
return {
|
|
61
142
|
namespace,
|
|
62
143
|
plugin: plugin.value,
|
|
63
|
-
url
|
|
144
|
+
url
|
|
64
145
|
};
|
|
65
146
|
});
|
|
66
|
-
const
|
|
147
|
+
const importWithLease = (specifier) => Effect.acquireRelease(serial(Effect.gen(function* () {
|
|
67
148
|
yield* checkOpen;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
149
|
+
const current = yield* Ref.get(state);
|
|
150
|
+
hooks.start(current.generation);
|
|
151
|
+
const exit = yield* Effect.exit(load(specifier));
|
|
152
|
+
const token = Symbol();
|
|
153
|
+
const root = Option.orElse(hooks.resolved(), () => String.startsWith("file:")(specifier) ? Option.some(specifier) : Option.none());
|
|
154
|
+
const attempt = Option.map(root, (url) => ({
|
|
155
|
+
url,
|
|
156
|
+
generation: current.generation,
|
|
157
|
+
...Option.match(hooks.fileRequest(specifier), {
|
|
158
|
+
onNone: () => ({}),
|
|
159
|
+
onSome: (request) => ({ request })
|
|
160
|
+
})
|
|
161
|
+
}));
|
|
162
|
+
if (Option.isSome(attempt)) yield* Ref.update(cached, HashMap.set(attempt.value.url, attempt.value));
|
|
163
|
+
yield* Ref.update(owners, HashMap.set(token, {
|
|
164
|
+
active: Exit.isSuccess(exit) ? attempt : Option.none(),
|
|
165
|
+
pending: Exit.isFailure(exit) ? attempt : Option.none()
|
|
166
|
+
}));
|
|
167
|
+
if (Exit.isSuccess(exit)) {
|
|
168
|
+
const entry = exit.value;
|
|
169
|
+
const previous = HashMap.get(current.entries, entry.url);
|
|
170
|
+
const leases = Option.match(previous, {
|
|
171
|
+
onNone: () => HashSet.empty(),
|
|
172
|
+
onSome: (entry) => entry.leases
|
|
173
|
+
});
|
|
174
|
+
yield* Ref.set(state, {
|
|
175
|
+
...current,
|
|
176
|
+
entries: HashMap.set(current.entries, entry.url, {
|
|
177
|
+
plugin: entry.plugin,
|
|
178
|
+
leases: HashSet.add(leases, token)
|
|
179
|
+
})
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
token,
|
|
184
|
+
exit: Exit.map(exit, (entry) => entry.namespace)
|
|
185
|
+
};
|
|
186
|
+
}).pipe(Effect.ensuring(Effect.sync(hooks.discard)), Effect.ensuring(collect))), ({ token }) => serial(Effect.gen(function* () {
|
|
187
|
+
yield* Ref.update(owners, HashMap.remove(token));
|
|
188
|
+
yield* Ref.update(state, (current) => ({
|
|
189
|
+
...current,
|
|
190
|
+
entries: HashMap.filterMap(current.entries, (entry) => {
|
|
191
|
+
const leases = HashSet.remove(entry.leases, token);
|
|
192
|
+
return HashSet.size(leases) === 0 ? Result.fail(void 0) : Result.succeed({
|
|
193
|
+
...entry,
|
|
194
|
+
leases
|
|
195
|
+
});
|
|
196
|
+
})
|
|
197
|
+
}));
|
|
198
|
+
}).pipe(Effect.ensuring(collect)))).pipe(Effect.flatMap(({ exit }) => exit), preserveExit);
|
|
199
|
+
const importerScoped = (specifier, options) => options === void 0 ? importWithLease(specifier) : importWithLease(specifier).pipe(Scope.provide(options.observationScope));
|
|
200
|
+
const importer = (specifier) => Ref.getUnsafe(closed) ? Promise.reject(closedError()) : run(importerScoped(specifier).pipe(Effect.provideService(Scope.Scope, imports)));
|
|
201
|
+
const unsupportedModules = Ref.get(state).pipe(Effect.map(({ generation, entries }) => hooks.unsupported(generation, Array.fromIterable(HashMap.keys(entries)))));
|
|
202
|
+
const reloadChanged = (dirty) => serial(Effect.gen(function* () {
|
|
74
203
|
yield* checkOpen;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
204
|
+
const fullRetry = yield* Ref.get(retryAll);
|
|
205
|
+
const force = dirty === void 0 || fullRetry;
|
|
206
|
+
const pendingDirty = yield* Ref.get(retryDirty);
|
|
207
|
+
const changedFiles = dirty === void 0 ? pendingDirty : HashSet.union(pendingDirty, dirty);
|
|
208
|
+
yield* Ref.set(retryDirty, changedFiles);
|
|
209
|
+
yield* Ref.set(retryAll, force);
|
|
210
|
+
const changed = yield* changedResolutions;
|
|
211
|
+
if (changed.length > 0) return WatchResult.Skipped({
|
|
212
|
+
reason: "restart-required",
|
|
213
|
+
modules: changed
|
|
214
|
+
});
|
|
215
|
+
const { entries } = yield* Ref.get(state);
|
|
216
|
+
const unsupported = yield* unsupportedModules;
|
|
217
|
+
if (HashSet.size(unsupported) > 0) return WatchResult.Skipped({
|
|
218
|
+
modules: Array.fromIterable(unsupported),
|
|
78
219
|
reason: "restart-required"
|
|
79
|
-
};
|
|
80
|
-
const
|
|
220
|
+
});
|
|
221
|
+
const owned = yield* Ref.get(owners);
|
|
222
|
+
const attempts = Array.flatMap(HashMap.toValues(owned), (owner) => Array.getSomes([owner.active, owner.pending]));
|
|
223
|
+
const active = Array.getSomes(Array.map(HashMap.toValues(owned), (owner) => owner.active));
|
|
224
|
+
let selected = entries;
|
|
225
|
+
let invalid = HashSet.empty();
|
|
226
|
+
if (!force) {
|
|
227
|
+
let affected = changedFiles;
|
|
228
|
+
while (true) {
|
|
229
|
+
invalid = hooks.invalidated(attempts, affected);
|
|
230
|
+
const plugins = HashSet.fromIterable(Array.filterMap(Array.fromIterable(entries), ([url, entry]) => HashSet.has(affected, url) || Array.some(Array.fromIterable(entry.leases), (token) => Option.exists(HashMap.get(owned, token), (owner) => Array.some(Array.getSomes([owner.active, owner.pending]), (attempt) => hooks.affected(attempt, invalid)))) ? Result.succeed(entry.plugin) : Result.fail(void 0)));
|
|
231
|
+
selected = HashMap.filter(entries, (entry) => HashSet.has(plugins, entry.plugin));
|
|
232
|
+
const expanded = HashSet.union(affected, HashSet.fromIterable(HashMap.keys(selected)));
|
|
233
|
+
if (HashSet.size(expanded) === HashSet.size(affected)) break;
|
|
234
|
+
affected = expanded;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const candidate = force ? hooks.prepare() : hooks.prepare(active, invalid);
|
|
81
238
|
hooks.start(candidate, true);
|
|
82
239
|
return yield* Effect.gen(function* () {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
240
|
+
const loaded = yield* Effect.forEach(selected, ([url, previous]) => Ref.update(owners, HashMap.map((owner, token) => HashSet.has(previous.leases, token) ? {
|
|
241
|
+
...owner,
|
|
242
|
+
pending: Option.map(owner.active, (attempt) => ({
|
|
243
|
+
...attempt,
|
|
244
|
+
url,
|
|
245
|
+
generation: candidate
|
|
246
|
+
}))
|
|
247
|
+
} : owner)).pipe(Effect.andThen(load(url)), Effect.map((entry) => Option.some({
|
|
248
|
+
url,
|
|
249
|
+
previous: previous.plugin,
|
|
250
|
+
leases: previous.leases,
|
|
251
|
+
plugin: entry.plugin
|
|
252
|
+
})), Effect.catchIf((error) => error.stage === "import" && isMissingEntry(url, error.cause), () => Effect.succeedNone)));
|
|
253
|
+
const candidates = Array.getSomes(loaded);
|
|
254
|
+
const alternatives = Array.reduce(candidates, HashMap.empty(), (groups, { previous, plugin }) => HashMap.set(groups, previous, HashSet.add(Option.getOrElse(HashMap.get(groups, previous), HashSet.empty), plugin)));
|
|
255
|
+
const ambiguous = Array.filter(candidates, ({ previous }) => Option.exists(HashMap.get(alternatives, previous), (plugins) => HashSet.size(plugins) > 1));
|
|
256
|
+
if (ambiguous.length > 0) return WatchResult.Skipped({
|
|
257
|
+
reason: "ambiguous-exports",
|
|
258
|
+
modules: Array.map(ambiguous, ({ url }) => url)
|
|
259
|
+
});
|
|
260
|
+
const replacements = HashMap.fromIterable(Array.map(candidates, ({ url, plugin, leases }) => [url, {
|
|
261
|
+
plugin,
|
|
262
|
+
leases
|
|
263
|
+
}]));
|
|
264
|
+
const next = HashMap.union(HashMap.filter(entries, (_, url) => !HashMap.has(selected, url)), replacements);
|
|
265
|
+
const pairs = Array.map(candidates, ({ previous, plugin }) => [previous, plugin]);
|
|
266
|
+
const changed = yield* changedResolutions;
|
|
267
|
+
if (changed.length > 0) return WatchResult.Skipped({
|
|
268
|
+
reason: "restart-required",
|
|
269
|
+
modules: changed
|
|
270
|
+
});
|
|
271
|
+
const unsafe = hooks.unsupported(candidate, Array.fromIterable(HashMap.keys(next)));
|
|
272
|
+
if (HashSet.size(unsafe) > 0) return WatchResult.Skipped({
|
|
273
|
+
reason: "restart-required",
|
|
274
|
+
modules: Array.fromIterable(unsafe)
|
|
275
|
+
});
|
|
276
|
+
const replacement = yield* Graft.replace(pairs).pipe(Effect.provideService(Graft.Graft, graft), Effect.result);
|
|
277
|
+
if (Result.isFailure(replacement)) return WatchResult.Skipped({
|
|
278
|
+
reason: "identity-conflict",
|
|
279
|
+
modules: Array.filterMap(candidates, ({ url }, index) => Array.contains(replacement.failure.indices, index) ? Result.succeed(url) : Result.fail(void 0))
|
|
280
|
+
});
|
|
281
|
+
yield* Ref.update(owners, (owners) => Array.reduce(candidates, owners, (owners, { url, leases }) => HashMap.map(owners, (owner, token) => HashSet.has(leases, token) ? {
|
|
282
|
+
active: Option.map(owner.pending, (attempt) => ({
|
|
283
|
+
...attempt,
|
|
284
|
+
url,
|
|
285
|
+
generation: candidate
|
|
286
|
+
})),
|
|
287
|
+
pending: Option.none()
|
|
288
|
+
} : owner)));
|
|
289
|
+
yield* Ref.set(state, {
|
|
290
|
+
entries: next,
|
|
291
|
+
generation: candidate
|
|
292
|
+
});
|
|
293
|
+
yield* Ref.set(cached, HashMap.map(next, (_, url) => ({
|
|
294
|
+
url,
|
|
295
|
+
generation: candidate
|
|
296
|
+
})));
|
|
297
|
+
yield* Ref.set(retryDirty, HashSet.empty());
|
|
298
|
+
yield* Ref.set(retryAll, false);
|
|
299
|
+
return WatchResult.Reloaded({ entries: Array.map(candidates, (candidate) => candidate.url) });
|
|
300
|
+
}).pipe(Effect.catchIf((error) => Schema.is(RestartRequired)(error.cause), (error) => Effect.succeed(WatchResult.Skipped({
|
|
301
|
+
modules: Array.fromIterable(HashSet.add(hooks.unsupported(candidate), error.cause.url)),
|
|
101
302
|
reason: "restart-required"
|
|
102
|
-
})), Effect.ensuring(Effect.sync(hooks.discard)));
|
|
303
|
+
}))), Effect.ensuring(Effect.sync(hooks.discard)), Effect.ensuring(collect));
|
|
103
304
|
}));
|
|
104
305
|
return {
|
|
105
306
|
importer,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
307
|
+
importerScoped,
|
|
308
|
+
reload: reloadChanged(),
|
|
309
|
+
watch: (paths, watchOptions) => paths.length === 0 ? Stream.empty : changes((use) => Ref.get(owners).pipe(Effect.map((owners) => hooks.files([...Array.getSomes(Array.map(HashMap.toValues(owners), (owner) => owner.active)), ...Array.getSomes(Array.map(HashMap.toValues(owners), (owner) => owner.pending))])), Effect.flatMap(use), semaphore.withPermits(1)), paths, watchOptions).pipe(Stream.mapEffect((dirty) => Effect.map(Effect.exit(reloadChanged(dirty)), (exit) => Exit.isSuccess(exit) ? exit.value : WatchResult.Failed({ cause: exit.cause }))), Stream.merge(Stream.tick("1 second").pipe(Stream.mapEffect(() => unsupportedModules.pipe(semaphore.withPermits(1))), Stream.changes, Stream.filter((modules) => HashSet.size(modules) > 0), Stream.map((modules) => WatchResult.Skipped({
|
|
310
|
+
reason: "restart-required",
|
|
311
|
+
modules: Array.fromIterable(modules)
|
|
110
312
|
})))))
|
|
111
313
|
};
|
|
112
314
|
});
|
|
113
|
-
export { HmrError, make };
|
|
315
|
+
export { HmrError, WatchResult, make };
|
|
114
316
|
|
|
115
317
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Str","Arr"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Hot module replacement for Graft plugins on Node.js.\n */\nimport { Graft, Plugin } from \"@graftland/graft\"\nimport {\n\tArray as Arr,\n\tCause,\n\tEffect,\n\tExit,\n\tFiberSet,\n\tFileSystem,\n\tHashMap,\n\tHashSet,\n\tOption,\n\tString as Str,\n\tSchema,\n\tSemaphore,\n\tStream,\n} from \"effect\"\nimport type { Duration, PlatformError, Scope } from \"effect\"\nimport { isMissingEntry, modules, RestartRequired } from \"./internal/modules\"\n\nexport class HmrError extends Schema.TaggedError<\n\tHmrError,\n\t{ readonly brand: unique symbol }\n>()(\"HmrError\", {\n\tstage: Schema.Literals([\"import\", \"exports\", \"closed\"]),\n\tspecifier: Schema.String,\n\tcause: Schema.Unknown,\n}) {\n\toverride get message(): string {\n\t\treturn `HMR ${this.stage} failed for ${this.specifier || \"the closed session\"}`\n\t}\n}\n\nexport type ReloadResult =\n\t| { readonly _tag: \"Reloaded\"; readonly entries: ReadonlyArray<string> }\n\t| {\n\t\t\treadonly _tag: \"Skipped\"\n\t\t\treadonly modules: ReadonlyArray<string>\n\t\t\treadonly reason: \"restart-required\"\n\t }\n\nexport type WatchResult =\n\t| ReloadResult\n\t| {\n\t\t\treadonly _tag: \"Failed\"\n\t\t\treadonly cause: Cause.Cause<HmrError>\n\t }\n\nexport interface Options {\n\t/** Application module URL, usually import.meta.url; used for bare and relative resolution. */\n\treadonly base: string\n\t/** Files to version. Defaults to file URLs outside node_modules. Entries are always included. */\n\treadonly include?: (url: string) => boolean\n}\n\nexport interface Hmr {\n\t/** Pass directly to ModuleResolver.layer({ base, importer }). */\n\treadonly importer: (specifier: string) => Promise<unknown>\n\t/** Imports every entry before replacing anything. Activation failures belong to Graft. */\n\treadonly reload: Effect.Effect<ReloadResult, HmrError>\n\t/** Consuming this stream installs the watchers; the consumer owns their lifetime. */\n\treadonly watch: (\n\t\tpaths: ReadonlyArray<string>,\n\t\toptions?: FileSystem.WatchOptions & { readonly debounce?: Duration.Input },\n\t) => Stream.Stream<\n\t\tWatchResult,\n\t\tPlatformError.PlatformError,\n\t\tFileSystem.FileSystem\n\t>\n}\n\n/**\n * Installs Node module hooks until the enclosing scope closes. Module evaluation\n * must be side-effect free. Versioned ESM cache entries are not reclaimed.\n */\nexport const make = (\n\toptions: Options,\n): Effect.Effect<Hmr, never, Graft.Graft | Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst graft = yield* Graft.Graft\n\t\tconst semaphore = yield* Semaphore.make(1)\n\t\tlet closed = false\n\t\t// Native imports cannot be cancelled: keep the lock until their promises settle.\n\t\tconst serial = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n\t\t\tsemaphore.withPermits(1)(Effect.uninterruptible(effect))\n\t\tconst hooks = yield* Effect.acquireRelease(\n\t\t\tEffect.sync(() =>\n\t\t\t\tmodules(\n\t\t\t\t\toptions.base,\n\t\t\t\t\toptions.include ??\n\t\t\t\t\t\t((url) => !Str.includes(\"/node_modules/\")(new URL(url).pathname)),\n\t\t\t\t),\n\t\t\t),\n\t\t\t(hooks) =>\n\t\t\t\tserial(\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\tclosed = true\n\t\t\t\t\t\thooks.close()\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t)\n\t\tlet generation = hooks.prepare()\n\t\tlet entries = HashMap.empty<string, Plugin.Plugin>()\n\t\tconst run = yield* FiberSet.makeRuntimePromise<never, unknown, HmrError>()\n\t\tconst checkOpen = Effect.suspend(() =>\n\t\t\tclosed\n\t\t\t\t? Effect.fail(\n\t\t\t\t\t\tHmrError.make({\n\t\t\t\t\t\t\tstage: \"closed\",\n\t\t\t\t\t\t\tspecifier: \"\",\n\t\t\t\t\t\t\tcause: \"HMR scope is closed\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t: Effect.void,\n\t\t)\n\t\tconst load = Effect.fnUntraced(function* (specifier: string) {\n\t\t\tconst namespace = yield* Effect.tryPromise({\n\t\t\t\ttry: () => import(/* @vite-ignore */ hooks.request(specifier)),\n\t\t\t\tcatch: (cause) => HmrError.make({ stage: \"import\", specifier, cause }),\n\t\t\t})\n\t\t\tconst plugin = Plugin.fromModule(namespace)\n\t\t\tif (Option.isNone(plugin))\n\t\t\t\treturn yield* HmrError.make({\n\t\t\t\t\tstage: \"exports\",\n\t\t\t\t\tspecifier,\n\t\t\t\t\tcause: \"Expected a default or named plugin export\",\n\t\t\t\t})\n\t\t\treturn { namespace, plugin: plugin.value, url: hooks.resolved()! }\n\t\t})\n\t\tconst importer = (specifier: string): Promise<unknown> =>\n\t\t\trun(\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* checkOpen\n\t\t\t\t\thooks.start(generation)\n\t\t\t\t\tconst entry = yield* load(specifier)\n\t\t\t\t\tentries = HashMap.set(entries, entry.url, entry.plugin)\n\t\t\t\t\treturn entry.namespace\n\t\t\t\t}).pipe(Effect.ensuring(Effect.sync(hooks.discard)), serial),\n\t\t\t)\n\t\tconst reload = serial(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* checkOpen\n\t\t\t\tif (HashSet.size(generation.unsupported) > 0)\n\t\t\t\t\treturn {\n\t\t\t\t\t\t_tag: \"Skipped\" as const,\n\t\t\t\t\t\tmodules: Arr.fromIterable(generation.unsupported),\n\t\t\t\t\t\treason: \"restart-required\" as const,\n\t\t\t\t\t}\n\t\t\t\t// Advance even without entries so retries bypass cached failed imports.\n\t\t\t\tconst candidate = hooks.prepare()\n\t\t\t\thooks.start(candidate, true)\n\t\t\t\treturn yield* Effect.gen(function* () {\n\t\t\t\t\tlet next = HashMap.empty<string, Plugin.Plugin>()\n\t\t\t\t\tlet pairs: ReadonlyArray<readonly [Plugin.Plugin, Plugin.Plugin]> = []\n\t\t\t\t\tfor (const [url, previous] of entries) {\n\t\t\t\t\t\tconst entry = yield* load(url).pipe(\n\t\t\t\t\t\t\tEffect.catchIf(\n\t\t\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\t\t\terror.stage === \"import\" && isMissingEntry(url, error.cause),\n\t\t\t\t\t\t\t\t() => Effect.undefined,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (entry === undefined) continue\n\t\t\t\t\t\tnext = HashMap.set(next, url, entry.plugin)\n\t\t\t\t\t\tpairs = Arr.append(pairs, [previous, entry.plugin])\n\t\t\t\t\t}\n\t\t\t\t\tyield* Graft.replace(pairs).pipe(\n\t\t\t\t\t\tEffect.provideService(Graft.Graft, graft),\n\t\t\t\t\t)\n\t\t\t\t\tentries = next\n\t\t\t\t\tgeneration = candidate\n\t\t\t\t\treturn {\n\t\t\t\t\t\t_tag: \"Reloaded\" as const,\n\t\t\t\t\t\tentries: Arr.fromIterable(HashMap.keys(entries)),\n\t\t\t\t\t}\n\t\t\t\t}).pipe(\n\t\t\t\t\tEffect.catchIf(\n\t\t\t\t\t\t(error) => Schema.is(RestartRequired)(error.cause),\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tEffect.succeed({\n\t\t\t\t\t\t\t\t_tag: \"Skipped\" as const,\n\t\t\t\t\t\t\t\tmodules: Arr.fromIterable(candidate.unsupported),\n\t\t\t\t\t\t\t\treason: \"restart-required\" as const,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.ensuring(Effect.sync(hooks.discard)),\n\t\t\t\t)\n\t\t\t}),\n\t\t)\n\t\treturn {\n\t\t\timporter,\n\t\t\treload,\n\t\t\twatch: (paths, watchOptions) =>\n\t\t\t\tStream.unwrap(\n\t\t\t\t\tEffect.map(FileSystem.FileSystem, (fs) =>\n\t\t\t\t\t\tStream.mergeAll(\n\t\t\t\t\t\t\tArr.map(paths, (path) => fs.watch(path, watchOptions)),\n\t\t\t\t\t\t\t{ concurrency: \"unbounded\" },\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tStream.debounce(watchOptions?.debounce ?? \"50 millis\"),\n\t\t\t\t\t\t\tStream.mapEffect(() =>\n\t\t\t\t\t\t\t\tEffect.map(Effect.exit(reload), (exit): WatchResult =>\n\t\t\t\t\t\t\t\t\tExit.isSuccess(exit)\n\t\t\t\t\t\t\t\t\t\t? exit.value\n\t\t\t\t\t\t\t\t\t\t: { _tag: \"Failed\", cause: exit.cause },\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t}\n\t})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Hot module replacement for Graft plugins on Node.js.\n */\n// @effect-diagnostics-next-line nodeBuiltinImport:off -- native import resolution must work without requiring a platform layer\nimport { realpath } from \"node:fs/promises\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport { Graft, Plugin } from \"@graftland/graft\"\nimport {\n\tArray,\n\tCause,\n\tData,\n\tDeferred,\n\tEffect,\n\tExit,\n\tFiberSet,\n\tFileSystem,\n\tHashMap,\n\tHashSet,\n\tOption,\n\tRef,\n\tResult,\n\tString,\n\tSchema,\n\tSemaphore,\n\tStream,\n\tScope,\n} from \"effect\"\nimport type { Duration, PlatformError } from \"effect\"\nimport { changes } from \"./internal/changes\"\nimport {\n\tisMissingEntry,\n\tmodules,\n\tRestartRequired,\n\ttype Attempt,\n} from \"./internal/modules\"\n\nexport class HmrError extends Schema.TaggedError<\n\tHmrError,\n\t{ readonly brand: unique symbol }\n>()(\"HmrError\", {\n\tstage: Schema.Literals([\"import\", \"exports\", \"closed\"]),\n\tspecifier: Schema.String,\n\tcause: Schema.Unknown,\n}) {\n\toverride get message(): string {\n\t\treturn `HMR ${this.stage} failed for ${this.specifier || \"the closed session\"}`\n\t}\n}\n\nexport type WatchResult = Data.TaggedEnum<{\n\tReloaded: { readonly entries: ReadonlyArray<string> }\n\tSkipped: {\n\t\treadonly modules: ReadonlyArray<string>\n\t\treadonly reason:\n\t\t\t\"restart-required\" | \"ambiguous-exports\" | \"identity-conflict\"\n\t}\n\tFailed: { readonly cause: Cause.Cause<HmrError> }\n}>\n\nexport const WatchResult = Data.taggedEnum<WatchResult>()\n\nexport type ReloadResult = Exclude<WatchResult, { readonly _tag: \"Failed\" }>\n\nexport interface Options {\n\t/** Application module URL, usually import.meta.url; used for bare and relative resolution. */\n\treadonly base: string\n\t/** Files to version. Defaults to file URLs outside node_modules. Entries are always included. */\n\treadonly include?: (url: string) => boolean\n}\n\nexport interface WatchOptions extends FileSystem.WatchOptions {\n\treadonly debounce?: Duration.Input\n\t/** Receives an absolute file path; applies to native events and reconciliation. */\n\treadonly filter?: (path: string) => boolean\n}\n\n/** Separates retry observations from an importer's temporary resource scope. */\nexport interface ImportOptions {\n\treadonly observationScope: Scope.Scope\n}\n\nexport interface Hmr {\n\t/** Keeps imported entries registered until this HMR session closes. */\n\treadonly importer: (specifier: string) => Promise<unknown>\n\t/** Registers an observation lease in observationScope, or the caller's scope when omitted. */\n\treadonly importerScoped: (\n\t\tspecifier: string,\n\t\toptions?: ImportOptions,\n\t) => Effect.Effect<unknown, HmrError, Scope.Scope>\n\t/** Imports every entry before replacing anything. Activation failures belong to Graft. */\n\treadonly reload: Effect.Effect<ReloadResult, HmrError>\n\t/**\n\t * Consuming this stream installs graph-driven directory watchers, polls stat once\n\t * per second, and audits contents every ten seconds to recover missed events. Known native loading limitations emit\n\t * Skipped even without a file event. The consumer owns the stream's lifetimes.\n\t */\n\treadonly watch: (\n\t\tpaths: ReadonlyArray<string>,\n\t\toptions?: WatchOptions,\n\t) => Stream.Stream<\n\t\tWatchResult,\n\t\tPlatformError.PlatformError,\n\t\tFileSystem.FileSystem\n\t>\n}\n\n/**\n * Installs Node module hooks until the enclosing scope closes. Module evaluation\n * must be side-effect free. Versioned ESM cache entries are not reclaimed.\n */\nexport const make = (\n\toptions: Options,\n): Effect.Effect<Hmr, never, Graft.Graft | Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst graft = yield* Graft.Graft\n\t\tconst semaphore = yield* Semaphore.make(1)\n\t\t// A lease owns both the running graph and its latest uncommitted attempt.\n\t\tconst owners = yield* Ref.make(\n\t\t\tHashMap.empty<\n\t\t\t\tsymbol,\n\t\t\t\t{\n\t\t\t\t\treadonly active: Option.Option<Attempt>\n\t\t\t\t\treadonly pending: Option.Option<Attempt>\n\t\t\t\t}\n\t\t\t>(),\n\t\t)\n\t\tconst closed = yield* Ref.make(false)\n\t\tconst session = yield* Effect.scope\n\t\t// Transfer the complete Exit before restoring interruption. Effect may replace\n\t\t// a failed masked operation with pending interruption at that boundary.\n\t\tconst preserveExit = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst completed = yield* Deferred.make<Exit.Exit<A, E>>()\n\t\t\t\treturn yield* effect.pipe(\n\t\t\t\t\tEffect.exit,\n\t\t\t\t\tEffect.tap((exit) => Deferred.succeed(completed, exit)),\n\t\t\t\t\tEffect.uninterruptible,\n\t\t\t\t\tEffect.flatten,\n\t\t\t\t\tEffect.onExit((exit) =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tif (Exit.isSuccess(exit) || !Cause.hasInterrupts(exit.cause))\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\tconst saved = yield* Deferred.poll(completed)\n\t\t\t\t\t\t\tif (Option.isNone(saved)) return\n\t\t\t\t\t\t\tconst original = yield* saved.value\n\t\t\t\t\t\t\tif (Exit.isFailure(original))\n\t\t\t\t\t\t\t\tyield* Effect.logError(\n\t\t\t\t\t\t\t\t\t\"HMR operation failed during cancellation\",\n\t\t\t\t\t\t\t\t\toriginal.cause,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t})\n\t\t// Native imports cannot be cancelled: keep the lock until their promises settle.\n\t\tconst serial = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n\t\t\teffect.pipe(preserveExit, semaphore.withPermits(1))\n\t\tconst hooks = yield* Effect.acquireRelease(\n\t\t\tEffect.sync(() =>\n\t\t\t\tmodules(\n\t\t\t\t\toptions.base,\n\t\t\t\t\toptions.include ??\n\t\t\t\t\t\t((url) =>\n\t\t\t\t\t\t\t!String.includes(\"/node_modules/\")(new URL(url).pathname)),\n\t\t\t\t),\n\t\t\t),\n\t\t\t(hooks) =>\n\t\t\t\tserial(\n\t\t\t\t\tRef.set(closed, true).pipe(Effect.andThen(Effect.sync(hooks.close))),\n\t\t\t\t),\n\t\t)\n\t\t// Node can return a cached entry after its last observation lease closes.\n\t\t// Keep current entry attempts without polling or reloading unleased entries.\n\t\t// The hooks also retain cached instance facts for older resolution views.\n\t\tconst cached = yield* Ref.make(HashMap.empty<string, Attempt>())\n\n\t\tconst retryDirty = yield* Ref.make(HashSet.empty<string>())\n\t\tconst retryAll = yield* Ref.make(false)\n\t\tconst observations = Ref.get(owners).pipe(\n\t\t\tEffect.map((owners) =>\n\t\t\t\tArray.flatMap(HashMap.toValues(owners), (owner) =>\n\t\t\t\t\tArray.getSomes([owner.active, owner.pending]),\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t\tconst collect = Effect.gen(function* () {\n\t\t\tconst attempts = yield* observations\n\t\t\tconst currentCache = yield* Ref.get(cached)\n\t\t\thooks.collect([...attempts, ...HashMap.toValues(currentCache)])\n\t\t\tconst files = hooks.files(attempts)\n\t\t\tyield* Ref.update(\n\t\t\t\tretryDirty,\n\t\t\t\tHashSet.filter((url) => HashMap.has(files, url)),\n\t\t\t)\n\t\t\tif (attempts.length === 0) yield* Ref.set(retryAll, false)\n\t\t})\n\n\t\tconst state = yield* Ref.make({\n\t\t\tgeneration: hooks.prepare(),\n\t\t\tentries: HashMap.empty<\n\t\t\t\tstring,\n\t\t\t\t{\n\t\t\t\t\treadonly plugin: Plugin.Plugin\n\t\t\t\t\treadonly leases: HashSet.HashSet<symbol>\n\t\t\t\t}\n\t\t\t>(),\n\t\t})\n\t\t// Stop Promise-backed operations before releasing their leases. Otherwise\n\t\t// a lease finalizer could wait on an import before its owner interrupts it.\n\t\tconst imports = yield* Scope.fork(session)\n\t\tconst run = yield* FiberSet.makeRuntimePromise<never, unknown, HmrError>()\n\t\tconst closedError = () =>\n\t\t\tHmrError.make({\n\t\t\t\tstage: \"closed\",\n\t\t\t\tspecifier: \"\",\n\t\t\t\tcause: \"HMR scope is closed\",\n\t\t\t})\n\t\tconst checkOpen = Ref.get(closed).pipe(\n\t\t\tEffect.flatMap((closed) =>\n\t\t\t\tclosed ? Effect.fail(closedError()) : Effect.void,\n\t\t\t),\n\t\t)\n\t\tconst physicalTarget = (request: string) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\ttry: () => realpath(fileURLToPath(request)),\n\t\t\t\tcatch: (cause) =>\n\t\t\t\t\tHmrError.make({ stage: \"import\", specifier: request, cause }),\n\t\t\t}).pipe(\n\t\t\t\tEffect.map((path) => pathToFileURL(path).href),\n\t\t\t\tEffect.catchIf(\n\t\t\t\t\t(error) => isMissingEntry(request, error.cause),\n\t\t\t\t\t() => Effect.undefined,\n\t\t\t\t),\n\t\t\t)\n\t\tconst changedResolution = Effect.fnUntraced(function* (\n\t\t\tattempt: Pick<Attempt, \"url\" | \"request\">,\n\t\t) {\n\t\t\tif (attempt.request === undefined) return Option.none<string>()\n\t\t\tconst request = attempt.request\n\t\t\tconst expected = pathToFileURL(fileURLToPath(attempt.url)).href\n\t\t\tconst actual = yield* physicalTarget(request)\n\t\t\tif (actual === expected) return Option.none<string>()\n\t\t\t// If both paths disappeared, this is ordinary entry deletion. A\n\t\t\t// missing link with a surviving old target must never reload that target.\n\t\t\tif (actual === undefined) {\n\t\t\t\tconst remaining = yield* physicalTarget(attempt.url)\n\t\t\t\tif (remaining === undefined) return Option.none<string>()\n\t\t\t}\n\t\t\treturn Option.some(request)\n\t\t})\n\n\t\tconst changedResolutions = Effect.gen(function* () {\n\t\t\tconst current = yield* Ref.get(owners)\n\t\t\tconst attempts = hooks.resolutions(\n\t\t\t\tArray.flatMap(HashMap.toValues(current), (owner) =>\n\t\t\t\t\tArray.getSomes([owner.active, owner.pending]),\n\t\t\t\t),\n\t\t\t)\n\t\t\tconst requests = HashSet.fromIterable(\n\t\t\t\tArray.filterMap(attempts, (attempt) =>\n\t\t\t\t\tattempt.request === undefined\n\t\t\t\t\t\t? Result.fail(undefined)\n\t\t\t\t\t\t: Result.succeed(attempt.request),\n\t\t\t\t),\n\t\t\t)\n\t\t\tconst resolveAll = (requests: HashSet.HashSet<string>) =>\n\t\t\t\tEffect.forEach(\n\t\t\t\t\trequests,\n\t\t\t\t\t(request) =>\n\t\t\t\t\t\tphysicalTarget(request).pipe(\n\t\t\t\t\t\t\tEffect.map((target) => [request, target] as const),\n\t\t\t\t\t\t),\n\t\t\t\t\t{ concurrency: 8 },\n\t\t\t\t).pipe(Effect.map(HashMap.fromIterable))\n\t\t\tconst actual = yield* resolveAll(requests)\n\t\t\tconst missingTargets = HashSet.fromIterable(\n\t\t\t\tArray.filterMap(attempts, (attempt) =>\n\t\t\t\t\tattempt.request !== undefined &&\n\t\t\t\t\tOption.getOrUndefined(HashMap.get(actual, attempt.request)) ===\n\t\t\t\t\t\tundefined &&\n\t\t\t\t\t!HashMap.has(actual, attempt.url)\n\t\t\t\t\t\t? Result.succeed(attempt.url)\n\t\t\t\t\t\t: Result.fail(undefined),\n\t\t\t\t),\n\t\t\t)\n\t\t\tconst remaining = yield* resolveAll(missingTargets)\n\t\t\tconst targets = HashMap.union(actual, remaining)\n\n\t\t\treturn Array.fromIterable(\n\t\t\t\tHashSet.fromIterable(\n\t\t\t\t\tArray.filterMap(attempts, (attempt) => {\n\t\t\t\t\t\tif (attempt.request === undefined) return Result.fail(undefined)\n\t\t\t\t\t\tconst actual = Option.getOrUndefined(\n\t\t\t\t\t\t\tHashMap.get(targets, attempt.request),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst expected = pathToFileURL(fileURLToPath(attempt.url)).href\n\t\t\t\t\t\treturn actual === expected ||\n\t\t\t\t\t\t\t(actual === undefined &&\n\t\t\t\t\t\t\t\tOption.getOrUndefined(HashMap.get(targets, attempt.url)) ===\n\t\t\t\t\t\t\t\t\tundefined)\n\t\t\t\t\t\t\t? Result.fail(undefined)\n\t\t\t\t\t\t\t: Result.succeed(attempt.request)\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t)\n\t\t})\n\n\t\tconst load = Effect.fnUntraced(function* (specifier: string) {\n\t\t\tconst namespace = yield* Effect.tryPromise({\n\t\t\t\ttry: () => import(/* @vite-ignore */ hooks.request(specifier)),\n\t\t\t\tcatch: (cause) => HmrError.make({ stage: \"import\", specifier, cause }),\n\t\t\t})\n\t\t\tconst plugin = Plugin.fromModule(namespace)\n\t\t\tif (Option.isNone(plugin))\n\t\t\t\treturn yield* HmrError.make({\n\t\t\t\t\tstage: \"exports\",\n\t\t\t\t\tspecifier,\n\t\t\t\t\tcause: \"Expected a default or named plugin export\",\n\t\t\t\t})\n\t\t\tconst url = yield* Effect.fromOption(hooks.resolved()).pipe(\n\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\tHmrError.make({ stage: \"import\", specifier, cause }),\n\t\t\t\t),\n\t\t\t)\n\t\t\tconst request = hooks.fileRequest(specifier)\n\t\t\tconst attempt = {\n\t\t\t\turl,\n\t\t\t\t...Option.match(request, {\n\t\t\t\t\tonNone: () => ({}),\n\t\t\t\t\tonSome: (request) => ({ request }),\n\t\t\t\t}),\n\t\t\t}\n\t\t\tconst changed = yield* changedResolution(attempt)\n\t\t\tif (Option.isSome(changed))\n\t\t\t\treturn yield* HmrError.make({\n\t\t\t\t\tstage: \"import\",\n\t\t\t\t\tspecifier,\n\t\t\t\t\tcause: RestartRequired.make({ url: changed.value }),\n\t\t\t\t})\n\t\t\treturn { namespace, plugin: plugin.value, url }\n\t\t})\n\t\tconst importWithLease = (specifier: string) =>\n\t\t\tEffect.acquireRelease(\n\t\t\t\tserial(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* checkOpen\n\t\t\t\t\t\tconst current = yield* Ref.get(state)\n\t\t\t\t\t\thooks.start(current.generation)\n\t\t\t\t\t\t// Acquire an owner for either Exit. Rethrow only after its finalizer\n\t\t\t\t\t\t// is registered, so failed imports remain available for scoped retry.\n\t\t\t\t\t\tconst exit = yield* Effect.exit(load(specifier))\n\t\t\t\t\t\tconst token: symbol = Symbol()\n\t\t\t\t\t\tconst root = Option.orElse(hooks.resolved(), () =>\n\t\t\t\t\t\t\tString.startsWith(\"file:\")(specifier)\n\t\t\t\t\t\t\t\t? Option.some(specifier)\n\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst attempt = Option.map(root, (url) => ({\n\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\tgeneration: current.generation,\n\t\t\t\t\t\t\t...Option.match(hooks.fileRequest(specifier), {\n\t\t\t\t\t\t\t\tonNone: () => ({}),\n\t\t\t\t\t\t\t\tonSome: (request) => ({ request }),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}))\n\t\t\t\t\t\tif (Option.isSome(attempt))\n\t\t\t\t\t\t\tyield* Ref.update(\n\t\t\t\t\t\t\t\tcached,\n\t\t\t\t\t\t\t\tHashMap.set(attempt.value.url, attempt.value),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\tyield* Ref.update(\n\t\t\t\t\t\t\towners,\n\t\t\t\t\t\t\tHashMap.set(token, {\n\t\t\t\t\t\t\t\tactive: Exit.isSuccess(exit) ? attempt : Option.none(),\n\t\t\t\t\t\t\t\tpending: Exit.isFailure(exit) ? attempt : Option.none(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (Exit.isSuccess(exit)) {\n\t\t\t\t\t\t\tconst entry = exit.value\n\t\t\t\t\t\t\tconst previous = HashMap.get(current.entries, entry.url)\n\t\t\t\t\t\t\tconst leases = Option.match(previous, {\n\t\t\t\t\t\t\t\tonNone: () => HashSet.empty<symbol>(),\n\t\t\t\t\t\t\t\tonSome: (entry) => entry.leases,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tyield* Ref.set(state, {\n\t\t\t\t\t\t\t\t...current,\n\t\t\t\t\t\t\t\tentries: HashMap.set(current.entries, entry.url, {\n\t\t\t\t\t\t\t\t\tplugin: entry.plugin,\n\t\t\t\t\t\t\t\t\tleases: HashSet.add(leases, token),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn { token, exit: Exit.map(exit, (entry) => entry.namespace) }\n\t\t\t\t\t}).pipe(\n\t\t\t\t\t\tEffect.ensuring(Effect.sync(hooks.discard)),\n\t\t\t\t\t\tEffect.ensuring(collect),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\t({ token }) =>\n\t\t\t\t\tserial(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tyield* Ref.update(owners, HashMap.remove(token))\n\t\t\t\t\t\t\tyield* Ref.update(state, (current) => ({\n\t\t\t\t\t\t\t\t...current,\n\t\t\t\t\t\t\t\tentries: HashMap.filterMap(current.entries, (entry) => {\n\t\t\t\t\t\t\t\t\tconst leases = HashSet.remove(entry.leases, token)\n\t\t\t\t\t\t\t\t\treturn HashSet.size(leases) === 0\n\t\t\t\t\t\t\t\t\t\t? Result.fail(undefined)\n\t\t\t\t\t\t\t\t\t\t: Result.succeed({ ...entry, leases })\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t}).pipe(Effect.ensuring(collect)),\n\t\t\t\t\t),\n\t\t\t).pipe(\n\t\t\t\tEffect.flatMap(({ exit }) => exit),\n\t\t\t\tpreserveExit,\n\t\t\t)\n\n\t\tconst importerScoped: Hmr[\"importerScoped\"] = (specifier, options) =>\n\t\t\toptions === undefined\n\t\t\t\t? importWithLease(specifier)\n\t\t\t\t: importWithLease(specifier).pipe(\n\t\t\t\t\t\tScope.provide(options.observationScope),\n\t\t\t\t\t)\n\n\t\tconst importer = (specifier: string): Promise<unknown> =>\n\t\t\t// A closed FiberSet interrupts new work before checkOpen can execute.\n\t\t\tRef.getUnsafe(closed)\n\t\t\t\t? Promise.reject(closedError())\n\t\t\t\t: run(\n\t\t\t\t\t\timporterScoped(specifier).pipe(\n\t\t\t\t\t\t\tEffect.provideService(Scope.Scope, imports),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\n\t\tconst unsupportedModules = Ref.get(state).pipe(\n\t\t\tEffect.map(({ generation, entries }) =>\n\t\t\t\thooks.unsupported(\n\t\t\t\t\tgeneration,\n\t\t\t\t\tArray.fromIterable(HashMap.keys(entries)),\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\n\t\tconst reloadChanged = (dirty?: HashSet.HashSet<string>) =>\n\t\t\tserial(\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* checkOpen\n\t\t\t\t\tconst fullRetry = yield* Ref.get(retryAll)\n\t\t\t\t\tconst force = dirty === undefined || fullRetry\n\t\t\t\t\tconst pendingDirty = yield* Ref.get(retryDirty)\n\t\t\t\t\tconst changedFiles =\n\t\t\t\t\t\tdirty === undefined\n\t\t\t\t\t\t\t? pendingDirty\n\t\t\t\t\t\t\t: HashSet.union(pendingDirty, dirty)\n\t\t\t\t\tyield* Ref.set(retryDirty, changedFiles)\n\t\t\t\t\tyield* Ref.set(retryAll, force)\n\t\t\t\t\tconst changed = yield* changedResolutions\n\t\t\t\t\tif (changed.length > 0)\n\t\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\t\treason: \"restart-required\",\n\t\t\t\t\t\t\tmodules: changed,\n\t\t\t\t\t\t})\n\t\t\t\t\tconst { entries } = yield* Ref.get(state)\n\t\t\t\t\tconst unsupported = yield* unsupportedModules\n\t\t\t\t\tif (HashSet.size(unsupported) > 0)\n\t\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\t\tmodules: Array.fromIterable(unsupported),\n\t\t\t\t\t\t\treason: \"restart-required\",\n\t\t\t\t\t\t})\n\t\t\t\t\tconst owned = yield* Ref.get(owners)\n\t\t\t\t\tconst attempts = Array.flatMap(HashMap.toValues(owned), (owner) =>\n\t\t\t\t\t\tArray.getSomes([owner.active, owner.pending]),\n\t\t\t\t\t)\n\t\t\t\t\tconst active = Array.getSomes(\n\t\t\t\t\t\tArray.map(HashMap.toValues(owned), (owner) => owner.active),\n\t\t\t\t\t)\n\t\t\t\t\tlet selected = entries\n\t\t\t\t\tlet invalid = HashSet.empty<string>()\n\t\t\t\t\tif (!force) {\n\t\t\t\t\t\tlet affected = changedFiles\n\t\t\t\t\t\t// Plugin identity groups and importer closure can expand each other.\n\t\t\t\t\t\t// Reach a fixed point before allocating any candidate instance.\n\t\t\t\t\t\twhile (true) {\n\t\t\t\t\t\t\tinvalid = hooks.invalidated(attempts, affected)\n\t\t\t\t\t\t\tconst plugins = HashSet.fromIterable(\n\t\t\t\t\t\t\t\tArray.filterMap(Array.fromIterable(entries), ([url, entry]) =>\n\t\t\t\t\t\t\t\t\tHashSet.has(affected, url) ||\n\t\t\t\t\t\t\t\t\tArray.some(Array.fromIterable(entry.leases), (token) =>\n\t\t\t\t\t\t\t\t\t\tOption.exists(HashMap.get(owned, token), (owner) =>\n\t\t\t\t\t\t\t\t\t\t\tArray.some(\n\t\t\t\t\t\t\t\t\t\t\t\tArray.getSomes([owner.active, owner.pending]),\n\t\t\t\t\t\t\t\t\t\t\t\t(attempt) => hooks.affected(attempt, invalid),\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t? Result.succeed(entry.plugin)\n\t\t\t\t\t\t\t\t\t\t: Result.fail(undefined),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tselected = HashMap.filter(entries, (entry) =>\n\t\t\t\t\t\t\t\tHashSet.has(plugins, entry.plugin),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tconst expanded = HashSet.union(\n\t\t\t\t\t\t\t\taffected,\n\t\t\t\t\t\t\t\tHashSet.fromIterable(HashMap.keys(selected)),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tif (HashSet.size(expanded) === HashSet.size(affected)) break\n\t\t\t\t\t\t\taffected = expanded\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Advance even without entries so retries bypass cached failed imports.\n\t\t\t\t\tconst candidate = force\n\t\t\t\t\t\t? hooks.prepare()\n\t\t\t\t\t\t: hooks.prepare(active, invalid)\n\n\t\t\t\t\thooks.start(candidate, true)\n\t\t\t\t\treturn yield* Effect.gen(function* () {\n\t\t\t\t\t\tconst loaded = yield* Effect.forEach(selected, ([url, previous]) =>\n\t\t\t\t\t\t\tRef.update(\n\t\t\t\t\t\t\t\towners,\n\t\t\t\t\t\t\t\tHashMap.map((owner, token) =>\n\t\t\t\t\t\t\t\t\tHashSet.has(previous.leases, token)\n\t\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t\t...owner,\n\t\t\t\t\t\t\t\t\t\t\t\tpending: Option.map(owner.active, (attempt) => ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t...attempt,\n\t\t\t\t\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\t\t\t\t\tgeneration: candidate,\n\t\t\t\t\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t: owner,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\t\tEffect.andThen(load(url)),\n\t\t\t\t\t\t\t\tEffect.map((entry) =>\n\t\t\t\t\t\t\t\t\tOption.some({\n\t\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\t\tprevious: previous.plugin,\n\t\t\t\t\t\t\t\t\t\tleases: previous.leases,\n\t\t\t\t\t\t\t\t\t\tplugin: entry.plugin,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tEffect.catchIf(\n\t\t\t\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\t\t\t\terror.stage === \"import\" &&\n\t\t\t\t\t\t\t\t\t\tisMissingEntry(url, error.cause),\n\t\t\t\t\t\t\t\t\t() => Effect.succeedNone,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst candidates = Array.getSomes(loaded)\n\t\t\t\t\t\tconst alternatives = Array.reduce(\n\t\t\t\t\t\t\tcandidates,\n\t\t\t\t\t\t\tHashMap.empty<Plugin.Plugin, HashSet.HashSet<Plugin.Plugin>>(),\n\t\t\t\t\t\t\t(groups, { previous, plugin }) =>\n\t\t\t\t\t\t\t\tHashMap.set(\n\t\t\t\t\t\t\t\t\tgroups,\n\t\t\t\t\t\t\t\t\tprevious,\n\t\t\t\t\t\t\t\t\tHashSet.add(\n\t\t\t\t\t\t\t\t\t\tOption.getOrElse(\n\t\t\t\t\t\t\t\t\t\t\tHashMap.get(groups, previous),\n\t\t\t\t\t\t\t\t\t\t\tHashSet.empty<Plugin.Plugin>,\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\tplugin,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst ambiguous = Array.filter(candidates, ({ previous }) =>\n\t\t\t\t\t\t\tOption.exists(\n\t\t\t\t\t\t\t\tHashMap.get(alternatives, previous),\n\t\t\t\t\t\t\t\t(plugins) => HashSet.size(plugins) > 1,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (ambiguous.length > 0)\n\t\t\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\t\t\treason: \"ambiguous-exports\",\n\t\t\t\t\t\t\t\tmodules: Array.map(ambiguous, ({ url }) => url),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\tconst replacements = HashMap.fromIterable(\n\t\t\t\t\t\t\tArray.map(\n\t\t\t\t\t\t\t\tcandidates,\n\t\t\t\t\t\t\t\t({ url, plugin, leases }) => [url, { plugin, leases }] as const,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst next = HashMap.union(\n\t\t\t\t\t\t\tHashMap.filter(entries, (_, url) => !HashMap.has(selected, url)),\n\t\t\t\t\t\t\treplacements,\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tconst pairs = Array.map(\n\t\t\t\t\t\t\tcandidates,\n\t\t\t\t\t\t\t({ previous, plugin }) => [previous, plugin] as const,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst changed = yield* changedResolutions\n\t\t\t\t\t\tif (changed.length > 0)\n\t\t\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\t\t\treason: \"restart-required\",\n\t\t\t\t\t\t\t\tmodules: changed,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t// A reused instance can acquire a native loading capability while the\n\t\t\t\t\t\t// candidate imports. Observe it without throwing into concurrent old code.\n\t\t\t\t\t\tconst unsafe = hooks.unsupported(\n\t\t\t\t\t\t\tcandidate,\n\t\t\t\t\t\t\tArray.fromIterable(HashMap.keys(next)),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (HashSet.size(unsafe) > 0)\n\t\t\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\t\t\treason: \"restart-required\",\n\t\t\t\t\t\t\t\tmodules: Array.fromIterable(unsafe),\n\t\t\t\t\t\t\t})\n\n\t\t\t\t\t\tconst replacement = yield* Graft.replace(pairs).pipe(\n\t\t\t\t\t\t\tEffect.provideService(Graft.Graft, graft),\n\t\t\t\t\t\t\tEffect.result,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (Result.isFailure(replacement))\n\t\t\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\t\t\treason: \"identity-conflict\",\n\t\t\t\t\t\t\t\tmodules: Array.filterMap(candidates, ({ url }, index) =>\n\t\t\t\t\t\t\t\t\tArray.contains(replacement.failure.indices, index)\n\t\t\t\t\t\t\t\t\t\t? Result.succeed(url)\n\t\t\t\t\t\t\t\t\t\t: Result.fail(undefined),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\tyield* Ref.update(owners, (owners) =>\n\t\t\t\t\t\t\tArray.reduce(candidates, owners, (owners, { url, leases }) =>\n\t\t\t\t\t\t\t\tHashMap.map(owners, (owner, token) =>\n\t\t\t\t\t\t\t\t\tHashSet.has(leases, token)\n\t\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t\tactive: Option.map(owner.pending, (attempt) => ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t...attempt,\n\t\t\t\t\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\t\t\t\t\tgeneration: candidate,\n\t\t\t\t\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\t\t\t\t\tpending: Option.none(),\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t: owner,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tyield* Ref.set(state, { entries: next, generation: candidate })\n\t\t\t\t\t\tyield* Ref.set(\n\t\t\t\t\t\t\tcached,\n\t\t\t\t\t\t\tHashMap.map(next, (_, url) => ({ url, generation: candidate })),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tyield* Ref.set(retryDirty, HashSet.empty())\n\t\t\t\t\t\tyield* Ref.set(retryAll, false)\n\t\t\t\t\t\treturn WatchResult.Reloaded({\n\t\t\t\t\t\t\tentries: Array.map(candidates, (candidate) => candidate.url),\n\t\t\t\t\t\t})\n\t\t\t\t\t}).pipe(\n\t\t\t\t\t\tEffect.catchIf(\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t): error is HmrError & { readonly cause: RestartRequired } =>\n\t\t\t\t\t\t\t\tSchema.is(RestartRequired)(error.cause),\n\t\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\t\tEffect.succeed(\n\t\t\t\t\t\t\t\t\tWatchResult.Skipped({\n\t\t\t\t\t\t\t\t\t\tmodules: Array.fromIterable(\n\t\t\t\t\t\t\t\t\t\t\tHashSet.add(\n\t\t\t\t\t\t\t\t\t\t\t\thooks.unsupported(candidate),\n\t\t\t\t\t\t\t\t\t\t\t\terror.cause.url,\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\treason: \"restart-required\",\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tEffect.ensuring(Effect.sync(hooks.discard)),\n\t\t\t\t\t\tEffect.ensuring(collect),\n\t\t\t\t\t)\n\t\t\t\t}),\n\t\t\t)\n\t\treturn {\n\t\t\timporter,\n\t\t\timporterScoped,\n\t\t\treload: reloadChanged(),\n\t\t\twatch: (paths, watchOptions) =>\n\t\t\t\tpaths.length === 0\n\t\t\t\t\t? Stream.empty\n\t\t\t\t\t: changes(\n\t\t\t\t\t\t\t(use) =>\n\t\t\t\t\t\t\t\tRef.get(owners).pipe(\n\t\t\t\t\t\t\t\t\tEffect.map((owners) =>\n\t\t\t\t\t\t\t\t\t\thooks.files([\n\t\t\t\t\t\t\t\t\t\t\t...Array.getSomes(\n\t\t\t\t\t\t\t\t\t\t\t\tArray.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\tHashMap.toValues(owners),\n\t\t\t\t\t\t\t\t\t\t\t\t\t(owner) => owner.active,\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t...Array.getSomes(\n\t\t\t\t\t\t\t\t\t\t\t\tArray.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\tHashMap.toValues(owners),\n\t\t\t\t\t\t\t\t\t\t\t\t\t(owner) => owner.pending,\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tEffect.flatMap(use),\n\t\t\t\t\t\t\t\t\t// Only snapshot and validation callbacks run under the lock.\n\t\t\t\t\t\t\t\t\t// File reads operate on the retained immutable snapshot outside it.\n\t\t\t\t\t\t\t\t\tsemaphore.withPermits(1),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tpaths,\n\t\t\t\t\t\t\twatchOptions,\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tStream.mapEffect((dirty) =>\n\t\t\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\t\t\tEffect.exit(reloadChanged(dirty)),\n\t\t\t\t\t\t\t\t\t(exit): WatchResult =>\n\t\t\t\t\t\t\t\t\t\tExit.isSuccess(exit)\n\t\t\t\t\t\t\t\t\t\t\t? exit.value\n\t\t\t\t\t\t\t\t\t\t\t: WatchResult.Failed({ cause: exit.cause }),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t// Native caches may hide both dependency edges and the first save.\n\t\t\t\t\t\t\t// Report known restart requirements independently of filesystem events.\n\t\t\t\t\t\t\tStream.merge(\n\t\t\t\t\t\t\t\tStream.tick(\"1 second\").pipe(\n\t\t\t\t\t\t\t\t\tStream.mapEffect(() =>\n\t\t\t\t\t\t\t\t\t\tunsupportedModules.pipe(semaphore.withPermits(1)),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tStream.changes,\n\t\t\t\t\t\t\t\t\tStream.filter((modules) => HashSet.size(modules) > 0),\n\t\t\t\t\t\t\t\t\tStream.map((modules) =>\n\t\t\t\t\t\t\t\t\t\tWatchResult.Skipped({\n\t\t\t\t\t\t\t\t\t\t\treason: \"restart-required\",\n\t\t\t\t\t\t\t\t\t\t\tmodules: Array.fromIterable(modules),\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t}\n\t})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { digest } from "./digest.js";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
import { Array, Cause, Clock, Effect, Equal, Exit, FileSystem, HashMap, HashSet, Option, Queue, Ref, Stream, String } from "effect";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
// src/internal/changes.ts
|
|
6
|
+
/** Observe the producer after its resource scope closes, before merge can discard its Exit. */
|
|
7
|
+
const observeSource = (source) => Stream.callback((queue) => source.pipe(Stream.runForEachArray((values) => Queue.offerAll(queue, values)), Effect.scoped, Effect.onExit((exit) => Exit.isFailure(exit) && Cause.hasInterrupts(exit.cause) && !Cause.hasInterruptsOnly(exit.cause) ? Effect.logError("Native watcher failed during cancellation", exit.cause) : Effect.void), Effect.andThen(Queue.end(queue))), { bufferSize: 16 });
|
|
8
|
+
/** Compare immutable observation tokens again after filesystem I/O releases the lock. */
|
|
9
|
+
const sameVersion = (a, b) => a.generation === b.generation && a.content === b.content && a.resolution === b.resolution;
|
|
10
|
+
/** Reconcile owned modules because native watchers can silently miss events. */
|
|
11
|
+
const changes = (withFiles, paths, options) => Stream.unwrap(Effect.gen(function* () {
|
|
12
|
+
if (paths.length === 0) return Stream.empty;
|
|
13
|
+
const fs = yield* FileSystem.FileSystem;
|
|
14
|
+
const roots = yield* Effect.forEach(paths, (path) => Effect.gen(function* () {
|
|
15
|
+
const canonical = yield* fs.realPath(path);
|
|
16
|
+
const info = yield* fs.stat(canonical);
|
|
17
|
+
return {
|
|
18
|
+
path: canonical,
|
|
19
|
+
urls: [pathToFileURL(canonical).href, pathToFileURL(resolve(path)).href],
|
|
20
|
+
directory: info.type === "Directory"
|
|
21
|
+
};
|
|
22
|
+
}));
|
|
23
|
+
const accepted = (path) => options?.filter?.(path) ?? true;
|
|
24
|
+
const watched = (url) => accepted(fileURLToPath(url)) && Array.some(roots, ({ urls, directory }) => Array.some(urls, (root) => {
|
|
25
|
+
const prefix = String.endsWith("/")(root) ? root : `${root}/`;
|
|
26
|
+
return fileURLToPath(url) === fileURLToPath(root) || directory && (options?.recursive === true ? String.startsWith(prefix)(url) : new URL(".", url).href === prefix);
|
|
27
|
+
}));
|
|
28
|
+
const files = withFiles((loaded) => Effect.succeed(HashMap.filter(loaded, (_, url) => watched(url))));
|
|
29
|
+
const missing = (effect) => effect.pipe(Effect.catchIf((error) => error.reason._tag === "NotFound", () => Effect.undefined));
|
|
30
|
+
const existingDirectory = (path) => Effect.gen(function* () {
|
|
31
|
+
return (yield* missing(fs.stat(path)))?.type === "Directory" || dirname(path) === path ? path : yield* existingDirectory(dirname(path));
|
|
32
|
+
});
|
|
33
|
+
const directories = Effect.gen(function* () {
|
|
34
|
+
const loaded = yield* files;
|
|
35
|
+
const requested = HashSet.fromIterable([...Array.map(roots, (root) => root.directory ? root.path : dirname(root.path)), ...Array.map(Array.fromIterable(HashMap.keys(loaded)), (url) => dirname(fileURLToPath(url)))]);
|
|
36
|
+
const existing = yield* Effect.forEach(requested, existingDirectory, { concurrency: 8 });
|
|
37
|
+
return HashSet.fromIterable(existing);
|
|
38
|
+
});
|
|
39
|
+
const dirty = yield* Ref.make(HashSet.empty());
|
|
40
|
+
const native = Stream.tick("1 second").pipe(Stream.mapEffect(() => directories), Stream.changesWith((a, b) => Equal.equals(a, b)), Stream.switchMap((directories) => Stream.mergeAll(Array.map(Array.fromIterable(directories), (directory) => fs.watch(directory, {
|
|
41
|
+
...options,
|
|
42
|
+
recursive: false
|
|
43
|
+
}).pipe(observeSource, Stream.map((event) => resolve(directory, event.path || ".")))), { concurrency: "unbounded" })), Stream.tap((path) => Ref.update(dirty, HashSet.add(path))), Stream.debounce(options?.debounce ?? "50 millis"), Stream.mapEffect(() => Ref.getAndSet(dirty, HashSet.empty())));
|
|
44
|
+
const observed = yield* Ref.make(HashMap.empty());
|
|
45
|
+
const scan = (events) => Effect.gen(function* () {
|
|
46
|
+
const loaded = yield* files;
|
|
47
|
+
const before = yield* Ref.get(observed);
|
|
48
|
+
const now = yield* Clock.currentTimeMillis;
|
|
49
|
+
const selected = Array.filter(Array.fromIterable(loaded), ([url]) => events === void 0 || Array.some(Array.fromIterable(events), (path) => {
|
|
50
|
+
const file = fileURLToPath(url);
|
|
51
|
+
return file === path || String.startsWith(`${path}/`)(file);
|
|
52
|
+
}));
|
|
53
|
+
const checked = yield* Effect.forEach(selected, ([url, baseline]) => Effect.gen(function* () {
|
|
54
|
+
const path = fileURLToPath(url);
|
|
55
|
+
const previous = Option.getOrUndefined(HashMap.get(before, url));
|
|
56
|
+
const info = yield* missing(fs.stat(path));
|
|
57
|
+
const stat = info === void 0 ? "missing" : `${info.size}:${Option.getOrUndefined(info.mtime)?.getTime()}:${Option.getOrUndefined(info.ino)}`;
|
|
58
|
+
const resolution = baseline.resolution === void 0 ? void 0 : yield* missing(fs.realPath(path)).pipe(Effect.map((path) => path === void 0 ? void 0 : pathToFileURL(path).href));
|
|
59
|
+
const audit = events !== void 0 || previous === void 0 || !sameVersion(previous.baseline, baseline) || previous.stat !== stat || now - previous.checked >= 1e4;
|
|
60
|
+
const content = audit ? yield* missing(fs.readFile(path)).pipe(Effect.map((bytes) => bytes === void 0 ? void 0 : digest(bytes))) : previous.content;
|
|
61
|
+
const expected = previous === void 0 ? baseline : {
|
|
62
|
+
content: baseline.content === void 0 || sameVersion(previous.baseline, baseline) ? previous.content : baseline.content,
|
|
63
|
+
resolution: sameVersion(previous.baseline, baseline) ? previous.resolution : baseline.resolution
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
url,
|
|
67
|
+
observation: {
|
|
68
|
+
baseline,
|
|
69
|
+
content,
|
|
70
|
+
resolution,
|
|
71
|
+
stat,
|
|
72
|
+
checked: audit ? now : previous.checked
|
|
73
|
+
},
|
|
74
|
+
changed: expected.content !== content || expected.resolution !== resolution
|
|
75
|
+
};
|
|
76
|
+
}), { concurrency: 8 });
|
|
77
|
+
return yield* withFiles((current) => Ref.modify(observed, (seen) => {
|
|
78
|
+
let next = HashMap.filter(seen, (_, url) => HashMap.has(current, url));
|
|
79
|
+
let changed = HashSet.empty();
|
|
80
|
+
for (const { url, observation, changed: differs } of checked) {
|
|
81
|
+
if (!Option.exists(HashMap.get(current, url), (version) => sameVersion(version, observation.baseline))) continue;
|
|
82
|
+
next = HashMap.set(next, url, observation);
|
|
83
|
+
if (differs) changed = HashSet.add(changed, url);
|
|
84
|
+
}
|
|
85
|
+
return [changed, next];
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
return Stream.merge(native, Stream.tick("1 second").pipe(Stream.drop(1), Stream.map(() => void 0))).pipe(Stream.mapEffect(scan), Stream.filter((changed) => HashSet.size(changed) > 0));
|
|
89
|
+
}));
|
|
90
|
+
export { changes };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.js","names":[],"sources":["../../src/internal/changes.ts"],"sourcesContent":["// @effect-diagnostics-next-line nodeBuiltinImport:off -- watcher paths use host-platform syntax without adding a Path service requirement\nimport { dirname, resolve } from \"node:path\"\nimport type { FileVersion } from \"./modules\"\nimport type { WatchOptions } from \"../index\"\nimport { digest } from \"./digest\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport {\n\tArray,\n\tCause,\n\tExit,\n\tEffect,\n\tFileSystem,\n\tHashMap,\n\tHashSet,\n\tEqual,\n\tClock,\n\tRef,\n\tQueue,\n\tOption,\n\tStream,\n\tString,\n} from \"effect\"\n\nexport type WithFiles = <A, E, R>(\n\tuse: (files: HashMap.HashMap<string, FileVersion>) => Effect.Effect<A, E, R>,\n) => Effect.Effect<A, E, R>\n\n/** Observe the producer after its resource scope closes, before merge can discard its Exit. */\nconst observeSource = <A, E, R>(source: Stream.Stream<A, E, R>) =>\n\tStream.callback<A, E, R>(\n\t\t(queue) =>\n\t\t\tsource.pipe(\n\t\t\t\tStream.runForEachArray((values) => Queue.offerAll(queue, values)),\n\t\t\t\tEffect.scoped,\n\t\t\t\tEffect.onExit((exit) =>\n\t\t\t\t\tExit.isFailure(exit) &&\n\t\t\t\t\tCause.hasInterrupts(exit.cause) &&\n\t\t\t\t\t!Cause.hasInterruptsOnly(exit.cause)\n\t\t\t\t\t\t? Effect.logError(\n\t\t\t\t\t\t\t\t\"Native watcher failed during cancellation\",\n\t\t\t\t\t\t\t\texit.cause,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: Effect.void,\n\t\t\t\t),\n\t\t\t\tEffect.andThen(Queue.end(queue)),\n\t\t\t),\n\t\t{ bufferSize: 16 },\n\t)\n\n/** Compare immutable observation tokens again after filesystem I/O releases the lock. */\nconst sameVersion = (a: FileVersion, b: FileVersion) =>\n\ta.generation === b.generation &&\n\ta.content === b.content &&\n\ta.resolution === b.resolution\n\n/** Reconcile owned modules because native watchers can silently miss events. */\nexport const changes = (\n\twithFiles: WithFiles,\n\tpaths: ReadonlyArray<string>,\n\toptions?: WatchOptions,\n) =>\n\tStream.unwrap(\n\t\tEffect.gen(function* () {\n\t\t\tif (paths.length === 0) return Stream.empty\n\t\t\tconst fs = yield* FileSystem.FileSystem\n\t\t\tconst roots = yield* Effect.forEach(paths, (path) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst canonical = yield* fs.realPath(path)\n\t\t\t\t\tconst info = yield* fs.stat(canonical)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpath: canonical,\n\t\t\t\t\t\turls: [\n\t\t\t\t\t\t\tpathToFileURL(canonical).href,\n\t\t\t\t\t\t\tpathToFileURL(resolve(path)).href,\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdirectory: info.type === \"Directory\",\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t)\n\t\t\tconst accepted = (path: string) => options?.filter?.(path) ?? true\n\t\t\tconst watched = (url: string) =>\n\t\t\t\taccepted(fileURLToPath(url)) &&\n\t\t\t\tArray.some(roots, ({ urls, directory }) =>\n\t\t\t\t\tArray.some(urls, (root) => {\n\t\t\t\t\t\tconst prefix = String.endsWith(\"/\")(root) ? root : `${root}/`\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tfileURLToPath(url) === fileURLToPath(root) ||\n\t\t\t\t\t\t\t(directory &&\n\t\t\t\t\t\t\t\t(options?.recursive === true\n\t\t\t\t\t\t\t\t\t? String.startsWith(prefix)(url)\n\t\t\t\t\t\t\t\t\t: new URL(\".\", url).href === prefix))\n\t\t\t\t\t\t)\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\tconst files = withFiles((loaded) =>\n\t\t\t\tEffect.succeed(HashMap.filter(loaded, (_, url) => watched(url))),\n\t\t\t)\n\t\t\tconst missing = <\n\t\t\t\tA,\n\t\t\t\tE extends import(\"effect\").PlatformError.PlatformError,\n\t\t\t\tR,\n\t\t\t>(\n\t\t\t\teffect: Effect.Effect<A, E, R>,\n\t\t\t) =>\n\t\t\t\teffect.pipe(\n\t\t\t\t\tEffect.catchIf(\n\t\t\t\t\t\t(error) => error.reason._tag === \"NotFound\",\n\t\t\t\t\t\t() => Effect.undefined,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t// Watch only graph directories, never recursively traverse an application root.\n\t\t\t// Missing descendants use their nearest existing ancestor until they appear.\n\t\t\tconst existingDirectory = (\n\t\t\t\tpath: string,\n\t\t\t): Effect.Effect<string, import(\"effect\").PlatformError.PlatformError> =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst info = yield* missing(fs.stat(path))\n\t\t\t\t\treturn info?.type === \"Directory\" || dirname(path) === path\n\t\t\t\t\t\t? path\n\t\t\t\t\t\t: yield* existingDirectory(dirname(path))\n\t\t\t\t})\n\t\t\tconst directories = Effect.gen(function* () {\n\t\t\t\tconst loaded = yield* files\n\t\t\t\tconst requested = HashSet.fromIterable([\n\t\t\t\t\t...Array.map(roots, (root) =>\n\t\t\t\t\t\troot.directory ? root.path : dirname(root.path),\n\t\t\t\t\t),\n\t\t\t\t\t...Array.map(Array.fromIterable(HashMap.keys(loaded)), (url) =>\n\t\t\t\t\t\tdirname(fileURLToPath(url)),\n\t\t\t\t\t),\n\t\t\t\t])\n\t\t\t\tconst existing = yield* Effect.forEach(requested, existingDirectory, {\n\t\t\t\t\tconcurrency: 8,\n\t\t\t\t})\n\t\t\t\treturn HashSet.fromIterable(existing)\n\t\t\t})\n\t\t\tconst dirty = yield* Ref.make(HashSet.empty<string>())\n\t\t\tconst native = Stream.tick(\"1 second\").pipe(\n\t\t\t\tStream.mapEffect(() => directories),\n\t\t\t\tStream.changesWith((a, b) => Equal.equals(a, b)),\n\t\t\t\tStream.switchMap((directories) =>\n\t\t\t\t\tStream.mergeAll(\n\t\t\t\t\t\tArray.map(Array.fromIterable(directories), (directory) =>\n\t\t\t\t\t\t\tfs.watch(directory, { ...options, recursive: false }).pipe(\n\t\t\t\t\t\t\t\tobserveSource,\n\t\t\t\t\t\t\t\tStream.map((event) => resolve(directory, event.path || \".\")),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t{ concurrency: \"unbounded\" },\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tStream.tap((path) => Ref.update(dirty, HashSet.add(path))),\n\t\t\t\tStream.debounce(options?.debounce ?? \"50 millis\"),\n\t\t\t\tStream.mapEffect(() => Ref.getAndSet(dirty, HashSet.empty<string>())),\n\t\t\t)\n\t\t\tinterface Observation {\n\t\t\t\treadonly baseline: FileVersion\n\t\t\t\treadonly content: string | undefined\n\t\t\t\treadonly resolution: string | undefined\n\t\t\t\treadonly stat: string\n\t\t\t\treadonly checked: number\n\t\t\t}\n\t\t\tconst observed = yield* Ref.make(HashMap.empty<string, Observation>())\n\t\t\tconst scan = (events: HashSet.HashSet<string> | undefined) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst loaded = yield* files\n\t\t\t\t\tconst before = yield* Ref.get(observed)\n\t\t\t\t\tconst now = yield* Clock.currentTimeMillis\n\t\t\t\t\tconst selected = Array.filter(\n\t\t\t\t\t\tArray.fromIterable(loaded),\n\t\t\t\t\t\t([url]) =>\n\t\t\t\t\t\t\tevents === undefined ||\n\t\t\t\t\t\t\tArray.some(Array.fromIterable(events), (path) => {\n\t\t\t\t\t\t\t\tconst file = fileURLToPath(url)\n\t\t\t\t\t\t\t\treturn file === path || String.startsWith(`${path}/`)(file)\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\tconst checked = yield* Effect.forEach(\n\t\t\t\t\t\tselected,\n\t\t\t\t\t\t([url, baseline]) =>\n\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\tconst path = fileURLToPath(url)\n\t\t\t\t\t\t\t\tconst previous = Option.getOrUndefined(HashMap.get(before, url))\n\t\t\t\t\t\t\t\tconst info = yield* missing(fs.stat(path))\n\t\t\t\t\t\t\t\tconst stat =\n\t\t\t\t\t\t\t\t\tinfo === undefined\n\t\t\t\t\t\t\t\t\t\t? \"missing\"\n\t\t\t\t\t\t\t\t\t\t: `${info.size}:${Option.getOrUndefined(info.mtime)?.getTime()}:${Option.getOrUndefined(info.ino)}`\n\t\t\t\t\t\t\t\tconst resolution =\n\t\t\t\t\t\t\t\t\tbaseline.resolution === undefined\n\t\t\t\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t\t\t\t: yield* missing(fs.realPath(path)).pipe(\n\t\t\t\t\t\t\t\t\t\t\t\tEffect.map((path) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tpath === undefined\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t: pathToFileURL(path).href,\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst audit =\n\t\t\t\t\t\t\t\t\tevents !== undefined ||\n\t\t\t\t\t\t\t\t\tprevious === undefined ||\n\t\t\t\t\t\t\t\t\t!sameVersion(previous.baseline, baseline) ||\n\t\t\t\t\t\t\t\t\tprevious.stat !== stat ||\n\t\t\t\t\t\t\t\t\tnow - previous.checked >= 10000\n\t\t\t\t\t\t\t\tconst content = audit\n\t\t\t\t\t\t\t\t\t? yield* missing(fs.readFile(path)).pipe(\n\t\t\t\t\t\t\t\t\t\t\tEffect.map((bytes) =>\n\t\t\t\t\t\t\t\t\t\t\t\tbytes === undefined ? undefined : digest(bytes),\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t: previous.content\n\t\t\t\t\t\t\t\tconst expected =\n\t\t\t\t\t\t\t\t\tprevious === undefined\n\t\t\t\t\t\t\t\t\t\t? baseline\n\t\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\t\tcontent:\n\t\t\t\t\t\t\t\t\t\t\t\t\tbaseline.content === undefined ||\n\t\t\t\t\t\t\t\t\t\t\t\t\tsameVersion(previous.baseline, baseline)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t? previous.content\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t: baseline.content,\n\t\t\t\t\t\t\t\t\t\t\t\tresolution: sameVersion(previous.baseline, baseline)\n\t\t\t\t\t\t\t\t\t\t\t\t\t? previous.resolution\n\t\t\t\t\t\t\t\t\t\t\t\t\t: baseline.resolution,\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\tobservation: {\n\t\t\t\t\t\t\t\t\t\tbaseline,\n\t\t\t\t\t\t\t\t\t\tcontent,\n\t\t\t\t\t\t\t\t\t\tresolution,\n\t\t\t\t\t\t\t\t\t\tstat,\n\t\t\t\t\t\t\t\t\t\tchecked: audit ? now : previous.checked,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tchanged:\n\t\t\t\t\t\t\t\t\t\texpected.content !== content ||\n\t\t\t\t\t\t\t\t\t\texpected.resolution !== resolution,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t{ concurrency: 8 },\n\t\t\t\t\t)\n\t\t\t\t\t// The immutable snapshot keeps its bytes alive while owners can be released.\n\t\t\t\t\t// A stale result cannot acknowledge a newer import, even when bytes coincide.\n\t\t\t\t\treturn yield* withFiles((current) =>\n\t\t\t\t\t\tRef.modify(observed, (seen) => {\n\t\t\t\t\t\t\tlet next = HashMap.filter(seen, (_, url) =>\n\t\t\t\t\t\t\t\tHashMap.has(current, url),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tlet changed = HashSet.empty<string>()\n\t\t\t\t\t\t\tfor (const { url, observation, changed: differs } of checked) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t!Option.exists(HashMap.get(current, url), (version) =>\n\t\t\t\t\t\t\t\t\t\tsameVersion(version, observation.baseline),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\tnext = HashMap.set(next, url, observation)\n\t\t\t\t\t\t\t\tif (differs) changed = HashSet.add(changed, url)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn [changed, next]\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\treturn Stream.merge(\n\t\t\t\tnative,\n\t\t\t\tStream.tick(\"1 second\").pipe(\n\t\t\t\t\tStream.drop(1),\n\t\t\t\t\tStream.map(() => undefined),\n\t\t\t\t),\n\t\t\t).pipe(\n\t\t\t\tStream.mapEffect(scan),\n\t\t\t\tStream.filter((changed) => HashSet.size(changed) > 0),\n\t\t\t)\n\t\t}),\n\t)\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.js","names":[],"sources":["../../src/internal/digest.ts"],"sourcesContent":["// @effect-diagnostics-next-line nodeBuiltinImport:off -- synchronous Node hooks need a synchronous digest of the bytes actually loaded\nimport { createHash } from \"node:crypto\"\n\nexport const digest = (bytes: string | Uint8Array): string =>\n\tcreateHash(\"sha256\").update(bytes).digest(\"hex\")\n"],"mappings":";;AAGA,MAAa,UAAU,UACtB,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK"}
|