Skip to content

Describe a collection. Get the whole data UI.

A collection is three small files. Its fields go in schema.json, how it looks in view.yaml, and where its rows live in source.yaml. The server answers for it over one JSON protocol, and the React data view draws it with search, filters, inline edits and a schema editor.

Get startedOpen the live examplenpm install @jsudc/react
collections/repos/The public GitHub repositories of digital-cube, a collection of the live example (abridged)

schema.json the fields

{
"version": "v1",
"fields": {
"name": { "type": "string" },
"description": { "type": "text" },
"language": { "type": "string" },
"stargazers_count": { "type": "integer", "min": 0 },
"pushed_at": { "type": "datetime" },
"topics": { "type": "string_array" },
"archived": { "type": "bool" }
}
}

view.yaml how it is shown

default_renderer: cards
available_renderers: [cards, table, chart]
search:
fields: [name, description]
filters:
- { field: language, kind: select }
- { field: archived, kind: bool }
columns:
- { field: name, pin: left, width: 220 }
- { field: language, render: badge }
chart_options:
type: pie
category_field: name
value_field: stargazers_count

source.yaml where the rows live

kind: http_api
options:
base_url: https://api.github.com
list_endpoint: /users/digital-cube/repos
id_field: name
field_map:
owner_avatar: owner.avatar_url
cache_ttl_ms: 300000
# or postgres_jsonb, memory, filesystem,
# yaml_menu; with no source.yaml, a
# Postgres table named like the directory

Seven renderers and a schema editor, from one line of YAML

Real screenshots of the live example. Each view lists the renderers it offers in available_renderers; the data view draws one tab per name.

  1. A table of companies with pinned columns, filter chips and a search boxA table of companies with pinned columns, filter chips and a search box
    Tablecompanies/view.yaml
  2. GitHub repositories as cards with their language, stars and last pushGitHub repositories as cards with their language, stars and last push
    Cardsrepos/view.yaml
  3. Tickets on a board in Ready, In progress, Backlog and Review columnsTickets on a board in Ready, In progress, Backlog and Review columns
    Kanbantickets/view.yaml
  4. A work-week calendar of clinic appointmentsA work-week calendar of clinic appointments
    Calendarappointments/view.yaml
  5. An organisation chart as an expandable tree of departments and peopleAn organisation chart as an expandable tree of departments and people
    Treeorg/view.yaml
  6. Live flights over the Balkans as plane markers on a mapLive flights over the Balkans as plane markers on a map
    Mapflights/view.yaml
  7. A bar chart of earthquake magnitudes over the last seven daysA bar chart of earthquake magnitudes over the last seven days
    Chartquakes/view.yaml
  8. The Add column dialog of the schema editor over the companies tableThe Add column dialog of the schema editor over the companies table
    Schema editorcompanies/view.yaml

One protocol, both sides

@jsudc/core is the contract: every request and every answer is an envelope checked by the same Zod schemas. The server imports it to check what arrives; the client imports it to check what comes back. Replace either side with your own and the other does not notice.

Every action is POST /udc/<action>: describe, list,read, mutate, and the schema edits that add, change or retire a field at runtime. The answer is always HTTP 200; its kind says ok or err.

@jsudc/react asks

POST /udc/list
{
"collection": "repos",
"filter": { "field": "language", "op": "eq", "value": "TypeScript" },
"sort": [{ "field": "stargazers_count", "dir": "desc" }],
"page": { "limit": 60 }
}

@jsudc/core validates both directions with the same schemas

@jsudc/server answers

{
"kind": "udc.list.ok",
"id": "",
"payload": { "rows": [ … ], "next_cursor": null, "total": 4 }
}

Start where you are

server.ts
import { createDispatcher, loadRegistry, makeSource, udcPlugin } from "@jsudc/server";
import Fastify from "fastify";
const registry = loadRegistry("./collections", (asset, registry) =>
makeSource(asset, { registry, env: process.env }),
);
const app = Fastify({ logger: true });
const dispatcher = createDispatcher(registry, { log: app.log });
await app.register(udcPlugin, { dispatcher, registry, swagger: false });
await app.listen({ port: 5100 });
  1. npm install @jsudc/server @jsudc/core fastify zod
  2. Next to it, a collections/books/ directory with a schema.json, aview.yaml and a source.yaml of kind: memory.
  3. node server.ts, then ask it for the books:curl localhost:5100/udc/list -H 'content-type: application/json' -d '{"collection":"books"}'
The three quick starts in full, on Get started

Three packages, one version line

  • @jsudc/core

    The protocol: envelopes, the filter language and its evaluator, the schema, view and source types, row validation, and the transport client.

    @jsudc/core on npm
  • @jsudc/server

    Dispatcher, registry, five sources, the schema store and its revisions, seeding, and a Fastify plugin with the OpenAPI of every action.

    @jsudc/server on npm
  • @jsudc/react

    The data view, seven renderers, the schema editor, hooks over TanStack Query, a headless layer, and a stylesheet you restyle with CSS variables.

    @jsudc/react on npm

MIT licence, as is the site. One semver line: the protocol has to match on both sides.