Comparing version 1.2.0 to 1.2.1
@@ -10,2 +10,19 @@ export * as default from "./functional.js"; | ||
export declare function join(iterable: Iterable<string>, separator?: string): string; | ||
/** | ||
* The pipeline operator is stuck in specification hell so this works as a replacement to unfold | ||
* sequential operations. | ||
* https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md | ||
*/ | ||
export declare function pipe<T0, T1>(vv: T0, fn0: (vv: T0) => T1): T1; | ||
export declare function pipe<T0, T1, T2>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2): T2; | ||
export declare function pipe<T0, T1, T2, T3>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3): T3; | ||
export declare function pipe<T0, T1, T2, T3, T4>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4): T4; | ||
export declare function pipe<T0, T1, T2, T3, T4, T5>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4, fn4: (vv: T4) => T5): T5; | ||
export declare function pipe<T0, T1, T2, T3, T4, T5, T6>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4, fn4: (vv: T4) => T5, fn5: (vv: T5) => T6): T6; | ||
export declare function pipe<T0, T1, T2, T3, T4, T5, T6, T7>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4, fn4: (vv: T4) => T5, fn5: (vv: T5) => T6, fn6: (vv: T6) => T7): T7; | ||
export declare function pipe<T0, T1, T2, T3, T4, T5, T6, T7, T8>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4, fn4: (vv: T4) => T5, fn5: (vv: T5) => T6, fn6: (vv: T6) => T7, fn7: (vv: T7) => T8): T8; | ||
export declare function pipe<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4, fn4: (vv: T4) => T5, fn5: (vv: T5) => T6, fn6: (vv: T6) => T7, fn7: (vv: T7) => T8, fn8: (vv: T8) => T9): T9; | ||
export declare function pipe<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4, fn4: (vv: T4) => T5, fn5: (vv: T5) => T6, fn6: (vv: T6) => T7, fn7: (vv: T7) => T8, fn8: (vv: T8) => T9, fn9: (vv: T9) => T10): T10; | ||
export declare function pipe<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4, fn4: (vv: T4) => T5, fn5: (vv: T5) => T6, fn6: (vv: T6) => T7, fn7: (vv: T7) => T8, fn8: (vv: T8) => T9, fn9: (vv: T9) => T10, fn10: (vv: T10) => T11): T11; | ||
export declare function pipe<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(vv: T0, fn0: (vv: T0) => T1, fn1: (vv: T1) => T2, fn2: (vv: T2) => T3, fn3: (vv: T3) => T4, fn4: (vv: T4) => T5, fn5: (vv: T5) => T6, fn6: (vv: T6) => T7, fn7: (vv: T7) => T8, fn8: (vv: T8) => T9, fn9: (vv: T9) => T10, fn10: (vv: T10) => T11, fn11: (vv: T11) => T12): T12; | ||
//# sourceMappingURL=functional.d.ts.map |
@@ -61,2 +61,5 @@ export * as default from "./functional.js"; | ||
} | ||
export function pipe(vv, ...fns) { | ||
return fns.reduce((vv, fn) => fn(vv), vv); | ||
} | ||
/** | ||
@@ -63,0 +66,0 @@ * Eagerly iterates the given iterable, invoking the reducer for each element in the iterable. |
@@ -109,2 +109,3 @@ import * as assert from "node:assert/strict"; | ||
parentURL: parentModuleURL, | ||
// @ts-expect-error | ||
importAssertions, | ||
@@ -135,2 +136,3 @@ }); | ||
parentURL: parentModuleURL, | ||
// @ts-expect-error | ||
importAssertions, | ||
@@ -178,3 +180,15 @@ }); | ||
// This import graph has bailed from the "hot:" scheme and is just forwarded to the host. | ||
return nextResolve(specifier, context); | ||
const parentURL = function () { | ||
if (context.parentURL.startsWith("hot:")) { | ||
const parentURL = new URL(context.parentURL); | ||
return parentURL.searchParams.get("url"); | ||
} | ||
else { | ||
return context.parentURL; | ||
} | ||
}(); | ||
return nextResolve(specifier, { | ||
...context, | ||
...parentURL !== null && { parentURL }, | ||
}); | ||
}; | ||
@@ -214,2 +228,3 @@ /** @internal */ | ||
// TODO [marcel 2024-04-27]: remove after nodejs v18(?) is not supported | ||
// @ts-expect-error | ||
importAssertions: importAttributes, | ||
@@ -216,0 +231,0 @@ importAttributes, |
@@ -10,9 +10,6 @@ import Fn from "dynohot/functional"; | ||
export class AdapterModuleController { | ||
url; | ||
namespace; | ||
resolutions; | ||
reloadable = false; | ||
state = { status: ModuleStatus.evaluated }; | ||
constructor(url, namespace) { | ||
this.url = url; | ||
this.reloadable = false; | ||
this.state = { status: ModuleStatus.evaluated }; | ||
// Memoize resolved exports to maintain equality of the resolution. Note that this will fail | ||
@@ -19,0 +16,0 @@ // under some corner cases like: |
@@ -0,1 +1,53 @@ | ||
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
if (value !== null && value !== void 0) { | ||
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); | ||
var dispose, inner; | ||
if (async) { | ||
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); | ||
dispose = value[Symbol.asyncDispose]; | ||
} | ||
if (dispose === void 0) { | ||
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); | ||
dispose = value[Symbol.dispose]; | ||
if (async) inner = dispose; | ||
} | ||
if (typeof dispose !== "function") throw new TypeError("Object not disposable."); | ||
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; | ||
env.stack.push({ value: value, dispose: dispose, async: async }); | ||
} | ||
else if (async) { | ||
env.stack.push({ async: true }); | ||
} | ||
return value; | ||
}; | ||
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { | ||
return function (env) { | ||
function fail(e) { | ||
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; | ||
env.hasError = true; | ||
} | ||
var r, s = 0; | ||
function next() { | ||
while (r = env.stack.pop()) { | ||
try { | ||
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); | ||
if (r.dispose) { | ||
var result = r.dispose.call(r.value); | ||
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); | ||
} | ||
else s |= 1; | ||
} | ||
catch (e) { | ||
fail(e); | ||
} | ||
} | ||
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); | ||
if (env.hasError) throw env.error; | ||
} | ||
return next(); | ||
}; | ||
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { | ||
var e = new Error(message); | ||
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; | ||
}); | ||
import * as assert from "node:assert/strict"; | ||
@@ -132,22 +184,9 @@ import { EventEmitter } from "node:events"; | ||
export class ReloadableModuleController { | ||
application; | ||
url; | ||
reloadable = true; | ||
/** The currently "active" module instance */ | ||
current; | ||
/** During an update, but before this module is evaluated, this is what `current` will become */ | ||
pending; | ||
/** Durning an update, this is what `current` was and/or is */ | ||
previous; | ||
/** Written to at *any* time in response to the file watcher */ | ||
staging; | ||
/** Temporary, maybe "unlinked" instances used for link testing */ | ||
temporary; | ||
fatalError; | ||
traversal = makeTraversalState(); | ||
visitIndex = 0; | ||
version = 0; | ||
constructor(application, url) { | ||
this.application = application; | ||
this.url = url; | ||
this.reloadable = true; | ||
this.traversal = makeTraversalState(); | ||
this.visitIndex = 0; | ||
this.version = 0; | ||
const watcher = new FileWatcher(); | ||
@@ -305,8 +344,9 @@ watcher.watch(this.url, () => { | ||
*traverse(iterate = node => node.iterateWithDynamics()) { | ||
const [release, visitIndex] = acquireVisitIndex(); | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const visitIndex = __addDisposableResource(env_1, acquireVisitIndex(), false); | ||
yield* function* traverse(node) { | ||
for (const child of iterate(node)) { | ||
if (child.visitIndex !== visitIndex) { | ||
child.visitIndex = visitIndex; | ||
if (child.visitIndex !== visitIndex.index) { | ||
child.visitIndex = visitIndex.index; | ||
yield child; | ||
@@ -318,4 +358,8 @@ yield* traverse(child); | ||
} | ||
catch (e_1) { | ||
env_1.error = e_1; | ||
env_1.hasError = true; | ||
} | ||
finally { | ||
release(); | ||
__disposeResources(env_1); | ||
} | ||
@@ -350,3 +394,3 @@ } | ||
const nodeHasNewCode = node.previous !== node.pending; | ||
hasNewCode ||= nodeHasNewCode; | ||
hasNewCode || (hasNewCode = nodeHasNewCode); | ||
if (nodeHasNewCode || | ||
@@ -362,4 +406,4 @@ node.current === undefined || | ||
const hasDecline = declined.length > 0 || Fn.some(forwardResults, result => result.hasDecline); | ||
needsDispatch ||= Fn.some(forwardResults, result => result.needsDispatch); | ||
hasNewCode ||= Fn.some(forwardResults, result => result.hasNewCode); | ||
needsDispatch || (needsDispatch = Fn.some(forwardResults, result => result.needsDispatch)); | ||
hasNewCode || (hasNewCode = Fn.some(forwardResults, result => result.hasNewCode)); | ||
return { forwardResults, declined, invalidated, hasDecline, hasNewCode, needsDispatch }; | ||
@@ -373,6 +417,9 @@ }, pendingNodes => { | ||
// Rollback routine which undoes the above traversal. | ||
const rollback = () => { | ||
const rollback = (resetStaging = false) => { | ||
for (const controller of nextControllers) { | ||
controller.pending = undefined; | ||
controller.previous = undefined; | ||
if (resetStaging) { | ||
controller.staging = undefined; | ||
} | ||
} | ||
@@ -386,6 +433,9 @@ }; | ||
else if (initialResult.hasDecline) { | ||
rollback(); | ||
rollback(true); | ||
const declined = Array.from(function* traverse(result) { | ||
yield* result.declined; | ||
yield* Fn.transform(result.forwardResults, traverse); | ||
// Don't re-traverse shared dependencies | ||
if (result.hasDecline) { | ||
yield* result.declined; | ||
yield* Fn.transform(result.forwardResults, traverse); | ||
} | ||
}(initialResult)); | ||
@@ -395,52 +445,49 @@ return { type: UpdateStatus.declined, declined }; | ||
else if (initialResult.invalidated.length > 0) { | ||
// Tracing invalidations is actually pretty cumbersome because we need to compare cyclic | ||
// groups with the actual import entries to get something parsable by a human. | ||
const [release, visitIndex] = acquireVisitIndex(); | ||
const env_2 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
rollback(true); | ||
// Tracing invalidations is actually pretty cumbersome because we need to compare cyclic | ||
// groups with the actual import entries to get something parsable by a human. | ||
const visitIndex = __addDisposableResource(env_2, acquireVisitIndex(), false); | ||
const chain = Array.from(function* traverse(result) { | ||
// Don't re-traverse shared dependencies | ||
for (const node of result.invalidated) { | ||
if (node.visitIndex === visitIndex) { | ||
yield { | ||
modules: result.invalidated, | ||
next: null, | ||
}; | ||
return; | ||
if (Fn.some(result.invalidated, node => node.visitIndex === visitIndex.index)) { | ||
yield { | ||
modules: result.invalidated, | ||
next: null, | ||
}; | ||
return; | ||
} | ||
else { | ||
for (const node of result.invalidated) { | ||
node.visitIndex = visitIndex.index; | ||
} | ||
} | ||
const hasDependency = Fn.pipe(result.invalidated, $$ => Fn.transform($$, controller => controller.select().iterateDependencies()), $$ => new Set($$), $$ => (controller) => $$.has(controller)); | ||
const next = Fn.pipe(result.forwardResults, $$ => Fn.filter($$, result => Fn.some(result.invalidated, hasDependency)), $$ => Fn.transform($$, traverse), $$ => Array.from($$), $$ => { | ||
if ($$.length === 0) { | ||
return undefined; | ||
} | ||
else if ($$.every(result => result.next === null)) { | ||
// Also, skip branches that only include truncated results from the | ||
// "re-traverse" conditional above. | ||
return null; | ||
} | ||
else { | ||
node.visitIndex = visitIndex; | ||
return $$; | ||
} | ||
} | ||
const importedModules = new Set(Fn.transform(result.invalidated, function* (controller) { | ||
const current = controller.select(); | ||
yield* current.iterateDependencies(); | ||
})); | ||
const importedInvalidations = Array.from(Fn.transform(result.forwardResults, function* (result) { | ||
const invalidatedImports = result.invalidated.filter(controller => importedModules.has(controller)); | ||
if (invalidatedImports.length > 0) { | ||
yield* traverse(result); | ||
} | ||
})); | ||
}); | ||
yield { | ||
modules: result.invalidated, | ||
next: function () { | ||
if (importedInvalidations.length === 0) { | ||
return undefined; | ||
} | ||
else if (importedInvalidations.every(result => result.next === null)) { | ||
// Also, skip branches that only include truncated results from the | ||
// "re-traverse" conditional above. | ||
return null; | ||
} | ||
else { | ||
return importedInvalidations; | ||
} | ||
}(), | ||
next, | ||
}; | ||
}(initialResult)); | ||
rollback(); | ||
return { type: UpdateStatus.unaccepted, chain }; | ||
} | ||
catch (e_2) { | ||
env_2.error = e_2; | ||
env_2.hasError = true; | ||
} | ||
finally { | ||
release(); | ||
__disposeResources(env_2); | ||
} | ||
@@ -447,0 +494,0 @@ } |
@@ -0,1 +1,13 @@ | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _a, _Hot_accepts, _Hot_acceptsSelf, _Hot_destructors, _Hot_dynamicImports, _Hot_instance, _Hot_module, _Hot_usesDynamicImport, _Hot_declined, _Hot_invalidated; | ||
import * as assert from "node:assert/strict"; | ||
@@ -35,151 +47,19 @@ import { EOL } from "node:os"; | ||
export class Hot { | ||
/** | ||
* This is the `data` object passed to the `dispose` handler of the previous `Hot` instance. | ||
* You can use this to stash references like an HTTP server or database connection for the | ||
* next instance of your module. | ||
*/ | ||
data; | ||
#accepts = []; | ||
#acceptsSelf = []; | ||
// dispose & prune | ||
#destructors = []; | ||
#dynamicImports = new Set(); | ||
#instance; | ||
#module; | ||
#usesDynamicImport; | ||
#declined = false; | ||
#invalidated = false; | ||
constructor(module, instance, usesDynamicImport, data) { | ||
this.#module = module; | ||
this.#instance = instance; | ||
this.#usesDynamicImport = usesDynamicImport; | ||
_Hot_accepts.set(this, []); | ||
_Hot_acceptsSelf.set(this, []); | ||
// dispose & prune | ||
_Hot_destructors.set(this, []); | ||
_Hot_dynamicImports.set(this, new Set()); | ||
_Hot_instance.set(this, void 0); | ||
_Hot_module.set(this, void 0); | ||
_Hot_usesDynamicImport.set(this, void 0); | ||
_Hot_declined.set(this, false); | ||
_Hot_invalidated.set(this, false); | ||
__classPrivateFieldSet(this, _Hot_module, module, "f"); | ||
__classPrivateFieldSet(this, _Hot_instance, instance, "f"); | ||
__classPrivateFieldSet(this, _Hot_usesDynamicImport, usesDynamicImport, "f"); | ||
this.data = data; | ||
Object.freeze(this); | ||
} | ||
static { | ||
didDynamicImport = (instance, controller) => { | ||
const hot = selectHot(instance); | ||
if (hot !== null) { | ||
hot.#dynamicImports.add(controller); | ||
} | ||
}; | ||
dispose = async (instance) => { | ||
const data = {}; | ||
const hot = selectHot(instance); | ||
if (hot !== null) { | ||
for (const callback of Fn.reverse(hot.#destructors)) { | ||
await callback(data); | ||
} | ||
} | ||
return data; | ||
}; | ||
isAccepted = (instance, modules) => { | ||
const hot = selectHot(instance); | ||
if (hot !== null && hot.#invalidated) { | ||
return false; | ||
} | ||
else { | ||
const imports = new Set(instance.iterateDependencies()); | ||
const acceptedModules = new Set(Fn.transform(hot === null ? [] : hot.#accepts, accepts => { | ||
const acceptedModules = accepts.localEntries.map(entry => entry.found ? entry.module : instance.lookupSpecifier(entry.specifier)); | ||
if (acceptedModules.every(module => module != null)) { | ||
return acceptedModules; | ||
} | ||
else { | ||
if (hot !== null) { | ||
const specifiers = Array.from(Fn.transform(acceptedModules.entries(), function* ([ii, module]) { | ||
if (module == null) { | ||
yield accepts.localEntries[ii].specifier; | ||
} | ||
})); | ||
hot.#module.application.log(`Warning: ${plural("specifier", specifiers.length)} ${specifiers.map(() => "%s").join(", ")} from %s could not be resolved.`, ...specifiers.map(specifier => JSON.stringify(specifier)), makeRelative(hot.#module.url)); | ||
if (accepts.localEntries.length > 1) { | ||
hot.#module.application.log(`The entire accept group of: ${accepts.localEntries.map(() => "%s").join(", ")} will be ignored.`, ...accepts.localEntries.map(entry => JSON.stringify(entry.specifier))); | ||
} | ||
} | ||
return []; | ||
} | ||
})); | ||
return modules.every(module => !imports.has(module) || acceptedModules.has(module)); | ||
} | ||
}; | ||
isAcceptedSelf = instance => { | ||
const hot = selectHot(instance); | ||
return hot !== null && hot.#acceptsSelf.length > 0; | ||
}; | ||
isDeclined = instance => { | ||
const hot = selectHot(instance); | ||
return hot !== null && hot.#declined; | ||
}; | ||
isInvalidated = instance => { | ||
const hot = selectHot(instance); | ||
return hot !== null && hot.#invalidated; | ||
}; | ||
prune = async (instance) => { | ||
const data = {}; | ||
const hot = selectHot(instance); | ||
if (hot !== null) { | ||
for (const callback of Fn.reverse(hot.#destructors)) { | ||
await callback(data, true); | ||
} | ||
} | ||
}; | ||
tryAccept = async (instance, modules) => { | ||
const hot = selectHot(instance); | ||
if (hot !== null && hot.#invalidated) { | ||
return false; | ||
} | ||
else { | ||
const imports = new Set(instance.iterateDependencies()); | ||
const acceptedHandlers = Array.from(Fn.filter(Fn.map(hot === null ? [] : hot.#accepts, accepts => { | ||
const acceptedModules = accepts.localEntries.map(entry => entry.found ? entry.module : instance.lookupSpecifier(entry.specifier)); | ||
if (acceptedModules.every(module => module != null)) { | ||
return { | ||
accepts, | ||
modules: acceptedModules, | ||
}; | ||
} | ||
}))); | ||
const acceptedModules = new Set(Fn.transform(acceptedHandlers, handler => handler.modules)); | ||
if (!modules.every(module => !imports.has(module) || acceptedModules.has(module))) { | ||
return false; | ||
} | ||
for (const { accepts, modules } of acceptedHandlers) { | ||
if (accepts.callback && modules.some(module => modules.includes(module))) { | ||
const namespaces = modules.map(module => module.select().moduleNamespace()()); | ||
assert.ok(hot); | ||
try { | ||
await accepts.callback(namespaces); | ||
} | ||
catch (error) { | ||
hot.#module.application.log(`Caught error in module '%s' during accept of [${accepts.localEntries.map(() => "%s").join(", ")}]:${EOL}%O`, hot.#module.url, ...accepts.localEntries.map(entry => `'${entry.specifier}'`), error); | ||
return false; | ||
} | ||
if (hot.#invalidated) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
} | ||
}; | ||
tryAcceptSelf = async (instance, self) => { | ||
const hot = selectHot(instance); | ||
if (hot === null || hot.#acceptsSelf.length === 0) { | ||
return false; | ||
} | ||
else { | ||
for (const callback of hot.#acceptsSelf) { | ||
try { | ||
await callback(self); | ||
} | ||
catch (error) { | ||
hot.#module.application.log(`Caught error in module '%s' during self-accept:${EOL}%O`, hot.#module.url, error); | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
}; | ||
} | ||
accept(arg1, arg2) { | ||
@@ -195,3 +75,3 @@ if (typeof arg1 === "string") { | ||
const localEntries = arg1.map(specifier => { | ||
const module = this.#instance.lookupSpecifier(specifier); | ||
const module = __classPrivateFieldGet(this, _Hot_instance, "f").lookupSpecifier(specifier); | ||
if (module == null) { | ||
@@ -208,3 +88,3 @@ return { found: false, module, specifier }; | ||
if (!descriptor.found) { | ||
if (!this.#usesDynamicImport) { | ||
if (!__classPrivateFieldGet(this, _Hot_usesDynamicImport, "f")) { | ||
console.trace(`[hot] ${arg1[ii]} was not imported by this module`); | ||
@@ -217,7 +97,7 @@ } | ||
} | ||
this.#accepts.push({ localEntries, callback }); | ||
__classPrivateFieldGet(this, _Hot_accepts, "f").push({ localEntries, callback }); | ||
} | ||
else { | ||
const callback = arg1; | ||
this.#acceptsSelf.push(async (self) => { | ||
__classPrivateFieldGet(this, _Hot_acceptsSelf, "f").push(async (self) => { | ||
await callback?.(self()); | ||
@@ -232,3 +112,3 @@ }); | ||
decline() { | ||
this.#declined = true; | ||
__classPrivateFieldSet(this, _Hot_declined, true, "f"); | ||
} | ||
@@ -242,3 +122,3 @@ /** | ||
assert.ok(typeof onDispose === "function"); | ||
this.#destructors.push(data => onDispose(data)); | ||
__classPrivateFieldGet(this, _Hot_destructors, "f").push(data => onDispose(data)); | ||
} | ||
@@ -250,4 +130,4 @@ /** | ||
invalidate() { | ||
this.#invalidated = true; | ||
void this.#module.application.requestUpdate(); | ||
__classPrivateFieldSet(this, _Hot_invalidated, true, "f"); | ||
void __classPrivateFieldGet(this, _Hot_module, "f").application.requestUpdate(); | ||
} | ||
@@ -259,3 +139,3 @@ /** | ||
prune(onPrune) { | ||
this.#destructors.push((data, prune) => { | ||
__classPrivateFieldGet(this, _Hot_destructors, "f").push((data, prune) => { | ||
if (prune) { | ||
@@ -267,9 +147,136 @@ return onPrune(); | ||
on(event, callback) { | ||
this.#module.application.emitter.addListener(event, callback); | ||
__classPrivateFieldGet(this, _Hot_module, "f").application.emitter.addListener(event, callback); | ||
return () => { | ||
this.#module.application.emitter.removeListener(event, callback); | ||
__classPrivateFieldGet(this, _Hot_module, "f").application.emitter.removeListener(event, callback); | ||
}; | ||
} | ||
} | ||
_a = Hot, _Hot_accepts = new WeakMap(), _Hot_acceptsSelf = new WeakMap(), _Hot_destructors = new WeakMap(), _Hot_dynamicImports = new WeakMap(), _Hot_instance = new WeakMap(), _Hot_module = new WeakMap(), _Hot_usesDynamicImport = new WeakMap(), _Hot_declined = new WeakMap(), _Hot_invalidated = new WeakMap(); | ||
(() => { | ||
didDynamicImport = (instance, controller) => { | ||
const hot = selectHot(instance); | ||
if (hot !== null) { | ||
__classPrivateFieldGet(hot, _Hot_dynamicImports, "f").add(controller); | ||
} | ||
}; | ||
dispose = async (instance) => { | ||
const data = {}; | ||
const hot = selectHot(instance); | ||
if (hot !== null) { | ||
for (const callback of Fn.reverse(__classPrivateFieldGet(hot, _Hot_destructors, "f"))) { | ||
await callback(data); | ||
} | ||
} | ||
return data; | ||
}; | ||
isAccepted = (instance, modules) => { | ||
const hot = selectHot(instance); | ||
if (hot !== null && __classPrivateFieldGet(hot, _Hot_invalidated, "f")) { | ||
return false; | ||
} | ||
else { | ||
const imports = new Set(instance.iterateDependencies()); | ||
const acceptedModules = new Set(Fn.transform(hot === null ? [] : __classPrivateFieldGet(hot, _Hot_accepts, "f"), accepts => { | ||
const acceptedModules = accepts.localEntries.map(entry => entry.found ? entry.module : instance.lookupSpecifier(entry.specifier)); | ||
if (acceptedModules.every(module => module != null)) { | ||
return acceptedModules; | ||
} | ||
else { | ||
if (hot !== null) { | ||
const specifiers = Array.from(Fn.transform(acceptedModules.entries(), function* ([ii, module]) { | ||
if (module == null) { | ||
yield accepts.localEntries[ii].specifier; | ||
} | ||
})); | ||
__classPrivateFieldGet(hot, _Hot_module, "f").application.log(`Warning: ${plural("specifier", specifiers.length)} ${specifiers.map(() => "%s").join(", ")} from %s could not be resolved.`, ...specifiers.map(specifier => JSON.stringify(specifier)), makeRelative(__classPrivateFieldGet(hot, _Hot_module, "f").url)); | ||
if (accepts.localEntries.length > 1) { | ||
__classPrivateFieldGet(hot, _Hot_module, "f").application.log(`The entire accept group of: ${accepts.localEntries.map(() => "%s").join(", ")} will be ignored.`, ...accepts.localEntries.map(entry => JSON.stringify(entry.specifier))); | ||
} | ||
} | ||
return []; | ||
} | ||
})); | ||
return modules.every(module => !imports.has(module) || acceptedModules.has(module)); | ||
} | ||
}; | ||
isAcceptedSelf = instance => { | ||
const hot = selectHot(instance); | ||
return hot !== null && __classPrivateFieldGet(hot, _Hot_acceptsSelf, "f").length > 0; | ||
}; | ||
isDeclined = instance => { | ||
const hot = selectHot(instance); | ||
return hot !== null && __classPrivateFieldGet(hot, _Hot_declined, "f"); | ||
}; | ||
isInvalidated = instance => { | ||
const hot = selectHot(instance); | ||
return hot !== null && __classPrivateFieldGet(hot, _Hot_invalidated, "f"); | ||
}; | ||
prune = async (instance) => { | ||
const data = {}; | ||
const hot = selectHot(instance); | ||
if (hot !== null) { | ||
for (const callback of Fn.reverse(__classPrivateFieldGet(hot, _Hot_destructors, "f"))) { | ||
await callback(data, true); | ||
} | ||
} | ||
}; | ||
tryAccept = async (instance, modules) => { | ||
const hot = selectHot(instance); | ||
if (hot !== null && __classPrivateFieldGet(hot, _Hot_invalidated, "f")) { | ||
return false; | ||
} | ||
else { | ||
const imports = new Set(instance.iterateDependencies()); | ||
const acceptedHandlers = Array.from(Fn.filter(Fn.map(hot === null ? [] : __classPrivateFieldGet(hot, _Hot_accepts, "f"), accepts => { | ||
const acceptedModules = accepts.localEntries.map(entry => entry.found ? entry.module : instance.lookupSpecifier(entry.specifier)); | ||
if (acceptedModules.every(module => module != null)) { | ||
return { | ||
accepts, | ||
acceptedModules, | ||
}; | ||
} | ||
}))); | ||
const acceptedModules = new Set(Fn.transform(acceptedHandlers, handler => handler.acceptedModules)); | ||
if (!modules.every(module => !imports.has(module) || acceptedModules.has(module))) { | ||
return false; | ||
} | ||
for (const { accepts, acceptedModules } of acceptedHandlers) { | ||
if (accepts.callback && modules.some(module => acceptedModules.includes(module))) { | ||
const namespaces = modules.map(module => module.select().moduleNamespace()()); | ||
assert.ok(hot); | ||
try { | ||
await accepts.callback(namespaces); | ||
} | ||
catch (error) { | ||
__classPrivateFieldGet(hot, _Hot_module, "f").application.log(`Caught error in module '%s' during accept of [${accepts.localEntries.map(() => "%s").join(", ")}]:${EOL}%O`, __classPrivateFieldGet(hot, _Hot_module, "f").url, ...accepts.localEntries.map(entry => `'${entry.specifier}'`), error); | ||
return false; | ||
} | ||
if (__classPrivateFieldGet(hot, _Hot_invalidated, "f")) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
} | ||
}; | ||
tryAcceptSelf = async (instance, self) => { | ||
const hot = selectHot(instance); | ||
if (hot === null || __classPrivateFieldGet(hot, _Hot_acceptsSelf, "f").length === 0) { | ||
return false; | ||
} | ||
else { | ||
for (const callback of __classPrivateFieldGet(hot, _Hot_acceptsSelf, "f")) { | ||
try { | ||
await callback(self); | ||
} | ||
catch (error) { | ||
__classPrivateFieldGet(hot, _Hot_module, "f").application.log(`Caught error in module '%s' during self-accept:${EOL}%O`, __classPrivateFieldGet(hot, _Hot_module, "f").url, error); | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
}; | ||
})(); | ||
Object.freeze(Hot.prototype); | ||
//# sourceMappingURL=hot.js.map |
@@ -11,11 +11,8 @@ import * as assert from "node:assert/strict"; | ||
export class ReloadableModuleInstance { | ||
controller; | ||
declaration; | ||
reloadable = true; | ||
state = { status: ModuleStatus.new }; | ||
dynamicImports = []; | ||
namespace; | ||
constructor(controller, declaration) { | ||
this.controller = controller; | ||
this.declaration = declaration; | ||
this.reloadable = true; | ||
this.state = { status: ModuleStatus.new }; | ||
this.dynamicImports = []; | ||
} | ||
@@ -36,2 +33,3 @@ clone() { | ||
hot, | ||
url: this.controller.url, | ||
}); | ||
@@ -269,3 +267,3 @@ return { hot, importMeta }; | ||
const namespace = Object.create(null); | ||
this.namespace ??= () => namespace; | ||
this.namespace ?? (this.namespace = () => namespace); | ||
const ambiguousNames = new Set(); | ||
@@ -272,0 +270,0 @@ const resolutions = new Map(); |
@@ -0,1 +1,53 @@ | ||
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
if (value !== null && value !== void 0) { | ||
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); | ||
var dispose, inner; | ||
if (async) { | ||
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); | ||
dispose = value[Symbol.asyncDispose]; | ||
} | ||
if (dispose === void 0) { | ||
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); | ||
dispose = value[Symbol.dispose]; | ||
if (async) inner = dispose; | ||
} | ||
if (typeof dispose !== "function") throw new TypeError("Object not disposable."); | ||
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; | ||
env.stack.push({ value: value, dispose: dispose, async: async }); | ||
} | ||
else if (async) { | ||
env.stack.push({ async: true }); | ||
} | ||
return value; | ||
}; | ||
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { | ||
return function (env) { | ||
function fail(e) { | ||
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; | ||
env.hasError = true; | ||
} | ||
var r, s = 0; | ||
function next() { | ||
while (r = env.stack.pop()) { | ||
try { | ||
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); | ||
if (r.dispose) { | ||
var result = r.dispose.call(r.value); | ||
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); | ||
} | ||
else s |= 1; | ||
} | ||
catch (e) { | ||
fail(e); | ||
} | ||
} | ||
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); | ||
if (env.hasError) throw env.error; | ||
} | ||
return next(); | ||
}; | ||
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { | ||
var e = new Error(message); | ||
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; | ||
}); | ||
import * as assert from "node:assert/strict"; | ||
@@ -7,13 +59,15 @@ import Fn, { mappedNumericComparator } from "dynohot/functional"; | ||
let lock = false; | ||
let currentVisitIndex = 0; | ||
let currentIndex = 0; | ||
return () => { | ||
assert.ok(!lock); | ||
lock = true; | ||
const visitIndex = ++currentVisitIndex; | ||
const release = () => { | ||
assert.ok(lock); | ||
assert.equal(currentVisitIndex, visitIndex); | ||
lock = false; | ||
const index = ++currentIndex; | ||
return { | ||
index, | ||
[Symbol.dispose]() { | ||
assert.ok(lock); | ||
assert.equal(currentIndex, index); | ||
lock = false; | ||
}, | ||
}; | ||
return [release, visitIndex]; | ||
}; | ||
@@ -36,114 +90,102 @@ }; | ||
export function traverseDepthFirst(root, peek, begin, join, unwind) { | ||
const expect = (node) => { | ||
const state = peek(node); | ||
assert.ok(state.visitIndex === visitIndex); | ||
return state; | ||
}; | ||
const inner = (node) => { | ||
// Initialize and add to stack | ||
const nodeIndex = index++; | ||
const holder = makeTraversalState(visitIndex, { | ||
index: nodeIndex, | ||
ancestorIndex: nodeIndex, | ||
order, | ||
forwardResults: undefined, | ||
result: undefined, | ||
}); | ||
const { state } = holder; | ||
const stackIndex = stack.length; | ||
stack.push(node); | ||
// Collect forward results | ||
let hasPromise = false; | ||
const forwardResultsMaybePromise = Array.from(Fn.transform(begin(node, holder), function* (child) { | ||
const holder = peek(child); | ||
const childState = holder.visitIndex === visitIndex ? holder.state : inner(child); | ||
const { result } = childState; | ||
if (result === undefined) { | ||
state.ancestorIndex = Math.min(state.ancestorIndex, childState.ancestorIndex); | ||
} | ||
else if (result.sync) { | ||
yield result.resolution; | ||
} | ||
else { | ||
hasPromise = true; | ||
yield result.promise; | ||
// nb: If we have a sibling which throws synchronously then this result is never | ||
// collected into `Promise.all`. Therefore this promise is never awaited and a | ||
// rejection will take down the process. It is explicitly marked as handled here. | ||
result.promise.then(() => { }, () => { }); | ||
} | ||
})); | ||
// Detect promise or sync | ||
state.forwardResults = function () { | ||
if (hasPromise) { | ||
return { | ||
sync: false, | ||
promise: Promise.all(forwardResultsMaybePromise), | ||
}; | ||
} | ||
else { | ||
return { | ||
sync: true, | ||
resolution: forwardResultsMaybePromise, | ||
}; | ||
} | ||
}(); | ||
// Join cyclic nodes | ||
assert.ok(state.ancestorIndex <= state.index); | ||
state.order = ++order; | ||
if (state.ancestorIndex === state.index) { | ||
const cycleNodes = stack.splice(stackIndex); | ||
cycleNodes.sort(mappedNumericComparator(node => peek(node).state.order)); | ||
// Collect forward results from cycle nodes | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const expect = (node) => { | ||
const state = peek(node); | ||
assert.ok(state.visitIndex === visitIndex.index); | ||
return state; | ||
}; | ||
const inner = (node) => { | ||
// Initialize and add to stack | ||
const nodeIndex = index++; | ||
const holder = makeTraversalState(visitIndex.index, { | ||
index: nodeIndex, | ||
ancestorIndex: nodeIndex, | ||
order, | ||
forwardResults: undefined, | ||
result: undefined, | ||
}); | ||
const { state } = holder; | ||
const stackIndex = stack.length; | ||
stack.push(node); | ||
// Collect forward results | ||
let hasPromise = false; | ||
const cyclicForwardResults = cycleNodes.map(node => { | ||
const { state: { forwardResults } } = expect(node); | ||
assert.ok(forwardResults !== undefined); | ||
if (forwardResults.sync) { | ||
return forwardResults.resolution; | ||
const forwardResultsMaybePromise = Array.from(Fn.transform(begin(node, holder), function* (child) { | ||
const holder = peek(child); | ||
const childState = holder.visitIndex === visitIndex.index ? holder.state : inner(child); | ||
const { result } = childState; | ||
if (result === undefined) { | ||
state.ancestorIndex = Math.min(state.ancestorIndex, childState.ancestorIndex); | ||
} | ||
else if (result.sync) { | ||
yield result.resolution; | ||
} | ||
else { | ||
hasPromise = true; | ||
return forwardResults.promise; | ||
yield result.promise; | ||
// nb: If we have a sibling which throws synchronously then this result is never | ||
// collected into `Promise.all`. Therefore this promise is never awaited and a | ||
// rejection will take down the process. It is explicitly marked as handled here. | ||
result.promise.then(() => { }, () => { }); | ||
} | ||
}); | ||
// Await completion of forward results of all cycle members | ||
const result = function () { | ||
})); | ||
// Detect promise or sync | ||
state.forwardResults = function () { | ||
if (hasPromise) { | ||
return { | ||
sync: false, | ||
promise: async function () { | ||
let forwardResults; | ||
try { | ||
forwardResults = collect(nodeIndex, await Promise.all(cyclicForwardResults)); | ||
} | ||
catch (error) { | ||
unwind?.(cycleNodes); | ||
throw error; | ||
} | ||
let result; | ||
const maybePromise = join(cycleNodes, forwardResults); | ||
if (typeof maybePromise?.then === "function") { | ||
result = await maybePromise; | ||
} | ||
else { | ||
result = maybePromise; | ||
} | ||
return { | ||
collectionIndex: -1, | ||
value: result, | ||
}; | ||
}(), | ||
promise: Promise.all(forwardResultsMaybePromise), | ||
}; | ||
} | ||
else { | ||
const forwardResults = collect(nodeIndex, cyclicForwardResults); | ||
const result = join(cycleNodes, forwardResults); | ||
if (typeof result?.then === "function") { | ||
return { | ||
sync: true, | ||
resolution: forwardResultsMaybePromise, | ||
}; | ||
} | ||
}(); | ||
// Join cyclic nodes | ||
assert.ok(state.ancestorIndex <= state.index); | ||
state.order = ++order; | ||
if (state.ancestorIndex === state.index) { | ||
const cycleNodes = stack.splice(stackIndex); | ||
cycleNodes.sort(mappedNumericComparator(node => peek(node).state.order)); | ||
// Collect forward results from cycle nodes | ||
let hasPromise = false; | ||
const cyclicForwardResults = cycleNodes.map(node => { | ||
const { state: { forwardResults } } = expect(node); | ||
assert.ok(forwardResults !== undefined); | ||
if (forwardResults.sync) { | ||
return forwardResults.resolution; | ||
} | ||
else { | ||
hasPromise = true; | ||
return forwardResults.promise; | ||
} | ||
}); | ||
// Await completion of forward results of all cycle members | ||
const result = function () { | ||
if (hasPromise) { | ||
return { | ||
sync: false, | ||
promise: async function () { | ||
let forwardResults; | ||
try { | ||
forwardResults = collect(nodeIndex, await Promise.all(cyclicForwardResults)); | ||
} | ||
catch (error) { | ||
unwind?.(cycleNodes); | ||
throw error; | ||
} | ||
let result; | ||
const maybePromise = join(cycleNodes, forwardResults); | ||
if (typeof maybePromise?.then === "function") { | ||
result = await maybePromise; | ||
} | ||
else { | ||
result = maybePromise; | ||
} | ||
return { | ||
collectionIndex: -1, | ||
value: await result, | ||
value: result, | ||
}; | ||
@@ -154,41 +196,60 @@ }(), | ||
else { | ||
return { | ||
sync: true, | ||
resolution: { | ||
collectionIndex: -1, | ||
value: result, | ||
}, | ||
}; | ||
const forwardResults = collect(nodeIndex, cyclicForwardResults); | ||
const result = join(cycleNodes, forwardResults); | ||
if (typeof result?.then === "function") { | ||
return { | ||
sync: false, | ||
promise: async function () { | ||
return { | ||
collectionIndex: -1, | ||
value: await result, | ||
}; | ||
}(), | ||
}; | ||
} | ||
else { | ||
return { | ||
sync: true, | ||
resolution: { | ||
collectionIndex: -1, | ||
value: result, | ||
}, | ||
}; | ||
} | ||
} | ||
}(); | ||
// Assign state to all cycle members | ||
for (const node of cycleNodes) { | ||
const childState = expect(node).state; | ||
assert.equal(childState.result, undefined); | ||
childState.result = result; | ||
} | ||
}(); | ||
// Assign state to all cycle members | ||
for (const node of cycleNodes) { | ||
const childState = expect(node).state; | ||
assert.equal(childState.result, undefined); | ||
childState.result = result; | ||
} | ||
return state; | ||
}; | ||
const visitIndex = __addDisposableResource(env_1, acquireVisitIndex(), false); | ||
let index = 0; | ||
let order = 0; | ||
const stack = []; | ||
try { | ||
const { result } = inner(root); | ||
assert.ok(result !== undefined); | ||
if (result.sync) { | ||
return result.resolution.value; | ||
} | ||
else { | ||
return result.promise.then(({ value }) => value); | ||
} | ||
} | ||
return state; | ||
}; | ||
const [release, visitIndex] = acquireVisitIndex(); | ||
let index = 0; | ||
let order = 0; | ||
const stack = []; | ||
try { | ||
const { result } = inner(root); | ||
assert.ok(result !== undefined); | ||
if (result.sync) { | ||
return result.resolution.value; | ||
catch (error) { | ||
unwind?.(stack); | ||
throw error; | ||
} | ||
else { | ||
return result.promise.then(({ value }) => value); | ||
} | ||
} | ||
catch (error) { | ||
unwind?.(stack); | ||
throw error; | ||
catch (e_1) { | ||
env_1.error = e_1; | ||
env_1.hasError = true; | ||
} | ||
finally { | ||
release(); | ||
__disposeResources(env_1); | ||
} | ||
@@ -195,0 +256,0 @@ } |
@@ -27,6 +27,6 @@ import * as assert from "node:assert"; | ||
if (running) { | ||
return pending ??= async function () { | ||
return pending ?? (pending = async function () { | ||
await running; | ||
return dispatch(); | ||
}(); | ||
}()); | ||
} | ||
@@ -33,0 +33,0 @@ else { |
@@ -8,3 +8,5 @@ import * as fs from "node:fs"; | ||
export class FileWatcher { | ||
watchers = new Map(); | ||
constructor() { | ||
this.watchers = new Map(); | ||
} | ||
watch(url, callback) { | ||
@@ -11,0 +13,0 @@ if (!url.startsWith("file://")) { |
{ | ||
"name": "dynohot", | ||
"type": "module", | ||
"version": "1.2.0", | ||
"version": "1.2.1", | ||
"exports": { | ||
@@ -29,2 +29,3 @@ ".": "./dist/loader/register.js", | ||
"devDependencies": { | ||
"@babel/plugin-proposal-explicit-resource-management": "^7.25.9", | ||
"@babel/preset-env": "^7.24.7", | ||
@@ -39,3 +40,3 @@ "@babel/preset-typescript": "^7.24.7", | ||
"@types/convert-source-map": "^2.0.2", | ||
"@types/node": "^20.14.9", | ||
"@types/node": "^22.9.3", | ||
"babel-jest": "^29.7.0", | ||
@@ -42,0 +43,0 @@ "babel-plugin-transform-import-meta": "^2.2.1", |
@@ -179,12 +179,12 @@ import type { ReloadableModuleInstance } from "./instance.js"; | ||
accepts, | ||
modules: acceptedModules, | ||
acceptedModules, | ||
}; | ||
} | ||
}))); | ||
const acceptedModules = new Set(Fn.transform(acceptedHandlers, handler => handler.modules)); | ||
const acceptedModules = new Set(Fn.transform(acceptedHandlers, handler => handler.acceptedModules)); | ||
if (!modules.every(module => !imports.has(module) || acceptedModules.has(module))) { | ||
return false; | ||
} | ||
for (const { accepts, modules } of acceptedHandlers) { | ||
if (accepts.callback && modules.some(module => modules.includes(module))) { | ||
for (const { accepts, acceptedModules } of acceptedHandlers) { | ||
if (accepts.callback && modules.some(module => acceptedModules.includes(module))) { | ||
const namespaces = modules.map(module => module.select().moduleNamespace()()); | ||
@@ -191,0 +191,0 @@ assert.ok(hot); |
166212
3446
16