@graftland/hmr 0.1.0 → 0.2.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -2
- package/README.zh-CN.md +120 -0
- package/README.zh-TW.md +120 -0
- package/dist/index.d.ts +39 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +187 -45
- package/dist/index.js.map +1 -1
- package/dist/internal/changes.js +59 -0
- package/dist/internal/changes.js.map +1 -0
- package/dist/internal/modules.js +141 -28
- package/dist/internal/modules.js.map +1 -1
- package/package.json +7 -5
- package/src/index.ts +451 -87
- package/src/internal/changes.ts +199 -0
- package/src/internal/modules.ts +335 -42
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,53 @@ 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 state = yield* Ref.make({
|
|
48
|
+
generation: hooks.prepare(),
|
|
49
|
+
entries: HashMap.empty()
|
|
50
|
+
});
|
|
51
|
+
const imports = yield* Scope.fork(session);
|
|
36
52
|
const run = yield* FiberSet.makeRuntimePromise();
|
|
37
|
-
const
|
|
53
|
+
const closedError = () => HmrError.make({
|
|
38
54
|
stage: "closed",
|
|
39
55
|
specifier: "",
|
|
40
56
|
cause: "HMR scope is closed"
|
|
41
|
-
})
|
|
57
|
+
});
|
|
58
|
+
const checkOpen = Ref.get(closed).pipe(Effect.flatMap((closed) => closed ? Effect.fail(closedError()) : Effect.void));
|
|
59
|
+
const physicalTarget = (request) => Effect.tryPromise({
|
|
60
|
+
try: () => realpath(fileURLToPath(request)),
|
|
61
|
+
catch: (cause) => HmrError.make({
|
|
62
|
+
stage: "import",
|
|
63
|
+
specifier: request,
|
|
64
|
+
cause
|
|
65
|
+
})
|
|
66
|
+
}).pipe(Effect.map((path) => pathToFileURL(path).href), Effect.catchIf((error) => isMissingEntry(request, error.cause), () => Effect.undefined));
|
|
67
|
+
const changedResolution = Effect.fnUntraced(function* (attempt) {
|
|
68
|
+
if (attempt.request === void 0) return Option.none();
|
|
69
|
+
const request = attempt.request;
|
|
70
|
+
const expected = pathToFileURL(fileURLToPath(attempt.url)).href;
|
|
71
|
+
const actual = yield* physicalTarget(request);
|
|
72
|
+
if (actual === expected) return Option.none();
|
|
73
|
+
if (actual === void 0) {
|
|
74
|
+
if ((yield* physicalTarget(attempt.url)) === void 0) return Option.none();
|
|
75
|
+
}
|
|
76
|
+
return Option.some(request);
|
|
77
|
+
});
|
|
78
|
+
const changedResolutions = Ref.get(owners).pipe(Effect.flatMap((owners) => Effect.forEach(hooks.resolutions(Array.flatMap(HashMap.toValues(owners), (owner) => Array.getSomes([owner.active, owner.pending]))), changedResolution)), Effect.map((changes) => Array.fromIterable(HashSet.fromIterable(Array.getSomes(changes)))));
|
|
42
79
|
const load = Effect.fnUntraced(function* (specifier) {
|
|
43
80
|
const namespace = yield* Effect.tryPromise({
|
|
44
81
|
try: () => import(
|
|
@@ -57,59 +94,164 @@ const make = (options) => Effect.gen(function* () {
|
|
|
57
94
|
specifier,
|
|
58
95
|
cause: "Expected a default or named plugin export"
|
|
59
96
|
});
|
|
97
|
+
const url = yield* Effect.fromOption(hooks.resolved()).pipe(Effect.mapError((cause) => HmrError.make({
|
|
98
|
+
stage: "import",
|
|
99
|
+
specifier,
|
|
100
|
+
cause
|
|
101
|
+
})));
|
|
102
|
+
const request = hooks.fileRequest(specifier);
|
|
103
|
+
const attempt = {
|
|
104
|
+
url,
|
|
105
|
+
...Option.match(request, {
|
|
106
|
+
onNone: () => ({}),
|
|
107
|
+
onSome: (request) => ({ request })
|
|
108
|
+
})
|
|
109
|
+
};
|
|
110
|
+
const changed = yield* changedResolution(attempt);
|
|
111
|
+
if (Option.isSome(changed)) return yield* HmrError.make({
|
|
112
|
+
stage: "import",
|
|
113
|
+
specifier,
|
|
114
|
+
cause: RestartRequired.make({ url: changed.value })
|
|
115
|
+
});
|
|
60
116
|
return {
|
|
61
117
|
namespace,
|
|
62
118
|
plugin: plugin.value,
|
|
63
|
-
url
|
|
119
|
+
url
|
|
64
120
|
};
|
|
65
121
|
});
|
|
66
|
-
const
|
|
122
|
+
const importWithLease = (specifier) => Effect.acquireRelease(serial(Effect.gen(function* () {
|
|
67
123
|
yield* checkOpen;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
124
|
+
const current = yield* Ref.get(state);
|
|
125
|
+
hooks.start(current.generation);
|
|
126
|
+
const exit = yield* Effect.exit(load(specifier));
|
|
127
|
+
const token = Symbol();
|
|
128
|
+
const root = Option.orElse(hooks.resolved(), () => String.startsWith("file:")(specifier) ? Option.some(specifier) : Option.none());
|
|
129
|
+
const attempt = Option.map(root, (url) => ({
|
|
130
|
+
url,
|
|
131
|
+
generation: current.generation,
|
|
132
|
+
...Option.match(hooks.fileRequest(specifier), {
|
|
133
|
+
onNone: () => ({}),
|
|
134
|
+
onSome: (request) => ({ request })
|
|
135
|
+
})
|
|
136
|
+
}));
|
|
137
|
+
yield* Ref.update(owners, HashMap.set(token, {
|
|
138
|
+
active: Exit.isSuccess(exit) ? attempt : Option.none(),
|
|
139
|
+
pending: Exit.isFailure(exit) ? attempt : Option.none()
|
|
140
|
+
}));
|
|
141
|
+
if (Exit.isSuccess(exit)) {
|
|
142
|
+
const entry = exit.value;
|
|
143
|
+
const previous = HashMap.get(current.entries, entry.url);
|
|
144
|
+
const leases = Option.match(previous, {
|
|
145
|
+
onNone: () => HashSet.empty(),
|
|
146
|
+
onSome: (entry) => entry.leases
|
|
147
|
+
});
|
|
148
|
+
yield* Ref.set(state, {
|
|
149
|
+
...current,
|
|
150
|
+
entries: HashMap.set(current.entries, entry.url, {
|
|
151
|
+
plugin: entry.plugin,
|
|
152
|
+
leases: HashSet.add(leases, token)
|
|
153
|
+
})
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
token,
|
|
158
|
+
exit: Exit.map(exit, (entry) => entry.namespace)
|
|
159
|
+
};
|
|
160
|
+
}).pipe(Effect.ensuring(Effect.sync(hooks.discard)))), ({ token }) => serial(Effect.gen(function* () {
|
|
161
|
+
yield* Ref.update(owners, HashMap.remove(token));
|
|
162
|
+
yield* Ref.update(state, (current) => ({
|
|
163
|
+
...current,
|
|
164
|
+
entries: HashMap.filterMap(current.entries, (entry) => {
|
|
165
|
+
const leases = HashSet.remove(entry.leases, token);
|
|
166
|
+
return HashSet.size(leases) === 0 ? Result.fail(void 0) : Result.succeed({
|
|
167
|
+
...entry,
|
|
168
|
+
leases
|
|
169
|
+
});
|
|
170
|
+
})
|
|
171
|
+
}));
|
|
172
|
+
}))).pipe(Effect.flatMap(({ exit }) => exit), preserveExit);
|
|
173
|
+
const importerScoped = (specifier, options) => options === void 0 ? importWithLease(specifier) : importWithLease(specifier).pipe(Scope.provide(options.observationScope));
|
|
174
|
+
const importer = (specifier) => Ref.getUnsafe(closed) ? Promise.reject(closedError()) : run(importerScoped(specifier).pipe(Effect.provideService(Scope.Scope, imports)));
|
|
175
|
+
const unsupportedModules = Ref.get(state).pipe(Effect.map(({ generation, entries }) => hooks.unsupported(generation, Array.fromIterable(HashMap.keys(entries)))));
|
|
73
176
|
const reload = serial(Effect.gen(function* () {
|
|
74
177
|
yield* checkOpen;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
178
|
+
const changed = yield* changedResolutions;
|
|
179
|
+
if (changed.length > 0) return WatchResult.Skipped({
|
|
180
|
+
reason: "restart-required",
|
|
181
|
+
modules: changed
|
|
182
|
+
});
|
|
183
|
+
const { entries } = yield* Ref.get(state);
|
|
184
|
+
const unsupported = yield* unsupportedModules;
|
|
185
|
+
if (HashSet.size(unsupported) > 0) return WatchResult.Skipped({
|
|
186
|
+
modules: Array.fromIterable(unsupported),
|
|
78
187
|
reason: "restart-required"
|
|
79
|
-
};
|
|
188
|
+
});
|
|
80
189
|
const candidate = hooks.prepare();
|
|
81
190
|
hooks.start(candidate, true);
|
|
82
191
|
return yield* Effect.gen(function* () {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
192
|
+
const loaded = yield* Effect.forEach(entries, ([url, previous]) => Ref.update(owners, HashMap.map((owner, token) => HashSet.has(previous.leases, token) ? {
|
|
193
|
+
...owner,
|
|
194
|
+
pending: Option.map(owner.active, (attempt) => ({
|
|
195
|
+
...attempt,
|
|
196
|
+
url,
|
|
197
|
+
generation: candidate
|
|
198
|
+
}))
|
|
199
|
+
} : owner)).pipe(Effect.andThen(load(url)), Effect.map((entry) => Option.some({
|
|
200
|
+
url,
|
|
201
|
+
previous: previous.plugin,
|
|
202
|
+
leases: previous.leases,
|
|
203
|
+
plugin: entry.plugin
|
|
204
|
+
})), Effect.catchIf((error) => error.stage === "import" && isMissingEntry(url, error.cause), () => Effect.succeedNone)));
|
|
205
|
+
const candidates = Array.getSomes(loaded);
|
|
206
|
+
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)));
|
|
207
|
+
const ambiguous = Array.filter(candidates, ({ previous }) => Option.exists(HashMap.get(alternatives, previous), (plugins) => HashSet.size(plugins) > 1));
|
|
208
|
+
if (ambiguous.length > 0) return WatchResult.Skipped({
|
|
209
|
+
reason: "ambiguous-exports",
|
|
210
|
+
modules: Array.map(ambiguous, ({ url }) => url)
|
|
211
|
+
});
|
|
212
|
+
const next = HashMap.fromIterable(Array.map(candidates, ({ url, plugin, leases }) => [url, {
|
|
213
|
+
plugin,
|
|
214
|
+
leases
|
|
215
|
+
}]));
|
|
216
|
+
const pairs = Array.map(candidates, ({ previous, plugin }) => [previous, plugin]);
|
|
217
|
+
const changed = yield* changedResolutions;
|
|
218
|
+
if (changed.length > 0) return WatchResult.Skipped({
|
|
219
|
+
reason: "restart-required",
|
|
220
|
+
modules: changed
|
|
221
|
+
});
|
|
222
|
+
const replacement = yield* Graft.replace(pairs).pipe(Effect.provideService(Graft.Graft, graft), Effect.result);
|
|
223
|
+
if (Result.isFailure(replacement)) return WatchResult.Skipped({
|
|
224
|
+
reason: "identity-conflict",
|
|
225
|
+
modules: Array.filterMap(candidates, ({ url }, index) => Array.contains(replacement.failure.indices, index) ? Result.succeed(url) : Result.fail(void 0))
|
|
226
|
+
});
|
|
227
|
+
yield* Ref.update(owners, (owners) => Array.reduce(candidates, owners, (owners, { url, leases }) => HashMap.map(owners, (owner, token) => HashSet.has(leases, token) ? {
|
|
228
|
+
active: Option.map(owner.pending, (attempt) => ({
|
|
229
|
+
...attempt,
|
|
230
|
+
url,
|
|
231
|
+
generation: candidate
|
|
232
|
+
})),
|
|
233
|
+
pending: Option.none()
|
|
234
|
+
} : owner)));
|
|
235
|
+
yield* Ref.set(state, {
|
|
236
|
+
entries: next,
|
|
237
|
+
generation: candidate
|
|
238
|
+
});
|
|
239
|
+
return WatchResult.Reloaded({ entries: Array.fromIterable(HashMap.keys(next)) });
|
|
240
|
+
}).pipe(Effect.catchIf((error) => Schema.is(RestartRequired)(error.cause), (error) => Effect.succeed(WatchResult.Skipped({
|
|
241
|
+
modules: Array.fromIterable(HashSet.add(hooks.unsupported(candidate), error.cause.url)),
|
|
101
242
|
reason: "restart-required"
|
|
102
|
-
})), Effect.ensuring(Effect.sync(hooks.discard)));
|
|
243
|
+
}))), Effect.ensuring(Effect.sync(hooks.discard)));
|
|
103
244
|
}));
|
|
104
245
|
return {
|
|
105
246
|
importer,
|
|
247
|
+
importerScoped,
|
|
106
248
|
reload,
|
|
107
|
-
watch: (paths, watchOptions) => Stream.
|
|
108
|
-
|
|
109
|
-
|
|
249
|
+
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.debounce(watchOptions?.debounce ?? "50 millis"), Stream.mapEffect(() => Effect.map(Effect.exit(reload), (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({
|
|
250
|
+
reason: "restart-required",
|
|
251
|
+
modules: Array.fromIterable(modules)
|
|
110
252
|
})))))
|
|
111
253
|
};
|
|
112
254
|
});
|
|
113
|
-
export { HmrError, make };
|
|
255
|
+
export { HmrError, WatchResult, make };
|
|
114
256
|
|
|
115
257
|
//# 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 watchers and checks loaded module contents once\n\t * per second 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\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 = Ref.get(owners).pipe(\n\t\t\tEffect.flatMap((owners) =>\n\t\t\t\tEffect.forEach(\n\t\t\t\t\thooks.resolutions(\n\t\t\t\t\t\tArray.flatMap(HashMap.toValues(owners), (owner) =>\n\t\t\t\t\t\t\tArray.getSomes([owner.active, owner.pending]),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tchangedResolution,\n\t\t\t\t),\n\t\t\t),\n\t\t\tEffect.map((changes) =>\n\t\t\t\tArray.fromIterable(HashSet.fromIterable(Array.getSomes(changes))),\n\t\t\t),\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\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\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(Effect.ensuring(Effect.sync(hooks.discard))),\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}),\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 reload = serial(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* checkOpen\n\t\t\t\tconst changed = yield* changedResolutions\n\t\t\t\tif (changed.length > 0)\n\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\treason: \"restart-required\",\n\t\t\t\t\t\tmodules: changed,\n\t\t\t\t\t})\n\t\t\t\tconst { entries } = yield* Ref.get(state)\n\t\t\t\tconst unsupported = yield* unsupportedModules\n\t\t\t\tif (HashSet.size(unsupported) > 0)\n\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\tmodules: Array.fromIterable(unsupported),\n\t\t\t\t\t\treason: \"restart-required\",\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\tconst loaded = yield* Effect.forEach(entries, ([url, previous]) =>\n\t\t\t\t\t\tRef.update(\n\t\t\t\t\t\t\towners,\n\t\t\t\t\t\t\tHashMap.map((owner, token) =>\n\t\t\t\t\t\t\t\tHashSet.has(previous.leases, token)\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t...owner,\n\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...attempt,\n\t\t\t\t\t\t\t\t\t\t\t\turl,\n\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})),\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: owner,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tEffect.andThen(load(url)),\n\t\t\t\t\t\t\tEffect.map((entry) =>\n\t\t\t\t\t\t\t\tOption.some({\n\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\tprevious: previous.plugin,\n\t\t\t\t\t\t\t\t\tleases: previous.leases,\n\t\t\t\t\t\t\t\t\tplugin: entry.plugin,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\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.succeedNone,\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\tconst candidates = Array.getSomes(loaded)\n\t\t\t\t\tconst alternatives = Array.reduce(\n\t\t\t\t\t\tcandidates,\n\t\t\t\t\t\tHashMap.empty<Plugin.Plugin, HashSet.HashSet<Plugin.Plugin>>(),\n\t\t\t\t\t\t(groups, { previous, plugin }) =>\n\t\t\t\t\t\t\tHashMap.set(\n\t\t\t\t\t\t\t\tgroups,\n\t\t\t\t\t\t\t\tprevious,\n\t\t\t\t\t\t\t\tHashSet.add(\n\t\t\t\t\t\t\t\t\tOption.getOrElse(\n\t\t\t\t\t\t\t\t\t\tHashMap.get(groups, previous),\n\t\t\t\t\t\t\t\t\t\tHashSet.empty<Plugin.Plugin>,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tplugin,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\tconst ambiguous = Array.filter(candidates, ({ previous }) =>\n\t\t\t\t\t\tOption.exists(\n\t\t\t\t\t\t\tHashMap.get(alternatives, previous),\n\t\t\t\t\t\t\t(plugins) => HashSet.size(plugins) > 1,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\tif (ambiguous.length > 0)\n\t\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\t\treason: \"ambiguous-exports\",\n\t\t\t\t\t\t\tmodules: Array.map(ambiguous, ({ url }) => url),\n\t\t\t\t\t\t})\n\t\t\t\t\tconst next = HashMap.fromIterable(\n\t\t\t\t\t\tArray.map(\n\t\t\t\t\t\t\tcandidates,\n\t\t\t\t\t\t\t({ url, plugin, leases }) => [url, { plugin, leases }] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\tconst pairs = Array.map(\n\t\t\t\t\t\tcandidates,\n\t\t\t\t\t\t({ previous, plugin }) => [previous, plugin] as const,\n\t\t\t\t\t)\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 replacement = yield* Graft.replace(pairs).pipe(\n\t\t\t\t\t\tEffect.provideService(Graft.Graft, graft),\n\t\t\t\t\t\tEffect.result,\n\t\t\t\t\t)\n\t\t\t\t\tif (Result.isFailure(replacement))\n\t\t\t\t\t\treturn WatchResult.Skipped({\n\t\t\t\t\t\t\treason: \"identity-conflict\",\n\t\t\t\t\t\t\tmodules: Array.filterMap(candidates, ({ url }, index) =>\n\t\t\t\t\t\t\t\tArray.contains(replacement.failure.indices, index)\n\t\t\t\t\t\t\t\t\t? Result.succeed(url)\n\t\t\t\t\t\t\t\t\t: Result.fail(undefined),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t})\n\t\t\t\t\tyield* Ref.update(owners, (owners) =>\n\t\t\t\t\t\tArray.reduce(candidates, owners, (owners, { url, leases }) =>\n\t\t\t\t\t\t\tHashMap.map(owners, (owner, token) =>\n\t\t\t\t\t\t\t\tHashSet.has(leases, token)\n\t\t\t\t\t\t\t\t\t? {\n\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...attempt,\n\t\t\t\t\t\t\t\t\t\t\t\turl,\n\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})),\n\t\t\t\t\t\t\t\t\t\t\tpending: Option.none(),\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: owner,\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\tyield* Ref.set(state, { entries: next, generation: candidate })\n\t\t\t\t\treturn WatchResult.Reloaded({\n\t\t\t\t\t\tentries: Array.fromIterable(HashMap.keys(next)),\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): error is HmrError & { readonly cause: RestartRequired } =>\n\t\t\t\t\t\t\tSchema.is(RestartRequired)(error.cause),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tEffect.succeed(\n\t\t\t\t\t\t\t\tWatchResult.Skipped({\n\t\t\t\t\t\t\t\t\tmodules: Array.fromIterable(\n\t\t\t\t\t\t\t\t\t\tHashSet.add(hooks.unsupported(candidate), error.cause.url),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\treason: \"restart-required\",\n\t\t\t\t\t\t\t\t}),\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\timporterScoped,\n\t\t\treload,\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// Hold ownership through the reads, so release cannot leave\n\t\t\t\t\t\t\t\t\t// an in-flight snapshot reading a retired path.\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.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: 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":";;;;;;;;;;AAoCA,IAAa,WAAb,cAA8B,OAAO,YAGnC,CAAC,CAAC,YAAY;CACf,OAAO,OAAO,SAAS;EAAC;EAAU;EAAW;CAAQ,CAAC;CACtD,WAAW,OAAO;CAClB,OAAO,OAAO;AACf,CAAC,CAAC,CAAC;CACF,IAAa,UAAkB;EAC9B,OAAO,OAAO,KAAK,MAAM,cAAc,KAAK,aAAa;CAC1D;AACD;AAYA,MAAa,cAAc,KAAK,WAAwB;;;;;AAmDxD,MAAa,QACZ,YAEA,OAAO,IAAI,aAAa;CACvB,MAAM,QAAQ,OAAO,MAAM;CAC3B,MAAM,YAAY,OAAO,UAAU,KAAK,CAAC;CAEzC,MAAM,SAAS,OAAO,IAAI,KACzB,QAAQ,MAMN,CACH;CACA,MAAM,SAAS,OAAO,IAAI,KAAK,KAAK;CACpC,MAAM,UAAU,OAAO,OAAO;CAG9B,MAAM,gBAAyB,WAC9B,OAAO,IAAI,aAAa;EACvB,MAAM,YAAY,OAAO,SAAS,KAAsB;EACxD,OAAO,OAAO,OAAO,KACpB,OAAO,MACP,OAAO,KAAK,SAAS,SAAS,QAAQ,WAAW,IAAI,CAAC,GACtD,OAAO,iBACP,OAAO,SACP,OAAO,QAAQ,SACd,OAAO,IAAI,aAAa;GACvB,IAAI,KAAK,UAAU,IAAI,KAAK,CAAC,MAAM,cAAc,KAAK,KAAK,GAC1D;GACD,MAAM,QAAQ,OAAO,SAAS,KAAK,SAAS;GAC5C,IAAI,OAAO,OAAO,KAAK,GAAG;GAC1B,MAAM,WAAW,OAAO,MAAM;GAC9B,IAAI,KAAK,UAAU,QAAQ,GAC1B,OAAO,OAAO,SACb,4CACA,SAAS,KACV;EACF,CAAC,CACF,CACD;CACD,CAAC;CAEF,MAAM,UAAmB,WACxB,OAAO,KAAK,cAAc,UAAU,YAAY,CAAC,CAAC;CACnD,MAAM,QAAQ,OAAO,OAAO,eAC3B,OAAO,WACN,QACC,QAAQ,MACR,QAAQ,aACL,QACD,CAAC,OAAO,SAAS,gBAAgB,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,QAAQ,EAC3D,CACD,IACC,UACA,OACC,IAAI,IAAI,QAAQ,IAAI,CAAC,CAAC,KAAK,OAAO,QAAQ,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC,CACpE,CACF;CACA,MAAM,QAAQ,OAAO,IAAI,KAAK;EAC7B,YAAY,MAAM,QAAQ;EAC1B,SAAS,QAAQ,MAMf;CACH,CAAC;CAGD,MAAM,UAAU,OAAO,MAAM,KAAK,OAAO;CACzC,MAAM,MAAM,OAAO,SAAS,mBAA6C;CACzE,MAAM,oBACL,SAAS,KAAK;EACb,OAAO;EACP,WAAW;EACX,OAAO;CACR,CAAC;CACF,MAAM,YAAY,IAAI,IAAI,MAAM,CAAC,CAAC,KACjC,OAAO,SAAS,WACf,SAAS,OAAO,KAAK,YAAY,CAAC,IAAI,OAAO,IAC9C,CACD;CACA,MAAM,kBAAkB,YACvB,OAAO,WAAW;EACjB,WAAW,SAAS,cAAc,OAAO,CAAC;EAC1C,QAAQ,UACP,SAAS,KAAK;GAAE,OAAO;GAAU,WAAW;GAAS;EAAM,CAAC;CAC9D,CAAC,CAAC,CAAC,KACF,OAAO,KAAK,SAAS,cAAc,IAAI,CAAC,CAAC,IAAI,GAC7C,OAAO,SACL,UAAU,eAAe,SAAS,MAAM,KAAK,SACxC,OAAO,SACd,CACD;CACD,MAAM,oBAAoB,OAAO,WAAW,WAC3C,SACC;EACD,IAAI,QAAQ,YAAY,KAAA,GAAW,OAAO,OAAO,KAAa;EAC9D,MAAM,UAAU,QAAQ;EACxB,MAAM,WAAW,cAAc,cAAc,QAAQ,GAAG,CAAC,CAAC,CAAC;EAC3D,MAAM,SAAS,OAAO,eAAe,OAAO;EAC5C,IAAI,WAAW,UAAU,OAAO,OAAO,KAAa;EAGpD,IAAI,WAAW,KAAA,GAEV;QAAA,OADqB,eAAe,QAAQ,GAAG,OACjC,KAAA,GAAW,OAAO,OAAO,KAAa;EAAA;EAEzD,OAAO,OAAO,KAAK,OAAO;CAC3B,CAAC;CAED,MAAM,qBAAqB,IAAI,IAAI,MAAM,CAAC,CAAC,KAC1C,OAAO,SAAS,WACf,OAAO,QACN,MAAM,YACL,MAAM,QAAQ,QAAQ,SAAS,MAAM,IAAI,UACxC,MAAM,SAAS,CAAC,MAAM,QAAQ,MAAM,OAAO,CAAC,CAC7C,CACD,GACA,iBACD,CACD,GACA,OAAO,KAAK,YACX,MAAM,aAAa,QAAQ,aAAa,MAAM,SAAS,OAAO,CAAC,CAAC,CACjE,CACD;CACA,MAAM,OAAO,OAAO,WAAW,WAAW,WAAmB;EAC5D,MAAM,YAAY,OAAO,OAAO,WAAW;GAC1C,WAAW;;IAA0B,MAAM,QAAQ,SAAS;;GAC5D,QAAQ,UAAU,SAAS,KAAK;IAAE,OAAO;IAAU;IAAW;GAAM,CAAC;EACtE,CAAC;EACD,MAAM,SAAS,OAAO,WAAW,SAAS;EAC1C,IAAI,OAAO,OAAO,MAAM,GACvB,OAAO,OAAO,SAAS,KAAK;GAC3B,OAAO;GACP;GACA,OAAO;EACR,CAAC;EACF,MAAM,MAAM,OAAO,OAAO,WAAW,MAAM,SAAS,CAAC,CAAC,CAAC,KACtD,OAAO,UAAU,UAChB,SAAS,KAAK;GAAE,OAAO;GAAU;GAAW;EAAM,CAAC,CACpD,CACD;EACA,MAAM,UAAU,MAAM,YAAY,SAAS;EAC3C,MAAM,UAAU;GACf;GACA,GAAG,OAAO,MAAM,SAAS;IACxB,eAAe,CAAC;IAChB,SAAS,aAAa,EAAE,QAAQ;GACjC,CAAC;EACF;EACA,MAAM,UAAU,OAAO,kBAAkB,OAAO;EAChD,IAAI,OAAO,OAAO,OAAO,GACxB,OAAO,OAAO,SAAS,KAAK;GAC3B,OAAO;GACP;GACA,OAAO,gBAAgB,KAAK,EAAE,KAAK,QAAQ,MAAM,CAAC;EACnD,CAAC;EACF,OAAO;GAAE;GAAW,QAAQ,OAAO;GAAO;EAAI;CAC/C,CAAC;CACD,MAAM,mBAAmB,cACxB,OAAO,eACN,OACC,OAAO,IAAI,aAAa;EACvB,OAAO;EACP,MAAM,UAAU,OAAO,IAAI,IAAI,KAAK;EACpC,MAAM,MAAM,QAAQ,UAAU;EAG9B,MAAM,OAAO,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC;EAC/C,MAAM,QAAgB,OAAO;EAC7B,MAAM,OAAO,OAAO,OAAO,MAAM,SAAS,SACzC,OAAO,WAAW,OAAO,CAAC,CAAC,SAAS,IACjC,OAAO,KAAK,SAAS,IACrB,OAAO,KAAK,CAChB;EACA,MAAM,UAAU,OAAO,IAAI,OAAO,SAAS;GAC1C;GACA,YAAY,QAAQ;GACpB,GAAG,OAAO,MAAM,MAAM,YAAY,SAAS,GAAG;IAC7C,eAAe,CAAC;IAChB,SAAS,aAAa,EAAE,QAAQ;GACjC,CAAC;EACF,EAAE;EACF,OAAO,IAAI,OACV,QACA,QAAQ,IAAI,OAAO;GAClB,QAAQ,KAAK,UAAU,IAAI,IAAI,UAAU,OAAO,KAAK;GACrD,SAAS,KAAK,UAAU,IAAI,IAAI,UAAU,OAAO,KAAK;EACvD,CAAC,CACF;EACA,IAAI,KAAK,UAAU,IAAI,GAAG;GACzB,MAAM,QAAQ,KAAK;GACnB,MAAM,WAAW,QAAQ,IAAI,QAAQ,SAAS,MAAM,GAAG;GACvD,MAAM,SAAS,OAAO,MAAM,UAAU;IACrC,cAAc,QAAQ,MAAc;IACpC,SAAS,UAAU,MAAM;GAC1B,CAAC;GACD,OAAO,IAAI,IAAI,OAAO;IACrB,GAAG;IACH,SAAS,QAAQ,IAAI,QAAQ,SAAS,MAAM,KAAK;KAChD,QAAQ,MAAM;KACd,QAAQ,QAAQ,IAAI,QAAQ,KAAK;IAClC,CAAC;GACF,CAAC;EACF;EACA,OAAO;GAAE;GAAO,MAAM,KAAK,IAAI,OAAO,UAAU,MAAM,SAAS;EAAE;CAClE,CAAC,CAAC,CAAC,KAAK,OAAO,SAAS,OAAO,KAAK,MAAM,OAAO,CAAC,CAAC,CACpD,IACC,EAAE,YACF,OACC,OAAO,IAAI,aAAa;EACvB,OAAO,IAAI,OAAO,QAAQ,QAAQ,OAAO,KAAK,CAAC;EAC/C,OAAO,IAAI,OAAO,QAAQ,aAAa;GACtC,GAAG;GACH,SAAS,QAAQ,UAAU,QAAQ,UAAU,UAAU;IACtD,MAAM,SAAS,QAAQ,OAAO,MAAM,QAAQ,KAAK;IACjD,OAAO,QAAQ,KAAK,MAAM,MAAM,IAC7B,OAAO,KAAK,KAAA,CAAS,IACrB,OAAO,QAAQ;KAAE,GAAG;KAAO;IAAO,CAAC;GACvC,CAAC;EACF,EAAE;CACH,CAAC,CACF,CACF,CAAC,CAAC,KACD,OAAO,SAAS,EAAE,WAAW,IAAI,GACjC,YACD;CAED,MAAM,kBAAyC,WAAW,YACzD,YAAY,KAAA,IACT,gBAAgB,SAAS,IACzB,gBAAgB,SAAS,CAAC,CAAC,KAC3B,MAAM,QAAQ,QAAQ,gBAAgB,CACvC;CAEH,MAAM,YAAY,cAEjB,IAAI,UAAU,MAAM,IACjB,QAAQ,OAAO,YAAY,CAAC,IAC5B,IACA,eAAe,SAAS,CAAC,CAAC,KACzB,OAAO,eAAe,MAAM,OAAO,OAAO,CAC3C,CACD;CAEH,MAAM,qBAAqB,IAAI,IAAI,KAAK,CAAC,CAAC,KACzC,OAAO,KAAK,EAAE,YAAY,cACzB,MAAM,YACL,YACA,MAAM,aAAa,QAAQ,KAAK,OAAO,CAAC,CACzC,CACD,CACD;CAEA,MAAM,SAAS,OACd,OAAO,IAAI,aAAa;EACvB,OAAO;EACP,MAAM,UAAU,OAAO;EACvB,IAAI,QAAQ,SAAS,GACpB,OAAO,YAAY,QAAQ;GAC1B,QAAQ;GACR,SAAS;EACV,CAAC;EACF,MAAM,EAAE,YAAY,OAAO,IAAI,IAAI,KAAK;EACxC,MAAM,cAAc,OAAO;EAC3B,IAAI,QAAQ,KAAK,WAAW,IAAI,GAC/B,OAAO,YAAY,QAAQ;GAC1B,SAAS,MAAM,aAAa,WAAW;GACvC,QAAQ;EACT,CAAC;EAEF,MAAM,YAAY,MAAM,QAAQ;EAChC,MAAM,MAAM,WAAW,IAAI;EAC3B,OAAO,OAAO,OAAO,IAAI,aAAa;GACrC,MAAM,SAAS,OAAO,OAAO,QAAQ,UAAU,CAAC,KAAK,cACpD,IAAI,OACH,QACA,QAAQ,KAAK,OAAO,UACnB,QAAQ,IAAI,SAAS,QAAQ,KAAK,IAC/B;IACA,GAAG;IACH,SAAS,OAAO,IAAI,MAAM,SAAS,aAAa;KAC/C,GAAG;KACH;KACA,YAAY;IACb,EAAE;GACH,IACC,KACJ,CACD,CAAC,CAAC,KACD,OAAO,QAAQ,KAAK,GAAG,CAAC,GACxB,OAAO,KAAK,UACX,OAAO,KAAK;IACX;IACA,UAAU,SAAS;IACnB,QAAQ,SAAS;IACjB,QAAQ,MAAM;GACf,CAAC,CACF,GACA,OAAO,SACL,UACA,MAAM,UAAU,YAAY,eAAe,KAAK,MAAM,KAAK,SACtD,OAAO,WACd,CACD,CACD;GACA,MAAM,aAAa,MAAM,SAAS,MAAM;GACxC,MAAM,eAAe,MAAM,OAC1B,YACA,QAAQ,MAAqD,IAC5D,QAAQ,EAAE,UAAU,aACpB,QAAQ,IACP,QACA,UACA,QAAQ,IACP,OAAO,UACN,QAAQ,IAAI,QAAQ,QAAQ,GAC5B,QAAQ,KACT,GACA,MACD,CACD,CACF;GACA,MAAM,YAAY,MAAM,OAAO,aAAa,EAAE,eAC7C,OAAO,OACN,QAAQ,IAAI,cAAc,QAAQ,IACjC,YAAY,QAAQ,KAAK,OAAO,IAAI,CACtC,CACD;GACA,IAAI,UAAU,SAAS,GACtB,OAAO,YAAY,QAAQ;IAC1B,QAAQ;IACR,SAAS,MAAM,IAAI,YAAY,EAAE,UAAU,GAAG;GAC/C,CAAC;GACF,MAAM,OAAO,QAAQ,aACpB,MAAM,IACL,aACC,EAAE,KAAK,QAAQ,aAAa,CAAC,KAAK;IAAE;IAAQ;GAAO,CAAC,CACtD,CACD;GACA,MAAM,QAAQ,MAAM,IACnB,aACC,EAAE,UAAU,aAAa,CAAC,UAAU,MAAM,CAC5C;GACA,MAAM,UAAU,OAAO;GACvB,IAAI,QAAQ,SAAS,GACpB,OAAO,YAAY,QAAQ;IAC1B,QAAQ;IACR,SAAS;GACV,CAAC;GACF,MAAM,cAAc,OAAO,MAAM,QAAQ,KAAK,CAAC,CAAC,KAC/C,OAAO,eAAe,MAAM,OAAO,KAAK,GACxC,OAAO,MACR;GACA,IAAI,OAAO,UAAU,WAAW,GAC/B,OAAO,YAAY,QAAQ;IAC1B,QAAQ;IACR,SAAS,MAAM,UAAU,aAAa,EAAE,OAAO,UAC9C,MAAM,SAAS,YAAY,QAAQ,SAAS,KAAK,IAC9C,OAAO,QAAQ,GAAG,IAClB,OAAO,KAAK,KAAA,CAAS,CACzB;GACD,CAAC;GACF,OAAO,IAAI,OAAO,SAAS,WAC1B,MAAM,OAAO,YAAY,SAAS,QAAQ,EAAE,KAAK,aAChD,QAAQ,IAAI,SAAS,OAAO,UAC3B,QAAQ,IAAI,QAAQ,KAAK,IACtB;IACA,QAAQ,OAAO,IAAI,MAAM,UAAU,aAAa;KAC/C,GAAG;KACH;KACA,YAAY;IACb,EAAE;IACF,SAAS,OAAO,KAAK;GACtB,IACC,KACJ,CACD,CACD;GACA,OAAO,IAAI,IAAI,OAAO;IAAE,SAAS;IAAM,YAAY;GAAU,CAAC;GAC9D,OAAO,YAAY,SAAS,EAC3B,SAAS,MAAM,aAAa,QAAQ,KAAK,IAAI,CAAC,EAC/C,CAAC;EACF,CAAC,CAAC,CAAC,KACF,OAAO,SACL,UACA,OAAO,GAAG,eAAe,CAAC,CAAC,MAAM,KAAK,IACtC,UACA,OAAO,QACN,YAAY,QAAQ;GACnB,SAAS,MAAM,aACd,QAAQ,IAAI,MAAM,YAAY,SAAS,GAAG,MAAM,MAAM,GAAG,CAC1D;GACA,QAAQ;EACT,CAAC,CACF,CACF,GACA,OAAO,SAAS,OAAO,KAAK,MAAM,OAAO,CAAC,CAC3C;CACD,CAAC,CACF;CACA,OAAO;EACN;EACA;EACA;EACA,QAAQ,OAAO,iBACd,MAAM,WAAW,IACd,OAAO,QACP,SACC,QACA,IAAI,IAAI,MAAM,CAAC,CAAC,KACf,OAAO,KAAK,WACX,MAAM,MAAM,CACX,GAAG,MAAM,SACR,MAAM,IACL,QAAQ,SAAS,MAAM,IACtB,UAAU,MAAM,MAClB,CACD,GACA,GAAG,MAAM,SACR,MAAM,IACL,QAAQ,SAAS,MAAM,IACtB,UAAU,MAAM,OAClB,CACD,CACD,CAAC,CACF,GACA,OAAO,QAAQ,GAAG,GAGlB,UAAU,YAAY,CAAC,CACxB,GACD,OACA,YACD,CAAC,CAAC,KACD,OAAO,SAAS,cAAc,YAAY,WAAW,GACrD,OAAO,gBACN,OAAO,IAAI,OAAO,KAAK,MAAM,IAAI,SAChC,KAAK,UAAU,IAAI,IAChB,KAAK,QACL,YAAY,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,CAC5C,CACD,GAGA,OAAO,MACN,OAAO,KAAK,UAAU,CAAC,CAAC,KACvB,OAAO,gBACN,mBAAmB,KAAK,UAAU,YAAY,CAAC,CAAC,CACjD,GACA,OAAO,SACP,OAAO,QAAQ,YAAY,QAAQ,KAAK,OAAO,IAAI,CAAC,GACpD,OAAO,KAAK,YACX,YAAY,QAAQ;GACnB,QAAQ;GACR,SAAS,MAAM,aAAa,OAAO;EACpC,CAAC,CACF,CACD,CACD,CACD;CACJ;AACD,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
|
+
import { Array, Cause, Effect, Exit, FileSystem, HashMap, Option, Queue, Ref, Stream, String } from "effect";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { Base64 } from "effect/encoding";
|
|
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
|
+
/** Reconcile loaded modules because native watchers can silently miss events. */
|
|
9
|
+
const changes = (withFiles, paths, options) => Stream.unwrap(Effect.gen(function* () {
|
|
10
|
+
if (paths.length === 0) return Stream.empty;
|
|
11
|
+
const fs = yield* FileSystem.FileSystem;
|
|
12
|
+
const roots = yield* Effect.forEach(paths, (path) => Effect.gen(function* () {
|
|
13
|
+
const canonical = yield* fs.realPath(path);
|
|
14
|
+
const info = yield* fs.stat(canonical);
|
|
15
|
+
return {
|
|
16
|
+
path: canonical,
|
|
17
|
+
urls: [pathToFileURL(canonical).href, pathToFileURL(resolve(path)).href],
|
|
18
|
+
directory: info.type === "Directory"
|
|
19
|
+
};
|
|
20
|
+
}));
|
|
21
|
+
const accepted = (path) => options?.filter?.(path) ?? true;
|
|
22
|
+
const watched = (url) => accepted(fileURLToPath(url)) && Array.some(roots, ({ urls }) => Array.some(urls, (root) => {
|
|
23
|
+
const directory = String.endsWith("/")(root) ? root : `${root}/`;
|
|
24
|
+
return fileURLToPath(url) === fileURLToPath(root) || (options?.recursive === true ? String.startsWith(directory)(url) : new URL(".", url).href === directory);
|
|
25
|
+
}));
|
|
26
|
+
const snapshot = withFiles((loaded) => Effect.forEach(Array.filter(Array.fromIterable(loaded), ([url]) => watched(url)), ([url, baseline]) => Effect.gen(function* () {
|
|
27
|
+
const resolution = baseline.resolution === void 0 ? void 0 : yield* fs.realPath(fileURLToPath(url)).pipe(Effect.map((path) => pathToFileURL(path).href), Effect.catchIf((error) => error.reason._tag === "NotFound", () => Effect.undefined));
|
|
28
|
+
return {
|
|
29
|
+
url,
|
|
30
|
+
baseline,
|
|
31
|
+
content: yield* fs.readFile(fileURLToPath(url)).pipe(Effect.map(Base64.encode), Effect.catchIf((error) => error.reason._tag === "NotFound", () => Effect.undefined)),
|
|
32
|
+
resolution
|
|
33
|
+
};
|
|
34
|
+
})));
|
|
35
|
+
const observed = yield* Ref.make(HashMap.empty());
|
|
36
|
+
return Stream.mergeAll([...Array.map(roots, (root) => fs.watch(root.path, options).pipe(observeSource, Stream.filter((event) => accepted(root.directory ? resolve(root.path, event.path) : root.path)), Stream.map(() => true))), Stream.tick("1 second").pipe(Stream.drop(1), Stream.map(() => false))], { concurrency: "unbounded" }).pipe(Stream.mapEffect((event) => snapshot.pipe(Effect.flatMap((current) => Ref.modify(observed, (before) => [event || Array.some(current, ({ url, baseline, content, resolution }) => {
|
|
37
|
+
const expected = Option.match(HashMap.get(before, url), {
|
|
38
|
+
onNone: () => ({
|
|
39
|
+
content: baseline.content,
|
|
40
|
+
resolution: baseline.resolution
|
|
41
|
+
}),
|
|
42
|
+
onSome: (seen) => {
|
|
43
|
+
const same = seen.baseline.generation === baseline.generation && seen.baseline.content === baseline.content && seen.baseline.resolution === baseline.resolution;
|
|
44
|
+
return {
|
|
45
|
+
content: baseline.content === void 0 || same ? seen.content : baseline.content,
|
|
46
|
+
resolution: same ? seen.resolution : baseline.resolution
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return expected.content !== content || expected.resolution !== resolution;
|
|
51
|
+
}), HashMap.fromIterable(Array.map(current, ({ url, baseline, content, resolution }) => [url, {
|
|
52
|
+
baseline,
|
|
53
|
+
content,
|
|
54
|
+
resolution
|
|
55
|
+
}]))])))), Stream.filter(Boolean), Stream.map(() => void 0));
|
|
56
|
+
}));
|
|
57
|
+
export { changes };
|
|
58
|
+
|
|
59
|
+
//# 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 { resolve } from \"node:path\"\nimport type { FileVersion } from \"./modules\"\nimport type { WatchOptions } from \"../index\"\nimport { Base64 } from \"effect/encoding\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport {\n\tArray,\n\tCause,\n\tExit,\n\tEffect,\n\tFileSystem,\n\tHashMap,\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/** Reconcile loaded 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 }) =>\n\t\t\t\t\tArray.some(urls, (root) => {\n\t\t\t\t\t\tconst directory = 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(options?.recursive === true\n\t\t\t\t\t\t\t\t? String.startsWith(directory)(url)\n\t\t\t\t\t\t\t\t: new URL(\".\", url).href === directory)\n\t\t\t\t\t\t)\n\t\t\t\t\t}),\n\t\t\t\t)\n\n\t\t\tconst snapshot = withFiles((loaded) =>\n\t\t\t\tEffect.forEach(\n\t\t\t\t\tArray.filter(Array.fromIterable(loaded), ([url]) => watched(url)),\n\t\t\t\t\t([url, baseline]) =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tconst resolution =\n\t\t\t\t\t\t\t\tbaseline.resolution === undefined\n\t\t\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t\t\t: yield* fs.realPath(fileURLToPath(url)).pipe(\n\t\t\t\t\t\t\t\t\t\t\tEffect.map((path) => pathToFileURL(path).href),\n\t\t\t\t\t\t\t\t\t\t\tEffect.catchIf(\n\t\t\t\t\t\t\t\t\t\t\t\t(error) => error.reason._tag === \"NotFound\",\n\t\t\t\t\t\t\t\t\t\t\t\t() => Effect.undefined,\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\tconst content = yield* fs.readFile(fileURLToPath(url)).pipe(\n\t\t\t\t\t\t\t\tEffect.map(Base64.encode),\n\t\t\t\t\t\t\t\t// Deletion is a change; other I/O failures belong to the consumer.\n\t\t\t\t\t\t\t\tEffect.catchIf(\n\t\t\t\t\t\t\t\t\t(error) => error.reason._tag === \"NotFound\",\n\t\t\t\t\t\t\t\t\t() => Effect.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\treturn { url, baseline, content, resolution }\n\t\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t)\n\t\t\t// An observation acknowledges disk bytes only for the import version\n\t\t\t// it reconciled. A new attempt must be checked against its own bytes.\n\t\t\tconst observed = yield* Ref.make(\n\t\t\t\tHashMap.empty<\n\t\t\t\t\tstring,\n\t\t\t\t\t{\n\t\t\t\t\t\treadonly baseline: FileVersion\n\t\t\t\t\t\treadonly content: string | undefined\n\t\t\t\t\t\treadonly resolution: string | undefined\n\t\t\t\t\t}\n\t\t\t\t>(),\n\t\t\t)\n\t\t\treturn Stream.mergeAll(\n\t\t\t\t[\n\t\t\t\t\t...Array.map(roots, (root) =>\n\t\t\t\t\t\tfs.watch(root.path, options).pipe(\n\t\t\t\t\t\t\tobserveSource,\n\t\t\t\t\t\t\tStream.filter((event) =>\n\t\t\t\t\t\t\t\taccepted(\n\t\t\t\t\t\t\t\t\troot.directory ? resolve(root.path, event.path) : root.path,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tStream.map(() => true),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tStream.tick(\"1 second\").pipe(\n\t\t\t\t\t\tStream.drop(1),\n\t\t\t\t\t\tStream.map(() => false),\n\t\t\t\t\t),\n\t\t\t\t],\n\t\t\t\t{ concurrency: \"unbounded\" },\n\t\t\t).pipe(\n\t\t\t\t// One consumer serializes snapshots from events and polling. A native\n\t\t\t\t// event also advances the baseline, avoiding a second reload on the next tick.\n\t\t\t\tStream.mapEffect((event) =>\n\t\t\t\t\tsnapshot.pipe(\n\t\t\t\t\t\tEffect.flatMap((current) =>\n\t\t\t\t\t\t\tRef.modify(observed, (before) => [\n\t\t\t\t\t\t\t\tevent ||\n\t\t\t\t\t\t\t\t\tArray.some(\n\t\t\t\t\t\t\t\t\t\tcurrent,\n\t\t\t\t\t\t\t\t\t\t({ url, baseline, content, resolution }) => {\n\t\t\t\t\t\t\t\t\t\t\tconst expected = Option.match(HashMap.get(before, url), {\n\t\t\t\t\t\t\t\t\t\t\t\tonNone: () => ({\n\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: baseline.content,\n\t\t\t\t\t\t\t\t\t\t\t\t\tresolution: baseline.resolution,\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\tonSome: (seen) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst same =\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tseen.baseline.generation === baseline.generation &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tseen.baseline.content === baseline.content &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tseen.baseline.resolution === baseline.resolution\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Failed attempts without source bytes keep the last\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// observation acknowledged until another save.\n\t\t\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\t\t\tbaseline.content === undefined || same\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? seen.content\n\t\t\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\t\t\tresolution: same\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? seen.resolution\n\t\t\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\t\t}\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\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\texpected.content !== content ||\n\t\t\t\t\t\t\t\t\t\t\t\texpected.resolution !== resolution\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\tHashMap.fromIterable(\n\t\t\t\t\t\t\t\t\tArray.map(\n\t\t\t\t\t\t\t\t\t\tcurrent,\n\t\t\t\t\t\t\t\t\t\t({ url, baseline, content, resolution }) =>\n\t\t\t\t\t\t\t\t\t\t\t[url, { baseline, content, resolution }] as const,\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\t\t\t),\n\t\t\t\t),\n\t\t\t\tStream.filter(Boolean),\n\t\t\t\tStream.map(() => undefined),\n\t\t\t)\n\t\t}),\n\t)\n"],"mappings":"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"}
|