Spreadsheet-UX op een echte relationele engine
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
import { randomBytes } from 'node:crypto';
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import dotenv from 'dotenv';
import { readLicence, type LicenceState } from './licence/index.js';
/**
* Locate the repo root by walking up for the workspace manifest. Resolving by
* a fixed number of `..` hops breaks between `src/` (tsx) and `dist/` (build).
*/
function findRepoRoot(start: string): string {
let dir = start;
for (let i = 0; i < 8; i++) {
const manifest = path.join(dir, 'package.json');
if (existsSync(manifest)) {
try {
const pkg = JSON.parse(readFileSync(manifest, 'utf8')) as { name?: string };
if (pkg.name === 'wellformed') return dir;
} catch {
// Unreadable manifest on the way up is not fatal; keep climbing.
}
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return start;
}
export const REPO_ROOT = findRepoRoot(path.dirname(fileURLToPath(import.meta.url)));
const envFile = path.join(REPO_ROOT, '.env');
if (existsSync(envFile)) dotenv.config({ path: envFile, quiet: true });
const isProd = process.env.NODE_ENV === 'production';
const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';
function resolveFromRoot(p: string): string {
return path.isAbsolute(p) ? p : path.resolve(REPO_ROOT, p);
}
function requiredSecret(): string {
const fromEnv = process.env.WELLFORMED_SECRET?.trim();
if (fromEnv && fromEnv.length >= 32) return fromEnv;
if (isProd) {
throw new Error(
'WELLFORMED_SECRET must be set to at least 32 characters in production. Generate one with:\n' +
' node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"',
);
}
if (fromEnv) {
console.warn('[wellformed] WELLFORMED_SECRET is shorter than 32 characters; using it anyway (development).');
return fromEnv;
}
return randomBytes(32).toString('hex');
}
const dataDir = resolveFromRoot(process.env.WELLFORMED_DATA_DIR ?? './data');
export interface Config {
/** Accounts, workspaces, memberships, sessions and invites. One per install. */
controlDbPath: string;
/** Directory holding one SQLite file per workspace. */
tenantsDir: string;
/**
* A pre-tenancy single-database install, adopted as the first workspace on
* the next boot. Nothing writes here afterwards; the workspace owns it.
*/
legacyDbPath: string;
legacySlug: string;
legacyName: string;
/**
* Whether strangers may create an account and a workspace.
*
* Open by default, which is what a hosted Wellformed wants. A self-hoster running
* this for one team closes it and adds people by invitation instead.
*/
signupsOpen: boolean;
/**
* Whose licence this install runs under, if anyone's.
*
* Read once, on start, from `WELLFORMED_LICENSE`. See `licence/index.ts` for
* what the states mean and why this is not a lock.
*/
licence: LicenceState;
/**
* Whether the plans are a ceiling on this install.
*
* A hosted Wellformed meters, because the plan is the thing a customer buys.
* An install running under its own licence has settled that on paper: there
* is nobody to bill and nothing to sell them, and metering it means a wall
* arriving on a machine the operator does not watch, with no way to lift it
* but SSH.
*
* So it follows from the licence, and from nothing else. Not from a mode
* word anyone can type — that was the old arrangement, and the trouble with
* it was that the setting asserted nothing checkable. A signed licence names
* the party it was granted to, and this is the one place that fact is
* allowed to change what the software does.
*/
metered: boolean;
/**
* Workspaces published read-only to people with no account, named by slug —
* usually one per language, so the marketing page can point at a demo that
* speaks the visitor's language. The first slug is the fallback for
* visitors whose language has no demo.
*
* The operator names them here rather than a flag on the workspace itself,
* so that no workspace admin can publish a workspace by clicking something —
* the one irreversible mistake in a product like this is exposing a customer
* database, and it should take shell access.
*/
demoSlugs: string[];
/** Workspace database handles kept open at once. */
maxOpenWorkspaces: number;
/** Close a workspace handle nobody has used for this long. */
workspaceIdleMs: number;
port: number;
host: string;
secret: string;
isProd: boolean;
isTest: boolean;
corsOrigin: string | false;
/** Absolute path to the built web app, when it exists. */
webDist: string;
/** The SSR bundle that renders the marketing page server-side, when built. */
webSsrEntry: string;
sessionTtlMs: number;
/**
* Where this install answers from, used only to make links in outbound mail
* absolute. Everything in the app builds URLs against the browser's own
* origin; a message has no browser to ask.
*/
publicUrl: string;
email: EmailConfig;
billing: BillingConfig;
}
/**
* Taking money, if this install does.
*
* `none` is a first-class state, not a broken one: a self-hosted Wellformed has
* nobody to bill, and every workspace on it simply runs on the free plan with
* the operator moving one by hand when they want to.
*/
export interface BillingConfig {
provider: 'none' | 'paddle';
/** Hosted checkout page; the plan's price id is appended as a query param. */
checkoutBase: string;
/** Where someone manages or cancels a subscription — the provider's page. */
portalUrl: string;
webhookSecret: string;
/** Plan id → the provider's price id. Only paid plans need one. */
priceIds: Record<string, string>;
}
function billingConfig(): BillingConfig {
const provider = (process.env.WELLFORMED_BILLING_PROVIDER ?? 'none').trim().toLowerCase();
const checkoutBase = process.env.WELLFORMED_BILLING_CHECKOUT_URL?.trim() ?? '';
const webhookSecret = process.env.WELLFORMED_BILLING_WEBHOOK_SECRET?.trim() ?? '';
const priceIds = { team: process.env.WELLFORMED_BILLING_PRICE_TEAM?.trim() ?? '' };
const portalUrl = process.env.WELLFORMED_BILLING_PORTAL_URL?.trim() ?? '';
if (provider !== 'paddle') {
return { provider: 'none', checkoutBase, portalUrl, webhookSecret, priceIds };
}
if (!checkoutBase || !webhookSecret || !priceIds.team) {
console.warn(
'[wellformed] WELLFORMED_BILLING_PROVIDER=paddle needs WELLFORMED_BILLING_CHECKOUT_URL, ' +
'WELLFORMED_BILLING_WEBHOOK_SECRET and WELLFORMED_BILLING_PRICE_TEAM; billing stays off.',
);
return { provider: 'none', checkoutBase, portalUrl, webhookSecret, priceIds };
}
return { provider: 'paddle', checkoutBase, portalUrl, webhookSecret, priceIds };
}
/**
* Outbound email.
*
* Off unless configured, and the app is expected to work with it off — that is
* why an invite is a link you pass on yourself. What email buys is the one flow
* a link cannot: reaching someone who is locked out and therefore cannot be
* handed anything through the product.
*
* Both providers are one HTTPS POST, so this costs no dependency.
*/
export interface EmailConfig {
provider: 'none' | 'resend' | 'postmark';
apiKey: string;
from: string;
}
function emailConfig(): EmailConfig {
const provider = (process.env.WELLFORMED_EMAIL_PROVIDER ?? 'none').trim().toLowerCase();
const apiKey = process.env.WELLFORMED_EMAIL_KEY?.trim() ?? '';
const from = process.env.WELLFORMED_EMAIL_FROM?.trim() ?? '';
if (provider !== 'resend' && provider !== 'postmark') return { provider: 'none', apiKey, from };
if (!apiKey || !from) {
console.warn(
`[wellformed] WELLFORMED_EMAIL_PROVIDER=${provider} needs WELLFORMED_EMAIL_KEY and WELLFORMED_EMAIL_FROM; email stays off.`,
);
return { provider: 'none', apiKey, from };
}
return { provider, apiKey, from };
}
/**
* Read once, here, rather than wherever it is wanted.
*
* Verifying a signature is cheap but not free, and more to the point a licence
* that changed under a running process would mean two requests disagreeing
* about what this install is.
*/
const licence = readLicence();
export const config: Config = {
controlDbPath: resolveFromRoot(process.env.WELLFORMED_CONTROL_DB ?? path.join(dataDir, 'control.db')),
tenantsDir: resolveFromRoot(process.env.WELLFORMED_TENANTS_DIR ?? path.join(dataDir, 'workspaces')),
legacyDbPath: resolveFromRoot(process.env.WELLFORMED_DB ?? path.join(dataDir, 'wellformed.db')),
legacySlug: process.env.WELLFORMED_LEGACY_SLUG ?? 'main',
legacyName: process.env.WELLFORMED_LEGACY_NAME ?? 'Workspace',
signupsOpen: process.env.WELLFORMED_SIGNUPS !== 'closed',
licence,
metered: licence.status !== 'licensed',
demoSlugs: (process.env.WELLFORMED_DEMO_SLUG ?? '')
.toLowerCase()
.split(',')
.map((slug) => slug.trim())
.filter(Boolean),
maxOpenWorkspaces: Number(process.env.WELLFORMED_MAX_OPEN_WORKSPACES ?? 64),
workspaceIdleMs: Number(process.env.WELLFORMED_WORKSPACE_IDLE_MS ?? 10 * 60_000),
port: Number(process.env.PORT ?? 4000),
host: process.env.HOST ?? '127.0.0.1',
secret: requiredSecret(),
isProd,
isTest,
corsOrigin: isProd ? false : (process.env.WELLFORMED_CORS_ORIGIN ?? 'http://localhost:5173'),
webDist: path.join(REPO_ROOT, 'packages', 'web', 'dist'),
webSsrEntry: path.join(REPO_ROOT, 'packages', 'web', 'dist-ssr', 'entry-ssr.js'),
sessionTtlMs: 1000 * 60 * 60 * 24 * 14,
publicUrl: (process.env.WELLFORMED_PUBLIC_URL ?? `http://localhost:${process.env.PORT ?? 4000}`).replace(/\/+$/, ''),
email: emailConfig(),
billing: billingConfig(),
};