Spreadsheet-UX op een echte relationele engine
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
import { assertWorkspaceSlug, type Locale, type Role } from '@wellformed/shared';
import { conflict } from '../errors.js';
import type { DemoChoice } from '../db/demo.js';
import { seed } from '../db/seed.js';
import { transactControl, type ControlDb } from '../control/db.js';
import {
createWorkspace,
deleteWorkspace,
getWorkspaceBySlug,
setMembership,
type WorkspaceRecord,
} from '../control/workspaces.js';
import type { TenantRegistry } from './registry.js';
export interface ProvisionInput {
slug: string;
name: string;
/** The account that gets the `admin` membership. */
ownerId: string;
/**
* What to build inside it: nothing, the demo's tables and views, or the
* demo with its data too. Omitted means empty — starting from your own
* spreadsheet is the expected road in.
*/
demo?: DemoChoice;
/**
* The language the workspace opens in — usually the browser language of
* whoever is creating it. Changeable afterwards in workspace settings.
* The demo, when asked for, is built in this language.
*/
locale?: Locale;
}
/**
* Create a workspace: a control record, an admin membership, a database file
* and, optionally, something to look at inside it.
*
* The control record and the file are created in that order but rolled back
* together. A control row pointing at a database that failed to build would
* hand its members a workspace that 500s on every request, which is worse than
* the signup simply failing.
*/
export function provisionWorkspace(
control: ControlDb,
registry: TenantRegistry,
input: ProvisionInput,
): WorkspaceRecord {
assertWorkspaceSlug(input.slug);
if (getWorkspaceBySlug(control, input.slug)) {
throw conflict(`The workspace address "${input.slug}" is already taken.`);
}
const workspace = transactControl(control, () => {
const created = createWorkspace(control, {
slug: input.slug,
name: input.name,
createdBy: input.ownerId,
locale: input.locale,
});
setMembership(control, created.id, input.ownerId, 'admin' satisfies Role);
return created;
});
try {
const db = registry.acquire(workspace);
try {
const demo = input.demo ?? 'none';
if (demo !== 'none') {
seed(db, { quiet: true, locale: workspace.locale, mode: demo });
}
} finally {
registry.release(workspace.id);
}
} catch (error) {
// Undo both halves, in the order that leaves nothing orphaned.
try {
registry.destroy(workspace);
} finally {
deleteWorkspace(control, workspace.id);
}
throw error;
}
return workspace;
}
/**
* Suggest a free slug near `base`, so signup does not bounce someone over a
* name collision they cannot see.
*/
export function availableSlug(control: ControlDb, base: string): string {
if (!getWorkspaceBySlug(control, base)) {
try {
assertWorkspaceSlug(base);
return base;
} catch {
// Fall through to the suffixed attempts below.
}
}
for (let n = 2; n < 500; n++) {
const candidate = `${base.slice(0, 36)}-${n}`;
try {
assertWorkspaceSlug(candidate);
} catch {
continue;
}
if (!getWorkspaceBySlug(control, candidate)) return candidate;
}
throw conflict('Could not find a free workspace address. Pick one yourself.');
}