yutabaseThe optional Node.js client and CLI for the YUTABASE PostgreSQL semantic-profile candidate. It keeps ordinary PostgreSQL accessible while adding full logical refs, governed word relations, explicit claim kinds, UUIDv7 generation, and the small YOUSPEAK compiler.
This source is 0.1.0-candidate.3; it is not a finished standard. Package and
database versions are intentionally separate: this SDK expects the exact
database identity YUTABASE/postgres@0.1.0-candidate.1 revision 5. The exact
candidate.3 archive is public as byte-identical GitHub and npm mirrors. npm
next selects candidate.3; latest intentionally remains candidate.1, so pin
the exact version.
git clone https://github.com/cambridgetcg/yutabase.git
cd yutabase/packages/sdk-ts
bun install --frozen-lockfile
bun run src/cli.ts init --conn postgresql://localhost/example
bun run src/cli.ts hello --conn postgresql://localhost/example
Use a fresh disposable database for this first run. init mutates the target:
it may upgrade a recognized original/revision-4 install, normalize candidate
objects and ACLs, and create four fixed cluster-wide capability roles. The
narrow yu_appender role has the thread-append capability but cannot sever or
mutate threads. hello is the read-only inspection command. Back up and
rehearse any real upgrade.
Install the exact npm mirror:
npm install --save-exact yutabase@0.1.0-candidate.3
npm exec -- yuta ...
Or install the byte-identical GitHub Release artifact:
npm install https://github.com/cambridgetcg/yutabase/releases/download/v0.1.0-candidate.3/yutabase-0.1.0-candidate.3.tgz
The verified release receipt links the protected publication run and provenance attestation. Do not select the older package candidate merely to make an install command succeed against a revision-5 database.
init requires an operator able to create the candidate extension and schemas.
For the cluster-wide role hierarchy it needs a superuser, or CREATEROLE plus
sufficient ADMIN OPTION/role-management authority on the canonical roles,
especially yu_reader; otherwise the transaction fails. It is not an
application startup migration. hello checks the installed identity and
returns the current lexicon and registered decks. Human-readable output shows
every logical/book -> physical.schema mapping and any non-default claim-column
mapping; yuta hello --json returns the same complete structure for agents.
yuta deck list [--json] is the compact mapping-only view. These commands do
not prove database-owner integrity or report an entire security posture.
yuta words --export writes a self-identifying, non-normative lexicon snapshot
to stdout. Add --output snapshot.md to create a file; an existing path is
refused unless --force is also present. File output is committed atomically
and refuses symbolic links and non-regular paths. The exported identity and
observation time help a later reader understand where the words came from, but
are not a currentness or conformance receipt.
The code example below assumes work/tasks and git/commits are registered
decks and produced has been added to the lexicon. They are illustrative
application names, not objects created by the starter migration. See the
existing-table integration
for the complete setup shape.
import { Yuta, type ClaimKind } from "yutabase";
const how: ClaimKind = "computed";
const yuta = new Yuta({
connectionString: process.env.DATABASE_URL,
claimant: "agent:builder/session-42",
});
try {
await yuta.assertCandidateBinding();
const task = "work/tasks/01900000-0000-7000-8000-000000000001";
const commit = "git/commits/01900000-0000-7000-8000-000000000002";
await yuta.thread(task, "produced", commit, how, {
note: 'build "release" output',
src: ["urn:example:build:42"],
});
const related = await yuta.traverse(task, "->", "produced");
const sameRowsInSql = await yuta.sqlTag`
SELECT from_ref, to_ref, gloss, word_version, how, src
FROM via.produced
WHERE from_ref = ${task}
`;
console.log({ related, sameRowsInSql });
} finally {
await yuta.close();
}
card, traverse, thread, and sever compile their arguments directly to
parameterized SQL. Quotes and whitespace inside notes or individual src
locators stay values; they are not reparsed as YOUSPEAK syntax. witnessed,
live, cached, computed, and declared are exported as the ClaimKind
union and CLAIM_KINDS constant. cached and computed require at least one
source locator. Whenever src is present, every entry must be nonblank and the
PostgreSQL value must be a canonical one-dimensional, one-based array. This
keeps the SDK value a portable string[] instead of a driver-specific nested
shape. The SDK mirrors the database’s exact portable blank set: ASCII TAB,
LF, VT, FF, CR, and SPACE. It also rejects NUL, which JavaScript can represent
but PostgreSQL text cannot.
YOUSPEAK is useful for a compact human/agent-facing surface:
const result = await yuta.query(
"work/tasks/01900000-0000-7000-8000-000000000001 -> produced",
);
console.log(yuta.explain("cards work/tasks newest 20"));
newest orders by the deck’s mapped at claim descending and uses the UUID
only as a deterministic tie-breaker. It does not infer authoritative time from
UUID bits. A card list defaults to 100 rows; newest N accepts 1 through 1000.
Traversal is capped at two hops and returns a path entry for every edge,
including its pinned word version, reading, and claim metadata. Result freshness
counts each traversal edge once instead of counting only its repeated final
edge; malformed cached timestamps remain unknown rather than becoming a numeric
age. Text forms deliberately accept a smaller grammar than JavaScript values;
programmatic callers should prefer the structured helpers above.
explain(text) is a pure logical preview: it does not connect, resolve a
registry mapping, check permissions, or execute the displayed operation.
Logical labels are shown as quoted pseudo-relations even when they are longer
than PostgreSQL physical identifiers. Write previews retain
'__CLAIMANT__' as an explicit unbound marker. The explain "<form>"
YOUSPEAK command returns that same complete preview as a text value.
The exported low-level compile(text) returns parameterized intermediate SQL.
For card forms, its fixed logical-relation sentinel and deckTarget metadata
must be resolved together; the intermediate SQL is not directly executable.
Use Yuta.query(), Yuta.card(), or Yuta.explain() unless you are
implementing another registry-aware adapter.
The SQL escape hatch is always available. sqlTag parameterizes interpolated
values. exec and execTransaction accept trusted operator SQL and must not be
fed untrusted text. These raw helpers intentionally bypass candidate-binding
checks; their caller owns compatibility, authorization, and SQL safety.
yuta --conn "$DATABASE_URL" --by "human:alice" \
deck annex public.tasks as work/tasks \
--id task_id --at observed_at --by claimant \
--how claim_kind --src sources
The physical table must already have a non-null uniquely indexed UUID identity
and compatible claim columns. Rows do not move. Registry mutation atomically
maintains the exact guard pair for deletion/mapped-UUID changes and TRUNCATE.
The caller needs registry write access (normally through yu_lexicographer),
must own the physical table or hold equivalent PostgreSQL authority for the
security-invoker trigger DDL, and still needs whatever ordinary table access
its later reads/writes require. Annexing grants no application-table
privileges and does not certify the truth of legacy rows.
| API | Purpose |
|---|---|
new Yuta(options) |
open a pooled PostgreSQL client; connection string or macOS Keychain fallback |
assertCandidateBinding() |
fail closed unless the exact supported database shape is visible |
hello() |
read installed identity, vocabulary, and deck mappings |
card(ref) |
fetch one logical card through the registry |
traverse(ref, direction, word) |
follow one governed relation |
thread(...) / sever(...) |
create or end a relation with an explicit claim |
query(text) / explain(text) |
execute YOUSPEAK or render its pure logical preview |
sqlTag / exec / execTransaction |
use PostgreSQL directly |
uuid() |
generate a UUIDv7; readers still treat the full UUID as identity |
close() |
end the pool |
The package also exports the pure ref parser/formatter, UUID helpers, YOUSPEAK compiler, candidate install planner, and result types.
YOUSPEAK and the SDK do not provide authentication, permissions, row-level security, synchronization, transport, signatures, backups, or proof that a stored claim is true. PostgreSQL and the surrounding application remain responsible for those operational boundaries.
Fresh initialization commits the original 0001+0002 base, then applies
0004 and 0005, each in its own fresh transaction. yuta hello, install
planning, and semantic client operations verify observable catalog shape as
well as the metadata row. Constructing Yuta only creates its pool; it does
not perform that verification until an operation runs. A successful check is
cached for that client instance; a failed check is retried on the next
operation so an explicit repair or completed migration can recover without
restarting the process. This is a compatibility check at observation time, not
continuous monitoring or tamper resistance against a database owner or
superuser.
A semantic operation resolves any logical card mappings and performs its
physical reads in one READ COMMITTED transaction. Shared row locks keep those
mapping rows stable until the operation finishes. This prevents one SDK
operation from straddling a concurrent registry remap; it does not make a
sequence of separate SDK calls one transaction.
Development uses Bun 1.3.5:
bun install --frozen-lockfile
bun run ci
YUTABASE_INTEGRATION_TEST=1 \
DATABASE_URL=postgresql://localhost/disposable_test \
bun run test:integration
The integration suite is destructive, single-use fixture work. It skips
without DATABASE_URL, refuses to run without the second explicit opt-in, and
leaves an sdk_integration namespace so an accidental rerun fails instead of
silently reusing old state. Recreate the disposable database for every run.