cloud carried a hand-vendored byte-copy of goja/bundle.js because this repo is a TS app with no Go module, so nothing checked what it embedded. The copy had already drifted: cloud's carried the link deny-list and this one did not. A std-lib-only embed module — the same shape github.com/hanzoai/sign has — makes the bundle a dependency cloud requires at a version and records in go.sum, so the three folds (sign, captable, dataroom) are all pinned the same way and there is no second copy to drift. The deny-list the copy was ahead by lands here with it: an address on a link's deny list is refused even when the allow list would admit it, checked first so deny cannot be bypassed.
3.6 KiB
dataroom — goja bundle (HIP-0106 fold, task #101)
This directory holds the canonical, self-contained business-logic bundle for
Hanzo Dataroom. It is the ESM-free port of the Next.js/Prisma API handlers,
authored to run verbatim inside the dop251/goja engine embedded in the unified
hanzoai/cloud binary — the same in-process pattern @hanzo/plans and
@hanzo/pricing use.
The standalone Next.js app + Postgres is retired: cloud serves the whole
dataroom surface (/v1/dataroom/*) itself, backed by Hanzo Base/SQLite
(one file per org) and the cloud object-storage seam. No Postgres, no Next.js.
It runs on the reusable clients/goja binding — the RW-Base goja host
captable pilots and esign reuses — so the bundle carries only domain logic;
persistence, per-tenant file selection, and per-request transactions are the
binding's job.
What the bundle does
The complete dataroom flow, re-expressed as a route table over injected host functions:
- documents — metadata rows; the file bytes live in object storage (the cloud VFS/S3 data plane), handled by the Go leaf. The bundle only ever stores/reads the opaque storage key.
- data rooms + dataroom_document — rooms and their document membership.
- shareable links — access controls: email gate + allow-list, bcrypt password, expiry, download toggle, archive.
- viewers + views — a viewer authenticates against a link's controls; a View (analytics session) is recorded.
- per-page view analytics — page-by-page tracking rows, aggregated per page
(
views,totalDuration,avgDuration).
The Prisma models (Document, Dataroom, DataroomDocument, Link, Viewer,
View, per-page tracking) become the CREATE TABLE statements in the bundle's
migrate route — the one source of truth for the Base schema.
Host contract
clients/goja injects these globals per dispatch (each dispatch runs inside
ONE per-tenant SQLite transaction that commits iff status < 400) and calls
handle once per request:
globalThis.__db.query(sql, args) -> [ {col: val, ...}, ... ] // tenant-scoped
globalThis.__db.exec(sql, args) -> { changes, lastId }
globalThis.__newId() -> collision-resistant id (crypto/rand)
globalThis.__now() -> unix milliseconds
globalThis.__bcrypt.hash(pw) -> bcrypt hash (leaf HostFn, vetted Go)
globalThis.__bcrypt.verify(pw, h) -> bool
globalThis.handle({ route, params, query, orgId, body }) -> { status, body }
__dbis bound per dispatch to exactly one tenant's SQLite file (selected from the caller's validated org), so a route can only ever touch its own org's rows.__bcryptis a leaf-injected HostFn (bcrypt in vetted Go) — link passwords are hashed, never plaintext.- The per-tenant schema (DDL) lives in the Go leaf (
clients/dataroom/ schema.go), run by gojabase on first open; this bundle only issues SQL. - Admin routes run under the caller's validated cloud principal (org); public viewer routes resolve their org from the link index the leaf maintains.
Constraints (goja target)
Same as @hanzo/plans: ES2020, no ESM import/export, no Node
builtins, no console dependency. Authored directly as the artifact goja runs
— there is exactly one bundle, one way to build it.
Vendoring
hanzoai/cloud/clients/dataroom/bundle.js is a byte-identical vendored copy
that cloud go:embeds (the task mandates go:embed here rather than importing a
Go module, since this repo is a TS app, not a Go module). Edit this file, then
re-vendor the copy into cloud.