first commit
This commit is contained in:
85
docs/devtools/tooling/overview/src/discover/discover.md
Normal file
85
docs/devtools/tooling/overview/src/discover/discover.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# `discover/discover.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Implements the filesystem walker that produces a validated `SuavePlanManifest`
|
||||
from a populated `packages/<category>/<plugin>/<pkg>/package.json` tree. Both
|
||||
the synchronous `discoverPackages` and the async `discoverPackagesIter` live
|
||||
here. The module is the only server-tier surface in `@suaveplan/overview`; it
|
||||
ships under the `./discover` sub-path export so universal consumers can avoid
|
||||
the `node:fs` / `node:path` imports entirely.
|
||||
|
||||
Discovery is straightforward: walk three directory levels deep, read each
|
||||
`package.json`, infer its tier, collect workspace deps, validate against
|
||||
`PackageRecordSchema`, then aggregate the resulting rows into a manifest.
|
||||
|
||||
## Features
|
||||
|
||||
- Synchronous `discoverPackages` returning a fully-validated manifest
|
||||
- Async `discoverPackagesIter` honouring `AbortSignal`
|
||||
- `onError: "throw" | "skip"` for graceful handling of malformed
|
||||
`package.json` files
|
||||
- Optional `tierHints` extending `SERVER_DEP_HINTS`
|
||||
- Emits `overview.packages_discovered_total`,
|
||||
`overview.records_rejected_total{reason}`, and
|
||||
`overview.discover_duration_ms`
|
||||
- Built-in roll-up so the manifest carries `plugins`, `categories`, and
|
||||
`tierCounts` ready for consumption
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { discoverPackages } from "@suaveplan/overview/discover";
|
||||
|
||||
const manifest = discoverPackages({ root: process.cwd() });
|
||||
console.log(`Found ${manifest.packages.length} packages.`);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { discoverPackagesIter } from "@suaveplan/overview/discover";
|
||||
|
||||
const controller = new AbortController();
|
||||
setTimeout(() => controller.abort(), 5_000);
|
||||
|
||||
try {
|
||||
for await (const r of discoverPackagesIter({
|
||||
root: "/repo",
|
||||
signal: controller.signal,
|
||||
onError: "skip",
|
||||
tierHints: new Set(["custom-native-binding"]),
|
||||
})) {
|
||||
process.stdout.write(`${r.name}\n`);
|
||||
}
|
||||
} catch (err) {
|
||||
// OverviewError with code OVERVIEW.DISCOVERY_ABORTED when the signal fired.
|
||||
}
|
||||
```
|
||||
|
||||
`onError: "skip"` is the right mode for tooling that should never abort on a
|
||||
single bad package.json (e.g. a long-running watcher); `"throw"` is right for
|
||||
one-shot CLIs that prefer a hard fail on corruption.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `discoverPackages(options: DiscoverOptions): SuavePlanManifest`
|
||||
- `discoverPackagesIter(options: AsyncDiscoverOptions): AsyncGenerator<PackageRecord>`
|
||||
- `interface DiscoverOptions { root, onError?, tierHints? }`
|
||||
- `interface AsyncDiscoverOptions extends DiscoverOptions { signal? }`
|
||||
- `type DiscoveryErrorMode = "throw" | "skip"`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The walker enforces exactly three directory levels (category → plugin →
|
||||
package). Anything shallower or deeper is silently ignored — partial-migration
|
||||
trees are common and a permissive walker keeps the tooling robust.
|
||||
`existsSync` + `statSync` are used to filter non-directories and packages
|
||||
without a `package.json`; both are cheap on a populated tree because the OS
|
||||
will have cached the directory listings. The histogram observation uses
|
||||
`performance.now()` when available (it always is in Node 20+/Bun) and falls
|
||||
back to `Date.now()` otherwise.
|
||||
86
docs/devtools/tooling/overview/src/errors/errors.md
Normal file
86
docs/devtools/tooling/overview/src/errors/errors.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# `errors/errors.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the stable error-code registry and the concrete `OverviewError`
|
||||
subclass of `SuaveplanError`. Every public entry point in `@suaveplan/overview`
|
||||
that throws does so through this class, so callers can rely on a single
|
||||
`instanceof OverviewError` check and a literal-typed `code` field for
|
||||
discrimination.
|
||||
|
||||
The registry has six entries — one per failure mode that the package can
|
||||
detect — and the §2c "never delete spec elements to satisfy a gate" rule means
|
||||
each entry is exercised by a real call site somewhere in `src/`, asserted by
|
||||
the static test `every code has a real call site in src/`.
|
||||
|
||||
## Features
|
||||
|
||||
- Six stable codes registered through `defineCodes("OVERVIEW", [...])`
|
||||
- Frozen, fully-inferred code record — `OVERVIEW_CODES.PARSE_FAILED` is the
|
||||
literal `"OVERVIEW.PARSE_FAILED"`
|
||||
- `OverviewCode` closed union for exhaustive switch statements
|
||||
- `OverviewError` extends `SuaveplanError<OverviewCode>` so `instanceof`
|
||||
narrows `error.code`
|
||||
- Preserves cause chain, context, and trace-id propagation via
|
||||
`SuaveplanErrorOptions`
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { OverviewError, OVERVIEW_CODES } from "@suaveplan/overview";
|
||||
|
||||
throw new OverviewError(
|
||||
OVERVIEW_CODES.UNKNOWN_PACKAGE,
|
||||
"overview.unknown_package",
|
||||
{ context: { pkg: "@suaveplan/missing" } },
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { OverviewError } from "@suaveplan/overview";
|
||||
|
||||
try {
|
||||
// ...
|
||||
} catch (err) {
|
||||
if (err instanceof OverviewError) {
|
||||
switch (err.code) {
|
||||
case "OVERVIEW.CYCLE_DETECTED":
|
||||
// type-narrowed; safe to read err.context?.cycle
|
||||
break;
|
||||
case "OVERVIEW.PARSE_FAILED":
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
The exhaustive `switch` benefits from `OverviewCode` being a closed union —
|
||||
adding a new code without handling it in a downstream `switch` is a TypeScript
|
||||
error.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `OVERVIEW_CODES` — `Readonly<{PARSE_FAILED, INVALID_RECORD, CYCLE_DETECTED,
|
||||
UNKNOWN_PACKAGE, DISCOVERY_ABORTED, INVALID_TIER}>` with literal `OVERVIEW.<X>`
|
||||
values
|
||||
- `type OverviewCode` — closed union of every value in `OVERVIEW_CODES`
|
||||
- `class OverviewError extends SuaveplanError<OverviewCode>` — constructor
|
||||
`(code, messageKey, options?)`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `defineCodes` helper from `@suaveplan/error` freezes its return value so the
|
||||
registry cannot be mutated at runtime. `OverviewError` deliberately re-exposes
|
||||
the `protected` constructor of `SuaveplanError` as `public` (the language
|
||||
requires each subclass to do this explicitly) so callers can `new` an instance
|
||||
directly without a factory. The class is final by convention — sub-classing it
|
||||
would dilute `instanceof` narrowing.
|
||||
90
docs/devtools/tooling/overview/src/graph/graph.md
Normal file
90
docs/devtools/tooling/overview/src/graph/graph.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# `graph/graph.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Implements the graph utilities that operate on a `WorkspaceGraph` built from a
|
||||
list of `PackageRecord`s. Covers the analyses every project tool eventually
|
||||
needs: build a forward / reverse adjacency map, compute a topological
|
||||
dependency-first order, detect cycles, gather transitive deps or dependents,
|
||||
and identify graph roots (no deps) and leaves (no dependents).
|
||||
|
||||
Edges where the target is not also a node are silently dropped at build time.
|
||||
This is the same posture as `scripts/folder-reorg/generate-manifest.ts` —
|
||||
necessary because Suaveplan migrates in waves and partial-manifest
|
||||
snapshots are routine.
|
||||
|
||||
## Features
|
||||
|
||||
- `buildGraph` — populates forward and reverse adjacency in one pass; emits
|
||||
`overview.graph_builds_total`
|
||||
- `topologicalOrder` — Kahn's algorithm with stable lexicographic tie-breaking;
|
||||
throws `OVERVIEW.CYCLE_DETECTED` on a cycle (error carries `context.cycle`)
|
||||
- `detectCycles` — Tarjan's SCC, returns every cycle as a sorted `string[]`
|
||||
- `transitiveDependencies` / `transitiveDependents` — BFS over the appropriate
|
||||
direction; throws `OVERVIEW.UNKNOWN_PACKAGE` for missing inputs
|
||||
- `leafPackages` — no dependents (no incoming edges)
|
||||
- `rootPackages` — no deps (no outgoing edges)
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { buildGraph, topologicalOrder } from "@suaveplan/overview";
|
||||
|
||||
const graph = buildGraph(records);
|
||||
const order = topologicalOrder(graph);
|
||||
// `order[0]` has no workspace deps; the last entry depends on everything.
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
buildGraph,
|
||||
detectCycles,
|
||||
transitiveDependencies,
|
||||
transitiveDependents,
|
||||
leafPackages,
|
||||
rootPackages,
|
||||
} from "@suaveplan/overview";
|
||||
|
||||
const graph = buildGraph(records);
|
||||
const cycles = detectCycles(graph);
|
||||
if (cycles.length > 0) {
|
||||
console.error("Cycles detected:", cycles);
|
||||
}
|
||||
|
||||
const blastRadius = transitiveDependents(graph, "@suaveplan/error");
|
||||
const supplyChain = transitiveDependencies(graph, "@suaveplan/api");
|
||||
const leaves = leafPackages(graph);
|
||||
const roots = rootPackages(graph);
|
||||
```
|
||||
|
||||
`topologicalOrder` is deterministic — two callers with the same graph see the
|
||||
same order — so it can drive reproducible build plans and CI fan-outs.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `buildGraph(records): WorkspaceGraph`
|
||||
- `topologicalOrder(graph): string[]` — throws `OVERVIEW.CYCLE_DETECTED`
|
||||
- `detectCycles(graph): string[][]`
|
||||
- `transitiveDependencies(graph, pkg): string[]` — throws
|
||||
`OVERVIEW.UNKNOWN_PACKAGE`
|
||||
- `transitiveDependents(graph, pkg): string[]` — throws
|
||||
`OVERVIEW.UNKNOWN_PACKAGE`
|
||||
- `leafPackages(graph): string[]`
|
||||
- `rootPackages(graph): string[]`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`topologicalOrder` initialises a remaining-out-degree counter per node and
|
||||
drains nodes with zero out-degree first; the queue is kept sorted on insertion
|
||||
so the output is stable across runs. `detectCycles` uses a recursive
|
||||
Tarjan implementation — the workspace size is bounded (hundreds of packages),
|
||||
so the stack-depth budget is comfortable. BFS in `transitiveDependencies` /
|
||||
`transitiveDependents` uses an array-based queue with `Array.prototype.shift`
|
||||
— acceptable at workspace scale; if the package ever has to handle tens of
|
||||
thousands of nodes the queue can be swapped for a head/tail index pair.
|
||||
62
docs/devtools/tooling/overview/src/rollups/rollups.md
Normal file
62
docs/devtools/tooling/overview/src/rollups/rollups.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# `rollups/rollups.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides the three aggregate views over a `PackageRecord[]` that the manifest
|
||||
needs: plugin roll-ups (one row per `(category, plugin)`), category counts
|
||||
(simple histogram), and tier bucketing (every tier key populated, even when
|
||||
empty). These are pure functions with no side effects, so callers can run them
|
||||
repeatedly without consulting the disk.
|
||||
|
||||
The roll-ups mirror the data that the human-readable `MANIFEST.md` table
|
||||
produces, but typed: downstream tooling can consume them directly instead of
|
||||
re-parsing the Markdown.
|
||||
|
||||
## Features
|
||||
|
||||
- `byPlugin` — sorted `PluginSummary[]`
|
||||
- `byCategory` — `Record<string, number>` of per-category package counts
|
||||
- `byTier` — `Record<Tier, PackageRecord[]>` with every tier key present
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { byPlugin, byCategory, byTier } from "@suaveplan/overview";
|
||||
|
||||
const plugins = byPlugin(records);
|
||||
const counts = byCategory(records);
|
||||
const tiered = byTier(records);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { byTier, TIERS } from "@suaveplan/overview";
|
||||
|
||||
const buckets = byTier(records);
|
||||
for (const tier of TIERS) {
|
||||
console.log(`${tier}: ${buckets[tier].length}`);
|
||||
}
|
||||
```
|
||||
|
||||
The `byTier` output always has every tier key, so downstream code never has to
|
||||
guard against missing keys — a tier with zero packages simply maps to `[]`.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `byPlugin(records): PluginSummary[]` — sorted by `"<category>/<plugin>"`
|
||||
- `byCategory(records): Record<string, number>`
|
||||
- `byTier(records): Record<Tier, PackageRecord[]>` — covers all six tier keys
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`byPlugin` accumulates into a `Map` keyed by `"<category>/<plugin>"`, then
|
||||
sorts by key on emit. `byTier` initialises the output via
|
||||
`Object.fromEntries(TIERS.map(...))` so the result has a stable insertion
|
||||
order matching `TIERS`. None of the helpers emit telemetry on their own — they
|
||||
are pure transformations; instrumentation is the caller's responsibility if
|
||||
needed.
|
||||
68
docs/devtools/tooling/overview/src/schemas/schemas.md
Normal file
68
docs/devtools/tooling/overview/src/schemas/schemas.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# `schemas/schemas.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides Zod schemas (and one helper, `parseTier`) for every public shape that
|
||||
`@suaveplan/overview` produces or accepts. The schemas are the authoritative
|
||||
runtime gate: any `PackageRecord` that does not parse against
|
||||
`PackageRecordSchema` is rejected by `discoverPackages` with
|
||||
`OVERVIEW.INVALID_RECORD`, and `parseTier` converts an unknown string into a
|
||||
typed `Tier` or throws `OVERVIEW.INVALID_TIER`.
|
||||
|
||||
The schemas are kept strict (`.strict()`) so extra fields are rejected — a
|
||||
downstream consumer that wants to attach extension data must clone the record
|
||||
into its own shape rather than smuggle properties through the manifest.
|
||||
|
||||
## Features
|
||||
|
||||
- `TierSchema` — the closed `Tier` enum
|
||||
- `PackageRecordSchema` — every required field, validated with `.strict()`
|
||||
- `PluginSummarySchema` — for plugin roll-ups
|
||||
- `SuavePlanManifestSchema` — the full snapshot, including all six tier-count
|
||||
keys as required numbers
|
||||
- `parseTier` — convenience wrapper that throws an `OverviewError` instead of
|
||||
returning a Zod `safeParse` failure
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { PackageRecordSchema } from "@suaveplan/overview";
|
||||
|
||||
const r = PackageRecordSchema.parse(rawRow);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { SuavePlanManifestSchema, parseTier } from "@suaveplan/overview";
|
||||
|
||||
const manifest = SuavePlanManifestSchema.parse(jsonFromDisk);
|
||||
const userInput: unknown = process.env.OVERVIEW_TIER;
|
||||
const tier = parseTier(userInput); // throws OVERVIEW.INVALID_TIER on bad input
|
||||
```
|
||||
|
||||
The strict schemas allow consumers to safely round-trip a manifest through JSON
|
||||
without losing fields or accidentally importing arbitrary extensions: a malformed
|
||||
JSON payload simply fails the parse.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `TierSchema: z.ZodType<Tier>`
|
||||
- `PackageRecordSchema: z.ZodObject<...>` (strict, eleven fields)
|
||||
- `PluginSummarySchema: z.ZodObject<...>` (strict, four fields)
|
||||
- `SuavePlanManifestSchema: z.ZodObject<...>` (strict, four fields)
|
||||
- `parseTier(input: unknown): Tier` — throws `OverviewError`
|
||||
(`OVERVIEW.INVALID_TIER`) on failure
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The schemas use `z.enum(TIERS as readonly [Tier, ...Tier[]])` so the source of
|
||||
truth is the `TIERS` tuple in `types/types.ts`; adding a new tier requires
|
||||
updating that tuple (and the `tierCounts` shape) — both compile-time changes
|
||||
that the test suite catches. `parseTier` deliberately surfaces a package-shaped
|
||||
error rather than re-throwing the Zod `ZodError` so call sites only ever deal
|
||||
with one error class.
|
||||
73
docs/devtools/tooling/overview/src/telemetry/telemetry.md
Normal file
73
docs/devtools/tooling/overview/src/telemetry/telemetry.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# `telemetry/telemetry.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Owns the OpenTelemetry meter for `@suaveplan/overview` and exposes the
|
||||
three counters and one histogram that the spec requires. The meter is
|
||||
constructed lazily at module import time via
|
||||
`metrics.getMeter("@suaveplan/overview", "0.1.0")` so a no-op OTel SDK is the
|
||||
default — there is no instrumentation cost on the cold path when no SDK is
|
||||
installed.
|
||||
|
||||
Every metric is exported as a typed `Counter` or `Histogram` so other modules
|
||||
can `.add` / `.record` against them directly without going through a wrapper.
|
||||
|
||||
## Features
|
||||
|
||||
- Single meter scoped to the package name + version
|
||||
- `overview.packages_discovered_total` — counter
|
||||
- `overview.records_rejected_total{reason}` — counter (reason is one of
|
||||
`parse_failed` | `invalid_record`)
|
||||
- `overview.graph_builds_total` — counter
|
||||
- `overview.discover_duration_ms` — histogram with millisecond unit
|
||||
- `RejectionReason` typed alias for the `reason` attribute
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { packagesDiscoveredTotal } from "@suaveplan/overview/telemetry";
|
||||
packagesDiscoveredTotal.add(7);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
discoverDurationMs,
|
||||
recordsRejectedTotal,
|
||||
type RejectionReason,
|
||||
} from "@suaveplan/overview/telemetry";
|
||||
|
||||
const start = performance.now();
|
||||
const result = await runDiscovery();
|
||||
discoverDurationMs.record(performance.now() - start);
|
||||
|
||||
if (!result.ok) {
|
||||
const reason: RejectionReason = result.reason;
|
||||
recordsRejectedTotal.add(1, { reason });
|
||||
}
|
||||
```
|
||||
|
||||
Plug `@opentelemetry/sdk-metrics` in your runtime to harvest these metrics; no
|
||||
adapter glue is needed inside `@suaveplan/overview`.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `packagesDiscoveredTotal: Counter`
|
||||
- `recordsRejectedTotal: Counter` (attribute: `reason: "parse_failed" |
|
||||
"invalid_record"`)
|
||||
- `graphBuildsTotal: Counter`
|
||||
- `discoverDurationMs: Histogram` (unit: `ms`)
|
||||
- `type RejectionReason = "parse_failed" | "invalid_record"`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The meter is obtained once at module load and cached as a `const`. With the
|
||||
no-op SDK installed (the default in test environments without a configured
|
||||
OTel SDK), every `.add` and `.record` call is a fast no-op — there is no need
|
||||
to feature-flag instrumentation. Tests use the SDK's `InMemoryMetricReader`
|
||||
to assert that the counters and histogram move when discovery runs.
|
||||
73
docs/devtools/tooling/overview/src/tier/tier.md
Normal file
73
docs/devtools/tooling/overview/src/tier/tier.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# `tier/tier.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Computes the deterministic `Tier` label for a package given its shortname and
|
||||
the parsed `package.json` shape. The inference order mirrors
|
||||
`scripts/folder-reorg/generate-manifest.ts` exactly, so consumers can rely on
|
||||
the library and the script producing identical tier assignments for the same
|
||||
input.
|
||||
|
||||
The function is pure and synchronous. It takes no side-effects, so it is safe
|
||||
to call from any tier — including the universal root barrel — even though
|
||||
some of the dep hints it inspects are Node-only.
|
||||
|
||||
## Features
|
||||
|
||||
- Six-rung priority cascade — python → r3f → react → browser → server →
|
||||
universal
|
||||
- Exported `SERVER_DEP_HINTS` set for downstream introspection
|
||||
- Optional `extraHints` parameter merges user-supplied server-tier dep names
|
||||
with the built-in set
|
||||
- Pure / synchronous — zero I/O, zero allocations on the universal fallback
|
||||
path beyond the merged-hint set
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { inferTier } from "@suaveplan/overview";
|
||||
|
||||
const tier = inferTier("foo", {
|
||||
dependencies: { "node:fs": "*" },
|
||||
});
|
||||
// tier === "server"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { inferTier, SERVER_DEP_HINTS } from "@suaveplan/overview";
|
||||
|
||||
const extra = new Set(["custom-native-binding"]);
|
||||
const tier = inferTier(
|
||||
"thing",
|
||||
{ dependencies: { "custom-native-binding": "*" } },
|
||||
extra,
|
||||
);
|
||||
|
||||
// SERVER_DEP_HINTS exposes the built-in set for tooling that wants to
|
||||
// surface the rule set in a UI without re-declaring it.
|
||||
console.log([...SERVER_DEP_HINTS]);
|
||||
```
|
||||
|
||||
The optional third argument is the path for tooling that wants to extend the
|
||||
hint set (e.g. a project-local native-binding registry) without forking the
|
||||
library. The base set is read-only and never mutated.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `inferTier(short: string, pkg: RawPackageJson, extraHints?: ReadonlySet<string>): Tier`
|
||||
- `const SERVER_DEP_HINTS: ReadonlySet<string>` — eighteen built-in entries
|
||||
spanning `node:*` modules and known native bindings
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The function performs at most one shallow `Object.keys` pass over each of
|
||||
`peerDependencies`, `engines`, `dependencies`, and `devDependencies` — O(1)
|
||||
work per `package.json` in practice. The `mergeHints` helper allocates a new
|
||||
`Set` only when `extraHints` carries entries; otherwise it returns the base
|
||||
set unchanged so the common path is allocation-free.
|
||||
84
docs/devtools/tooling/overview/src/types/types.md
Normal file
84
docs/devtools/tooling/overview/src/types/types.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# `types/types.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Declares the type-level vocabulary of `@suaveplan/overview`: the closed `Tier`
|
||||
union, the `PackageRecord` row, the `PluginSummary` aggregate, the
|
||||
`SuavePlanManifest` snapshot, the `WorkspaceGraph` adjacency structure, and the
|
||||
`RawPackageJson` narrowing of the on-disk `package.json` fields that influence
|
||||
tier inference.
|
||||
|
||||
Every other module in the package consumes these shapes. There is no runtime
|
||||
code in this file — only `export type` / `export interface` declarations and
|
||||
the `TIERS` constant array used for stable iteration order across roll-ups and
|
||||
tests.
|
||||
|
||||
## Features
|
||||
|
||||
- Closed `Tier` union with documented priority order
|
||||
- `TIERS` ordered tuple for deterministic iteration in roll-ups and tests
|
||||
- `readonly`-deep `PackageRecord` shape — no accidental mutation downstream
|
||||
- `PluginSummary` matches the columns produced by
|
||||
`scripts/folder-reorg/generate-manifest.ts`
|
||||
- `SuavePlanManifest` carries every aggregate that the Markdown manifest emits
|
||||
- `WorkspaceGraph` exposes both forward + reverse adjacency for O(1) traversal
|
||||
- `RawPackageJson` narrows `package.json` to only the fields the package reads
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import type { PackageRecord, Tier } from "@suaveplan/overview";
|
||||
|
||||
const record: PackageRecord = {
|
||||
name: "@suaveplan/x",
|
||||
short: "x",
|
||||
category: "foundation",
|
||||
plugin: "core",
|
||||
dirName: "x",
|
||||
version: "0.1.0",
|
||||
description: "",
|
||||
tier: "universal",
|
||||
workspaceDeps: [],
|
||||
hasBin: false,
|
||||
hasBrowserField: false,
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { TIERS, type SuavePlanManifest, type Tier } from "@suaveplan/overview";
|
||||
|
||||
function totalCount(manifest: SuavePlanManifest): number {
|
||||
return TIERS.reduce<number>(
|
||||
(acc, tier: Tier) => acc + manifest.tierCounts[tier],
|
||||
0,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`TIERS` is ordered for human-readable output (universal first, python last);
|
||||
runtime code that needs the closed enum but is happy with any order can iterate
|
||||
`Object.keys(manifest.tierCounts) as Tier[]`.
|
||||
|
||||
## API Reference
|
||||
|
||||
- `type Tier = "python" | "r3f" | "react" | "browser" | "server" | "universal"`
|
||||
- `const TIERS: readonly Tier[]` — fixed ordering
|
||||
- `interface PackageRecord` — `readonly` row, eleven fields
|
||||
- `interface PluginSummary` — `{category, plugin, packages, workspaceDeps}`
|
||||
- `interface SuavePlanManifest` — `{packages, plugins, categories, tierCounts}`
|
||||
- `interface WorkspaceGraph` — `{nodes, forward, reverse}`
|
||||
- `interface RawPackageJson` — only the fields read by `inferTier`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Types live in a sibling `.types.ts` is not appropriate here because this file
|
||||
defines the cross-module vocabulary rather than the types of a single feature
|
||||
implementation. It is referenced directly by `src/index.ts` and every other
|
||||
feature folder. The `readonly` modifiers cascade so downstream consumers cannot
|
||||
accidentally mutate manifests they receive; mutation must be a conscious clone.
|
||||
Reference in New Issue
Block a user