@sembl/compiler 0.1.0 → 0.2.1

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.
@@ -0,0 +1,220 @@
1
+ import { SchemaBundle, RuntimeSchema, FieldConstraints, FieldType, Provider, EnumResolver, CoerceOptions } from '@sembl/core';
2
+ import { Node, ClassDeclaration, PropertyDeclaration, Type } from 'ts-morph';
3
+ import { EvalReport, EvalDiff, TokenPrices } from '@sembl/testing';
4
+
5
+ /**
6
+ * Annotation extracted from a @Schema decorator.
7
+ */
8
+ interface SchemaAnnotation {
9
+ /** Class name */
10
+ className: string;
11
+ /** Description string from @Schema(description) */
12
+ description: string;
13
+ }
14
+ /**
15
+ * Annotation extracted from a @Describe decorator on a property.
16
+ */
17
+ interface FieldAnnotation {
18
+ /** Property name */
19
+ name: string;
20
+ /** Description string from @Describe(description) */
21
+ description: string;
22
+ /** Raw TypeScript type text */
23
+ rawType: string;
24
+ /** Whether the property is optional (?:) */
25
+ optional: boolean;
26
+ }
27
+ /**
28
+ * Configuration for the compiler.
29
+ */
30
+ interface CompilerConfig {
31
+ /** Glob patterns for input source files */
32
+ inputPatterns: string[];
33
+ /** Output directory for generated schema files */
34
+ outputDir: string;
35
+ /** Optional tsconfig path */
36
+ tsconfigPath?: string;
37
+ }
38
+ /**
39
+ * Result of extracting schemas from source files.
40
+ *
41
+ * Extends `SchemaBundle` so it can be handed straight to the emitter, and adds
42
+ * the diagnostics raised on the way — a field whose type the schema contract
43
+ * cannot express still emits, so the warnings are what tell the caller the
44
+ * output does not match the source.
45
+ */
46
+ interface ExtractionResult extends SchemaBundle {
47
+ /** Any warnings generated during extraction, in discovery order */
48
+ warnings: string[];
49
+ }
50
+
51
+ interface ExtractOptions {
52
+ /** Glob patterns for source files */
53
+ filePatterns: string[];
54
+ /** Optional tsconfig.json path */
55
+ tsconfigPath?: string;
56
+ }
57
+ /**
58
+ * Extract all @Schema-decorated classes from the given source files.
59
+ *
60
+ * Returns the schemas alongside any warnings raised while resolving them —
61
+ * unsupported field types, annotations that could not be read. Extraction
62
+ * never throws on a bad field, so the warnings are the only signal that the
63
+ * emitted schemas are not what the source described.
64
+ */
65
+ declare function extractSchemas(options: ExtractOptions): ExtractionResult;
66
+
67
+ /**
68
+ * Emit a SchemaBundle as a set of individual .schema.ts files
69
+ * plus a bundle index file.
70
+ */
71
+ declare function emitSchemas(bundle: SchemaBundle, outputDir: string): string[];
72
+
73
+ /**
74
+ * Where a field sits in the source, carried through type resolution and
75
+ * decorator parsing.
76
+ *
77
+ * Every diagnostic names the class and the property, because a warning that
78
+ * only says "unsupported type" sends the reader hunting through the whole
79
+ * input directory for it.
80
+ */
81
+ interface FieldScope {
82
+ /** Name of the @Schema class that owns the field. */
83
+ className: string;
84
+ /** Property name, dotted through synthesized inline objects. */
85
+ propertyPath: string;
86
+ /**
87
+ * Declaration the type was read from. Members of an anonymous object type
88
+ * have no declaration of their own to resolve against, so they are typed
89
+ * relative to this node.
90
+ */
91
+ node: Node;
92
+ /** Collector for diagnostics and synthesized schemas. */
93
+ context: ExtractionContext;
94
+ }
95
+ /**
96
+ * Accumulates everything an extraction produces beyond the schemas themselves:
97
+ * diagnostics, schemas synthesized for anonymous object types, and the nested
98
+ * schema references that can only be validated once all files are visited.
99
+ */
100
+ interface ExtractionContext {
101
+ /** Diagnostics raised so far, in discovery order. */
102
+ readonly warnings: readonly string[];
103
+ /** Schemas synthesized for anonymous inline object types, keyed by id. */
104
+ readonly synthesizedSchemas: Readonly<Record<string, RuntimeSchema>>;
105
+ /** Record a diagnostic against a field. */
106
+ warn(scope: FieldScope, message: string): void;
107
+ /** Add a schema synthesized for an inline object type. */
108
+ registerSynthesizedSchema(schema: RuntimeSchema): void;
109
+ /** Note that a field points at `nestedSchemaId`, to be checked later. */
110
+ recordNestedReference(scope: FieldScope, nestedSchemaId: string, typeText: string): void;
111
+ /**
112
+ * Warn about every recorded reference whose target is not among
113
+ * `knownSchemaIds`. Call once, after all classes have been visited.
114
+ */
115
+ reportUnresolvedNestedSchemas(knownSchemaIds: ReadonlySet<string>): void;
116
+ }
117
+ /**
118
+ * Create an empty {@link ExtractionContext} for a single extraction run.
119
+ */
120
+ declare function createExtractionContext(): ExtractionContext;
121
+ /**
122
+ * Derive the id of a schema synthesized from an anonymous object type.
123
+ *
124
+ * The id becomes a filename and an exported binding in the generated output
125
+ * (`<id>.schema.ts`, `<id>Schema`), so it has to be a valid identifier. The
126
+ * path prefix keeps generated files traceable back to the declaration they
127
+ * came from; the digest of the resolved shape makes the id collision-resistant
128
+ * against a hand-written @Schema class and stable across runs and machines —
129
+ * it is derived from the resolved fields, never from absolute source paths.
130
+ */
131
+ declare function synthesizedSchemaId(scope: FieldScope, structuralSignature: string): string;
132
+
133
+ /**
134
+ * Visit a class declaration and extract a RuntimeSchema if it has @Schema decorator.
135
+ * Returns undefined if the class is not decorated with @Schema.
136
+ *
137
+ * Diagnostics and schemas synthesized for inline object types are collected
138
+ * into `context` rather than thrown, so one questionable field does not stop
139
+ * the rest of the extraction.
140
+ */
141
+ declare function visitClass(classDecl: ClassDeclaration, context: ExtractionContext): RuntimeSchema | undefined;
142
+
143
+ /**
144
+ * Extract the @Schema description from a class declaration.
145
+ * Returns undefined if the class doesn't have a @Schema decorator.
146
+ */
147
+ declare function parseSchemaDecorator(classDecl: ClassDeclaration): string | undefined;
148
+ /**
149
+ * Extract the @Describe description from a property declaration.
150
+ * Returns undefined if the property doesn't have a @Describe decorator.
151
+ */
152
+ declare function parseDescribeDecorator(propDecl: PropertyDeclaration): string | undefined;
153
+ /**
154
+ * Extract the @Constrain bounds from a property declaration.
155
+ *
156
+ * The decorator's argument has to be an inline object literal: decorators are
157
+ * never evaluated, so a value that is not written out in source cannot be
158
+ * resolved. Unreadable and unknown entries are warned about and skipped
159
+ * individually, so one bad bound does not discard the rest.
160
+ *
161
+ * Returns undefined if there is no @Constrain decorator, or if nothing in it
162
+ * could be read.
163
+ */
164
+ declare function parseConstrainDecorator(propDecl: PropertyDeclaration, scope: FieldScope): FieldConstraints | undefined;
165
+ /**
166
+ * Extract the enum source id from a property's @ValuesFrom decorator.
167
+ *
168
+ * Returns undefined if there is no @ValuesFrom decorator, or if its argument
169
+ * is not a string literal.
170
+ */
171
+ declare function parseValuesFromDecorator(propDecl: PropertyDeclaration, scope: FieldScope): string | undefined;
172
+
173
+ /**
174
+ * Resolve a TypeScript type to a FieldType descriptor.
175
+ *
176
+ * Maps TS types to the schema type system: string, number, boolean, array,
177
+ * object, enum. Anything the contract cannot express is reported through
178
+ * `scope.context` rather than quietly coerced.
179
+ */
180
+ declare function resolveFieldType(type: Type, scope: FieldScope): FieldType;
181
+
182
+ interface EvalCommandOptions {
183
+ /** A JS module exporting the schema and provider to evaluate with. */
184
+ config: string;
185
+ /** Directory of fixture JSON files. */
186
+ fixtures: string;
187
+ /** Where to write the report; defaults to `<fixtures>/.sembl-eval/last-run.json`. */
188
+ out?: string;
189
+ mode?: "coerce" | "partialCoerce";
190
+ provenance?: boolean;
191
+ concurrency?: number;
192
+ /** Replay recordings from this directory, recording misses through the provider. */
193
+ replay?: string;
194
+ /** Exit non-zero when overall recall lands below this fraction. */
195
+ minRecall?: number;
196
+ /** Exit non-zero when overall precision lands below this fraction. */
197
+ minPrecision?: number;
198
+ }
199
+ /**
200
+ * What an eval config module exports. Written as a plain JS module so the
201
+ * CLI can `import()` it without a TypeScript loader; a TS project can point
202
+ * at its build output, or keep the config in `.mjs`.
203
+ */
204
+ interface EvalConfig {
205
+ schema: RuntimeSchema;
206
+ provider: Provider;
207
+ bundle?: SchemaBundle;
208
+ enumResolver?: EnumResolver;
209
+ prices?: TokenPrices;
210
+ /** Any other coercion options: `onInvalidField`, `maxRepairAttempts`, `maxInputChars`, … */
211
+ coerceOptions?: Partial<Omit<CoerceOptions, "schema" | "provider" | "bundle" | "enumResolver">>;
212
+ }
213
+ interface EvalCommandResult {
214
+ report?: EvalReport;
215
+ diff?: EvalDiff;
216
+ exitCode: number;
217
+ }
218
+ declare function evalCommand(options: EvalCommandOptions): Promise<EvalCommandResult>;
219
+
220
+ export { type CompilerConfig, type EvalCommandOptions, type EvalCommandResult, type EvalConfig, type ExtractOptions, type ExtractionContext, type ExtractionResult, type FieldAnnotation, type FieldScope, type SchemaAnnotation, createExtractionContext, emitSchemas, evalCommand, extractSchemas, parseConstrainDecorator, parseDescribeDecorator, parseSchemaDecorator, parseValuesFromDecorator, resolveFieldType, synthesizedSchemaId, visitClass };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { SchemaBundle, RuntimeSchema, FieldConstraints, FieldType } from '@sembl/core';
1
+ import { SchemaBundle, RuntimeSchema, FieldConstraints, FieldType, Provider, EnumResolver, CoerceOptions } from '@sembl/core';
2
2
  import { Node, ClassDeclaration, PropertyDeclaration, Type } from 'ts-morph';
3
+ import { EvalReport, EvalDiff, TokenPrices } from '@sembl/testing';
3
4
 
4
5
  /**
5
6
  * Annotation extracted from a @Schema decorator.
@@ -178,4 +179,42 @@ declare function parseValuesFromDecorator(propDecl: PropertyDeclaration, scope:
178
179
  */
179
180
  declare function resolveFieldType(type: Type, scope: FieldScope): FieldType;
180
181
 
181
- export { type CompilerConfig, type ExtractOptions, type ExtractionContext, type ExtractionResult, type FieldAnnotation, type FieldScope, type SchemaAnnotation, createExtractionContext, emitSchemas, extractSchemas, parseConstrainDecorator, parseDescribeDecorator, parseSchemaDecorator, parseValuesFromDecorator, resolveFieldType, synthesizedSchemaId, visitClass };
182
+ interface EvalCommandOptions {
183
+ /** A JS module exporting the schema and provider to evaluate with. */
184
+ config: string;
185
+ /** Directory of fixture JSON files. */
186
+ fixtures: string;
187
+ /** Where to write the report; defaults to `<fixtures>/.sembl-eval/last-run.json`. */
188
+ out?: string;
189
+ mode?: "coerce" | "partialCoerce";
190
+ provenance?: boolean;
191
+ concurrency?: number;
192
+ /** Replay recordings from this directory, recording misses through the provider. */
193
+ replay?: string;
194
+ /** Exit non-zero when overall recall lands below this fraction. */
195
+ minRecall?: number;
196
+ /** Exit non-zero when overall precision lands below this fraction. */
197
+ minPrecision?: number;
198
+ }
199
+ /**
200
+ * What an eval config module exports. Written as a plain JS module so the
201
+ * CLI can `import()` it without a TypeScript loader; a TS project can point
202
+ * at its build output, or keep the config in `.mjs`.
203
+ */
204
+ interface EvalConfig {
205
+ schema: RuntimeSchema;
206
+ provider: Provider;
207
+ bundle?: SchemaBundle;
208
+ enumResolver?: EnumResolver;
209
+ prices?: TokenPrices;
210
+ /** Any other coercion options: `onInvalidField`, `maxRepairAttempts`, `maxInputChars`, … */
211
+ coerceOptions?: Partial<Omit<CoerceOptions, "schema" | "provider" | "bundle" | "enumResolver">>;
212
+ }
213
+ interface EvalCommandResult {
214
+ report?: EvalReport;
215
+ diff?: EvalDiff;
216
+ exitCode: number;
217
+ }
218
+ declare function evalCommand(options: EvalCommandOptions): Promise<EvalCommandResult>;
219
+
220
+ export { type CompilerConfig, type EvalCommandOptions, type EvalCommandResult, type EvalConfig, type ExtractOptions, type ExtractionContext, type ExtractionResult, type FieldAnnotation, type FieldScope, type SchemaAnnotation, createExtractionContext, emitSchemas, evalCommand, extractSchemas, parseConstrainDecorator, parseDescribeDecorator, parseSchemaDecorator, parseValuesFromDecorator, resolveFieldType, synthesizedSchemaId, visitClass };