@animus-ui/next-plugin 0.1.7 → 0.1.9

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.
@@ -10,6 +10,74 @@ export declare function getSharedExternalDirs(): string[];
10
10
  export declare function setSharedExternalDirs(dirs: string[]): void;
11
11
  export declare function getSharedExternalEntries(): Map<string, string>;
12
12
  export declare function setSharedExternalEntries(entries: Map<string, string>): void;
13
+ /** Structural view of the owning session a forwarded watch batch targets —
14
+ * kept minimal (and defined here, not imported) so the singleton never
15
+ * depends on the session module it serves. */
16
+ export interface WatchBatchTarget {
17
+ ingestForwardedBatch(changes: {
18
+ modifiedFiles?: ReadonlySet<string>;
19
+ removedFiles?: ReadonlySet<string>;
20
+ }): Promise<void>;
21
+ }
22
+ /**
23
+ * The session that completed the full pipeline and holds system state — the
24
+ * only instance that can run watch analysis. Each MultiCompiler child holds
25
+ * its own session AND its own watcher with its own modified set; a
26
+ * non-owning session forwards its batch here instead of dropping it (a file
27
+ * only that compiler watches would otherwise never be analyzed).
28
+ */
29
+ export declare function getOwningWatchSession(): WatchBatchTarget | null;
30
+ export declare function setOwningWatchSession(session: WatchBatchTarget | null): void;
31
+ /**
32
+ * Per-file analyzed content hashes of the last published analysis (relPath →
33
+ * contentHash of the exact bytes analyzed) — the loader's witness for the
34
+ * unconditional catching-up guard (openspec:
35
+ * next-webpack-served-transform-coherence, design D4). Includes files
36
+ * analyzed with ZERO animus entries: analyzed identity is membership in the
37
+ * analysis input set, not manifest entry count.
38
+ */
39
+ export declare function getAnalyzedHashes(): ReadonlyMap<string, string> | null;
40
+ export declare function setAnalyzedHashes(hashes: Map<string, string>): void;
41
+ /**
42
+ * Session identity is per Next INVOCATION — one process, however many
43
+ * compiler instances it holds (openspec:
44
+ * next-turbopack-served-transform-coherence, design D2). The first
45
+ * ExtractionSession constructed in a process claims a fresh randomUUID;
46
+ * every later instance (client/server/RSC compilers each construct one)
47
+ * adopts it, so all compilers alias, watch-ignore, and publish ONE
48
+ * session-scoped artifact tree. Separate invocations (`next dev` +
49
+ * `next build` co-writing) are separate processes and therefore separate
50
+ * sessions by construction.
51
+ */
52
+ export declare function claimProcessSessionId(): string;
53
+ /**
54
+ * Absolute session artifact directory of the OWNING session's last
55
+ * publication — the webpack loader's source for the session-scoped epoch
56
+ * dependency path (the loader shares the process with the pipeline; the
57
+ * Turbopack loader instead receives the directory via its options).
58
+ */
59
+ export declare function getSessionArtifactDir(): string | null;
60
+ export declare function setSessionArtifactDir(dir: string): void;
61
+ /**
62
+ * Canonical replacement epoch of the last published analysis
63
+ * (`hashReplacementPlans(snapshotFilePlans(manifest), systemPropsContent)`
64
+ * — openspec: next-webpack-served-transform-coherence, design D5; the
65
+ * served system-props module rides as the served-dependency witness so
66
+ * offline system-props changes move the epoch). Published by the
67
+ * owning session AFTER the manifest so a reader that observes the epoch
68
+ * always observes at least that generation's manifest. (Session
69
+ * attribution lives on the DISK epoch artifact — no in-process mirror.)
70
+ */
71
+ export declare function getReplacementEpoch(): string | null;
72
+ export declare function setReplacementEpoch(epoch: string): void;
73
+ /**
74
+ * The one in-flight watch-analysis transaction (design D3): the first
75
+ * compiler entering a watch batch runs analysis + publication; every other
76
+ * compiler (client/server/RSC — each holds its own session instance) joins
77
+ * this promise instead of proceeding against a pre-transaction generation.
78
+ */
79
+ export declare function getWatchTransaction(): Promise<void> | null;
80
+ export declare function setWatchTransaction(transaction: Promise<void> | null): void;
13
81
  export type AnimusEngine = 'v2';
14
82
  /** Engine selection travels through the singleton so non-owning compiler
15
83
  * instances and the webpack loader honor the same choice as the owner. */
@@ -20,4 +88,9 @@ export declare function getSharedEngine(): AnimusEngine;
20
88
  * module's own .d.ts is the authoritative surface. */
21
89
  export declare function requireEngine(): any;
22
90
  export declare function engineApi(): any;
91
+ /**
92
+ * Every globalThis key this module owns — the single authority test
93
+ * harnesses snapshot/clear per test (never re-declare this list).
94
+ */
95
+ export declare const SINGLETON_GLOBAL_KEYS: readonly ["__animus_manifest_json__", "__animus_analysis_promise__", "__animus_shared_css__", "__animus_shared_system_props__", "__animus_external_pkg_dirs__", "__animus_external_source_entries__", "__animus_analyzed_hashes__", "__animus_replacement_epoch__", "__animus_watch_transaction__", "__animus_process_session_id__", "__animus_session_artifact_dir__", "__animus_owning_watch_session__", "__animus_engine__", "__animus_v2_engine__", "__animus_v2_sent_sources__", "__animus_v2_drift_warned__"];
23
96
  //# sourceMappingURL=singleton.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../src/singleton.ts"],"names":[],"mappings":"AAkBA,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAIzD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAE/D;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAMhD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAE1D;AAED,wBAAgB,wBAAwB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAM9D;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAG3E;AAID,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC;AAEhC;2EAC2E;AAC3E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAE1D;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;uDAEuD;AAEvD,wBAAgB,aAAa,IAAI,GAAG,CAInC;AAoDD,wBAAgB,SAAS,IAAI,GAAG,CAE/B"}
1
+ {"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../src/singleton.ts"],"names":[],"mappings":"AAmBA,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAIzD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAE/D;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAMhD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAE1D;AAED,wBAAgB,wBAAwB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAM9D;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAG3E;AA6BD;;+CAE+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,oBAAoB,CAAC,OAAO,EAAE;QAC5B,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QACpC,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,gBAAgB,GAAG,IAAI,CAE/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAE5E;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAEtE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAEnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAO9C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CAErD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAEnD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE3E;AAID,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC;AAEhC;2EAC2E;AAC3E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAE1D;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;uDAEuD;AAEvD,wBAAgB,aAAa,IAAI,GAAG,CAInC;AAoDD,wBAAgB,SAAS,IAAI,GAAG,CAE/B;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,qfAiBxB,CAAC"}
@@ -36,13 +36,18 @@ export declare function resolveTurbopackMode(options: AnimusNextOptions, env?: R
36
36
  * (file-level allowlisting lives in the loader via manifest lookup — Next 15
37
37
  * rules have no condition algebra) plus resolve aliases for the virtual
38
38
  * system-props id, the emitter's stylesheet id, and each collected external
39
- * package specifier redirected to its source entry.
39
+ * package specifier redirected to its source entry. Loader options carry
40
+ * the session identity (design D2: an options-borne, JSON-serializable
41
+ * Turbopack task input — restart-cold by construction), and the artifact
42
+ * aliases point into the session-scoped tree.
40
43
  */
41
44
  export declare function buildTurbopackConfig(args: {
42
45
  rootDir: string;
43
46
  loaderPath: string;
44
47
  options: AnimusNextOptions;
45
48
  externalSourceEntries: ReadonlyMap<string, string>;
49
+ sessionId: string;
50
+ sessionDir: string;
46
51
  }): TurbopackConfigFragment;
47
52
  /**
48
53
  * Locate the Turbopack loader module: built dist first, source fallback for
@@ -1 +1 @@
1
- {"version":3,"file":"turbopack-config.d.ts","sourceRoot":"","sources":["../src/turbopack-config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;GAIG;AAEH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;AAElD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;gEAGgE;AAChE,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE;;wBAEwB;AACxB,eAAO,MAAM,yBAAyB,gCAAgC,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,EAC1B,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,OAAO,CAMT;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,qBAAqB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpD,GAAG,uBAAuB,CA8B1B;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAMpE"}
1
+ {"version":3,"file":"turbopack-config.d.ts","sourceRoot":"","sources":["../src/turbopack-config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;GAIG;AAEH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;AAElD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;gEAGgE;AAChE,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE;;wBAEwB;AACxB,eAAO,MAAM,yBAAyB,gCAAgC,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,EAC1B,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,OAAO,CAMT;AAQD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,qBAAqB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,uBAAuB,CA0C1B;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAMpE"}
@@ -1,14 +1,45 @@
1
- const require_loader_core = require("./loader-core-CbaYNhBu.cjs");
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ //#region \0rolldown/runtime.js
6
+ var __create = Object.create;
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __getProtoOf = Object.getPrototypeOf;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
14
+ key = keys[i];
15
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+ //#endregion
27
+ const require_session_paths = require("./session-paths-CZoi19NW.cjs");
28
+ const require_loader_core = require("./loader-core-C40WbC6s.cjs");
2
29
  let _animus_ui_extract_pipeline = require("@animus-ui/extract/pipeline");
3
30
  let fs = require("fs");
31
+ fs = __toESM(fs);
4
32
  let path = require("path");
5
33
  //#region src/turbopack-loader.ts
6
34
  let engine = null;
7
35
  let sentSources = null;
8
36
  let driftWarned = false;
37
+ /** Hydration identity: `${commitPath}:${contentHash(commitRaw)}` — the
38
+ * commit CONTENT keys hydration; mtime/size never do (design D1). */
9
39
  let hydratedKey = null;
10
40
  let hydratedManifestJson = null;
11
- let hydrateFailedWarned = false;
41
+ /** Committed per-file analyzed content hashes (from the inputs). */
42
+ let hydratedFileHashes = null;
12
43
  const engineModuleId = "@animus-ui/extract";
13
44
  const engineApi = (0, _animus_ui_extract_pipeline.createV2EngineApi)({
14
45
  label: "animus-next-turbopack",
@@ -30,61 +61,222 @@ const engineApi = (0, _animus_ui_extract_pipeline.createV2EngineApi)({
30
61
  }
31
62
  }
32
63
  });
33
- /**
34
- * Hydrate this worker's engine from the `.animus/` artifact pair — the v2
35
- * engine serves transforms only from analyze-retained state, so a fresh
36
- * worker replays the persisted analysis inputs once (and again whenever
37
- * either artifact changes; keyed by mtime+size of both). The persisted
38
- * manifest is a hard prerequisite (spec: next-turbopack-integration /
39
- * Stateless per-file transformation — absent or unreadable
40
- * `.animus/manifest.json` means passthrough, even when analysis inputs
41
- * exist). Returns the manifest JSON, or null (caller passes through).
42
- */
43
- function hydrate(rootDir) {
44
- const inputsPath = (0, path.join)(rootDir, ".animus", "analysis-inputs.json");
45
- const manifestPath = (0, path.join)(rootDir, ".animus", "manifest.json");
46
- let inputsStat;
47
- let manifestStat;
64
+ let fsImpl = fs;
65
+ /** @internal test seam — pass null to restore the real fs. */
66
+ function __setTurbopackLoaderFsForTests(fs$1) {
67
+ fsImpl = fs$1 ?? fs;
68
+ }
69
+ /** Epoch artifact paths SEEN on disk (per-worker) — the artifact is never
70
+ * deleted once written, so a positive probe is stable; negatives re-probe. */
71
+ const epochSeenPaths = /* @__PURE__ */ new Set();
72
+ /** @internal test seam — drop worker-local hydration state. */
73
+ function __resetTurbopackLoaderStateForTests() {
74
+ hydratedKey = null;
75
+ hydratedManifestJson = null;
76
+ hydratedFileHashes = null;
77
+ epochSeenPaths.clear();
78
+ }
79
+ function readFileOrNull(path$1) {
48
80
  try {
49
- inputsStat = (0, fs.statSync)(inputsPath, { bigint: true });
50
- manifestStat = (0, fs.statSync)(manifestPath, { bigint: true });
81
+ return fsImpl.readFileSync(path$1, "utf-8");
51
82
  } catch {
52
83
  return null;
53
84
  }
54
- const key = `${inputsPath}:${inputsStat.mtimeNs}:${inputsStat.size}:${manifestStat.mtimeNs}:${manifestStat.size}`;
55
- if (key === hydratedKey && hydratedManifestJson !== null) return hydratedManifestJson;
56
- try {
57
- JSON.parse((0, fs.readFileSync)(manifestPath, "utf-8"));
58
- const inputs = JSON.parse((0, fs.readFileSync)(inputsPath, "utf-8"));
85
+ }
86
+ const SEQLOCK_RETRIES = 5;
87
+ /**
88
+ * Seqlock-style artifact read: commit C0 → payloads → hash verification
89
+ * against C0 → commit re-read; accept only if unchanged, else retry
90
+ * (bounded) — a torn or mismatched set is never consumed (design D1).
91
+ * Hydration replays the committed analysis inputs once per commit content.
92
+ */
93
+ function hydrateSession(sessionDir, sessionId) {
94
+ const commitPath = require_session_paths.analysisCommitPath(sessionDir);
95
+ for (let attempt = 0; attempt < SEQLOCK_RETRIES; attempt++) {
96
+ const c0raw = readFileOrNull(commitPath);
97
+ if (c0raw === null) return { kind: "absent" };
98
+ let c0;
99
+ try {
100
+ c0 = JSON.parse(c0raw);
101
+ } catch {
102
+ continue;
103
+ }
104
+ if (c0.sessionId !== sessionId) return {
105
+ kind: "foreign",
106
+ artifactSessionId: String(c0.sessionId)
107
+ };
108
+ const key = `${commitPath}:${(0, _animus_ui_extract_pipeline.contentHash)(c0raw)}`;
109
+ if (key === hydratedKey && hydratedManifestJson !== null && hydratedFileHashes !== null) return {
110
+ kind: "ok",
111
+ hydration: {
112
+ manifestJson: hydratedManifestJson,
113
+ fileHashes: hydratedFileHashes,
114
+ commitRaw: c0raw
115
+ }
116
+ };
117
+ const manifestRaw = readFileOrNull(require_session_paths.manifestPath(sessionDir));
118
+ const inputsRaw = readFileOrNull(require_session_paths.analysisInputsPath(sessionDir));
119
+ if (manifestRaw === null || inputsRaw === null) continue;
120
+ if ((0, _animus_ui_extract_pipeline.contentHash)(manifestRaw) !== c0.manifestHash || (0, _animus_ui_extract_pipeline.contentHash)(inputsRaw) !== c0.inputsHash) continue;
121
+ if (readFileOrNull(commitPath) !== c0raw) continue;
122
+ let inputs;
123
+ let fileHashes;
124
+ try {
125
+ const envelopeSession = require_session_paths.readJsonEnvelope(manifestRaw)?.sessionId;
126
+ if (envelopeSession !== void 0 && envelopeSession !== sessionId) return {
127
+ kind: "foreign",
128
+ artifactSessionId: String(envelopeSession)
129
+ };
130
+ const inputsParsed = JSON.parse(inputsRaw);
131
+ inputs = inputsParsed;
132
+ fileHashes = inputsParsed.analyzedHashes ? new Map(Object.entries(inputsParsed.analyzedHashes)) : new Map(JSON.parse(inputs.filesJson).map((entry) => [entry.path, entry.hash ?? (0, _animus_ui_extract_pipeline.contentHash)(entry.source)]));
133
+ } catch {
134
+ return { kind: "torn" };
135
+ }
59
136
  const { analyzeProject } = engineApi();
60
137
  hydratedManifestJson = analyzeProject(...(0, _animus_ui_extract_pipeline.buildAnalyzeProjectArgs)(inputs));
138
+ hydratedFileHashes = fileHashes;
61
139
  hydratedKey = key;
62
- return hydratedManifestJson;
63
- } catch (e) {
64
- if (!hydrateFailedWarned) {
65
- hydrateFailedWarned = true;
66
- console.warn(`[animus-extract] Turbopack worker hydration failed (${String(e)}); sources pass through untransformed`);
67
- }
140
+ return {
141
+ kind: "ok",
142
+ hydration: {
143
+ manifestJson: hydratedManifestJson,
144
+ fileHashes,
145
+ commitRaw: c0raw
146
+ }
147
+ };
148
+ }
149
+ return { kind: "torn" };
150
+ }
151
+ function readStatus(sessionDir) {
152
+ const raw = readFileOrNull(require_session_paths.analysisStatusPath(sessionDir));
153
+ if (raw === null) return null;
154
+ try {
155
+ return JSON.parse(raw);
156
+ } catch {
68
157
  return null;
69
158
  }
70
159
  }
160
+ const CATCHUP_POLL_INITIAL_MS = 10;
161
+ const CATCHUP_POLL_MAX_MS = 25;
162
+ const CATCHUP_WAIT_MARGIN_MS = 50;
163
+ const CATCHUP_WATCHDOG_MS = 2e3;
164
+ /** The loader cannot read the orchestrator's configured debounce, so its
165
+ * own ceiling assumes the default watcher debounce. */
166
+ const DEFAULT_DEBOUNCE_CEILING_MS = 75;
167
+ function sleep(ms) {
168
+ return new Promise((resolve) => setTimeout(resolve, ms));
169
+ }
170
+ const ACTIVE_STATES = new Set([
171
+ "starting",
172
+ "debouncing",
173
+ "analyzing",
174
+ "committing"
175
+ ]);
71
176
  /**
72
177
  * Turbopack loader for Animus source transformation. Derives everything
73
- * from the incoming source, its JSON-serializable options, and the
74
- * `.animus/` disk artifacts written by the out-of-band orchestrator.
178
+ * from the incoming source, its JSON-serializable options (carrying the
179
+ * session identity), and the session's COMMITTED disk artifacts written by
180
+ * the out-of-band orchestrator. Async by contract — the evidence-based
181
+ * catch-up (design D3) waits, bounded, for an imminent commit.
75
182
  */
76
183
  function animusTurbopackLoader(source) {
77
- const opts = this.getOptions?.() ?? {};
78
- const rootDir = opts.rootDir ?? this.rootContext;
79
- const manifestJson = hydrate(rootDir);
80
- if (!manifestJson) return source;
81
- return require_loader_core.transformWithManifest({
184
+ if (typeof this.async !== "function") throw new Error("[animus-extract] the Animus Turbopack loader requires an async-capable loader runner (this.async is unavailable)");
185
+ const callback = this.async();
186
+ runLoader(this, source).then((code) => callback(null, code), (err) => callback(err instanceof Error ? err : new Error(String(err))));
187
+ }
188
+ async function runLoader(ctx, source) {
189
+ const opts = ctx.getOptions?.() ?? {};
190
+ const rootDir = opts.rootDir ?? ctx.rootContext;
191
+ const sessionId = opts.sessionId;
192
+ const sessionDir = opts.sessionDir ?? (sessionId !== void 0 ? require_session_paths.sessionArtifactDir(rootDir, sessionId) : void 0);
193
+ if (sessionId === void 0 || sessionDir === void 0) return source;
194
+ const commitPath = require_session_paths.analysisCommitPath(sessionDir);
195
+ const statusPath = require_session_paths.analysisStatusPath(sessionDir);
196
+ const epochPath = require_session_paths.replacementEpochPath(sessionDir);
197
+ if (typeof ctx.addDependency === "function" && (epochSeenPaths.has(epochPath) || fsImpl.existsSync(epochPath))) {
198
+ epochSeenPaths.add(epochPath);
199
+ ctx.addDependency(epochPath);
200
+ }
201
+ const filename = (0, path.relative)(rootDir, ctx.resourcePath);
202
+ /** EVERY failing path registers the commit + status artifacts as
203
+ * dependencies BEFORE throwing (the loader-runner sends dependency IPC
204
+ * ahead of the error check upstream), so a landing commit or status
205
+ * transition re-triggers the invocation — the documented
206
+ * read→registration race makes this a recovery aid, never the primary
207
+ * wakeup (design D3). Successful paths add NO commit/status dependency. */
208
+ const coverageFailure = (message) => {
209
+ ctx.addDependency?.(commitPath);
210
+ ctx.addDependency?.(statusPath);
211
+ return new Error(message);
212
+ };
213
+ const transform = (manifestJson) => require_loader_core.transformWithManifest({
82
214
  source,
83
- filename: (0, path.relative)(rootDir, this.resourcePath),
215
+ filename,
84
216
  manifestJson,
85
217
  engineApi,
86
218
  opts
87
219
  });
220
+ const outcome = hydrateSession(sessionDir, sessionId);
221
+ if (outcome.kind === "foreign") throw coverageFailure(`ANIMUS_FOREIGN_SESSION: artifacts under ${sessionDir} belong to session ${outcome.artifactSessionId}, not ${sessionId} — refusing to transform from a foreign generation`);
222
+ if (outcome.kind === "torn") throw coverageFailure(`ANIMUS_ARTIFACT_READ_TORN: could not obtain a consistent committed artifact set for ${filename} after ${SEQLOCK_RETRIES} attempts`);
223
+ let sourceHash = null;
224
+ if (outcome.kind === "ok") {
225
+ const analyzedHash = outcome.hydration.fileHashes.get(filename);
226
+ if (analyzedHash === void 0) return transform(outcome.hydration.manifestJson);
227
+ sourceHash = (0, _animus_ui_extract_pipeline.contentHash)(source);
228
+ if (analyzedHash === sourceHash) return transform(outcome.hydration.manifestJson);
229
+ }
230
+ return awaitCoverage({
231
+ source,
232
+ sourceHash: sourceHash ?? (0, _animus_ui_extract_pipeline.contentHash)(source),
233
+ filename,
234
+ sessionId,
235
+ sessionDir,
236
+ coverageFailure,
237
+ transform
238
+ });
239
+ }
240
+ /**
241
+ * The catch-up decision table (design D3 — implemented verbatim). Waits —
242
+ * polled, bounded — ONLY in the two evidence states: commit absent while an
243
+ * attempt is active, or the active attempt's pending set contains this file
244
+ * at its current hash. Everything else fails immediately with its stable
245
+ * diagnostic. The moment a commit lands that covers the input, the seqlock
246
+ * hydration consumes it and the transform proceeds with no diagnostic.
247
+ */
248
+ async function awaitCoverage(args) {
249
+ const { source, sourceHash, filename, sessionId, sessionDir, coverageFailure, transform } = args;
250
+ const ceiling = Date.now() + DEFAULT_DEBOUNCE_CEILING_MS + CATCHUP_WATCHDOG_MS + CATCHUP_WAIT_MARGIN_MS;
251
+ let poll = CATCHUP_POLL_INITIAL_MS;
252
+ for (;;) {
253
+ const outcome = hydrateSession(sessionDir, sessionId);
254
+ if (outcome.kind === "foreign") throw coverageFailure(`ANIMUS_FOREIGN_SESSION: artifacts under ${sessionDir} belong to session ${outcome.artifactSessionId}, not ${sessionId}`);
255
+ if (outcome.kind === "ok") {
256
+ const analyzedHash = outcome.hydration.fileHashes.get(filename);
257
+ if (analyzedHash !== void 0 && analyzedHash === sourceHash) return transform(outcome.hydration.manifestJson);
258
+ }
259
+ const commitPresent = outcome.kind === "ok";
260
+ const status = readStatus(sessionDir);
261
+ if (status === null) {
262
+ if (!commitPresent) return source;
263
+ throw coverageFailure(`ANIMUS_ANALYSIS_CATCHING_UP: ${filename} is not covered by the committed analysis and no analysis-status exists for session ${sessionId} — is the animus orchestrator running?`);
264
+ }
265
+ if (status.sessionId !== sessionId) throw coverageFailure(`ANIMUS_FOREIGN_SESSION: analysis-status belongs to session ${status.sessionId}, not ${sessionId}`);
266
+ if (status.state === "failed") throw coverageFailure(`ANIMUS_ANALYSIS_FAILED: the analysis covering ${filename} failed: ${status.diagnostic ?? "unknown analysis error"}`);
267
+ if (status.state === "idle" || !ACTIVE_STATES.has(status.state)) throw coverageFailure(`ANIMUS_ANALYSIS_NOT_SCHEDULED: ${filename} changed but no analysis attempt is scheduled for it (watcher miss?)`);
268
+ if (commitPresent) {
269
+ if (!(status.pending ?? []).some(([key, hash]) => key === filename && hash === sourceHash)) throw coverageFailure(`ANIMUS_ANALYSIS_NOT_SCHEDULED: the active analysis attempt (${status.attemptId}) has not observed ${filename} at its current content`);
270
+ }
271
+ const now = Date.now();
272
+ if (typeof status.deadlineAt === "number" && now > status.deadlineAt) throw coverageFailure(`ANIMUS_ANALYSIS_STALLED: analysis attempt ${status.attemptId} exceeded its deadline while ${filename} waited for coverage`);
273
+ if (now > ceiling) throw coverageFailure(`ANIMUS_ANALYSIS_CATCHING_UP: ${filename} changed after the committed analysis; timed out waiting for the commit to advance — retrying on the next invalidation`);
274
+ await sleep(poll);
275
+ poll = CATCHUP_POLL_MAX_MS;
276
+ }
88
277
  }
89
278
  //#endregion
90
- module.exports = animusTurbopackLoader;
279
+ exports.__resetTurbopackLoaderStateForTests = __resetTurbopackLoaderStateForTests;
280
+ exports.__setTurbopackLoaderFsForTests = __setTurbopackLoaderFsForTests;
281
+ exports.__toESM = __toESM;
282
+ exports.default = animusTurbopackLoader;
@@ -1,16 +1,39 @@
1
+ import * as nodeFs from 'fs';
1
2
  import type { LoaderPolicyOptions } from './loader-core';
3
+ /** JSON-serializable loader options (Turbopack forwards them across process
4
+ * boundaries). The session identity is a REAL task input (design D2):
5
+ * restarts mint a new session id, so cross-session cache reuse is
6
+ * impossible by construction — restart-cold is normative. */
7
+ export interface TurbopackLoaderOptions extends LoaderPolicyOptions {
8
+ rootDir?: string;
9
+ sessionId?: string;
10
+ sessionDir?: string;
11
+ }
12
+ type LoaderCallback = (err: Error | null, content?: string) => void;
2
13
  type LoaderContext = {
3
14
  resourcePath: string;
4
15
  rootContext: string;
5
- getOptions: () => LoaderPolicyOptions & {
6
- rootDir?: string;
7
- };
16
+ getOptions: () => TurbopackLoaderOptions;
17
+ /** File-dependency registration (Turbopack loader-runner IPC sends
18
+ * dependencies BEFORE the error check — throw-after-addDependency is a
19
+ * sound recovery primitive; see design D3). */
20
+ addDependency?: (file: string) => void;
21
+ /** Async-completion handle (webpack loader convention) — required: the
22
+ * evidence-based catch-up wait cannot run synchronously. */
23
+ async?: () => LoaderCallback;
8
24
  };
25
+ type LoaderFs = Pick<typeof nodeFs, 'readFileSync' | 'existsSync'>;
26
+ /** @internal test seam — pass null to restore the real fs. */
27
+ export declare function __setTurbopackLoaderFsForTests(fs: LoaderFs | null): void;
28
+ /** @internal test seam — drop worker-local hydration state. */
29
+ export declare function __resetTurbopackLoaderStateForTests(): void;
9
30
  /**
10
31
  * Turbopack loader for Animus source transformation. Derives everything
11
- * from the incoming source, its JSON-serializable options, and the
12
- * `.animus/` disk artifacts written by the out-of-band orchestrator.
32
+ * from the incoming source, its JSON-serializable options (carrying the
33
+ * session identity), and the session's COMMITTED disk artifacts written by
34
+ * the out-of-band orchestrator. Async by contract — the evidence-based
35
+ * catch-up (design D3) waits, bounded, for an imminent commit.
13
36
  */
14
- export default function animusTurbopackLoader(this: LoaderContext, source: string): string;
37
+ export default function animusTurbopackLoader(this: LoaderContext, source: string): void;
15
38
  export {};
16
39
  //# sourceMappingURL=turbopack-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"turbopack-loader.d.ts","sourceRoot":"","sources":["../src/turbopack-loader.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAMzD,KAAK,aAAa,GAAG;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,mBAAmB,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9D,CAAC;AAgGF;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAC3C,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GACb,MAAM,CAeR"}
1
+ {"version":3,"file":"turbopack-loader.d.ts","sourceRoot":"","sources":["../src/turbopack-loader.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,MAAM,IAAI,CAAC;AAc7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAOzD;;;8DAG8D;AAC9D,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,cAAc,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpE,KAAK,aAAa,GAAG;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,sBAAsB,CAAC;IACzC;;oDAEgD;IAChD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;iEAC6D;IAC7D,KAAK,CAAC,EAAE,MAAM,cAAc,CAAC;CAC9B,CAAC;AAgDF,KAAK,QAAQ,GAAG,IAAI,CAAC,OAAO,MAAM,EAAE,cAAc,GAAG,YAAY,CAAC,CAAC;AAGnE,8DAA8D;AAC9D,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAExE;AAMD,+DAA+D;AAC/D,wBAAgB,mCAAmC,IAAI,IAAI,CAK1D;AAkKD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAC3C,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GACb,IAAI,CAcN"}