User callout: 'Hax' is Kaysser's nickname. The module id should not use it. Rename the Foundry module id from 'hax-hooks-lib' to 'foundry-hooks-lib'. Gitea repo name stays as 'hooks-lib' (kept for the user-facing URL); the Gitea manifest URL is unchanged. **Scope of rename:** - module.json: id, title, version (0.2.0 -> 0.3.0), download URL - package.json: name - README.md, HOOK_CONTRACT.md, LICENSE: branding text - All 6 production JS files: MODULE_ID constant + comments - 4 active test files: console.log strings + test descriptions - Rename of release zips in git: hooks-lib-X.Y.Z.zip -> foundry-hooks-lib-X.Y.Z.zip (preserves the v0.1.0 and v0.2.0 zips as historical artifacts; the v0.3.0 zip is the new release artifact) - .gitignore: glob + un-ignore lines updated to match **Out of scope (deliberate):** - Gitea repo name 'kaykayyali/hooks-lib' stays. Per the user's direction, only the module id is renamed; the Gitea URL path is preserved for the existing 'url', 'manifest', 'download' fields. - scripts/_archive/v0.1.0/*: historical v0.1.0 code is left as-is. Those files tested 'hax-hooks-lib v0.1.0'; rewriting the history would be misleading. - tests/_archive_v0.1.0_*.mjs: same reason, left untouched. - .hermes/plans/* session-historian plans that reference 'Hax's Tools split': session artifact, not a release asset. **Verification:** 554/554 smoke assertions pass, 6/6 perf assertions pass, median 0.0004ms/fire (well under 0.1ms budget). No logic change; rename is string-only. **Consumer action required:** battle-focus and its-achievable both declare 'relationships.requires' pointing to 'hax-hooks-lib'. The next commits on those repos will update their relationships to 'foundry-hooks-lib' + bump their versions. Foundry instances with v0.2.0 of the old id installed will need to be reinstalled as v0.3.0 of the new id.
80 lines
2.8 KiB
JavaScript
80 lines
2.8 KiB
JavaScript
// scripts/internal/envelope.js
|
|
//
|
|
// HOOK_CONTRACT.md §2 (envelope shape) + §8 (sync vs async dispatch).
|
|
//
|
|
// buildEnvelope(rawHookName, args): produces a {ts, hook, args} object
|
|
// or null if the raw hook is not in our registered set.
|
|
//
|
|
// dispatchEnvelope(envelope): routes the envelope to subscribers via
|
|
// the subscribers module. For sync-mode hooks, this is inline. For
|
|
// async-mode hooks, this is deferred to a microtask.
|
|
//
|
|
// This is the only module that touches the envelope shape. All other
|
|
// modules consume envelopes through subscribers.subscribe.
|
|
|
|
import { getEntryForRawName, getEntryForEnvelope } from "./registered-hooks.js";
|
|
import { ARG_SHAPES, maybeSynthesize } from "./anti-corruption.js";
|
|
import { dispatch } from "./subscribers.js";
|
|
|
|
const MODULE_ID = "foundry-hooks-lib";
|
|
|
|
/**
|
|
* Build an envelope for a Foundry hook fire.
|
|
*
|
|
* Returns:
|
|
* - { envelope, synthesized?: true } for the primary envelope
|
|
* - or null if the raw hook is not registered
|
|
*
|
|
* For hooks that synthesize additional envelopes (combatInactive from
|
|
* updateCombat), the synthesized envelope(s) are also returned in the
|
|
* `synthesized` array on the result.
|
|
*/
|
|
export function buildEnvelope(rawHookName, args) {
|
|
const entry = getEntryForRawName(rawHookName);
|
|
if (!entry) return null;
|
|
const normalize = ARG_SHAPES[entry.envelope];
|
|
const normalizedArgs = normalize ? normalize(args ?? []) : (args ?? []);
|
|
const ts = Date.now();
|
|
const primary = { ts, hook: entry.envelope, args: normalizedArgs };
|
|
// Anti-corruption: hooks with synthesized envelopes may produce
|
|
// additional envelopes from a single Foundry fire.
|
|
const synth = maybeSynthesize(rawHookName, args);
|
|
return { envelope: primary, synthesized: synth };
|
|
}
|
|
|
|
/**
|
|
* Dispatch an envelope. Called by the Foundry hook wrappers
|
|
* registered in main.js.
|
|
*
|
|
* For sync-mode hooks, dispatches inline. For async-mode hooks,
|
|
* schedules dispatch on a microtask. The wrapper returns immediately
|
|
* in both cases (microtask dispatch returns synchronously to Foundry).
|
|
*
|
|
* Returns immediately in both modes — never awaited by callers.
|
|
*/
|
|
export function dispatchEnvelope(envelope, mode) {
|
|
if (mode === "sync") {
|
|
dispatch(envelope);
|
|
} else {
|
|
// Async dispatch via microtask. We schedule a single microtask
|
|
// per envelope; the dispatch itself runs synchronously inside
|
|
// the microtask.
|
|
Promise.resolve().then(() => dispatch(envelope));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch a batch of envelopes. Used when a single Foundry fire
|
|
* produces multiple envelopes (e.g. updateCombat produces both
|
|
* updateCombat and combatInactive). Each envelope is dispatched
|
|
* according to its own mode.
|
|
*/
|
|
export function dispatchEnvelopes(envelopes) {
|
|
for (const env of envelopes) {
|
|
const entry = getEntryForEnvelope(env.hook);
|
|
if (!entry) continue;
|
|
dispatchEnvelope(env, entry.mode);
|
|
}
|
|
}
|
|
|
|
export { MODULE_ID }; |