* feat(goja): self-contained goja bundle — fold dataroom into hanzoai/cloud (#101) Extract the dataroom business logic into a self-contained, ESM-free goja bundle (goja/bundle.js) exposing globalThis.handle(req), authored to run verbatim inside the unified hanzoai/cloud binary (HIP-0106, task #101 / epic #96) — the same in-process pattern @hanzo/plans and @hanzo/pricing use. The complete flow — documents (metadata; bytes on the cloud object-storage seam), data rooms + membership, shareable links (email/allow-list gate + bcrypt password + expiry + download toggle), viewers, per-page view analytics — is re-expressed as a route table over injected host functions (globalThis.db.query/exec bound per-tenant to Base/SQLite, globalThis.crypto bcrypt helpers). The Prisma models become CREATE TABLE statements in the bundle's migrate route. No Postgres, no Next.js; the standalone pod is retired. cloud/clients/dataroom go:embeds a byte-identical vendored copy (task mandates go:embed here since this repo is a TS app, not a Go module). Claude-Session: https://claude.ai/code/session_016yg7GPhYdWCh9vpp4HEwLZ * goja: run on the shared clients/gojabase RW-Base binding (#101) Rework the bundle onto the reusable clients/gojabase host contract (the RW-Base goja binding captable pilots, esign reuses) instead of a bespoke DB binding — one binding, not two. The bundle now calls __db.query/__db.exec, __newId, __now and __bcrypt.hash/verify (the leaf's bcrypt HostFn), and handle({route,params,query, orgId,body}); the per-tenant schema (DDL) moves to the Go leaf where gojabase runs it on first open, and each dispatch is one per-tenant transaction (commits iff status<400). No behaviour change to the dataroom flow; README updated. Claude-Session: https://claude.ai/code/session_016yg7GPhYdWCh9vpp4HEwLZ --------- Co-authored-by: hanzo-dev <dev@hanzo.ai>
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/gojabase 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/gojabase 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.