/** * ESC-1 - managed compiler-plugin contributions (RFC 0006 / `docs/solid` ESC-1 reduced checkpoint). * * THE PROBLEM. A preconstructed React/Solid Vite plugin cannot be globally scoped: `vite-plugin-solid` * (and `@vitejs/plugin-react`) capture their `createFilter` at CONSTRUCTION, and the correct scope for * a file - "the files this framework owns MINUS every other framework's files" - only exists after the * host has seen ALL configured apps. So a renderer hands the host a LAZY, branded contribution * (identity + a `project` pointer + opaque options + a preparation hook), and the host constructs the * real plugin once the combined ownership universe is known. * * THIS MODULE is the pure, Vite-value-free contract + host-side algebra: * - the dependency-free brand (renderers reproduce the literal by value, never import it at runtime); * - the opaque public `TaujsManagedPluginContribution` type (the ONE new public concept); * - the internal shapes the host and the renderers agree on (non-public, versioned by the brand); * - the pure helpers the two-phase host pre-pass is built from (partition, group, identity assertion, * effective-scope algebra, ownership-severity classification). * * The host is framework-NEUTRAL: it never constructs `solid()`/`react()`, never derives tsconfig * boundaries, and contains no `if (solid)`/`if (react)` branch. All framework knowledge lives behind * {@link CompilerImpl.prepare} in `@taujs/react` / `@taujs/solid` (ESC-0 ruling: generic aggregation + * ownership diagnostics only). * * Only `import type` from `vite` here - this module must stay runtime-Vite-free so it can be imported * from the config surface without pulling Vite into a plain consumer's runtime. */ /** * The structural brand marking a managed contribution, versioned so an incompatible internal shape is a * different brand rather than a silent mismatch. Renderers reproduce this LITERAL by value (they must * not import it at runtime - the brand is dependency-free so raw `pluginReact()`/`pluginSolid()` work in * a plain Vite project with no `@taujs/server` present). The literal TYPE {@link ManagedContributionBrand} * IS type-imported by renderers, so a host-side brand bump breaks their hardcoded assignment at compile * time - a safety net without a runtime dependency. */ declare const MANAGED_CONTRIBUTION_BRAND: "taujs.managed-plugin-contribution/v1"; type ManagedContributionBrand = typeof MANAGED_CONTRIBUTION_BRAND; /** A positive, Vite-`createFilter`-compatible ownership matcher (checkpoint §3 "faithful positive matchers"). */ type OwnershipMatcher = string | RegExp; /** The effective scope the host computes for one compiler (checkpoint §3 set-algebra). */ type EffectiveScope = { /** This key's merged ownership claims. */ include: OwnershipMatcher[]; /** All OTHER keys' merged claims only (the tsconfig's own `exclude` is already folded into the claims). */ exclude: OwnershipMatcher[]; }; /** * Generic, framework-neutral input the host passes to renderer preparation. Carries NO framework * knowledge - the renderer already holds its `project`/options via the group members. */ type PrepareInput = { /** τjs `projectRoot`; relative `project` paths resolve from here identically in dev and build (checkpoint §2). */ projectRoot: string; /** The Vite lifecycle this preparation feeds; preparation stays classify-only regardless (checkpoint §6). */ lifecycle: 'dev' | 'build'; }; /** * The plan a renderer returns from {@link CompilerImpl.prepare} for one same-key group. Classify-only: * `createPlugin` is NOT called here - the host calls it per Vite environment, and only for keys that * environment instantiates (build containment, checkpoint §6). */ type PreparedPlan = { /** The diagnostic key this plan owns (`'react'`/`'solid'`). */ key: string; /** * The positive ownership set for this key: the tsconfig project's `include` globs (resolving * `references`/`extends`) plus the exact node_modules package-directory matchers, compiled to * `createFilter`-compatible patterns. Same-key union across apps = array union. The project's own * `exclude` is carried separately in {@link PreparedPlan.exclude} and subtracted by the host when it * evaluates ownership (a positive matcher list cannot encode subtraction). */ claims: OwnershipMatcher[]; /** * Renderer-SUPPLIED expected-owner boundary matchers (checkpoint §3/§5) - the region a JSX/TSX file * SHOULD be owned in (broader than `claims`, so a file in the boundary that no compiler claims is a * zero-owner gap). The host evaluates these; it never derives tsconfig boundaries itself. "Expected * boundary" is NOT the whole app root: {@link PreparedPlan.exclude} is subtracted here too, so a * deliberately excluded file (fixtures, generated files) falls OUTSIDE the boundary and is not * reported as a zero-owner error. */ boundaries: OwnershipMatcher[]; /** * Renderer-SUPPLIED negative matchers = the tsconfig project's own `exclude` (checkpoint §3: "the * tsconfig's own exclude is already folded into the claims"). The host subtracts these from BOTH * `claims` and `boundaries` when evaluating ownership/region - a file the project deliberately * excludes is neither owned nor flagged. Cross-key exclusion (the effective-scope algebra) uses the * OTHER keys' `claims` only; the renderer folds its own `exclude` into the compiler it constructs. */ exclude?: OwnershipMatcher[]; /** * Constructs a FRESH real Vite plugin (a `PluginOption`) for the given effective scope. Called afresh * per `vite.build()` invocation and once per active renderer in dev - constructed plugins may carry * lifecycle state and are never reused (checkpoint §6). The renderer folds its own options (e.g. `ssr`) * in here. * * Typed `unknown` deliberately: the renderer builds the plugin with ITS OWN Vite type instance, and * under multiple `@types/node` versions in a workspace TypeScript treats `@taujs/react`'s `PluginOption` * and `@taujs/server`'s as unrelated types (same runtime module, distinct type identities). The host * casts the result to its own `PluginOption` at the one boundary where it feeds `composePlugins`. */ createPlugin: (scope: EffectiveScope) => unknown; }; /** * A renderer's implementation token. Its OBJECT IDENTITY is the implementation identity (correction 4): * every contribution produced by one installed copy of `@taujs/react` shares one `CompilerImpl` object; * two installed copies/versions produce two distinct objects, so the host detects "one key, two impls" * and fails closed. NOT a string (indistinguishable across copies) and NOT `Symbol.for()` (a global name * collapses distinct copies). */ type CompilerImpl = { /** The grouping/diagnostic key (`'react'`/`'solid'`); must equal every member contribution's `key`. */ readonly key: string; /** * Renderer-owned group preparation (async; classify-only). Receives the COMPLETE same-key group and * the generic input; merges the group's options deterministically (incompatible chain-global options * fail BEFORE Vite starts) and returns ONE {@link PreparedPlan}. */ prepare: (group: ReadonlyArray, input: PrepareInput) => Promise; }; /** * The runtime shape a renderer factory produces. NON-public and unstable (versioned by the brand); the * public face is the opaque {@link TaujsManagedPluginContribution}. App association is added by the host * at grouping time (the renderer does not know which app it lands in), not carried here. */ type ManagedContributionShape = { readonly brand: ManagedContributionBrand; /** Diagnostic key + grouping axis (`'react'`/`'solid'`); equals `impl.key`. */ readonly key: string; /** Reference-identity implementation token (see {@link CompilerImpl}). */ readonly impl: CompilerImpl; /** tsconfig project pointer; relative resolves from `projectRoot`, absolute stays absolute (checkpoint §2). */ readonly project: string; /** Opaque renderer options (the host never introspects them). Managed filter options are RESERVED. */ readonly options: unknown; }; /** A managed contribution paired with the app it was declared in (host-side, added during partition). */ type ManagedGroupMember = { contribution: ManagedContributionShape; appId: string; appRoot: string; }; /** * Renderer v1 (RFC 0006 / `docs/solid` renderer design v5) - the renderer CONTRIBUTION contract. * * A renderer factory returns ONE opaque branded contribution declared * on an app's REQUIRED singular `renderer:`. It is the paired contract's config-time DECLARATION half: it * names the framework identity + render-module contract version the host validates the loaded * {@link RenderModule} against (the runtime half), and carries EITHER a managed compiler contribution (a * JSX renderer - scoped ownership, reusing the ESC-1 machinery unchanged) OR a fresh-per-environment raw * plugin pack (Vue - its ordinary `.vue` compiler, NO ownership machinery). * * Framework knowledge stays in the renderer packages; the host is NEUTRAL (aggregation + validation only, * no `if (react)`/`if (vue)` branch). Runtime-Vite-free (only `import type` from vite downstream) so it can * be referenced from the config surface without pulling Vite into a plain consumer's runtime - exactly the * discipline {@link ./ManagedPlugins} keeps. */ /** * Structural brand for a renderer contribution, versioned so an incompatible shape is a different brand. * The brand IS the contribution-protocol discriminator: v2 is the LAZY protocol (compiler machinery is * loaded through async loaders by build/dev only; production never invokes them). The eager v1 protocol * is recognised explicitly BY ITS BRAND in {@link requireRendererContribution} and rejected with upgrade * guidance - never inferred from missing properties. */ declare const RENDERER_CONTRIBUTION_BRAND: "taujs.renderer-contribution/v2"; type RendererContributionBrand = typeof RENDERER_CONTRIBUTION_BRAND; /** * The render-MODULE contract version - the runtime `{ renderSSR, renderStream }` shape a framework's * `createRenderer` produces. Distinct from {@link RENDERER_CONTRIBUTION_BRAND} (the config-time contribution * shape): a render-shape bump and an ownership-shape bump version independently. Reproduced BY VALUE in the * framework packages (they never runtime-import `@taujs/server`); the type keeps them in sync at compile time. */ declare const RENDER_CONTRACT_VERSION: "v1"; type RenderContractVersion = typeof RENDER_CONTRACT_VERSION; /** The identity fields every v2 contribution carries, protocol-variant-independent. */ type RendererContributionBase = { readonly brand: RendererContributionBrand; /** Framework identity + (when managed) the ESC-1 grouping key. */ readonly key: string; /** The render-module contract version the app's loaded {@link RenderModule} must match. */ readonly contractVersion: string; }; /** * A managed-compilation renderer (React/Solid): JSX/TSX compilation COLLIDES across frameworks and needs * scoped ownership, carried as a lazy ESC-1 compiler contribution. `loadCompiler` is invoked by the host * prepass (once per prepass invocation: one dev boot, one `taujsBuild` run); the factory memoises the * contribution promise, so the managed contribution is CONSTRUCTED once per contribution lifetime. * Production never invokes it, so the compiler toolchain never resolves in a production process. */ type ManagedRendererContribution = RendererContributionBase & { readonly managedCompilation: true; readonly loadCompiler: () => Promise; readonly loadEnvironmentPlugins?: never; }; /** * A non-managed renderer (Vue): its compiler is an ordinary unscoped Vite plugin, produced lazily and * FRESH once per Vite environment (the ESC-1 lifecycle lesson - plugin objects are never reused across * environments; only the module import behind the loader is cached by ESM). The resolved value is typed * `unknown` for the same cross-`@types/node` Vite type-identity reason as `PreparedPlan.createPlugin`; * the host casts to its own `PluginOption` at the composition seam. */ type EnvironmentRendererContribution = RendererContributionBase & { readonly managedCompilation: false; readonly loadCompiler?: never; readonly loadEnvironmentPlugins: (lifecycle: 'dev' | 'build') => Promise; }; /** * The runtime shape a renderer factory produces - a DISCRIMINATED UNION on `managedCompilation`, so the * protocol's central invariant (exactly one loader, selected by the discriminant) is structural rather * than asserted. NON-public + unstable (versioned by the brand); the public face is the opaque * {@link TaujsRendererContribution}. App association is added by the host at grouping time, not carried * here. */ type RendererContributionShape = ManagedRendererContribution | EnvironmentRendererContribution; declare const RENDERER_OPAQUE: unique symbol; /** * The ONE new public concept: an opaque renderer contribution obtained ONLY from a renderer factory * and declared on an app's required singular `renderer:`. Application * code never constructs or introspects it. Every renderer supplies a runtime render module the host * validates - there is no compiler-only/incomplete-renderer mode. */ type TaujsRendererContribution = { readonly [RENDERER_OPAQUE]: true; }; export type { CompilerImpl, EffectiveScope, EnvironmentRendererContribution, ManagedContributionBrand, ManagedContributionShape, ManagedGroupMember, ManagedRendererContribution, PrepareInput, PreparedPlan, RenderContractVersion, RendererContributionBrand, RendererContributionShape, TaujsRendererContribution };