@instroc/data
v2.12.0
Published
Data hooks for Instroc Cloud: useQuery, useMutation, realtime, file storage and billing hooks
Readme
@instroc/data
Read and write your Instroc app's tables from React: queries that always return an array, mutations that refresh the queries they affect, live queries, file uploads, subscriptions and invoices. Requests carry the signed-in user's session, so each table's access rules apply without extra code.
Install
Apps built on Instroc already have it. Instroc pins the version for you; you do not install this by hand. Outside an Instroc app, the package installs like any other:
npm install @instroc/dataQuick start
import { useMutation, useQuery } from "@instroc/data";
type Task = { id: string; title: string; done: boolean };
export function Tasks() {
const {
data: tasks,
loading,
error,
} = useQuery<Task>("tasks", { order: "created_at:desc" });
const { insert } = useMutation<Task>("tasks");
if (loading) return <p>Loading</p>;
if (error) return <p>{error.message}</p>;
return (
<ul>
{tasks.map((t) => (
<li key={t.id}>{t.title}</li>
))}
<li>
<button onClick={() => insert({ title: "New task", done: false })}>
Add
</button>
</li>
</ul>
);
}Text search
For a table that has search turned on, pass search to useQuery, useLazyQuery, useInfiniteQuery or useLiveQuery. Rows come back ranked, best match first, unless you pass order.
import { useState } from "react";
import { useQuery, useDebouncedValue } from "@instroc/data";
function Listings() {
const [text, setText] = useState("");
const search = useDebouncedValue(text); // settles 250 ms after the last key
const { data, snippets, building } = useQuery("listings", {
search: { query: search, highlight: true },
});
// ...
}searchis a string, or{ query, columns?, prefix?, highlight? }.prefixdefaults totrue(shofindsshoes); an empty string is no search.- With
highlight: true,snippetsmaps a row's primary key and then a column to an excerpt. The only markup in a snippet is<mark>; the rest is HTML-escaped, so it is safe to render as HTML. buildingistruewhile the table's search index is still being built; results cover the rows indexed so far.useInfiniteQuerystarts again from page one when the search changes and drops rows an earlier page already delivered.
Live messages and presence
Visitors can message each other, and see who is here, through a channel. The channel needs a rule that says who may send and whether it keeps presence; a channel without one relays nothing from browsers.
import { useChannel, useSend, usePresence } from "@instroc/data";
function Lobby({ id, name }: { id: string; name: string }) {
const channel = `lobby:${id}`;
const { send, live, lastError } = useSend(channel);
const { members, count } = usePresence(channel, { name });
useChannel(channel, (payload, meta) => {
// meta.from.key says who sent it; meta.at is the channel room's clock
});
// send({ wave: true }) // true once it is on the socket
// await send({ move }, { self: false }) // true once the room relayed it
}- At most once, nothing queued. A send while the socket is offline returns
false; a peer that is reconnecting misses the message. A message that must not be lost is a row. A send is at most 4 KB. - Presence needs send access. Appearing on a channel publishes a state other visitors read, so the rule's
sendmust beanyoneorauthenticated(owners and admins always pass). Watching needs onlyreceive. Passnullas the state to watch without appearing. A state is at most 512 bytes and 16 keys; a changed state goes out at most once a second and is announced again after a reconnect. - Nothing throws. Hooks return
live,lastError(not_allowed,payload_too_large,state_too_large,rate_limited,offline,timeout,room_unavailable,invalid,unsupported,paused), and the booleansunsupportedandpaused. A hub or app without live messages isunsupported; a paused app stayspauseduntil the page reloads after the owner resumes it. meis your presence key when you are signed in (u:<userId>); an anonymous visitor's key is the hub's and is not told to the browser.
Versioning
This package follows semantic versioning: a patch release fixes a bug without changing the API, a minor release only adds (new exports, new optional parameters), and anything that removes an export or changes its meaning, shape, default or observable behaviour waits for the next major release. An export on its way out is marked @deprecated with its replacement named, keeps working for at least one minor release and at least 90 days after the changelog announces it, and warns once per page load in development builds. It is removed only in the next major release.
Links
- Documentation: https://docs.instroc.com
- Changelog: CHANGELOG.md, also shipped inside the package at
node_modules/@instroc/data/CHANGELOG.md
