diff --git a/etc/types.api.md b/etc/types.api.md index d0ef588..cc405ec 100644 --- a/etc/types.api.md +++ b/etc/types.api.md @@ -89,7 +89,6 @@ export abstract class BaseTypeImpl implements withBrand(name: BrandName): Type, TypeConfig>; withConfig(name: BrandName, newConfig: TypeConfig): Type, TypeConfig>; withConstraint(name: BrandName, constraint: Validator): Type, TypeConfig>; - withDefault(...args: [value: DeepUnbranded] | [name: string, value: DeepUnbranded] | [options: WithDefaultOptions, value: DeepUnbranded]): this; withName(name: string): this; withParser(...args: [newConstructor: (i: unknown) => unknown] | [name: string, newConstructor: (i: unknown) => unknown] | [options: ParserOptions, newConstructor: (i: unknown) => unknown]): this; withValidation(validation: Validator): this; @@ -110,9 +109,6 @@ export type Branded = T extends WithBrands(arr: T[]): OneOrMore; - // @public export function createType>(impl: Impl, override?: Partial | 'typeValidator' | 'typeParser' | 'customValidators', PropertyDescriptor>>): TypeImpl; @@ -236,9 +232,6 @@ export class IntersectionType>; } -// @public -export function isOneOrMore(arr: T[]): arr is OneOrMore; - // @public export function isType(value: unknown): value is Type; @@ -675,15 +668,13 @@ export interface Visitor { // (undocumented) visitCustomType(type: BaseTypeImpl): R; // (undocumented) - visitIntersectionType(type: IntersectionType>>): R; - // (undocumented) visitKeyofType(type: KeyofType, any>): R; // (undocumented) visitLiteralType(type: LiteralType): R; // (undocumented) visitNumberType(type: BaseTypeImpl): R; // (undocumented) - visitObjectType(type: InterfaceType): R; + visitObjectLikeType(type: BaseObjectLikeTypeImpl): R; // (undocumented) visitRecordType(type: RecordType, number | string, BaseTypeImpl, unknown>): R; // (undocumented) @@ -708,12 +699,6 @@ export type WithBrands = T & { }; }; -// @public -export interface WithDefaultOptions { - clone?: boolean; - name?: string; -} - // @public export type Writable = { -readonly [P in keyof T]: T[P]; diff --git a/markdown/types.basetypeimpl.md b/markdown/types.basetypeimpl.md index 73bd8e6..f1a152e 100644 --- a/markdown/types.basetypeimpl.md +++ b/markdown/types.basetypeimpl.md @@ -54,7 +54,6 @@ All type-implementations must extend this base class. Use [createType()](./types | [withBrand(name)](./types.basetypeimpl.withbrand.md) | | Create a new instance of this Type with the given name. | | [withConfig(name, newConfig)](./types.basetypeimpl.withconfig.md) | | Create a new instance of this Type with the additional type-specific config, such as min/max values. | | [withConstraint(name, constraint)](./types.basetypeimpl.withconstraint.md) | | Create a new type based on the current type and use the given constraint function as validation. | -| [withDefault(args)](./types.basetypeimpl.withdefault.md) | | Define a new type with the same specs, but with the given default value. | | [withName(name)](./types.basetypeimpl.withname.md) | | Create a new instance of this Type with the given name. | | [withParser(args)](./types.basetypeimpl.withparser.md) | | Define a new type with the same specs, but with the given parser and an optional new name. | | [withValidation(validation)](./types.basetypeimpl.withvalidation.md) | | Clone the type with the added validation. | diff --git a/markdown/types.basetypeimpl.withdefault.md b/markdown/types.basetypeimpl.withdefault.md deleted file mode 100644 index d171fa8..0000000 --- a/markdown/types.basetypeimpl.withdefault.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@skunkteam/types](./types.md) > [BaseTypeImpl](./types.basetypeimpl.md) > [withDefault](./types.basetypeimpl.withdefault.md) - -## BaseTypeImpl.withDefault() method - -Define a new type with the same specs, but with the given default value. - -**Signature:** - -```typescript -withDefault(...args: [value: DeepUnbranded] | [name: string, value: DeepUnbranded] | [options: WithDefaultOptions, value: DeepUnbranded]): this; -``` - -## Parameters - -| Parameter | Type | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| args | \[value: [DeepUnbranded](./types.deepunbranded.md)<ResultType>\] \| \[name: string, value: [DeepUnbranded](./types.deepunbranded.md)<ResultType>\] \| \[options: [WithDefaultOptions](./types.withdefaultoptions.md), value: [DeepUnbranded](./types.deepunbranded.md)<ResultType>\] | | - -**Returns:** - -this - -## Remarks - -This is a convenient method that adds a simple parser that resolves to the given `value` whenever the `input` to the parser is `undefined`. diff --git a/markdown/types.checkoneormore.md b/markdown/types.checkoneormore.md deleted file mode 100644 index 083dbfa..0000000 --- a/markdown/types.checkoneormore.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@skunkteam/types](./types.md) > [checkOneOrMore](./types.checkoneormore.md) - -## checkOneOrMore() function - -Returns the original array if and only if it has at least one element. - -**Signature:** - -```typescript -declare function checkOneOrMore(arr: T[]): OneOrMore; -``` - -## Parameters - -| Parameter | Type | Description | -| --------- | ----- | ----------- | -| arr | T\[\] | | - -**Returns:** - -[OneOrMore](./types.oneormore.md)<T> diff --git a/markdown/types.isoneormore.md b/markdown/types.isoneormore.md deleted file mode 100644 index fb16eb8..0000000 --- a/markdown/types.isoneormore.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@skunkteam/types](./types.md) > [isOneOrMore](./types.isoneormore.md) - -## isOneOrMore() function - -Type guard for `OneOrMore` - -**Signature:** - -```typescript -declare function isOneOrMore(arr: T[]): arr is OneOrMore; -``` - -## Parameters - -| Parameter | Type | Description | -| --------- | ----- | ----------- | -| arr | T\[\] | | - -**Returns:** - -arr is [OneOrMore](./types.oneormore.md)<T> - -## Remarks - -This checks if the array has at least one element. diff --git a/markdown/types.md b/markdown/types.md index e5f9258..a7185f9 100644 --- a/markdown/types.md +++ b/markdown/types.md @@ -35,10 +35,8 @@ Runtime type-validation with derived TypeScript types. | [autoCast(type)](./types.autocast.md) | Returns the same type, but with an auto-casting default parser installed. | | [autoCastAll(type)](./types.autocastall.md) | Create a recursive autocasting version of the given type. | | [booleanAutoCaster(input)](./types.booleanautocaster.md) | | -| [checkOneOrMore(arr)](./types.checkoneormore.md) | Returns the original array if and only if it has at least one element. | | [createType(impl, override)](./types.createtype.md) | Create a Type from the given type-implementation. | | [intersection(args)](./types.intersection.md) | Intersect the given types. | -| [isOneOrMore(arr)](./types.isoneormore.md) | Type guard for OneOrMore | | [isType(value)](./types.istype.md) | Type-guard that asserts that a given value is a Type. | | [keyof(args)](./types.keyof.md) | | | [literal(value)](./types.literal.md) | | @@ -73,7 +71,6 @@ Runtime type-validation with derived TypeScript types. | [TypeLink](./types.typelink.md) | An object that has an associated TypeScript type. | | [ValidationOptions](./types.validationoptions.md) | | | [Visitor](./types.visitor.md) | Interface for a visitor that is accepted by all types (classic visitor-pattern). | -| [WithDefaultOptions](./types.withdefaultoptions.md) | Options that can be passed to [BaseTypeImpl.withDefault()](./types.basetypeimpl.withdefault.md). | ## Variables diff --git a/markdown/types.oneormore.md b/markdown/types.oneormore.md index 36e6b41..5b9fb7f 100644 --- a/markdown/types.oneormore.md +++ b/markdown/types.oneormore.md @@ -11,7 +11,3 @@ An Array with at least one element. ```typescript type OneOrMore = [T, ...T[]]; ``` - -## Remarks - -Note that this type does not disable the mutable methods of the array, which may still invalidate the non-emptiness of the array. diff --git a/markdown/types.visitor.md b/markdown/types.visitor.md index ae689bd..7c0593a 100644 --- a/markdown/types.visitor.md +++ b/markdown/types.visitor.md @@ -19,11 +19,10 @@ interface Visitor | [visitArrayType(type)](./types.visitor.visitarraytype.md) | | | [visitBooleanType(type)](./types.visitor.visitbooleantype.md) | | | [visitCustomType(type)](./types.visitor.visitcustomtype.md) | | -| [visitIntersectionType(type)](./types.visitor.visitintersectiontype.md) | | | [visitKeyofType(type)](./types.visitor.visitkeyoftype.md) | | | [visitLiteralType(type)](./types.visitor.visitliteraltype.md) | | | [visitNumberType(type)](./types.visitor.visitnumbertype.md) | | -| [visitObjectType(type)](./types.visitor.visitobjecttype.md) | | +| [visitObjectLikeType(type)](./types.visitor.visitobjectliketype.md) | | | [visitRecordType(type)](./types.visitor.visitrecordtype.md) | | | [visitStringType(type)](./types.visitor.visitstringtype.md) | | | [visitUnionType(type)](./types.visitor.visituniontype.md) | | diff --git a/markdown/types.visitor.visitintersectiontype.md b/markdown/types.visitor.visitintersectiontype.md deleted file mode 100644 index dd070de..0000000 --- a/markdown/types.visitor.visitintersectiontype.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@skunkteam/types](./types.md) > [Visitor](./types.visitor.md) > [visitIntersectionType](./types.visitor.visitintersectiontype.md) - -## Visitor.visitIntersectionType() method - -**Signature:** - -```typescript -visitIntersectionType(type: IntersectionType>>): R; -``` - -## Parameters - -| Parameter | Type | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| type | [IntersectionType](./types.intersectiontype.md)<[OneOrMore](./types.oneormore.md)<[BaseObjectLikeTypeImpl](./types.baseobjectliketypeimpl.md)<unknown>>> | | - -**Returns:** - -R diff --git a/markdown/types.visitor.visitobjectliketype.md b/markdown/types.visitor.visitobjectliketype.md new file mode 100644 index 0000000..5b890c6 --- /dev/null +++ b/markdown/types.visitor.visitobjectliketype.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [@skunkteam/types](./types.md) > [Visitor](./types.visitor.md) > [visitObjectLikeType](./types.visitor.visitobjectliketype.md) + +## Visitor.visitObjectLikeType() method + +**Signature:** + +```typescript +visitObjectLikeType(type: BaseObjectLikeTypeImpl): R; +``` + +## Parameters + +| Parameter | Type | Description | +| --------- | ---------------------------------------------------------------------------------- | ----------- | +| type | [BaseObjectLikeTypeImpl](./types.baseobjectliketypeimpl.md)<unknown> | | + +**Returns:** + +R diff --git a/markdown/types.visitor.visitobjecttype.md b/markdown/types.visitor.visitobjecttype.md deleted file mode 100644 index 9ccd76f..0000000 --- a/markdown/types.visitor.visitobjecttype.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@skunkteam/types](./types.md) > [Visitor](./types.visitor.md) > [visitObjectType](./types.visitor.visitobjecttype.md) - -## Visitor.visitObjectType() method - -**Signature:** - -```typescript -visitObjectType(type: InterfaceType): R; -``` - -## Parameters - -| Parameter | Type | Description | -| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| type | [InterfaceType](./types.interfacetype.md)<[Properties](./types.properties.md), [unknownRecord](./types.unknownrecord.md)> | | - -**Returns:** - -R diff --git a/markdown/types.withdefaultoptions.clone.md b/markdown/types.withdefaultoptions.clone.md deleted file mode 100644 index c646d5b..0000000 --- a/markdown/types.withdefaultoptions.clone.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@skunkteam/types](./types.md) > [WithDefaultOptions](./types.withdefaultoptions.md) > [clone](./types.withdefaultoptions.clone.md) - -## WithDefaultOptions.clone property - -Whether to clone the given value on each use. - -**Signature:** - -```typescript -clone?: boolean; -``` - -## Remarks - -When `true` (the default), on each use the value will be cloned using `structuredClone` to prevent subtle bugs because of object reuse. diff --git a/markdown/types.withdefaultoptions.md b/markdown/types.withdefaultoptions.md deleted file mode 100644 index b439dd6..0000000 --- a/markdown/types.withdefaultoptions.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@skunkteam/types](./types.md) > [WithDefaultOptions](./types.withdefaultoptions.md) - -## WithDefaultOptions interface - -Options that can be passed to [BaseTypeImpl.withDefault()](./types.basetypeimpl.withdefault.md). - -**Signature:** - -```typescript -interface WithDefaultOptions -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --------------------------------------------- | --------- | ------- | ---------------------------------------------------------- | -| [clone?](./types.withdefaultoptions.clone.md) | | boolean | _(Optional)_ Whether to clone the given value on each use. | -| [name?](./types.withdefaultoptions.name.md) | | string | _(Optional)_ The new name to use in error messages. | diff --git a/markdown/types.withdefaultoptions.name.md b/markdown/types.withdefaultoptions.name.md deleted file mode 100644 index b665feb..0000000 --- a/markdown/types.withdefaultoptions.name.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@skunkteam/types](./types.md) > [WithDefaultOptions](./types.withdefaultoptions.md) > [name](./types.withdefaultoptions.name.md) - -## WithDefaultOptions.name property - -The new name to use in error messages. - -**Signature:** - -```typescript -name?: string; -``` diff --git a/src/interfaces.ts b/src/interfaces.ts index c43c95d..76e66cb 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -221,10 +221,14 @@ export type DeepUnbranded = T extends readonly [any, ...any[]] | readonly [] : T extends ReadonlyArray ? ReadonlyArray> : T extends Record - ? UnbrandValues> + ? UnbrandRecordLike> : Unbranded; export type UnbrandValues = { [P in keyof T]: DeepUnbranded }; +/** Check if the key type of the record-like structure (could also be an object) is branded. In that case, explicitely unbrand the keytype */ +export type UnbrandRecordLike = keyof T extends WithBrands | WithBrands + ? Record, DeepUnbranded> + : UnbrandValues; /** * The properties of an object type. diff --git a/src/types/record.test.ts b/src/types/record.test.ts index 8cdbdae..171b326 100644 --- a/src/types/record.test.ts +++ b/src/types/record.test.ts @@ -1,10 +1,12 @@ +import { expectTypeOf } from 'expect-type'; +import { WithBrands } from '..'; import { autoCast, autoCastAll } from '../autocast'; -import type { MessageDetails, The } from '../interfaces'; +import { DeepUnbranded, type MessageDetails, type The } from '../interfaces'; import { createExample, defaultUsualSuspects, stripped, testTypeImpl } from '../testutils'; import { printKey, printValue } from '../utils'; import { object } from './interface'; import { keyof } from './keyof'; -import { literal } from './literal'; +import { literal, undefinedType } from './literal'; import { int, number } from './number'; import { record } from './record'; import { string } from './string'; @@ -244,3 +246,173 @@ testTypeImpl({ ], ], }); + +// Branded keys and values have a particular interaction with the Record type. +describe('Branded entries', () => { + type BrandedString = The; + const BrandedString = string.withBrand('A'); + test('Branded strings', () => { + type BrandedKVRecord = The; + const BrandedKVRecord = record('BrandedKVRecord', BrandedString, BrandedString); + + // Strict typing on the Key and Value type of the record: + expectTypeOf().toEqualTypeOf>(); + expectTypeOf>().toEqualTypeOf>(); + + const brandedVal = BrandedString('branded'); + const regularVal = String('abc'); + + // Regular strings used as keys and values don't match the stricter type: + expectTypeOf({ [regularVal]: regularVal }).not.toEqualTypeOf(); + expectTypeOf({ [regularVal]: regularVal }).toEqualTypeOf>(); + + // Also normal: + expectTypeOf({ [regularVal]: brandedVal }).toEqualTypeOf>(); + + // Using `brandedVal` at an index position like this actually calls `toString()` on it, widening the type to `string`. + // See: https://basarat.gitbook.io/typescript/type-system/index-signatures + // So this is expected behaviour: + expectTypeOf({ [brandedVal]: regularVal }).toEqualTypeOf>(); + expectTypeOf({ [brandedVal]: brandedVal }).toEqualTypeOf>(); + + // Works with explicit type notations though: + const explicitlyTypedValue: BrandedKVRecord = { [brandedVal]: brandedVal }; + expectTypeOf(explicitlyTypedValue).toEqualTypeOf>(); + + // Literal allows for unbranded entry: + expectTypeOf(BrandedKVRecord.literal({ a: 'b' })).toEqualTypeOf(); + }); + + type BrandedNumber = The; + const BrandedNumber = number.withBrand('B'); + test('Branded numbers', () => { + type BrandedKVRecord = The; + const BrandedKVRecord = record('BrandedKVRecord', BrandedNumber, BrandedNumber); + + // Strict typing on the Key and Value type of the record: + expectTypeOf().toEqualTypeOf>(); + expectTypeOf>().toEqualTypeOf>(); + + const brandedVal = BrandedNumber(1); + const regularVal = Number(1); + + // Regular numbers used as keys and values don't match the stricter type: + expectTypeOf({ [regularVal]: regularVal }).not.toEqualTypeOf(); + expectTypeOf({ [regularVal]: regularVal }).toEqualTypeOf>(); + + // Also normal: + expectTypeOf({ [regularVal]: brandedVal }).toEqualTypeOf>(); + + // Using `brandedVal` at an index position like this also widens the type to `number`: + expectTypeOf({ [brandedVal]: regularVal }).toEqualTypeOf>(); + expectTypeOf({ [brandedVal]: brandedVal }).toEqualTypeOf>(); + + // Works with explicit type notations though: + const explicitlyTypedValue: BrandedKVRecord = { [brandedVal]: brandedVal }; + expectTypeOf(explicitlyTypedValue).toEqualTypeOf>(); + + // Literal allows for unbranded entry: + expectTypeOf(BrandedKVRecord.literal({ 1: 2 })).toEqualTypeOf(); + }); + + describe('Unions over branded values', () => { + test('Homogeneous branded types', () => { + type OtherBrandedString = The; + const OtherBrandedString = string.withBrand('C'); + + type BrandedStringUnion = The; + const BrandedStringUnion = union('BrandedStringUnion', [BrandedString, OtherBrandedString]); + expectTypeOf().toEqualTypeOf(); + + type HomogeneousStringsRecord = The; + const HomogeneousStringsRecord = record('HomogeneousStringsRecord', BrandedString, BrandedStringUnion); + + // This works as expected: + expectTypeOf().toEqualTypeOf>(); + expectTypeOf>().toEqualTypeOf>(); + expectTypeOf(HomogeneousStringsRecord.literal({ a: 'b' })).toEqualTypeOf(); + }); + + test('Inhomogeneous branded types', () => { + type MixedTypeBranding = The; + const MixedTypeBranding = union('MixedTypeBranding', [BrandedString, BrandedNumber]); + + // At this point everything is fine. We have a union of two differently branded types: + expectTypeOf().toEqualTypeOf(); + + type MixedTypeRecord = The; + const MixedTypeRecord = record('MixedTypeRecord', BrandedString, MixedTypeBranding); + + // Here we have some unexpected behaviour: + expectTypeOf().not.toEqualTypeOf>(); + // The branding information gets lost in a weird way: + expectTypeOf().toEqualTypeOf< + Record< + BrandedString, // Key stays branded... + WithBrands | WithBrands // ...but the values lose the specific brand names. + > + >(); + + // Unbranding works: + expectTypeOf>().toEqualTypeOf>(); + expectTypeOf(MixedTypeRecord.literal({ a: 'b', c: 4 })).toEqualTypeOf< + Record | WithBrands> + >(); + }); + + test('Mixing branded and undefinedType', () => { + type MixedBranding = The; + const MixedBranding = union('MixedBranding', [BrandedNumber, undefinedType]); + + // At this point everything is fine. We have a union over a branded number and unbranded `undefined`: + expectTypeOf().toEqualTypeOf(); + + type MixedBrandedRecord = The; + const MixedBrandedRecord = record('MixedBrandedRecord', BrandedString, MixedBranding); + + // This also doesn't work as expected: + expectTypeOf().not.toEqualTypeOf>(); + // Branding is now completely lost on the value type: + expectTypeOf().toEqualTypeOf>(); + + // DeepUnbranded works: + expectTypeOf>().toEqualTypeOf>(); + expectTypeOf(MixedBrandedRecord.literal({ a: 2, c: undefined })).toEqualTypeOf>(); + }); + + test('Mixing branded and unbranded types', () => { + type MixedBranding = The; + const MixedBranding = union('MixedBranding', [BrandedNumber, string]); + + // Union over a branded number and unbranded `string`: + expectTypeOf().toEqualTypeOf(); + + type MixedBrandedRecord = The; + const MixedBrandedRecord = record('MixedBrandedRecord', BrandedString, MixedBranding); + + // This works (though whether it's expected at this point remains questionable...): + expectTypeOf().toEqualTypeOf>(); + + // DeepUnbranded works: + expectTypeOf>().toEqualTypeOf>(); + expectTypeOf(MixedBrandedRecord.literal({ a: 2, c: 'weird' })).toEqualTypeOf>(); + }); + }); + + test('Branded object value', () => { + type BrandedObject = The; + const BrandedObject = object({ a: string }).withBrand('BrandedObject'); + + type BrandedVRecord = The; + const BrandedVRecord = record('BrandedVRecord', string, BrandedObject.or(literal('whatever'))); + + expectTypeOf().toEqualTypeOf>(); + + const unbranded = { a: 'abc' }; + const branded = BrandedObject.literal(unbranded); + const someString = String('abc'); + + expectTypeOf({ [someString]: unbranded }).not.toMatchTypeOf(); + expectTypeOf({ [someString]: branded }).toMatchTypeOf(); + }); +}); diff --git a/src/types/record.ts b/src/types/record.ts index d206332..880618a 100644 --- a/src/types/record.ts +++ b/src/types/record.ts @@ -100,13 +100,34 @@ define( }, ); +/** + * Small helper type that nudges the TS compiler to not widen branded string and number types to their base type. + */ +export type Unwidened = T extends T ? T : never; + /** * Note: record has strict validation by default, while type does not have strict validation, both are strict in construction though. TODO: document + * + * Warning: mixed unions of branded types as `valueType` behave unpredictably - the brand names get lost: + * ```ts + * type MixedValueType = The; + * const MixedValueType = record('MixedValueType', string, union([number.withBrand('A'), string.withBrand('B')]); + * // MixedValueType = Record | WithBrands> + * // instead of: Record | WithBrands> + * ``` + * + * Warning: unions of branded types with `undefinedType` as `valueType` behave unpredictably - the branding dissapears completely: + * ```ts + * type WithUndefined = The; + * const WithUndefined = record('WithUndefined', string, union([number.withBrand('A'), undefinedType]); + * // WithUndefined = Record + * // instead of: Record | undefined> + * ``` */ export function record( ...args: - | [name: string, keyType: BaseTypeImpl, valueType: BaseTypeImpl, strict?: boolean] - | [keyType: BaseTypeImpl, valueType: BaseTypeImpl, strict?: boolean] + | [name: string, keyType: BaseTypeImpl>, valueType: BaseTypeImpl>, strict?: boolean] + | [keyType: BaseTypeImpl>, valueType: BaseTypeImpl>, strict?: boolean] ): TypeImpl, KeyType, BaseTypeImpl, ValueType>> { const [name, keyType, valueType, strict] = decodeOptionalName(args); return createType(new RecordType(acceptNumberLikeKey(keyType), valueType, name, strict));