recma-static-refiner 0.9.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.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # recma-static-refiner
2
+
3
+ A robust **Unified/Recma** plugin for validating, transforming, and pruning MDX props at **build time**.
4
+
5
+ It allows you to enforce strict TypeScript contracts on your MDX components, derive complex data from simple props, and clean up the final output—all before the code reaches the browser.
6
+
7
+ ## Features
8
+
9
+ - **🛡️ Build-time Validation:** Enforce schemas (Zod, Valibot, ArkType) on props passed in MDX.
10
+ - **⚡ Computed Props (`derive`):** Generate complex runtime data (e.g., loading file contents, calculating layouts) based on static props.
11
+ - **✂️ Pruning:** Automatically remove "Source-Only" props that shouldn't leak to the runtime bundle.
12
+ - **✨ Zero Runtime Overhead:** All transformations happen during the AST compilation phase.
13
+ - **🔒 Type Safety:** First-class TypeScript inference for your rule registry.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install recma-static-refiner
19
+ # or
20
+ pnpm add recma-static-refiner
21
+ ```
@@ -0,0 +1,386 @@
1
+ import { Plugin } from 'unified';
2
+ import { Program } from 'estree';
3
+ import { StandardSchemaV1 } from '@standard-schema/spec';
4
+
5
+ /**
6
+ * The foundational structural constraint for component props.
7
+ *
8
+ * Role: Input Constraint.
9
+ * Aliased to `object` to serve as the generic lower bound, ensuring strict
10
+ * compatibility with both TypeScript Interfaces and Type Aliases across
11
+ * the plugin architecture.
12
+ */
13
+ type BaseProps = object;
14
+ /**
15
+ * The validation strategy definition for a component rule.
16
+ *
17
+ * Role: Behavioral Driver.
18
+ * Defines the allowed validation sources. This type acts as the control flow
19
+ * switch in inference utilities (e.g., `InferOutput`):
20
+ * - `StandardSchemaV1`: Triggers strict validation and output coercion.
21
+ * - `undefined`: Triggers "Passthrough Mode" (raw partial props).
22
+ */
23
+ type ComponentSchema = StandardSchemaV1 | undefined;
24
+
25
+ /**
26
+ * Internal DX Helper:
27
+ * Flattens the type output to improve tooltips in editors.
28
+ *
29
+ * This forces TypeScript to resolve intersections (A & B) into a single flat
30
+ * object structure, making hover-previews for complex types readable.
31
+ *
32
+ * @see https://github.com/sindresorhus/type-fest/blob/main/source/simplify.d.ts
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * type A = { x: 1 };
37
+ * type B = { y: 2 };
38
+ *
39
+ * // Without Simplify: Shows "A & B" on hover.
40
+ * type Complex = A & B;
41
+ *
42
+ * // With Simplify: Shows "{ x: 1; y: 2 }" on hover.
43
+ * type Flat = Simplify<A & B>;
44
+ * ```
45
+ */
46
+ type Simplify<T> = {
47
+ [KeyType in keyof T]: T[KeyType];
48
+ } & {};
49
+ /**
50
+ * Helper 1: The Anchor
51
+ * Extracts a specific key `K` from type `T` and enforces it as strictly required.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * type T = { a?: string; b?: number };
56
+ *
57
+ * // Result: { a: string }
58
+ * type Anchor = OneRequired<T, 'a'>;
59
+ * ```
60
+ */
61
+ type OneRequired<T, K extends keyof T> = Required<Pick<T, K>>;
62
+ /**
63
+ * Helper 2: The Remainder
64
+ * Constructs a type containing all properties of `T` *except* the anchor key `K`,
65
+ * ensuring they are all marked as optional.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * type T = { a: string; b: number };
70
+ *
71
+ * // Result: { b?: number }
72
+ * type Remainder = OthersOptional<T, 'a'>;
73
+ * ```
74
+ */
75
+ type OthersOptional<T, K extends keyof T> = Partial<Omit<T, K>>;
76
+ /**
77
+ * Helper 3: The Variant Builder
78
+ * Composes a specific variant of the original type where the anchor key `K`
79
+ * is required and all other keys are optional.
80
+ *
81
+ * Utilizes {@link Simplify} to ensure the resulting type is displayed as a
82
+ * clean object literal rather than an intersection of helpers.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * type T = { a: string; b: string };
87
+ *
88
+ * // Result: { a: string; b?: string }
89
+ * type Variant = VariantWith<T, 'a'>;
90
+ * ```
91
+ */
92
+ type VariantWith<T, K extends keyof T> = Simplify<OneRequired<T, K> & OthersOptional<T, K>>;
93
+ /**
94
+ * Constructs a union type where at least one of the keys from the original
95
+ * type `T` is required to exist.
96
+ *
97
+ * Logic:
98
+ * Iterates over every key `K` in `T` and generates a corresponding {@link VariantWith}
99
+ * where `K` acts as the required anchor. The result is a union of all possible variants.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * type T = { schema?: S; derive?: D };
104
+ *
105
+ * type Result = RequireAtLeastOne<T>;
106
+ * // Resulting Union:
107
+ * // | { schema: S; derive?: D } (Variant A)
108
+ * // | { derive: D; schema?: S } (Variant B)
109
+ * ```
110
+ */
111
+ type RequireAtLeastOne<T> = {
112
+ [K in keyof T]: VariantWith<T, K>;
113
+ }[keyof T];
114
+ /**
115
+ * Generic Logic Helper: Strict Conditional Check
116
+ *
117
+ * Branches types based on whether `Type` strictly extends `Constraint`,
118
+ * disabling the default distributive behavior of conditional types.
119
+ *
120
+ * Mechanism:
121
+ * 1. **Tuple Wrapping:** The syntax `[Type]` and `[Constraint]` wraps inputs
122
+ * into tuples.
123
+ * 2. **Blocking Distribution:** TypeScript distributes conditional types over
124
+ * unions (e.g., checking `A` and `B` separately in `A | B`). Tuples cannot
125
+ * be distributed, forcing the compiler to treat `Type` as a single,
126
+ * indivisible unit.
127
+ * 3. **Strict Comparison:** The check succeeds only if the entire `Type`
128
+ * (including all union members or `undefined`) fits within `Constraint`.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * // Scenario: Checking if a Union extends a single type.
133
+ * type Input = string | number;
134
+ *
135
+ * // 1. Standard (Distributive) Behavior:
136
+ * // (string extends string) | (number extends string)
137
+ * // => 'Yes' | 'No'
138
+ * type Standard = Input extends string ? 'Yes' : 'No';
139
+ *
140
+ * // 2. Strict (Non-Distributive) Behavior via helper:
141
+ * // [string | number] extends [string]
142
+ * // => 'No' (The complete union does not extend string)
143
+ * type Result = IfStrictExtends<Input, string, 'Yes', 'No'>;
144
+ * ```
145
+ *
146
+ * @template Type The candidate type to check.
147
+ * @template Constraint The target type to check against.
148
+ * @template True Result if the check passes.
149
+ * @template False Result if the check fails.
150
+ */
151
+ type IfStrictExtends<Type, Constraint, True, False> = [Type] extends [
152
+ Constraint
153
+ ] ? True : False;
154
+
155
+ /**
156
+ * Strategy A: Schema Exists
157
+ * Extracts the validated output type (O) from a Standard Schema.
158
+ *
159
+ * Logic:
160
+ * Infers the strict output type defined by the `StandardSchemaV1` interface.
161
+ */
162
+ type SchemaValidatedProps<S> = S extends StandardSchemaV1<unknown, infer Output> ? Output : never;
163
+ /**
164
+ * Strategy B: No Schema
165
+ * Returns the raw props as optional (Passthrough).
166
+ *
167
+ * Rationale for Partial:
168
+ * When no schema is provided to enforce strictness or apply default values,
169
+ * the type assumes that authored MDX props may be incomplete subset of the
170
+ * full interface.
171
+ */
172
+ type PassthroughProps<P> = Partial<P>;
173
+ /**
174
+ * Main Utility: Output Inference
175
+ *
176
+ * Resolves the final prop type by selecting the appropriate strategy.
177
+ *
178
+ * Logic:
179
+ * Uses {@link IfStrictExtends} to determine if a valid schema is provided.
180
+ * 1. Schema provided -> Applies {@link SchemaValidatedProps} (Strict/Coerced).
181
+ * 2. No Schema -> Applies {@link PassthroughProps} (Loose/Partial).
182
+ */
183
+ type InferOutput<S extends ComponentSchema, Props extends BaseProps> = IfStrictExtends<S, StandardSchemaV1, SchemaValidatedProps<S>, PassthroughProps<Props>>;
184
+
185
+ /**
186
+ * Function signature for the `derive` pipeline phase.
187
+ *
188
+ * Computes derived prop values based on the upstream input (either validated
189
+ * data or raw props) and emits them via the provided `set` callback.
190
+ *
191
+ * @template Props - Component props interface constraining what can be set.
192
+ * @template S - Schema type providing the input inference strategy.
193
+ *
194
+ * @param derivationInput - The input data for the derivation logic.
195
+ * - If `S` is a Schema: Coerced/Validated output.
196
+ * - If `S` is undefined: Raw `Partial<Props>`.
197
+ * @param set - Callback to emit derived key/value pairs. TypeScript enforces
198
+ * that only keys from `Props` can be passed (excess property check).
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * derive: (input, set) => {
203
+ * set({
204
+ * initialState: buildInitialState(input),
205
+ * invalidProp: 'value' // Type Error: does not exist in type 'Partial<Props>'
206
+ * });
207
+ * }
208
+ * ```
209
+ */
210
+ type DeriveFunction<Props extends BaseProps, S extends ComponentSchema = undefined> = (derivationInput: InferOutput<S, Props>, set: (values: Partial<Props>) => void) => void;
211
+ /**
212
+ * Base Configuration Shape: The available properties for a component rule.
213
+ *
214
+ * This type defines the raw structure (schema, derive, pruneKeys) where all
215
+ * fields are optional. It serves as the intermediate building block for the
216
+ * strict {@link ComponentRule} type, which enforces that at least one feature
217
+ * must be active.
218
+ */
219
+ type ComponentRuleFeatures<Props extends BaseProps = BaseProps, S extends ComponentSchema = undefined> = {
220
+ /**
221
+ * The validation schema for this component.
222
+ *
223
+ * The generic parameter `S` captures the *specific* schema type provided
224
+ * (e.g. a specific Zod or Valibot instance), preserving exact output types
225
+ * downstream instead of widening them to the generic `StandardSchemaV1`.
226
+ *
227
+ * Type Behavior:
228
+ * - Constraint: `S` must satisfy the Standard Schema V1 interface.
229
+ * - Default: Defaults to `undefined`. If no schema is provided/inferred,
230
+ * the rule operates in "Passthrough Mode" (derivation receives raw props).
231
+ *
232
+ * @example
233
+ * `defineRule( { schema: MyZodSchema } )` → `S` is `MyZodSchema`
234
+ */
235
+ schema?: S;
236
+ /**
237
+ * Hook to compute new values based on the upstream input.
238
+ *
239
+ * This corresponds to the **`derive`** pipeline phase.
240
+ *
241
+ * Logic:
242
+ * Receives the input data (either schema-validated output or raw props) and
243
+ * a `set` callback to emit new prop values into the AST.
244
+ *
245
+ * @see {@link DeriveFunction} for the strict signature and usage examples.
246
+ */
247
+ derive?: DeriveFunction<Props, S>;
248
+ /**
249
+ * Explicitly removes specific root-level keys from the compiled output.
250
+ *
251
+ * Use this to strip "Source-Only" props—data that acts as an input for the
252
+ * build pipeline (e.g. used by `derive` to calculate other values) but
253
+ * is unnecessary in the final runtime execution.
254
+ *
255
+ * @example ['sourceData', 'legacyId']
256
+ */
257
+ pruneKeys?: readonly string[];
258
+ };
259
+ /**
260
+ * Public Definition: The strict contract for a component rule.
261
+ *
262
+ * Defines the configuration for validation (`schema`), data transformation
263
+ * (`derive`), and output cleanup (`pruneKeys`).
264
+ *
265
+ * Type Mechanics:
266
+ * 1. Enforcement:
267
+ * Uses {@link RequireAtLeastOne} to ensure the rule is not empty
268
+ * (it must define at least one active feature).
269
+ * 2. Safety:
270
+ * Uses `NonNullable` to explicitly strip the `undefined` type that results
271
+ * from mapping over entirely optional keys.
272
+ * This guarantees that a `ComponentRule` is always a concrete object,
273
+ * preventing "possibly undefined" errors in downstream processing.
274
+ */
275
+ type ComponentRule<Props extends BaseProps = BaseProps, S extends ComponentSchema = undefined> = NonNullable<RequireAtLeastOne<ComponentRuleFeatures<Props, S>>>;
276
+ /**
277
+ * A type-erased handle for a specific component rule.
278
+ *
279
+ * Purpose:
280
+ * Serves as the universal value type for storage. By abstracting specific `Props`
281
+ * into `BaseProps` (object), it allows heterogeneous rules (rules with different
282
+ * prop interfaces) to coexist within the same registry record.
283
+ *
284
+ * Guarantees:
285
+ * - Is a strict object (not undefined/null).
286
+ * - Has at least one active feature (schema, derive, or pruneKeys).
287
+ */
288
+ type ComponentRuleBase = ComponentRule<BaseProps, ComponentSchema>;
289
+
290
+ /**
291
+ * The generic constraint for rule inference.
292
+ *
293
+ * Purpose:
294
+ * Used by `defineRuleRegistry` to validate the input structure while allowing
295
+ * specific subtypes to pass through via generics.
296
+ *
297
+ * Structure:
298
+ * - Key (`string`):
299
+ * Represents the component name (e.g., "CustomComponent").
300
+ * Must match the identifier used in the JSX callsite.
301
+ * - Value ({@link ComponentRuleBase}):
302
+ * The "Common Denominator" type for rules.
303
+ * It allows rules with different specific prop interfaces to coexist in the
304
+ * same object because they all satisfy the base contract of operating on an
305
+ * `object`.
306
+ */
307
+ type RuleRegistryConstraint = Record<string, ComponentRuleBase>;
308
+ /**
309
+ * The concrete registry structure used by the plugin runtime.
310
+ *
311
+ * Purpose:
312
+ * Represents the lookup table used by the component resolver to match AST
313
+ * callsites. Specific prop interfaces are abstracted away at this stage;
314
+ * the runtime relies solely on the structural contract of {@link ComponentRuleBase}
315
+ * to ensure uniform and safe execution.
316
+ */
317
+ type RuleMap = RuleRegistryConstraint;
318
+ /**
319
+ * Creates the rule registry with strict type inference.
320
+ *
321
+ * Acts as an identity function that validates the input structure against
322
+ * {@link RuleRegistryConstraint} without widening the type. This preserves
323
+ * specific `Props` interfaces for downstream tooling while enforcing the
324
+ * registry contract.
325
+ *
326
+ * @template T - The specific registry shape (inferred).
327
+ * @param registry - The map of component names to rules.
328
+ * @returns The registry object with specific types preserved.
329
+ */
330
+ declare function defineRuleRegistry<T extends RuleRegistryConstraint>(registry: T): T;
331
+ /**
332
+ * Creates a typed rule definition helper for a specific component interface.
333
+ *
334
+ * Implementation Note:
335
+ * Utilizes a curried function pattern to allow explicit definition of the
336
+ * `Props` generic, while automatically inferring the `Schema` type from
337
+ * the provided configuration object.
338
+ *
339
+ * Usage:
340
+ * ```ts
341
+ * defineRule<MyProps>()({
342
+ * schema: MySchema,
343
+ * derive: (props, set) => { ... }
344
+ * })
345
+ * ```
346
+ */
347
+ declare function defineRule<Props extends BaseProps>(): <S extends ComponentSchema = undefined>(rule: ComponentRule<Props, S>) => ComponentRule<Props, S>;
348
+ type PluginOptions = {
349
+ /**
350
+ * Defines the behavior rules for each component.
351
+ * Maps component names to their validation, derivation, and pruning logic.
352
+ */
353
+ rules: RuleMap;
354
+ /**
355
+ * Controls whether validated values are written back to the compiled output.
356
+ *
357
+ * - `true`: Applies patches to the AST (Transpiler Mode).
358
+ * - `false`: Validates props without modifying the source (Linter / Dry-Run Mode).
359
+ *
360
+ * Scenario: Type Correction
361
+ * - Source: `<Component zIndex="50" />`
362
+ * - Schema: `zIndex` is a number.
363
+ * - `true`: Updates AST to `zIndex={50}`.
364
+ * - `false`: Leaves AST as `zIndex="50"` (throws if validation fails).
365
+ *
366
+ * @default true
367
+ */
368
+ applyTransforms?: boolean;
369
+ /**
370
+ * Prop keys identifying **Dynamic Runtime Expressions** (e.g., `children`, `onClick`).
371
+ *
372
+ * Values for these keys are treated as executable code, not static data:
373
+ * - Extraction: Captured as-is; not resolved to static data.
374
+ * - Validation: Passed through via placeholders.
375
+ * - Patching: Protected (traversal stops; the expression remains verbatim).
376
+ *
377
+ * Use this for properties containing variables, functions, or JSX elements.
378
+ *
379
+ * @default ['children']
380
+ */
381
+ preservedKeys?: readonly string[];
382
+ };
383
+
384
+ declare const recmaStaticRefiner: Plugin<[PluginOptions], Program>;
385
+
386
+ export { defineRule, defineRuleRegistry, recmaStaticRefiner };