✨ Finalize and document the public API surface
Export the matcher builder types from the barrel and rename them off the internal Strict/Widening suffixes, so the type a factory returns is nameable: PrimitiveUnionMatcher/W, TaggedUnionMatcher/W, and the tagged-union factory types. Add TSDoc to every public symbol — the declarations carry it into the published package — and pin the exported names in src/index.test.ts. Document the API in README and record the surface decision (and the rejected matcher-shared export) in development/library.md.
This commit is contained in:
1 parent
acf9d06ddd
commit
f485b1bae2
8 files changed
+317
-20
No files matched your search
@@ -8,8 +8,18 @@ import {
|
||||
getPrimitiveUnionMatcherW,
|
||||
getTaggedUnionMatcher,
|
||||
getTaggedUnionMatcherW,
|
||||
type PrimitiveUnionMatcher,
|
||||
type PrimitiveUnionMatcherW,
|
||||
type TaggedUnionMatcher,
|
||||
type TaggedUnionMatcherFactory,
|
||||
type TaggedUnionMatcherW,
|
||||
type TaggedUnionMatcherWFactory,
|
||||
} from "./index.ts";
|
||||
|
||||
interface Shape {
|
||||
readonly kind: "circle" | "square";
|
||||
}
|
||||
|
||||
// The published entry point is the barrel (`package.json` exports
|
||||
// `./dist/index.js`), so every factory must be reachable from here. Importing it
|
||||
// also loads the module, which is what lets c8's `--all` measure it — see
|
||||
@@ -25,3 +35,42 @@ test("index: the public entry point re-exports every matcher factory", () => {
|
||||
expectTypeOf(getTaggedUnionMatcherW).toBeFunction();
|
||||
assert.equal(typeof getTaggedUnionMatcherW, "function");
|
||||
});
|
||||
|
||||
// The builder types are part of the public contract: a consumer annotates a
|
||||
// factory result with them, so the exported names must match the inferred
|
||||
// return types.
|
||||
test("index: the primitive-union factories return the exported builder types", () => {
|
||||
// Act
|
||||
const strict = getPrimitiveUnionMatcher<"a" | "b">();
|
||||
const widening = getPrimitiveUnionMatcherW<"a" | "b">();
|
||||
|
||||
// Assert
|
||||
expectTypeOf(strict).toEqualTypeOf<PrimitiveUnionMatcher<"a" | "b">>();
|
||||
assert.equal(typeof strict, "function");
|
||||
expectTypeOf(widening).toEqualTypeOf<PrimitiveUnionMatcherW<"a" | "b">>();
|
||||
assert.equal(typeof widening, "function");
|
||||
});
|
||||
|
||||
test("index: the tagged-union factories return the exported factory types", () => {
|
||||
// Act
|
||||
const strict = getTaggedUnionMatcher<Shape>();
|
||||
const widening = getTaggedUnionMatcherW<Shape>();
|
||||
|
||||
// Assert
|
||||
expectTypeOf(strict).toEqualTypeOf<TaggedUnionMatcherFactory<Shape>>();
|
||||
assert.equal(typeof strict, "function");
|
||||
expectTypeOf(widening).toEqualTypeOf<TaggedUnionMatcherWFactory<Shape>>();
|
||||
assert.equal(typeof widening, "function");
|
||||
});
|
||||
|
||||
test("index: the tagged-union key step returns the exported builder types", () => {
|
||||
// Act
|
||||
const strict = getTaggedUnionMatcher<Shape>()("kind");
|
||||
const widening = getTaggedUnionMatcherW<Shape>()("kind");
|
||||
|
||||
// Assert
|
||||
expectTypeOf(strict).toEqualTypeOf<TaggedUnionMatcher<Shape, "kind">>();
|
||||
assert.equal(typeof strict, "function");
|
||||
expectTypeOf(widening).toEqualTypeOf<TaggedUnionMatcherW<Shape, "kind">>();
|
||||
assert.equal(typeof widening, "function");
|
||||
});
|
||||
@@ -1,8 +1,26 @@
|
||||
/**
|
||||
* The public entry point of `tiny-pattern-ts`.
|
||||
*
|
||||
* Exports the four matcher factories and the builder types they return.
|
||||
* Everything else in `src/` is an implementation detail.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
export {
|
||||
getPrimitiveUnionMatcher,
|
||||
getPrimitiveUnionMatcherW,
|
||||
} from "./primitive-union.ts";
|
||||
export type {
|
||||
PrimitiveUnionMatcher,
|
||||
PrimitiveUnionMatcherW,
|
||||
} from "./primitive-union.ts";
|
||||
export {
|
||||
getTaggedUnionMatcher,
|
||||
getTaggedUnionMatcherW,
|
||||
} from "./tagged-union.ts";
|
||||
export type {
|
||||
TaggedUnionMatcher,
|
||||
TaggedUnionMatcherFactory,
|
||||
TaggedUnionMatcherW,
|
||||
TaggedUnionMatcherWFactory,
|
||||
} from "./tagged-union.ts";
|
||||
+54
-4
@@ -44,7 +44,14 @@ type MustBePartial<T extends Matchable, Handled> =
|
||||
// Strict returns: one common `R`. Overload order is load-bearing:
|
||||
// #1 Handlers (first) -> the exhaustive form and the autocomplete popup
|
||||
// #2 Fallback (last) -> accepts a partial handler map plus a fallback
|
||||
interface PrimitiveUnionMatcherStrict<T extends Matchable> {
|
||||
/**
|
||||
* The builder returned by {@link getPrimitiveUnionMatcher}: call it with a
|
||||
* handler map for every member of `T` to get a strict matcher, or with a partial
|
||||
* map plus a fallback.
|
||||
*
|
||||
* @typeParam T - The finite universe of primitive members to match.
|
||||
*/
|
||||
export interface PrimitiveUnionMatcher<T extends Matchable> {
|
||||
<R>(handlers: Handlers<T, R> & UniverseGate<T>): UnaryFn<T, R>;
|
||||
<
|
||||
R,
|
||||
@@ -59,7 +66,14 @@ interface PrimitiveUnionMatcherStrict<T extends Matchable> {
|
||||
// Widened returns: the union of every handler's return type. `P` is inferred
|
||||
// from the whole handler map, whose closed constraint supplies the
|
||||
// contextual/autocomplete type.
|
||||
interface PrimitiveUnionMatcherWidening<T extends Matchable> {
|
||||
/**
|
||||
* The builder returned by {@link getPrimitiveUnionMatcherW}: like
|
||||
* {@link PrimitiveUnionMatcher}, but the result's return type is the union of
|
||||
* every handler's return type instead of one common `R`.
|
||||
*
|
||||
* @typeParam T - The finite universe of primitive members to match.
|
||||
*/
|
||||
export interface PrimitiveUnionMatcherW<T extends Matchable> {
|
||||
<P extends Exact<Handlers<T, unknown>, P>>(
|
||||
handlers: P & UniverseGate<T>,
|
||||
): UnaryFn<T, PatternReturns<P>>;
|
||||
@@ -96,9 +110,45 @@ const dispatch =
|
||||
shape as never,
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a matcher for a finite primitive universe, with one common return type.
|
||||
*
|
||||
* The universe `T` must be a finite union of literals with no
|
||||
* value/stringification collision: broad members (`string`, `number`, template
|
||||
* literals) and `true | "true"` / `1 | "1"` are rejected at the call site. The
|
||||
* returned builder takes a handler map keyed by the members; supplying a second
|
||||
* fallback argument allows a partial map and receives the unhandled remainder.
|
||||
*
|
||||
* @typeParam T - The finite universe of primitive members to match.
|
||||
* @returns A builder for the handler map, or the handler map plus a fallback.
|
||||
* @example
|
||||
* const describe = getPrimitiveUnionMatcher<"yes" | "no">()({
|
||||
* yes: () => "agreed",
|
||||
* no: () => "declined",
|
||||
* });
|
||||
* describe("yes"); // "agreed"
|
||||
*/
|
||||
export const getPrimitiveUnionMatcher = <
|
||||
T extends Matchable,
|
||||
>(): PrimitiveUnionMatcherStrict<T> => dispatch;
|
||||
>(): PrimitiveUnionMatcher<T> => dispatch;
|
||||
|
||||
/**
|
||||
* Create a matcher for a finite primitive universe whose return type is the
|
||||
* union of every handler's return type.
|
||||
*
|
||||
* The widening counterpart of {@link getPrimitiveUnionMatcher}: use it when the
|
||||
* handlers return different types and the union, not one common `R`, is wanted.
|
||||
* The universe constraint and the optional fallback are identical.
|
||||
*
|
||||
* @typeParam T - The finite universe of primitive members to match.
|
||||
* @returns A builder for the handler map, or the handler map plus a fallback.
|
||||
* @example
|
||||
* const reply = getPrimitiveUnionMatcherW<"yes" | "no">()({
|
||||
* yes: () => 1,
|
||||
* no: () => "declined",
|
||||
* });
|
||||
* // reply: (shape: "yes" | "no") => number | string
|
||||
*/
|
||||
export const getPrimitiveUnionMatcherW = <
|
||||
T extends Matchable,
|
||||
>(): PrimitiveUnionMatcherWidening<T> => dispatch;
|
||||
>(): PrimitiveUnionMatcherW<T> => dispatch;
|
||||
+78
-9
@@ -87,7 +87,15 @@ type MustBePartial<T extends object, K extends keyof T, Handled> =
|
||||
// Strict returns: one common `R`. Overload order is load-bearing:
|
||||
// #1 Handlers (first) -> the exhaustive form and the autocomplete popup
|
||||
// #2 Fallback (last) -> accepts a partial handler map plus a fallback
|
||||
interface TaggedUnionMatcherStrict<T extends object, K extends keyof T> {
|
||||
/**
|
||||
* The builder returned by the key step of {@link getTaggedUnionMatcher}: call it
|
||||
* with a handler map for every tag under `K` to get a strict matcher, or with a
|
||||
* partial map plus a fallback.
|
||||
*
|
||||
* @typeParam T - The discriminated-union type to match.
|
||||
* @typeParam K - The discriminant property of `T`.
|
||||
*/
|
||||
export interface TaggedUnionMatcher<T extends object, K extends keyof T> {
|
||||
<R>(handlers: Handlers<T, K, R> & UniverseGate<Tags<T, K>>): UnaryFn<T, R>;
|
||||
<
|
||||
R,
|
||||
@@ -104,7 +112,15 @@ interface TaggedUnionMatcherStrict<T extends object, K extends keyof T> {
|
||||
// Widened returns: the union of every handler's return type. `P` is inferred
|
||||
// from the whole handler map, whose closed constraint supplies the
|
||||
// contextual/autocomplete type.
|
||||
interface TaggedUnionMatcherWidening<T extends object, K extends keyof T> {
|
||||
/**
|
||||
* The builder returned by the key step of {@link getTaggedUnionMatcherW}: like
|
||||
* {@link TaggedUnionMatcher}, but the result's return type is the union of every
|
||||
* handler's return type instead of one common `R`.
|
||||
*
|
||||
* @typeParam T - The discriminated-union type to match.
|
||||
* @typeParam K - The discriminant property of `T`.
|
||||
*/
|
||||
export interface TaggedUnionMatcherW<T extends object, K extends keyof T> {
|
||||
<P extends Exact<Handlers<T, K, unknown>, P>>(
|
||||
handlers: P & UniverseGate<Tags<T, K>>,
|
||||
): UnaryFn<T, PatternReturns<P>>;
|
||||
@@ -121,15 +137,29 @@ interface TaggedUnionMatcherWidening<T extends object, K extends keyof T> {
|
||||
// The key-taking step of the curried factory. Naming it lets the factory return
|
||||
// `dispatch` directly, the tacit twin of the primitive-union factory's bare
|
||||
// `=> dispatch`.
|
||||
type TaggedUnionMatcherFactory<T extends object> = <K extends Discriminated<T>>(
|
||||
k: K,
|
||||
) => TaggedUnionMatcherStrict<T, K>;
|
||||
|
||||
type TaggedUnionMatcherWideningFactory<T extends object> = <
|
||||
/**
|
||||
* The function returned by {@link getTaggedUnionMatcher}: call it with the
|
||||
* discriminant property's name to get the handler-map builder.
|
||||
*
|
||||
* @typeParam T - The discriminated-union type to match.
|
||||
*/
|
||||
export type TaggedUnionMatcherFactory<T extends object> = <
|
||||
K extends Discriminated<T>,
|
||||
>(
|
||||
k: K,
|
||||
) => TaggedUnionMatcherWidening<T, K>;
|
||||
) => TaggedUnionMatcher<T, K>;
|
||||
|
||||
/**
|
||||
* The function returned by {@link getTaggedUnionMatcherW}: call it with the
|
||||
* discriminant property's name to get the widening handler-map builder.
|
||||
*
|
||||
* @typeParam T - The discriminated-union type to match.
|
||||
*/
|
||||
export type TaggedUnionMatcherWFactory<T extends object> = <
|
||||
K extends Discriminated<T>,
|
||||
>(
|
||||
k: K,
|
||||
) => TaggedUnionMatcherW<T, K>;
|
||||
|
||||
const dispatch =
|
||||
(k: PropertyKey) =>
|
||||
@@ -152,10 +182,49 @@ const dispatch =
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a matcher for a discriminated union, with one common return type.
|
||||
*
|
||||
* The first call fixes the union `T`; the returned function takes the
|
||||
* discriminant property's name (`K`, restricted to properties whose values are
|
||||
* tags), and that returns the handler-map builder. Supplying a second fallback
|
||||
* argument to the builder allows a partial map and receives the members whose
|
||||
* tag was not handled. A `boolean`, `null` or `undefined` tag is keyed by its
|
||||
* stringified form (`true` -> `"true"`); see README § Caveats.
|
||||
*
|
||||
* @typeParam T - The discriminated-union type to match.
|
||||
* @returns A function that takes the discriminant property's name.
|
||||
* @example
|
||||
* type Shape =
|
||||
* | { kind: "circle"; radius: number }
|
||||
* | { kind: "square"; side: number };
|
||||
*
|
||||
* const area = getTaggedUnionMatcher<Shape>()("kind")({
|
||||
* circle: (s) => Math.PI * s.radius ** 2,
|
||||
* square: (s) => s.side ** 2,
|
||||
* });
|
||||
*/
|
||||
export const getTaggedUnionMatcher = <
|
||||
T extends object,
|
||||
>(): TaggedUnionMatcherFactory<T> => dispatch;
|
||||
|
||||
/**
|
||||
* Create a matcher for a discriminated union whose return type is the union of
|
||||
* every handler's return type.
|
||||
*
|
||||
* The widening counterpart of {@link getTaggedUnionMatcher}: use it when the
|
||||
* handlers return different types and the union, not one common `R`, is wanted.
|
||||
* The curried key step and the optional fallback are identical.
|
||||
*
|
||||
* @typeParam T - The discriminated-union type to match.
|
||||
* @returns A function that takes the discriminant property's name.
|
||||
* @example
|
||||
* const describe = getTaggedUnionMatcherW<Shape>()("kind")({
|
||||
* circle: () => "round",
|
||||
* square: () => 4,
|
||||
* });
|
||||
* // describe: (shape: Shape) => string | number
|
||||
*/
|
||||
export const getTaggedUnionMatcherW = <
|
||||
T extends object,
|
||||
>(): TaggedUnionMatcherWideningFactory<T> => dispatch;
|
||||
>(): TaggedUnionMatcherWFactory<T> => dispatch;
|
||||
Reference in new issue
Block a user