All checks were successful
CD / Build and push images (push) Successful in 2m51s
CI / Lint, typecheck, test (push) Successful in 1m55s
CI / Auth e2e pack (push) Successful in 2m0s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 11s
The collaboration server becomes the writer of page state (ADR 0003, realtime-collaboration.md §lifecycle): - onLoadDocument reconstructs a page's Y.Doc from PostgreSQL by applying `pages.ydoc_state` and then every `page_updates` row in order, so a page with a long update log loads correctly. - onStoreDocument persists debounced (2 s, max 30 s): it appends the delta since the last flush to `page_updates`, periodically merges the log back into `ydoc_state` (inline threshold; the session-aware compaction of idle pages remains the separate job, #40), refreshes `page_content_cache` (plain text / Markdown / HTML / outline via the shared derivation, #24), bumps `pages.updated_at`, and keeps `Attachment.pageId` pointed at the embedding page (#31). Each flush runs in one transaction and its duration is logged. - The document size ceiling (MAX_PAGE_DOCUMENT_BYTES) is enforced on store: an oversize document is not persisted and the clients are notified with a stateless error so they can revert. Persistence is an injected port (PagePersistence): the Postgres implementation is covered by a DB-backed test (store/load round-trip, content-cache refresh, a 1000-entry update log, size-ceiling rejection, not-found), and the hook wiring — two-client sync, survival across a server restart, and the size-ceiling stateless notification — by an integration test using an in-memory fake. The collab package gains its own vitest setup that provisions an isolated `_collab` test database. The REST `PUT /pages/:id/state` write path stays in place for now and is retired (410) together with switching the editor to live collaboration in #36, so the deployed editor is never left unable to save between the two deploys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { execFileSync } from 'node:child_process';
|
|
import { createRequire } from 'node:module';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { Client } from 'pg';
|
|
|
|
import { collabTestDatabaseUrl } from './src/testing/test-db.js';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
|
|
/**
|
|
* Prepare the isolated collab test database (see `test-db.ts`): create it if
|
|
* missing, then push the current Prisma schema (owned by the api package) into
|
|
* it. Runs once per test run; a no-op when `TEST_DATABASE_URL` is unset, in
|
|
* which case the DB-backed suite skips itself.
|
|
*/
|
|
export default async function globalSetup(): Promise<void> {
|
|
const base = process.env.TEST_DATABASE_URL;
|
|
if (!base) return;
|
|
|
|
const target = collabTestDatabaseUrl(base);
|
|
const dbName = decodeURIComponent(new URL(target).pathname.slice(1));
|
|
|
|
const admin = new Client({ connectionString: base });
|
|
await admin.connect();
|
|
try {
|
|
const exists = await admin.query('SELECT 1 FROM pg_database WHERE datname = $1', [dbName]);
|
|
if (exists.rowCount === 0) {
|
|
// Identifier can't be parameterised; dbName is derived from our own env.
|
|
await admin.query(`CREATE DATABASE "${dbName}"`);
|
|
}
|
|
} finally {
|
|
await admin.end();
|
|
}
|
|
|
|
execFileSync(
|
|
process.execPath,
|
|
[
|
|
require.resolve('prisma/build/index.js'),
|
|
'db',
|
|
'push',
|
|
'--skip-generate',
|
|
'--schema',
|
|
resolve(here, '../api/prisma/schema.prisma'),
|
|
],
|
|
{ env: { ...process.env, DATABASE_URL: target }, stdio: 'inherit', cwd: here },
|
|
);
|
|
}
|