@flinkk/database
v1.0.16
Published
The single Prisma client and schema for every Flinkk app, plus the license/tenant-isolation layer that makes single-org ("standalone") and restricted multi-org ("saas-standalone") deployments possible on top of the same shared-multi-tenant schema.
Readme
@flinkk/database
The single Prisma client and schema for every Flinkk app, plus the license/tenant-isolation layer that makes single-org ("standalone") and restricted multi-org ("saas-standalone") deployments possible on top of the same shared-multi-tenant schema.
Core exports
| Subpath | What it is |
| --- | --- |
| @flinkk/database/prisma | The shared prisma client instance, extended with automatic tenant scoping. Every tenant-scoped model gets tenantId filled in / filtered on automatically — see "Tenant isolation" below. |
| @flinkk/database/server | runWithContext(ctx, fn) / getCurrentTenantId() / getCurrentUserId() — the AsyncLocalStorage-based request context the extension reads from. |
| @flinkk/database/context | Same as ./server, exported directly from its own module for callers inside this package. |
| @flinkk/database/license | getLicense() — verifies the signed LICENSE_KEY env var against a compiled-in Ed25519 public key and resolves it to a { type, allowedTenants } structure. Cached per-process. |
| @flinkk/database/startup-check | assertLicenseAtStartup() / getLicenseViolation() — call once at app boot to fail fast (or render a friendly error page) if the license is invalid/expired. |
| @flinkk/database/system-roles | Seeds/updates the default Owner/System Manager/etc. CustomRole rows and their permissions for a tenant. |
| @flinkk/database/field-utils | Custom-field permission and listing helpers (getFieldPermissions, getCustomFieldsForEntity, getCustomFields). |
| @flinkk/database/query-utils | Generic record CRUD helpers used by the dynamic /api/method/[model]/* routes. |
| @flinkk/database/seed-data, ./seed-utils/* | Tenant provisioning seed data (standard fields, currency configurations, lead-conversion mappings) and the functions that apply them to a new tenant. |
| @flinkk/database/extend-middleware | Prisma extensions beyond tenant scoping: soft delete, activity logging, naming series, notification triggers, etc. |
| @flinkk/database/utils/* | Misc helpers (table-fields, condition-utils, suggested-worklog, openai-client, udnse/*). |
| @flinkk/database/types/* | Shared TypeScript types for cross-package payloads (user, contact, account, ticket, xero). |
Tenant isolation
extensions/tenant-filter.ts is the one place tenant scoping lives, replacing hundreds of hand-written where: { tenantId } filters. Behavior depends on the active license (getLicense()):
saas— unrestricted. If no tenant is in the current request context, the query just runs as the caller wrote it (callers scope themselves explicitly).standalone— every query is force-scoped to the license's one fixed tenant, regardless of context. This is what makes code written before this license type existed keep working unmodified.saas-standalone— scoped to whichever tenant is in the current request context, rejected if that tenant isn't in the license's allowed list, and rejected if there's no tenant in context at all (fails closed — an unscoped query can't be allowed to silently skip the restriction this license type exists to enforce).
Because of the last point, any code path reachable under a saas-standalone license must run inside runWithContext({ tenantId }, fn) before touching a tenant-scoped model — including "cold" lookups by an opaque id/token where the tenant isn't known yet (see @flinkk/shared-rbac/with-tenant-context's resolveAcrossAllowedTenants in the consuming app for that case).
Usage
import { prisma } from "@flinkk/database/prisma";
import { runWithContext } from "@flinkk/database/server";
const contact = await runWithContext({ tenantId, userId }, async () => {
return await prisma.contact.findUnique({ where: { id } });
});import { getLicense } from "@flinkk/database/license";
import { assertLicenseAtStartup } from "@flinkk/database/startup-check";
// once at process boot
await assertLicenseAtStartup();
const license = await getLicense();
if (license.type === "saas-standalone") {
// license.allowedTenants is the exact tenant id allow-list
}Environment variables
DATABASE_URL— Postgres connection string (Prisma).LICENSE_KEY— signed license JWT; required forgetLicense()to resolve. The public key it's verified against is compiled intolicense-public-key.ts, not read from configuration, so whoever controls env vars can't sign their own license.
License
ISC License - Internal Flinkk library
