papr
Version:
MongoDB TypeScript-aware Models
466 lines (465 loc) • 16.7 kB
TypeScript
import { Binary, ObjectId, Decimal128 } from 'mongodb';
import type { TupleItems } from './TupleItems.ts';
import type { ObjectType } from './utils.ts';
interface RequiredOptions {
required: boolean;
}
type GenericOptions = Partial<RequiredOptions>;
interface ArrayOptions extends GenericOptions {
maxItems?: number;
minItems?: number;
uniqueItems?: boolean;
}
interface NumberOptions extends GenericOptions {
enum?: number[];
exclusiveMaximum?: boolean;
exclusiveMinimum?: boolean;
maximum?: number;
minimum?: number;
multipleOf?: number;
}
interface ObjectOptions extends GenericOptions {
additionalProperties?: boolean;
dependencies?: Record<string, unknown>;
maxProperties?: number;
minProperties?: number;
patternProperties?: Record<string, unknown>;
}
interface StringOptions extends GenericOptions {
enum?: string[];
maxLength?: number;
minLength?: number;
pattern?: string;
}
type GetType<Type, Options> = Options extends RequiredOptions ? Options['required'] extends true ? Type : Type | undefined : Type | undefined;
/**
* @module intro
* @description
*
* Types are the building blocks of `papr` [schemas](schema.md), which provide TypeScript type definitions,
* as well as the ability to generate [JSON schema](https://docs.mongodb.com/manual/core/schema-validation/#json-schema)
* for validators in MongoDB collections.
*
* Some types have additional options, based on the available options from JSON schema for that data type.
*
* The following data types are available to define the schemas of your `papr` models:
*/
/**
* The following type creator functions return valid JSON schema definitions at runtime,
* however for TypeScript they return actual TypeScript types.
*
* To workaround this difference between runtime and type-checking, we use the pattern `return X as unknown as Y`;
*
* All type creator functions below return a `$required` attribute, which is used only internally
* to compute the `required` keys array in the containing parent object.
*
* This `$required` value is removed in the `createSchemaType` function.
*/
export declare function any<Options extends GenericOptions>(options?: Options): any;
declare function array<Item, Options extends ArrayOptions>(items: Item, options?: Options): GetType<NonNullable<Item>[], Options>;
declare function constant<Value, Options extends GenericOptions>(value: Value, options?: Options): GetType<Value, Options>;
declare function enumType<Enum, Options extends GenericOptions>(values: readonly Enum[], options?: Options): GetType<Enum, Options>;
declare function number<Options extends NumberOptions>(options?: Options): GetType<number, Options>;
export declare function object<Properties extends Record<string, any>, Options extends ObjectOptions>(properties: Properties, options?: Options): GetType<ObjectType<Properties>, Options>;
export declare function objectGeneric<Property, Options extends ObjectOptions>(property: Property, pattern?: string, options?: Options): GetType<Record<string, Property>, Options>;
export declare function oneOf<Types extends any[], Options extends GenericOptions>(types: Types, options?: Options): GetType<Exclude<Types[number], undefined>, Options>;
declare function string<Options extends StringOptions>(options?: Options): GetType<string, Options>;
declare function tuple<Items extends readonly unknown[], Options extends GenericOptions>(items: Items, options?: Options): GetType<TupleItems<Items>, Options>;
declare function unknown<Options extends GenericOptions>(options?: Options): unknown;
declare const _default: {
/**
* Creates an array consisting of items of a single type.
*
* @param item {TItem}
* @param [options] {ArrayOptions}
* @param [options.maxItems] {number}
* @param [options.minItems] {number}
* @param [options.required] {boolean}
* @param [options.uniqueItems] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* requiredList: types.array(types.number(), { required: true }),
* optionalList: types.array(types.number()),
* // All inner types are `required` by default, so optionalList and anotherOptionalList
* // are equivalent types
* anotherOptionalList: types.array(types.number({ required: true }))
* listWithAllOptions: types.array(types.number(), {
* maxItems: 10,
* minItems: 1,
* required: true,
* uniqueItems: true,
* }),
* });
*
*/
array: typeof array;
/**
* Creates a binary type. Useful for storing `Buffer` or any other binary data.
*
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* requiredBinary: types.binary({ required: true }),
* optionalBinary: types.binary(),
* });
*/
binary: <Options extends GenericOptions>(options?: Options | undefined) => GetType<Binary, Options>;
/**
* Creates a boolean type.
*
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* requiredBoolean: types.boolean({ required: true }),
* optionalBoolean: types.boolean(),
* });
*/
boolean: <Options extends GenericOptions>(options?: Options | undefined) => GetType<boolean, Options>;
/**
* Creates a constant value. Useful for creating discriminated unions with the `oneOf` type.
*
* @param value {TValue}
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* shape: types.oneOf([
* types.object({
* type: types.constant('circle' as const, { required: true }),
* radius: types.number({ required: true }),
* }),
* types.object({
* type: types.constant('rectangle' as const, { required: true }),
* width: types.number({ required: true }),
* length: types.number({ required: true }),
* }),
* ]),
* });
*/
constant: typeof constant;
/**
* Creates a date type.
*
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* requiredDate: types.date({ required: true }),
* optionalDate: types.date(),
* });
*/
date: <Options extends GenericOptions>(options?: Options | undefined) => GetType<Date, Options>;
/**
* Creates a IEEE 754 decimal-based 128 bit floating-point number type.
* Useful for storing monetary values, scientific computations or any other number that requires high precision.
*
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* requiredDecimal: types.decimal({ required: true }),
* optionalDecimal: types.decimal(),
* });
*/
decimal: <Options extends GenericOptions>(options?: Options | undefined) => GetType<Decimal128, Options>;
/**
* With `enum` you can create an enum type based on either:
*
* - a TypeScript `enum` structure
* - a readonly/const array (`as const`)
*
* Enum types may contain `null` as well.
*
* Const enums require a full type cast when used in the schema `defaults`.
*
* @param values {Array<TValue>}
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* enum SampleEnum {
* foo = 'foo',
* bar = 'bar'
* }
*
* const SampleConstArray = ['foo', 'bar'] as const;
*
* schema({
* // type: SampleEnum
* requiredEnum: types.enum(Object.values(SampleEnum), { required: true }),
* // type: SampleEnum | undefined
* optionalEnum: types.enum(Object.values(SampleEnum)),
* // type: SampleEnum | null | undefined
* optionalEnumWithNull: types.enum([...Object.values(SampleEnum), null]),
* // type: 'foo' | 'bar'
* requiredEnumAsConstArray: types.enum(SampleConstArray, { required: true }),
* // type: 'foo' | 'bar' | undefined
* optionalEnumAsConstArray: types.enum(SampleConstArray),
* });
*/
enum: typeof enumType;
/**
* Creates a `null` type. Use discouraged. Typically used in conjunction with
* another type when applying a schema to a collection that already contains
* `null` values in a field.
*
* Usage of `null` as a value in Mongo is discouraged, as it makes some
* common query patterns ambiguous: `find({ myField: null })` will match
* documents that have the `myField` value set to the literal `null` _or_
* that match `{ myField: { $exists: false } }`.
*
* To match documents with a literal `null` value you must query with
* `{ myField: { $type: 10 } }` (where `10` is the [BSON null type
* constant](https://www.mongodb.com/docs/manual/reference/bson-types/))
*
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* nullableNumber: types.oneOf([ types.number(), types.null() ]),
* });
*/
null: <Options extends GenericOptions>(options?: Options | undefined) => GetType<null, Options>;
/**
* Creates a number type.
*
* @param [options] {NumberOptions}
* @param [options.enum] {Array<number>}
* @param [options.exclusiveMaximum] {boolean}
* @param [options.exclusiveMinimum] {boolean}
* @param [options.maximum] {number}
* @param [options.minimum] {number}
* @param [options.mulitpleOf] {number}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* optionalNumber: types.number(),
* requiredNumber: types.number({ required: true }),
* numberWithAllOptions: types.number({
* enum: [1, 2, 3, 5, 8, 13],
* exclusiveMaximum: true,
* exclusiveMinimum: true,
* maximum: 0,
* minimum: 14,
* multipleOf: 1,
* required: true,
* }),
* });
*/
number: typeof number;
/**
* Creates an object type specifying all the known properties upfront.
*
* @param properties {TProperties}
* @param [options] {ObjectOptions}
* @param [options.additionalProperties] {boolean}
* @param [options.dependencies] {Record<string, Array<string>>}
* @param [options.maxProperties] {number}
* @param [options.minProperties] {number}
* @param [options.patternProperties] {Record<string, unknown>}
* @param [options.required] {boolean}
*
* The advanced JSON schema options for this type (e.g. `patternProperties`) are also available, but these will not get type checked.
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* optionalObject: types.object({
* foo: types.number(),
* bar: types.string(),
* }),
* requiredObject: types.object(
* {
* foo: types.number(),
* bar: types.string(),
* },
* { required: true }
* ),
* objectWithAllOptions: types.object(
* {
* foo: types.number(),
* bar: types.string(),
* },
* {
* additionalProperties: true,
* dependencies: {
* foo: ['bar'],
* },
* maxProperties: 10,
* minProperties: 2,
* patternProperties: {
* '^f.+': { type: 'string' },
* },
* required: true,
* }
* ),
* });
*/
object: typeof object;
/**
* Creates an object type without any upfront properties defined, instead you define only a pattern for the properties names. All properties will expect the same type as value (`TValue`).
*
* Note: It's recommended to avoid using such a type. It might throw a TypeScript error (TS2589) in the projection logic due to the looseness of the type definition.
*
* @param value {TValue}
* @param [pattern=.+] {string}
* @param [options] {ObjectOptions}
* @param [options.additionalProperties] {boolean}
* @param [options.dependencies] {Record<string, Array<string>>}
* @param [options.maxProperties] {number}
* @param [options.minProperties] {number}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* // This accepts any property name with the value as a number
* optionalObjectGeneric: types.objectGeneric(types.number()),
* // This accepts only objects with properties starting with `foo`
* requiredObjectGeneric: types.objectGeneric(
* types.number(),
* '^foo.+',
* { required: true }
* ),
* });
*/
objectGeneric: typeof objectGeneric;
/**
* Creates an `ObjectId` type.
*
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* optionalObjectId: types.objectId(),
* requiredObjectId: types.objectId({ required: true }),
* });
*/
objectId: <Options extends GenericOptions>(options?: Options | undefined) => GetType<ObjectId, Options>;
/**
* Creates a union type of multiple other types.
*
* This is useful when combined with `objectGeneric`.
*
* @param types {Type[]}
* @param [options] {StringOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* optionalStringOrNumber: types.oneOf([types.string(), types.number()]),
* requiredStringOrNumber: types.oneOf([types.string(), types.number()], { required: true }),
* });
*/
oneOf: typeof oneOf;
/**
* Creates a string type.
*
* @param [options] {StringOptions}
* @param [options.enum] {Array<string>}
* @param [options.maxLength] {number}
* @param [options.minLength] {number}
* @param [options.pattern] {string}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* optionalString: types.string(),
* requiredString: types.string({ required: true }),
* stringWithAllOptions: types.number({
* enum: ['foo', 'bar'],
* maxLength: 3,
* minLength: 1,
* pattern: '^\\w+$',
* required: true,
* }),
* });
*/
string: typeof string;
/**
* Creates a tuple type for the items in the supplied items array.
*
* Items passed to tuple must be readonly to preserve their order and any optional properties preceding a required property are implicitly required as well.
*
* @param types {Type[]}
* @param [options] {GenericOptions}
* @param [options.required] {boolean}
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* requiredTuple: types.tuple([
* types.number(),
* types.string()
* ] as const, { required: true }),
* optionalTuple: types.tuple([
* types.number(),
* types.string()
* ] as const),
* });
*/
tuple: typeof tuple;
/**
* We recommend avoiding this type. It only exists as an escape hatch for unknown data.
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* unknownData: types.any(),
* });
*/
any: typeof any;
/**
* This allows any value to be assigned, but is typed as unknown to force assertions
* before relying on the data. Like with `any`, we recommend avoiding this type.
* It only exists as an escape hatch for unknown data.
*
* @example
* import { schema, types } from 'papr';
*
* schema({
* unknownData: types.unknown(),
* });
*/
unknown: typeof unknown;
};
export default _default;