mirror of
https://github.com/hanzoai/sign.git
synced 2026-10-21 01:21:51 +00:00
The client and CLI were raised to Prisma 7 without the migration that major requires, so `prisma generate` stopped at P1012 and nothing downstream of it could build. Prisma 7 takes the connection URL out of the schema and has no query engine: the CLI reads prisma.config.ts, and the client is handed a driver adapter. Both read the one DATABASE_URL. The adapter holds a single connection, which is the single writer SQLite wants by construction — the `?connection_limit=1` suffix the old engine needed is gone, and had to be: the adapter strips `file:` and takes the rest as the file's name. THE TRAP IS THE ADAPTER'S DEFAULT. The engine this replaces wrote every DateTime as an epoch-millisecond INTEGER; that is what a deployed sign.db holds and what sqlite-sql.ts divides by 1000 to read. The better-sqlite3 adapter defaults to ISO-8601 TEXT. Left alone it would write strings into columns full of integers, SQLite would order them by storage class rather than by time, and nothing would throw. `timestampFormat: 'unixepoch-ms'` keeps one representation, and timestamp.test.ts reads the column's storage class over a second connection. Checked both ways: on the default it fails with `createdAt is stored as text: 2026-03-04T05:06:07.890+00:00`. packages/prisma: 52 of 52. Co-authored-by: Hanzo Dev <[email protected]>
47 lines
2.2 KiB
TypeScript
47 lines
2.2 KiB
TypeScript
/// <reference types="@hanzo/esign-tsconfig/process-env.d.ts" />
|
|
|
|
/**
|
|
* Database resolution for the Base SQLite store.
|
|
*
|
|
* esign is a single-binary multi-tenant app. Identity is global: a `User` owns
|
|
* and belongs to MANY organisations (`User.ownedOrganisations[]` /
|
|
* `organisationMember[]`), and `Session` / `Account` / `Passkey` / `ApiToken`
|
|
* carry no `organisationId`. Auth is resolved BEFORE any org is known —
|
|
* `validateSessionToken` reads `Session` to discover the user — so a database
|
|
* cannot be selected per-org at request entry (the chicken-and-egg the prior
|
|
* file-per-org design could never satisfy). Only 8 of 47 tables even carry an
|
|
* `organisationId`; the rest scope through relations (`Envelope → Team →
|
|
* Organisation`) that a per-file split would make un-joinable.
|
|
*
|
|
* Therefore there is ONE Base SQLite database. Tenant isolation is enforced
|
|
* where it has always lived in this schema: row-level org/team scoping in the
|
|
* `packages/lib/server-only/*` handlers — every cross-org read funnels through
|
|
* the single `buildTeamWhereQuery({ teamId, userId })` predicate keyed on the
|
|
* authenticated user, never on a client-supplied value. That predicate, not a
|
|
* filesystem boundary, is the isolation boundary. See
|
|
* `packages/prisma/__tests__/tenant-isolation.test.ts` for the proof that a
|
|
* forged team/org id returns zero rows.
|
|
*
|
|
* This module owns the ONE place a SQLite connection URL is constructed, and
|
|
* fails closed if it cannot be resolved unambiguously.
|
|
*/
|
|
|
|
/**
|
|
* The Prisma datasource URL for the Base SQLite store.
|
|
*
|
|
* Single source of truth: `DATABASE_URL`. Fails closed when unset — there is no
|
|
* implicit fallback path that could silently point production at the wrong
|
|
* file. It is returned as given: the driver adapter opens exactly one
|
|
* connection, so there is one writer without asking for one. (The query engine
|
|
* this replaced pooled connections, and needed `?connection_limit=1` appended
|
|
* to get the same thing; the adapter would read that suffix as part of the
|
|
* file's NAME.)
|
|
*/
|
|
export const databaseUrl = (): string => {
|
|
const url = process.env.DATABASE_URL;
|
|
if (!url) {
|
|
throw new Error('DATABASE_URL is not set — cannot resolve the SQLite database.');
|
|
}
|
|
return url;
|
|
};
|