npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@useorbis/db-sdk

v0.0.60-alpha

Published

Orbis' Typescript SDK for building open-data experiences.

Downloads

901

Readme

OrbisDB SDK

OrbisDB SDK is a client-side complement to OrbisDB - a relational database designed for onchain builders.

Our SDK is offering an ORM-like approach and abstracts away complexity from the development process by offering a very well-known framework (INSERT, UPDATE, SELECT). Start your project easily with a Postgres database, authentication, and signatures.

Full SDK documentation is accessible here: Getting started

[!IMPORTANT]
🚨 OrbisDB SDK also integrates with a dashboard for easier access and management. Get an early access of our hosted service provided by Orbis: OrbisDB Studio.

Installation

The SDK is available publicly on NPM. You can install it using your preferred package manager.

npm install @useorbis/db-sdk

[!WARNING]
This SDK is a work in progress and is being developed in parallel with the OrbisDB node.
Things will change, however, the core components have been ported over from the Orbis Social SDK and should have a stable-enough interface.
Find any notable or breaking changes in CHANGELOG.md.

Description

OrbisDB SDK is a client-side complement to OrbisDB - a decentralized database built on top of Ceramic.
It inherits the DX of our TS SDK which enables simple user authentication while providing new (more generic) methods to manipulate data.

How does this compare to @orbisclub/sdk?

@orbisclub/sdk is our Typescript SDK for Orbis Social from which OrbisDB was born.
It is not compatible with the new SDK nor is there any feature parity between the two.

Orbis Social will be migrated to our new OrbisDB infrastructure once we reach stable prod.

The new SDK does not come with opinionated primitives (ie. Posts, Groups, Messages), nor does it have built-in encryption.
OrbisDB SDK is aimed at the flexibility of data management for social, but also many other use-cases.

API Reference

Initialize the SDK

Initializing the SDK requires 2 gateways - one for your Ceramic node and another one for your OrbisDB.

For Dedicated Instances

Dedicated instances are OrbisDB instances that you operate yourself or through a node provider exclusively for your usage.

import { OrbisDB } from "@useorbis/db-sdk"

const db = new OrbisDB({
    ceramic: {
        gateway: "YOUR_CERAMIC_NODE_URL"
    },
    nodes: [
        {
            gateway: "YOUR_ORBIS_NODE_URL"
        }
    ]
})

For Shared Instances

Shared instances are publicly accessible OrbisDB instances managed by a third party, allowing you to use them without setting up your own infrastructure. When using a shared instance, you must specify the env parameter to select the desired environment within the shared instance.

import { OrbisDB } from "@useorbis/db-sdk"

const db = new OrbisDB({
    ceramic: {
        gateway: "YOUR_CERAMIC_NODE_URL"
    },
    nodes: [
        {
            gateway: "YOUR_ORBIS_NODE_URL",
            env: "YOUR_ENVIRONMENT_ID"
        }
    ]
})

Why is nodes argument an array?

We have plans to support connecting to multiple OrbisDB instances for fallback, load-balancing as well as automatic query rerouting. Currently, only the first node will be used and no node rotation will happen.

Ceramic gateways might be inferred from the OrbisDB node's metadata in the future, however, we want to make sure exposing the Ceramic node is optional to ensure privacy and security of your infrastructure.

Handling errors

try / catch

Standard try/catch practices apply.

let document
try{
    document = await orbis.insert(...).run()
} catch(error){
    console.log("Error", error)
}

console.log("Result", document)

catchError

This is a utility method provided by Orbis, originally implemented in Radash. We've modified the call signature to make it more convenient for our use case.

import { catchError } from "@useorbis/db-sdk/util"

const [document, error] = await catchError(
    () => orbis.insert(...).run()
)

if(error){
    console.warn("Error", error)
}

console.log("Result", document)

User authentication

Authentication is handled by OrbisAuthenticators which generate the DID session in did:pkh (OrbisEVMAuth, OrbisSolanaAuth, OrbisTezosAuth) and did:key (OrbisKeyDidAuth) formats.

By default, sessions are persisted in localStorage and are valid for up to 3 months.
In order to bypass this behavior, pass { saveSession: false } to the connectUser method.

EVM (did:pkh)

import { OrbisDB } from "@useorbis/db-sdk"
import { OrbisEVMAuth } from "@useorbis/db-sdk/auth"

// Browser provider
const provider = window.ethereum

// Ethers provider
const provider = new Wallet(...)

// Orbis Authenticator
const auth = new OrbisEVMAuth(provider)

// Authenticate the user and persist the session in localStorage
const authResult: OrbisConnectResult = await orbis.connectUser({ auth })

// Authenticate, but don't persist the session in localStorage
const authResult: OrbisConnectResult = await orbis.connectUser({ auth, saveSession: false })

// Log the result
console.log({ authResult })

KeyDid (did:key)

import { OrbisDB } from "@useorbis/db-sdk"
import { OrbisKeyDidAuth } from "@useorbis/db-sdk/auth"

// Generate the seed
const seed = await OrbisKeyDidAuth.generateSeed()

// Initiate the authneticator using the generated (or persisted) seed
const auth = await OrbisKeyDidAuth.fromSeed(seed)

// Authenticate the user and persist the session in localStorage
const authResult: OrbisConnectResult = await orbis.connectUser({ auth })

// Authenticate, but don't persist the session in localStorage
const authResult: OrbisConnectResult = await orbis.connectUser({ auth, saveSession: false })

// Log the result
console.log({ authResult })

Check if a user is connected

This method always returns true/false.

// Check if any user is connected
const connected = await orbis.isUserConnected()

// Check if a user with the specified wallet address is connected
const connected = await orbis.isUserConnected("0x00...")

Get the currently connected user

This method either returns the currently connected user (OrbisConnectResult) or false.

// Get the currently connected user
const currentUser = await orbis.getConnectedUser()
if(!currentUser){
  // Notify the user or reconnect
  throw "There is no active user session."
}

console.log({ currentUser })

Managing data

OrbisDB SDK makes creating, updating and reading data simple and consistent.
We took inspiration from Web2 SDKs from solutions like Supabase/PostgREST, Knex, MongoDB, etc.

Operations are divided into insert, update and select.

All methods allow you to use friendly model names if you have them set up in the connected OrbisDB node.
Contexts are also a Ceramic-native feature and are exposed in all data management methods.

Method chaining is being used to construct queries with all methods and a .run() method executes the chain.

DELETE statement-equivalent is WIP as we're looking to solve this at the core protocol layer.

INSERT

Inserts execute Ceramic MID writes. This has been abstracted using a query-builder interface to simplify execution and allow optimizations of the underlying calls in the future, without modifying the original interface.

Insert a single row
const insertStatement = await orbis
    .insert("MODEL_ID" | "TABLE_NAME")
    .value(
        {
            column: value,
            column2: value2,
        }
    )
    // optionally, you can scope this insert to a specific context
    .context("CONTEXT_ID")

// Perform local JSON Schema validation before running the query
const validation = await insertStatement.validate()
if(!validation.valid){
    throw "Error during validation: " + validation.error
}

const [result, error] = await catchError(() => insertStatement.run())

// All runs of a statement are stored within the statement, in case you want to reuse the same statmenet
console.log(insertStatement.runs)
Insert multiple rows
const insertStatement = await orbis
    .insertBulk("MODEL_ID" | "TABLE_NAME")
    .values(
        {
            column: value,
            column2: value2,
        },
        {
            column: value,
            column2: value2,
        },
        ...
    )
    .value(
        {
            column: value,
            column2: value2,
        }
    )

// Perform local JSON Schema validation before running the query
const validation = await insertStatement.validate()
if(!validation.valid){
    console.error("Errors during validation", validation.errors)
    throw "Errors during validation"
}

// bulkStatements DO NOT throw in case a run partially fails
// As each insert is handled as an isolated case, you may have partial-success
const { success, errors } = await insertStatement.run()

if(errors.length){
    console.error("Errors occurred during execution", errors)
}

console.log(success)

// All runs of a statement are stored within the statement, in case you want to reuse the same statmenet
console.log(insertStatement.runs)

UPDATE

Updates can replace the entire row or perform shallow merging with existing data.

Replace a row
// This will replace the provided row with provided values
const updateStatement = await orbis
    .update("DOCUMENT_ID")
    .replace(
        {
            column: value,
            column2: value2,
        }
    )

const [result, error] = await catchError(() => updateStatement.run())

// All runs of a statement are stored within the statement, in case you want to reuse the same statmenet
console.log(updateStatement.runs)
Update a row partially
// This will perform a shallow merge before updating the document 
// { ...oldContent, ...newContent }
const updateStatement = await orbis
    .update("DOCUMENT_ID")
    .set(
        {
            column: value,
        }
    )

const [result, error] = await catchError(() => updateStatement.run())

// All runs of a statement are stored within the statement, in case you want to reuse the same statmenet
console.log(updateStatement.runs)

SELECT

Querying data is done using a custom-built query builder.
The interface has been kept simple and familiar, as it mimics popular QB solutions such as Knex.js.

Query is being sent to the OrbisDB node in JSON format where it gets parsed and executed.

You can preview the final query by using .build().

Why a custom query builder?

Our initial POCs were using existing QB solutions such as Knex.js and waterfall/JSON SQL builders.
However, these libraries are built with backend environments in mind and made our query interface more complex, as we aren't executing queries against a DB engine directly.

Building a custom QB gave us the option to separate query building, serializing and final SQL outputs.
It also allows us to expose custom options such as .context() and .contexts(), further abstracting the underlying data model and making future optimizations and changes in the node easier.

We also did not require multiple engine support and we kept our dependencies to the minimum.

We will keep expanding QB functionality with simple joins, new operators and other features that will make interacting with OrbisDB simpler and more efficient.

Building a SELECT query
const selectStatement = await orbis
    // SELECT column1, column2
    // if no columns are passed, all columns (*) will be returned
    .select("column1", "column2")
    // FROM model_id | table_name | view_id
    .from("MODEL_ID" | "TABLE_NAME" | "VIEW_ID")
    // WHERE ...conditions
    // unless specified, all conditions will be treated as logical AND
    .where(
        {
            // column = "value"
            column: "value",
            // columns2 in (value1, value2)
            column2 = ["value1", "value2"]
        }
    )
    // you can scope this query to a specific context
    .context("CONTEXT_ID")
    // or multiple contexts
    .contexts("CONTEXT_ID", "CONTEXT_ID", ...)
    // ORDER BY
    .orderBy(
        // orderBy a single column
        ["column", "asc" | "desc"],
        // or multiple columns by providing additional
        // column arrays
        ["column2", "asc" | "desc"]
    )
    // LIMIT
    .limit(number)
    // OFFSET
    .offset(number)

const query = selectStatement.build()
console.log("Query that will be run", query)

const [result, error] = await catchError(() => selectStatement.run())
if(error){
    throw error
}

// columns: Array<string>
// rows: Array<T | Record<string, any>>
const { columns, rows } = result

console.log({ columns, rows })
Using operators

Operator helpers are exposed to provide query flexibility.
These include logical, comparison and aggregation operators.

You can find the entire list of operators and resulting queries here.

import { count, sum, contains, ilike, or, gte } from "@useorbis/db-sdk/operators"

const selectStatement = await orbis
    // if no columns are passed, all columns (*) will be returned
    .select(
        "column1", 
        "column2", 
        sum("column3"), 
        count("column4", "count_column4")
    )
    .from("MODEL_ID" | "TABLE_NAME" | "VIEW_ID")
    // unless specified, all conditions will be treated as logical AND
    .where(
        {
            // column = "value"
            column: "value",
            // columns2 in ("value1", "value2")
            column2 = ["value1", "value2"],
            // column3 ILIKE "%value"
            column3: ilike("%value"),
            // column4 LIKE "%value%"
            column4: contains("value"),
            // column5 >= 5
            column5: gte(5),
            // column = "value" OR column2 = "value2"
            ...or(
                {
                    column: "value"
                },
                {
                    column2: "value2"
                }
            )
        }
    )

const query = selectStatement.build()
console.log("Query that will be run", query)

const [result, error] = await catchError(() => selectStatement.run())
if(error){
    throw error
}

// columns: Array<string>
// rows: Array<T | Record<string, any>>
const { columns, rows } = result

console.log({ columns, rows })