diff --git a/README.md b/README.md index e8e44d8..767fcb8 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ const data = { preferred_format: 'hardback', } }; -const restServer = new FakeRest.FetchServer('http://localhost:3000'); +const restServer = new FakeRest.FetchServer({ baseUrl: 'http://localhost:3000' }); restServer.init(data); fetchMock.mock('begin:http://localhost:3000', restServer.getHandler()); ``` @@ -366,7 +366,7 @@ Operators are specified as suffixes on each filtered field. For instance, applyi ```js // initialize a rest server with a custom base URL -var restServer = new FakeRest.Server('http://my.custom.domain'); // // only URLs starting with my.custom.domain will be intercepted +const restServer = new FakeRest.Server({ baseUrl: 'http://my.custom.domain' }); // only URLs starting with my.custom.domain will be intercepted restServer.toggleLogging(); // logging is off by default, enable it to see network calls in the console // Set all JSON data at once - only if identifier name is 'id' restServer.init(json); @@ -406,24 +406,45 @@ restServer.setDefaultQuery(function(resourceName) { restServer.setBatchUrl('/batch'); // you can create more than one fake server to listen to several domains -var restServer2 = new FakeRest.Server('http://my.other.domain'); +const restServer2 = new FakeRest.Server({ baseUrl: 'http://my.other.domain' }); // Set data collection by collection - allows to customize the identifier name -var authorsCollection = new FakeRest.Collection([], '_id'); +const authorsCollection = new FakeRest.Collection({ items: [], identifierName: '_id' }); authorsCollection.addOne({ first_name: 'Leo', last_name: 'Tolstoi' }); // { _id: 0, first_name: 'Leo', last_name: 'Tolstoi' } authorsCollection.addOne({ first_name: 'Jane', last_name: 'Austen' }); // { _id: 1, first_name: 'Jane', last_name: 'Austen' } -// collections have autoincremented identifier but accept identifiers already set +// collections have auto incremented identifiers by default but accept identifiers already set authorsCollection.addOne({ _id: 3, first_name: 'Marcel', last_name: 'Proust' }); // { _id: 3, first_name: 'Marcel', last_name: 'Proust' } restServer2.addCollection('authors', authorsCollection); // collections are mutable authorsCollection.updateOne(1, { last_name: 'Doe' }); // { _id: 1, first_name: 'Jane', last_name: 'Doe' } authorsCollection.removeOne(3); // { _id: 3, first_name: 'Marcel', last_name: 'Proust' } -var server = sinon.fakeServer.create(); +const server = sinon.fakeServer.create(); server.autoRespond = true; server.respondWith(restServer.getHandler()); server.respondWith(restServer2.getHandler()); ``` +## Configure Identifiers Generation + +By default, FakeRest uses an auto incremented sequence for the items identifiers. If you'd rather use another type of identifiers (e.g. UUIDs), you can provide your own `getNewId` function at the server level: + +```js +import FakeRest from 'fakerest'; +import uuid from 'uuid'; + +const restServer = new FakeRest.Server({ baseUrl: 'http://my.custom.domain', getNewId: () => uuid.v5() }); +``` + +This can also be specified at the collection level: + +```js +import FakeRest from 'fakerest'; +import uuid from 'uuid'; + +const restServer = new FakeRest.Server({ baseUrl: 'http://my.custom.domain' }); +const authorsCollection = new FakeRest.Collection({ items: [], identifierName: '_id', getNewId: () => uuid.v5() }); +``` + ## Development ```sh diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000..923dc5b --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,42 @@ +# Upgrading to 4.0.0 + +## Constructors Of `FetchServer` and `Server` Take An Object + +For `Server`: + +```diff +import { Server } from 'fakerest'; +import { data } from './data'; + +-const server = new Server('http://myapi.com'); ++const server = new Server({ baseUrl: 'http://myapi.com' }); +server.init(data); +``` + +For `FetchServer`: + +```diff +import { FetchServer } from 'fakerest'; +import { data } from './data'; + +-const server = new FetchServer('http://myapi.com'); ++const server = new FetchServer({ baseUrl: 'http://myapi.com' }); +server.init(data); +``` + +## Constructor Of `Collection` Takes An Object + +```diff +-const posts = new Collection([ +- { id: 1, title: 'baz' }, +- { id: 2, title: 'biz' }, +- { id: 3, title: 'boz' }, +-]); ++const posts = new Collection({ ++ items: [ ++ { id: 1, title: 'baz' }, ++ { id: 2, title: 'biz' }, ++ { id: 3, title: 'boz' }, ++ ], ++}); +``` \ No newline at end of file diff --git a/example/App.tsx b/example/App.tsx index 0b0ed42..961b4ea 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,12 +1,41 @@ import React from 'react'; -import { Admin, ListGuesser, Resource } from 'react-admin'; +import { + Admin, + Create, + EditGuesser, + ListGuesser, + Resource, + ShowGuesser, +} from 'react-admin'; import { dataProvider } from './dataProvider'; export const App = () => { return ( - - + + ); }; + +import { Edit, ReferenceInput, SimpleForm, TextInput } from 'react-admin'; + +export const BookCreate = () => ( + + + + + + +); diff --git a/example/fetchMock.ts b/example/fetchMock.ts index c6e7e7d..aaffc9e 100644 --- a/example/fetchMock.ts +++ b/example/fetchMock.ts @@ -3,7 +3,9 @@ import FakeRest from 'fakerest'; import { data } from './data'; export const initializeFetchMock = () => { - const restServer = new FakeRest.FetchServer('http://localhost:3000'); + const restServer = new FakeRest.FetchServer({ + baseUrl: 'http://localhost:3000', + }); if (window) { // @ts-ignore window.restServer = restServer; // give way to update data in the console diff --git a/src/BaseServer.ts b/src/BaseServer.ts index 72550d7..d743709 100644 --- a/src/BaseServer.ts +++ b/src/BaseServer.ts @@ -9,9 +9,17 @@ export class BaseServer { batchUrl: string | null = null; collections: Record> = {}; singles: Record> = {}; + getNewId?: () => number | string; - constructor(baseUrl = '') { + constructor({ + baseUrl = '', + getNewId, + }: { + baseUrl?: string; + getNewId?: () => number | string; + } = {}) { this.baseUrl = baseUrl; + this.getNewId = getNewId; } /** @@ -21,7 +29,14 @@ export class BaseServer { for (const name in data) { const value = data[name]; if (Array.isArray(value)) { - this.addCollection(name, new Collection(value, 'id')); + this.addCollection( + name, + new Collection({ + items: value, + identifierName: 'id', + getNewId: this.getNewId, + }), + ); } else { this.addSingle(name, new Single(value)); } @@ -103,7 +118,7 @@ export class BaseServer { return this.collections[name].getAll(params); } - getOne(name: string, identifier: number, params?: Query) { + getOne(name: string, identifier: string | number, params?: Query) { return this.collections[name].getOne(identifier, params); } @@ -111,17 +126,21 @@ export class BaseServer { if (!Object.prototype.hasOwnProperty.call(this.collections, name)) { this.addCollection( name, - new Collection([] as CollectionItem[], 'id'), + new Collection({ + items: [], + identifierName: 'id', + getNewId: this.getNewId, + }), ); } return this.collections[name].addOne(item); } - updateOne(name: string, identifier: number, item: CollectionItem) { + updateOne(name: string, identifier: string | number, item: CollectionItem) { return this.collections[name].updateOne(identifier, item); } - removeOne(name: string, identifier: number) { + removeOne(name: string, identifier: string | number) { return this.collections[name].removeOne(identifier); } @@ -205,7 +224,7 @@ export class BaseServer { // handle collections const matches = request.url?.match( - new RegExp(`^${this.baseUrl}\\/([^\\/?]+)(\\/(\\d+))?(\\?.*)?$`), + new RegExp(`^${this.baseUrl}\\/([^\\/?]+)(\\/(\\w))?(\\?.*)?$`), ); if (!matches) { return { status: 404, headers: {} }; diff --git a/src/Collection.spec.ts b/src/Collection.spec.ts index a7333a7..6acee29 100644 --- a/src/Collection.spec.ts +++ b/src/Collection.spec.ts @@ -5,10 +5,12 @@ import type { CollectionItem } from './types.js'; describe('Collection', () => { describe('constructor', () => { it('should set the initial set of data', () => { - const collection = new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]); + const collection = new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }); expect(collection.getAll()).toEqual([ { id: 1, name: 'foo' }, { id: 2, name: 'bar' }, @@ -27,21 +29,19 @@ describe('Collection', () => { }); it('should return the collection size', () => { - expect(new Collection([{}, {}]).getCount()).toEqual(2); + expect(new Collection({ items: [{}, {}] }).getCount()).toEqual(2); }); it('should return the correct collection size, even when items were removed', () => { - const collection = new Collection([{}, {}, {}]); + const collection = new Collection({ items: [{}, {}, {}] }); collection.removeOne(1); expect(collection.getCount()).toEqual(2); }); it('should accept a query object', () => { - const collection = new Collection([ - {}, - { name: 'a' }, - { name: 'b' }, - ]); + const collection = new Collection({ + items: [{}, { name: 'a' }, { name: 'b' }], + }); function filter(item: CollectionItem) { return item.name === 'a' || item.name === 'b'; } @@ -55,10 +55,12 @@ describe('Collection', () => { }); it('should return all collections', () => { - const collection = new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]); + const collection = new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }); expect(collection.getAll()).toEqual([ { id: 1, name: 'foo' }, { id: 2, name: 'bar' }, @@ -75,11 +77,9 @@ describe('Collection', () => { }); it('should sort by sort function', () => { - const collection = new Collection([ - { name: 'c' }, - { name: 'a' }, - { name: 'b' }, - ]); + const collection = new Collection({ + items: [{ name: 'c' }, { name: 'a' }, { name: 'b' }], + }); const expected = [ { name: 'a', id: 1 }, { name: 'b', id: 2 }, @@ -99,11 +99,9 @@ describe('Collection', () => { }); it('should sort by sort name', () => { - const collection = new Collection([ - { name: 'c' }, - { name: 'a' }, - { name: 'b' }, - ]); + const collection = new Collection({ + items: [{ name: 'c' }, { name: 'a' }, { name: 'b' }], + }); const expected = [ { name: 'a', id: 1 }, { name: 'b', id: 2 }, @@ -113,11 +111,9 @@ describe('Collection', () => { }); it('should sort by sort name and direction', () => { - const collection = new Collection([ - { name: 'c' }, - { name: 'a' }, - { name: 'b' }, - ]); + const collection = new Collection({ + items: [{ name: 'c' }, { name: 'a' }, { name: 'b' }], + }); let expected: CollectionItem[]; expected = [ { name: 'a', id: 1 }, @@ -138,11 +134,9 @@ describe('Collection', () => { }); it('should not affect further requests', () => { - const collection = new Collection([ - { name: 'c' }, - { name: 'a' }, - { name: 'b' }, - ]); + const collection = new Collection({ + items: [{ name: 'c' }, { name: 'a' }, { name: 'b' }], + }); collection.getAll({ sort: 'name' }); const expected = [ { name: 'c', id: 0 }, @@ -163,11 +157,9 @@ describe('Collection', () => { }); it('should filter by filter function', () => { - const collection = new Collection([ - { name: 'c' }, - { name: 'a' }, - { name: 'b' }, - ]); + const collection = new Collection({ + items: [{ name: 'c' }, { name: 'a' }, { name: 'b' }], + }); const expected = [ { name: 'c', id: 0 }, { name: 'b', id: 2 }, @@ -179,11 +171,9 @@ describe('Collection', () => { }); it('should filter by filter object', () => { - const collection = new Collection([ - { name: 'c' }, - { name: 'a' }, - { name: 'b' }, - ]); + const collection = new Collection({ + items: [{ name: 'c' }, { name: 'a' }, { name: 'b' }], + }); const expected = [{ name: 'b', id: 2 }]; expect(collection.getAll({ filter: { name: 'b' } })).toEqual( expected, @@ -191,11 +181,13 @@ describe('Collection', () => { }); it('should filter values with deep paths', () => { - const collection = new Collection([ - { name: 'c', deep: { value: 'c' } }, - { name: 'a', deep: { value: 'a' } }, - { name: 'b', deep: { value: 'b' } }, - ]); + const collection = new Collection({ + items: [ + { name: 'c', deep: { value: 'c' } }, + { name: 'a', deep: { value: 'a' } }, + { name: 'b', deep: { value: 'b' } }, + ], + }); const expected = [{ name: 'b', deep: { value: 'b' }, id: 2 }]; expect( collection.getAll({ filter: { 'deep.value': 'b' } }), @@ -203,11 +195,13 @@ describe('Collection', () => { }); it('should filter values with objects', () => { - const collection = new Collection([ - { name: 'c', deep: { value: 'c' } }, - { name: 'a', deep: { value: 'a' } }, - { name: 'b', deep: { value: 'b' } }, - ]); + const collection = new Collection({ + items: [ + { name: 'c', deep: { value: 'c' } }, + { name: 'a', deep: { value: 'a' } }, + { name: 'b', deep: { value: 'b' } }, + ], + }); const expected = [{ name: 'b', deep: { value: 'b' }, id: 2 }]; expect( collection.getAll({ filter: { deep: { value: 'b' } } }), @@ -215,11 +209,13 @@ describe('Collection', () => { }); it('should filter boolean values properly', () => { - const collection = new Collection([ - { name: 'a', is: true }, - { name: 'b', is: false }, - { name: 'c', is: true }, - ]); + const collection = new Collection({ + items: [ + { name: 'a', is: true }, + { name: 'b', is: false }, + { name: 'c', is: true }, + ], + }); const expectedFalse = [{ name: 'b', id: 1, is: false }]; const expectedTrue = [ { name: 'a', id: 0, is: true }, @@ -240,11 +236,13 @@ describe('Collection', () => { }); it('should filter array values properly', () => { - const collection = new Collection([ - { tags: ['a', 'b', 'c'] }, - { tags: ['b', 'c', 'd'] }, - { tags: ['c', 'd', 'e'] }, - ]); + const collection = new Collection({ + items: [ + { tags: ['a', 'b', 'c'] }, + { tags: ['b', 'c', 'd'] }, + { tags: ['c', 'd', 'e'] }, + ], + }); const expected = [ { id: 0, tags: ['a', 'b', 'c'] }, { id: 1, tags: ['b', 'c', 'd'] }, @@ -258,11 +256,13 @@ describe('Collection', () => { }); it('should filter array values properly within deep paths', () => { - const collection = new Collection([ - { deep: { tags: ['a', 'b', 'c'] } }, - { deep: { tags: ['b', 'c', 'd'] } }, - { deep: { tags: ['c', 'd', 'e'] } }, - ]); + const collection = new Collection({ + items: [ + { deep: { tags: ['a', 'b', 'c'] } }, + { deep: { tags: ['b', 'c', 'd'] } }, + { deep: { tags: ['c', 'd', 'e'] } }, + ], + }); const expected = [ { id: 0, deep: { tags: ['a', 'b', 'c'] } }, { id: 1, deep: { tags: ['b', 'c', 'd'] } }, @@ -276,11 +276,13 @@ describe('Collection', () => { }); it('should filter array values properly inside deep paths', () => { - const collection = new Collection([ - { tags: { deep: ['a', 'b', 'c'] } }, - { tags: { deep: ['b', 'c', 'd'] } }, - { tags: { deep: ['c', 'd', 'e'] } }, - ]); + const collection = new Collection({ + items: [ + { tags: { deep: ['a', 'b', 'c'] } }, + { tags: { deep: ['b', 'c', 'd'] } }, + { tags: { deep: ['c', 'd', 'e'] } }, + ], + }); const expected = [ { id: 0, tags: { deep: ['a', 'b', 'c'] } }, { id: 1, tags: { deep: ['b', 'c', 'd'] } }, @@ -294,11 +296,13 @@ describe('Collection', () => { }); it('should filter array values properly with deep paths', () => { - const collection = new Collection([ - { tags: [{ name: 'a' }, { name: 'b' }, { name: 'c' }] }, - { tags: [{ name: 'b' }, { name: 'c' }, { name: 'd' }] }, - { tags: [{ name: 'c' }, { name: 'd' }, { name: 'e' }] }, - ]); + const collection = new Collection({ + items: [ + { tags: [{ name: 'a' }, { name: 'b' }, { name: 'c' }] }, + { tags: [{ name: 'b' }, { name: 'c' }, { name: 'd' }] }, + { tags: [{ name: 'c' }, { name: 'd' }, { name: 'e' }] }, + ], + }); const expected = [ { id: 0, @@ -318,11 +322,13 @@ describe('Collection', () => { }); it('should filter array values properly when receiving several values within deep paths', () => { - const collection = new Collection([ - { deep: { tags: ['a', 'b', 'c'] } }, - { deep: { tags: ['b', 'c', 'd'] } }, - { deep: { tags: ['c', 'd', 'e'] } }, - ]); + const collection = new Collection({ + items: [ + { deep: { tags: ['a', 'b', 'c'] } }, + { deep: { tags: ['b', 'c', 'd'] } }, + { deep: { tags: ['c', 'd', 'e'] } }, + ], + }); const expected = [{ id: 1, deep: { tags: ['b', 'c', 'd'] } }]; expect( collection.getAll({ filter: { 'deep.tags': ['b', 'd'] } }), @@ -335,11 +341,13 @@ describe('Collection', () => { }); it('should filter array values properly when receiving several values with deep paths', () => { - const collection = new Collection([ - { tags: [{ name: 'a' }, { name: 'b' }, { name: 'c' }] }, - { tags: [{ name: 'c' }, { name: 'd' }, { name: 'e' }] }, - { tags: [{ name: 'e' }, { name: 'f' }, { name: 'g' }] }, - ]); + const collection = new Collection({ + items: [ + { tags: [{ name: 'a' }, { name: 'b' }, { name: 'c' }] }, + { tags: [{ name: 'c' }, { name: 'd' }, { name: 'e' }] }, + { tags: [{ name: 'e' }, { name: 'f' }, { name: 'g' }] }, + ], + }); const expected = [ { id: 0, @@ -359,11 +367,13 @@ describe('Collection', () => { }); it('should filter array values properly when receiving several values', () => { - const collection = new Collection([ - { tags: ['a', 'b', 'c'] }, - { tags: ['b', 'c', 'd'] }, - { tags: ['c', 'd', 'e'] }, - ]); + const collection = new Collection({ + items: [ + { tags: ['a', 'b', 'c'] }, + { tags: ['b', 'c', 'd'] }, + { tags: ['c', 'd', 'e'] }, + ], + }); const expected = [{ id: 1, tags: ['b', 'c', 'd'] }]; expect( collection.getAll({ filter: { tags: ['b', 'd'] } }), @@ -374,15 +384,17 @@ describe('Collection', () => { }); it('should filter by the special q full-text filter', () => { - const collection = new Collection([ - { a: 'Hello', b: 'world' }, - { a: 'helloworld', b: 'bunny' }, - { a: 'foo', b: 'bar' }, - { a: { b: 'bar' } }, - { a: '', b: '' }, - { a: null, b: null }, - {}, - ]); + const collection = new Collection({ + items: [ + { a: 'Hello', b: 'world' }, + { a: 'helloworld', b: 'bunny' }, + { a: 'foo', b: 'bar' }, + { a: { b: 'bar' } }, + { a: '', b: '' }, + { a: null, b: null }, + {}, + ], + }); expect(collection.getAll({ filter: { q: 'hello' } })).toEqual([ { id: 0, a: 'Hello', b: 'world' }, { id: 1, a: 'helloworld', b: 'bunny' }, @@ -394,11 +406,9 @@ describe('Collection', () => { }); it('should filter by range using _gte, _gt, _lte, and _lt', () => { - const collection = new Collection([ - { v: 1 }, - { v: 2 }, - { v: 3 }, - ]); + const collection = new Collection({ + items: [{ v: 1 }, { v: 2 }, { v: 3 }], + }); expect(collection.getAll({ filter: { v_gte: 2 } })).toEqual([ { v: 2, id: 1 }, { v: 3, id: 2 }, @@ -418,11 +428,9 @@ describe('Collection', () => { }); it('should filter by inequality using _neq', () => { - const collection = new Collection([ - { v: 1 }, - { v: 2 }, - { v: 3 }, - ]); + const collection = new Collection({ + items: [{ v: 1 }, { v: 2 }, { v: 3 }], + }); expect(collection.getAll({ filter: { v_neq: 2 } })).toEqual([ { v: 1, id: 0 }, { v: 3, id: 2 }, @@ -430,22 +438,18 @@ describe('Collection', () => { }); it('should filter by equality using _eq', () => { - const collection = new Collection([ - { v: 1 }, - { v: 2 }, - { v: 3 }, - ]); + const collection = new Collection({ + items: [{ v: 1 }, { v: 2 }, { v: 3 }], + }); expect(collection.getAll({ filter: { v_eq: 2 } })).toEqual([ { v: 2, id: 1 }, ]); }); it('should filter using _eq_any', () => { - const collection = new Collection([ - { v: 1 }, - { v: 2 }, - { v: 3 }, - ]); + const collection = new Collection({ + items: [{ v: 1 }, { v: 2 }, { v: 3 }], + }); expect( collection.getAll({ filter: { v_eq_any: [1, 3] } }), ).toEqual([ @@ -455,22 +459,18 @@ describe('Collection', () => { }); it('should filter using _neq_any', () => { - const collection = new Collection([ - { v: 1 }, - { v: 2 }, - { v: 3 }, - ]); + const collection = new Collection({ + items: [{ v: 1 }, { v: 2 }, { v: 3 }], + }); expect( collection.getAll({ filter: { v_neq_any: [1, 3] } }), ).toEqual([{ v: 2, id: 1 }]); }); it('should filter using _inc_any', () => { - const collection = new Collection([ - { v: [1, 2] }, - { v: [2, 4] }, - { v: [3, 1] }, - ]); + const collection = new Collection({ + items: [{ v: [1, 2] }, { v: [2, 4] }, { v: [3, 1] }], + }); expect( collection.getAll({ filter: { v_inc_any: [1, 3] } }), ).toEqual([ @@ -480,33 +480,27 @@ describe('Collection', () => { }); it('should filter using _ninc_any', () => { - const collection = new Collection([ - { v: [1, 2] }, - { v: [2, 4] }, - { v: [3, 1] }, - ]); + const collection = new Collection({ + items: [{ v: [1, 2] }, { v: [2, 4] }, { v: [3, 1] }], + }); expect( collection.getAll({ filter: { v_ninc_any: [1, 3] } }), ).toEqual([{ v: [2, 4], id: 1 }]); }); it('should filter using _inc', () => { - const collection = new Collection([ - { v: [1, 2] }, - { v: [2, 4] }, - { v: [3, 1] }, - ]); + const collection = new Collection({ + items: [{ v: [1, 2] }, { v: [2, 4] }, { v: [3, 1] }], + }); expect( collection.getAll({ filter: { v_inc: [1, 3] } }), ).toEqual([{ v: [3, 1], id: 2 }]); }); it('should filter by text search using _q', () => { - const collection = new Collection([ - { v: 'abCd' }, - { v: 'cDef' }, - { v: 'EFgh' }, - ]); + const collection = new Collection({ + items: [{ v: 'abCd' }, { v: 'cDef' }, { v: 'EFgh' }], + }); expect(collection.getAll({ filter: { v_q: 'cd' } })).toEqual([ { id: 0, v: 'abCd' }, { id: 1, v: 'cDef' }, @@ -518,13 +512,15 @@ describe('Collection', () => { }); it('should filter by array', () => { - const collection = new Collection([ - { a: 'H' }, - { a: 'e' }, - { a: 'l' }, - { a: 'l' }, - { a: 'o' }, - ]); + const collection = new Collection({ + items: [ + { a: 'H' }, + { a: 'e' }, + { a: 'l' }, + { a: 'l' }, + { a: 'o' }, + ], + }); expect(collection.getAll({ filter: { id: [] } })).toEqual([]); expect( collection.getAll({ filter: { id: [1, 2, 3] } }), @@ -543,22 +539,18 @@ describe('Collection', () => { }); it('should combine all filters with an AND logic', () => { - const collection = new Collection([ - { v: 1 }, - { v: 2 }, - { v: 3 }, - ]); + const collection = new Collection({ + items: [{ v: 1 }, { v: 2 }, { v: 3 }], + }); expect( collection.getAll({ filter: { v_gte: 2, v_lte: 2 } }), ).toEqual([{ v: 2, id: 1 }]); }); it('should not affect further requests', () => { - const collection = new Collection([ - { name: 'c' }, - { name: 'a' }, - { name: 'b' }, - ]); + const collection = new Collection({ + items: [{ name: 'c' }, { name: 'a' }, { name: 'b' }], + }); function filter(item: CollectionItem) { return item.name !== 'a'; } @@ -581,13 +573,15 @@ describe('Collection', () => { }).toThrow(new Error('Unsupported range type')); }); - const collection = new Collection([ - { id: 0, name: 'a' }, - { id: 1, name: 'b' }, - { id: 2, name: 'c' }, - { id: 3, name: 'd' }, - { id: 4, name: 'e' }, - ]); + const collection = new Collection({ + items: [ + { id: 0, name: 'a' }, + { id: 1, name: 'b' }, + { id: 2, name: 'c' }, + { id: 3, name: 'd' }, + { id: 4, name: 'e' }, + ], + }); it('should return a range in the collection', () => { let expected: CollectionItem[]; @@ -611,11 +605,13 @@ describe('Collection', () => { }); it('should not affect further requests', () => { - const collection = new Collection([ - { id: 0, name: 'a' }, - { id: 1, name: 'b' }, - { id: 2, name: 'c' }, - ]); + const collection = new Collection({ + items: [ + { id: 0, name: 'a' }, + { id: 1, name: 'b' }, + { id: 2, name: 'c' }, + ], + }); collection.getAll({ range: [1] }); const expected = [ { id: 0, name: 'a' }, @@ -628,7 +624,9 @@ describe('Collection', () => { describe('embed query', () => { it('should throw an error when trying to embed a non-existing collection', () => { - const foos = new Collection([{ name: 'John', bar_id: 123 }]); + const foos = new Collection({ + items: [{ name: 'John', bar_id: 123 }], + }); const server = new Server(); server.addCollection('foos', foos); expect(() => { @@ -639,8 +637,10 @@ describe('Collection', () => { }); it('should return the original object for missing embed one', () => { - const foos = new Collection([{ name: 'John', bar_id: 123 }]); - const bars = new Collection([]); + const foos = new Collection({ + items: [{ name: 'John', bar_id: 123 }], + }); + const bars = new Collection({ items: [] }); const server = new Server(); server.addCollection('foos', foos); server.addCollection('bars', bars); @@ -649,15 +649,19 @@ describe('Collection', () => { }); it('should return the object with the reference object for embed one', () => { - const foos = new Collection([ - { name: 'John', bar_id: 123 }, - { name: 'Jane', bar_id: 456 }, - ]); - const bars = new Collection([ - { id: 1, bar: 'nobody wants me' }, - { id: 123, bar: 'baz' }, - { id: 456, bar: 'bazz' }, - ]); + const foos = new Collection({ + items: [ + { name: 'John', bar_id: 123 }, + { name: 'Jane', bar_id: 456 }, + ], + }); + const bars = new Collection({ + items: [ + { id: 1, bar: 'nobody wants me' }, + { id: 123, bar: 'baz' }, + { id: 456, bar: 'bazz' }, + ], + }); const server = new Server(); server.addCollection('foos', foos); server.addCollection('bars', bars); @@ -679,7 +683,9 @@ describe('Collection', () => { }); it('should throw an error when trying to embed many a non-existing collection', () => { - const foos = new Collection([{ name: 'John', bar_id: 123 }]); + const foos = new Collection({ + items: [{ name: 'John', bar_id: 123 }], + }); const server = new Server(); server.addCollection('foos', foos); expect(() => { @@ -690,10 +696,12 @@ describe('Collection', () => { }); it('should return the object with an empty array for missing embed many', () => { - const foos = new Collection([{ name: 'John', bar_id: 123 }]); - const bars = new Collection([ - { id: 1, bar: 'nobody wants me' }, - ]); + const foos = new Collection({ + items: [{ name: 'John', bar_id: 123 }], + }); + const bars = new Collection({ + items: [{ id: 1, bar: 'nobody wants me' }], + }); const server = new Server(); server.addCollection('foos', foos); server.addCollection('bars', bars); @@ -702,16 +710,20 @@ describe('Collection', () => { }); it('should return the object with an array of references for embed many', () => { - const foos = new Collection([ - { id: 1, name: 'John', bar_id: 123 }, - { id: 2, name: 'Jane', bar_id: 456 }, - { id: 3, name: 'Jules', bar_id: 456 }, - ]); - const bars = new Collection([ - { id: 1, bar: 'nobody wants me' }, - { id: 123, bar: 'baz' }, - { id: 456, bar: 'bazz' }, - ]); + const foos = new Collection({ + items: [ + { id: 1, name: 'John', bar_id: 123 }, + { id: 2, name: 'Jane', bar_id: 456 }, + { id: 3, name: 'Jules', bar_id: 456 }, + ], + }); + const bars = new Collection({ + items: [ + { id: 1, bar: 'nobody wants me' }, + { id: 123, bar: 'baz' }, + { id: 456, bar: 'bazz' }, + ], + }); const server = new Server(); server.addCollection('foos', foos); server.addCollection('bars', bars); @@ -735,16 +747,20 @@ describe('Collection', () => { }); it('should return the object with an array of references for embed many using inner array', () => { - const foos = new Collection([ - { id: 1, name: 'John' }, - { id: 2, name: 'Jane' }, - { id: 3, name: 'Jules' }, - ]); - const bars = new Collection([ - { id: 1, bar: 'nobody wants me' }, - { id: 123, bar: 'baz', foos: [1] }, - { id: 456, bar: 'bazz', foos: [2, 3] }, - ]); + const foos = new Collection({ + items: [ + { id: 1, name: 'John' }, + { id: 2, name: 'Jane' }, + { id: 3, name: 'Jules' }, + ], + }); + const bars = new Collection({ + items: [ + { id: 1, bar: 'nobody wants me' }, + { id: 123, bar: 'baz', foos: [1] }, + { id: 456, bar: 'bazz', foos: [2, 3] }, + ], + }); const server = new Server(); server.addCollection('foos', foos); server.addCollection('bars', bars); @@ -764,29 +780,35 @@ describe('Collection', () => { }); it('should allow multiple embeds', () => { - const books = new Collection([ - { id: 1, title: 'Pride and Prejudice', author_id: 1 }, - { id: 2, title: 'Sense and Sensibility', author_id: 1 }, - { id: 3, title: 'War and Preace', author_id: 2 }, - ]); - const authors = new Collection([ - { - id: 1, - firstName: 'Jane', - lastName: 'Austen', - country_id: 1, - }, - { - id: 2, - firstName: 'Leo', - lastName: 'Tosltoi', - country_id: 2, - }, - ]); - const countries = new Collection([ - { id: 1, name: 'England' }, - { id: 2, name: 'Russia' }, - ]); + const books = new Collection({ + items: [ + { id: 1, title: 'Pride and Prejudice', author_id: 1 }, + { id: 2, title: 'Sense and Sensibility', author_id: 1 }, + { id: 3, title: 'War and Preace', author_id: 2 }, + ], + }); + const authors = new Collection({ + items: [ + { + id: 1, + firstName: 'Jane', + lastName: 'Austen', + country_id: 1, + }, + { + id: 2, + firstName: 'Leo', + lastName: 'Tosltoi', + country_id: 2, + }, + ], + }); + const countries = new Collection({ + items: [ + { id: 1, name: 'England' }, + { id: 2, name: 'Russia' }, + ], + }); const server = new Server(); server.addCollection('books', books); server.addCollection('authors', authors); @@ -830,11 +852,13 @@ describe('Collection', () => { describe('composite query', () => { it('should execute all commands of the query object', () => { - const collection = new Collection([ - { id: 0, name: 'c', arg: false }, - { id: 1, name: 'b', arg: true }, - { id: 2, name: 'a', arg: true }, - ]); + const collection = new Collection({ + items: [ + { id: 0, name: 'c', arg: false }, + { id: 1, name: 'b', arg: true }, + { id: 2, name: 'a', arg: true }, + ], + }); const query = { filter: { arg: true }, sort: 'name', @@ -857,22 +881,24 @@ describe('Collection', () => { }); it('should return the first collection matching the identifier', () => { - const collection = new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]); + const collection = new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }); expect(collection.getOne(1)).toEqual({ id: 1, name: 'foo' }); expect(collection.getOne(2)).toEqual({ id: 2, name: 'bar' }); }); it('should use the identifierName', () => { - const collection = new Collection( - [ + const collection = new Collection({ + items: [ { _id: 1, name: 'foo' }, { _id: 2, name: 'bar' }, ], - '_id', - ); + identifierName: '_id', + }); expect(collection.getOne(1)).toEqual({ _id: 1, name: 'foo' }); expect(collection.getOne(2)).toEqual({ _id: 2, name: 'bar' }); }); @@ -909,9 +935,9 @@ describe('Collection', () => { }); it('should refuse insertion with existing identifier', () => { - const collection = new Collection([ - { name: 'foo' }, - ]); + const collection = new Collection({ + items: [{ name: 'foo' }], + }); expect(() => { collection.addOne({ id: 0, name: 'bar' }); }).toThrow( @@ -938,9 +964,9 @@ describe('Collection', () => { }); it('should return the updated item', () => { - const collection = new Collection([ - { name: 'foo' }, - ]); + const collection = new Collection({ + items: [{ name: 'foo' }], + }); expect(collection.updateOne(0, { id: 0, name: 'bar' })).toEqual({ id: 0, name: 'bar', @@ -948,10 +974,9 @@ describe('Collection', () => { }); it('should update the item', () => { - const collection = new Collection([ - { name: 'foo' }, - { name: 'baz' }, - ]); + const collection = new Collection({ + items: [{ name: 'foo' }, { name: 'baz' }], + }); collection.updateOne(0, { id: 0, name: 'bar' }); expect(collection.getOne(0)).toEqual({ id: 0, name: 'bar' }); expect(collection.getOne(1)).toEqual({ id: 1, name: 'baz' }); @@ -981,11 +1006,9 @@ describe('Collection', () => { }); it('should decrement the sequence only if the removed item is the last', () => { - const collection = new Collection([ - { id: 0 }, - { id: 1 }, - { id: 2 }, - ]); + const collection = new Collection({ + items: [{ id: 0 }, { id: 1 }, { id: 2 }], + }); expect(collection.sequence).toEqual(3); collection.removeOne(2); expect(collection.sequence).toEqual(2); @@ -995,4 +1018,57 @@ describe('Collection', () => { expect(r.id).toEqual(2); }); }); + + describe('custom identifier generation', () => { + test('should use the custom identifier provided at initialization', () => { + const collection = new Collection({ + items: [ + { + id: '6090eb22-e140-4720-b7b2-e1416a3d2447', + name: 'foo', + }, + { + id: 'fb1c2ce1-5df7-4af8-be1c-7af234b67f7d', + name: 'baz', + }, + ], + identifierName: 'id', + }); + + expect( + collection.getOne('6090eb22-e140-4720-b7b2-e1416a3d2447'), + ).toEqual({ + id: '6090eb22-e140-4720-b7b2-e1416a3d2447', + name: 'foo', + }); + }); + + test('should use the custom identifier provided at insertion', () => { + const collection = new Collection({ + items: [], + identifierName: 'id', + }); + + const item = collection.addOne({ + id: '6090eb22-e140-4720-b7b2-e1416a3d2447', + name: 'foo', + }); + + expect(item.id).toEqual('6090eb22-e140-4720-b7b2-e1416a3d2447'); + }); + + test('should use the custom identifier generation function at insertion', () => { + const collection = new Collection({ + items: [], + identifierName: 'id', + getNewId: () => '6090eb22-e140-4720-b7b2-e1416a3d2447', + }); + + const item = collection.addOne({ + name: 'foo', + }); + + expect(item.id).toEqual('6090eb22-e140-4720-b7b2-e1416a3d2447'); + }); + }); }); diff --git a/src/Collection.ts b/src/Collection.ts index 395faa9..9be8fac 100644 --- a/src/Collection.ts +++ b/src/Collection.ts @@ -17,14 +17,24 @@ export class Collection { server: BaseServer | null = null; name: string | null = null; identifierName = 'id'; + getNewId: () => number | string; - constructor(items: T[] = [], identifierName = 'id') { + constructor({ + items = [], + identifierName = 'id', + getNewId, + }: { + items?: T[]; + identifierName?: string; + getNewId?: () => number | string; + } = {}) { if (!Array.isArray(items)) { throw new Error( "Can't initialize a Collection with anything else than an array of items", ); } this.identifierName = identifierName; + this.getNewId = getNewId || this.getNewIdFromSequence; items.map(this.addOne.bind(this)); } @@ -160,14 +170,14 @@ export class Collection { return items; } - getIndex(identifier: number) { + getIndex(identifier: number | string) { return this.items.findIndex( // biome-ignore lint/suspicious/noDoubleEquals: we want implicit type coercion (item) => item[this.identifierName] == identifier, ); } - getOne(identifier: number, query?: Query) { + getOne(identifier: number | string, query?: Query) { const index = this.getIndex(identifier); if (index === -1) { throw new Error(`No item with identifier ${identifier}`); @@ -180,29 +190,30 @@ export class Collection { return item; } + getNewIdFromSequence() { + return this.sequence++; + } + addOne(item: T) { const identifier = item[this.identifierName]; - if (identifier != null && typeof identifier !== 'number') { - throw new Error( - `Item must have an identifier of type number, got ${typeof identifier}`, - ); - } if (identifier != null) { if (this.getIndex(identifier) !== -1) { throw new Error( `An item with the identifier ${identifier} already exists`, ); } - this.sequence = Math.max(this.sequence, identifier) + 1; + if (typeof identifier === 'number') { + this.sequence = Math.max(this.sequence, identifier) + 1; + } } else { // @ts-expect-error - For some reason, TS does not accept writing a generic types with the index signature - item[this.identifierName] = this.sequence++; + item[this.identifierName] = this.getNewId(); } this.items.push(item); return Object.assign({}, item); // clone item to avoid returning the original; } - updateOne(identifier: number, item: T) { + updateOne(identifier: number | string, item: T) { const index = this.getIndex(identifier); if (index === -1) { throw new Error(`No item with identifier ${identifier}`); @@ -213,7 +224,7 @@ export class Collection { return Object.assign({}, this.items[index]); // clone item to avoid returning the original } - removeOne(identifier: number) { + removeOne(identifier: number | string) { const index = this.getIndex(identifier); if (index === -1) { throw new Error(`No item with identifier ${identifier}`); @@ -221,7 +232,7 @@ export class Collection { const item = this.items[index]; this.items.splice(index, 1); // biome-ignore lint/suspicious/noDoubleEquals: we want implicit type coercion - if (identifier == this.sequence - 1) { + if (typeof identifier === 'number' && identifier == this.sequence - 1) { this.sequence--; } return item; diff --git a/src/Server.spec.ts b/src/Server.spec.ts index 3da125c..0e6c813 100644 --- a/src/Server.spec.ts +++ b/src/Server.spec.ts @@ -46,10 +46,12 @@ describe('Server', () => { describe('addCollection', () => { it('should add a collection and index it by name', () => { const server = new Server(); - const collection = new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]); + const collection = new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }); server.addCollection('foo', collection); const newcollection = server.getCollection('foo'); expect(newcollection).toEqual(collection); @@ -70,14 +72,16 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); server.addCollection( 'baz', - new Collection([{ id: 1, name: 'baz' }]), + new Collection({ items: [{ id: 1, name: 'baz' }] }), ); expect(server.getAll('foo')).toEqual([ { id: 1, name: 'foo' }, @@ -90,11 +94,13 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 0, name: 'c', arg: false }, - { id: 1, name: 'b', arg: true }, - { id: 2, name: 'a', arg: true }, - ]), + new Collection({ + items: [ + { id: 0, name: 'c', arg: false }, + { id: 1, name: 'b', arg: true }, + { id: 2, name: 'a', arg: true }, + ], + }), ); const params = { filter: { arg: true }, @@ -114,7 +120,7 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([{ id: 1, name: 'foo' }]), + new Collection({ items: [{ id: 1, name: 'foo' }] }), ); expect(() => { server.getOne('foo', 2); @@ -125,10 +131,12 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); expect(server.getOne('foo', 1)).toEqual({ id: 1, name: 'foo' }); expect(server.getOne('foo', 2)).toEqual({ id: 2, name: 'bar' }); @@ -138,13 +146,13 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection( - [ + new Collection({ + items: [ { _id: 1, name: 'foo' }, { _id: 2, name: 'bar' }, ], - '_id', - ), + identifierName: '_id', + }), ); expect(server.getOne('foo', 1)).toEqual({ _id: 1, name: 'foo' }); expect(server.getOne('foo', 2)).toEqual({ _id: 2, name: 'bar' }); @@ -178,10 +186,12 @@ describe('Server', () => { }); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); let request: SinonFakeXMLHttpRequest | null; request = getFakeXMLHTTPRequest('GET', '/foo?_start=1&_end=1'); @@ -223,10 +233,12 @@ describe('Server', () => { }); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); const request = getFakeXMLHTTPRequest('GET', '/foo'); if (request == null) throw new Error('request is null'); @@ -267,10 +279,12 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); const request = getFakeXMLHTTPRequest('GET', '/foo'); if (request == null) throw new Error('request is null'); @@ -291,15 +305,17 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foos', - new Collection([ - { id: 0, name: 'c', arg: false }, - { id: 1, name: 'b', arg: true }, - { id: 2, name: 'a', arg: true }, - ]), + new Collection({ + items: [ + { id: 0, name: 'c', arg: false }, + { id: 1, name: 'b', arg: true }, + { id: 2, name: 'a', arg: true }, + ], + }), ); server.addCollection( 'bars', - new Collection([{ id: 0, name: 'a', foo_id: 1 }]), + new Collection({ items: [{ id: 0, name: 'a', foo_id: 1 }] }), ); const request = getFakeXMLHTTPRequest( 'GET', @@ -323,7 +339,9 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]), + new Collection({ + items: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], + }), ); // 11 items let request: SinonFakeXMLHttpRequest | null; request = getFakeXMLHTTPRequest('GET', '/foo'); @@ -375,10 +393,12 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); const request = getFakeXMLHTTPRequest( 'POST', @@ -426,10 +446,12 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); const request = getFakeXMLHTTPRequest('GET', '/foo/2'); if (request == null) throw new Error('request is null'); @@ -456,10 +478,12 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); const request = getFakeXMLHTTPRequest( 'PUT', @@ -483,7 +507,7 @@ describe('Server', () => { it('should respond to PUT /foo/:id on a non-existing id with a 404', () => { const server = new Server(); - server.addCollection('foo', new Collection([])); + server.addCollection('foo', new Collection({ items: [] })); const request = getFakeXMLHTTPRequest( 'PUT', '/foo/3', @@ -498,10 +522,12 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); const request = getFakeXMLHTTPRequest( 'PATCH', @@ -525,7 +551,7 @@ describe('Server', () => { it('should respond to PATCH /foo/:id on a non-existing id with a 404', () => { const server = new Server(); - server.addCollection('foo', new Collection([])); + server.addCollection('foo', new Collection({ items: [] })); const request = getFakeXMLHTTPRequest( 'PATCH', '/foo/3', @@ -540,10 +566,12 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([ - { id: 1, name: 'foo' }, - { id: 2, name: 'bar' }, - ]), + new Collection({ + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + }), ); const request = getFakeXMLHTTPRequest('DELETE', '/foo/2'); if (request == null) throw new Error('request is null'); @@ -560,7 +588,7 @@ describe('Server', () => { it('should respond to DELETE /foo/:id on a non-existing id with a 404', () => { const server = new Server(); - server.addCollection('foo', new Collection([])); + server.addCollection('foo', new Collection({ items: [] })); const request = getFakeXMLHTTPRequest('DELETE', '/foo/3'); if (request == null) throw new Error('request is null'); server.handle(request); @@ -631,7 +659,9 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]), + new Collection({ + items: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}], + }), ); // 10 items server.setDefaultQuery(() => { return { range: [2, 4] }; @@ -653,7 +683,9 @@ describe('Server', () => { const server = new Server(); server.addCollection( 'foo', - new Collection([{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]), + new Collection({ + items: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}], + }), ); // 10 items server.setDefaultQuery((name) => ({ range: [2, 4] })); const request = getFakeXMLHTTPRequest('GET', '/foo?range=[0,4]'); diff --git a/src/Server.ts b/src/Server.ts index b7053a7..ec2566b 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -6,10 +6,6 @@ export class Server extends BaseServer { requestInterceptors: SinonRequestInterceptor[] = []; responseInterceptors: SinonResponseInterceptor[] = []; - constructor(baseUrl = '') { - super(baseUrl); - } - addRequestInterceptor(interceptor: SinonRequestInterceptor) { this.requestInterceptors.push(interceptor); } diff --git a/src/Single.spec.ts b/src/Single.spec.ts index 44001c6..93306a0 100644 --- a/src/Single.spec.ts +++ b/src/Single.spec.ts @@ -30,7 +30,7 @@ describe('Single', () => { it('should return the original object for missing embed one', () => { const foo = new Single({ name: 'foo', bar_id: 123 }); - const bars = new Collection([]); + const bars = new Collection({ items: [] }); const server = new Server(); server.addSingle('foo', foo); server.addCollection('bars', bars); @@ -40,11 +40,13 @@ describe('Single', () => { it('should return the object with the reference object for embed one', () => { const foo = new Single({ name: 'foo', bar_id: 123 }); - const bars = new Collection([ - { id: 1, bar: 'nobody wants me' }, - { id: 123, bar: 'baz' }, - { id: 456, bar: 'bazz' }, - ]); + const bars = new Collection({ + items: [ + { id: 1, bar: 'nobody wants me' }, + { id: 123, bar: 'baz' }, + { id: 456, bar: 'bazz' }, + ], + }); const server = new Server(); server.addSingle('foo', foo); server.addCollection('bars', bars); @@ -69,11 +71,13 @@ describe('Single', () => { it('should return the object with an array of references for embed many using inner array', () => { const foo = new Single({ name: 'foo', bars: [1, 3] }); - const bars = new Collection([ - { id: 1, bar: 'baz' }, - { id: 2, bar: 'biz' }, - { id: 3, bar: 'boz' }, - ]); + const bars = new Collection({ + items: [ + { id: 1, bar: 'baz' }, + { id: 2, bar: 'biz' }, + { id: 3, bar: 'boz' }, + ], + }); const server = new Server(); server.addSingle('foo', foo); server.addCollection('bars', bars); @@ -93,16 +97,20 @@ describe('Single', () => { bars: [1, 3], bazs: [4, 5], }); - const bars = new Collection([ - { id: 1, name: 'bar1' }, - { id: 2, name: 'bar2' }, - { id: 3, name: 'bar3' }, - ]); - const bazs = new Collection([ - { id: 4, name: 'baz1' }, - { id: 5, name: 'baz2' }, - { id: 6, name: 'baz3' }, - ]); + const bars = new Collection({ + items: [ + { id: 1, name: 'bar1' }, + { id: 2, name: 'bar2' }, + { id: 3, name: 'bar3' }, + ], + }); + const bazs = new Collection({ + items: [ + { id: 4, name: 'baz1' }, + { id: 5, name: 'baz2' }, + { id: 6, name: 'baz3' }, + ], + }); const server = new Server(); server.addSingle('foo', foo); server.addCollection('bars', bars); diff --git a/src/msw.ts b/src/msw.ts index cb49425..89113fd 100644 --- a/src/msw.ts +++ b/src/msw.ts @@ -5,11 +5,13 @@ import { BaseServer } from './BaseServer.js'; export const getMswHandlers = ({ baseUrl = 'http://localhost:3000', data, + getNewId, }: { baseUrl?: string; data: Record; + getNewId?: () => number | string; }) => { - const server = new BaseServer(baseUrl); + const server = new BaseServer({ baseUrl, getNewId }); server.init(data); const collections = Object.keys(data); @@ -27,30 +29,34 @@ const getCollectionHandlers = ({ collectionName: string; server: BaseServer; }) => { - return http.all(`${baseUrl}/${collectionName}`, async ({ request }) => { - const url = new URL(request.url); - const params = Object.fromEntries( - Array.from(new URLSearchParams(url.search).entries()).map( - ([key, value]) => [key, JSON.parse(value)], - ), - ); - let requestJson: Record | undefined = undefined; - try { - const text = await request.text(); - requestJson = JSON.parse(text); - } catch (e) { - // not JSON, no big deal - } - const response = server.handleRequest({ - url: request.url.split('?')[0], - method: request.method, - requestJson, - params, - }); + return http.all( + // Using a regex ensures we match all URLs that start with the collection name + new RegExp(`${baseUrl}/${collectionName}`), + async ({ request }) => { + const url = new URL(request.url); + const params = Object.fromEntries( + Array.from(new URLSearchParams(url.search).entries()).map( + ([key, value]) => [key, JSON.parse(value)], + ), + ); + let requestJson: Record | undefined = undefined; + try { + const text = await request.text(); + requestJson = JSON.parse(text); + } catch (e) { + // not JSON, no big deal + } + const response = server.handleRequest({ + url: request.url.split('?')[0], + method: request.method, + requestJson, + params, + }); - return HttpResponse.json(response.body, { - status: response.status, - headers: response.headers, - }); - }); + return HttpResponse.json(response.body, { + status: response.status, + headers: response.headers, + }); + }, + ); };