Skip to content

Commit

Permalink
Added any match support
Browse files Browse the repository at this point in the history
  • Loading branch information
chronoDave committed Jul 16, 2023
1 parent 2ba7828 commit 391d070
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 280 deletions.
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ const doc = db.insert([draft]);

### Basic query

`await db.findOne(string | Query) => Promise<Doc>`
`await db.find(string[] | Query) => Promise<Doc[]>`
`await db.findOne(...Query[]) => Promise<Doc>`
`await db.find(...Query[]) => Promise<Doc[]>`

Find doc(s) matching query. Operators are supported and can be mixed together with object properties.

Expand All @@ -176,24 +176,28 @@ await db.find({ _id: '1' });
// [1, 2, 3] (Doc _id's)
await db.find({ type: 'normal' })

// Find all docs matching type 'normal' and important 'true'
// Find docs matching type 'normal' and important 'true'
// [2], all fields must match
await db.find({ type: 'normal', important: 'true' })

// Find all docs with variants 'weak'
// Find docs with variants 'weak'
// [4], note how only 4 matches, even though all entries contain weak
// Array content and order must mach
await db.find({ variant: ['weak'] })

// Find all docs with variants 'strong', 'weak', in that order
// Find docs with variants 'strong', 'weak', in that order
// []
await db.find({ variant: ['strong', 'weak'] })

// Find all docs with parent '3'
// Find docs with parent '3'
// [], all keys must be present
await db.find({ properties: { parent: '3' } })
// [4], key order does not matter
await db.find({ properties: { parent: '3', type: 'weak' } })

// Find docs that either have parent '4' or important 'false'
// [1, 3]
await db.find({ properties: { parent: '4' } }, { important: false });
```

### Operators
Expand Down
64 changes: 9 additions & 55 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import {
isDoc,
isModifier,
isQuery,
isQueryMatch,
isUpdate
} from './validation';
Expand All @@ -18,7 +17,6 @@ import {
DUPLICATE_DOC,
DUPLICATE_DOCS,
INVALID_DOC,
INVALID_QUERY,
INVALID_UPDATE,
MEMORY_MODE
} from './errors';
Expand Down Expand Up @@ -118,75 +116,31 @@ export default class LeafDB<T extends Draft> {
}, []);
}

async findOne(query: string | Query<Doc<T>>) {
if (typeof query === 'string') {
const doc = this._memory.get(query);
return Promise.resolve(doc);
}

if (!isQuery(query)) return Promise.reject(INVALID_QUERY(query));

for (const doc of this._memory.docs()) {
if (!doc.__deleted && isQueryMatch(doc, query)) return Promise.resolve(doc);
}

return Promise.resolve(null);
}

async find(query: string[] | Query<Doc<T>>) {
if (Array.isArray(query)) {
const docs = query
.reduce<Array<Doc<T>>>((acc, cur) => {
const doc = this._memory.get(cur);
if (doc) acc.push(doc);
return acc;
}, []);

return Promise.resolve(docs);
}
if (!isQuery(query)) return Promise.reject(INVALID_QUERY(query));

async find(...queries: Array<Query<Doc<T>>>) {
const docs: Array<Doc<T>> = [];
for (const doc of this._memory.docs()) {
if (!doc.__deleted && isQueryMatch(doc, query)) docs.push(doc);
if (
!doc.__deleted &&
queries.some(query => isQueryMatch(doc, query))
) docs.push(doc);
}

return Promise.resolve(docs);
}

async updateOne(query: string | Query<Doc<T>>, update: Update<T>) {
async update(update: Update<T>, ...queries: Array<Query<Doc<T>>>) {
if (!isUpdate(update)) return Promise.reject(INVALID_UPDATE(update));

const doc = await this.findOne(query);
if (!doc) return Promise.resolve(null);

const newDoc = isModifier(update) ?
modify(doc, update) :
{ ...update, _id: doc._id };
return Promise.resolve(this._set(newDoc));
}

async update(query: string[] | Query<Doc<T>>, update: Update<T>) {
if (!isUpdate(update)) return Promise.reject(INVALID_UPDATE(update));

const newDocs = this.find(query)
const newDocs = this.find(...queries)
.then(docs => docs.map(doc => isModifier(update) ?
modify(doc, update) :
{ ...update, _id: doc._id }));

return Promise.resolve(newDocs);
}

async deleteOne(query: string | Query<Doc<T>>): Promise<boolean> {
const doc = await this.findOne(query);
if (!doc) return Promise.resolve(false);

this._delete(doc._id);
return Promise.resolve(true);
}

async delete(query: string[] | Query<Doc<T>>): Promise<number> {
const docs = await this.find(query);
async delete(...queries: Array<Query<Doc<T>>>): Promise<number> {
const docs = await this.find(...queries);
if (!Array.isArray(docs)) return Promise.resolve(0);

return docs.reduce<number>((acc, cur) => {
Expand Down
32 changes: 17 additions & 15 deletions test/unit/model/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ import path from 'path';
import LeafDB from '../../../src/model';
import datasetMeteorites from '../../assets/nasa_earth-meteorite-landings';

export type DataModel = {
name: string
id: string
nametype: string
recclass: string
mass: string
fall: string
year: string
reclat: string
reclong: string
geolocation: {
type: string,
coordinates: [number, number]
}
};

export type Options = {
root: string
name: string
Expand Down Expand Up @@ -37,21 +53,7 @@ export default (options?: Partial<Options>) => {
};

export const memory = Object.fromEntries(datasetMeteorites.map(x => [x._id, x]));
export const data = datasetMeteorites as unknown as Array<{
name: string
id: string
nametype: string
recclass: string
mass: string
fall: string
year: string
reclat: string
reclong: string
geolocation: {
type: string,
coordinates: [number, number]
}
}>;
export const data = datasetMeteorites as unknown as DataModel[];

export const production = {
_id: '1',
Expand Down
27 changes: 15 additions & 12 deletions test/unit/model/model.delete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,11 @@ import test from 'tape';

import setup, { memory } from './fixture';

test('[model.delete] deletes docs if matches are found (ids)', async t => {
const { db } = setup({ memory });

const docs = await db.delete(['1', '2']);
t.strictEqual(docs, 2, 'deletes docs');

t.end();
});

test('[model.delete] deletes docs if matches are found (simple)', async t => {
const { db } = setup({ memory });

const docs = await db.delete({ mass: '720' });
t.strictEqual(docs, 2, 'deletes docs');
t.strictEqual(docs, 2, 'deletes docs (simple)');

t.end();
});
Expand All @@ -24,7 +15,7 @@ test('[model.delete] deletes docs if matches are found (nested)', async t => {
const { db } = setup({ memory });

const docs = await db.delete({ geolocation: { type: 'Point' } });
t.strictEqual(docs, 988, 'deletes docs');
t.strictEqual(docs, 988, 'deletes docs (nested)');

t.end();
});
Expand All @@ -33,7 +24,7 @@ test('[model.delete] deletes docs if matches are found (complex)', async t => {
const { db } = setup({ memory });

const docs = await db.delete({ geolocation: { coordinates: { $has: 10.23333 } } });
t.strictEqual(docs, 1, 'deletes docs');
t.strictEqual(docs, 1, 'deletes docs (complex)');

t.end();
});
Expand All @@ -46,3 +37,15 @@ test('[model.delete] returns 0 if no match is found', async t => {

t.end();
});

test('[model.delete] deletes docs if any query matches', async t => {
const { db } = setup({ memory });

const docs = await db.delete(
{ _id: '-1' },
{ geolocation: { coordinates: { $has: 10.23333 } } }
);
t.strictEqual(docs, 1, 'delete docs (any match)');

t.end();
});
48 changes: 0 additions & 48 deletions test/unit/model/model.deleteOne.spec.ts

This file was deleted.

40 changes: 25 additions & 15 deletions test/unit/model/model.find.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,7 @@ test('[model.find] returns docs on empty query', async t => {

const docs = await db.find({});
t.true(Array.isArray(docs), 'is array');
t.strictEqual(docs.length, Object.keys(memory).length, 'finds docs');

t.end();
});

test('[model.find] returns docs on query match (ids)', async t => {
const { db } = setup({ memory });

const docs = await db.find(['1', '2', '-1']);
t.true(Array.isArray(docs), 'is array');
t.strictEqual(docs.length, 2, 'finds docs');
t.deepEqual(docs[0], memory['1'], 'is doc');
t.strictEqual(docs.length, Object.keys(memory).length, 'finds docs (empty)');

t.end();
});
Expand All @@ -27,7 +16,7 @@ test('[model.find] returns docs on query match (simple)', async t => {
const { db } = setup({ memory });

const docs = await db.find({ nametype: 'Valid' });
t.strictEqual(docs.length, 1000, 'finds docs');
t.strictEqual(docs.length, 1000, 'finds docs (simple)');

t.end();
});
Expand All @@ -36,7 +25,7 @@ test('[model.find] returns docs on query match (nested)', async t => {
const { db } = setup({ memory });

const docs = await db.find({ geolocation: { type: 'Point' } });
t.strictEqual(docs.length, 988, 'finds docs');
t.strictEqual(docs.length, 988, 'finds docs (nested)');

t.end();
});
Expand All @@ -45,7 +34,28 @@ test('[model.find] returns docs on query match (complex)', async t => {
const { db } = setup({ memory });

const docs = await db.find({ geolocation: { coordinates: { $has: 56.18333 } } });
t.strictEqual(docs.length, 1, 'finds docs');
t.strictEqual(docs.length, 1, 'finds docs (complex)');

t.end();
});

test('[model.find] returns empty array if query does not match', async t => {
const { db } = setup({ memory });

const docs = await db.find({ geolocation: { coordinates: { $has: -1 } } });
t.strictEqual(docs.length, 0, 'does not find docs (no match)');

t.end();
});

test('[model.find] returns docs if any query matches', async t => {
const { db } = setup({ memory });

const docs = await db.find(
{ geolocation: { coordinates: { $has: -1 } } },
{ geolocation: { coordinates: { $has: 56.18333 } } }
);
t.strictEqual(docs.length, 1, 'finds docs (any match)');

t.end();
});
Loading

0 comments on commit 391d070

Please sign in to comment.