Skip to content

JavaScript SDK

@hornetcloud/hive-js, the JavaScript and TypeScript client for HornetHive: connecting, filters, inserts and upserts, updates and deletes, functions, SQL, schemas, types and errors.

Install and connect

@hornetcloud/hive-js is the JavaScript and TypeScript client for the REST API. It has no dependencies and uses the built-in fetch: Node 18 or later, the browser, or any runtime that has fetch. Every method returns a promise.

Give it apiUrl: 'https://api.hornethive.io', to which it adds /v1, and an API key. hive.from(databaseId) picks the database; the id is on its page in the dashboard. In the browser use a read key, from an origin you registered; keep write keys on your server.

npm install @hornetcloud/hive-js

import { createHiveClient } from '@hornetcloud/hive-js'

const hive = createHiveClient({ apiUrl: 'https://api.hornethive.io', apiKey: process.env.HIVE_KEY })
const db = hive.from(process.env.HIVE_DB)

Reading rows

db.from('table') starts a query. select(), the filters, order(), limit() and offset() chain, and get() runs it and returns the rows; getWithCount() returns { rows, count }, the count being every matching row, for paging. Without limit() you get the first 10; one call returns at most 1,000.

const books = await db.from('books').select('id, title, year').order('year', 'desc').limit(20).get()

const { rows, count } = await db.from('books').gte('year', 2000).limit(20).offset(40).getWithCount()

Filters

eq, neq, gt, gte, lt, lte, like and ilike (* or % is the wildcard), match and imatch (regular expressions), in(column, [values]), is(column, null | true | false), contains, containedBy and overlaps for arrays and ranges, and textSearch(column, query, { type, config }). Each call adds a condition, and all of them must match.

not(column, operator, value) negates one, filter(column, operator, value) takes any operator by name, and or('a.lt.18,b.gt.65') matches any of its conditions, written in the REST syntax. order() again sorts by another column; its second argument is 'asc', 'desc' or { ascending, nullsFirst }.

const orders = await db.from('orders')
  .in('status', ['paid', 'shipped'])
  .not('deleted_at', 'is', null)
  .or('total.gt.500,priority.is.true')
  .textSearch('note', 'refund', { type: 'websearch', config: 'english' })
  .order('created_at', { ascending: false })
  .get()

Inserting, upserting, updating, deleting

insert(row) returns the new row and insert([rows]) the new rows, in one statement. upsert(rows, { onConflict, ignoreDuplicates }) updates the rows whose key exists, or skips them, and inserts the rest; the key is the primary key unless onConflict names other unique columns.

update(values) and delete() act on every row matching the filters before them and need at least one, or they throw before sending anything. update returns the rows; delete returns { deleted }, or the rows with { returning: true }. All of these need a write key.

await db.from('books').insert([{ title: 'Dune', year: 1965 }, { title: 'Ubik', year: 1969 }])
await db.from('members').upsert({ email: '[email protected]', plan: 'pro' }, { onConflict: 'email' })
await db.from('orders').eq('status', 'pending').lt('created_at', '2026-10-01').update({ status: 'expired' })
const removed = await db.from('sessions').lt('expires_at', 'now').delete({ returning: true })

Functions, SQL and schemas

rpc('name', { args }) calls a Postgres function with named arguments. sql('SELECT …', [params]) runs one statement with $1, $2… and returns { columns, rows }. With a read key both run read-only. schema('shop') returns a client for another schema, and leaves the one you called it on as it was.

const top = await db.rpc('top_books', { n: 5 })
const { rows } = await db.sql('SELECT author, count(*) FROM books GROUP BY author HAVING count(*) > $1', [3])
const orders = await db.schema('shop').from('orders').get()

TypeScript

Pass the row type to get<Row>(), insert<Row>(), rpc<T>() and sql<Row>(). To generate those types from your tables, fetch the database's OpenAPI document with db.openapi() and run openapi-typescript on it.

import { writeFile } from 'node:fs/promises'
await writeFile('openapi.json', JSON.stringify(await db.openapi()))
// npx openapi-typescript openapi.json -o hive.d.ts

interface Book { id: number; title: string; year: number | null }
const books = await db.from('books').get<Book>()

Errors

A refused call throws HiveError, with the API's message, status (the HTTP status) and body (the API's answer, which has the Postgres code when the database refused it). Network failures throw what fetch throws.

import { HiveError } from '@hornetcloud/hive-js'

try {
  await db.from('members').insert({ email: '[email protected]' })
} catch (e) {
  if (e instanceof HiveError && e.status === 409) console.log('already a member')
  else throw e
}

HornetHive accounts

hive.auth signs in to a HornetHive account: signUp, signIn, refresh and signOut. It is for tools that manage your own HornetHive account, not a login system for your app's users. The token is kept in memory unless you pass storage: localStorage; getToken() reads it and setToken(token) sets one you already hold. The API key can be read and changed with hive.getApiKey() and hive.setApiKey(key).

Versions

1.4 added every filter but eq, or(), several order()s, getWithCount(), bulk insert(), upsert(), rpc(), schema() and openapi(), and apiUrl on api. hosts. With 1.3 and earlier, apiUrl is 'https://hornethive.io', which works until its sunset date (see the REST API); upgrade and change it.