From a71f625796493c2e69f87e9d385c2184d695734c Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Fri, 3 May 2024 17:12:59 +0200 Subject: [PATCH 1/9] Add support for custom id generation --- README.md | 31 +++++++++++++++++++++----- src/BaseServer.ts | 17 +++++++++----- src/Collection.spec.ts | 50 ++++++++++++++++++++++++++++++++++++++++++ src/Collection.ts | 33 +++++++++++++++++----------- src/Server.ts | 4 ---- 5 files changed, 107 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index e8e44d8..ed84c15 100644 --- a/README.md +++ b/README.md @@ -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('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('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([], '_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 uses UUID for instance but would like to avoid providing them when you insert new items, you can provide your own function: + +```js +import FakeRest from 'fakerest'; +import uuid from 'uuid'; + +const restServer = new FakeRest.Server('http://my.custom.domain', () => 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('http://my.custom.domain'); +const authorsCollection = new FakeRest.Collection([], '_id', , () => uuid.v5()); +``` + ## Development ```sh diff --git a/src/BaseServer.ts b/src/BaseServer.ts index 72550d7..ff8c7cd 100644 --- a/src/BaseServer.ts +++ b/src/BaseServer.ts @@ -9,9 +9,11 @@ export class BaseServer { batchUrl: string | null = null; collections: Record> = {}; singles: Record> = {}; + getNewId?: () => number | string; - constructor(baseUrl = '') { + constructor(baseUrl = '', getNewId?: () => number | string) { this.baseUrl = baseUrl; + this.getNewId = getNewId; } /** @@ -21,7 +23,10 @@ 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(value, 'id', this.getNewId), + ); } else { this.addSingle(name, new Single(value)); } @@ -103,7 +108,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 +116,17 @@ export class BaseServer { if (!Object.prototype.hasOwnProperty.call(this.collections, name)) { this.addCollection( name, - new Collection([] as CollectionItem[], 'id'), + new Collection([] as CollectionItem[], 'id', 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); } diff --git a/src/Collection.spec.ts b/src/Collection.spec.ts index a7333a7..f93ee58 100644 --- a/src/Collection.spec.ts +++ b/src/Collection.spec.ts @@ -995,4 +995,54 @@ describe('Collection', () => { expect(r.id).toEqual(2); }); }); + + describe('custom identifier generation', () => { + test('should use the custom identifier provided at initialization', () => { + const collection = new Collection( + [ + { + id: '6090eb22-e140-4720-b7b2-e1416a3d2447', + name: 'foo', + }, + { + id: 'fb1c2ce1-5df7-4af8-be1c-7af234b67f7d', + name: 'baz', + }, + ], + '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([], '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( + [], + 'id', + () => '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..4937924 100644 --- a/src/Collection.ts +++ b/src/Collection.ts @@ -17,14 +17,20 @@ export class Collection { server: BaseServer | null = null; name: string | null = null; identifierName = 'id'; + getNewId: () => number | string; - constructor(items: T[] = [], identifierName = 'id') { + constructor( + items: T[] = [], + identifierName = 'id', + 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 +166,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 +186,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 +220,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 +228,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.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); } From 8bea60f5f3ee4c11f75dcf6e79d11b2cd7c4280a Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Fri, 3 May 2024 17:15:43 +0200 Subject: [PATCH 2/9] Fix RegExp for identifier matching --- src/BaseServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BaseServer.ts b/src/BaseServer.ts index ff8c7cd..11772d3 100644 --- a/src/BaseServer.ts +++ b/src/BaseServer.ts @@ -210,7 +210,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: {} }; From 1c5b4f0628dd01a3d9752b4e44ab0449ce5fbd68 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Fri, 3 May 2024 17:17:40 +0200 Subject: [PATCH 3/9] Fix MSW --- src/msw.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/msw.ts b/src/msw.ts index cb49425..08c4137 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); From f5e2506573f13cecc2b02298b86fb9af5e902aff Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Wed, 29 May 2024 18:19:10 +0200 Subject: [PATCH 4/9] [no ci] Apply suggestions from code review --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed84c15..94d799f 100644 --- a/README.md +++ b/README.md @@ -426,7 +426,7 @@ server.respondWith(restServer2.getHandler()); ## Configure Identifiers Generation -By default, FakeRest uses an auto incremented sequence for the items identifiers. If you'd rather uses UUID for instance but would like to avoid providing them when you insert new items, you can provide your own function: +By default, FakeRest uses an auto incremented sequence for the items identifiers. If you'd rather use UUIDs for instance but would like to avoid providing them when you insert new items, you can provide your own function: ```js import FakeRest from 'fakerest'; @@ -442,7 +442,7 @@ import FakeRest from 'fakerest'; import uuid from 'uuid'; const restServer = new FakeRest.Server('http://my.custom.domain'); -const authorsCollection = new FakeRest.Collection([], '_id', , () => uuid.v5()); +const authorsCollection = new FakeRest.Collection([], '_id', () => uuid.v5()); ``` ## Development From 3520e97c5444b3dea84c08c389286b11204db2c8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Thu, 30 May 2024 11:48:05 +0200 Subject: [PATCH 5/9] change definition of Server and Collection constructors to use objects --- README.md | 14 +- example/fetchMock.ts | 4 +- src/BaseServer.ts | 20 +- src/Collection.spec.ts | 578 +++++++++++++++++++++-------------------- src/Collection.ts | 12 +- src/Server.spec.ts | 166 +++++++----- src/Single.spec.ts | 50 ++-- src/msw.ts | 2 +- 8 files changed, 466 insertions(+), 380 deletions(-) diff --git a/README.md b/README.md index 94d799f..9d4195a 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 -const 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,9 +406,9 @@ restServer.setDefaultQuery(function(resourceName) { restServer.setBatchUrl('/batch'); // you can create more than one fake server to listen to several domains -const 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 -const 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 auto incremented identifiers by default but accept identifiers already set @@ -432,7 +432,7 @@ By default, FakeRest uses an auto incremented sequence for the items identifiers import FakeRest from 'fakerest'; import uuid from 'uuid'; -const restServer = new FakeRest.Server('http://my.custom.domain', () => uuid.v5()); +const restServer = new FakeRest.Server({ baseUrl: 'http://my.custom.domain', getNewId: () => uuid.v5() }); ``` This can also be specified at the collection level: @@ -441,8 +441,8 @@ This can also be specified at the collection level: import FakeRest from 'fakerest'; import uuid from 'uuid'; -const restServer = new FakeRest.Server('http://my.custom.domain'); -const authorsCollection = new FakeRest.Collection([], '_id', () => uuid.v5()); +const restServer = new FakeRest.Server({ baseUrl: 'http://my.custom.domain' }); +const authorsCollection = new FakeRest.Collection({ items: [], identifierName: '_id', getNewId: () => uuid.v5() }); ``` ## Development 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 11772d3..d743709 100644 --- a/src/BaseServer.ts +++ b/src/BaseServer.ts @@ -11,7 +11,13 @@ export class BaseServer { singles: Record> = {}; getNewId?: () => number | string; - constructor(baseUrl = '', getNewId?: () => number | string) { + constructor({ + baseUrl = '', + getNewId, + }: { + baseUrl?: string; + getNewId?: () => number | string; + } = {}) { this.baseUrl = baseUrl; this.getNewId = getNewId; } @@ -25,7 +31,11 @@ export class BaseServer { if (Array.isArray(value)) { this.addCollection( name, - new Collection(value, 'id', this.getNewId), + new Collection({ + items: value, + identifierName: 'id', + getNewId: this.getNewId, + }), ); } else { this.addSingle(name, new Single(value)); @@ -116,7 +126,11 @@ export class BaseServer { if (!Object.prototype.hasOwnProperty.call(this.collections, name)) { this.addCollection( name, - new Collection([] as CollectionItem[], 'id', this.getNewId), + new Collection({ + items: [], + identifierName: 'id', + getNewId: this.getNewId, + }), ); } return this.collections[name].addOne(item); diff --git a/src/Collection.spec.ts b/src/Collection.spec.ts index f93ee58..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); @@ -998,8 +1021,8 @@ describe('Collection', () => { describe('custom identifier generation', () => { test('should use the custom identifier provided at initialization', () => { - const collection = new Collection( - [ + const collection = new Collection({ + items: [ { id: '6090eb22-e140-4720-b7b2-e1416a3d2447', name: 'foo', @@ -1009,8 +1032,8 @@ describe('Collection', () => { name: 'baz', }, ], - 'id', - ); + identifierName: 'id', + }); expect( collection.getOne('6090eb22-e140-4720-b7b2-e1416a3d2447'), @@ -1021,7 +1044,10 @@ describe('Collection', () => { }); test('should use the custom identifier provided at insertion', () => { - const collection = new Collection([], 'id'); + const collection = new Collection({ + items: [], + identifierName: 'id', + }); const item = collection.addOne({ id: '6090eb22-e140-4720-b7b2-e1416a3d2447', @@ -1032,11 +1058,11 @@ describe('Collection', () => { }); test('should use the custom identifier generation function at insertion', () => { - const collection = new Collection( - [], - 'id', - () => '6090eb22-e140-4720-b7b2-e1416a3d2447', - ); + const collection = new Collection({ + items: [], + identifierName: 'id', + getNewId: () => '6090eb22-e140-4720-b7b2-e1416a3d2447', + }); const item = collection.addOne({ name: 'foo', diff --git a/src/Collection.ts b/src/Collection.ts index 4937924..9be8fac 100644 --- a/src/Collection.ts +++ b/src/Collection.ts @@ -19,11 +19,15 @@ export class Collection { identifierName = 'id'; getNewId: () => number | string; - constructor( - items: T[] = [], + constructor({ + items = [], identifierName = 'id', - getNewId?: () => number | string, - ) { + 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", 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/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 08c4137..c34bb97 100644 --- a/src/msw.ts +++ b/src/msw.ts @@ -11,7 +11,7 @@ export const getMswHandlers = ({ data: Record; getNewId?: () => number | string; }) => { - const server = new BaseServer(baseUrl, getNewId); + const server = new BaseServer({ baseUrl, getNewId }); server.init(data); const collections = Object.keys(data); From f0d2880a8a1440e679c864c0c5ae4b7037f44cab Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 3 Jun 2024 09:48:25 +0200 Subject: [PATCH 6/9] Add more views to the example --- example/App.tsx | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) 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 = () => ( + + + + + + +); From b14b188038c9b76907b907a0926ce045c3a3a770 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 3 Jun 2024 09:49:05 +0200 Subject: [PATCH 7/9] Fix MSW integration --- src/msw.ts | 54 +++++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/msw.ts b/src/msw.ts index c34bb97..89113fd 100644 --- a/src/msw.ts +++ b/src/msw.ts @@ -29,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, + }); + }, + ); }; From 175604802db36438af8cdd84a7d30080609a021e Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 3 Jun 2024 10:19:30 +0200 Subject: [PATCH 8/9] Add upgrade guide --- UPGRADE.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 UPGRADE.md diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000..36af71b --- /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` Take 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 From a879f474401f99297fb36c0535e1170c9380e119 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:33:11 +0200 Subject: [PATCH 9/9] Apply suggestions from code review Co-authored-by: Francois Zaninotto --- README.md | 2 +- UPGRADE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d4195a..767fcb8 100644 --- a/README.md +++ b/README.md @@ -426,7 +426,7 @@ server.respondWith(restServer2.getHandler()); ## Configure Identifiers Generation -By default, FakeRest uses an auto incremented sequence for the items identifiers. If you'd rather use UUIDs for instance but would like to avoid providing them when you insert new items, you can provide your own function: +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'; diff --git a/UPGRADE.md b/UPGRADE.md index 36af71b..923dc5b 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -24,7 +24,7 @@ import { data } from './data'; server.init(data); ``` -## Constructor Of `Collection` Take An Object +## Constructor Of `Collection` Takes An Object ```diff -const posts = new Collection([