Skip to content

Instantly share code, notes, and snippets.

@chriskrycho
Created August 29, 2018 16:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chriskrycho/2f0cf3fd839d084bb4751137d2ded197 to your computer and use it in GitHub Desktop.
Save chriskrycho/2f0cf3fd839d084bb4751137d2ded197 to your computer and use it in GitHub Desktop.
Our current mirage types at work
import Mirage, { faker } from 'ember-cli-mirage';
export default class AwesomeThingFactory extends Mirage.Factory.extend({
id(i: number) {
return i;
},
name: faker.hacker.noun,
city: 'Buffalo',
state: 'NY',
}) {}
export type AwesomeThingInstance = MirageInstance<AwesomeThingFactory>;
export interface Collection<T> {
/**
* 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. Note each id can be an int or a
* string, but integer ids as strings (e.g. the string “1”) will be treated as
* integers.
*/
find(id: number | string): T;
find(ids: (number | string)[]): T[];
filter(predicate: (t: T) => boolean): Array<T>;
map<U>(mapFn: (t: T) => U): Array<U>;
/**
* 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?: Partial<T>, attributesForCreate?: Partial<T>): T;
/**
* Inserts `data` into the `Collection`. `Data` can be a single object or an
* array of objects. Returns the inserted record.
*/
insert(data: Partial<T> | Partial<T>[]): T;
/**
* Removes one or more records in collection.
*
* If `target` is undefined, removes all records. If `target` is a number or
* string, removes a single record using `target` as id. If `target` is a
* POJO, queries `Collection` for records that match the key-value pairs in
* `target`, and removes them from the collection.
*/
remove(target?: number | string | Partial<T>): void;
/**
* Updates one or more records in `Collection`.
*
* If `attrs` is the only arg present, updates all records in the collection
* according to the key-value pairs in `attrs`.
*
* If `target` is present, restricts updates to those that match `target`. If
* `target` is a number or string, finds a single record whose id is `target`
* to update. If `target` is a POJO, queries collection for records that match
* the key-value pairs in `target`, and updates their `attrs`.
*
* Returns the updated record or records.
*/
update(...attrs: Partial<T>[]): T[];
update(target: number | string, ...attrs: Partial<T>[]): T;
/**
* Returns an array of models from `Collection` that match the key-value pairs
* in the query object. Note that a string comparison is used. `query` is a
* POJO.
*/
where(query: Partial<T>): T[];
// Look up by index into the backing array.
[key: number]: T;
}
export default Collection;
declare global {
/**
Create a mapped type whose values are all the non-dynamic counterpart of the
original type. That is:
- if the original type's property was a function, the corresponding property
on the mapped type is the return type of the function.
- if the original type's property was *not* a funciton, the corresponding
property on the mapped type is the same as the original.
In the context of Mirage, this lets us take dynamic functions (whether we
generate them ourselves or whether we use Faker) and figure out what the
static instance type will be instead.
*/
type Undynamicized<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? ReturnType<T[K]> : T[K]
};
}
// Type definitions for ember-cli-mirage 0.1.14
// Project: Ember-CLI-Mirage
// Definitions by: Chris Krycho <github.com/chriskrycho>
// Definitions: https://github.com/ololabs/mobile-web-client
import EmberObject from '@ember/object';
import * as Registry from './registry';
/**
Create a type which has *only* the properties which do not come from either
Ember's `EmberObject` or Mirage's `Factory` types.
*/
type MirageInstanceProps<T> = Pick<T, Exclude<keyof T, keyof MirageFactory & keyof EmberObject>>;
declare global {
/**
The type of an instance generated from a Mirage `Factory`.
Where the factory has functions to generate dynamic types, the instance has
only static property values.
For example, if the factory is defined like this:
```ts
import Mirage, { faker } from 'ember-cli-mirage';
export default class Address extends Mirage.Factory.extend({
id(i: number) {
return i;
},
name: faker.hacker.noun,
street: faker.address.streetAddress,
city: faker.address.city,
delivery: {
supported: true,
cost: 1.23,
}
}) {}
```
then we can define an *instance* type like this:
```ts
type AddressInstance = MirageInstance<Address>;
```
The resulting type of `AddressInstance` will be:
```ts
type AddressInstance = {
id: number;
name: string;
street: string;
city: string;
delivery: {
supported: boolean;
cost: number;
}
};
```
*/
type MirageInstance<T extends MirageFactory> = Undynamicized<MirageInstanceProps<T>>
}
interface MirageResponse {
new (status: number, headers?: Partial<Headers>, payload?: any): MirageResponse;
new (status: number, payload?: any): MirageResponse;
}
export const Response: MirageResponse;
declare class MirageFactory extends EmberObject {}
export const Factory: typeof MirageFactory;
declare class MirageModel extends EmberObject {}
export const Model: typeof MirageModel;
/**
* Your Mirage server has a database which you can interact with in your route
* handlers. You retrieve or modify data from the database, then return what you
* want for that route.
*/
export interface Database extends Registry.DbCollections {
createCollection(name: string): void;
}
export interface Request<QP> {
requestBody: string;
params: QP;
queryParams: QP;
}
type SchemaObject = {
db: Database
};
export type RouteHandler<QP = any, T = any> = (schema: SchemaObject, request: Request<QP>) => T;
type ResponseCode = number | string;
type Shorthand =
| Registry.ModelNames
| Registry.CollectionNames
| Array<Registry.ModelNames | Registry.CollectionNames>;
export interface Server {
/** Set the base namespace used for all routes defined with get, post, put or del. */
namespace: string;
/**
* Generates a single model of type `type`, inserts it into the database
* (giving it an id), and returns the data that was added. You can override
* the attributes from the factory definition with a hash passed in as the
* second parameter.
*/
create<N extends Registry.ModelNames>(
type: N,
attrs?: DeepPartial<Registry.Models[N]>
): Registry.Models[N];
/**
* Creates amount models of type `type`, optionally overriding the attributes
* from the factory with attrs.
*
* Returns the array of records that were added to the database.
*/
createList<N extends Registry.ModelNames>(
type: N,
count: number,
attrs?: DeepPartial<Registry.Models[N]>
): Registry.Models[N][];
// Route handlers
get<QP = void, T = void>(
route: string,
handler: RouteHandler<QP, T>,
responseCode?: ResponseCode
): void;
get(route: string, shorthand: Shorthand, responseCode?: ResponseCode): void;
get(route: string, object: object, responseCode?: ResponseCode): void;
get(route: string): void;
post<QP = void, T = void>(
route: string,
handler: RouteHandler<QP, T>,
responseCode?: ResponseCode
): void;
post(route: string, shorthand: Shorthand, responseCode?: ResponseCode): void;
post(route: string, object: object, responseCode?: ResponseCode): void;
post(route: string): void;
put(route: string): void;
put(route: string, object: object, responseCode?: ResponseCode): void;
put(route: string, shorthand: Shorthand, responseCode?: ResponseCode): void;
put<QP = void, T = void>(
route: string,
handler: RouteHandler<QP, T>,
responseCode?: ResponseCode
): void;
del(route: string): void;
del(route: string, object: object, responseCode?: ResponseCode): void;
del(route: string, shorthand: Shorthand, responseCode?: ResponseCode): void;
del<QP = void, T = void>(
route: string,
handler: RouteHandler<QP, T>,
responseCode?: ResponseCode
): void;
/**
* By default, all the data files under `/fixtures` will be loaded during
* testing if you don’t have factories defined, and during development if you
* don’t have `/scenarios/default.js` defined. You can use `loadFixtures()` to
* also load fixture files in either of these environments, in addition to
* using factories to seed your database.
*
* `server.loadFixtures()` loads all the files, and
* `server.loadFixtures(file1, file2...)` loads selective fixture files.
*
* For example, in a test you may want to start out with all your fixture data
* loaded, or in development, you may want to load a few reference fixture
* files, and use factories to define the rest of your data.
*/
loadFixtures(): void;
loadFixtures(...files: string[]): void;
/**
* By default, if your Ember app makes a request that is not defined in your
* server config, Mirage will throw an error. You can use passthrough to
* whitelist requests, and allow them to pass through your Mirage server to
* the actual network layer.
*
* _**Tip:** Put all passthrough config at the bottom of your `config.js`
* file, to give your route handlers precedence._
*
* You can also pass a list of paths, or call `passthrough` multiple times.
*
* If you want only certain verbs to pass through, pass an array as the last
* argument with the specified verbs.
*
* If you want all requests on the current domain to pass through, simply
* invoke the method with no arguments. Note again that the current namespace
* (i.e. any `namespace` property defined above this call) will be applied.
*
* You can also allow other-origin hosts to passthrough. If you use a
* fully-qualified domain name, the `namespace` property will be ignored.
* Use two * wildcards to match all requests under a path.
*
* Be aware that currently, passthrough only works with jQuery >= 2.x. See [this issue] for details.
*
* [this issue]: https://github.com/pretenderjs/pretender/issues/85
*/
passthrough(): void;
passthrough(route: string, methods?: string[]): void;
passthrough(route: string, route1: string, methods?: string[]): void;
passthrough(route: string, route1: string, route2: string, methods?: string[]): void;
passthrough(
route: string,
route1: string,
route2: string,
route3: string,
methods?: string[]
): void;
passthrough(
route: string,
route1: string,
route2: string,
route3: string,
route4: string,
methods?: string[]
): void;
passthrough(
route: string,
route1: string,
route2: string,
route3: string,
route4: string,
route5: string,
methods?: string[]
): void;
passthrough(route: string, ...moreRoutes: string[]): void;
timing: number;
shutdown(): void;
/**
* Mirage uses [pretender.js] as its xhttp interceptor. In your Mirage config,
* `this.pretender` refers to the actual pretender instance, so any config
* options that work there will work here as well. By default, content
* returned is json stringified, so you can just return js objects.
*
* [pretender.js]: https://github.com/trek/pretender
*
* Refer to [pretender’s docs] if you want to change this or any other options
* on your pretender instance.
*
* [pretender's docs]: https://github.com/trek/pretender#mutating-the-body
*/
pretender: any; // FIX TYPES: no any!
/**
* If your entire Ember app uses an external (other-origin) API, you can
* globally configure the domain via `urlPrefix`.
*/
urlPrefix: string;
db: Database;
}
declare global {
export const server: Server;
}
declare namespace Mirage {
const Response: MirageResponse;
const Factory: typeof MirageFactory;
const Model: typeof MirageModel;
}
export namespace faker {
const address: {
city(): string;
countryCode(): string;
latitude(): string;
longitude(): string;
state(): string;
streetAddress(): string;
zipCode(): string;
};
const commerce: {
price(): number;
product(): string;
productName(): string;
productAdjective(): string;
productMaterial(): string;
};
const hacker: {
noun(): string;
};
const internet: {
email(): string;
password(): string;
};
const lorem: {
sentence(): string;
};
const name: {
firstName(): string;
lastName(): string;
};
const phone: {
phoneNumber(): string;
};
const random: {
number(options?:{min:number, max:number} | number): number;
};
const finance: {
amount(min:number, max:number, decimals:number): number;
}
}
declare global {
interface Window {
server: Server;
}
}
export default Mirage;
// NOTE: this also works with interface merging, so you can just put these in
// your files definition your Mirage factories
import Collection from './collection';
import { AwesomeThingInstance } from 'my-app/mirage/factories/awesome-thing';
export interface Models {
awesomeThing: AwesomeThingInstance;
}
export type ModelNames = keyof Models;
export interface DbCollections {
awesomeThings: Collection<AwesomeThingInstance>;
}
export interface ServerCollections {
'awesome-things': Collection<AwesomeThingInstance>;
}
export type CollectionNames = keyof ServerCollections;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment