rspress-plugin-api-extractor 0.11.0 → 0.12.0

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 (46) hide show
  1. package/BuildEnv.js +3 -3
  2. package/build-program.js +2 -3
  3. package/build-stages.js +3 -5
  4. package/config-helpers.js +7 -7
  5. package/errors.js +1 -5
  6. package/index.d.ts +84 -86
  7. package/layers/AppLayer.js +67 -0
  8. package/layers/api-results.js +83 -0
  9. package/layers/build-metrics.js +1 -1
  10. package/layers/{ConfigServiceLive.js → config-resolution.js} +76 -175
  11. package/layers/external-types.js +74 -0
  12. package/layers/{ObservabilityLive.js → observability.js} +3 -3
  13. package/layers/type-environment.js +109 -0
  14. package/layers/xdg.js +1 -1
  15. package/markdown/shiki-utils.js +1 -1
  16. package/model-loader.js +3 -3
  17. package/observability/EventBus.js +2 -2
  18. package/observability/heartbeat.js +1 -1
  19. package/observability/sinks/metrics-sink.js +1 -1
  20. package/observability/sinks/trace-sink.js +4 -4
  21. package/og-resolver.js +32 -1
  22. package/package.json +2 -2
  23. package/plugin.js +30 -33
  24. package/prettier-formatter.js +1 -1
  25. package/remark-api-codeblocks.js +1 -1
  26. package/remark-with-api.js +1 -1
  27. package/schemas/config.js +0 -2
  28. package/services/ConfigService.js +37 -2
  29. package/services/HighlighterService.js +48 -3
  30. package/services/OgService.js +169 -2
  31. package/services/PluginConfig.js +1 -1
  32. package/services/TwoslashCacheService.js +128 -2
  33. package/services/TwoslashEnvironments.js +30 -2
  34. package/services/TypeRegistryService.js +178 -2
  35. package/shiki-transformer.js +1 -1
  36. package/sync-node-fs.js +6 -6
  37. package/tsconfig-parser.js +77 -95
  38. package/twoslash-access.js +1 -1
  39. package/twoslash-transformer.js +1 -1
  40. package/layers/HighlighterServiceLive.js +0 -52
  41. package/layers/OgServiceLive.js +0 -134
  42. package/layers/TwoslashCacheServiceLive.js +0 -108
  43. package/layers/TwoslashEnvironmentsLive.js +0 -33
  44. package/layers/TypeRegistryServiceLive.js +0 -162
  45. package/markdown/index.js +0 -11
  46. package/schemas/index.js +0 -6
package/BuildEnv.js CHANGED
@@ -7,12 +7,12 @@ import { Context } from "effect";
7
7
  *
8
8
  * @remarks
9
9
  * Each of these used to travel by hand. `thresholds` made a four-hop journey —
10
- * `plugin.ts` → `ConfigServiceLive`'s fourth constructor argument →
10
+ * `plugin.ts` → `ConfigService.layer`'s fourth constructor argument →
11
11
  * a `ResolvedBuildContext` field → destructured in `build-program.ts` → every
12
12
  * `withPhase` call — while `obs.thresholds` already held the same value one
13
13
  * scope away. `buildId` was worse: 24 event emit sites wrote
14
14
  * `ctx: { buildId: "" }` because the value was not reachable from where they
15
- * stood, and in `TypeRegistryServiceLive` it genuinely was not — that layer is
15
+ * stood, and in `TypeRegistryService.layer` it genuinely was not — that layer is
16
16
  * module-level and has no build to name. A Reference is the fix precisely
17
17
  * because it reaches module-level code that no parameter can.
18
18
  *
@@ -46,7 +46,7 @@ const Thresholds = Context.Reference("rspress-plugin-api-extractor/Thresholds",
46
46
  * How many pages the build pipeline generates concurrently.
47
47
  *
48
48
  * @remarks
49
- * Defaults to the CPU count, which is what `ConfigServiceLive` computed
49
+ * Defaults to the CPU count, which is what `ConfigService.layer` computed
50
50
  * inline. Kept a Reference rather than a constant so a consumer with a
51
51
  * constrained CI runner can lower it without a code change.
52
52
  */
package/build-program.js CHANGED
@@ -1,17 +1,16 @@
1
1
  import { BuildId, PageConcurrency, SuppressExampleErrors } from "./BuildEnv.js";
2
2
  import { setProseLinker } from "./markdown/prose-linker.js";
3
- import "./markdown/index.js";
4
3
  import { buildPipelineForApi, cleanupAndCommit, prepareWorkItems, writeMetadata } from "./build-stages.js";
5
4
  import { HideCutLinesTransformer, MemberFormatTransformer } from "./hide-cut-transformer.js";
6
5
  import { withPhase } from "./observability/spans.js";
7
6
  import { HighlighterService } from "./services/HighlighterService.js";
7
+ import { addTypeRoutes } from "./twoslash-transformer.js";
8
8
  import { TwoslashEnvironments } from "./services/TwoslashEnvironments.js";
9
9
  import { ShikiCrossLinker } from "./shiki-transformer.js";
10
- import { addTypeRoutes } from "./twoslash-transformer.js";
11
10
  import { VfsRegistry } from "./vfs-registry.js";
12
11
  import path from "node:path";
13
- import { SnapshotService } from "@tsdoctor/snapshot";
14
12
  import { Effect, FileSystem } from "effect";
13
+ import { SnapshotService } from "@tsdoctor/snapshot";
15
14
 
16
15
  //#region src/build-program.ts
17
16
  /**
package/build-stages.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { parseFrontmatter, stringifyFrontmatter } from "./frontmatter.js";
2
+ import { BuildMetrics } from "./layers/build-metrics.js";
2
3
  import { PluginEvent } from "./observability/events.js";
3
4
  import { emit } from "./observability/EventBus.js";
4
- import { BuildMetrics } from "./layers/build-metrics.js";
5
- import "./layers/ObservabilityLive.js";
6
5
  import { emitSync, syncBuildId } from "./observability/sync-emitter.js";
7
6
  import { generateFrontmatter } from "./markdown/helpers.js";
8
7
  import { ClassPageGenerator } from "./markdown/page-generators/class-page.js";
@@ -13,12 +12,11 @@ import { InterfacePageGenerator } from "./markdown/page-generators/interface-pag
13
12
  import { NamespacePageGenerator } from "./markdown/page-generators/namespace-page.js";
14
13
  import { TypeAliasPageGenerator } from "./markdown/page-generators/type-alias-page.js";
15
14
  import { VariablePageGenerator } from "./markdown/page-generators/variable-page.js";
16
- import "./markdown/index.js";
17
15
  import { createPageMetadata } from "./og-resolver.js";
18
16
  import { OgService } from "./services/OgService.js";
19
17
  import path from "node:path";
20
- import { SnapshotService, hashContent, hashFrontmatter } from "@tsdoctor/snapshot";
21
18
  import { Effect, FileSystem, Metric, Option, Stream } from "effect";
19
+ import { SnapshotService, hashContent, hashFrontmatter } from "@tsdoctor/snapshot";
22
20
  import { ApiItemKind } from "@microsoft/api-extractor-model";
23
21
  import { ApiItems, EntryPoints, Routes, SyntheticBases } from "@tsdoctor/model";
24
22
 
@@ -427,7 +425,7 @@ function writeSingleFile(result, ctx) {
427
425
  };
428
426
  }
429
427
  let finalContent = stringifyFrontmatter(bodyContent, frontmatter);
430
- if (siteUrl && packageName) {
428
+ if (siteUrl != null && packageName) {
431
429
  const ogSvc = yield* OgService;
432
430
  const ogImageResult = yield* Effect.result(ogSvc.resolveImage({
433
431
  config: ogImage,
package/config-helpers.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { normalizeBaseRoute } from "./path-derivation.js";
2
2
  import { SyncDiscoveryLayer } from "./sync-node-fs.js";
3
- import fs from "node:fs";
3
+ import fsSync from "node:fs";
4
4
  import path from "node:path";
5
5
  import { Effect, Result } from "effect";
6
6
  import { discoverBundle } from "@tsdoctor/bundle";
@@ -18,14 +18,14 @@ const PREFIX = "[rspress-plugin-api-extractor]";
18
18
  function requirePackageJson(dir) {
19
19
  let stat;
20
20
  try {
21
- stat = fs.statSync(dir);
21
+ stat = fsSync.statSync(dir);
22
22
  } catch {
23
23
  throw new Error(`${PREFIX} api.fromDir: directory not found: ${dir}`);
24
24
  }
25
25
  if (!stat.isDirectory()) throw new Error(`${PREFIX} api.fromDir: not a directory: ${dir}`);
26
26
  let pkg;
27
27
  try {
28
- pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
28
+ pkg = JSON.parse(fsSync.readFileSync(path.join(dir, "package.json"), "utf8"));
29
29
  } catch {
30
30
  throw new Error(`${PREFIX} api.fromDir: missing or unreadable package.json in ${dir}`);
31
31
  }
@@ -103,9 +103,9 @@ function fromDir(dir, overrides = {}) {
103
103
  };
104
104
  }
105
105
  function isModelFolder(dir) {
106
- if (!fs.existsSync(path.join(dir, "package.json"))) return false;
106
+ if (!fsSync.existsSync(path.join(dir, "package.json"))) return false;
107
107
  try {
108
- return fs.readdirSync(dir).some((f) => f.endsWith(".api.json"));
108
+ return fsSync.readdirSync(dir).some((f) => f.endsWith(".api.json"));
109
109
  } catch {
110
110
  return false;
111
111
  }
@@ -124,12 +124,12 @@ function fromParentDir(parentDir, options = {}) {
124
124
  const absParent = path.resolve(cwd ?? process.cwd(), parentDir);
125
125
  let stat;
126
126
  try {
127
- stat = fs.statSync(absParent);
127
+ stat = fsSync.statSync(absParent);
128
128
  } catch {
129
129
  throw new Error(`${PREFIX} apis.fromDir: directory not found: ${absParent}`);
130
130
  }
131
131
  if (!stat.isDirectory()) throw new Error(`${PREFIX} apis.fromDir: not a directory: ${absParent}`);
132
- const subdirs = fs.readdirSync(absParent, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
132
+ const subdirs = fsSync.readdirSync(absParent, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
133
133
  const configs = [];
134
134
  for (const name of subdirs) {
135
135
  const subdir = path.join(absParent, name);
package/errors.js CHANGED
@@ -7,16 +7,12 @@ var ConfigValidationError = class extends ConfigValidationErrorBase {
7
7
  return `Config validation failed for '${this.field}': ${this.reason}`;
8
8
  }
9
9
  };
10
- const ApiModelLoadErrorBase = Data.TaggedError("ApiModelLoadError");
11
10
  const TypeRegistryErrorBase = Data.TaggedError("TypeRegistryError");
12
11
  var TypeRegistryError = class extends TypeRegistryErrorBase {
13
12
  get message() {
14
13
  return `Type registry error for '${this.packageName}@${this.version}': ${this.reason}`;
15
14
  }
16
15
  };
17
- const PageGenerationErrorBase = Data.TaggedError("PageGenerationError");
18
- const TwoslashProcessingErrorBase = Data.TaggedError("TwoslashProcessingError");
19
- const PrettierFormatErrorBase = Data.TaggedError("PrettierFormatError");
20
16
 
21
17
  //#endregion
22
- export { ConfigValidationError, ConfigValidationErrorBase, TypeRegistryError, TypeRegistryErrorBase };
18
+ export { ConfigValidationError, TypeRegistryError };
package/index.d.ts CHANGED
@@ -669,8 +669,6 @@ declare const PluginOptions: Schema.Struct<{
669
669
  */
670
670
  readonly compilerOptions: Schema.optional<Schema.Unknown>;
671
671
  }>>>>>;
672
- /** Canonical site URL used for Open Graph absolute URLs. */
673
- readonly siteUrl: Schema.optional<Schema.String>;
674
672
  /** Global Open Graph image configuration (overridden per-API). */
675
673
  readonly ogImage: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Struct<{
676
674
  readonly url: Schema.String;
@@ -746,90 +744,6 @@ declare const PluginOptions: Schema.Struct<{
746
744
  /** @public */
747
745
  type PluginOptions = typeof PluginOptions.Encoded;
748
746
  //#endregion
749
- //#region src/schemas/opengraph.d.ts
750
- /**
751
- * Structured Open Graph image metadata (alternative to a plain URL string).
752
- *
753
- * @public
754
- */
755
- declare const OpenGraphImageMetadata: Schema.Struct<{
756
- /** Absolute URL of the image. */
757
- readonly url: Schema.String;
758
- /** HTTPS URL of the image (for secure contexts). */
759
- readonly secureUrl: Schema.optional<Schema.String>;
760
- /** MIME type of the image (e.g. `"image/png"`). */
761
- readonly type: Schema.optional<Schema.String>;
762
- /** Image width in pixels. */
763
- readonly width: Schema.optional<Schema.Number>;
764
- /** Image height in pixels. */
765
- readonly height: Schema.optional<Schema.Number>;
766
- /** Alt text for the image. */
767
- readonly alt: Schema.optional<Schema.String>;
768
- }>;
769
- /** @public */
770
- type OpenGraphImageMetadata = typeof OpenGraphImageMetadata.Type;
771
- /**
772
- * Open Graph image: either a plain URL string or structured `OpenGraphImageMetadata`.
773
- *
774
- * @public
775
- */
776
- declare const OpenGraphImageConfig: Schema.Union<readonly [Schema.String, Schema.Struct<{
777
- /** Absolute URL of the image. */
778
- readonly url: Schema.String;
779
- /** HTTPS URL of the image (for secure contexts). */
780
- readonly secureUrl: Schema.optional<Schema.String>;
781
- /** MIME type of the image (e.g. `"image/png"`). */
782
- readonly type: Schema.optional<Schema.String>;
783
- /** Image width in pixels. */
784
- readonly width: Schema.optional<Schema.Number>;
785
- /** Image height in pixels. */
786
- readonly height: Schema.optional<Schema.Number>;
787
- /** Alt text for the image. */
788
- readonly alt: Schema.optional<Schema.String>;
789
- }>]>;
790
- /** @public */
791
- type OpenGraphImageConfig = typeof OpenGraphImageConfig.Type;
792
- /**
793
- * Resolved Open Graph metadata emitted into each generated page's frontmatter.
794
- *
795
- * @public
796
- */
797
- declare const OpenGraphMetadata: Schema.Struct<{
798
- /** Canonical site base URL. */
799
- readonly siteUrl: Schema.String;
800
- /** Page route path (e.g. `/api/classes/myclass`). */
801
- readonly pageRoute: Schema.String;
802
- /** Page description for the `og:description` tag. */
803
- readonly description: Schema.String;
804
- /** ISO 8601 date string for `article:published_time`. */
805
- readonly publishedTime: Schema.String;
806
- /** ISO 8601 date string for `article:modified_time`. */
807
- readonly modifiedTime: Schema.String;
808
- /** Article section label (e.g. `"API"`). */
809
- readonly section: Schema.String;
810
- /** Article tag keywords. */
811
- readonly tags: Schema.mutable<Schema.$Array<Schema.String>>;
812
- /** Optional structured image metadata. */
813
- readonly ogImage: Schema.optional<Schema.Struct<{
814
- /** Absolute URL of the image. */
815
- readonly url: Schema.String;
816
- /** HTTPS URL of the image (for secure contexts). */
817
- readonly secureUrl: Schema.optional<Schema.String>;
818
- /** MIME type of the image (e.g. `"image/png"`). */
819
- readonly type: Schema.optional<Schema.String>;
820
- /** Image width in pixels. */
821
- readonly width: Schema.optional<Schema.Number>;
822
- /** Image height in pixels. */
823
- readonly height: Schema.optional<Schema.Number>;
824
- /** Alt text for the image. */
825
- readonly alt: Schema.optional<Schema.String>;
826
- }>>;
827
- /** Open Graph object type (e.g. `"article"`). */
828
- readonly ogType: Schema.String;
829
- }>;
830
- /** @public */
831
- type OpenGraphMetadata = typeof OpenGraphMetadata.Type;
832
- //#endregion
833
747
  //#region src/config-helpers.d.ts
834
748
  /**
835
749
  * Metadata discovered from a single rslib-builder localPaths package folder.
@@ -940,6 +854,90 @@ declare const ApiExtractorPlugin: typeof ApiExtractorPluginImpl & {
940
854
  };
941
855
  };
942
856
  //#endregion
857
+ //#region src/schemas/opengraph.d.ts
858
+ /**
859
+ * Structured Open Graph image metadata (alternative to a plain URL string).
860
+ *
861
+ * @public
862
+ */
863
+ declare const OpenGraphImageMetadata: Schema.Struct<{
864
+ /** Absolute URL of the image. */
865
+ readonly url: Schema.String;
866
+ /** HTTPS URL of the image (for secure contexts). */
867
+ readonly secureUrl: Schema.optional<Schema.String>;
868
+ /** MIME type of the image (e.g. `"image/png"`). */
869
+ readonly type: Schema.optional<Schema.String>;
870
+ /** Image width in pixels. */
871
+ readonly width: Schema.optional<Schema.Number>;
872
+ /** Image height in pixels. */
873
+ readonly height: Schema.optional<Schema.Number>;
874
+ /** Alt text for the image. */
875
+ readonly alt: Schema.optional<Schema.String>;
876
+ }>;
877
+ /** @public */
878
+ type OpenGraphImageMetadata = typeof OpenGraphImageMetadata.Type;
879
+ /**
880
+ * Open Graph image: either a plain URL string or structured `OpenGraphImageMetadata`.
881
+ *
882
+ * @public
883
+ */
884
+ declare const OpenGraphImageConfig: Schema.Union<readonly [Schema.String, Schema.Struct<{
885
+ /** Absolute URL of the image. */
886
+ readonly url: Schema.String;
887
+ /** HTTPS URL of the image (for secure contexts). */
888
+ readonly secureUrl: Schema.optional<Schema.String>;
889
+ /** MIME type of the image (e.g. `"image/png"`). */
890
+ readonly type: Schema.optional<Schema.String>;
891
+ /** Image width in pixels. */
892
+ readonly width: Schema.optional<Schema.Number>;
893
+ /** Image height in pixels. */
894
+ readonly height: Schema.optional<Schema.Number>;
895
+ /** Alt text for the image. */
896
+ readonly alt: Schema.optional<Schema.String>;
897
+ }>]>;
898
+ /** @public */
899
+ type OpenGraphImageConfig = typeof OpenGraphImageConfig.Type;
900
+ /**
901
+ * Resolved Open Graph metadata emitted into each generated page's frontmatter.
902
+ *
903
+ * @public
904
+ */
905
+ declare const OpenGraphMetadata: Schema.Struct<{
906
+ /** Canonical site base URL. */
907
+ readonly siteUrl: Schema.String;
908
+ /** Page route path (e.g. `/api/classes/myclass`). */
909
+ readonly pageRoute: Schema.String;
910
+ /** Page description for the `og:description` tag. */
911
+ readonly description: Schema.String;
912
+ /** ISO 8601 date string for `article:published_time`. */
913
+ readonly publishedTime: Schema.String;
914
+ /** ISO 8601 date string for `article:modified_time`. */
915
+ readonly modifiedTime: Schema.String;
916
+ /** Article section label (e.g. `"API"`). */
917
+ readonly section: Schema.String;
918
+ /** Article tag keywords. */
919
+ readonly tags: Schema.mutable<Schema.$Array<Schema.String>>;
920
+ /** Optional structured image metadata. */
921
+ readonly ogImage: Schema.optional<Schema.Struct<{
922
+ /** Absolute URL of the image. */
923
+ readonly url: Schema.String;
924
+ /** HTTPS URL of the image (for secure contexts). */
925
+ readonly secureUrl: Schema.optional<Schema.String>;
926
+ /** MIME type of the image (e.g. `"image/png"`). */
927
+ readonly type: Schema.optional<Schema.String>;
928
+ /** Image width in pixels. */
929
+ readonly width: Schema.optional<Schema.Number>;
930
+ /** Image height in pixels. */
931
+ readonly height: Schema.optional<Schema.Number>;
932
+ /** Alt text for the image. */
933
+ readonly alt: Schema.optional<Schema.String>;
934
+ }>>;
935
+ /** Open Graph object type (e.g. `"article"`). */
936
+ readonly ogType: Schema.String;
937
+ }>;
938
+ /** @public */
939
+ type OpenGraphMetadata = typeof OpenGraphMetadata.Type;
940
+ //#endregion
943
941
  //#region src/serve.d.ts
944
942
  /**
945
943
  * Which RSPress server {@link serve} runs.
@@ -0,0 +1,67 @@
1
+ import { BuildId, PageConcurrency, SuppressExampleErrors, Thresholds } from "../BuildEnv.js";
2
+ import { OgService } from "../services/OgService.js";
3
+ import { collectShikiThemes } from "../markdown/shiki-utils.js";
4
+ import { HighlighterService } from "../services/HighlighterService.js";
5
+ import { TwoslashEnvironments } from "../services/TwoslashEnvironments.js";
6
+ import { PluginConfig } from "../services/PluginConfig.js";
7
+ import { PlatformLive } from "./xdg.js";
8
+ import { TypeRegistryService } from "../services/TypeRegistryService.js";
9
+ import { TwoslashCacheService } from "../services/TwoslashCacheService.js";
10
+ import { ConfigService } from "../services/ConfigService.js";
11
+ import { makeSummaryLoggerLayer } from "./observability.js";
12
+ import { Layer } from "effect";
13
+ import { SnapshotService } from "@tsdoctor/snapshot";
14
+ import { NodeFileSystem } from "@effect/platform-node";
15
+
16
+ //#region src/layers/AppLayer.ts
17
+ /**
18
+ * The build's layer stack, tiered.
19
+ *
20
+ * @remarks
21
+ * `plugin.ts` used to merge eleven layers side by side in one `Layer.mergeAll`
22
+ * — `NodeFileSystem.layer` (platform) next to `TypeRegistryService.layer`
23
+ * (domain) next to `makeSummaryLoggerLayer` (observability) — with one of them
24
+ * carrying a local `Layer.provide` because a flat merge could not feed it its
25
+ * dependencies. Reading that told you what the build contains but not what
26
+ * depends on what.
27
+ *
28
+ * The tiers below are ordered by what they may reach: platform knows nothing
29
+ * about this plugin, core services know the platform, and build-scoped services
30
+ * know both.
31
+ *
32
+ * @packageDocumentation
33
+ */
34
+ /**
35
+ * Build both stacks for one build.
36
+ *
37
+ * @remarks
38
+ * **A layer factory: call it once and bind the result to a `const`.** Layers
39
+ * memoize by reference, so a second call mints a second stack — a second Shiki
40
+ * highlighter, a second snapshot database, a second metric registry.
41
+ */
42
+ function makeAppLayers(input) {
43
+ /**
44
+ * Per-build configuration, provided to BOTH stacks. Sharing these values is
45
+ * what lets a sync island and an Effect program agree on the build id and
46
+ * the slow-block threshold without either being handed them.
47
+ */
48
+ const BuildEnvLayer = Layer.mergeAll(Layer.succeed(BuildId, input.buildId), Layer.succeed(Thresholds, input.obs.thresholds), Layer.succeed(PageConcurrency, input.pageConcurrency), Layer.succeed(SuppressExampleErrors, input.options.errors?.example !== "show"));
49
+ /**
50
+ * Sinks, metrics and the logger gate. Synchronously buildable, which is what
51
+ * lets the emitter stack below reuse it wholesale.
52
+ */
53
+ const ObservabilityLayer = Layer.mergeAll(input.eventBus, input.metrics.layer, makeSummaryLoggerLayer(input.obs.logLevel));
54
+ /** Services that own a resource and need only the platform to build. */
55
+ const CoreLayer = Layer.mergeAll(TypeRegistryService.layer, TwoslashCacheService.layer, SnapshotService.layer(input.dbPath), Layer.provide(OgService.layer, PlatformLive));
56
+ /** Bound to a `const`: this is a factory, and a second call acquires a second highlighter. */
57
+ const HighlighterLive = HighlighterService.layer(collectShikiThemes(input.options.api ? [input.options.api] : input.options.apis ?? []));
58
+ /** Services scoped to this build's configuration. */
59
+ const BuildLayer = Layer.mergeAll(Layer.succeed(PluginConfig, input.options), HighlighterLive, TwoslashEnvironments.layer, BuildEnvLayer);
60
+ return {
61
+ app: Layer.provideMerge(ConfigService.layer, Layer.mergeAll(BuildLayer, CoreLayer, ObservabilityLayer, NodeFileSystem.layer)),
62
+ emitter: Layer.mergeAll(ObservabilityLayer, BuildEnvLayer)
63
+ };
64
+ }
65
+
66
+ //#endregion
67
+ export { makeAppLayers };
@@ -0,0 +1,83 @@
1
+ import { PluginEvent } from "../observability/events.js";
2
+ import { emit } from "../observability/EventBus.js";
3
+ import { Effect } from "effect";
4
+
5
+ //#region src/layers/api-results.ts
6
+ /**
7
+ * Accumulating one API's resolution result into the build-wide totals.
8
+ *
9
+ * @remarks
10
+ * `ConfigService.layer` resolves APIs down three paths — versioned, single
11
+ * non-versioned, and multi-API — and each one ended with a near-identical
12
+ * ~35-line block that merged the same three accumulators and then emitted the
13
+ * same two events per VFS entry. Three copies of one algorithm is three places
14
+ * for it to drift, and the third copy had ALREADY drifted: it emits its events
15
+ * inside the per-API effect and merges afterwards, rather than doing both in
16
+ * one pass.
17
+ *
18
+ * Splitting the block in two is what makes all three paths expressible. The
19
+ * merge is pure and the emission is effectful, and the multi-API path needs
20
+ * them at different moments; a single combined helper would have fitted two
21
+ * paths and forced the third to keep its own copy.
22
+ *
23
+ * @packageDocumentation
24
+ */
25
+ /**
26
+ * Merge one result into the build-wide accumulators.
27
+ *
28
+ * @remarks
29
+ * Mutates `acc` rather than returning a new one. The accumulators are three
30
+ * `const` collections in a long generator that appends to them from several
31
+ * branches, and threading a replacement through every branch would be a larger
32
+ * change than this task is buying.
33
+ *
34
+ * **The VFS is a single flat namespace shared by every documented API.** A
35
+ * later entry silently overwrites an earlier one at the same path, which is
36
+ * load-bearing rather than accidental: cross-package type references resolve
37
+ * only because every package's declarations live in one environment (see
38
+ * `type-loading-vfs.md`).
39
+ */
40
+ function mergeApiResult(acc, result) {
41
+ for (const [filepath, content] of result.vfs.entries()) acc.combinedVfs.set(filepath, content);
42
+ if (result.externalPackages.length > 0) acc.allExternalPackages.push(...result.externalPackages);
43
+ if (result.config) acc.apiConfigs.push(result.config);
44
+ }
45
+ /**
46
+ * Emit the per-entry VFS events for one API's payloads.
47
+ *
48
+ * @remarks
49
+ * `ImportsPrepended` fires only when imports were actually prepended, so an
50
+ * entry that needed none produces one event rather than two.
51
+ *
52
+ * `wantTrace` gates the two heavy fields — the full declaration text and the
53
+ * resolved import refs. Both are only ever read by the JSONL trace sink, and
54
+ * carrying them unconditionally would put every generated declaration file
55
+ * through the event bus on every build.
56
+ */
57
+ function emitVfsPayloadEvents(packageName, payloads, wantTrace) {
58
+ return Effect.gen(function* () {
59
+ for (const payload of payloads) {
60
+ const ctx = {
61
+ packageName,
62
+ ...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
63
+ };
64
+ yield* emit(PluginEvent.VfsGenerated({
65
+ ctx,
66
+ level: "debug",
67
+ file: payload.file,
68
+ declCount: payload.declCount,
69
+ contentHash: payload.contentHash,
70
+ ...wantTrace && payload.content ? { content: payload.content } : {}
71
+ }));
72
+ if (payload.hasImports) yield* emit(PluginEvent.ImportsPrepended({
73
+ ctx,
74
+ level: "debug",
75
+ file: payload.file,
76
+ imports: wantTrace ? payload.importRefs : []
77
+ }));
78
+ }
79
+ });
80
+ }
81
+
82
+ //#endregion
83
+ export { emitVfsPayloadEvents, mergeApiResult };
@@ -18,7 +18,7 @@ function makeMetricStore() {
18
18
  * build still gets its own counters — see {@link MetricRegistryLive}.
19
19
  *
20
20
  * Extracted into its own module so that `metrics-sink.ts` can import it
21
- * without creating a circular dependency through `ObservabilityLive.ts`
21
+ * without creating a circular dependency through `observability.ts`
22
22
  * (which itself imports `metrics-sink.ts`).
23
23
  */
24
24
  const BuildMetrics = {