Skip to content

Commit

Permalink
feat: add merge operation to interface types
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `InterfaceType#withOptional(...)` no longer returns an `IntersectionType`, but an `InterfaceType` instead. Should cause no issues when upgrading, but some consumer code might depend on it. It is still possible to manually create an `IntersectionType` using `intersection(...)`. There are also some minor type changes to make all that possible.
  • Loading branch information
pavadeli committed Oct 8, 2024
1 parent b0be062 commit 3f4b760
Show file tree
Hide file tree
Showing 48 changed files with 741 additions and 226 deletions.
6 changes: 3 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
"editor.detectIndentation": false,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.fixAll": "explicit"
},
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"files.associations": { "*.json": "jsonc" },
"javascript.preferences.importModuleSpecifierEnding": "js",
"typescript.preferences.importModuleSpecifierEnding": "js",
"files.readonlyInclude": {
"etc/**/*": true,
"markdown/**/*": true,
"node_modules/**/*": true
},
"task.allowAutomaticTasks": "on"
"task.allowAutomaticTasks": "on",
"editor.wordWrapColumn": 140
}
64 changes: 48 additions & 16 deletions etc/types.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export interface ArrayTypeConfig extends LengthChecksConfig {
// @public
export type ArrayViolation = LengthViolation;

// @public
export const autoCastFailure: unique symbol;

// @public
export abstract class BaseObjectLikeTypeImpl<ResultType, TypeConfig = unknown> extends BaseTypeImpl<ResultType, TypeConfig> {
and<Other extends BaseObjectLikeTypeImpl<any, any>>(_other: Other): ObjectType<MergeIntersection<ResultType & Other[typeof designType]>> & TypedPropertyInformation<this['props'] & Other['props']>;
Expand All @@ -41,7 +44,7 @@ export abstract class BaseObjectLikeTypeImpl<ResultType, TypeConfig = unknown> e
abstract readonly possibleDiscriminators: readonly PossibleDiscriminator[];
// (undocumented)
abstract readonly props: Properties;
protected get propsArray(): ReadonlyArray<[string, Type<unknown>]>;
protected get propsArray(): ReadonlyArray<[string, PropertyInfo]>;
// (undocumented)
abstract readonly propsInfo: PropertiesInfo;
}
Expand All @@ -63,6 +66,7 @@ export abstract class BaseTypeImpl<ResultType, TypeConfig = unknown> implements
// (undocumented)
protected createAutoCastAllType(): this;
protected createResult(input: unknown, result: unknown, validatorResult: ValidationResult): Result<ResultType>;
protected readonly customValidators: ReadonlyArray<(<T extends ResultType>(this: void, input: T, options: ValidationOptions) => Result<T>)>;
readonly enumerableLiteralDomain?: Iterable<LiteralValue>;
extendWith<const E>(factory: (type: this) => E): this & E;
get is(): TypeguardFor<ResultType>;
Expand Down Expand Up @@ -96,7 +100,10 @@ export function booleanAutoCaster(input: unknown): boolean | typeof autoCastFail
export type Branded<T, BrandName extends string> = T extends WithBrands<infer Base, infer ExistingBrands> ? WithBrands<Base, BrandName | ExistingBrands> : WithBrands<T, BrandName>;

// @public
export function createType<Impl extends BaseTypeImpl<any, any>>(impl: Impl, override?: Partial<Record<keyof BaseTypeImpl<any, any> | 'typeValidator' | 'typeParser', PropertyDescriptor>>): TypeImpl<Impl>;
export const brands: unique symbol;

// @public
export function createType<Impl extends BaseTypeImpl<any, any>>(impl: Impl, override?: Partial<Record<keyof BaseTypeImpl<any, any> | 'typeValidator' | 'typeParser' | 'customValidators', PropertyDescriptor>>): TypeImpl<Impl>;

// @public
export type CustomMessage<T, E = void> = undefined | string | ((got: string, input: T, explanation: E) => string);
Expand All @@ -108,6 +115,9 @@ export type DeepUnbranded<T> = T extends ReadonlyArray<unknown> ? {
[P in keyof T]: DeepUnbranded<T[P]>;
}, typeof brands> : Unbranded<T>;

// @public
export const designType: unique symbol;

// @public
export interface Failure {
details: OneOrMore<FailureDetails>;
Expand All @@ -122,24 +132,32 @@ export interface Failure {
export type FailureDetails = ValidationDetails & MessageDetails;

// @public (undocumented)
export type FullType<Props extends Properties> = TypeImpl<InterfaceType<Props, TypeOfProperties<Writable<Props>>>>;
export type FullType<Props extends Properties> = TypeImpl<InterfaceType<Simplify<Props>, Simplify<TypeOfProperties<Writable<Props>>>>>;

// @public (undocumented)
export type int = The<typeof int>;

// @public (undocumented)
export const int: Type<Branded<number, 'int'>, NumberTypeConfig>;

// @public (undocumented)
export interface InterfaceMergeOptions {
ignoreParsers?: true;
ignoreValidations?: true;
name?: string;
}

// @public
export class InterfaceType<Props extends Properties, ResultType> extends BaseObjectLikeTypeImpl<ResultType> implements TypedPropertyInformation<Props> {
constructor(
props: Props, options: InterfaceTypeOptions);
propsInfo: PropertiesInfo<Props>, options: InterfaceTypeOptions);
accept<R>(visitor: Visitor<R>): R;
readonly basicType: 'object';
// (undocumented)
readonly isDefaultName: boolean;
readonly keys: readonly (keyof Props)[];
maybeStringify(value: ResultType): string;
mergeWith<OtherProps extends Properties, OtherType>(...args: [type: InterfaceType<OtherProps, OtherType>] | [name: string, type: InterfaceType<OtherProps, OtherType>] | [options: InterfaceMergeOptions, type: InterfaceType<OtherProps, OtherType>]): MergeType<Props, ResultType, OtherProps, OtherType>;
readonly name: string;
// (undocumented)
readonly options: InterfaceTypeOptions;
Expand All @@ -152,17 +170,22 @@ export class InterfaceType<Props extends Properties, ResultType> extends BaseObj
toPartial(name?: string): PartialType<Props>;
readonly typeConfig: undefined;
protected typeValidator(input: unknown, options: ValidationOptions): Result<ResultType>;
withOptional<PartialProps extends Properties>(...args: [props: PartialProps] | [name: string, props: PartialProps]): TypeImpl<BaseObjectLikeTypeImpl<MergeIntersection<ResultType & Partial<TypeOfProperties<Writable<PartialProps>>>>>> & TypedPropertyInformation<Props & PartialProps>;
withOptional<PartialProps extends Properties>(...args: [props: PartialProps] | [name: string, props: PartialProps] | [options: InterfaceMergeOptions, props: PartialProps]): MergeType<Props, ResultType, PartialProps, Partial<TypeOfProperties<Writable<PartialProps>>>>;
withRequired<OtherProps extends Properties>(...args: [props: OtherProps] | [name: string, props: OtherProps] | [options: InterfaceMergeOptions, props: OtherProps]): MergeType<Props, ResultType, OtherProps, TypeOfProperties<Writable<OtherProps>>>;
}

// @public
export interface InterfaceTypeOptions {
checkOnly?: boolean;
name?: string;
partial?: boolean;
strictMissingKeys?: boolean;
}

// @public (undocumented)
export interface InterfaceTypeOptionsWithPartial extends InterfaceTypeOptions {
partial?: boolean;
}

// @public
export function intersection<Types extends OneOrMore<BaseObjectLikeTypeImpl<unknown>>>(...args: [name: string, types: Types] | [types: Types]): TypeImpl<IntersectionType<Types>>;

Expand Down Expand Up @@ -249,9 +272,10 @@ export class LiteralType<ResultType extends LiteralValue> extends BaseTypeImpl<R
export type LiteralValue = string | number | boolean | null | undefined | void;

// @public
export type MergeIntersection<T> = T extends Record<PropertyKey, unknown> ? {
[P in keyof T]: T[P];
} & {} : T;
export type MergeIntersection<T> = T extends Record<PropertyKey, unknown> ? Simplify<T> : T;

// @public (undocumented)
export type MergeType<Props extends Properties, ResultType, OtherProps extends Properties, OtherResultType> = TypeImpl<InterfaceType<Simplify<Omit<Props, keyof OtherProps> & OtherProps>, Simplify<Omit<ResultType, keyof OtherResultType> & OtherResultType>>>;

// @public
export type MessageDetails = Partial<ValidationDetails> & {
Expand Down Expand Up @@ -324,7 +348,7 @@ export type NumberTypeConfig = {
export type NumberViolation = 'min' | 'max' | 'multipleOf';

// @public
export function object<Props extends Properties>(...args: [props: Props] | [name: string, props: Props] | [options: InterfaceTypeOptions, props: Props]): FullType<Props>;
export function object<Props extends Properties>(...args: [props: Props] | [name: string, props: Props] | [options: InterfaceTypeOptionsWithPartial, props: Props]): FullType<Props>;

// @public
export type ObjectType<ResultType, TypeConfig = unknown> = TypeImpl<BaseObjectLikeTypeImpl<ResultType, TypeConfig>>;
Expand All @@ -339,10 +363,10 @@ export interface ParserOptions {
}

// @public
export function partial<Props extends Properties>(...args: [props: Props] | [name: string, props: Props] | [options: Omit<InterfaceTypeOptions, 'partial'>, props: Props]): PartialType<Props>;
export function partial<Props extends Properties>(...args: [props: Props] | [name: string, props: Props] | [options: InterfaceTypeOptions, props: Props]): PartialType<Props>;

// @public (undocumented)
export type PartialType<Props extends Properties> = TypeImpl<InterfaceType<Props, Partial<TypeOfProperties<Writable<Props>>>>>;
export type PartialType<Props extends Properties> = TypeImpl<InterfaceType<Simplify<Props>, Simplify<Partial<TypeOfProperties<Writable<Props>>>>>>;

// @public (undocumented)
export function pattern<const BrandName extends string>(name: BrandName, regExp: RegExp, customMessage?: StringTypeConfig['customMessage']): Type<Branded<string, BrandName>, StringTypeConfig>;
Expand Down Expand Up @@ -374,10 +398,7 @@ export type Properties = Record<string, Type<any>>;

// @public
export type PropertiesInfo<Props extends Properties = Properties> = {
[Key in keyof Props]: {
partial: boolean;
type: Props[Key];
};
[Key in keyof Props]: PropertyInfo<Props[Key]>;
};

// @public (undocumented)
Expand All @@ -387,6 +408,12 @@ export type PropertiesOfTypeTuple<Tuple> = Tuple extends [{
readonly props: infer A;
}, ...infer Rest] ? MergeIntersection<A & PropertiesOfTypeTuple<Rest>> : Properties;

// @public
export type PropertyInfo<T extends Type<unknown> = Type<unknown>> = {
partial: boolean;
type: T;
};

// @public
export function record<KeyType extends number | string, ValueType>(...args: [name: string, keyType: BaseTypeImpl<KeyType>, valueType: BaseTypeImpl<ValueType>, strict?: boolean] | [keyType: BaseTypeImpl<KeyType>, valueType: BaseTypeImpl<ValueType>, strict?: boolean]): TypeImpl<RecordType<BaseTypeImpl<KeyType>, KeyType, BaseTypeImpl<ValueType>, ValueType>>;

Expand Down Expand Up @@ -445,6 +472,11 @@ export interface SimpleTypeOptions<ResultType, TypeConfig> {
typeConfig: BaseTypeImpl<ResultType, TypeConfig>['typeConfig'];
}

// @public
export type Simplify<T> = {
[P in keyof T]: T[P];
} & {};

// @public
export const string: Type<string, StringTypeConfig>;

Expand Down
13 changes: 13 additions & 0 deletions markdown/types.autocastfailure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@skunkteam/types](./types.md) &gt; [autoCastFailure](./types.autocastfailure.md)

## autoCastFailure variable

Returned by an autocaster to indicate that it is not able to auto-cast the given input.

**Signature:**

```typescript
autoCastFailure: unique symbol
```
2 changes: 1 addition & 1 deletion markdown/types.baseobjectliketypeimpl.and.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ and<Other extends BaseObjectLikeTypeImpl<any, any>>(_other: Other): ObjectType<M
**Returns:**
[ObjectType](./types.objecttype.md)<!-- -->&lt;[MergeIntersection](./types.mergeintersection.md)<!-- -->&lt;ResultType &amp; Other\[typeof designType\]&gt;&gt; &amp; [TypedPropertyInformation](./types.typedpropertyinformation.md)<!-- -->&lt;this\['props'\] &amp; Other\['props'\]&gt;
[ObjectType](./types.objecttype.md)<!-- -->&lt;[MergeIntersection](./types.mergeintersection.md)<!-- -->&lt;ResultType &amp; Other\[typeof [designType](./types.designtype.md)<!-- -->\]&gt;&gt; &amp; [TypedPropertyInformation](./types.typedpropertyinformation.md)<!-- -->&lt;this\['props'\] &amp; Other\['props'\]&gt;
## Remarks
Expand Down
14 changes: 7 additions & 7 deletions markdown/types.baseobjectliketypeimpl.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ Object-like types need to provide more information to be able to correctly compo
## Properties
| Property | Modifiers | Type | Description |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| [isDefaultName](./types.baseobjectliketypeimpl.isdefaultname.md) | <p><code>abstract</code></p><p><code>readonly</code></p> | boolean | |
| [possibleDiscriminators](./types.baseobjectliketypeimpl.possiblediscriminators.md) | <p><code>abstract</code></p><p><code>readonly</code></p> | readonly [PossibleDiscriminator](./types.possiblediscriminator.md)<!-- -->\[\] | |
| [props](./types.baseobjectliketypeimpl.props.md) | <p><code>abstract</code></p><p><code>readonly</code></p> | [Properties](./types.properties.md) | |
| [propsArray](./types.baseobjectliketypeimpl.propsarray.md) | <p><code>protected</code></p><p><code>readonly</code></p> | ReadonlyArray&lt;\[string, [Type](./types.type.md)<!-- -->&lt;unknown&gt;\]&gt; | Array of props tuples (<code>Object.entries(this.prop)</code>). |
| [propsInfo](./types.baseobjectliketypeimpl.propsinfo.md) | <p><code>abstract</code></p><p><code>readonly</code></p> | [PropertiesInfo](./types.propertiesinfo.md) | |
| Property | Modifiers | Type | Description |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| [isDefaultName](./types.baseobjectliketypeimpl.isdefaultname.md) | <p><code>abstract</code></p><p><code>readonly</code></p> | boolean | |
| [possibleDiscriminators](./types.baseobjectliketypeimpl.possiblediscriminators.md) | <p><code>abstract</code></p><p><code>readonly</code></p> | readonly [PossibleDiscriminator](./types.possiblediscriminator.md)<!-- -->\[\] | |
| [props](./types.baseobjectliketypeimpl.props.md) | <p><code>abstract</code></p><p><code>readonly</code></p> | [Properties](./types.properties.md) | |
| [propsArray](./types.baseobjectliketypeimpl.propsarray.md) | <p><code>protected</code></p><p><code>readonly</code></p> | ReadonlyArray&lt;\[string, [PropertyInfo](./types.propertyinfo.md)<!-- -->\]&gt; | Array of props tuples (<code>Object.entries(this.prop)</code>). |
| [propsInfo](./types.baseobjectliketypeimpl.propsinfo.md) | <p><code>abstract</code></p><p><code>readonly</code></p> | [PropertiesInfo](./types.propertiesinfo.md) | |
## Methods
Expand Down
2 changes: 1 addition & 1 deletion markdown/types.baseobjectliketypeimpl.propsarray.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Array of props tuples (`Object.entries(this.prop)`<!-- -->).
**Signature:**

```typescript
protected get propsArray(): ReadonlyArray<[string, Type<unknown>]>;
protected get propsArray(): ReadonlyArray<[string, PropertyInfo]>;
```
13 changes: 13 additions & 0 deletions markdown/types.basetypeimpl.customvalidators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@skunkteam/types](./types.md) &gt; [BaseTypeImpl](./types.basetypeimpl.md) &gt; [customValidators](./types.basetypeimpl.customvalidators.md)

## BaseTypeImpl.customValidators property

Additional custom validation added using [withValidation](./types.basetypeimpl.withvalidation.md) or [withConstraint](./types.basetypeimpl.withconstraint.md)<!-- -->.

**Signature:**

```typescript
protected readonly customValidators: ReadonlyArray<(<T extends ResultType>(this: void, input: T, options: ValidationOptions) => Result<T>)>;
```
Loading

0 comments on commit 3f4b760

Please sign in to comment.