0.12.1 • Published 9 months ago

@canvas-js/modeldb v0.12.1

Weekly downloads
-
License
-
Repository
-
Last release
9 months ago

@canvas-js/modeldb

ModelDB is a minimalist cross-platform relational database wrapper. It currently supports the following backends:

  • IndexedDB (browser)
  • Sqlite + Wasm (browser) with either an OPFS store or transient in-memory storage
  • PostgreSQL (NodeJS)
  • Native Sqlite (NodeJS)

Table of Contents

Usage

Initialization

Import ModelDB from either @canvas-js/modeldb-idb (browser) or @canvas-js/modeldb-sqlite (NodeJS).

import { ModelDB } from "@canvas-js/modeldb-sqlite"

const db = await ModelDB.init({
  path: "/path/to/db.sqlite", // set `path: null` for an in-memory database
  models: { ... }
})
import { ModelDB } from "@canvas-js/modeldb-idb"

const db = await ModelDB.init({
  name: "my-database-name", // used as the IndexedDB database name
  models: { ... }
})

Schemas

Databases are configured with a models schema, provided as a JSON DSL. Every model has a mandatory string primary key and supports nullable and non-nullable integer, float, string and bytes datatypes. It also supports a non-nullable json datatype.

const db = await ModelDB.init({
  models: {
    user: {
      // exactly one "primary" property is required
      id: "primary",
      // properties are non-null by default
      name: "string",
      // declare nullable properties using `?`
      birthday: "string?",
      // json data is also supported
      metadata: "json",
    },
  },
})

await db.set("user", { id: "xxx", name: "John", birthday: "1990-01-01", metadata: {} })
await db.set("user", { id: "xxx", name: "John Doe", birthday: "1990-01-01", metadata: { home: "New York" } })
await db.get("user", "xxx") // { id: "xxx", name: "John Doe", birthday: "1990-01-01", metadata: { home: "New York" } }

Reference properties (@user with string values), nullable reference properties (@user? with string | null values), and relation properties (@user[] with string[] values) are also supported, although the foreign key constraint is not enforced.

const db = await ModelDB.init({
  models: {
    user: {
      user_id: "primary",
      name: "string",
    },
    room: {
      room_id: "primary",
      members: "@user[]",
    },
    message: {
      message_id: "primary",
      user: "@user",
      content: "string",
      timestamp: "integer",
    },
  },
})

Setting and deleting records

Mutate the database using either the set and delete methods, or the lower-level apply method to batch operations in an atomic transaction:

await db.set("user", { user_id: "xxx", name: "John Doe" })
await db.set("user", { user_id: "yyy", name: "Jane Doe" })
await db.delete("user", "xxx")

await db.apply([
  { model: "user", operation: "set", value: { user_id: "xxx", name: "John Doe" } },
  { model: "user", operation: "set", value: { user_id: "yyy", name: "Jane Doe" } },
  { model: "user", operation: "delete", key: "xxx" },
])

Queries

Access data using the query method, or use the get to retrieve records by primary key.

await db.set("user", { user_id: "a", name: "Alice" })
await db.set("user", { user_id: "b", name: "Bob" })
await db.set("user", { user_id: "c", name: "Carol" })

await db.get("user", "a") // { user_id: "a", name: "Alice" }
await db.get("user", "d") // null

await db.query("user", { where: { user_id: { gte: "b" } } })
// [
//   { user_id: "b", name: "Bob" },
//   { user_id: "c", name: "Carol" },
// ]

Queries support select, where, orderBy, and limit expressions. where conditions can have equality, inequality, and range terms.

export type QueryParams = {
  select?: Record<string, boolean>
  where?: WhereCondition
  orderBy?: Record<string, "asc" | "desc">
  limit?: number
  offset?: number
}

export type WhereCondition = Record<string, PropertyValue | NotExpression | RangeExpression>
export type NotExpression = {
  neq: PropertyValue
}

export type RangeExpression = {
  gt?: PrimitiveValue
  gte?: PrimitiveValue
  lt?: PrimitiveValue
  lte?: PrimitiveValue
}

Indexes

By default, queries translate into filters applied to a full table scan. You can create indexes using the special $indexes: string[] property:

const db = await ModelDB.init({
  models: {
    ...
    message: {
      message_id: "primary",
      user: "@user",
      content: "string",
      timestamp: "integer",
      $indexes: ["timestamp"]
    },
  },
})

// this will use the `timestamp` index to avoid a full table scan
const recentMessages = await db.query("message", { orderBy: { timestamp: "desc" }, limit: 10 })

Multi-property index support will be added soon.

Name restrictions

Model names and property names can contain [a-zA-Z0-9$:_\-\.].

Testing

ModelDB has a test suite that that uses Ava as its test runner and Puppeteer for browser testing. The SQLite + Wasm implementations make use of Web APIs and are tested in the browser. The IndexedDB implementation is tested in NodeJS using a mock IndexedDB implementation.

npm run test --workspace=@canvas-js/modeldb

License

MIT © Canvas Technologies, Inc.

0.13.0-next.16

9 months ago

0.13.0-next.15

9 months ago

0.13.0-next.14

9 months ago

0.13.0-next.13

9 months ago

0.12.1

9 months ago

0.13.0-next.12

9 months ago

0.13.0-next.11

9 months ago

0.13.0-next.10

9 months ago

0.13.0-next.9

9 months ago

0.13.0-next.8

9 months ago

0.13.0-next.1

10 months ago

0.13.0-next.3

10 months ago

0.13.0-next.2

10 months ago

0.13.0-next.5

10 months ago

0.13.0-next.4

10 months ago

0.13.0-next.7

9 months ago

0.13.0-next.6

10 months ago

0.11.0

10 months ago

0.12.0

10 months ago

0.10.10

11 months ago

0.10.9

11 months ago

0.10.1

1 year ago

0.10.2

1 year ago

0.10.3

1 year ago

0.10.4

12 months ago

0.10.5

12 months ago

0.10.6

11 months ago

0.10.7

11 months ago

0.10.8

11 months ago

0.10.0

1 year ago

0.10.0-alpha.1

1 year ago

0.8.29

1 year ago

0.10.0-beta.2

1 year ago

0.10.0-beta.3

1 year ago

0.10.0-beta.1

1 year ago

0.10.0-beta.4

1 year ago

0.8.28

1 year ago

0.8.27-patch.21

1 year ago

0.8.27-patch.14

1 year ago

0.8.27-patch.13

1 year ago

0.8.27-patch.16

1 year ago

0.8.27-patch.15

1 year ago

0.8.27-patch.10

1 year ago

0.8.27-patch.6

1 year ago

0.8.27-patch.7

1 year ago

0.8.27-patch.12

1 year ago

0.8.27-patch.8

1 year ago

0.8.27-patch.11

1 year ago

0.8.27-patch.9

1 year ago

0.8.27-patch.20

1 year ago

0.9.1

1 year ago

0.8.27-patch.18

1 year ago

0.8.27-patch.17

1 year ago

0.8.27-patch.19

1 year ago

0.8.27-patch.2

1 year ago

0.8.27-patch.3

1 year ago

0.8.27-patch.4

1 year ago

0.8.27-patch.5

1 year ago

0.8.27-patch.1

1 year ago

0.9.0

1 year ago

0.9.0-next.1

1 year ago

0.8.26-alpha.4

2 years ago

0.8.26-alpha.3

2 years ago

0.8.26-alpha.2

2 years ago

0.8.26

2 years ago

0.8.26-alpha.1

2 years ago

0.8.25

2 years ago

0.8.24

2 years ago

0.8.23

2 years ago

0.8.22

2 years ago

0.8.21

2 years ago

0.8.20

2 years ago

0.8.19

2 years ago

0.8.18

2 years ago

0.8.17

2 years ago

0.8.16

2 years ago

0.8.15

2 years ago

0.8.14-alpha.1

2 years ago

0.8.14

2 years ago

0.8.13

2 years ago

0.8.12

2 years ago

0.8.11

2 years ago

0.8.10

2 years ago

0.8.9

2 years ago

0.8.8

2 years ago

0.8.5

2 years ago

0.8.4

2 years ago

0.8.7

2 years ago

0.8.6

2 years ago

0.8.2-alpha.1

2 years ago

0.8.2-patch.1

2 years ago

0.8.3

2 years ago

0.8.2

2 years ago

0.7.2-alpha.1

2 years ago

0.7.2-alpha.2

2 years ago

0.7.2-alpha.3

2 years ago

0.7.2-alpha.4

2 years ago

0.7.2-alpha.5

2 years ago

0.7.2-alpha.6

2 years ago

0.7.2-alpha.7

2 years ago

0.8.1

2 years ago

0.7.2

2 years ago

0.8.0

2 years ago

0.7.1

2 years ago

0.7.3

2 years ago

0.7.0

2 years ago

0.6.1

2 years ago

0.6.0

2 years ago

0.5.1

2 years ago

0.5.0

2 years ago

0.6.0-alpha7

2 years ago

0.6.0-alpha6

2 years ago

0.6.0-alpha5

2 years ago

0.6.0-alpha4

2 years ago

0.6.0-alpha3

2 years ago

0.6.0-alpha2

2 years ago