UNPKG

miragejs

Version:

A client-side server to help you build, test and demo your JavaScript app

769 lines (639 loc) 24.1 kB
// Minimum TypeScript Version: 4.2 /* * Inspired by Dan Freeman * https://github.com/dfreeman/ * * Source: https://gist.github.com/dfreeman/33fc80164c0ad91d5e9480a94aa6454c#file-tests-model-ts */ declare module "miragejs" { import { FactoryDefinition, ModelDefinition, BelongsTo, HasMany, } from "miragejs/-types"; import IdentityManager from "miragejs/identity-manager"; export { IdentityManager }; export { Server, createServer } from "miragejs/server"; export { Registry, Instantiate, ModelInstance } from "miragejs/-types"; export { Serializer, ActiveModelSerializer, JSONAPISerializer, RestSerializer, } from "miragejs/serializer"; /** * A fake HTTP request */ export class Request { /** The request body, if defined */ readonly requestBody: string; /** The URL of the request */ readonly url: string; /** Any headers associated with the request, with downcased names */ readonly requestHeaders: Record<string, string>; /** Any parameter specified via dynamic route segments */ readonly params: Record<string, string>; /** Any query parameters associated with the request */ readonly queryParams: Record<string, string[] | string | null | undefined>; } /** * A fake HTTP response. May be returned from a Mirage route * handler for finer-grained control over the response behavior. */ export class Response { /** * @param code The HTTP status code for this response * @param headers Any custom headers to set in this response * @param body Data to send in the response body */ constructor( code: number, headers?: Record<string, string>, body?: string | {} ); toRackResponse(): [ number, Record<string, string> | undefined, string | {} | undefined ]; } /** * The base definition for Mirage models. * * Use `Model.extend({ ... })` to define a model's relationships * (via `belongsTo()` and `hasMany()`) and any static default * attribute values. */ export const Model: ModelDefinition; /** * The base definition for Mirage factories. * * Use `Factory.extend({ ... })` to define methods that * will generate default attribute values when `server.create` * or the corresponding `schema` method is called for this * type. */ export const Factory: FactoryDefinition; /** * A collection of zero or more Mirage model instances. */ export class Collection<T> { /** The number of models in the collection. */ length: number; /** The dasherized model name this Collection represents. */ modelName: string; /** The underlying plain JavaScript array of Models in this Collection. */ models: T[]; /** Adds a model to this collection. */ add(model: T): Collection<T>; /** Destroys the db record for all models in the collection. */ destroy(): Collection<T>; /** Returns a new Collection with its models filtered according to the provided callback function. */ filter(f: (value: T, index: number, models: T[]) => unknown): Collection<T>; /** Checks if the Collection includes the given model. */ includes(model: T): boolean; /** Modifies the Collection by merging the models from another collection. */ mergeCollection(collection: Collection<T>): Collection<T>; /** Reloads each model in the collection. */ reload(): Collection<T>; /** Removes a model from this collection. */ remove(model: T): Collection<T>; /** Saves all models in the collection. */ save(): Collection<T>; /** Returns a new Collection with a subset of its models selected from begin to end. */ slice(begin: number, end: number): Collection<T>; /** Returns a new Collection with its models sorted according to the provided compare function. */ sort(f: (a: T, b: T) => number): Collection<T>; /** Updates each model in the collection, and immediately persists all changes to the db. */ update<K extends keyof T>(key: K, val: T[K]): Collection<T>; } export interface RelationshipOptions { inverse?: string | null; polymorphic?: boolean; } /** Declares a one-to-one relationship to another Mirage model type. */ export function belongsTo<K extends string>( key?: K, options?: RelationshipOptions ): BelongsTo<K>; export function belongsTo<K extends string>( options?: RelationshipOptions ): BelongsTo<K>; /** Declares a one-to-many relationship to another Mirage model type. */ export function hasMany<K extends string>( key?: K, options?: RelationshipOptions ): HasMany<K>; export function hasMany<K extends string>( options?: RelationshipOptions ): HasMany<K>; } declare module "miragejs/-types" { import { Collection, Response } from "miragejs"; /* A 1:1 relationship between models */ export class BelongsTo<Name extends string> { private name: Name; } /* A 1:many relationship between models */ export class HasMany<Name extends string> { private name: Name; } // Captures the result of a `Model.extend()` call interface ModelDefinition<Data extends {} = {}> { extend<NewData>(data: NewData): ModelDefinition<Assign<Data, NewData>>; } // Captures the result of a `Factory.extend()` call interface FactoryDefinition<Data extends {} = {}> { extend<NewData>( data: WithFactoryMethods<NewData> ): FactoryDefinition<Assign<Data, FlattenFactoryMethods<NewData>>>; } type WithFactoryMethods<T> = { [K in keyof T]: T[K] | ((n: number) => T[K]); }; // Extract factory method return values from a factory definition type FlattenFactoryMethods<T> = { [K in keyof T]: T[K] extends (n: number) => infer V ? V : T[K]; }; /** * Given a registry and the name of one of the models defined in it, * returns the type of that model as instantiated by Mirage. */ export type Instantiate< Registry, ModelName extends keyof Registry > = ModelInstance< { // Splitting and rejoining on `ModelName` ensures that unions distribute // properly, so that `Instantiate<Reg, 'foo' | 'bar'>` expands out like // `Instantiate<Reg, 'foo'> | Instantiate<Reg, 'bar'>` rather than something // that only has the intersection of `foo` and `bar`'s keys. [Model in ModelName]: { [Key in keyof Registry[Model]]: InstantiateValue< Registry, Registry[Model][Key] >; }; }[ModelName] >; // Given a registry and value type, checks whether that type represents // if Mirage relationship. If so, returns the corresponding model or // collection type from the registry; otherwise returns the type unchanged. type InstantiateValue<Registry, T> = T extends BelongsTo<infer ModelName> ? InstantiateIfDefined<Registry, ModelName> | null : T extends HasMany<infer ModelName> ? Collection<InstantiateIfDefined<Registry, ModelName>> : T; // Returns the instantiated type of the given model if it exists in the // given registry, or `unknown` otherwise. type InstantiateIfDefined<Registry, ModelName> = ModelName extends keyof Registry ? Instantiate<Registry, ModelName> : unknown; // The type-level equivalent of `Object.assign` type Assign<T, U> = U & Omit<T, keyof U>; // Extracts model definition info for the given key, if a corresponding model is defined type ExtractModelData<Models, K> = K extends keyof Models ? Models[K] extends ModelDefinition<infer Data> ? Data : {} : {}; // Extracts factory definition info for the given key, if a corresponding factory is defined type ExtractFactoryData<Factories, K> = K extends keyof Factories ? Factories[K] extends FactoryDefinition<infer Data> ? FlattenFactoryMethods<Data> : {} : {}; /** * Models all available information about a given set of model and * factory definitions, determining the behavior of ORM methods on * a `Server` and its corresponding `Schema` instance. */ export type Registry< Models extends AnyModels, Factories extends AnyFactories > = { [K in keyof Models | keyof Factories]: ExtractModelData<Models, K> & ExtractFactoryData<Factories, K>; }; export type AnyModels = Record<string, ModelDefinition>; export type AnyFactories = Record<string, FactoryDefinition>; /** A marker type for easily constraining type parameters that must be shaped like a Registry */ export type AnyRegistry = Registry<AnyModels, AnyFactories>; type MaybePromise<T> = T | PromiseLike<T>; type ValidResponse = | Record<PropertyKey, any> | number | string | boolean | null; export type AnyResponse = MaybePromise< ModelInstance | Response | ValidResponse | ValidResponse[] >; type CollectionOrListValue<Value> = Value extends Collection< infer ElementType > ? ElementType[] | Collection<ElementType> : Value; /** Convert any Collection<ElementType> to ElementType[] | Collection<ElementType> */ type CollectionOrList<Data extends {} = {}> = { [K in keyof Data]: CollectionOrListValue<Data[K]>; }; /** Represents the type of an instantiated Mirage model. */ export type ModelInstance<Data extends {} = {}> = Data & { id?: string; attrs: Data; modelName: string; /** Persists any updates on this model back to the Mirage database. */ save(): void; /** Updates and immediately persists a single or multiple attr(s) on this model. */ update<K extends keyof Data>( key: K, value: CollectionOrListValue<Data[K]> ): void; update(changes: Partial<CollectionOrList<Data>>): void; /** Removes this model from the Mirage database. */ destroy(): void; /** Reloads this model's data from the Mirage database. */ reload(): void; }; } declare module "miragejs/server" { import { Request, Registry as MirageRegistry } from "miragejs"; import { AnyRegistry, AnyModels, AnyFactories, AnyResponse, Instantiate, } from "miragejs/-types"; import Db from "miragejs/db"; import IdentityManager from "miragejs/identity-manager"; import Schema from "miragejs/orm/schema"; import PretenderServer from "pretender"; /** * Possible HTTP verbs * @see https://github.com/pretenderjs/pretender/blob/master/index.d.ts#L13 **/ type HTTPVerb = | "get" | "put" | "post" | "patch" | "delete" | "options" | "head"; type PassthroughArg = ((request: Request) => any) | string; type PassthroughVerbs = HTTPVerb[]; /** A callback that will be invoked when a given Mirage route is hit. */ export type RouteHandler< Registry extends AnyRegistry, Response extends AnyResponse = AnyResponse > = (schema: Schema<Registry>, request: Request) => Response; export type Middleware< Registry extends AnyRegistry, Response extends AnyResponse = AnyResponse > = ( schema: Schema<Registry>, request: Request, next?: (request?: Request) => Response ) => Response; export interface HandlerOptions { /** A number of ms to artificially delay responses to this route. */ timing?: number; } type ShorthandOptions = "index" | "show" | "create" | "update" | "delete"; export interface ResourceOptions { /** Whitelist of shorthand options */ only?: ShorthandOptions[]; /** Exclude list of shorthand options */ except?: ShorthandOptions[]; /** Shorthand route path */ path?: string; } export interface ServerConfig< Models extends AnyModels, Factories extends AnyFactories > { urlPrefix?: string; fixtures?: any; namespace?: string; timing?: number; environment?: string; trackRequests?: boolean; useDefaultPassthroughs?: boolean; logging?: boolean; seeds?: (server: Server<MirageRegistry<Models, Factories>>) => void; scenarios?: (server: Server<MirageRegistry<Models, Factories>>) => void; routes?: (this: Server<MirageRegistry<Models, Factories>>) => void; baseConfig?: (this: Server<MirageRegistry<Models, Factories>>) => void; testConfig?: (this: Server<MirageRegistry<Models, Factories>>) => void; inflector?: object; identityManagers?: { [modelName in keyof Models]?: typeof IdentityManager; } & { application?: typeof IdentityManager }; models?: Models; serializers?: any; factories?: Factories; pretender?: PretenderServer; } /** * Starts up a Mirage server with the given configuration. */ export function createServer< Models extends AnyModels, Factories extends AnyFactories >( config: ServerConfig<Models, Factories> ): Server<MirageRegistry<Models, Factories>>; export class Server<Registry extends AnyRegistry = AnyRegistry> { constructor(options?: ServerConfig<AnyModels, AnyFactories>); /** The underlying in-memory database instance for this server. */ readonly db: Db; /** An interface to the Mirage ORM that allows for querying and creating records. */ readonly schema: Schema<Registry>; /** Creates a model of the given type. */ readonly create: Schema<Registry>["create"]; /** Whether or not Mirage should log all requests/response cycles. */ logging: boolean; /** A default number of ms to artificially delay responses for all routes. */ timing: number; /** A default prefix applied to all subsequent route definitions. */ namespace: string; /** * A set of middleware applied to subsequent route definitions. * * Usage: * ```js * // Example middleware which randomly returns a * // 500 response: * function random500() { * return (schema, req, next) => { * return (Math.random() > 0.7) * ? new Response(500, {}, 'no') * : next(); * } * } * * // Routes which use the middleware defined above: * routes() { * this.middleware = [ * random500(), * // ... * ] * * server.get('/users', (schema, req) => { * return new Response(204, {}, null); * }); * } * ``` */ middleware: Middleware<Registry, Response>[]; /** Sets a string to prefix all route handler URLs with. */ urlPrefix: string; /** Actual Pretender instance */ pretender: PretenderServer; /** Creates multiple models of the given type. */ createList< K extends keyof Registry, Init extends Instantiate<Registry, K>, Data extends Partial<Init> >(modelName: K, count: number, data?: Data): Array<Init & Data>; /** Handle a GET request to the given path. */ get<Response extends AnyResponse>( path: string, handler?: RouteHandler<Registry, Response>, options?: HandlerOptions ): void; /** Handle a POST request to the given path. */ post<Response extends AnyResponse>( path: string, handler?: RouteHandler<Registry, Response>, options?: HandlerOptions ): void; /** Handle a PUT request to the given path. */ put<Response extends AnyResponse>( path: string, handler?: RouteHandler<Registry, Response>, options?: HandlerOptions ): void; /** Handle a PATCH request to the given path. */ patch<Response extends AnyResponse>( path: string, handler?: RouteHandler<Registry, Response>, options?: HandlerOptions ): void; /** Handle an OPTIONS request to the given path. */ options<Response extends AnyResponse>( path: string, handler?: RouteHandler<Registry, Response>, options?: HandlerOptions ): void; /** Handle a DELETE request to the given path. */ del<Response extends AnyResponse>( path: string, handler?: RouteHandler<Registry, Response>, options?: HandlerOptions ): void; delete<Response extends AnyResponse>( path: string, handler?: RouteHandler<Registry, Response>, options?: HandlerOptions ): void; /** Handle a HEAD request to the given path. */ head<Response extends AnyResponse>( path: string, handler?: RouteHandler<Registry, Response>, options?: HandlerOptions ): void; /** Define multiple shorthands for a given resource */ resource<K extends keyof Registry>( modelName: K, options?: ResourceOptions ): void; /** Pass through one or more URLs to make real requests. */ passthrough(...urls: PassthroughArg[]): void; passthrough( ...args: [PassthroughArg, ...PassthroughArg[], PassthroughVerbs] ): void; /** Load all available fixture data matching the given name(s). */ loadFixtures(...names: string[]): void; seeds(server: Server): void; routes(): void; /** Shutdown the server and stop intercepting network requests. */ shutdown(): void; } } declare module "miragejs/db" { import DbCollection from "miragejs/db-collection"; import IdentityManager from "miragejs/identity-manager"; type DbLookup = { [key: string]: ReturnType<DbCollection["all"]> & Omit<DbCollection, "all">; }; class DbClass { constructor(initialData: [], identityManagers?: IdentityManager[]); createCollection(name: string, initialData?: any[]): void; dump(): void; emptyData(): void; loadData(data: any): void; } /** The in-memory database containing all currently active data keyed by collection name. */ export type Db = DbClass & DbLookup; export const Db: Db; export default Db; } declare module "miragejs/db-collection" { import IdentityManager from "miragejs/identity-manager"; export default class DbCollection { constructor( name: string, initialData: any[], identityManager?: IdentityManager ); /** Returns a copy of the data, to prevent inadvertent data manipulation. */ all(): any[]; /** Returns a single record from the `collection` if `ids` is a single id, or an array of records if `ids` is an array of ids. */ find(id: number | string | number[] | string[]): any; /** Returns the first model from `collection` that matches the key-value pairs in the `query` object. */ findBy(query: object): any; /** Finds the first record matching the provided _query_ in `collection`, or creates a new record using a merge of the `query` and optional `attributesForCreate`. */ firstOrCreate(query: object, attributesForCreate?: object): any; /** Inserts `data` into the collection. `data` can be a single object or an array of objects. */ insert(data: any): any; /** Removes one or more records in *collection*. */ remove(target?: object | number | string): void; /** Updates one or more records in the collection. */ update(target: object | number | string, attrs?: object): any; /** Returns an array of models from `collection` that match the key-value pairs in the `query` object. */ where(query: object): any; } } declare module "miragejs/identity-manager" { /** An IdentityManager is a class that's responsible for generating unique identifiers. You can define a custom identity manager for your entire application, as well as on a per-model basis. */ export default class IdentityManager { constructor(); get?(): number; /** Registers `uniqueIdentifier` as used. */ set(uniqueIdentifier: string | number): void; inc?(): number; /** Returns the next unique identifier. */ fetch(): string; /** Resets the identity manager, marking all unique identifiers as available. */ reset(): void; } } declare module "miragejs/orm/schema" { import { Collection } from "miragejs"; import { AnyRegistry, Instantiate } from "miragejs/-types"; import Db from "miragejs/db"; type ModelInitializer<Data> = { [K in keyof Data]: Data[K] extends Collection<infer M> ? Collection<M> | M[] : Data[K]; }; /** * An interface to the Mirage ORM that allows for querying and creating records. */ export default class Schema<Registry extends AnyRegistry> { /** Mirage's in-memory database */ readonly db: Db; /** * Creates a model of the given type. * @param modelName The type of model to instantiate * @param data Optional initial values for model attributes/relationships */ create< K extends keyof Registry, Init extends Instantiate<Registry, K>, Data extends Partial<ModelInitializer<Init>> >( modelName: K, data?: Data ): Init & { [K in keyof Init & keyof Data]: Exclude<Init[K], undefined | null>; }; /** Locates one or more existing models of the given type by ID(s). */ find<K extends keyof Registry>( type: K, id: string ): Instantiate<Registry, K> | null; find<K extends keyof Registry>( type: K, ids: string[] ): Collection<Instantiate<Registry, K>>; /** Locates an existing model of the given type by attribute value(s), if one exists. */ findBy<K extends keyof Registry>( type: K, attributes: Partial<Instantiate<Registry, K>> ): Instantiate<Registry, K> | null; findBy<K extends keyof Registry>( type: K, predicate: (instance: Instantiate<Registry, K>) => boolean ): Instantiate<Registry, K> | null; /** Locates an existing model of the given type by attribute value(s), creating one if it doesn't exist. */ findOrCreateBy<K extends keyof Registry>( type: K, attributes: Partial<Instantiate<Registry, K>> ): Instantiate<Registry, K>; /** Locates an existing model of the given type by attribute value(s), if one exists. */ where<K extends keyof Registry>( type: K, attributes: | Partial<Instantiate<Registry, K>> | ((item: Instantiate<Registry, K>) => unknown) ): Collection<Instantiate<Registry, K>>; /** Returns a collection of all known records of the given type */ all<K extends keyof Registry>( type: K ): Collection<Instantiate<Registry, K>>; /** Returns an empty collection of the given type */ none<K extends keyof Registry>( type: K ): Collection<Instantiate<Registry, K>>; /** Returns the first model instance found of the given type */ first<K extends keyof Registry>(type: K): Instantiate<Registry, K> | null; } } declare module "miragejs/serializer" { import Schema from "miragejs/orm/schema"; interface SerializerInterface { schema?: Schema<any>; attrs?: any; embed?: boolean | ((key: string) => boolean); root?: any; serializeIds?: any; include?: any; keyForAttribute?(attr: any): any; keyForCollection?(modelName: any): any; keyForEmbeddedRelationship?(attributeName: any): any; keyForForeignKey?(relationshipName: any): any; keyForModel?(modelName: any): any; keyForPolymorphicForeignKeyId?(relationshipName: string): string; keyForPolymorphicForeignKeyType?(relationshipName: string): string; keyForRelationship?(modelName: any): any; keyForRelationshipIds?(modelName: any): any; normalize?(json: any): any; serialize?(primaryResource: any, request: any): any; extend?(param?: SerializerInterface): SerializerInterface; } class Serializer implements SerializerInterface { static extend(param?: SerializerInterface | {}): SerializerInterface | {}; } interface JSONAPISerializerInterface extends SerializerInterface { alwaysIncludeLinkageData?: boolean; links?(model: any): any; shouldIncludeLinkageData?(relationshipKey: string, model: any): boolean; typeKeyForModel?(model: any): string; } class JSONAPISerializer extends Serializer implements JSONAPISerializerInterface { static extend( param?: JSONAPISerializerInterface | {} ): JSONAPISerializerInterface; } class ActiveModelSerializer extends Serializer {} class RestSerializer extends Serializer {} }