Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ArrayCake and TupleCake #48

Merged
merged 3 commits into from
Jan 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,32 @@ type Person = Infer<typeof Person>;
> const Person: Cake<Person> = bake(...);
> ```

### Creating Complex Cakes

More complex types, with nested objects and arrays, are also supported:

```ts
type Account = {
person: Person;
friends: string[];
settings: {
sendNotifications: boolean;
};
};

const Account = bake({
person: Person,
friends: array(string),
settings: {
sendNotifications: boolean,
},
} as const);
```

> - We can refer to the existing `Person` Cake when defining the `Account` Cake.
> - The [array](#array) helper returns a Cake that represents arrays of the given type.
> - Nested objects don't require any special syntax.

### More Cakes

When you created the `Person` Cake, you imported the [string](#string) and [number](#number) Cakes to define the types of a Person's properties. However, you can also use these Cakes directly:
Expand Down Expand Up @@ -240,7 +266,7 @@ const Person = bake({
age: optional(number),
} as const);

Person.is({ name: Alice }); // true
Person.is({ name: "Alice" }); // true
```

Use [Infer](#infer) to infer the TypeScript type from the Cake:
Expand All @@ -254,6 +280,27 @@ type Person = Infer<typeof Person>;
const bob: Person = { name: "Bob", age: 42 };
```

</td></tr>
<tr><td>

An array:

```ts
type Numbers = number[];
```

</td><td>

Use [array](#array):

```ts
import { array, number } from "caketype";

const Numbers = array(number);

Numbers.is([2, 3]); // true
```

</td></tr>
</table>

Expand All @@ -275,6 +322,7 @@ const bob: Person = { name: "Bob", age: 42 };
- [`Infer`](#infer)
- [Built-in Cakes](#built-in-cakes)
- [`any`](#any)
- [`array`](#array)
- [`boolean`](#boolean)
- [`bigint`](#bigint)
- [`never`](#never)
Expand Down Expand Up @@ -632,6 +680,22 @@ of `unknown` instead of `any`.

---

#### `array`

Return a [Cake](#cake) representing an array of the specified type.

```ts
const nums = array(number);

nums.is([2, 3]); // true
nums.is([]); // true

nums.is(["oops"]); // false
nums.is({}); // false
```

---

#### `boolean`

A [Cake](#cake) representing the `boolean` type.
Expand Down
122 changes: 113 additions & 9 deletions etc/caketype.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@
// @public
export const any: TypeGuardCake<any>;

// @public
export function array<B extends Bakeable>(bakeable: B): ArrayCake<Baked<B>>;

// @public (undocumented)
export class ArrayCake<C extends Cake = Cake> extends TupleCake<readonly [], readonly [], C, readonly []> implements ArrayCakeRecipe<C> {
constructor(recipe: ArrayCakeRecipe<C>);
// (undocumented)
readonly element: C;
// (undocumented)
withName(name: string | null): ArrayCake<C>;
}

// @public (undocumented)
export interface ArrayCakeRecipe<C extends Cake> extends CakeRecipe {
// (undocumented)
element: C;
}

// @public
export type Assert<T extends true> = never;

Expand Down Expand Up @@ -324,6 +342,17 @@ export const never: TypeGuardCake<never>;
// @public
export type Not<T extends boolean> = T extends true ? false : true;

// @public (undocumented)
export class NotAnArrayCakeError extends CakeError {
constructor(cake: Cake, value: unknown);
// (undocumented)
readonly cake: Cake;
// (undocumented)
dispatchFormat(context: CakeErrorDispatchFormatContext): StringTree;
// (undocumented)
readonly value: unknown;
}

// @public (undocumented)
export class NotAnObjectCakeError extends CakeError {
constructor(cake: Cake, value: unknown);
Expand Down Expand Up @@ -389,15 +418,6 @@ export class ObjectPropertiesCakeError extends CakeError {
readonly value: object;
}

// @public (undocumented)
export class ObjectRequiredPropertyMissingCakeError extends CakeError {
constructor(cake: Cake);
// (undocumented)
readonly cake: Cake;
// (undocumented)
dispatchFormat(context: CakeErrorDispatchFormatContext): StringTree;
}

// @public
export const ObjectUtils: {
readonly entries: typeof entries;
Expand Down Expand Up @@ -486,6 +506,23 @@ export interface ReferenceCakeRecipe<C extends Cake> extends CakeRecipe {
readonly get: () => C;
}

// @public (undocumented)
export class RequiredPropertyMissingCakeError extends CakeError {
constructor(cake: Cake);
// (undocumented)
readonly cake: Cake;
// (undocumented)
dispatchFormat(context: CakeErrorDispatchFormatContext): StringTree;
}

// @public (undocumented)
export function rest<T>(value: T): RestTag<T>;

// @public (undocumented)
export class RestTag<T> extends Tag<"rest", T> {
constructor(untagged: T);
}

// @public
export type Result<T, E> = Ok<T> | Err<E>;

Expand All @@ -509,6 +546,73 @@ export type StringTree = string | readonly [string, readonly StringTree[]];
// @public
export const symbol: TypeGuardCake<symbol>;

// Warning: (ae-forgotten-export) The symbol "MapInfer" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "MapInferOptional" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "TupleCakeRecipe" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export class TupleCake<S extends readonly Cake[], O extends readonly Cake[], R extends Cake | null, E extends readonly Cake[]> extends Cake<[
...MapInfer<S>,
...MapInferOptional<O>,
...(R extends Cake ? [...Infer<R>[]] : []),
...MapInfer<E>
]> implements TupleCakeRecipe<S, O, R, E> {
constructor(recipe: TupleCakeRecipe<S, O, R, E>);
// (undocumented)
dispatchCheck(value: unknown, context: CakeDispatchCheckContext): CakeError | null;
// (undocumented)
dispatchStringify(context: CakeDispatchStringifyContext): string;
// Warning: (ae-forgotten-export) The symbol "MapOptionalTag" needs to be exported by the entry point index.d.ts
//
// (undocumented)
elements(): [
...S,
...MapOptionalTag<O>,
...(R extends null ? [] : [RestTag<R>]),
...E
];
// (undocumented)
elements(length: number): (S[number] | OptionalTag<O[number]> | (R extends null ? never : RestTag<R>) | E[number])[] | null;
// (undocumented)
readonly endElements: E;
// (undocumented)
maxLength(): number | null;
// (undocumented)
minLength(): number;
// (undocumented)
readonly optionalElements: O;
// (undocumented)
readonly restElement: R;
// (undocumented)
readonly startElements: S;
// (undocumented)
withName(name: string | null): TupleCake<S, O, R, E>;
}

// @public (undocumented)
export class TupleElementsCakeError extends CakeError {
constructor(cake: TupleCake<readonly Cake[], readonly Cake[], Cake | null, readonly Cake[]>, value: unknown, errors: Record<string, CakeError>);
// (undocumented)
readonly cake: TupleCake<readonly Cake[], readonly Cake[], Cake | null, readonly Cake[]>;
// (undocumented)
dispatchFormat(context: CakeErrorDispatchFormatContext): StringTree;
// (undocumented)
readonly errors: Record<string, CakeError>;
// (undocumented)
readonly value: unknown;
}

// @public (undocumented)
export class TupleWrongLengthCakeError extends CakeError {
constructor(cake: TupleCake<readonly Cake[], readonly Cake[], Cake | null, readonly Cake[]>, value: unknown[]);
// (undocumented)
readonly cake: TupleCake<readonly Cake[], readonly Cake[], Cake | null, readonly Cake[]>;
// (undocumented)
dispatchFormat(context: CakeErrorDispatchFormatContext): StringTree;
// (undocumented)
readonly value: unknown[];
}

// @public (undocumented)
export function typeGuard<T>(name: string, guard: (value: unknown) => value is T): TypeGuardCake<T>;

Expand Down
66 changes: 66 additions & 0 deletions src/cake/ArrayCake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
bake,
Bakeable,
Baked,
Cake,
CakeRecipe,
TupleCake,
} from "./index-internal";

/**
* @public
*/
interface ArrayCakeRecipe<C extends Cake> extends CakeRecipe {
element: C;
}

/**
* @public
*/
class ArrayCake<C extends Cake = Cake>
extends TupleCake<readonly [], readonly [], C, readonly []>
implements ArrayCakeRecipe<C>
{
readonly element: C;

constructor(recipe: ArrayCakeRecipe<C>) {
const { element, ...base } = recipe;
super({
...base,
startElements: [],
optionalElements: [],
restElement: element,
endElements: [],
});
this.element = element;
}

override withName(name: string | null): ArrayCake<C> {
return new ArrayCake({ ...this, name });
}
}

/**
* Return a {@link Cake} representing an array of the specified type.
*
* @example
*
* ```ts
* const nums = array(number);
*
* nums.is([2, 3]); // true
* nums.is([]); // true
*
* nums.is(["oops"]); // false
* nums.is({}); // false
* ```
*
* @public
*/
function array<B extends Bakeable>(bakeable: B): ArrayCake<Baked<B>> {
return new ArrayCake({
element: bake(bakeable),
}) as ArrayCake<Baked<B>>;
}

export { ArrayCake, ArrayCakeRecipe, array };
14 changes: 6 additions & 8 deletions src/cake/ObjectCake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class ObjectCake<P extends ObjectCakeProperties>
if (getOption(this, "objectExcessProperties") === "error") {
for (const [key, propertyValue] of entriesIncludingSymbols(value)) {
if (!(key in this.properties)) {
errors[key] = new ObjectExcessPropertyCakeError(propertyValue);
errors[key] = new ExcessPropertyPresentCakeError(propertyValue);
hasError = true;
}
}
Expand Down Expand Up @@ -117,9 +117,7 @@ class ObjectCake<P extends ObjectCakeProperties>
}
return recurse(untagged, propertyValue);
} else {
return isOptional
? null
: new ObjectRequiredPropertyMissingCakeError(untagged);
return isOptional ? null : new RequiredPropertyMissingCakeError(untagged);
}
}

Expand Down Expand Up @@ -171,7 +169,7 @@ class NotAnObjectCakeError extends CakeError {
/**
* @public
*/
class ObjectExcessPropertyCakeError extends CakeError {
class ExcessPropertyPresentCakeError extends CakeError {
constructor(readonly value: unknown) {
super();
}
Expand All @@ -187,7 +185,7 @@ class ObjectExcessPropertyCakeError extends CakeError {
/**
* @public
*/
class ObjectRequiredPropertyMissingCakeError extends CakeError {
class RequiredPropertyMissingCakeError extends CakeError {
constructor(readonly cake: Cake) {
super();
}
Expand Down Expand Up @@ -231,7 +229,7 @@ export {
ObjectCakeProperties,
ObjectCakeRecipe,
NotAnObjectCakeError,
ObjectExcessPropertyCakeError,
ExcessPropertyPresentCakeError,
ObjectPropertiesCakeError,
ObjectRequiredPropertyMissingCakeError,
RequiredPropertyMissingCakeError,
};
Loading