@ttsc/unplugin 0.28.1 → 0.28.3

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.
Files changed (49) hide show
  1. package/README.md +3 -1
  2. package/lib/api.d.cts +8 -0
  3. package/lib/api.d.mts +8 -0
  4. package/lib/bun-register.d.cts +25 -0
  5. package/lib/bun-register.d.mts +25 -0
  6. package/lib/bun.d.cts +95 -0
  7. package/lib/bun.d.mts +95 -0
  8. package/lib/core/index.d.cts +23 -0
  9. package/lib/core/index.d.mts +23 -0
  10. package/lib/core/index.js +18 -1
  11. package/lib/core/index.js.map +1 -1
  12. package/lib/core/index.mjs +19 -2
  13. package/lib/core/index.mjs.map +1 -1
  14. package/lib/core/options.d.cts +54 -0
  15. package/lib/core/options.d.mts +54 -0
  16. package/lib/core/transform.d.cts +433 -0
  17. package/lib/core/transform.d.mts +433 -0
  18. package/lib/core/transform.d.ts +23 -4
  19. package/lib/core/transform.js +715 -117
  20. package/lib/core/transform.js.map +1 -1
  21. package/lib/core/transform.mjs +715 -117
  22. package/lib/core/transform.mjs.map +1 -1
  23. package/lib/core/tsconfigPaths.d.cts +29 -0
  24. package/lib/core/tsconfigPaths.d.mts +29 -0
  25. package/lib/core/viteServe.d.cts +81 -0
  26. package/lib/core/viteServe.d.mts +81 -0
  27. package/lib/esbuild.d.cts +3 -0
  28. package/lib/esbuild.d.mts +3 -0
  29. package/lib/farm.d.cts +3 -0
  30. package/lib/farm.d.mts +3 -0
  31. package/lib/index.d.cts +12 -0
  32. package/lib/index.d.mts +12 -0
  33. package/lib/next.d.cts +37 -0
  34. package/lib/next.d.mts +37 -0
  35. package/lib/rolldown.d.cts +3 -0
  36. package/lib/rolldown.d.mts +3 -0
  37. package/lib/rollup.d.cts +3 -0
  38. package/lib/rollup.d.mts +3 -0
  39. package/lib/rspack.d.cts +3 -0
  40. package/lib/rspack.d.mts +3 -0
  41. package/lib/turbopack.d.cts +58 -0
  42. package/lib/turbopack.d.mts +58 -0
  43. package/lib/vite.d.cts +3 -0
  44. package/lib/vite.d.mts +3 -0
  45. package/lib/webpack.d.cts +3 -0
  46. package/lib/webpack.d.mts +3 -0
  47. package/package.json +122 -17
  48. package/src/core/index.ts +18 -1
  49. package/src/core/transform.ts +1014 -139
package/README.md CHANGED
@@ -312,7 +312,9 @@ The transform host reports the program's reference graph (the transform envelope
312
312
 
313
313
  Transform plugins may additionally report, per file, the source files they consulted (the envelope's `dependencies` field); the adapter registers those as watch files too, union semantics. A plugin that declares such a list [complete](https://ttsc.dev/docs/development/concepts/protocol#dependency-completeness) for a file narrows the registration instead: only its own list plus the tsconfig chain, so files the transform never consulted stop invalidating it. The JavaScript host also reports the descriptor's loaded CommonJS graph, every package manifest inspected by auto-discovery, and plugin-declared `hostInputs`. First-party plugins use that last field for implicit config discovery and evaluated config dependencies. These remain universal without turning arbitrary project assets into configuration. Files a plugin declares `volatile` (output depending on non-file inputs such as environment or time) bypass the adapter's transform cache and are marked uncacheable where the bundler exposes that control.
314
314
 
315
- The transform cache snapshots every regular file reached by the non-following project walk plus graph-reported inputs outside that walk (`node_modules` declarations, monorepo sibling sources, files reached through symlinks or Windows junctions, and out-of-root `extends` ancestry). One whole-project compile already contains every module's output. Adapters with a guaranteed build boundary clear the previous generation there, then check only the supplied source on each module's first delivery from a complete generation in that build. An incomplete generation never takes that shortcut. Later deliveries of a graph-bearing generation validate that file's reference closure, globals, configs, resolution candidates, plugin dependencies, and exact host inputs. Every input a generation has already proven carries the nanosecond metadata signature captured around the read that proved it, so a later delivery that finds the signature unchanged reuses that proof instead of re-reading the bytes; any signature change falls back to the full content comparison. A signature is recorded only for a read nothing raced, only for an input whose recorded state came from bytes that were read, since the metadata of a path nothing could read holds still while the bytes behind it appear, and only once the observed filesystem's own clock has provably left the tick that minted the input's modification stamp. A filesystem stamps writes once per clock tick, so a same-length rewrite landing inside the recorded stamp's tick would leave the signature unchanged; until some stamp the same filesystem minted later separates that tick, the content comparison keeps running, and the signature is re-earned the moment it can be. The reference instants come from the filesystem itself, never from the process clock: every stamp the adapter observes raises a floor kept per reporting device, and the adapter also stamps a probe in its own scratch directory (the way git separates racily-clean index entries with the index file's own timestamp) to cover a tree whose files were all written inside one tick. The probe counts only when the scratch volume is the inputs' volume, so on a split-volume layout the observed stamps carry the rule alone; an input they cannot separate is re-read rather than trusted. Both sides of every comparison are therefore stamps of equal granularity minted by one clock, so a filesystem clock running behind the host changes nothing. The floor is a maximum over stamps it merely observed, so what defeats it is a stamp that was set into the future rather than minted, such as a stamp-preserving extraction or copy from a machine whose clock ran ahead. A restored past stamp is harmless, since it never raises the floor. A clock that jumps backwards strands the floor above the present the same way, which is a different hazard from a clock running at a constant offset: an offset moves both stamps being compared and changes nothing, while a jump moves only the present. No stamp-based freshness proof survives either one. Sibling modules therefore share one proof of the closure and the globals rather than repeating it per module. Generation-scoped directory notifications detect new, removed, or renamed project inputs without repeating a directory-stat pass per module; Windows isolates those notifications so deletion of a watched temporary tree cannot crash the host. An absent resolution candidate is the one input a metadata signature cannot stand for, since a path that is not there has no metadata to compare. The generation therefore registers those names with a watcher of their own, along with the directory components of the spelling that lead to them, down to the project's own root, and a delivery reads the notification instead of re-probing each candidate. That makes the notification the sole positive evidence for this one input class, which is why the registration reaches past the candidate itself: a watcher opened on a path that traverses a link follows it, so retargeting the link would otherwise move the answer without disturbing what is watched, and a package link inside `node_modules` is exactly that. It stops at the project root, and a candidate whose spelling leaves the project subtree before reaching it is not claimed at all: above that line the components are the machine's own layout rather than the project's, which nobody retargets and which changes for reasons no generation should hear about. It listens for renames alone, since every event that can change a candidate's answer is one — the file appearing, a component being created, replaced or retargeted — while the directories carrying them have their attributes moved by anything written below. A candidate whose watch could not be opened, whose watcher has since failed, or that belongs to a set spanning more directories than the generation will watch, is probed exactly as before: a host that runs out of watch descriptors would fail the tracker outright, which costs every delivery a whole-project comparison rather than one probe. What none of that covers is a filesystem that accepts a watch and then reports nothing, which some network mounts do; that is the same assumption the project-membership proof beside it already rests on, though a wider one here: the project walk skips `node_modules`, so a candidate under it was never covered by that proof and kept a notification-independent probe until now. A project on such a mount should be treated as one where notifications do not work rather than one where they merely have not fired. The barrier that lets a synchronous edit reach those watchers before a delivery reads their verdict is the watcher's own acknowledgement rather than a fixed wait: an in-process watcher answers on the next turn of the loop its callbacks are queued on, and the Windows broker answers by an ordered round-trip, so a delivery waits for the crossing instead of guessing at it. The project is snapshotted before and after compilation, while every graph member carries the compiler filesystem's content and physical-identity proof; only a complete generation that still matches both may authorize narrow reuse. This also rejects an in-project or external A→B→A change whose restored post-compile bytes would otherwise hide the transient state used by the compiler. `dependenciesComplete` narrows file inputs by the same contract as watch registration, and a project reaches it without a type-driven plugin: ttsc's own transform lanes print syntactically, so the host declares every file complete when no plugin can contribute to it, and the first-party `@ttsc/banner` and `@ttsc/strip` declare their own contribution the same way. Such a delivery validates the universal inputs and the file itself instead of its reference closure. A narrowed file's diagnostics narrow with it: an edit to a file the declaration dropped no longer re-runs the compile, so a type error introduced there surfaces at the next compile the build runs for another reason rather than at the edit. Run the compiler's own check beside the bundler when you want it at the edit. Envelopes without a graph, and generations whose watchers could not be opened or have since failed, retain complete-snapshot validation: losing a notification is the absence of a membership proof rather than evidence of a change, so such a generation keeps validating against its own recorded state (the directory snapshot, the input hashes, and the universal descriptor inputs whose every rejection is evidence of a change rather than an inability to prove one) instead of being discarded. A reported membership event is evidence, and still replaces the generation. Only an unstable or incomplete generation is refused outright. Bun runtime setup defines one process-scoped loading session. A dev server started without a watcher takes the build-scoped lifecycle instead of persistent validation. `server.watch: null` leaves the session no channel through which a file change could ever reach it, so what per-delivery validation buys there is incoherence rather than freshness: modules delivered before an edit and after it would come from two different compilations of one program. Each module's first delivery in such a session is therefore settled against the generation the session started from, exactly as under a build, while a module the session already delivered keeps revalidating on its next request. Long-lived hosts without either boundary (Metro workers, the Turbopack loader, and a watching Vite development server, whose initial `buildStart` spans later HMR edits) apply the file-specific validation on every generation hit.
315
+ The transform cache snapshots every regular file reached by the non-following project walk plus graph-reported inputs outside that walk (`node_modules` declarations, monorepo sibling sources, files reached through symlinks or Windows junctions, and out-of-root `extends` ancestry). One whole-project compile already contains every module's output. Adapters with a guaranteed build boundary clear the previous generation there, then check only the supplied source on each module's first delivery from a complete generation in that build. An incomplete generation never takes that shortcut. Later deliveries of a graph-bearing generation validate that file's reference closure, globals, configs, resolution candidates, plugin dependencies, and exact host inputs. Every input a generation has already proven carries the nanosecond metadata signature captured around the read that proved it, so a later delivery that finds the signature unchanged reuses that proof instead of re-reading the bytes; any signature change falls back to the full content comparison. A signature is recorded only for a read nothing raced, only for an input whose recorded state came from bytes that were read, since the metadata of a path nothing could read holds still while the bytes behind it appear, and only once the observed filesystem's own clock has provably left the tick that minted the input's modification stamp. A filesystem stamps writes once per clock tick, so a same-length rewrite landing inside the recorded stamp's tick would leave the signature unchanged; until some stamp the same filesystem minted later separates that tick, the content comparison keeps running, and the signature is re-earned the moment it can be. The reference instants come from the filesystem itself, never from the process clock: every stamp the adapter observes raises a floor kept per reporting device, and the adapter also stamps a probe in its own scratch directory (the way git separates racily-clean index entries with the index file's own timestamp) to cover a tree whose files were all written inside one tick. The probe counts only when the scratch volume is the inputs' volume, so on a split-volume layout the observed stamps carry the rule alone; an input they cannot separate is re-read rather than trusted. Both sides of every comparison are therefore stamps of equal granularity minted by one clock, so a filesystem clock running behind the host changes nothing. The floor is a maximum over stamps it merely observed, so what defeats it is a stamp that was set into the future rather than minted, such as a stamp-preserving extraction or copy from a machine whose clock ran ahead. A restored past stamp is harmless, since it never raises the floor. A clock that jumps backwards strands the floor above the present the same way, which is a different hazard from a clock running at a constant offset: an offset moves both stamps being compared and changes nothing, while a jump moves only the present. No stamp-based freshness proof survives either one. Sibling modules therefore share one proof of the closure and the globals rather than repeating it per module. Generation-scoped directory notifications detect new, removed, or renamed project inputs without repeating a directory-stat pass per module; Windows isolates those notifications so deletion of a watched temporary tree cannot crash the host. An absent resolution candidate is the one input a metadata signature cannot stand for, since a path that is not there has no metadata to compare. The generation therefore registers those names with a watcher of their own, along with the directory components of the spelling that lead to them, down to the project's own root, and a delivery reads the notification instead of re-probing each candidate. That makes the notification the sole positive evidence for this one input class, which is why the registration reaches past the candidate itself: a watcher opened on a path that traverses a link follows it, so retargeting the link would otherwise move the answer without disturbing what is watched, and a package link inside `node_modules` is exactly that. It stops at the project root, and a candidate whose spelling leaves the project subtree before reaching it is not claimed at all: above that line the components are the machine's own layout rather than the project's, which nobody retargets and which changes for reasons no generation should hear about. It listens for renames alone, since every event that can change a candidate's answer is one — the file appearing, a component being created, replaced or retargeted — while the directories carrying them have their attributes moved by anything written below. A candidate whose watch could not be opened, whose watcher has since failed, or that belongs to a set spanning more directories than the generation will watch, is probed exactly as before: a host that runs out of watch descriptors would fail the tracker outright, which costs every delivery a whole-project comparison rather than one probe. What none of that covers is a filesystem that accepts a watch and then reports nothing, which some network mounts do; that is the same assumption the project-membership proof beside it already rests on, though a wider one here: the project walk skips `node_modules`, so a candidate under it was never covered by that proof and kept a notification-independent probe until now. A project on such a mount should be treated as one where notifications do not work rather than one where they merely have not fired. The barrier that lets a synchronous edit reach those watchers before a delivery reads their verdict is the watcher's own acknowledgement rather than a fixed wait: an in-process watcher answers on the next turn of the loop its callbacks are queued on, and the Windows broker answers by an ordered round-trip, so a delivery waits for the crossing instead of guessing at it. The project is snapshotted before and after compilation, while every graph member carries the compiler filesystem's content and physical-identity proof; only a complete generation that still matches both may authorize narrow reuse. This also rejects an in-project or external A→B→A change whose restored post-compile bytes would otherwise hide the transient state used by the compiler. `dependenciesComplete` narrows file inputs by the same contract as watch registration, and a project reaches it without a type-driven plugin: ttsc's own transform lanes print syntactically, so the host declares every file complete when no plugin can contribute to it, and the first-party `@ttsc/banner` and `@ttsc/strip` declare their own contribution the same way. Such a delivery validates the universal inputs and the file itself instead of its reference closure. A narrowed file's diagnostics narrow with it: an edit to a file the declaration dropped no longer re-runs the compile, so a type error introduced there surfaces at the next compile the build runs for another reason rather than at the edit. Run the compiler's own check beside the bundler when you want it at the edit. Envelopes without a graph, and generations whose watchers could not be opened or have since failed, retain complete-snapshot validation: losing a notification is the absence of a membership proof rather than evidence of a change, so such a generation keeps validating against its own recorded state (the directory snapshot, the input hashes, and the universal descriptor inputs whose every rejection is evidence of a change rather than an inability to prove one) instead of being discarded. A reported membership event is evidence, and still replaces the generation. For a caching host, an unstable or incomplete attempt is never published: all waiters share one bounded stabilization retry inside the same generation. A stable retry alone resolves and is reused; a second unprovable attempt rejects the shared generation with bounded path-and-proof witnesses. That terminal verdict stays cached while its project, external, and host-input fingerprints remain unchanged, so later request waves pay only the confirmation probes and the compile count is independent of module count. An input change or an explicit cache lifecycle reset permits a new generation. One-shot calls without a cache retain their single compile. Bun runtime setup defines one process-scoped loading session. A dev server started without a watcher takes the build-scoped lifecycle instead of persistent validation. `server.watch: null` leaves the session no channel through which a file change could ever reach it, so what per-delivery validation buys there is incoherence rather than freshness: modules delivered before an edit and after it would come from two different compilations of one program. Each module's first delivery in such a session is therefore settled against the generation the session started from, exactly as under a build, while a module the session already delivered keeps revalidating on its next request. Long-lived hosts without either boundary (Metro workers, the Turbopack loader, and a watching Vite development server, whose initial `buildStart` spans later HMR edits) apply the file-specific validation on every generation hit.
316
+
317
+ Transform source outputs outside the project walk are kept in that external snapshot too. A non-declaration output is reusable only when the graph carries its compiler-time content and physical-identity proof; a post-compile disk read cannot prove which bytes produced the output. Its source hash then lets sibling module requests validate the same generation without adding arbitrary output keys to the project-file universe.
316
318
 
317
319
  ## Sponsors
318
320
 
package/lib/api.d.cts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Public API surface for `@ttsc/unplugin`.
3
+ *
4
+ * Re-exports everything from `core/index` so consumers can access the unified
5
+ * `unplugin` instance, the transform helpers, and the option types from the
6
+ * `@ttsc/unplugin/api` entry point without importing directly from `core/`.
7
+ */
8
+ export * from "./core/index.cjs";
package/lib/api.d.mts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Public API surface for `@ttsc/unplugin`.
3
+ *
4
+ * Re-exports everything from `core/index` so consumers can access the unified
5
+ * `unplugin` instance, the transform helpers, and the option types from the
6
+ * `@ttsc/unplugin/api` entry point without importing directly from `core/`.
7
+ */
8
+ export * from "./core/index.mjs";
@@ -0,0 +1,25 @@
1
+ import type { TtscUnpluginOptions } from "./core/options.cjs";
2
+ /**
3
+ * Register the ttsc transform as a Bun **runtime** plugin.
4
+ *
5
+ * The other `@ttsc/unplugin/*` adapters cover bundlers (`Bun.build`, Vite,
6
+ * Webpack, …). This entry is the runtime counterpart: loading it registers the
7
+ * same transform on Bun's module loader, so `bun run` / `bun test` apply ttsc
8
+ * plugins (e.g. typia's `typia/lib/transform`) as files are imported, with no
9
+ * bundling step. Wire it up once via a `bunfig.toml` preload entry — `preload =
10
+ * ["@ttsc/unplugin/bun-register"]` — or imperatively with `import
11
+ * "@ttsc/unplugin/bun-register"`. Options are read from the nearest
12
+ * `tsconfig.json`, identical to the bundler adapters.
13
+ *
14
+ * Registration is idempotent: the first call (implicit on import, or explicit)
15
+ * registers the one loader with Bun; later calls only update the effective
16
+ * options, so accessing the explicit API cannot install a second default loader
17
+ * that shadows the caller's configuration. Repeated explicit calls are
18
+ * last-write-wins for the effective options.
19
+ *
20
+ * @throws When called explicitly off the Bun runtime (`globalThis.Bun.plugin`
21
+ * is unavailable). The auto-registration below stays silent off Bun so the
22
+ * module is harmless to import from Node (tests, tooling).
23
+ */
24
+ export declare function register(options?: TtscUnpluginOptions): void;
25
+ export default register;
@@ -0,0 +1,25 @@
1
+ import type { TtscUnpluginOptions } from "./core/options.mjs";
2
+ /**
3
+ * Register the ttsc transform as a Bun **runtime** plugin.
4
+ *
5
+ * The other `@ttsc/unplugin/*` adapters cover bundlers (`Bun.build`, Vite,
6
+ * Webpack, …). This entry is the runtime counterpart: loading it registers the
7
+ * same transform on Bun's module loader, so `bun run` / `bun test` apply ttsc
8
+ * plugins (e.g. typia's `typia/lib/transform`) as files are imported, with no
9
+ * bundling step. Wire it up once via a `bunfig.toml` preload entry — `preload =
10
+ * ["@ttsc/unplugin/bun-register"]` — or imperatively with `import
11
+ * "@ttsc/unplugin/bun-register"`. Options are read from the nearest
12
+ * `tsconfig.json`, identical to the bundler adapters.
13
+ *
14
+ * Registration is idempotent: the first call (implicit on import, or explicit)
15
+ * registers the one loader with Bun; later calls only update the effective
16
+ * options, so accessing the explicit API cannot install a second default loader
17
+ * that shadows the caller's configuration. Repeated explicit calls are
18
+ * last-write-wins for the effective options.
19
+ *
20
+ * @throws When called explicitly off the Bun runtime (`globalThis.Bun.plugin`
21
+ * is unavailable). The auto-registration below stays silent off Bun so the
22
+ * module is harmless to import from Node (tests, tooling).
23
+ */
24
+ export declare function register(options?: TtscUnpluginOptions): void;
25
+ export default register;
package/lib/bun.d.cts ADDED
@@ -0,0 +1,95 @@
1
+ import type { TtscUnpluginOptions } from "./core/options.cjs";
2
+ /**
3
+ * Minimal subset of the Bun plugin API consumed by this adapter.
4
+ *
5
+ * Bun does not yet ship TypeScript types for its bundler plugin interface, so
6
+ * we define the subset we need here. This keeps the adapter free of a Bun
7
+ * runtime dependency while remaining type-safe.
8
+ */
9
+ export interface BunLikePlugin {
10
+ /** Plugin identifier shown in Bun bundler output. */
11
+ name: string;
12
+ /** Called by Bun when the plugin is registered. */
13
+ setup(build: BunLikeBuild): void | Promise<void>;
14
+ }
15
+ /** Bun loader identifiers this adapter can emit (only TypeScript is matched). */
16
+ export type BunLoader = "ts" | "tsx";
17
+ /**
18
+ * Options accepted by {@link bun}, either resolved eagerly or supplied through a
19
+ * provider evaluated lazily on the first `onLoad` call.
20
+ *
21
+ * The provider form exists for the runtime registration path (`bun-register`),
22
+ * where a single Bun plugin is registered on import but its effective options
23
+ * may be overridden by an explicit `register(options)` call made in the same
24
+ * synchronous tick. Resolving through the provider on first load, rather than
25
+ * at registration, lets that later call win without Bun ever seeing a second
26
+ * shadowing loader.
27
+ */
28
+ export type TtscBunOptions = TtscUnpluginOptions | (() => TtscUnpluginOptions | undefined);
29
+ /**
30
+ * Minimal subset of the Bun `BuildConfig` plugin build object.
31
+ *
32
+ * `onLoad` drives the source transform. Bun's bundler also exposes `onStart`,
33
+ * which is used when available to forward the shared plugin's build lifecycle
34
+ * and clear its per-build cache. The runtime plugin API omits that hook, so
35
+ * plugin setup itself starts its one process-scoped module-loading session.
36
+ */
37
+ export interface BunLikeBuild {
38
+ /**
39
+ * Build configuration exposed unchanged by Bun's bundler plugin builder.
40
+ *
41
+ * Runtime plugin builders do not supply `files`. Bun's bundler accepts an
42
+ * in-memory file map whose values deliberately remain `unknown` here because
43
+ * this adapter only needs to preserve ownership, not consume their contents.
44
+ */
45
+ config?: {
46
+ files?: Readonly<Record<string, unknown>>;
47
+ };
48
+ /**
49
+ * Register a callback for the start of a bundler build.
50
+ *
51
+ * Optional because `Bun.plugin()` runtime builders do not expose this hook.
52
+ */
53
+ onStart?(callback: () => void | Promise<void>): void;
54
+ /**
55
+ * Register a loader callback for files matching `filter`.
56
+ *
57
+ * The callback receives the file path and must return the transformed file
58
+ * contents plus the `loader` Bun should apply next. Configured in-memory
59
+ * files retain relative key spellings; ordinary disk files are normally
60
+ * absolute. The `loader` matters most for the runtime path (`Bun.plugin`),
61
+ * where Bun must be told the returned contents are still TypeScript so it
62
+ * keeps transpiling them before execution.
63
+ */
64
+ onLoad(options: {
65
+ filter: RegExp;
66
+ }, loader: (args: {
67
+ path: string;
68
+ }) => Promise<{
69
+ contents: string;
70
+ loader: BunLoader;
71
+ } | undefined>): void;
72
+ }
73
+ /**
74
+ * Create a ttsc plugin for Bun's bundler AND runtime.
75
+ *
76
+ * Bun does not implement the unplugin protocol, so this adapter wires the
77
+ * shared ttsc transform core to Bun's `onLoad` hook directly. It reads each
78
+ * included file from disk and forwards the content to the transform. Under
79
+ * `Bun.build`, excluded files and no-op transforms return `undefined` so the
80
+ * next loader retains ownership. Entries supplied through `BuildConfig.files`
81
+ * also stay with Bun's in-memory loader: they are not filesystem project inputs
82
+ * and reading the same path from disk would either fail or silently replace the
83
+ * configured contents. The runtime `Bun.plugin()` API rejects an undefined
84
+ * `onLoad` result, so that path explicitly returns the original source and
85
+ * loader instead.
86
+ *
87
+ * The same object works for `Bun.build({ plugins: [ttsc()] })` (bundler) and
88
+ * for `Bun.plugin(ttsc())` / a `bunfig.toml` preload (runtime) — see
89
+ * `bun-register`. Every result carries an explicit `loader` so Bun keeps
90
+ * transpiling the emitted TypeScript at runtime; `bunSourceFilePattern` only
91
+ * matches TypeScript, so the loader is always `ts`/`tsx`. A runtime plugin
92
+ * instance is one immutable load session, like Bun's own module cache; restart
93
+ * the process after changing compiler inputs.
94
+ */
95
+ export default function bun(options?: TtscBunOptions): BunLikePlugin;
package/lib/bun.d.mts ADDED
@@ -0,0 +1,95 @@
1
+ import type { TtscUnpluginOptions } from "./core/options.mjs";
2
+ /**
3
+ * Minimal subset of the Bun plugin API consumed by this adapter.
4
+ *
5
+ * Bun does not yet ship TypeScript types for its bundler plugin interface, so
6
+ * we define the subset we need here. This keeps the adapter free of a Bun
7
+ * runtime dependency while remaining type-safe.
8
+ */
9
+ export interface BunLikePlugin {
10
+ /** Plugin identifier shown in Bun bundler output. */
11
+ name: string;
12
+ /** Called by Bun when the plugin is registered. */
13
+ setup(build: BunLikeBuild): void | Promise<void>;
14
+ }
15
+ /** Bun loader identifiers this adapter can emit (only TypeScript is matched). */
16
+ export type BunLoader = "ts" | "tsx";
17
+ /**
18
+ * Options accepted by {@link bun}, either resolved eagerly or supplied through a
19
+ * provider evaluated lazily on the first `onLoad` call.
20
+ *
21
+ * The provider form exists for the runtime registration path (`bun-register`),
22
+ * where a single Bun plugin is registered on import but its effective options
23
+ * may be overridden by an explicit `register(options)` call made in the same
24
+ * synchronous tick. Resolving through the provider on first load, rather than
25
+ * at registration, lets that later call win without Bun ever seeing a second
26
+ * shadowing loader.
27
+ */
28
+ export type TtscBunOptions = TtscUnpluginOptions | (() => TtscUnpluginOptions | undefined);
29
+ /**
30
+ * Minimal subset of the Bun `BuildConfig` plugin build object.
31
+ *
32
+ * `onLoad` drives the source transform. Bun's bundler also exposes `onStart`,
33
+ * which is used when available to forward the shared plugin's build lifecycle
34
+ * and clear its per-build cache. The runtime plugin API omits that hook, so
35
+ * plugin setup itself starts its one process-scoped module-loading session.
36
+ */
37
+ export interface BunLikeBuild {
38
+ /**
39
+ * Build configuration exposed unchanged by Bun's bundler plugin builder.
40
+ *
41
+ * Runtime plugin builders do not supply `files`. Bun's bundler accepts an
42
+ * in-memory file map whose values deliberately remain `unknown` here because
43
+ * this adapter only needs to preserve ownership, not consume their contents.
44
+ */
45
+ config?: {
46
+ files?: Readonly<Record<string, unknown>>;
47
+ };
48
+ /**
49
+ * Register a callback for the start of a bundler build.
50
+ *
51
+ * Optional because `Bun.plugin()` runtime builders do not expose this hook.
52
+ */
53
+ onStart?(callback: () => void | Promise<void>): void;
54
+ /**
55
+ * Register a loader callback for files matching `filter`.
56
+ *
57
+ * The callback receives the file path and must return the transformed file
58
+ * contents plus the `loader` Bun should apply next. Configured in-memory
59
+ * files retain relative key spellings; ordinary disk files are normally
60
+ * absolute. The `loader` matters most for the runtime path (`Bun.plugin`),
61
+ * where Bun must be told the returned contents are still TypeScript so it
62
+ * keeps transpiling them before execution.
63
+ */
64
+ onLoad(options: {
65
+ filter: RegExp;
66
+ }, loader: (args: {
67
+ path: string;
68
+ }) => Promise<{
69
+ contents: string;
70
+ loader: BunLoader;
71
+ } | undefined>): void;
72
+ }
73
+ /**
74
+ * Create a ttsc plugin for Bun's bundler AND runtime.
75
+ *
76
+ * Bun does not implement the unplugin protocol, so this adapter wires the
77
+ * shared ttsc transform core to Bun's `onLoad` hook directly. It reads each
78
+ * included file from disk and forwards the content to the transform. Under
79
+ * `Bun.build`, excluded files and no-op transforms return `undefined` so the
80
+ * next loader retains ownership. Entries supplied through `BuildConfig.files`
81
+ * also stay with Bun's in-memory loader: they are not filesystem project inputs
82
+ * and reading the same path from disk would either fail or silently replace the
83
+ * configured contents. The runtime `Bun.plugin()` API rejects an undefined
84
+ * `onLoad` result, so that path explicitly returns the original source and
85
+ * loader instead.
86
+ *
87
+ * The same object works for `Bun.build({ plugins: [ttsc()] })` (bundler) and
88
+ * for `Bun.plugin(ttsc())` / a `bunfig.toml` preload (runtime) — see
89
+ * `bun-register`. Every result carries an explicit `loader` so Bun keeps
90
+ * transpiling the emitted TypeScript at runtime; `bunSourceFilePattern` only
91
+ * matches TypeScript, so the loader is always `ts`/`tsx`. A runtime plugin
92
+ * instance is one immutable load session, like Bun's own module cache; restart
93
+ * the process after changing compiler inputs.
94
+ */
95
+ export default function bun(options?: TtscBunOptions): BunLikePlugin;
@@ -0,0 +1,23 @@
1
+ import type { UnpluginInstance } from "unplugin";
2
+ import type { TtscUnpluginOptions } from "./options.cjs";
3
+ import { resolveOptions } from "./options.cjs";
4
+ import { beginTtscTransformBuild, collectExternalInputHashes, collectProjectInputHashes, createTtscTransformCache, isProjectWalkPath, resetTtscTransformCache, transformTtsc } from "./transform.cjs";
5
+ /**
6
+ * Matches any TypeScript or JavaScript source extension (.ts, .tsx, .mts, .cts,
7
+ * etc.). Shared with the Bun adapter (`bun.ts`) so the filter is defined once
8
+ * and both adapters stay in sync.
9
+ */
10
+ export declare const sourceFilePattern: RegExp;
11
+ declare const unplugin: UnpluginInstance<TtscUnpluginOptions | undefined, false>;
12
+ export type { TtscUnpluginCompilerOptionsJson, TtscUnpluginOptions, } from "./options.cjs";
13
+ export type { TtscTransformFilesystemOperations, TtscTransformHooks, TtscWatchInputEvidence, } from "./transform.cjs";
14
+ export { beginTtscTransformBuild, collectExternalInputHashes, collectProjectInputHashes, createTtscTransformCache, isProjectWalkPath, resetTtscTransformCache, resolveOptions, transformTtsc, unplugin, };
15
+ export default unplugin;
16
+ /**
17
+ * Returns `true` when the module id refers to a real TypeScript/JavaScript
18
+ * source file that should be processed by the ttsc transform.
19
+ *
20
+ * Excluded ids: virtual modules (NUL prefix), `.d.ts` declaration files, and
21
+ * anything inside `node_modules`.
22
+ */
23
+ export declare function isTransformTarget(id: string): boolean;
@@ -0,0 +1,23 @@
1
+ import type { UnpluginInstance } from "unplugin";
2
+ import type { TtscUnpluginOptions } from "./options.mjs";
3
+ import { resolveOptions } from "./options.mjs";
4
+ import { beginTtscTransformBuild, collectExternalInputHashes, collectProjectInputHashes, createTtscTransformCache, isProjectWalkPath, resetTtscTransformCache, transformTtsc } from "./transform.mjs";
5
+ /**
6
+ * Matches any TypeScript or JavaScript source extension (.ts, .tsx, .mts, .cts,
7
+ * etc.). Shared with the Bun adapter (`bun.ts`) so the filter is defined once
8
+ * and both adapters stay in sync.
9
+ */
10
+ export declare const sourceFilePattern: RegExp;
11
+ declare const unplugin: UnpluginInstance<TtscUnpluginOptions | undefined, false>;
12
+ export type { TtscUnpluginCompilerOptionsJson, TtscUnpluginOptions, } from "./options.mjs";
13
+ export type { TtscTransformFilesystemOperations, TtscTransformHooks, TtscWatchInputEvidence, } from "./transform.mjs";
14
+ export { beginTtscTransformBuild, collectExternalInputHashes, collectProjectInputHashes, createTtscTransformCache, isProjectWalkPath, resetTtscTransformCache, resolveOptions, transformTtsc, unplugin, };
15
+ export default unplugin;
16
+ /**
17
+ * Returns `true` when the module id refers to a real TypeScript/JavaScript
18
+ * source file that should be processed by the ttsc transform.
19
+ *
20
+ * Excluded ids: virtual modules (NUL prefix), `.d.ts` declaration files, and
21
+ * anything inside `node_modules`.
22
+ */
23
+ export declare function isTransformTarget(id: string): boolean;
package/lib/core/index.js CHANGED
@@ -42,6 +42,12 @@ const unpluginFactory = (rawOptions = {}) => {
42
42
  let aliases;
43
43
  let viteCommand;
44
44
  let viteWatching = true;
45
+ // A restart can start the replacement plugin container before closing the
46
+ // old one, and Vite calls buildEnd even for a container that never started.
47
+ // Track the stable per-container PluginContext identity so that unstarted
48
+ // old containers cannot dispose a replacement's freshly initialized cache.
49
+ const viteBuildOwners = new WeakSet();
50
+ let viteBuildLifecycles = 0;
45
51
  return {
46
52
  name,
47
53
  enforce: "pre",
@@ -74,12 +80,23 @@ const unpluginFactory = (rawOptions = {}) => {
74
80
  missingInputs.attach(server);
75
81
  },
76
82
  // Vite calls buildEnd when the dev server (or build) closes; drop every
77
- // poller so a stopped server leaks no watch state.
83
+ // poller and, once the last overlapping container has closed, every
84
+ // generation-owned filesystem tracker as well.
78
85
  buildEnd() {
79
86
  missingInputs.dispose();
87
+ if (viteBuildOwners.delete(this)) {
88
+ viteBuildLifecycles -= 1;
89
+ }
90
+ if (viteBuildLifecycles === 0) {
91
+ transform.resetTtscTransformCache(transformCache);
92
+ }
80
93
  },
81
94
  },
82
95
  buildStart() {
96
+ if (viteCommand !== undefined && !viteBuildOwners.has(this)) {
97
+ viteBuildOwners.add(this);
98
+ viteBuildLifecycles += 1;
99
+ }
83
100
  // Persistent validation exists for a session that spans edits it can
84
101
  // observe, and a dev server told to open no watcher is not one:
85
102
  // `server.watch: null` leaves Vite with no change channel at all, so no
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/core/index.ts"],"sourcesContent":[null],"names":["options","resolveOptions","createTtscTransformCache","createViteServeMissingInputWatch","resetTtscTransformCache","beginTtscTransformBuild","stripQuery","transformTtsc","createUnplugin","isDeclarationFile"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/core/index.ts"],"sourcesContent":[null],"names":["options","resolveOptions","createTtscTransformCache","createViteServeMissingInputWatch","resetTtscTransformCache","beginTtscTransformBuild","stripQuery","transformTtsc","createUnplugin","isDeclarationFile"],"mappings":"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"}
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createUnplugin } from 'unplugin';
4
4
  import { resolveOptions } from './options.mjs';
5
- import { createTtscTransformCache, stripQuery, transformTtsc, beginTtscTransformBuild, isDeclarationFile, resetTtscTransformCache } from './transform.mjs';
5
+ import { createTtscTransformCache, stripQuery, transformTtsc, beginTtscTransformBuild, resetTtscTransformCache, isDeclarationFile } from './transform.mjs';
6
6
  export { collectExternalInputHashes, collectProjectInputHashes, isProjectWalkPath } from './transform.mjs';
7
7
  import { createViteServeMissingInputWatch } from './viteServe.mjs';
8
8
 
@@ -39,6 +39,12 @@ const unpluginFactory = (rawOptions = {}) => {
39
39
  let aliases;
40
40
  let viteCommand;
41
41
  let viteWatching = true;
42
+ // A restart can start the replacement plugin container before closing the
43
+ // old one, and Vite calls buildEnd even for a container that never started.
44
+ // Track the stable per-container PluginContext identity so that unstarted
45
+ // old containers cannot dispose a replacement's freshly initialized cache.
46
+ const viteBuildOwners = new WeakSet();
47
+ let viteBuildLifecycles = 0;
42
48
  return {
43
49
  name,
44
50
  enforce: "pre",
@@ -71,12 +77,23 @@ const unpluginFactory = (rawOptions = {}) => {
71
77
  missingInputs.attach(server);
72
78
  },
73
79
  // Vite calls buildEnd when the dev server (or build) closes; drop every
74
- // poller so a stopped server leaks no watch state.
80
+ // poller and, once the last overlapping container has closed, every
81
+ // generation-owned filesystem tracker as well.
75
82
  buildEnd() {
76
83
  missingInputs.dispose();
84
+ if (viteBuildOwners.delete(this)) {
85
+ viteBuildLifecycles -= 1;
86
+ }
87
+ if (viteBuildLifecycles === 0) {
88
+ resetTtscTransformCache(transformCache);
89
+ }
77
90
  },
78
91
  },
79
92
  buildStart() {
93
+ if (viteCommand !== undefined && !viteBuildOwners.has(this)) {
94
+ viteBuildOwners.add(this);
95
+ viteBuildLifecycles += 1;
96
+ }
80
97
  // Persistent validation exists for a session that spans edits it can
81
98
  // observe, and a dev server told to open no watcher is not one:
82
99
  // `server.watch: null` leaves Vite with no change channel at all, so no
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/core/index.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../../src/core/index.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { ITtscProjectPluginConfig } from "ttsc";
2
+ /** Raw compiler-options overlay supplied by the caller as a plain JSON value. */
3
+ export type TtscUnpluginCompilerOptionsJson = Record<string, unknown>;
4
+ /** Options accepted by the `@ttsc/unplugin` bundler adapter. */
5
+ export interface TtscUnpluginOptions {
6
+ /**
7
+ * Project config used by the bundler adapter.
8
+ *
9
+ * Relative paths resolve from `process.cwd()`. When omitted, the nearest
10
+ * `tsconfig.json` is discovered from the transformed file.
11
+ */
12
+ project?: string;
13
+ /**
14
+ * Compiler options overlaid on top of the selected project config.
15
+ *
16
+ * This can include `plugins`; `plugins` passed at the top level still wins as
17
+ * the explicit plugin override.
18
+ */
19
+ compilerOptions?: TtscUnpluginCompilerOptionsJson;
20
+ /**
21
+ * `ttsc` plugin entries.
22
+ *
23
+ * `undefined` reads project plugins from `compilerOptions.plugins` and
24
+ * directly installed package markers, `false` disables project plugins, and
25
+ * an array overrides the project plugin list.
26
+ */
27
+ plugins?: readonly ITtscProjectPluginConfig[] | false;
28
+ }
29
+ /**
30
+ * Fully-resolved plugin options with all defaults applied.
31
+ *
32
+ * Produced by {@link resolveOptions}; consumed internally by the transform
33
+ * pipeline. Every field is present and normalised; callers should not construct
34
+ * this type directly.
35
+ */
36
+ export interface ResolvedTtscUnpluginOptions {
37
+ /** Compiler-options overlay applied on top of the discovered tsconfig. */
38
+ compilerOptions: TtscUnpluginCompilerOptionsJson;
39
+ /**
40
+ * Resolved plugin list; mirrors the semantics of
41
+ * {@link TtscUnpluginOptions.plugins}.
42
+ */
43
+ plugins?: readonly ITtscProjectPluginConfig[] | false;
44
+ /** Resolved path to the project tsconfig, or `undefined` to auto-discover. */
45
+ project?: string;
46
+ }
47
+ /**
48
+ * Normalise raw user-supplied options into {@link ResolvedTtscUnpluginOptions}.
49
+ *
50
+ * Merges provided values with defaults. The `plugins` field uses an explicit
51
+ * `"plugins" in options` presence check rather than a falsy guard so that
52
+ * `plugins: false` (disable all plugins) is preserved as-is.
53
+ */
54
+ export declare function resolveOptions(options?: TtscUnpluginOptions): ResolvedTtscUnpluginOptions;
@@ -0,0 +1,54 @@
1
+ import type { ITtscProjectPluginConfig } from "ttsc";
2
+ /** Raw compiler-options overlay supplied by the caller as a plain JSON value. */
3
+ export type TtscUnpluginCompilerOptionsJson = Record<string, unknown>;
4
+ /** Options accepted by the `@ttsc/unplugin` bundler adapter. */
5
+ export interface TtscUnpluginOptions {
6
+ /**
7
+ * Project config used by the bundler adapter.
8
+ *
9
+ * Relative paths resolve from `process.cwd()`. When omitted, the nearest
10
+ * `tsconfig.json` is discovered from the transformed file.
11
+ */
12
+ project?: string;
13
+ /**
14
+ * Compiler options overlaid on top of the selected project config.
15
+ *
16
+ * This can include `plugins`; `plugins` passed at the top level still wins as
17
+ * the explicit plugin override.
18
+ */
19
+ compilerOptions?: TtscUnpluginCompilerOptionsJson;
20
+ /**
21
+ * `ttsc` plugin entries.
22
+ *
23
+ * `undefined` reads project plugins from `compilerOptions.plugins` and
24
+ * directly installed package markers, `false` disables project plugins, and
25
+ * an array overrides the project plugin list.
26
+ */
27
+ plugins?: readonly ITtscProjectPluginConfig[] | false;
28
+ }
29
+ /**
30
+ * Fully-resolved plugin options with all defaults applied.
31
+ *
32
+ * Produced by {@link resolveOptions}; consumed internally by the transform
33
+ * pipeline. Every field is present and normalised; callers should not construct
34
+ * this type directly.
35
+ */
36
+ export interface ResolvedTtscUnpluginOptions {
37
+ /** Compiler-options overlay applied on top of the discovered tsconfig. */
38
+ compilerOptions: TtscUnpluginCompilerOptionsJson;
39
+ /**
40
+ * Resolved plugin list; mirrors the semantics of
41
+ * {@link TtscUnpluginOptions.plugins}.
42
+ */
43
+ plugins?: readonly ITtscProjectPluginConfig[] | false;
44
+ /** Resolved path to the project tsconfig, or `undefined` to auto-discover. */
45
+ project?: string;
46
+ }
47
+ /**
48
+ * Normalise raw user-supplied options into {@link ResolvedTtscUnpluginOptions}.
49
+ *
50
+ * Merges provided values with defaults. The `plugins` field uses an explicit
51
+ * `"plugins" in options` presence check rather than a falsy guard so that
52
+ * `plugins: false` (disable all plugins) is preserved as-is.
53
+ */
54
+ export declare function resolveOptions(options?: TtscUnpluginOptions): ResolvedTtscUnpluginOptions;