Database schema, email, logging, notifications, and the conversation, document and identity contracts.
Part of Intelligo, an application framework and
operational platform for vertical AI SaaS products. Every @intelligo-dev/*
package is released at one version and shares one database schema;
pnpm dlx @intelligo-dev/cli create my-app installs the set an
application needs. Documentation:
intelligo.dev/docs/packages/core.
pnpm add @intelligo-dev/core drizzle-ormdrizzle-orm is a peer; react 19 is an optional one, needed only by the
email templates. Every other @intelligo-dev/* package depends on this one,
and this one imports none of them.
import { db, withWorkspaceFilter } from "@intelligo-dev/core/db";
import { conversations } from "@intelligo-dev/core/db/schema";
import { createLogger } from "@intelligo-dev/core/logger";
import { eq } from "drizzle-orm";
const log = createLogger("Inbox");
const mine = await db
.select()
.from(conversations)
.where(
withWorkspaceFilter(
conversations.workspaceId,
workspace.id,
eq(conversations.userId, user.id)
)
);
log.info("Loaded conversations", { count: String(mine.length) });db connects on first use from DATABASE_URL: a Neon host takes the
WebSocket driver and anything else takes node-postgres, unless
INTELLIGO_DB_DRIVER says pg or neon-serverless. Every tenant-scoped query
filters on workspaceId; workspaceEq and withWorkspaceFilter make that the
short way to write one.
The leaves import nothing, so a client bundle, an edge runtime or another package can reach them without pulling in the database:
import { formatMoney, fromMajor } from "@intelligo-dev/core/money";
import { sanitizeForSystemPrompt } from "@intelligo-dev/core/prompt";
formatMoney(fromMajor(12.5, "USD"), "en-US"); // "$12.50", held as 12_500_000 micros
const system = `${basePrompt}\n\nAbout the user: ${sanitizeForSystemPrompt(profile)}`;| Subpath | What |
|---|---|
/db |
Drizzle client and the workspace-scoped query helpers |
/db/schema |
Every framework table |
/conversations |
Conversation and message persistence |
/documents |
Document persistence and the title classifier |
/identity |
The identity graph: facts, memories, profile snapshots |
/attachments |
Rows for the files users put into conversations |
/storage |
Where those files' bytes live: one adapter (S3, R2, disk) bound by the application |
/storage/s3 |
That adapter for any S3-compatible bucket (S3, R2, MinIO), signed without an SDK |
/email |
Transactional email and its templates |
/notifications |
In-app notification records and triggers |
/logger |
Structured logging with credential redaction |
/registry |
Registries that survive a bundler duplicating a module |
/money |
Amount-plus-currency value object in micros; imports nothing |
/request-context |
Where the framework reads the request's headers from; bound by the adapter |
/prompt |
Prompt-injection sanitisation for user text bound for a system prompt; imports nothing |
/env |
Environment validation |
/storage is the port; /storage/s3 is an adapter for any S3-compatible
bucket. It signs requests itself (Signature Version 4 over fetch and Web
Crypto), so it adds no dependency and runs wherever fetch does. Bind it from
the composition root:
import { setStorageAdapter } from "@intelligo-dev/core/storage";
import { s3StorageFromEnv } from "@intelligo-dev/core/storage/s3";
const storage = s3StorageFromEnv(); // null without STORAGE_BUCKET
if (storage) setStorageAdapter(storage);s3StorageFromEnv reads STORAGE_BUCKET, STORAGE_ACCESS_KEY_ID,
STORAGE_SECRET_ACCESS_KEY, STORAGE_REGION (default auto, R2's) and
STORAGE_ENDPOINT (R2's https://<account>.r2.cloudflarestorage.com, a MinIO
URL; unset means AWS). A custom endpoint is addressed path-style; pass
createS3Storage({ …, pathStyle }) to choose. Uploads are buffered to be
hashed.
assertEnv() from /env fails at boot rather than at the first query when
DATABASE_URL, BETTER_AUTH_SECRET or NEXT_PUBLIC_APP_URL is missing;
validateEnv() returns the same findings, with warnings for the optional
Stripe, email and CRON_SECRET values. The composition root calls it once.
The framework migration chain ships inside this package
(src/db/migrations) and is applied by intelligo migrate from
@intelligo-dev/cli.
Do not provision with drizzle-kit push. A pushed database has the schema and
no migration records, and the migrator refuses it rather than half-applying the
chain to tables that already exist.
Apache-2.0