@typra/emitter 0.2.5 → 0.2.7

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.
package/README.md CHANGED
@@ -11,9 +11,14 @@ ship runtime service implementations or product-specific contracts.
11
11
  ## Install
12
12
 
13
13
  ```powershell
14
- npm install --save-dev @typra/emitter @typespec/compiler
14
+ npm install --save-dev @typra/emitter @typespec/compiler@1.10.0 @typespec/json-schema@1.10.0
15
15
  ```
16
16
 
17
+ Typra currently validates against TypeSpec compiler and JSON schema emitter
18
+ `1.10.0`. Unvalidated TypeSpec versions report a clear diagnostic during emit;
19
+ set `allow-unsupported-typespec-version: true` only when you intentionally accept
20
+ possible generated output churn.
21
+
17
22
  ## Configure TypeSpec
18
23
 
19
24
  Add the emitter to `tspconfig.yaml`:
@@ -50,12 +55,21 @@ npx tsp compile ./path/to/main.tsp --config ./tspconfig.yaml
50
55
 
51
56
  ## CLI
52
57
 
53
- The package includes the `typra-generate` command:
58
+ The package includes `typra-generate`, `typra-verify`, and a generic
59
+ `typra-consumer-smoke` harness:
54
60
 
55
61
  ```powershell
56
62
  npx typra-generate --help
63
+ npx typra-verify --baseline ./baseline --current ./generated
64
+ npx typra-consumer-smoke --config ./typra-smoke.json
57
65
  ```
58
66
 
67
+ `typra-verify` compares committed `.typra-generated` metadata against current
68
+ generated metadata and prints deterministic review summaries for exports,
69
+ protocols, files, package/module identity, toolchain, protected paths, schema
70
+ evolution, stale cleanup dry-runs, hydration seams, and breaking-change
71
+ classification. It never deletes files.
72
+
59
73
  ## Supported output
60
74
 
61
75
  Typra includes emitters for:
@@ -78,6 +92,18 @@ Generated source files include Typra markers, and the emitter records a
78
92
  generated-file manifest for each output root. Stale-file deletion is not enabled
79
93
  yet, so Typra will not remove hand-authored runtime files.
80
94
 
95
+ Consumers can declare hand-authored boundaries in verifier config:
96
+
97
+ ```json
98
+ {
99
+ "protectedPaths": ["src/adapters/**"],
100
+ "hydrationZones": ["src/extensions/**"]
101
+ }
102
+ ```
103
+
104
+ The emitter records hydration seam metadata for generated protocol adapters, but
105
+ runtime behavior remains hand-authored by the consuming project.
106
+
81
107
  ## Links
82
108
 
83
109
  - Repository: <https://github.com/sethjuarez/typra>
@@ -1,5 +1,16 @@
1
1
  import { EmitContext } from "@typespec/compiler";
2
2
  import { TypraEmitterOptions } from "../lib.js";
3
+ export interface GeneratedManifestEntry {
4
+ outputRoot: string;
5
+ path: string;
6
+ marker: boolean;
7
+ }
8
+ export interface GeneratedManifest {
9
+ emitter: "typra-emitter";
10
+ version: 1;
11
+ generatedAt: string;
12
+ files: GeneratedManifestEntry[];
13
+ }
3
14
  export declare function emitGeneratedFile(context: EmitContext<TypraEmitterOptions>, filePath: string, content: string, options?: {
4
15
  marker?: boolean;
5
16
  outputRoot?: string;
@@ -0,0 +1,32 @@
1
+ import { EmitContext, NoTarget } from "@typespec/compiler";
2
+ import { TypraEmitterOptions } from "./lib.js";
3
+ export declare const SUPPORTED_TYPESPEC_COMPILER_VERSION = "1.10.0";
4
+ export declare const SUPPORTED_TYPESPEC_JSON_SCHEMA_VERSION = "1.10.0";
5
+ export interface ToolchainPackageMetadata {
6
+ name: string;
7
+ version: string;
8
+ supportedRange: string;
9
+ supported: boolean;
10
+ }
11
+ export interface ToolchainMetadata {
12
+ packages: ToolchainPackageMetadata[];
13
+ }
14
+ interface CompatibilityDiagnosticContext {
15
+ options: Pick<TypraEmitterOptions, "allow-unsupported-typespec-version">;
16
+ program: {
17
+ reportDiagnostic(diagnostic: {
18
+ code: string;
19
+ message: string;
20
+ severity: "error" | "warning";
21
+ target: typeof NoTarget;
22
+ }): void;
23
+ };
24
+ }
25
+ export declare function getToolchainMetadata(): ToolchainMetadata;
26
+ export declare function buildToolchainMetadata(packages: Array<Omit<ToolchainPackageMetadata, "supported"> & Partial<Pick<ToolchainPackageMetadata, "supported">>>): ToolchainMetadata;
27
+ export declare function reportTypeSpecCompatibility(context: EmitContext<TypraEmitterOptions>): ToolchainMetadata;
28
+ export declare function shouldBlockUnsupportedTypeSpecToolchain(options: Pick<TypraEmitterOptions, "allow-unsupported-typespec-version">, toolchain: ToolchainMetadata): boolean;
29
+ export declare function reportToolchainCompatibility(context: CompatibilityDiagnosticContext, toolchain: ToolchainMetadata): void;
30
+ export declare function getUnsupportedTypeSpecPackages(toolchain: ToolchainMetadata): ToolchainPackageMetadata[];
31
+ export declare function formatUnsupportedTypeSpecVersionMessage(unsupported: ToolchainPackageMetadata[], allowedUnsupportedVersion: boolean): string;
32
+ export {};
@@ -0,0 +1,96 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { fileURLToPath } from "node:url";
5
+ import { NoTarget } from "@typespec/compiler";
6
+ export const SUPPORTED_TYPESPEC_COMPILER_VERSION = "1.10.0";
7
+ export const SUPPORTED_TYPESPEC_JSON_SCHEMA_VERSION = "1.10.0";
8
+ const require = createRequire(import.meta.url);
9
+ export function getToolchainMetadata() {
10
+ const emitterVersion = resolveNearestPackageVersion("@typra/emitter", dirname(fileURLToPath(import.meta.url)));
11
+ return buildToolchainMetadata([
12
+ {
13
+ name: "@typespec/compiler",
14
+ version: resolveInstalledPackageVersion("@typespec/compiler"),
15
+ supportedRange: SUPPORTED_TYPESPEC_COMPILER_VERSION,
16
+ },
17
+ {
18
+ name: "@typespec/json-schema",
19
+ version: resolveInstalledPackageVersion("@typespec/json-schema"),
20
+ supportedRange: SUPPORTED_TYPESPEC_JSON_SCHEMA_VERSION,
21
+ },
22
+ {
23
+ name: "@typra/emitter",
24
+ version: emitterVersion,
25
+ supportedRange: emitterVersion,
26
+ },
27
+ ]);
28
+ }
29
+ export function buildToolchainMetadata(packages) {
30
+ return {
31
+ packages: packages
32
+ .map((entry) => ({
33
+ ...entry,
34
+ supported: entry.supported ?? isSupportedVersion(entry.version, entry.supportedRange),
35
+ }))
36
+ .sort((left, right) => left.name.localeCompare(right.name)),
37
+ };
38
+ }
39
+ export function reportTypeSpecCompatibility(context) {
40
+ const toolchain = getToolchainMetadata();
41
+ reportToolchainCompatibility(context, toolchain);
42
+ return toolchain;
43
+ }
44
+ export function shouldBlockUnsupportedTypeSpecToolchain(options, toolchain) {
45
+ return options["allow-unsupported-typespec-version"] !== true && getUnsupportedTypeSpecPackages(toolchain).length > 0;
46
+ }
47
+ export function reportToolchainCompatibility(context, toolchain) {
48
+ const unsupported = getUnsupportedTypeSpecPackages(toolchain);
49
+ if (unsupported.length === 0) {
50
+ return;
51
+ }
52
+ context.program.reportDiagnostic({
53
+ code: "typra-emitter-unsupported-typespec-version",
54
+ message: formatUnsupportedTypeSpecVersionMessage(unsupported, context.options["allow-unsupported-typespec-version"] === true),
55
+ severity: context.options["allow-unsupported-typespec-version"] === true ? "warning" : "error",
56
+ target: NoTarget,
57
+ });
58
+ }
59
+ export function getUnsupportedTypeSpecPackages(toolchain) {
60
+ return toolchain.packages.filter((entry) => (entry.name === "@typespec/compiler" || entry.name === "@typespec/json-schema") && !entry.supported);
61
+ }
62
+ export function formatUnsupportedTypeSpecVersionMessage(unsupported, allowedUnsupportedVersion) {
63
+ const actual = unsupported.map((entry) => `${entry.name}@${entry.version}`).join(", ");
64
+ const expected = unsupported.map((entry) => `${entry.name}@${entry.supportedRange}`).join(", ");
65
+ const action = allowedUnsupportedVersion
66
+ ? "Generation will continue because allow-unsupported-typespec-version is enabled."
67
+ : "Pin the TypeSpec toolchain to the supported versions, or set allow-unsupported-typespec-version: true to continue with a warning.";
68
+ return `@typra/emitter has only been validated with ${expected}; found ${actual}. ${action}`;
69
+ }
70
+ function isSupportedVersion(version, supportedRange) {
71
+ return version === supportedRange;
72
+ }
73
+ function resolveInstalledPackageVersion(packageName) {
74
+ try {
75
+ const packageEntry = require.resolve(packageName);
76
+ return resolveNearestPackageVersion(packageName, dirname(packageEntry));
77
+ }
78
+ catch {
79
+ return "unresolved";
80
+ }
81
+ }
82
+ function resolveNearestPackageVersion(expectedPackageName, startDir) {
83
+ let current = startDir;
84
+ while (current !== dirname(current)) {
85
+ const candidate = join(current, "package.json");
86
+ if (existsSync(candidate)) {
87
+ const metadata = JSON.parse(readFileSync(candidate, "utf8"));
88
+ if (metadata.name === expectedPackageName && typeof metadata.version === "string") {
89
+ return metadata.version;
90
+ }
91
+ }
92
+ current = dirname(current);
93
+ }
94
+ return "unresolved";
95
+ }
96
+ //# sourceMappingURL=compatibility.js.map
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { parseArgs } from "node:util";
5
+ import { verifyTypraMetadata } from "./verify/index.js";
6
+ const HELP = `
7
+ typra-consumer-smoke - Run a generic Typra consumer smoke harness
8
+
9
+ Usage:
10
+ npx typra-consumer-smoke --config typra-smoke.json
11
+
12
+ Config shape:
13
+ {
14
+ "install": ["npm ci"],
15
+ "generate": ["npx tsp compile ./typespec/main.tsp --config ./tspconfig.yaml"],
16
+ "verify": { "baseline": "./baseline", "current": "./generated" },
17
+ "smoke": ["npm test"]
18
+ }
19
+ `;
20
+ async function main() {
21
+ const { values } = parseArgs({
22
+ options: {
23
+ config: { type: "string" },
24
+ help: { type: "boolean", short: "h" },
25
+ },
26
+ });
27
+ if (values.help) {
28
+ console.log(HELP);
29
+ process.exit(0);
30
+ }
31
+ if (!values.config) {
32
+ console.error("Error: --config is required.\n");
33
+ console.log(HELP);
34
+ process.exit(1);
35
+ }
36
+ const config = readConfig(values.config);
37
+ runCommands("install", config.install ?? []);
38
+ runCommands("generate", config.generate ?? []);
39
+ if (config.verify) {
40
+ const result = verifyTypraMetadata({
41
+ baselineRoot: config.verify.baseline,
42
+ currentRoot: config.verify.current,
43
+ configPath: config.verify.config,
44
+ });
45
+ if (!result.ok) {
46
+ console.error(JSON.stringify(result, null, 2));
47
+ process.exit(1);
48
+ }
49
+ }
50
+ runCommands("smoke", config.smoke ?? []);
51
+ }
52
+ function readConfig(configPath) {
53
+ if (!existsSync(configPath)) {
54
+ throw new Error(`Missing consumer smoke config: ${configPath}`);
55
+ }
56
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
57
+ for (const key of ["install", "generate", "smoke"]) {
58
+ if (config[key] !== undefined && (!Array.isArray(config[key]) || config[key]?.some((entry) => typeof entry !== "string"))) {
59
+ throw new Error(`Invalid consumer smoke config: ${key} must be an array of commands.`);
60
+ }
61
+ }
62
+ if (config.verify && (typeof config.verify.baseline !== "string" || typeof config.verify.current !== "string")) {
63
+ throw new Error("Invalid consumer smoke config: verify.baseline and verify.current are required strings.");
64
+ }
65
+ return config;
66
+ }
67
+ function runCommands(label, commands) {
68
+ for (const command of commands) {
69
+ console.log(`[typra-consumer-smoke:${label}] ${command}`);
70
+ execFileSync(command, {
71
+ cwd: process.cwd(),
72
+ shell: true,
73
+ stdio: "inherit",
74
+ });
75
+ }
76
+ }
77
+ main().catch((error) => {
78
+ console.error(error instanceof Error ? error.message : String(error));
79
+ process.exit(1);
80
+ });
81
+ //# sourceMappingURL=consumer-smoke.js.map
@@ -1,6 +1,7 @@
1
1
  import { EmitContext } from "@typespec/compiler";
2
2
  import { EmitTarget, TypraEmitterOptions } from "./lib.js";
3
3
  import { TypeNode } from "./ir/ast.js";
4
+ import { ToolchainMetadata } from "./compatibility.js";
4
5
  export interface ExportSurfaceMethod {
5
6
  name: string;
6
7
  returns: string;
@@ -11,6 +12,8 @@ export interface ExportSurfaceMethod {
11
12
  export interface ExportSurfaceProtocol {
12
13
  name: string;
13
14
  group: string;
15
+ symbol: string;
16
+ source: string;
14
17
  methods: ExportSurfaceMethod[];
15
18
  }
16
19
  export interface ExportSurfaceEntry {
@@ -39,6 +42,7 @@ export interface TargetExportSurface {
39
42
  export interface ExportSurfaceSnapshot {
40
43
  emitter: "typra-emitter";
41
44
  version: 1;
45
+ toolchain: ToolchainMetadata;
42
46
  root: {
43
47
  object: string;
44
48
  namespace: string;
@@ -46,5 +50,5 @@ export interface ExportSurfaceSnapshot {
46
50
  };
47
51
  targets: TargetExportSurface[];
48
52
  }
49
- export declare function buildExportSurfaceSnapshot(rootObject: string, rootNamespace: string, rootAlias: string, targets: EmitTarget[], nodes: TypeNode[]): ExportSurfaceSnapshot;
53
+ export declare function buildExportSurfaceSnapshot(rootObject: string, rootNamespace: string, rootAlias: string, targets: EmitTarget[], nodes: TypeNode[], toolchain?: ToolchainMetadata): ExportSurfaceSnapshot;
50
54
  export declare function emitExportSurfaceSnapshot(context: EmitContext<TypraEmitterOptions>, snapshot: ExportSurfaceSnapshot): Promise<void>;
@@ -1,9 +1,11 @@
1
1
  import { emitFile, resolvePath } from "@typespec/compiler";
2
2
  import { toKebabCase, toSnakeCase } from "./ir/utilities.js";
3
- export function buildExportSurfaceSnapshot(rootObject, rootNamespace, rootAlias, targets, nodes) {
3
+ import { getToolchainMetadata } from "./compatibility.js";
4
+ export function buildExportSurfaceSnapshot(rootObject, rootNamespace, rootAlias, targets, nodes, toolchain = getToolchainMetadata()) {
4
5
  return {
5
6
  emitter: "typra-emitter",
6
7
  version: 1,
8
+ toolchain,
7
9
  root: {
8
10
  object: rootObject,
9
11
  namespace: rootNamespace,
@@ -32,6 +34,8 @@ function buildTargetSurface(rootNamespace, target, nodes) {
32
34
  .map((node) => ({
33
35
  name: node.typeName.name,
34
36
  group: node.group || "",
37
+ symbol: node.typeName.name,
38
+ source: sourceFor(targetName, node, node.group || ""),
35
39
  methods: node.methods
36
40
  .map((method) => ({
37
41
  name: method.name,
@@ -8,6 +8,8 @@ import { generateGo } from "./languages/go/driver.js";
8
8
  import { generateRust } from "./languages/rust/driver.js";
9
9
  import { emitGeneratedFile, emitGeneratedManifest } from "./cleanup/generated-file.js";
10
10
  import { buildExportSurfaceSnapshot, emitExportSurfaceSnapshot } from "./contract-surface.js";
11
+ import { reportTypeSpecCompatibility, shouldBlockUnsupportedTypeSpecToolchain } from "./compatibility.js";
12
+ import { buildHydrationBoundarySnapshot, emitHydrationBoundarySnapshot } from "./hydration-seams.js";
11
13
  /**
12
14
  * Filter nodes based on omit-models option.
13
15
  * Matches against model name (e.g., "AgentManifest") or fully qualified name (e.g., "Prompty.AgentManifest")
@@ -69,6 +71,10 @@ const generators = {
69
71
  rust: generateRust,
70
72
  };
71
73
  export async function $onEmit(context) {
74
+ const toolchain = reportTypeSpecCompatibility(context);
75
+ if (shouldBlockUnsupportedTypeSpecToolchain(context.options, toolchain)) {
76
+ return;
77
+ }
72
78
  const options = {
73
79
  emitterOutputDir: context.emitterOutputDir,
74
80
  ...context.options,
@@ -159,7 +165,9 @@ export async function $onEmit(context) {
159
165
  }
160
166
  }
161
167
  await emitGeneratedFile(context, resolvePath(context.emitterOutputDir, "json-ast", "model.json"), JSON.stringify(model.getSanitizedObject(), null, 2), { marker: false });
162
- await emitExportSurfaceSnapshot(context, buildExportSurfaceSnapshot(rootObject, rootNamespace, rootAlias, targets, exportSurfaceNodes));
168
+ const exportSurfaceSnapshot = buildExportSurfaceSnapshot(rootObject, rootNamespace, rootAlias, targets, exportSurfaceNodes, toolchain);
169
+ await emitExportSurfaceSnapshot(context, exportSurfaceSnapshot);
170
+ await emitHydrationBoundarySnapshot(context, buildHydrationBoundarySnapshot(exportSurfaceSnapshot, options));
163
171
  await emitGeneratedManifest(context);
164
172
  }
165
173
  //# sourceMappingURL=emitter.js.map
@@ -0,0 +1,20 @@
1
+ import { EmitContext } from "@typespec/compiler";
2
+ import { ExportSurfaceSnapshot } from "./contract-surface.js";
3
+ import { TypraEmitterOptions } from "./lib.js";
4
+ export interface HydrationSeam {
5
+ contract: string;
6
+ target: string;
7
+ group: string;
8
+ symbol: string;
9
+ generatedSource: string;
10
+ seamKind: "protocol-adapter";
11
+ }
12
+ export interface HydrationBoundarySnapshot {
13
+ emitter: "typra-emitter";
14
+ version: 1;
15
+ protectedPaths: string[];
16
+ hydrationZones: string[];
17
+ seams: HydrationSeam[];
18
+ }
19
+ export declare function buildHydrationBoundarySnapshot(exportSurface: ExportSurfaceSnapshot, options: Pick<TypraEmitterOptions, "protected-paths" | "hydration-zones">): HydrationBoundarySnapshot;
20
+ export declare function emitHydrationBoundarySnapshot(context: EmitContext<TypraEmitterOptions>, snapshot: HydrationBoundarySnapshot): Promise<void>;
@@ -0,0 +1,32 @@
1
+ import { emitFile, resolvePath } from "@typespec/compiler";
2
+ export function buildHydrationBoundarySnapshot(exportSurface, options) {
3
+ return {
4
+ emitter: "typra-emitter",
5
+ version: 1,
6
+ protectedPaths: uniqueSorted(options["protected-paths"] ?? []),
7
+ hydrationZones: uniqueSorted(options["hydration-zones"] ?? []),
8
+ seams: exportSurface.targets
9
+ .flatMap((target) => target.protocols.map((protocol) => ({
10
+ contract: protocol.name,
11
+ target: target.target,
12
+ group: protocol.group,
13
+ symbol: protocol.symbol,
14
+ generatedSource: protocol.source,
15
+ seamKind: "protocol-adapter",
16
+ })))
17
+ .sort((left, right) => seamKey(left).localeCompare(seamKey(right))),
18
+ };
19
+ }
20
+ export async function emitHydrationBoundarySnapshot(context, snapshot) {
21
+ await emitFile(context.program, {
22
+ path: resolvePath(context.emitterOutputDir, ".typra-generated", "hydration-seams.json"),
23
+ content: `${JSON.stringify(snapshot, null, 2)}\n`,
24
+ });
25
+ }
26
+ function uniqueSorted(values) {
27
+ return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
28
+ }
29
+ function seamKey(seam) {
30
+ return `${seam.target}:${seam.group}:${seam.contract}:${seam.symbol}`;
31
+ }
32
+ //# sourceMappingURL=hydration-seams.js.map
@@ -2,3 +2,5 @@ export { $onEmit, GeneratorOptions, filterNodes } from "./emitter.js";
2
2
  export { $lib } from "./lib.js";
3
3
  export { $sample, $abstract, $coerce, $factory, $method, $knownAs, $defaultFor, $protocol } from "./decorators.js";
4
4
  export { generate, GenerateOptions, GenerateResult, TargetLanguage, TargetOptions } from "./generate.js";
5
+ export { compareTypraMetadata, formatVerifySummary, loadTypraMetadata, loadVerifyConfig, verifyTypraMetadata, TypraMetadataSet, TypraVerifyConfig, TypraVerifyFailure, TypraVerifyResult, TypraVerifySummary, } from "./verify/index.js";
6
+ export { buildHydrationBoundarySnapshot, emitHydrationBoundarySnapshot, HydrationBoundarySnapshot, HydrationSeam, } from "./hydration-seams.js";
package/dist/src/index.js CHANGED
@@ -2,4 +2,6 @@ export { $onEmit, filterNodes } from "./emitter.js";
2
2
  export { $lib } from "./lib.js";
3
3
  export { $sample, $abstract, $coerce, $factory, $method, $knownAs, $defaultFor, $protocol } from "./decorators.js";
4
4
  export { generate } from "./generate.js";
5
+ export { compareTypraMetadata, formatVerifySummary, loadTypraMetadata, loadVerifyConfig, verifyTypraMetadata, } from "./verify/index.js";
6
+ export { buildHydrationBoundarySnapshot, emitHydrationBoundarySnapshot, } from "./hydration-seams.js";
5
7
  //# sourceMappingURL=index.js.map
package/dist/src/lib.d.ts CHANGED
@@ -18,6 +18,9 @@ export interface TypraEmitterOptions {
18
18
  "omit-models"?: string[];
19
19
  "schema-output-dir"?: string;
20
20
  "additional-roots"?: string[];
21
+ "allow-unsupported-typespec-version"?: boolean;
22
+ "protected-paths"?: string[];
23
+ "hydration-zones"?: string[];
21
24
  }
22
25
  export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{
23
26
  [code: string]: import("@typespec/compiler").DiagnosticMessages;
package/dist/src/lib.js CHANGED
@@ -82,6 +82,24 @@ const TypraEmitterOptionsSchema = {
82
82
  items: { type: "string" },
83
83
  nullable: true,
84
84
  description: "Additional root types to resolve and generate alongside the main root object. These types need not be referenced from the main root. Specified as fully-qualified names (e.g., 'Typra.Message')."
85
+ },
86
+ "allow-unsupported-typespec-version": {
87
+ type: "boolean",
88
+ nullable: true,
89
+ default: false,
90
+ description: "Allow generation with an unvalidated TypeSpec compiler/json-schema version. Unsupported versions report a warning instead of an error."
91
+ },
92
+ "protected-paths": {
93
+ type: "array",
94
+ items: { type: "string" },
95
+ nullable: true,
96
+ description: "Hand-authored paths Typra must not own. Recorded for verifier boundary checks; generation still does not delete files."
97
+ },
98
+ "hydration-zones": {
99
+ type: "array",
100
+ items: { type: "string" },
101
+ nullable: true,
102
+ description: "Hand-authored extension zones adjacent to generated output. Recorded as verifier boundary metadata; Typra does not generate runtime behavior."
85
103
  }
86
104
  },
87
105
  required: ["root-object"],
@@ -0,0 +1,140 @@
1
+ import { ExportSurfaceSnapshot } from "../contract-surface.js";
2
+ import { GeneratedManifest } from "../cleanup/generated-file.js";
3
+ import { HydrationBoundarySnapshot } from "../hydration-seams.js";
4
+ export interface TypraMetadataSet {
5
+ exportSurface: ExportSurfaceSnapshot;
6
+ manifest: GeneratedManifest;
7
+ model?: SchemaNode;
8
+ hydration?: HydrationBoundarySnapshot;
9
+ }
10
+ export interface TypraVerifyConfig {
11
+ protectedPaths?: string[];
12
+ hydrationZones?: string[];
13
+ }
14
+ export interface TypraVerifyFailure {
15
+ code: string;
16
+ message: string;
17
+ blocking: boolean;
18
+ }
19
+ export interface TypraVerifySummary {
20
+ exports: {
21
+ added: number;
22
+ removed: number;
23
+ changed: number;
24
+ };
25
+ protocols: {
26
+ added: number;
27
+ removed: number;
28
+ changed: number;
29
+ };
30
+ files: {
31
+ added: number;
32
+ deleted: number;
33
+ ownershipChanged: number;
34
+ };
35
+ packageNamesChanged: number;
36
+ modulesChanged: number;
37
+ toolchain: {
38
+ changed: number;
39
+ unsupported: number;
40
+ };
41
+ protectedPathTouches: number;
42
+ hydrationZoneTouches: number;
43
+ staleCleanupCandidates: number;
44
+ schema: {
45
+ addedTypes: number;
46
+ removedTypes: number;
47
+ addedOptionalProperties: number;
48
+ addedRequiredProperties: number;
49
+ removedProperties: number;
50
+ requirednessChanged: number;
51
+ propertyTypesChanged: number;
52
+ wireNamesChanged: number;
53
+ discriminatorsChanged: number;
54
+ enumValuesChanged: number;
55
+ };
56
+ }
57
+ export interface TypraVerifyResult {
58
+ ok: boolean;
59
+ breakingChange: "patch" | "minor" | "major";
60
+ summary: TypraVerifySummary;
61
+ failures: TypraVerifyFailure[];
62
+ schemaEvolution: SchemaEvolutionChange[];
63
+ conformanceMap: ConformanceMapEntry[];
64
+ staleCleanupDryRun: StaleCleanupCandidate[];
65
+ hydrationBoundaries: HydrationBoundaryReport;
66
+ }
67
+ export interface SchemaNode {
68
+ typeName?: {
69
+ namespace?: string;
70
+ name?: string;
71
+ };
72
+ base?: {
73
+ namespace?: string;
74
+ name?: string;
75
+ };
76
+ isAbstract?: boolean;
77
+ isProtocol?: boolean;
78
+ discriminator?: string;
79
+ childTypes?: SchemaNode[];
80
+ properties?: SchemaProperty[];
81
+ }
82
+ export interface SchemaProperty {
83
+ name?: string;
84
+ typeName?: {
85
+ namespace?: string;
86
+ name?: string;
87
+ };
88
+ isOptional?: boolean;
89
+ knownAs?: Array<{
90
+ provider?: string;
91
+ name?: string;
92
+ }>;
93
+ allowedValues?: string[];
94
+ enumName?: string | null;
95
+ isOpenEnum?: boolean;
96
+ isScalar?: boolean;
97
+ isCollection?: boolean;
98
+ isAny?: boolean;
99
+ isDict?: boolean;
100
+ type?: SchemaNode;
101
+ }
102
+ export interface SchemaEvolutionChange {
103
+ kind: "type-added" | "type-removed" | "property-added-optional" | "property-added-required" | "property-removed" | "property-requiredness-changed" | "property-type-changed" | "property-wire-name-changed" | "type-discriminator-changed" | "property-enum-values-changed";
104
+ path: string;
105
+ severity: "patch" | "minor" | "major";
106
+ message: string;
107
+ }
108
+ export interface ConformanceMapEntry {
109
+ contract: string;
110
+ protocol: boolean;
111
+ targets: Array<{
112
+ target: string;
113
+ symbol: string;
114
+ source: string;
115
+ packageName?: string;
116
+ namespace?: string;
117
+ outputRoot: string;
118
+ modules: string[];
119
+ exported: boolean;
120
+ }>;
121
+ }
122
+ export interface StaleCleanupCandidate {
123
+ path: string;
124
+ reasons: string[];
125
+ safe: boolean;
126
+ }
127
+ export interface HydrationBoundaryReport {
128
+ protectedPaths: string[];
129
+ hydrationZones: string[];
130
+ seams: HydrationBoundarySnapshot["seams"];
131
+ }
132
+ export declare function verifyTypraMetadata(options: {
133
+ baselineRoot: string;
134
+ currentRoot: string;
135
+ configPath?: string;
136
+ }): TypraVerifyResult;
137
+ export declare function loadTypraMetadata(root: string): TypraMetadataSet;
138
+ export declare function loadVerifyConfig(configPath: string): TypraVerifyConfig;
139
+ export declare function compareTypraMetadata(baseline: TypraMetadataSet, current: TypraMetadataSet, config?: TypraVerifyConfig): TypraVerifyResult;
140
+ export declare function formatVerifySummary(result: TypraVerifyResult): string;