Skip to content

Commit

Permalink
Merge branch 'main' into documentation-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
maradotwebp authored May 1, 2023
2 parents 06bc2b1 + 738471d commit e328aa0
Show file tree
Hide file tree
Showing 44 changed files with 1,035 additions and 177 deletions.
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) BlinkDB

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
59 changes: 59 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = nixpkgs.legacyPackages.${system}; in
{
# SHELL
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
nodejs
];
};
}
);
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/benchmarks/src/benchmarks/blinkdb/get-one.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { randFirstName } from "@ngneat/falso";
import { clear, createDB, createTable, insertMany, one } from "blinkdb";
import { createDB, createTable, insertMany, one } from "blinkdb";
import loki from "lokijs";
import { Bench } from "tinybench";

Expand Down Expand Up @@ -32,5 +32,5 @@ export const bench = new Bench()
lokiUserTable.get(2);
})
.add("blinkdb", async () => {
await one(blinkUserTable, { where: { id: 2 } });
await one(blinkUserTable, 2);
});
58 changes: 58 additions & 0 deletions packages/benchmarks/src/benchmarks/blinkdb/upsert-many.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { randFirstName } from "@ngneat/falso";
import { clear, createDB, createTable, insertMany, upsertMany } from "blinkdb";
import loki from "lokijs";
import { Bench } from "tinybench";

interface User {
id: number;
name: string;
age?: number;
}

const blinkdb = createDB({ clone: false });
let blinkUserTable = createTable<User>(blinkdb, "users")();

const lokidb = new loki("users.db");
let lokiUserTable = lokidb.addCollection<User>("users", { unique: ["id"] });

let users: User[] = [];
for (let i = 0; i < 10000; i++) {
users.push({
id: i,
name: randFirstName(),
age: Math.random() > 0.2 ? Math.random() * 40 : undefined,
});
}

lokiUserTable.insert(users.slice(0, 50000));
insertMany(blinkUserTable, users.slice(0, 50000));

export const bench = new Bench()
.add(
"lokijs",
() => {
const usersToInsert: User[] = [];
const usersToUpdate: User[] = [];
for(const user of users) {
if(lokiUserTable.get((user as any).$loki)) {
usersToUpdate.push(user);
} else {
usersToInsert.push(user);
}
}
lokiUserTable.insert(usersToInsert);
lokiUserTable.update(usersToUpdate);
},
{
beforeEach: () => lokiUserTable.clear(),
}
)
.add(
"blinkdb",
async () => {
await upsertMany(blinkUserTable, users);
},
{
beforeEach: () => clear(blinkUserTable),
}
);
43 changes: 43 additions & 0 deletions packages/benchmarks/src/benchmarks/blinkdb/upsert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { randFirstName } from "@ngneat/falso";
import { createDB, createTable, insertMany, upsert } from "blinkdb";
import loki from "lokijs";
import { Bench } from "tinybench";

interface User {
id: number;
name: string;
age?: number;
}

const blinkdb = createDB({ clone: false });
let blinkUserTable = createTable<User>(blinkdb, "users")();
let blinkIndex = 0;

const lokidb = new loki("users.db");
let lokiUserTable = lokidb.addCollection<User>("users", { unique: ["id"] });
let lokiIndex = 0;

let users: User[] = [];
for (let i = 0; i < 100000; i++) {
users.push({
id: i,
name: randFirstName(),
age: Math.random() > 0.2 ? Math.random() * 40 : undefined,
});
}

lokiUserTable.insert(users.slice(0, 50000));
insertMany(blinkUserTable, users.slice(0, 50000));

export const bench = new Bench()
.add("lokijs", () => {
const user = users[lokiIndex++];
if(lokiUserTable.get((user as any).$loki)) {
lokiUserTable.update(user);
} else {
lokiUserTable.insert(user);
}
})
.add("blinkdb", async () => {
await upsert(blinkUserTable, users[blinkIndex++]);
});
2 changes: 1 addition & 1 deletion packages/db/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "blinkdb",
"version": "0.11.0",
"version": "0.12.0",
"description": "A lightning fast, in-memory database for client-side JS",
"main": "dist/index.js",
"module": "src/index.ts",
Expand Down
46 changes: 46 additions & 0 deletions packages/db/src/core/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Query } from "../query/types";

/**
* Thrown if the primary key of an item being inserted is already present within the table.
*/
export class PrimaryKeyAlreadyInUseError<T> extends Error {
constructor(public readonly primaryKey: T) {
super(`Primary key "${primaryKey}" already in use.`);
}
}

/**
* Thrown if an invalid value is given as a primary key.
*/
export class InvalidPrimaryKeyError<T> extends Error {
constructor(public readonly primaryKey: T) {
super(`"${primaryKey}" is an invalid primary key value.`);
}
}

/**
* Thrown if an invalid value is given as a primary key.
*/
export class PrimaryKeyCannotBeModifiedError<T> extends Error {
constructor(public readonly primaryKey: T) {
super(`Primary key "${primaryKey}" cannot be modified in update queries.`);
}
}

/**
* Thrown if a table retrieval method expects to find exactly one item, but finds none.
*/
export class ItemNotFoundError<T extends object, P extends keyof T> extends Error {
constructor(public readonly queryOrId: Query<T, P>|T[P]) {
super(`No item found for query "${queryOrId}".`);
}
}

/**
* Thrown if a table retrieval method expects to find exactly one item, but finds more than one.
*/
export class MoreThanOneItemFoundError<T extends object, P extends keyof T> extends Error {
constructor(public readonly queryOrId: Query<T, P>|T[P]) {
super(`More than one item found for query "${queryOrId}".`)
}
}
22 changes: 21 additions & 1 deletion packages/db/src/core/first.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ beforeEach(async () => {
await insertMany(userTable, users);
});

describe("without filter", () => {
describe("without filter or id", () => {
it("should return null if there are no users in table", async () => {
await clear(userTable);
const item = await first(userTable);
Expand All @@ -37,6 +37,26 @@ describe("without filter", () => {
});
});

describe("with id", () => {
it("should return null if there is no match", async () => {
const item = await first(userTable, "1337");

expect(item).toBe(null);
});

it("should return the item if it finds a match", async () => {
const item = await first(userTable, "0");

expect(item).toStrictEqual(users.find((u) => u.id === "0"));
});

it("should clone returned items", async () => {
const item = await first(userTable, "0");

expect(item).not.toBe(users.find((u) => u.id === "0"));
});
});

describe("with filter", () => {
it("should return null if there is no match", async () => {
const item = await first(userTable, { where: { id: "1337" } });
Expand Down
33 changes: 25 additions & 8 deletions packages/db/src/core/first.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { middleware } from "../events/Middleware";
import { get } from "../query";
import { Query } from "../query/types";
import { OrdProps, Query } from "../query/types";
import { clone } from "./clone";
import { BlinkKey } from "./createDB";
import { Table } from "./createTable";
Expand Down Expand Up @@ -32,33 +32,50 @@ export async function first<T extends object, P extends keyof T>(
query: Query<T, P>
): Promise<T | null>;

/**
* Retrieves the first entity from `table` with the given `id`.
*
* @example
* const db = createDB();
* const userTable = createTable<User>(db, "users")();
* // Retrieve the 'Alice' user by their id
* const firstUser = await first(userTable, 'alice-uuid');
*/
export async function first<T extends object, P extends keyof T>(
table: Table<T, P>,
query?: Query<T, P>
id: T[P]
): Promise<T | null>;

export async function first<T extends object, P extends keyof T>(
table: Table<T, P>,
queryOrId?: Query<T, P>|T[P]
): Promise<T | null> {
return middleware<T, P, "first">(
table,
{ action: "first", params: [table, query] },
{ action: "first", params: [table, queryOrId] },
(table, query) => internalFirst(table, query)
);
}

export async function internalFirst<T extends object, P extends keyof T>(
table: Table<T, P>,
query?: Query<T, P>
queryOrId?: Query<T, P>|T[P]
): Promise<T | null> {
if (query === undefined) {
if (queryOrId === undefined) {
const btree = table[BlinkKey].storage.primary;
const minKey = btree.minKey();
let entity = minKey ? btree.get(minKey) ?? null : null;
entity = table[BlinkKey].db[BlinkKey].options.clone ? clone(entity) : entity;
return entity;
} else if(typeof queryOrId !== "object") {
let entity = table[BlinkKey].storage.primary.get(queryOrId as T[P] & OrdProps) ?? null;
entity = table[BlinkKey].db[BlinkKey].options.clone ? clone(entity) : entity;
return entity;
}

const res = get(table, query);
const res = get(table, queryOrId as Query<T, P>);
if (!res[0]) {
return null;
}
const entity = table[BlinkKey].db[BlinkKey].options.clone ? clone(res[0]) : res[0];
return entity;
return table[BlinkKey].db[BlinkKey].options.clone ? clone(res[0]) : res[0];
}
Loading

0 comments on commit e328aa0

Please sign in to comment.