The wire carried another vendor's namespace in its error codes (PGRST1xx) and
in the media type a caller sends to claim a single row. Both ends are ours, so
it said someone else's name for no reason a caller could act on.
Two vocabularies meet in a fault and the split is the point: a constraint the
STORE refused answers with the store's own SQLSTATE, because every SQL engine
already agrees on how to say that; a request the WIRE would not accept answers
with a word of ours — bad_query, bad_body, not_one_row, refused, not_found,
store_failed — because that is a fact about this API and nobody else gets to
name it. Cardinality is asked for with application/vnd.hanzo.object+json.
LLM.md described the product as a translation of somebody else's seven
services, mapping their names onto ours, which is both wrong (their auth is not
Hanzo IAM) and backwards. It says what Base is: one binary, one file, the
surfaces it serves, and rules enforced here in Go rather than pushed down.
plugins/zap/forward.go still called itself additive to four native message
types that plugin.go had already removed — one node, one way in, and the table
wire arrives over it like every other route.
The comments on this door carried another company's brand 72 times — their
product name, their client library, their console. This is our canonical
repo; the surface is ours and reads as ours.
What the prose says now is what the code DOES: a REST data wire with one
table per path and filters in the query string, answering a bare array with
the count in Content-Range. Every fact that made those comments worth having
survives — why a value binds rather than renders, why an unaligned offset is
refused, why neq is `<>` and not the null-safe form — because none of it
depended on whose wire it is.
The wire VALUES stay exactly as they are: PGRST116, PGRST100,
application/vnd.pgrst.object+json, SQLSTATE 23505. Those are not branding,
they are the contract — a client branches on them, and renaming one would
break every caller while changing nothing about who owns the surface.
Two halves of one gap.
The detection was SQLite's wording alone -- a literal match on "unique
constraint failed" -- so on Postgres a duplicate never normalized into a
field error and surfaced as a 500 instead of a 400. IsUniqueViolation reads
both engines now, including the bare SQLSTATE. Postgres names the INDEX
rather than the columns, so the field is recovered from the index name, which
Base builds out of the column; the match is on a whole underscore-delimited
token, because a substring would let a field named "e" claim every index
containing the letter. There is no Postgres in CI, so that half is proven
against its wording in a unit test.
On /rest/v1 a failure now renders PostgREST's {message, details, hint, code}
rather than Base's {data, message, status}, and a unique violation becomes
409 with code 23505. That is not cosmetic: handling a duplicate is ordinary
application logic, and every `error.code === '23505'` branch in a migrated
app is dead against a body with no code field. Tested against a real
violation of idx_unique_demo2_title, not a simulated one.
Base's own door is unchanged and a test says so -- the same duplicate still
answers validation_not_unique there. The PostgREST body belongs to the
PostgREST wire, not to the store.
Replace github.com/pocketbase/pocketbase with github.com/hanzoai/base
in 106 Go source files. Keep pocketbase/dbx and pocketbase/tygoja as-is
(upstream dependencies). Add compiled binary to .gitignore. Include
realtime DX design document inspired by Convex patterns.