diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
index d7f6733..fedd0d3 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -62,13 +62,13 @@ jobs:
platform: linux/amd64
runner: ubuntu-24.04
context: .
- dockerfile: ./compute-worker/Dockerfile
+ dockerfile: ./packages/compute-worker/Dockerfile
- image_target: compute-worker
arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
context: .
- dockerfile: ./compute-worker/Dockerfile
+ dockerfile: ./packages/compute-worker/Dockerfile
steps:
- name: Checkout repository
diff --git a/Dockerfile b/Dockerfile
index 83230da..f3396d1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -20,8 +20,9 @@ WORKDIR /app
# Copy workspace manifests needed for dependency installation
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
-COPY compute-worker/package.json ./compute-worker/package.json
-COPY docker/entrypoint-migration-tools/package.json ./docker/entrypoint-migration-tools/package.json
+COPY packages/bootstrap/package.json ./packages/bootstrap/package.json
+COPY packages/compute-worker/package.json ./packages/compute-worker/package.json
+COPY packages/database/package.json ./packages/database/package.json
# Install dependencies
RUN pnpm install --frozen-lockfile
@@ -32,8 +33,9 @@ COPY . .
# Build the Next.js application
RUN pnpm exec next telemetry disable
RUN AUTH_SECRET=build-placeholder-secret-value-32chars!! BASE_URL=http://localhost:3003 pnpm build
-RUN pnpm --config.inject-workspace-packages=true --filter @openreader/entrypoint-migration-tools deploy /opt/entrypoint-migration-tools
-RUN pnpm --config.inject-workspace-packages=true --filter @openreader/compute-worker deploy /opt/embedded-compute-worker
+RUN pnpm --config.inject-workspace-packages=true --filter @openreader/bootstrap deploy /opt/openreader/bootstrap
+RUN pnpm --dir /opt/openreader/bootstrap rebuild better-sqlite3 ffmpeg-static
+RUN pnpm --config.inject-workspace-packages=true --filter @openreader/compute-worker deploy /opt/openreader/embedded-compute-worker
# Generate third-party dependency license report plus copied license files.
RUN mkdir -p /app/THIRD_PARTY_LICENSES && \
pnpm dlx license-checker-rseidelsohn@4.3.0 \
@@ -62,34 +64,15 @@ COPY --from=app-builder /app/.next/standalone ./
COPY --from=app-builder /app/.next/static ./.next/static
COPY --from=app-builder /app/public ./public
-# Copy the entrypoint and migration/runtime helper files it invokes directly.
-COPY --from=app-builder /app/scripts/openreader-entrypoint.mjs ./scripts/openreader-entrypoint.mjs
-COPY --from=app-builder /app/scripts/migrate-fs-v2.mjs ./scripts/migrate-fs-v2.mjs
-COPY --from=app-builder /app/drizzle ./drizzle
-COPY --from=app-builder /app/drizzle.config.pg.ts ./drizzle.config.pg.ts
-COPY --from=app-builder /app/drizzle.config.sqlite.ts ./drizzle.config.sqlite.ts
-COPY --from=app-builder /app/src/db ./src/db
-
-# Merge in the dependency subset needed by the entrypoint migration scripts.
-COPY --from=app-builder /opt/entrypoint-migration-tools/node_modules /tmp/runtime-tools-node_modules
-RUN mkdir -p /app/node_modules && \
- rm -rf /app/node_modules/pg && \
- rm -rf /tmp/runtime-tools-node_modules/@aws-sdk \
- /tmp/runtime-tools-node_modules/better-sqlite3 \
- /tmp/runtime-tools-node_modules/ffmpeg-static && \
- cp -an /tmp/runtime-tools-node_modules/. /app/node_modules/ && \
- rm -rf /tmp/runtime-tools-node_modules
-# Drizzle Kit discovers the Postgres driver through ESM import during startup migrations.
-RUN node --input-type=module -e "await import('pg')"
-
-# Ship the embedded compute worker as a separate deployed bundle.
-COPY --from=app-builder /opt/embedded-compute-worker ./embedded-compute-worker
+# Ship startup orchestration and the embedded worker as independent deployed bundles.
+COPY --from=app-builder /opt/openreader/bootstrap /opt/openreader/bootstrap
+COPY --from=app-builder /opt/openreader/embedded-compute-worker /opt/openreader/embedded-compute-worker
# Include third-party license report and copied license texts at a stable path in the image.
COPY --from=app-builder /app/THIRD_PARTY_LICENSES /licenses
# Include SeaweedFS license text for the copied weed binary.
COPY --from=seaweedfs-builder /tmp/SeaweedFS-LICENSE.txt /licenses/SeaweedFS-LICENSE.txt
# Include static model notices for runtime-downloaded assets.
-COPY --from=app-builder /app/compute-worker/src/inference/pdf/assets/LICENSE.txt /licenses/pp-doclayoutv3-LICENSE.txt
+COPY --from=app-builder /app/packages/compute-worker/src/inference/pdf/assets/LICENSE.txt /licenses/pp-doclayoutv3-LICENSE.txt
# Copy seaweedfs weed binary for optional embedded local S3.
COPY --from=seaweedfs-builder /tmp/weed /usr/local/bin/weed
@@ -99,7 +82,7 @@ COPY --from=nats-builder /tmp/nats-server /usr/local/bin/nats-server
RUN chmod +x /usr/local/bin/nats-server
# Include OpenAI Whisper license text for runtime-downloaded ONNX artifacts.
-COPY --from=app-builder /app/compute-worker/src/inference/whisper/assets/LICENSE.txt /licenses/openai-whisper-LICENSE.txt
+COPY --from=app-builder /app/packages/compute-worker/src/inference/whisper/assets/LICENSE.txt /licenses/openai-whisper-LICENSE.txt
# Match the app's historical container port now that standalone server.js
# is started directly instead of `next start -p 3003`.
@@ -109,5 +92,5 @@ ENV PORT=3003
EXPOSE 3003
# Start the application
-ENTRYPOINT ["node", "scripts/openreader-entrypoint.mjs", "--"]
+ENTRYPOINT ["node", "/opt/openreader/bootstrap/src/cli.mjs", "--"]
CMD ["node", "server.js"]
diff --git a/docker/entrypoint-migration-tools/package.json b/docker/entrypoint-migration-tools/package.json
deleted file mode 100644
index db7e2a3..0000000
--- a/docker/entrypoint-migration-tools/package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "@openreader/entrypoint-migration-tools",
- "version": "0.0.0",
- "private": true,
- "dependencies": {
- "@aws-sdk/client-s3": "^3.1061.0",
- "better-sqlite3": "^12.10.0",
- "dotenv": "^17.4.2",
- "drizzle-kit": "^0.31.10",
- "drizzle-orm": "^0.45.2",
- "ffmpeg-static": "^5.3.0",
- "pg": "^8.21.0"
- }
-}
diff --git a/docker/examples/compose.local.yml b/docker/examples/compose.local.yml
index a50b667..9c0a6d6 100644
--- a/docker/examples/compose.local.yml
+++ b/docker/examples/compose.local.yml
@@ -103,7 +103,7 @@ services:
image: openreader-compute-worker:local
build:
context: ../..
- dockerfile: compute-worker/Dockerfile
+ dockerfile: packages/compute-worker/Dockerfile
depends_on:
nats:
condition: service_started
diff --git a/docs-site/docs/configure/migrations.md b/docs-site/docs/configure/migrations.md
index 43df512..71a51ed 100644
--- a/docs-site/docs/configure/migrations.md
+++ b/docs-site/docs/configure/migrations.md
@@ -7,6 +7,18 @@ import TabItem from '@theme/TabItem';
This page covers migration behavior for both database schema and storage data in OpenReader.
+## Runtime ownership
+
+- `@openreader/database` owns database clients, schemas, SQL migration files, and programmatic
+ migration execution for SQLite and PostgreSQL.
+- `@openreader/bootstrap` owns startup orchestration, storage migration, and optional embedded
+ SeaweedFS, NATS, and compute-worker processes.
+- The Next.js app imports `@openreader/database` directly, but does not orchestrate migrations or
+ child processes.
+
+Docker deploys bootstrap as an isolated runtime bundle under `/opt/openreader/bootstrap`; it does
+not merge migration dependencies into the standalone Next.js app under `/app`.
+
## Startup migration behavior
By default, the shared entrypoint runs migrations automatically before app startup in:
@@ -53,11 +65,6 @@ In most cases, you do not need manual migration commands because startup runs mi
- Postgres when `POSTGRES_URL` is set
- SQLite when `POSTGRES_URL` is unset
-You can always override the target explicitly with `--config`.
-
-
-
-
```bash
# Run pending migrations for one target:
# - Postgres if POSTGRES_URL is set
@@ -71,26 +78,15 @@ pnpm migrate-fs
pnpm migrate-fs:dry-run
```
-
-
-
-```bash
-# Migrate SQLite
-pnpm exec drizzle-kit migrate --config drizzle.config.sqlite.ts
-
-# Migrate Postgres
-pnpm exec drizzle-kit migrate --config drizzle.config.pg.ts
-```
-
-
-
+`pnpm migrate` uses the programmatic Drizzle migrator from `@openreader/database`. Drizzle Kit is
+not a production or startup dependency; it is used only to generate new migration files.
## Generate migrations
`pnpm generate` is a two-phase script for contributors and schema changes:
1. **Better Auth schema generation** — runs the Better Auth CLI twice (once for SQLite, once for Postgres) to produce auto-generated Drizzle schema files for auth tables (`user`, `session`, `account`, `verification`).
-2. **Drizzle migration generation** — runs `drizzle-kit generate` for both `drizzle.config.sqlite.ts` and `drizzle.config.pg.ts`, producing SQL migration files from all schema files (app + auth).
+2. **Drizzle migration generation** — runs `drizzle-kit generate` for both configs in `packages/database`, producing SQL migration files from all schema files (app + auth).
:::note
Most users do not need to run `pnpm generate`. Use it when contributing or when you have changed Drizzle schema files and need new migration files.
@@ -100,22 +96,23 @@ Most users do not need to run `pnpm generate`. Use it when contributing or when
Auth tables are owned by Better Auth. Their Drizzle schema definitions are auto-generated and should **not** be hand-edited:
-- `src/db/schema_auth_sqlite.ts`
-- `src/db/schema_auth_postgres.ts`
+- `packages/database/src/schema_auth_sqlite.ts`
+- `packages/database/src/schema_auth_postgres.ts`
App-specific tables are manually maintained in the standard Drizzle schema files:
-- `src/db/schema_sqlite.ts`
-- `src/db/schema_postgres.ts`
+- `packages/database/src/schema_sqlite.ts`
+- `packages/database/src/schema_postgres.ts`
-Both sets of schema files are included in the Drizzle configs, so `drizzle-kit generate` and `drizzle-kit migrate` handle all tables together.
+Both sets of schema files are included in the Drizzle generation configs. Runtime migration
+execution is owned by `@openreader/database`.
When app schema changes (for example `tts_segments`), keep these in sync:
-- `src/db/schema_sqlite.ts`
-- `src/db/schema_postgres.ts`
-- `drizzle/sqlite/*.sql` + `drizzle/sqlite/meta/_journal.json`
-- `drizzle/postgres/*.sql` + `drizzle/postgres/meta/_journal.json`
+- `packages/database/src/schema_sqlite.ts`
+- `packages/database/src/schema_postgres.ts`
+- `packages/database/migrations/sqlite/*.sql` + `packages/database/migrations/sqlite/meta/_journal.json`
+- `packages/database/migrations/postgres/*.sql` + `packages/database/migrations/postgres/meta/_journal.json`
@@ -130,10 +127,10 @@ pnpm generate
```bash
# Generate SQLite migrations only (skips Better Auth CLI)
-pnpm exec drizzle-kit generate --config drizzle.config.sqlite.ts
+pnpm exec drizzle-kit generate --config packages/database/drizzle.config.sqlite.ts
# Generate Postgres migrations only (skips Better Auth CLI)
-pnpm exec drizzle-kit generate --config drizzle.config.pg.ts
+pnpm exec drizzle-kit generate --config packages/database/drizzle.config.pg.ts
```
:::warning
diff --git a/docs-site/docs/deploy/vercel-deployment.md b/docs-site/docs/deploy/vercel-deployment.md
index fc634c8..298851c 100644
--- a/docs-site/docs/deploy/vercel-deployment.md
+++ b/docs-site/docs/deploy/vercel-deployment.md
@@ -106,7 +106,7 @@ For all variables and defaults, see [Environment Variables](../reference/environ
## 4. Database and data migrations
-Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so automatic startup migrations do not run there.
+Vercel deployments do not run the `@openreader/bootstrap` process, so automatic startup migrations do not run there.
- Run `pnpm migrate` in a controlled environment to apply Drizzle schema migrations to your Postgres DB.
- Run `pnpm migrate-fs` only when migrating legacy local filesystem data (`docstore/documents_v1`, `docstore/audiobooks_v1`) into object storage + DB rows. Fresh Vercel deployments usually do not need this.
diff --git a/drizzle/scripts/migrate.mjs b/drizzle/scripts/migrate.mjs
deleted file mode 100644
index d7c9e8e..0000000
--- a/drizzle/scripts/migrate.mjs
+++ /dev/null
@@ -1,56 +0,0 @@
-import { spawnSync } from 'node:child_process';
-import path from 'node:path';
-import fs from 'node:fs';
-import * as dotenv from 'dotenv';
-
-function loadEnvFiles() {
- // Approximate Next.js behavior enough for server-side scripts.
- // Load .env first, then .env.local (local overrides).
- const cwd = process.cwd();
- const envPath = path.join(cwd, '.env');
- const envLocalPath = path.join(cwd, '.env.local');
-
- if (fs.existsSync(envPath)) {
- dotenv.config({ path: envPath });
- }
- if (fs.existsSync(envLocalPath)) {
- dotenv.config({ path: envLocalPath, override: true });
- }
-}
-
-loadEnvFiles();
-
-const extraArgs = process.argv.slice(2);
-
-const hasConfigArg = extraArgs.includes('--config');
-const configFile = process.env.POSTGRES_URL ? 'drizzle.config.pg.ts' : 'drizzle.config.sqlite.ts';
-const configArgs = hasConfigArg ? [] : ['--config', configFile];
-
-// Ensure the docstore directory exists for SQLite migrations.
-// drizzle-kit opens the database file directly and will fail if the parent
-// directory is missing (e.g. in a fresh CI checkout).
-if (!process.env.POSTGRES_URL) {
- const dbDir = path.join(process.cwd(), 'docstore');
- if (!fs.existsSync(dbDir)) {
- fs.mkdirSync(dbDir, { recursive: true });
- }
-}
-
-function resolveDrizzleKitBin() {
- const binName = process.platform === 'win32' ? 'drizzle-kit.cmd' : 'drizzle-kit';
- const localBin = path.join(process.cwd(), 'node_modules', '.bin', binName);
- if (fs.existsSync(localBin)) return localBin;
- return 'drizzle-kit';
-}
-
-const result = spawnSync(resolveDrizzleKitBin(), ['migrate', ...configArgs, ...extraArgs], {
- stdio: 'inherit',
- env: process.env,
-});
-
-if (result.error) {
- console.error(result.error.message);
- process.exit(1);
-}
-
-process.exit(result.status ?? 1);
diff --git a/next.config.ts b/next.config.ts
index a5908d7..4417b46 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -46,7 +46,7 @@ const nextConfig: NextConfig = {
canvas: '@napi-rs/canvas',
},
},
- transpilePackages: [],
+ transpilePackages: ['@openreader/database'],
serverExternalPackages,
outputFileTracingIncludes: {
'/api/audiobook': [
diff --git a/package.json b/package.json
index 37ad06a..9c9b62d 100644
--- a/package.json
+++ b/package.json
@@ -4,12 +4,12 @@
"private": true,
"packageManager": "pnpm@10.33.4",
"scripts": {
- "dev": "node scripts/openreader-entrypoint.mjs -- pnpm dev:raw",
+ "dev": "pnpm --filter @openreader/bootstrap start pnpm dev:raw",
"dev:raw": "next dev --turbopack -p 3003",
"build": "next build",
"build:bundle-guard": "node scripts/check-next-server-bundle.mjs",
"check:compute-boundary": "node scripts/check-compute-boundary.mjs",
- "start": "node scripts/openreader-entrypoint.mjs -- pnpm start:raw",
+ "start": "pnpm --filter @openreader/bootstrap start pnpm start:raw",
"start:raw": "next start -p 3003",
"lint": "next lint",
"test": "playwright test",
@@ -19,10 +19,10 @@
"compose": "docker compose -f docker/examples/compose.yml up",
"compose:full": "docker compose -f docker/examples/compose.full.yml up",
"compose:local": "docker compose -f docker/examples/compose.local.yml up --build",
- "migrate": "node drizzle/scripts/migrate.mjs",
- "migrate-fs": "node scripts/migrate-fs-v2.mjs",
- "migrate-fs:dry-run": "node scripts/migrate-fs-v2.mjs --dry-run true",
- "generate": "node drizzle/scripts/generate.mjs",
+ "migrate": "pnpm --filter @openreader/database migrate",
+ "migrate-fs": "pnpm --filter @openreader/bootstrap migrate-storage",
+ "migrate-fs:dry-run": "pnpm --filter @openreader/bootstrap migrate-storage --dry-run true",
+ "generate": "node packages/database/scripts/generate.mjs",
"docs:init": "pnpm --dir docs-site install",
"docs:dev": "pnpm --dir docs-site start",
"docs:build": "pnpm --dir docs-site build",
@@ -31,8 +31,8 @@
"docs:clear": "pnpm --dir docs-site clear",
"compute:dev": "pnpm --filter @openreader/compute-worker dev",
"compute:start": "pnpm --filter @openreader/compute-worker start",
- "compute:openapi:generate": "pnpm --dir compute-worker openapi:generate && openapi-typescript compute-worker/openapi.json -o src/lib/server/compute-worker/generated.ts",
- "compute:openapi:check": "pnpm compute:openapi:generate && git diff --exit-code -- compute-worker/openapi.json src/lib/server/compute-worker/generated.ts",
+ "compute:openapi:generate": "pnpm --dir packages/compute-worker openapi:generate && openapi-typescript packages/compute-worker/openapi.json -o src/lib/server/compute-worker/generated.ts",
+ "compute:openapi:check": "pnpm compute:openapi:generate && git diff --exit-code -- packages/compute-worker/openapi.json src/lib/server/compute-worker/generated.ts",
"lint:route-errors": "node scripts/check-route-error-responses.mjs"
},
"dependencies": {
@@ -42,20 +42,19 @@
"@microsoft/antissrf": "^1.0.0",
"@mozilla/readability": "^0.6.0",
"@napi-rs/canvas": "^0.1.100",
+ "@openreader/database": "workspace:*",
"@speech-sdk/core": "^0.15.0",
"@tanstack/react-query": "^5.101.0",
"@types/archiver": "^7.0.0",
"@vercel/analytics": "^1.6.1",
"archiver": "^7.0.1",
"better-auth": "^1.6.14",
- "better-sqlite3": "^12.10.0",
"cmpstr": "^3.3.0",
"compromise": "^14.15.1",
"core-js": "^3.49.0",
"dexie": "^4.4.3",
"dexie-react-hooks": "^4.4.0",
"dotenv": "^17.4.2",
- "drizzle-kit": "^0.31.10",
"drizzle-orm": "^0.45.2",
"epubjs": "^0.3.93",
"fast-xml-parser": "^5.8.0",
@@ -67,7 +66,6 @@
"next": "^15.5.19",
"openai": "^6.41.0",
"pdfjs-dist": "4.8.69",
- "pg": "^8.21.0",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"react": "^19.2.7",
@@ -98,6 +96,8 @@
"@types/react-dom": "^19.2.3",
"@types/turndown": "^5.0.6",
"@types/uuid": "^10.0.0",
+ "better-sqlite3": "^12.10.0",
+ "drizzle-kit": "^0.31.10",
"eslint": "^9.39.4",
"eslint-config-next": "^15.5.19",
"postcss": "^8.5.15",
diff --git a/packages/bootstrap/package.json b/packages/bootstrap/package.json
new file mode 100644
index 0000000..0f7a8fa
--- /dev/null
+++ b/packages/bootstrap/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@openreader/bootstrap",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "files": [
+ "src"
+ ],
+ "bin": {
+ "openreader": "./src/cli.mjs",
+ "openreader-migrate-storage": "./src/storage-migration.mjs"
+ },
+ "scripts": {
+ "start": "node src/cli.mjs --",
+ "migrate-storage": "node src/storage-migration.mjs"
+ },
+ "dependencies": {
+ "@aws-sdk/client-s3": "^3.1061.0",
+ "@openreader/database": "workspace:*",
+ "better-sqlite3": "^12.10.0",
+ "dotenv": "^17.4.2",
+ "ffmpeg-static": "^5.3.0",
+ "pg": "^8.21.0"
+ }
+}
diff --git a/scripts/openreader-entrypoint.mjs b/packages/bootstrap/src/cli.mjs
similarity index 77%
rename from scripts/openreader-entrypoint.mjs
rename to packages/bootstrap/src/cli.mjs
index 957ad63..887ecd7 100644
--- a/scripts/openreader-entrypoint.mjs
+++ b/packages/bootstrap/src/cli.mjs
@@ -3,11 +3,23 @@ import { spawn, spawnSync } from 'node:child_process';
import { randomBytes } from 'node:crypto';
import { once } from 'node:events';
import fs from 'node:fs';
-import os from 'node:os';
import path from 'node:path';
import process from 'node:process';
import { setTimeout as delay } from 'node:timers/promises';
+import { fileURLToPath } from 'node:url';
import * as dotenv from 'dotenv';
+import { runMigrations } from '@openreader/database/migrate';
+import { hasNatsBinary } from './embedded-nats.mjs';
+import {
+ detectHostForDefaultEndpoint,
+ hasWeedBinary,
+ isRunningInDocker,
+ loopbackS3Endpoint,
+ parseS3Endpoint,
+ parseUrlHost,
+ waitForEndpoint,
+} from './embedded-seaweedfs.mjs';
+import { resolveEmbeddedWorkerLaunch } from './embedded-worker.mjs';
function loadEnvFiles() {
const cwd = process.cwd();
@@ -49,83 +61,6 @@ function requireAuthEnv(env) {
}
}
-function isPrivateIPv4(address) {
- if (!address) return false;
- if (address.startsWith('10.')) return true;
- if (address.startsWith('192.168.')) return true;
- const m = /^172\.(\d+)\./.exec(address);
- if (m) {
- const second = Number.parseInt(m[1], 10);
- if (second >= 16 && second <= 31) return true;
- }
- return false;
-}
-
-function detectHostForDefaultEndpoint() {
- const interfaces = os.networkInterfaces();
- const ipv4 = [];
-
- for (const entries of Object.values(interfaces)) {
- for (const entry of entries || []) {
- if (!entry) continue;
- const family = typeof entry.family === 'string' ? entry.family : String(entry.family);
- if (family !== 'IPv4') continue;
- if (entry.internal) continue;
- ipv4.push(entry.address);
- }
- }
-
- const privateAddr = ipv4.find(isPrivateIPv4);
- if (privateAddr) return privateAddr;
- if (ipv4[0]) return ipv4[0];
- return '127.0.0.1';
-}
-
-function parseS3Endpoint(endpoint) {
- let url;
- try {
- url = new URL(endpoint);
- } catch {
- throw new Error(`Invalid S3_ENDPOINT: ${endpoint}`);
- }
-
- if (!url.hostname) {
- throw new Error(`Invalid S3_ENDPOINT host: ${endpoint}`);
- }
-
- const port = Number.parseInt(url.port || '8333', 10);
- if (!Number.isFinite(port) || port < 1 || port > 65535) {
- throw new Error(`Invalid S3_ENDPOINT port: ${endpoint}`);
- }
-
- return {
- hostname: url.hostname,
- port,
- normalized: `${url.protocol}//${url.hostname}:${port}`,
- };
-}
-
-function loopbackS3Endpoint(endpoint) {
- const parsed = parseS3Endpoint(endpoint);
- const url = new URL(parsed.normalized);
- return `${url.protocol}//127.0.0.1:${parsed.port}`;
-}
-
-function parseUrlHost(urlValue, fieldName) {
- let url;
- try {
- url = new URL(urlValue);
- } catch {
- throw new Error(`Invalid ${fieldName}: ${urlValue}`);
- }
-
- if (!url.hostname) {
- throw new Error(`Invalid ${fieldName} host: ${urlValue}`);
- }
-
- return url.hostname;
-}
-
function parseCommandFromArgs(argv) {
const marker = argv.indexOf('--');
if (marker >= 0) return argv.slice(marker + 1);
@@ -143,51 +78,6 @@ function forwardChildStream(stream, target) {
};
}
-function hasWeedBinary() {
- const probe = spawnSync('weed', ['version'], { stdio: 'ignore' });
- if (probe.error) return false;
- return true;
-}
-
-function hasNatsBinary() {
- const probe = spawnSync('nats-server', ['-v'], { stdio: 'ignore' });
- if (probe.error) return false;
- return true;
-}
-
-async function waitForEndpoint(url, timeoutSeconds) {
- const waitMs = Math.max(1, timeoutSeconds) * 1000;
- const deadline = Date.now() + waitMs;
-
- while (Date.now() < deadline) {
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 2000);
-
- try {
- const res = await fetch(url, { method: 'GET', signal: controller.signal });
- if (res) return;
- } catch {
- // retry
- } finally {
- clearTimeout(timeoutId);
- }
- await delay(1000);
- }
-
- throw new Error(`Embedded weed mini did not become ready at ${url} within ${timeoutSeconds}s.`);
-}
-
-function isRunningInDocker() {
- if (process.platform !== 'linux') return false;
- if (fs.existsSync('/.dockerenv')) return true;
- try {
- const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
- return /(docker|containerd|kubepods|podman)/i.test(cgroup);
- } catch {
- return false;
- }
-}
-
function spawnMainCommand(command, env) {
const [cmd, ...args] = command;
const child = spawn(cmd, args, {
@@ -219,53 +109,13 @@ function spawnMainCommand(command, env) {
return { child, exitPromise };
}
-function resolveEmbeddedWorkerLaunch() {
- const candidateDirs = [
- path.join(process.cwd(), 'embedded-compute-worker'),
- path.join(process.cwd(), 'compute-worker'),
- ];
-
- for (const workerDir of candidateDirs) {
- const serverEntry = path.join(workerDir, 'src', 'server.ts');
- if (!fs.existsSync(serverEntry)) continue;
- return {
- cmd: process.execPath,
- args: ['--import', 'tsx', 'src/server.ts'],
- cwd: workerDir,
- };
- }
-
- throw new Error(
- 'Could not find an embedded compute worker runtime. '
- + 'Include embedded-compute-worker/src/server.ts in the runtime image or keep compute-worker available locally.',
- );
-}
-
-function runDbMigrations(env) {
- const migrateScript = path.join(process.cwd(), 'drizzle', 'scripts', 'migrate.mjs');
- if (!fs.existsSync(migrateScript)) {
- throw new Error(`Could not find migration script at ${migrateScript}`);
- }
-
+async function runDbMigrations(env) {
console.log('Running database migrations...');
- const migration = spawnSync(process.execPath, [migrateScript], {
- env,
- stdio: 'inherit',
- });
-
- if (migration.error) {
- throw migration.error;
- }
- if (typeof migration.status === 'number' && migration.status !== 0) {
- throw new Error(`Database migrations failed with exit code ${migration.status}.`);
- }
+ await runMigrations({ cwd: process.cwd(), env });
}
function runStorageMigrations(env) {
- const migrateScript = path.join(process.cwd(), 'scripts', 'migrate-fs-v2.mjs');
- if (!fs.existsSync(migrateScript)) {
- throw new Error(`Could not find storage migration script at ${migrateScript}`);
- }
+ const migrateScript = fileURLToPath(new URL('./storage-migration.mjs', import.meta.url));
console.log('Running storage migrations (v2)...');
const migration = spawnSync(process.execPath, [migrateScript, '--dry-run', 'false', '--delete-local', 'false'], {
@@ -335,7 +185,7 @@ async function main() {
const command = parseCommandFromArgs(process.argv.slice(2));
if (command.length === 0) {
- console.error('Usage: node scripts/openreader-entrypoint.mjs -- [args]');
+ console.error('Usage: openreader -- [args]');
process.exit(2);
}
@@ -424,7 +274,7 @@ async function main() {
try {
const shouldRunDbMigrations = resolveBooleanEnv(runtimeEnv, 'RUN_DRIZZLE_MIGRATIONS', true);
if (shouldRunDbMigrations) {
- runDbMigrations(runtimeEnv);
+ await runDbMigrations(runtimeEnv);
}
if (useEmbeddedWeed) {
@@ -482,7 +332,7 @@ async function main() {
console.log('Starting embedded SeaweedFS weed mini...');
launchWeed(runtimeEnv.S3_ENDPOINT);
const startupEndpoint = parseS3Endpoint(runtimeEnv.S3_ENDPOINT);
- await waitForEndpoint(`http://127.0.0.1:${startupEndpoint.port}`, waitTimeout);
+ await waitForEndpoint(`http://127.0.0.1:${startupEndpoint.port}`, waitTimeout, 'Embedded SeaweedFS');
console.log(`Embedded SeaweedFS is ready at ${runtimeEnv.S3_ENDPOINT}`);
}
@@ -555,7 +405,7 @@ async function main() {
console.error(`Embedded nats-server failed to start: ${error instanceof Error ? error.message : String(error)}`);
scheduleFatalShutdown('nats-server', null, null, 'failed to start');
});
- await waitForEndpoint(`http://127.0.0.1:${embeddedNatsMonitorPort}/healthz`, 20);
+ await waitForEndpoint(`http://127.0.0.1:${embeddedNatsMonitorPort}/healthz`, 20, 'Embedded nats-server');
console.log(`Embedded nats-server is ready at nats://127.0.0.1:${embeddedNatsPort}`);
console.log(`Starting embedded compute-worker on 127.0.0.1:${embeddedWorkerPort}...`);
@@ -589,7 +439,7 @@ async function main() {
console.error(`Embedded compute-worker failed to start: ${error instanceof Error ? error.message : String(error)}`);
scheduleFatalShutdown('compute-worker', null, null, 'failed to start');
});
- await waitForEndpoint(`http://127.0.0.1:${embeddedWorkerPort}/health/ready`, 30);
+ await waitForEndpoint(`http://127.0.0.1:${embeddedWorkerPort}/health/ready`, 30, 'Embedded compute-worker');
console.log(`Embedded compute-worker is ready at http://127.0.0.1:${embeddedWorkerPort}`);
} else if (!runtimeEnv.COMPUTE_WORKER_URL?.trim() || !runtimeEnv.COMPUTE_WORKER_TOKEN?.trim()) {
throw new Error('COMPUTE_WORKER_URL and COMPUTE_WORKER_TOKEN are required when embedded compute worker startup is disabled.');
diff --git a/packages/bootstrap/src/embedded-nats.mjs b/packages/bootstrap/src/embedded-nats.mjs
new file mode 100644
index 0000000..d9d1238
--- /dev/null
+++ b/packages/bootstrap/src/embedded-nats.mjs
@@ -0,0 +1,5 @@
+import { spawnSync } from 'node:child_process';
+
+export function hasNatsBinary() {
+ return !spawnSync('nats-server', ['-v'], { stdio: 'ignore' }).error;
+}
diff --git a/packages/bootstrap/src/embedded-seaweedfs.mjs b/packages/bootstrap/src/embedded-seaweedfs.mjs
new file mode 100644
index 0000000..4fc40d8
--- /dev/null
+++ b/packages/bootstrap/src/embedded-seaweedfs.mjs
@@ -0,0 +1,95 @@
+import { spawnSync } from 'node:child_process';
+import fs from 'node:fs';
+import os from 'node:os';
+import process from 'node:process';
+import { setTimeout as delay } from 'node:timers/promises';
+
+function isPrivateIPv4(address) {
+ if (!address) return false;
+ if (address.startsWith('10.')) return true;
+ if (address.startsWith('192.168.')) return true;
+ const match = /^172\.(\d+)\./.exec(address);
+ if (!match) return false;
+ const second = Number.parseInt(match[1], 10);
+ return second >= 16 && second <= 31;
+}
+
+export function hasWeedBinary() {
+ return !spawnSync('weed', ['version'], { stdio: 'ignore' }).error;
+}
+
+export function detectHostForDefaultEndpoint() {
+ const ipv4 = Object.values(os.networkInterfaces())
+ .flatMap((entries) => entries || [])
+ .filter((entry) => entry && !entry.internal && String(entry.family) === 'IPv4')
+ .map((entry) => entry.address);
+
+ return ipv4.find(isPrivateIPv4) || ipv4[0] || '127.0.0.1';
+}
+
+export function parseS3Endpoint(endpoint) {
+ let url;
+ try {
+ url = new URL(endpoint);
+ } catch {
+ throw new Error(`Invalid S3_ENDPOINT: ${endpoint}`);
+ }
+
+ if (!url.hostname) throw new Error(`Invalid S3_ENDPOINT host: ${endpoint}`);
+ const port = Number.parseInt(url.port || '8333', 10);
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
+ throw new Error(`Invalid S3_ENDPOINT port: ${endpoint}`);
+ }
+
+ return {
+ hostname: url.hostname,
+ port,
+ normalized: `${url.protocol}//${url.hostname}:${port}`,
+ };
+}
+
+export function loopbackS3Endpoint(endpoint) {
+ const parsed = parseS3Endpoint(endpoint);
+ const url = new URL(parsed.normalized);
+ return `${url.protocol}//127.0.0.1:${parsed.port}`;
+}
+
+export function parseUrlHost(urlValue, fieldName) {
+ let url;
+ try {
+ url = new URL(urlValue);
+ } catch {
+ throw new Error(`Invalid ${fieldName}: ${urlValue}`);
+ }
+
+ if (!url.hostname) throw new Error(`Invalid ${fieldName} host: ${urlValue}`);
+ return url.hostname;
+}
+
+export async function waitForEndpoint(url, timeoutSeconds, serviceName = 'service') {
+ const deadline = Date.now() + Math.max(1, timeoutSeconds) * 1000;
+ while (Date.now() < deadline) {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 2000);
+ try {
+ const response = await fetch(url, { method: 'GET', signal: controller.signal });
+ if (response) return;
+ } catch {
+ // Retry until the startup deadline.
+ } finally {
+ clearTimeout(timeoutId);
+ }
+ await delay(1000);
+ }
+ throw new Error(`${serviceName} did not become ready at ${url} within ${timeoutSeconds}s.`);
+}
+
+export function isRunningInDocker() {
+ if (process.platform !== 'linux') return false;
+ if (fs.existsSync('/.dockerenv')) return true;
+ try {
+ return /(docker|containerd|kubepods|podman)/i.test(fs.readFileSync('/proc/1/cgroup', 'utf8'));
+ } catch {
+ return false;
+ }
+}
diff --git a/packages/bootstrap/src/embedded-worker.mjs b/packages/bootstrap/src/embedded-worker.mjs
new file mode 100644
index 0000000..f4ea4b6
--- /dev/null
+++ b/packages/bootstrap/src/embedded-worker.mjs
@@ -0,0 +1,30 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import process from 'node:process';
+import { fileURLToPath } from 'node:url';
+
+const bootstrapDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+
+export function resolveEmbeddedWorkerLaunch() {
+ const candidateDirs = [
+ path.resolve(bootstrapDir, '..', 'embedded-compute-worker'),
+ path.resolve(bootstrapDir, '..', 'compute-worker'),
+ path.join(process.cwd(), 'embedded-compute-worker'),
+ path.join(process.cwd(), 'packages', 'compute-worker'),
+ path.join(process.cwd(), 'compute-worker'),
+ ];
+
+ for (const workerDir of candidateDirs) {
+ if (!fs.existsSync(path.join(workerDir, 'src', 'server.ts'))) continue;
+ return {
+ cmd: process.execPath,
+ args: ['--import', 'tsx', 'src/server.ts'],
+ cwd: workerDir,
+ };
+ }
+
+ throw new Error(
+ 'Could not find an embedded compute worker runtime. '
+ + 'Include embedded-compute-worker/src/server.ts in the runtime image or keep packages/compute-worker available locally.',
+ );
+}
diff --git a/scripts/migrate-fs-v2.mjs b/packages/bootstrap/src/storage-migration.mjs
similarity index 100%
rename from scripts/migrate-fs-v2.mjs
rename to packages/bootstrap/src/storage-migration.mjs
diff --git a/compute-worker/.env.example b/packages/compute-worker/.env.example
similarity index 100%
rename from compute-worker/.env.example
rename to packages/compute-worker/.env.example
diff --git a/compute-worker/Dockerfile b/packages/compute-worker/Dockerfile
similarity index 72%
rename from compute-worker/Dockerfile
rename to packages/compute-worker/Dockerfile
index 356190f..bcec9ac 100644
--- a/compute-worker/Dockerfile
+++ b/packages/compute-worker/Dockerfile
@@ -1,19 +1,17 @@
FROM node:lts AS deploy-stage
-RUN npm install -g pnpm@11.1.2
+RUN npm install -g pnpm@10.33.4
WORKDIR /workspace
COPY .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml ./
-COPY compute-worker/package.json compute-worker/package.json
-COPY compute-worker compute-worker
+COPY packages/compute-worker/package.json packages/compute-worker/package.json
+COPY packages/compute-worker packages/compute-worker
RUN pnpm --config.inject-workspace-packages=true --filter @openreader/compute-worker deploy /opt/compute-worker
FROM node:lts
-RUN npm install -g pnpm@11.1.2
-
WORKDIR /workspace
COPY --from=deploy-stage /opt/compute-worker ./
diff --git a/compute-worker/openapi.json b/packages/compute-worker/openapi.json
similarity index 100%
rename from compute-worker/openapi.json
rename to packages/compute-worker/openapi.json
diff --git a/compute-worker/package.json b/packages/compute-worker/package.json
similarity index 100%
rename from compute-worker/package.json
rename to packages/compute-worker/package.json
diff --git a/compute-worker/scripts/generate-openapi.ts b/packages/compute-worker/scripts/generate-openapi.ts
similarity index 100%
rename from compute-worker/scripts/generate-openapi.ts
rename to packages/compute-worker/scripts/generate-openapi.ts
diff --git a/compute-worker/src/api/app.ts b/packages/compute-worker/src/api/app.ts
similarity index 100%
rename from compute-worker/src/api/app.ts
rename to packages/compute-worker/src/api/app.ts
diff --git a/compute-worker/src/api/compute-operation.ts b/packages/compute-worker/src/api/compute-operation.ts
similarity index 100%
rename from compute-worker/src/api/compute-operation.ts
rename to packages/compute-worker/src/api/compute-operation.ts
diff --git a/compute-worker/src/api/http-hooks.ts b/packages/compute-worker/src/api/http-hooks.ts
similarity index 100%
rename from compute-worker/src/api/http-hooks.ts
rename to packages/compute-worker/src/api/http-hooks.ts
diff --git a/compute-worker/src/api/routes.ts b/packages/compute-worker/src/api/routes.ts
similarity index 100%
rename from compute-worker/src/api/routes.ts
rename to packages/compute-worker/src/api/routes.ts
diff --git a/compute-worker/src/api/schemas.ts b/packages/compute-worker/src/api/schemas.ts
similarity index 100%
rename from compute-worker/src/api/schemas.ts
rename to packages/compute-worker/src/api/schemas.ts
diff --git a/compute-worker/src/api/types.ts b/packages/compute-worker/src/api/types.ts
similarity index 100%
rename from compute-worker/src/api/types.ts
rename to packages/compute-worker/src/api/types.ts
diff --git a/compute-worker/src/inference/pdf/assets/LICENSE.txt b/packages/compute-worker/src/inference/pdf/assets/LICENSE.txt
similarity index 100%
rename from compute-worker/src/inference/pdf/assets/LICENSE.txt
rename to packages/compute-worker/src/inference/pdf/assets/LICENSE.txt
diff --git a/compute-worker/src/inference/pdf/assets/manifest.json b/packages/compute-worker/src/inference/pdf/assets/manifest.json
similarity index 100%
rename from compute-worker/src/inference/pdf/assets/manifest.json
rename to packages/compute-worker/src/inference/pdf/assets/manifest.json
diff --git a/compute-worker/src/inference/pdf/document-layout.ts b/packages/compute-worker/src/inference/pdf/document-layout.ts
similarity index 100%
rename from compute-worker/src/inference/pdf/document-layout.ts
rename to packages/compute-worker/src/inference/pdf/document-layout.ts
diff --git a/compute-worker/src/inference/pdf/layout-model.ts b/packages/compute-worker/src/inference/pdf/layout-model.ts
similarity index 100%
rename from compute-worker/src/inference/pdf/layout-model.ts
rename to packages/compute-worker/src/inference/pdf/layout-model.ts
diff --git a/compute-worker/src/inference/pdf/model.ts b/packages/compute-worker/src/inference/pdf/model.ts
similarity index 100%
rename from compute-worker/src/inference/pdf/model.ts
rename to packages/compute-worker/src/inference/pdf/model.ts
diff --git a/compute-worker/src/inference/pdf/normalize-text.ts b/packages/compute-worker/src/inference/pdf/normalize-text.ts
similarity index 100%
rename from compute-worker/src/inference/pdf/normalize-text.ts
rename to packages/compute-worker/src/inference/pdf/normalize-text.ts
diff --git a/compute-worker/src/inference/pdf/parse.ts b/packages/compute-worker/src/inference/pdf/parse.ts
similarity index 100%
rename from compute-worker/src/inference/pdf/parse.ts
rename to packages/compute-worker/src/inference/pdf/parse.ts
diff --git a/compute-worker/src/inference/pdf/pdfjs.ts b/packages/compute-worker/src/inference/pdf/pdfjs.ts
similarity index 100%
rename from compute-worker/src/inference/pdf/pdfjs.ts
rename to packages/compute-worker/src/inference/pdf/pdfjs.ts
diff --git a/compute-worker/src/inference/pdf/render.ts b/packages/compute-worker/src/inference/pdf/render.ts
similarity index 100%
rename from compute-worker/src/inference/pdf/render.ts
rename to packages/compute-worker/src/inference/pdf/render.ts
diff --git a/compute-worker/src/inference/pdf/stitch.ts b/packages/compute-worker/src/inference/pdf/stitch.ts
similarity index 100%
rename from compute-worker/src/inference/pdf/stitch.ts
rename to packages/compute-worker/src/inference/pdf/stitch.ts
diff --git a/compute-worker/src/inference/runtime.ts b/packages/compute-worker/src/inference/runtime.ts
similarity index 100%
rename from compute-worker/src/inference/runtime.ts
rename to packages/compute-worker/src/inference/runtime.ts
diff --git a/compute-worker/src/inference/whisper/align.ts b/packages/compute-worker/src/inference/whisper/align.ts
similarity index 100%
rename from compute-worker/src/inference/whisper/align.ts
rename to packages/compute-worker/src/inference/whisper/align.ts
diff --git a/compute-worker/src/inference/whisper/assets/LICENSE.txt b/packages/compute-worker/src/inference/whisper/assets/LICENSE.txt
similarity index 100%
rename from compute-worker/src/inference/whisper/assets/LICENSE.txt
rename to packages/compute-worker/src/inference/whisper/assets/LICENSE.txt
diff --git a/compute-worker/src/inference/whisper/assets/manifest.json b/packages/compute-worker/src/inference/whisper/assets/manifest.json
similarity index 100%
rename from compute-worker/src/inference/whisper/assets/manifest.json
rename to packages/compute-worker/src/inference/whisper/assets/manifest.json
diff --git a/compute-worker/src/inference/whisper/assets/mel_filters.npz b/packages/compute-worker/src/inference/whisper/assets/mel_filters.npz
similarity index 100%
rename from compute-worker/src/inference/whisper/assets/mel_filters.npz
rename to packages/compute-worker/src/inference/whisper/assets/mel_filters.npz
diff --git a/compute-worker/src/inference/whisper/decoder.ts b/packages/compute-worker/src/inference/whisper/decoder.ts
similarity index 100%
rename from compute-worker/src/inference/whisper/decoder.ts
rename to packages/compute-worker/src/inference/whisper/decoder.ts
diff --git a/compute-worker/src/inference/whisper/model.ts b/packages/compute-worker/src/inference/whisper/model.ts
similarity index 100%
rename from compute-worker/src/inference/whisper/model.ts
rename to packages/compute-worker/src/inference/whisper/model.ts
diff --git a/compute-worker/src/inference/whisper/timestamps.ts b/packages/compute-worker/src/inference/whisper/timestamps.ts
similarity index 100%
rename from compute-worker/src/inference/whisper/timestamps.ts
rename to packages/compute-worker/src/inference/whisper/timestamps.ts
diff --git a/compute-worker/src/infrastructure/config.ts b/packages/compute-worker/src/infrastructure/config.ts
similarity index 100%
rename from compute-worker/src/infrastructure/config.ts
rename to packages/compute-worker/src/infrastructure/config.ts
diff --git a/compute-worker/src/infrastructure/json-codec.ts b/packages/compute-worker/src/infrastructure/json-codec.ts
similarity index 100%
rename from compute-worker/src/infrastructure/json-codec.ts
rename to packages/compute-worker/src/infrastructure/json-codec.ts
diff --git a/compute-worker/src/infrastructure/nats-adapters.ts b/packages/compute-worker/src/infrastructure/nats-adapters.ts
similarity index 100%
rename from compute-worker/src/infrastructure/nats-adapters.ts
rename to packages/compute-worker/src/infrastructure/nats-adapters.ts
diff --git a/compute-worker/src/infrastructure/nats-session.ts b/packages/compute-worker/src/infrastructure/nats-session.ts
similarity index 100%
rename from compute-worker/src/infrastructure/nats-session.ts
rename to packages/compute-worker/src/infrastructure/nats-session.ts
diff --git a/compute-worker/src/infrastructure/nats.ts b/packages/compute-worker/src/infrastructure/nats.ts
similarity index 100%
rename from compute-worker/src/infrastructure/nats.ts
rename to packages/compute-worker/src/infrastructure/nats.ts
diff --git a/compute-worker/src/infrastructure/platform.ts b/packages/compute-worker/src/infrastructure/platform.ts
similarity index 100%
rename from compute-worker/src/infrastructure/platform.ts
rename to packages/compute-worker/src/infrastructure/platform.ts
diff --git a/compute-worker/src/infrastructure/storage.ts b/packages/compute-worker/src/infrastructure/storage.ts
similarity index 100%
rename from compute-worker/src/infrastructure/storage.ts
rename to packages/compute-worker/src/infrastructure/storage.ts
diff --git a/compute-worker/src/jobs/handlers.ts b/packages/compute-worker/src/jobs/handlers.ts
similarity index 100%
rename from compute-worker/src/jobs/handlers.ts
rename to packages/compute-worker/src/jobs/handlers.ts
diff --git a/compute-worker/src/jobs/pdf-artifact-persistence.ts b/packages/compute-worker/src/jobs/pdf-artifact-persistence.ts
similarity index 100%
rename from compute-worker/src/jobs/pdf-artifact-persistence.ts
rename to packages/compute-worker/src/jobs/pdf-artifact-persistence.ts
diff --git a/compute-worker/src/jobs/pdf-progress.ts b/packages/compute-worker/src/jobs/pdf-progress.ts
similarity index 100%
rename from compute-worker/src/jobs/pdf-progress.ts
rename to packages/compute-worker/src/jobs/pdf-progress.ts
diff --git a/compute-worker/src/jobs/worker-loop-policy.ts b/packages/compute-worker/src/jobs/worker-loop-policy.ts
similarity index 100%
rename from compute-worker/src/jobs/worker-loop-policy.ts
rename to packages/compute-worker/src/jobs/worker-loop-policy.ts
diff --git a/compute-worker/src/jobs/worker-loop.ts b/packages/compute-worker/src/jobs/worker-loop.ts
similarity index 100%
rename from compute-worker/src/jobs/worker-loop.ts
rename to packages/compute-worker/src/jobs/worker-loop.ts
diff --git a/compute-worker/src/operations/contracts.ts b/packages/compute-worker/src/operations/contracts.ts
similarity index 100%
rename from compute-worker/src/operations/contracts.ts
rename to packages/compute-worker/src/operations/contracts.ts
diff --git a/compute-worker/src/operations/index.ts b/packages/compute-worker/src/operations/index.ts
similarity index 100%
rename from compute-worker/src/operations/index.ts
rename to packages/compute-worker/src/operations/index.ts
diff --git a/compute-worker/src/operations/keys.ts b/packages/compute-worker/src/operations/keys.ts
similarity index 100%
rename from compute-worker/src/operations/keys.ts
rename to packages/compute-worker/src/operations/keys.ts
diff --git a/compute-worker/src/operations/reconciliation.ts b/packages/compute-worker/src/operations/reconciliation.ts
similarity index 100%
rename from compute-worker/src/operations/reconciliation.ts
rename to packages/compute-worker/src/operations/reconciliation.ts
diff --git a/compute-worker/src/operations/recovery.ts b/packages/compute-worker/src/operations/recovery.ts
similarity index 100%
rename from compute-worker/src/operations/recovery.ts
rename to packages/compute-worker/src/operations/recovery.ts
diff --git a/compute-worker/src/operations/service.ts b/packages/compute-worker/src/operations/service.ts
similarity index 100%
rename from compute-worker/src/operations/service.ts
rename to packages/compute-worker/src/operations/service.ts
diff --git a/compute-worker/src/operations/sse.ts b/packages/compute-worker/src/operations/sse.ts
similarity index 100%
rename from compute-worker/src/operations/sse.ts
rename to packages/compute-worker/src/operations/sse.ts
diff --git a/compute-worker/src/operations/state-machine.ts b/packages/compute-worker/src/operations/state-machine.ts
similarity index 100%
rename from compute-worker/src/operations/state-machine.ts
rename to packages/compute-worker/src/operations/state-machine.ts
diff --git a/compute-worker/src/operations/types.ts b/packages/compute-worker/src/operations/types.ts
similarity index 100%
rename from compute-worker/src/operations/types.ts
rename to packages/compute-worker/src/operations/types.ts
diff --git a/compute-worker/src/server.ts b/packages/compute-worker/src/server.ts
similarity index 100%
rename from compute-worker/src/server.ts
rename to packages/compute-worker/src/server.ts
diff --git a/compute-worker/src/storage/artifact-addressing.ts b/packages/compute-worker/src/storage/artifact-addressing.ts
similarity index 100%
rename from compute-worker/src/storage/artifact-addressing.ts
rename to packages/compute-worker/src/storage/artifact-addressing.ts
diff --git a/compute-worker/tests/api/routes.test.ts b/packages/compute-worker/tests/api/routes.test.ts
similarity index 100%
rename from compute-worker/tests/api/routes.test.ts
rename to packages/compute-worker/tests/api/routes.test.ts
diff --git a/compute-worker/tests/compute/algorithms/pdf-merge-text-with-regions.test.ts b/packages/compute-worker/tests/compute/algorithms/pdf-merge-text-with-regions.test.ts
similarity index 100%
rename from compute-worker/tests/compute/algorithms/pdf-merge-text-with-regions.test.ts
rename to packages/compute-worker/tests/compute/algorithms/pdf-merge-text-with-regions.test.ts
diff --git a/compute-worker/tests/compute/algorithms/pdf-parse-normalize-text-items.test.ts b/packages/compute-worker/tests/compute/algorithms/pdf-parse-normalize-text-items.test.ts
similarity index 100%
rename from compute-worker/tests/compute/algorithms/pdf-parse-normalize-text-items.test.ts
rename to packages/compute-worker/tests/compute/algorithms/pdf-parse-normalize-text-items.test.ts
diff --git a/compute-worker/tests/compute/algorithms/pdf-stitch-cross-page-blocks.test.ts b/packages/compute-worker/tests/compute/algorithms/pdf-stitch-cross-page-blocks.test.ts
similarity index 100%
rename from compute-worker/tests/compute/algorithms/pdf-stitch-cross-page-blocks.test.ts
rename to packages/compute-worker/tests/compute/algorithms/pdf-stitch-cross-page-blocks.test.ts
diff --git a/compute-worker/tests/compute/algorithms/support/document-fixtures.ts b/packages/compute-worker/tests/compute/algorithms/support/document-fixtures.ts
similarity index 93%
rename from compute-worker/tests/compute/algorithms/support/document-fixtures.ts
rename to packages/compute-worker/tests/compute/algorithms/support/document-fixtures.ts
index 5a3d697..4d8d23d 100644
--- a/compute-worker/tests/compute/algorithms/support/document-fixtures.ts
+++ b/packages/compute-worker/tests/compute/algorithms/support/document-fixtures.ts
@@ -1,4 +1,4 @@
-import type { BaseDocument } from '../../../../../src/types/documents';
+import type { BaseDocument } from '../../../../../../src/types/documents';
import type { ParsedPdfBlock, ParsedPdfBlockKind, ParsedPdfDocument, ParsedPdfPage } from '../../../../src/api/types';
export function makeBaseDocument(overrides: Partial = {}): BaseDocument {
diff --git a/compute-worker/tests/compute/algorithms/whisper-alignment-mapping.test.ts b/packages/compute-worker/tests/compute/algorithms/whisper-alignment-mapping.test.ts
similarity index 100%
rename from compute-worker/tests/compute/algorithms/whisper-alignment-mapping.test.ts
rename to packages/compute-worker/tests/compute/algorithms/whisper-alignment-mapping.test.ts
diff --git a/compute-worker/tests/compute/algorithms/whisper-decoder.test.ts b/packages/compute-worker/tests/compute/algorithms/whisper-decoder.test.ts
similarity index 100%
rename from compute-worker/tests/compute/algorithms/whisper-decoder.test.ts
rename to packages/compute-worker/tests/compute/algorithms/whisper-decoder.test.ts
diff --git a/compute-worker/tests/compute/algorithms/whisper-spectral.test.ts b/packages/compute-worker/tests/compute/algorithms/whisper-spectral.test.ts
similarity index 100%
rename from compute-worker/tests/compute/algorithms/whisper-spectral.test.ts
rename to packages/compute-worker/tests/compute/algorithms/whisper-spectral.test.ts
diff --git a/compute-worker/tests/compute/algorithms/whisper-token-timestamps.test.ts b/packages/compute-worker/tests/compute/algorithms/whisper-token-timestamps.test.ts
similarity index 100%
rename from compute-worker/tests/compute/algorithms/whisper-token-timestamps.test.ts
rename to packages/compute-worker/tests/compute/algorithms/whisper-token-timestamps.test.ts
diff --git a/compute-worker/tests/compute/control-plane/orchestrator.test.ts b/packages/compute-worker/tests/compute/control-plane/orchestrator.test.ts
similarity index 100%
rename from compute-worker/tests/compute/control-plane/orchestrator.test.ts
rename to packages/compute-worker/tests/compute/control-plane/orchestrator.test.ts
diff --git a/compute-worker/tests/compute/control-plane/run-layout-model.test.ts b/packages/compute-worker/tests/compute/control-plane/run-layout-model.test.ts
similarity index 100%
rename from compute-worker/tests/compute/control-plane/run-layout-model.test.ts
rename to packages/compute-worker/tests/compute/control-plane/run-layout-model.test.ts
diff --git a/compute-worker/tests/compute/control-plane/sse.test.ts b/packages/compute-worker/tests/compute/control-plane/sse.test.ts
similarity index 100%
rename from compute-worker/tests/compute/control-plane/sse.test.ts
rename to packages/compute-worker/tests/compute/control-plane/sse.test.ts
diff --git a/compute-worker/tests/compute/control-plane/state-machine.test.ts b/packages/compute-worker/tests/compute/control-plane/state-machine.test.ts
similarity index 100%
rename from compute-worker/tests/compute/control-plane/state-machine.test.ts
rename to packages/compute-worker/tests/compute/control-plane/state-machine.test.ts
diff --git a/compute-worker/tests/compute/helpers/in-memory-control-plane.ts b/packages/compute-worker/tests/compute/helpers/in-memory-control-plane.ts
similarity index 100%
rename from compute-worker/tests/compute/helpers/in-memory-control-plane.ts
rename to packages/compute-worker/tests/compute/helpers/in-memory-control-plane.ts
diff --git a/compute-worker/tests/fixtures/fake-control-plane.ts b/packages/compute-worker/tests/fixtures/fake-control-plane.ts
similarity index 100%
rename from compute-worker/tests/fixtures/fake-control-plane.ts
rename to packages/compute-worker/tests/fixtures/fake-control-plane.ts
diff --git a/compute-worker/tests/setup-env.ts b/packages/compute-worker/tests/setup-env.ts
similarity index 100%
rename from compute-worker/tests/setup-env.ts
rename to packages/compute-worker/tests/setup-env.ts
diff --git a/compute-worker/tests/tsconfig.json b/packages/compute-worker/tests/tsconfig.json
similarity index 100%
rename from compute-worker/tests/tsconfig.json
rename to packages/compute-worker/tests/tsconfig.json
diff --git a/compute-worker/tests/unit/jetstream-adapters.test.ts b/packages/compute-worker/tests/unit/jetstream-adapters.test.ts
similarity index 95%
rename from compute-worker/tests/unit/jetstream-adapters.test.ts
rename to packages/compute-worker/tests/unit/jetstream-adapters.test.ts
index 8425061..d694594 100644
--- a/compute-worker/tests/unit/jetstream-adapters.test.ts
+++ b/packages/compute-worker/tests/unit/jetstream-adapters.test.ts
@@ -45,6 +45,17 @@ class FakeKvStore implements KvStoreLike {
this.revision += 1;
this.data.set(key, { operation: 'PUT', value: data.slice(), revision: this.revision });
}
+
+ async keys(filter?: string | string[]): Promise> {
+ const keys = Array.from(this.data.keys());
+ return {
+ async *[Symbol.asyncIterator]() {
+ for (const key of keys) {
+ yield key;
+ }
+ }
+ };
+ }
}
class FakeJetStream {
diff --git a/compute-worker/tests/unit/key-and-progress.test.ts b/packages/compute-worker/tests/unit/key-and-progress.test.ts
similarity index 100%
rename from compute-worker/tests/unit/key-and-progress.test.ts
rename to packages/compute-worker/tests/unit/key-and-progress.test.ts
diff --git a/compute-worker/tests/unit/orphan-recovery.test.ts b/packages/compute-worker/tests/unit/orphan-recovery.test.ts
similarity index 88%
rename from compute-worker/tests/unit/orphan-recovery.test.ts
rename to packages/compute-worker/tests/unit/orphan-recovery.test.ts
index 7ec771d..54db314 100644
--- a/compute-worker/tests/unit/orphan-recovery.test.ts
+++ b/packages/compute-worker/tests/unit/orphan-recovery.test.ts
@@ -24,8 +24,8 @@ describe('orphan recovery', () => {
});
const firstSweep = await recoverOrphanedOperations({
- operationStateStore: fake.deps.operationStateStore,
- orchestrator: fake.deps.orchestrator,
+ operationStateStore: fake.deps.operationStateStore!,
+ orchestrator: fake.deps.orchestrator!,
whisperTimeoutMs: 30_000,
pdfTimeoutMs: 300_000,
opStaleMs: 1_800_000,
@@ -38,8 +38,8 @@ describe('orphan recovery', () => {
vi.advanceTimersByTime(31_000);
const secondSweep = await recoverOrphanedOperations({
- operationStateStore: fake.deps.operationStateStore,
- orchestrator: fake.deps.orchestrator,
+ operationStateStore: fake.deps.operationStateStore!,
+ orchestrator: fake.deps.orchestrator!,
whisperTimeoutMs: 30_000,
pdfTimeoutMs: 300_000,
opStaleMs: 1_800_000,
diff --git a/compute-worker/tests/unit/pdf-artifact-persistence.test.ts b/packages/compute-worker/tests/unit/pdf-artifact-persistence.test.ts
similarity index 100%
rename from compute-worker/tests/unit/pdf-artifact-persistence.test.ts
rename to packages/compute-worker/tests/unit/pdf-artifact-persistence.test.ts
diff --git a/compute-worker/tests/unit/worker-loop-policy.test.ts b/packages/compute-worker/tests/unit/worker-loop-policy.test.ts
similarity index 100%
rename from compute-worker/tests/unit/worker-loop-policy.test.ts
rename to packages/compute-worker/tests/unit/worker-loop-policy.test.ts
diff --git a/compute-worker/tests/unit/worker-loop.test.ts b/packages/compute-worker/tests/unit/worker-loop.test.ts
similarity index 100%
rename from compute-worker/tests/unit/worker-loop.test.ts
rename to packages/compute-worker/tests/unit/worker-loop.test.ts
diff --git a/compute-worker/tsconfig.json b/packages/compute-worker/tsconfig.json
similarity index 73%
rename from compute-worker/tsconfig.json
rename to packages/compute-worker/tsconfig.json
index 820e03b..428e252 100644
--- a/compute-worker/tsconfig.json
+++ b/packages/compute-worker/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "../tsconfig.json",
+ "extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": true,
},
diff --git a/packages/database/bin/migrate.mjs b/packages/database/bin/migrate.mjs
new file mode 100755
index 0000000..39fd805
--- /dev/null
+++ b/packages/database/bin/migrate.mjs
@@ -0,0 +1,9 @@
+#!/usr/bin/env node
+import { runMigrations } from '../src/migrate.mjs';
+
+try {
+ await runMigrations();
+} catch (error) {
+ console.error(error instanceof Error ? error.message : String(error));
+ process.exitCode = 1;
+}
diff --git a/drizzle.config.pg.ts b/packages/database/drizzle.config.pg.ts
similarity index 72%
rename from drizzle.config.pg.ts
rename to packages/database/drizzle.config.pg.ts
index 663a9f7..b2ff9f6 100644
--- a/drizzle.config.pg.ts
+++ b/packages/database/drizzle.config.pg.ts
@@ -10,8 +10,8 @@ if (!url) {
}
export default {
- schema: ['./src/db/schema_postgres.ts', './src/db/schema_auth_postgres.ts'],
- out: './drizzle/postgres',
+ schema: ['./packages/database/src/schema_postgres.ts', './packages/database/src/schema_auth_postgres.ts'],
+ out: './packages/database/migrations/postgres',
dialect: 'postgresql',
dbCredentials: {
url,
diff --git a/drizzle.config.sqlite.ts b/packages/database/drizzle.config.sqlite.ts
similarity index 57%
rename from drizzle.config.sqlite.ts
rename to packages/database/drizzle.config.sqlite.ts
index 74cc605..ec64ec4 100644
--- a/drizzle.config.sqlite.ts
+++ b/packages/database/drizzle.config.sqlite.ts
@@ -4,8 +4,8 @@ import * as dotenv from 'dotenv';
dotenv.config();
export default {
- schema: ['./src/db/schema_sqlite.ts', './src/db/schema_auth_sqlite.ts'],
- out: './drizzle/sqlite',
+ schema: ['./packages/database/src/schema_sqlite.ts', './packages/database/src/schema_auth_sqlite.ts'],
+ out: './packages/database/migrations/sqlite',
dialect: 'sqlite',
dbCredentials: {
url: 'docstore/sqlite3.db',
diff --git a/drizzle/postgres/0000_black_lucky_pierre.sql b/packages/database/migrations/postgres/0000_black_lucky_pierre.sql
similarity index 100%
rename from drizzle/postgres/0000_black_lucky_pierre.sql
rename to packages/database/migrations/postgres/0000_black_lucky_pierre.sql
diff --git a/drizzle/postgres/0001_tts_segments.sql b/packages/database/migrations/postgres/0001_tts_segments.sql
similarity index 100%
rename from drizzle/postgres/0001_tts_segments.sql
rename to packages/database/migrations/postgres/0001_tts_segments.sql
diff --git a/drizzle/postgres/0002_add_segment_key_to_tts_segments.sql b/packages/database/migrations/postgres/0002_add_segment_key_to_tts_segments.sql
similarity index 100%
rename from drizzle/postgres/0002_add_segment_key_to_tts_segments.sql
rename to packages/database/migrations/postgres/0002_add_segment_key_to_tts_segments.sql
diff --git a/drizzle/postgres/0003_tts_segments_v2_split.sql b/packages/database/migrations/postgres/0003_tts_segments_v2_split.sql
similarity index 100%
rename from drizzle/postgres/0003_tts_segments_v2_split.sql
rename to packages/database/migrations/postgres/0003_tts_segments_v2_split.sql
diff --git a/drizzle/postgres/0004_admin_panel.sql b/packages/database/migrations/postgres/0004_admin_panel.sql
similarity index 100%
rename from drizzle/postgres/0004_admin_panel.sql
rename to packages/database/migrations/postgres/0004_admin_panel.sql
diff --git a/drizzle/postgres/0005_pdf_layout_and_compute.sql b/packages/database/migrations/postgres/0005_pdf_layout_and_compute.sql
similarity index 100%
rename from drizzle/postgres/0005_pdf_layout_and_compute.sql
rename to packages/database/migrations/postgres/0005_pdf_layout_and_compute.sql
diff --git a/drizzle/postgres/0006_preview-defaults-cleanup.sql b/packages/database/migrations/postgres/0006_preview-defaults-cleanup.sql
similarity index 100%
rename from drizzle/postgres/0006_preview-defaults-cleanup.sql
rename to packages/database/migrations/postgres/0006_preview-defaults-cleanup.sql
diff --git a/drizzle/postgres/0007_pdf_parse_progress.sql b/packages/database/migrations/postgres/0007_pdf_parse_progress.sql
similarity index 100%
rename from drizzle/postgres/0007_pdf_parse_progress.sql
rename to packages/database/migrations/postgres/0007_pdf_parse_progress.sql
diff --git a/drizzle/postgres/0008_user_job_events.sql b/packages/database/migrations/postgres/0008_user_job_events.sql
similarity index 100%
rename from drizzle/postgres/0008_user_job_events.sql
rename to packages/database/migrations/postgres/0008_user_job_events.sql
diff --git a/drizzle/postgres/0009_drop_pdf_parse.sql b/packages/database/migrations/postgres/0009_drop_pdf_parse.sql
similarity index 100%
rename from drizzle/postgres/0009_drop_pdf_parse.sql
rename to packages/database/migrations/postgres/0009_drop_pdf_parse.sql
diff --git a/drizzle/postgres/0010_user-data-cleanup-cascades.sql b/packages/database/migrations/postgres/0010_user-data-cleanup-cascades.sql
similarity index 100%
rename from drizzle/postgres/0010_user-data-cleanup-cascades.sql
rename to packages/database/migrations/postgres/0010_user-data-cleanup-cascades.sql
diff --git a/drizzle/postgres/0011_scheduled-tasks.sql b/packages/database/migrations/postgres/0011_scheduled-tasks.sql
similarity index 100%
rename from drizzle/postgres/0011_scheduled-tasks.sql
rename to packages/database/migrations/postgres/0011_scheduled-tasks.sql
diff --git a/drizzle/postgres/meta/0000_snapshot.json b/packages/database/migrations/postgres/meta/0000_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0000_snapshot.json
rename to packages/database/migrations/postgres/meta/0000_snapshot.json
diff --git a/drizzle/postgres/meta/0001_snapshot.json b/packages/database/migrations/postgres/meta/0001_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0001_snapshot.json
rename to packages/database/migrations/postgres/meta/0001_snapshot.json
diff --git a/drizzle/postgres/meta/0002_snapshot.json b/packages/database/migrations/postgres/meta/0002_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0002_snapshot.json
rename to packages/database/migrations/postgres/meta/0002_snapshot.json
diff --git a/drizzle/postgres/meta/0003_snapshot.json b/packages/database/migrations/postgres/meta/0003_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0003_snapshot.json
rename to packages/database/migrations/postgres/meta/0003_snapshot.json
diff --git a/drizzle/postgres/meta/0004_snapshot.json b/packages/database/migrations/postgres/meta/0004_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0004_snapshot.json
rename to packages/database/migrations/postgres/meta/0004_snapshot.json
diff --git a/drizzle/postgres/meta/0005_snapshot.json b/packages/database/migrations/postgres/meta/0005_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0005_snapshot.json
rename to packages/database/migrations/postgres/meta/0005_snapshot.json
diff --git a/drizzle/postgres/meta/0006_snapshot.json b/packages/database/migrations/postgres/meta/0006_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0006_snapshot.json
rename to packages/database/migrations/postgres/meta/0006_snapshot.json
diff --git a/drizzle/postgres/meta/0007_snapshot.json b/packages/database/migrations/postgres/meta/0007_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0007_snapshot.json
rename to packages/database/migrations/postgres/meta/0007_snapshot.json
diff --git a/drizzle/postgres/meta/0008_snapshot.json b/packages/database/migrations/postgres/meta/0008_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0008_snapshot.json
rename to packages/database/migrations/postgres/meta/0008_snapshot.json
diff --git a/drizzle/postgres/meta/0009_snapshot.json b/packages/database/migrations/postgres/meta/0009_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0009_snapshot.json
rename to packages/database/migrations/postgres/meta/0009_snapshot.json
diff --git a/drizzle/postgres/meta/0010_snapshot.json b/packages/database/migrations/postgres/meta/0010_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0010_snapshot.json
rename to packages/database/migrations/postgres/meta/0010_snapshot.json
diff --git a/drizzle/postgres/meta/0011_snapshot.json b/packages/database/migrations/postgres/meta/0011_snapshot.json
similarity index 100%
rename from drizzle/postgres/meta/0011_snapshot.json
rename to packages/database/migrations/postgres/meta/0011_snapshot.json
diff --git a/drizzle/postgres/meta/_journal.json b/packages/database/migrations/postgres/meta/_journal.json
similarity index 100%
rename from drizzle/postgres/meta/_journal.json
rename to packages/database/migrations/postgres/meta/_journal.json
diff --git a/drizzle/sqlite/0000_familiar_caretaker.sql b/packages/database/migrations/sqlite/0000_familiar_caretaker.sql
similarity index 100%
rename from drizzle/sqlite/0000_familiar_caretaker.sql
rename to packages/database/migrations/sqlite/0000_familiar_caretaker.sql
diff --git a/drizzle/sqlite/0001_tts_segments.sql b/packages/database/migrations/sqlite/0001_tts_segments.sql
similarity index 100%
rename from drizzle/sqlite/0001_tts_segments.sql
rename to packages/database/migrations/sqlite/0001_tts_segments.sql
diff --git a/drizzle/sqlite/0002_add_segment_key_to_tts_segments.sql b/packages/database/migrations/sqlite/0002_add_segment_key_to_tts_segments.sql
similarity index 100%
rename from drizzle/sqlite/0002_add_segment_key_to_tts_segments.sql
rename to packages/database/migrations/sqlite/0002_add_segment_key_to_tts_segments.sql
diff --git a/drizzle/sqlite/0003_tts_segments_v2_split.sql b/packages/database/migrations/sqlite/0003_tts_segments_v2_split.sql
similarity index 100%
rename from drizzle/sqlite/0003_tts_segments_v2_split.sql
rename to packages/database/migrations/sqlite/0003_tts_segments_v2_split.sql
diff --git a/drizzle/sqlite/0004_admin_panel.sql b/packages/database/migrations/sqlite/0004_admin_panel.sql
similarity index 100%
rename from drizzle/sqlite/0004_admin_panel.sql
rename to packages/database/migrations/sqlite/0004_admin_panel.sql
diff --git a/drizzle/sqlite/0005_pdf_layout_and_compute.sql b/packages/database/migrations/sqlite/0005_pdf_layout_and_compute.sql
similarity index 100%
rename from drizzle/sqlite/0005_pdf_layout_and_compute.sql
rename to packages/database/migrations/sqlite/0005_pdf_layout_and_compute.sql
diff --git a/drizzle/sqlite/0006_preview-defaults-cleanup.sql b/packages/database/migrations/sqlite/0006_preview-defaults-cleanup.sql
similarity index 100%
rename from drizzle/sqlite/0006_preview-defaults-cleanup.sql
rename to packages/database/migrations/sqlite/0006_preview-defaults-cleanup.sql
diff --git a/drizzle/sqlite/0007_pdf_parse_progress.sql b/packages/database/migrations/sqlite/0007_pdf_parse_progress.sql
similarity index 100%
rename from drizzle/sqlite/0007_pdf_parse_progress.sql
rename to packages/database/migrations/sqlite/0007_pdf_parse_progress.sql
diff --git a/drizzle/sqlite/0008_user_job_events.sql b/packages/database/migrations/sqlite/0008_user_job_events.sql
similarity index 100%
rename from drizzle/sqlite/0008_user_job_events.sql
rename to packages/database/migrations/sqlite/0008_user_job_events.sql
diff --git a/drizzle/sqlite/0009_drop_pdf_parse.sql b/packages/database/migrations/sqlite/0009_drop_pdf_parse.sql
similarity index 100%
rename from drizzle/sqlite/0009_drop_pdf_parse.sql
rename to packages/database/migrations/sqlite/0009_drop_pdf_parse.sql
diff --git a/drizzle/sqlite/0010_user-data-cleanup-cascades.sql b/packages/database/migrations/sqlite/0010_user-data-cleanup-cascades.sql
similarity index 100%
rename from drizzle/sqlite/0010_user-data-cleanup-cascades.sql
rename to packages/database/migrations/sqlite/0010_user-data-cleanup-cascades.sql
diff --git a/drizzle/sqlite/0011_scheduled-tasks.sql b/packages/database/migrations/sqlite/0011_scheduled-tasks.sql
similarity index 100%
rename from drizzle/sqlite/0011_scheduled-tasks.sql
rename to packages/database/migrations/sqlite/0011_scheduled-tasks.sql
diff --git a/drizzle/sqlite/meta/0000_snapshot.json b/packages/database/migrations/sqlite/meta/0000_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0000_snapshot.json
rename to packages/database/migrations/sqlite/meta/0000_snapshot.json
diff --git a/drizzle/sqlite/meta/0001_snapshot.json b/packages/database/migrations/sqlite/meta/0001_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0001_snapshot.json
rename to packages/database/migrations/sqlite/meta/0001_snapshot.json
diff --git a/drizzle/sqlite/meta/0002_snapshot.json b/packages/database/migrations/sqlite/meta/0002_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0002_snapshot.json
rename to packages/database/migrations/sqlite/meta/0002_snapshot.json
diff --git a/drizzle/sqlite/meta/0003_snapshot.json b/packages/database/migrations/sqlite/meta/0003_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0003_snapshot.json
rename to packages/database/migrations/sqlite/meta/0003_snapshot.json
diff --git a/drizzle/sqlite/meta/0004_snapshot.json b/packages/database/migrations/sqlite/meta/0004_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0004_snapshot.json
rename to packages/database/migrations/sqlite/meta/0004_snapshot.json
diff --git a/drizzle/sqlite/meta/0005_snapshot.json b/packages/database/migrations/sqlite/meta/0005_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0005_snapshot.json
rename to packages/database/migrations/sqlite/meta/0005_snapshot.json
diff --git a/drizzle/sqlite/meta/0006_snapshot.json b/packages/database/migrations/sqlite/meta/0006_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0006_snapshot.json
rename to packages/database/migrations/sqlite/meta/0006_snapshot.json
diff --git a/drizzle/sqlite/meta/0007_snapshot.json b/packages/database/migrations/sqlite/meta/0007_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0007_snapshot.json
rename to packages/database/migrations/sqlite/meta/0007_snapshot.json
diff --git a/drizzle/sqlite/meta/0008_snapshot.json b/packages/database/migrations/sqlite/meta/0008_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0008_snapshot.json
rename to packages/database/migrations/sqlite/meta/0008_snapshot.json
diff --git a/drizzle/sqlite/meta/0009_snapshot.json b/packages/database/migrations/sqlite/meta/0009_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0009_snapshot.json
rename to packages/database/migrations/sqlite/meta/0009_snapshot.json
diff --git a/drizzle/sqlite/meta/0010_snapshot.json b/packages/database/migrations/sqlite/meta/0010_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0010_snapshot.json
rename to packages/database/migrations/sqlite/meta/0010_snapshot.json
diff --git a/drizzle/sqlite/meta/0011_snapshot.json b/packages/database/migrations/sqlite/meta/0011_snapshot.json
similarity index 100%
rename from drizzle/sqlite/meta/0011_snapshot.json
rename to packages/database/migrations/sqlite/meta/0011_snapshot.json
diff --git a/drizzle/sqlite/meta/_journal.json b/packages/database/migrations/sqlite/meta/_journal.json
similarity index 100%
rename from drizzle/sqlite/meta/_journal.json
rename to packages/database/migrations/sqlite/meta/_journal.json
diff --git a/packages/database/package.json b/packages/database/package.json
new file mode 100644
index 0000000..ee40dda
--- /dev/null
+++ b/packages/database/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@openreader/database",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "files": [
+ "bin",
+ "migrations",
+ "src"
+ ],
+ "exports": {
+ ".": "./src/index.ts",
+ "./migrate": "./src/migrate.mjs",
+ "./schema": "./src/schema.ts",
+ "./schema-postgres": "./src/schema_postgres.ts",
+ "./schema-sqlite": "./src/schema_sqlite.ts",
+ "./schema-auth-postgres": "./src/schema_auth_postgres.ts",
+ "./schema-auth-sqlite": "./src/schema_auth_sqlite.ts",
+ "./run-in-transaction": "./src/run-in-transaction.ts"
+ },
+ "bin": {
+ "openreader-migrate": "./bin/migrate.mjs"
+ },
+ "scripts": {
+ "migrate": "node bin/migrate.mjs"
+ },
+ "dependencies": {
+ "better-sqlite3": "^12.10.0",
+ "dotenv": "^17.4.2",
+ "drizzle-orm": "^0.45.2",
+ "pg": "^8.21.0"
+ }
+}
diff --git a/drizzle/scripts/generate.mjs b/packages/database/scripts/generate.mjs
similarity index 91%
rename from drizzle/scripts/generate.mjs
rename to packages/database/scripts/generate.mjs
index 320c334..8e56aa3 100644
--- a/drizzle/scripts/generate.mjs
+++ b/packages/database/scripts/generate.mjs
@@ -43,7 +43,7 @@ console.log('\n--- Generating Better Auth schema (SQLite) ---');
delete envSqlite.POSTGRES_URL; // force SQLite adapter
const result = spawnSync('npx', [
'@better-auth/cli', 'generate',
- '--output', 'src/db/schema_auth_sqlite.ts',
+ '--output', 'packages/database/src/schema_auth_sqlite.ts',
'--yes',
], { stdio: 'inherit', env: envSqlite });
if ((result.status ?? 1) !== 0) process.exit(result.status ?? 1);
@@ -58,7 +58,7 @@ console.log('\n--- Generating Better Auth schema (Postgres) ---');
}
const result = spawnSync('npx', [
'@better-auth/cli', 'generate',
- '--output', 'src/db/schema_auth_postgres.ts',
+ '--output', 'packages/database/src/schema_auth_postgres.ts',
'--yes',
], { stdio: 'inherit', env: envPg });
if ((result.status ?? 1) !== 0) process.exit(result.status ?? 1);
@@ -67,7 +67,10 @@ console.log('\n--- Generating Better Auth schema (Postgres) ---');
// ---------------------------------------------------------------------------
// Step 2: Generate Drizzle migrations for both dialects.
// ---------------------------------------------------------------------------
-for (const configFile of ['drizzle.config.sqlite.ts', 'drizzle.config.pg.ts']) {
+for (const configFile of [
+ 'packages/database/drizzle.config.sqlite.ts',
+ 'packages/database/drizzle.config.pg.ts',
+]) {
console.log(`\n--- Drizzle generate (${configFile}) ---`);
const result = spawnSync('drizzle-kit', ['generate', '--config', configFile, ...extraArgs], {
stdio: 'inherit',
diff --git a/src/db/index.ts b/packages/database/src/index.ts
similarity index 100%
rename from src/db/index.ts
rename to packages/database/src/index.ts
diff --git a/packages/database/src/migrate.mjs b/packages/database/src/migrate.mjs
new file mode 100644
index 0000000..8aba886
--- /dev/null
+++ b/packages/database/src/migrate.mjs
@@ -0,0 +1,51 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import * as dotenv from 'dotenv';
+
+const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+
+function loadEnvFiles(cwd) {
+ const envPath = path.join(cwd, '.env');
+ const envLocalPath = path.join(cwd, '.env.local');
+
+ if (fs.existsSync(envPath)) dotenv.config({ path: envPath });
+ if (fs.existsSync(envLocalPath)) dotenv.config({ path: envLocalPath, override: true });
+}
+
+export async function runMigrations({ cwd = process.cwd(), env = process.env } = {}) {
+ loadEnvFiles(cwd);
+
+ if (env.POSTGRES_URL?.trim()) {
+ const [{ drizzle }, { migrate }, { Pool }] = await Promise.all([
+ import('drizzle-orm/node-postgres'),
+ import('drizzle-orm/node-postgres/migrator'),
+ import('pg'),
+ ]);
+ const pool = new Pool({ connectionString: env.POSTGRES_URL });
+ try {
+ await migrate(drizzle(pool), {
+ migrationsFolder: path.join(packageDir, 'migrations', 'postgres'),
+ });
+ } finally {
+ await pool.end();
+ }
+ return;
+ }
+
+ const dbPath = path.join(cwd, 'docstore', 'sqlite3.db');
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
+ const [{ drizzle }, { migrate }, { default: BetterSqlite3 }] = await Promise.all([
+ import('drizzle-orm/better-sqlite3'),
+ import('drizzle-orm/better-sqlite3/migrator'),
+ import('better-sqlite3'),
+ ]);
+ const sqlite = new BetterSqlite3(dbPath);
+ try {
+ migrate(drizzle(sqlite), {
+ migrationsFolder: path.join(packageDir, 'migrations', 'sqlite'),
+ });
+ } finally {
+ sqlite.close();
+ }
+}
diff --git a/src/db/run-in-transaction.ts b/packages/database/src/run-in-transaction.ts
similarity index 96%
rename from src/db/run-in-transaction.ts
rename to packages/database/src/run-in-transaction.ts
index 1ec6ecd..8813e88 100644
--- a/src/db/run-in-transaction.ts
+++ b/packages/database/src/run-in-transaction.ts
@@ -1,4 +1,4 @@
-import { db } from '@/db';
+import { db } from './index';
/**
* Run `fn` with a database connection, wrapped in a transaction on Postgres.
diff --git a/src/db/schema.ts b/packages/database/src/schema.ts
similarity index 100%
rename from src/db/schema.ts
rename to packages/database/src/schema.ts
diff --git a/src/db/schema_auth_postgres.ts b/packages/database/src/schema_auth_postgres.ts
similarity index 100%
rename from src/db/schema_auth_postgres.ts
rename to packages/database/src/schema_auth_postgres.ts
diff --git a/src/db/schema_auth_sqlite.ts b/packages/database/src/schema_auth_sqlite.ts
similarity index 100%
rename from src/db/schema_auth_sqlite.ts
rename to packages/database/src/schema_auth_sqlite.ts
diff --git a/src/db/schema_postgres.ts b/packages/database/src/schema_postgres.ts
similarity index 100%
rename from src/db/schema_postgres.ts
rename to packages/database/src/schema_postgres.ts
diff --git a/src/db/schema_sqlite.ts b/packages/database/src/schema_sqlite.ts
similarity index 100%
rename from src/db/schema_sqlite.ts
rename to packages/database/src/schema_sqlite.ts
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 32a7d38..0ca9ff3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -29,6 +29,9 @@ importers:
'@napi-rs/canvas':
specifier: ^0.1.100
version: 0.1.100
+ '@openreader/database':
+ specifier: workspace:*
+ version: link:packages/database
'@speech-sdk/core':
specifier: ^0.15.0
version: 0.15.0
@@ -47,9 +50,6 @@ importers:
better-auth:
specifier: ^1.6.14
version: 1.6.14(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(kysely@0.28.17)(pg@8.21.0))(next@15.5.19(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8(@types/node@20.19.41)(vite@8.0.14(@types/node@20.19.41)(esbuild@0.28.0)(jiti@1.21.7)(tsx@4.22.4)(yaml@2.9.0)))
- better-sqlite3:
- specifier: ^12.10.0
- version: 12.10.0
cmpstr:
specifier: ^3.3.0
version: 3.3.0
@@ -68,9 +68,6 @@ importers:
dotenv:
specifier: ^17.4.2
version: 17.4.2
- drizzle-kit:
- specifier: ^0.31.10
- version: 0.31.10
drizzle-orm:
specifier: ^0.45.2
version: 0.45.2(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(kysely@0.28.17)(pg@8.21.0)
@@ -104,9 +101,6 @@ importers:
pdfjs-dist:
specifier: 4.8.69
version: 4.8.69
- pg:
- specifier: ^8.21.0
- version: 8.21.0
pino:
specifier: ^10.3.1
version: 10.3.1
@@ -192,6 +186,12 @@ importers:
'@types/uuid':
specifier: ^10.0.0
version: 10.0.0
+ better-sqlite3:
+ specifier: ^12.10.0
+ version: 12.10.0
+ drizzle-kit:
+ specifier: ^0.31.10
+ version: 0.31.10
eslint:
specifier: ^9.39.4
version: 9.39.4(jiti@1.21.7)
@@ -214,7 +214,28 @@ importers:
specifier: ^4.1.8
version: 4.1.8(@types/node@20.19.41)(vite@8.0.14(@types/node@20.19.41)(esbuild@0.28.0)(jiti@1.21.7)(tsx@4.22.4)(yaml@2.9.0))
- compute-worker:
+ packages/bootstrap:
+ dependencies:
+ '@aws-sdk/client-s3':
+ specifier: ^3.1061.0
+ version: 3.1061.0
+ '@openreader/database':
+ specifier: workspace:*
+ version: link:../database
+ better-sqlite3:
+ specifier: ^12.10.0
+ version: 12.10.0
+ dotenv:
+ specifier: ^17.4.2
+ version: 17.4.2
+ ffmpeg-static:
+ specifier: ^5.3.0
+ version: 5.3.0
+ pg:
+ specifier: ^8.21.0
+ version: 8.21.0
+
+ packages/compute-worker:
dependencies:
'@aws-sdk/client-s3':
specifier: ^3.1061.0
@@ -265,26 +286,17 @@ importers:
specifier: ^4.4.3
version: 4.4.3
- docker/entrypoint-migration-tools:
+ packages/database:
dependencies:
- '@aws-sdk/client-s3':
- specifier: ^3.1061.0
- version: 3.1061.0
better-sqlite3:
specifier: ^12.10.0
version: 12.10.0
dotenv:
specifier: ^17.4.2
version: 17.4.2
- drizzle-kit:
- specifier: ^0.31.10
- version: 0.31.10
drizzle-orm:
specifier: ^0.45.2
version: 0.45.2(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(kysely@0.28.17)(pg@8.21.0)
- ffmpeg-static:
- specifier: ^5.3.0
- version: 5.3.0
pg:
specifier: ^8.21.0
version: 8.21.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index e94fdc1..d7d7a9f 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,7 +1,6 @@
packages:
- .
- - compute-worker
- - docker/*
+ - packages/*
allowBuilds:
'@napi-rs/canvas': true
diff --git a/scripts/check-compute-boundary.mjs b/scripts/check-compute-boundary.mjs
index 8cc6a20..62ad85b 100644
--- a/scripts/check-compute-boundary.mjs
+++ b/scripts/check-compute-boundary.mjs
@@ -7,7 +7,7 @@ const forbidden = [
'@openreader/compute-worker',
'compute/core',
'compute/worker',
- 'compute-worker/src',
+ 'packages/compute-worker/src',
];
const failures = [];
diff --git a/scripts/check-next-server-bundle.mjs b/scripts/check-next-server-bundle.mjs
index 8174d40..795c60b 100644
--- a/scripts/check-next-server-bundle.mjs
+++ b/scripts/check-next-server-bundle.mjs
@@ -13,7 +13,7 @@ const forbidden = [
'onnxruntime-node',
'@huggingface/tokenizers',
'@openreader/compute-worker',
- '/compute-worker/src/inference/',
+ '/packages/compute-worker/src/inference/',
];
const includeExt = new Set(['.js', '.mjs', '.cjs']);
diff --git a/src/app/api/audiobook/chapter/route.ts b/src/app/api/audiobook/chapter/route.ts
index 7aa881a..f080fbe 100644
--- a/src/app/api/audiobook/chapter/route.ts
+++ b/src/app/api/audiobook/chapter/route.ts
@@ -5,8 +5,8 @@ import { tmpdir } from 'os';
import { join } from 'path';
import { randomUUID } from 'crypto';
import { and, eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { audiobooks, audiobookChapters } from '@/db/schema';
+import { db } from '@openreader/database';
+import { audiobooks, audiobookChapters } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { rateLimiter, resolveRateLimitThresholds } from '@/lib/server/rate-limit/rate-limiter';
import { getClientIp } from '@/lib/server/rate-limit/request-ip';
diff --git a/src/app/api/audiobook/route.ts b/src/app/api/audiobook/route.ts
index 336c772..a2955dc 100644
--- a/src/app/api/audiobook/route.ts
+++ b/src/app/api/audiobook/route.ts
@@ -4,8 +4,8 @@ import { mkdtemp, readFile, rm, writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';
import { and, eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { audiobooks, audiobookChapters } from '@/db/schema';
+import { db } from '@openreader/database';
+import { audiobooks, audiobookChapters } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { errorToLog, serverLogger } from '@/lib/server/logger';
import { errorResponse } from '@/lib/server/errors/next-response';
diff --git a/src/app/api/audiobook/status/route.ts b/src/app/api/audiobook/status/route.ts
index 6416dc3..14519ca 100644
--- a/src/app/api/audiobook/status/route.ts
+++ b/src/app/api/audiobook/status/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { audiobooks, audiobookChapters } from '@/db/schema';
+import { db } from '@openreader/database';
+import { audiobooks, audiobookChapters } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { getAudiobookObjectBuffer, isMissingBlobError, listAudiobookObjects } from '@/lib/server/audiobooks/blobstore';
import { decodeChapterFileName } from '@/lib/server/audiobooks/chapters';
diff --git a/src/app/api/documents/[id]/parsed/events/route.ts b/src/app/api/documents/[id]/parsed/events/route.ts
index fd8e08d..2a49029 100644
--- a/src/app/api/documents/[id]/parsed/events/route.ts
+++ b/src/app/api/documents/[id]/parsed/events/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq, inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { getComputeWorkerClient } from '@/lib/server/compute-worker/client';
import { isValidDocumentId } from '@/lib/server/documents/blobstore';
diff --git a/src/app/api/documents/[id]/parsed/route.ts b/src/app/api/documents/[id]/parsed/route.ts
index 0234ade..898dac7 100644
--- a/src/app/api/documents/[id]/parsed/route.ts
+++ b/src/app/api/documents/[id]/parsed/route.ts
@@ -1,8 +1,8 @@
import { randomUUID } from 'node:crypto';
import { NextRequest, NextResponse } from 'next/server';
import { and, eq, inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { isValidDocumentId } from '@/lib/server/documents/blobstore';
import {
diff --git a/src/app/api/documents/[id]/settings/route.ts b/src/app/api/documents/[id]/settings/route.ts
index 0b2b282..f618022 100644
--- a/src/app/api/documents/[id]/settings/route.ts
+++ b/src/app/api/documents/[id]/settings/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { documentSettings, documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documentSettings, documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { mergeDocumentSettings } from '@/lib/shared/document-settings';
import { DEFAULT_DOCUMENT_SETTINGS, type DocumentSettings } from '@/types/document-settings';
diff --git a/src/app/api/documents/blob/get/fallback/route.ts b/src/app/api/documents/blob/get/fallback/route.ts
index 0db67e0..7ca9006 100644
--- a/src/app/api/documents/blob/get/fallback/route.ts
+++ b/src/app/api/documents/blob/get/fallback/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq, inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { contentTypeForName } from '@/lib/server/storage/library-mount';
import { getDocumentBlob, isMissingBlobError, isValidDocumentId } from '@/lib/server/documents/blobstore';
diff --git a/src/app/api/documents/blob/get/presign/route.ts b/src/app/api/documents/blob/get/presign/route.ts
index 4992354..685e4dd 100644
--- a/src/app/api/documents/blob/get/presign/route.ts
+++ b/src/app/api/documents/blob/get/presign/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq, inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { isValidDocumentId, presignGet } from '@/lib/server/documents/blobstore';
import { getOpenReaderTestNamespace } from '@/lib/server/testing/test-namespace';
diff --git a/src/app/api/documents/blob/preview/fallback/route.ts b/src/app/api/documents/blob/preview/fallback/route.ts
index 004aa74..4341a08 100644
--- a/src/app/api/documents/blob/preview/fallback/route.ts
+++ b/src/app/api/documents/blob/preview/fallback/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq, inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { errorToLog, serverLogger } from '@/lib/server/logger';
import { errorResponse } from '@/lib/server/errors/next-response';
diff --git a/src/app/api/documents/blob/preview/utils.ts b/src/app/api/documents/blob/preview/utils.ts
index 937942f..ad19bce 100644
--- a/src/app/api/documents/blob/preview/utils.ts
+++ b/src/app/api/documents/blob/preview/utils.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq, inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { isValidDocumentId } from '@/lib/server/documents/blobstore';
import { isPreviewableDocumentType } from '@/lib/server/documents/previews';
diff --git a/src/app/api/documents/route.ts b/src/app/api/documents/route.ts
index c7d21d8..328591f 100644
--- a/src/app/api/documents/route.ts
+++ b/src/app/api/documents/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { toDocumentTypeFromName } from '@/lib/server/documents/utils';
import { errorToLog, serverLogger } from '@/lib/server/logger';
diff --git a/src/app/api/tts/segments/ensure/route.ts b/src/app/api/tts/segments/ensure/route.ts
index 28d7a0a..f93e551 100644
--- a/src/app/api/tts/segments/ensure/route.ts
+++ b/src/app/api/tts/segments/ensure/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq, inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents, ttsSegmentEntries, ttsSegmentVariants } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents, ttsSegmentEntries, ttsSegmentVariants } from '@openreader/database/schema';
import { isS3Configured, getS3Config } from '@/lib/server/storage/s3';
import { generateTTSBuffer } from '@/lib/server/tts/generate';
import {
diff --git a/src/app/api/tts/segments/manifest/route.ts b/src/app/api/tts/segments/manifest/route.ts
index f33155d..89621f3 100644
--- a/src/app/api/tts/segments/manifest/route.ts
+++ b/src/app/api/tts/segments/manifest/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, asc, eq, gt, inArray, or } from 'drizzle-orm';
-import { db } from '@/db';
-import { ttsSegmentEntries, ttsSegmentVariants } from '@/db/schema';
+import { db } from '@openreader/database';
+import { ttsSegmentEntries, ttsSegmentVariants } from '@openreader/database/schema';
import { resolveSegmentDocumentScope } from '@/lib/server/tts/segments-auth';
import {
decodeManifestCursor,
diff --git a/src/app/api/user/claim/route.ts b/src/app/api/user/claim/route.ts
index 5745cd7..8d4b918 100644
--- a/src/app/api/user/claim/route.ts
+++ b/src/app/api/user/claim/route.ts
@@ -1,8 +1,8 @@
import { NextRequest, NextResponse } from 'next/server';
import { claimAnonymousData } from '@/lib/server/user/claim-data';
import { auth } from '@/lib/server/auth/auth';
-import { db } from '@/db';
-import { audiobooks, documentSettings, documents, userDocumentProgress, userPreferences } from '@/db/schema';
+import { db } from '@openreader/database';
+import { audiobooks, documentSettings, documents, userDocumentProgress, userPreferences } from '@openreader/database/schema';
import { count, eq, ne } from 'drizzle-orm';
import { getOpenReaderTestNamespace, getUnclaimedUserIdForNamespace } from '@/lib/server/testing/test-namespace';
import { errorToLog, serverLogger } from '@/lib/server/logger';
diff --git a/src/app/api/user/export/route.ts b/src/app/api/user/export/route.ts
index e65b035..eae1047 100644
--- a/src/app/api/user/export/route.ts
+++ b/src/app/api/user/export/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { PassThrough, Readable } from 'stream';
import { auth } from '@/lib/server/auth/auth';
-import { db } from '@/db';
+import { db } from '@openreader/database';
import {
documents,
audiobooks,
@@ -13,9 +13,9 @@ import {
userJobEvents,
userPreferences,
userTtsChars,
-} from '@/db/schema';
-import * as authSchemaSqlite from '@/db/schema_auth_sqlite';
-import * as authSchemaPostgres from '@/db/schema_auth_postgres';
+} from '@openreader/database/schema';
+import * as authSchemaSqlite from '@openreader/database/schema-auth-sqlite';
+import * as authSchemaPostgres from '@openreader/database/schema-auth-postgres';
import { and, desc, eq, inArray } from 'drizzle-orm';
import archiver from 'archiver';
import { appendUserExportArchive } from '@/lib/server/user/data-export';
diff --git a/src/app/api/user/state/changelog/version-check/route.ts b/src/app/api/user/state/changelog/version-check/route.ts
index b36d74f..498467b 100644
--- a/src/app/api/user/state/changelog/version-check/route.ts
+++ b/src/app/api/user/state/changelog/version-check/route.ts
@@ -1,7 +1,7 @@
import { eq } from 'drizzle-orm';
import { NextRequest, NextResponse } from 'next/server';
-import { db } from '@/db';
-import { userPreferences } from '@/db/schema';
+import { db } from '@openreader/database';
+import { userPreferences } from '@openreader/database/schema';
import { normalizeVersion, shouldOpenChangelogForVersionChange } from '@/lib/shared/changelog';
import { nowTimestampMs } from '@/lib/shared/timestamps';
import { resolveUserStateScope } from '@/lib/server/user/resolve-state-scope';
diff --git a/src/app/api/user/state/preferences/route.ts b/src/app/api/user/state/preferences/route.ts
index 9eeae34..4c81de2 100644
--- a/src/app/api/user/state/preferences/route.ts
+++ b/src/app/api/user/state/preferences/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { userPreferences } from '@/db/schema';
+import { db } from '@openreader/database';
+import { userPreferences } from '@openreader/database/schema';
import { type SyncedPreferencesPatch } from '@/types/user-state';
import { resolveUserStateScope } from '@/lib/server/user/resolve-state-scope';
import { coerceTimestampMs, nowTimestampMs } from '@/lib/shared/timestamps';
diff --git a/src/app/api/user/state/progress/route.ts b/src/app/api/user/state/progress/route.ts
index 1fef9f5..b365588 100644
--- a/src/app/api/user/state/progress/route.ts
+++ b/src/app/api/user/state/progress/route.ts
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { and, eq, sql } from 'drizzle-orm';
-import { db } from '@/db';
-import { userDocumentProgress } from '@/db/schema';
+import { db } from '@openreader/database';
+import { userDocumentProgress } from '@openreader/database/schema';
import type { ReaderType } from '@/types/user-state';
import { isValidDocumentId } from '@/lib/server/documents/blobstore';
import { resolveUserStateScope } from '@/lib/server/user/resolve-state-scope';
diff --git a/src/lib/server/admin/email-sync.ts b/src/lib/server/admin/email-sync.ts
index 105ec17..1c9696b 100644
--- a/src/lib/server/admin/email-sync.ts
+++ b/src/lib/server/admin/email-sync.ts
@@ -1,5 +1,5 @@
import { eq } from 'drizzle-orm';
-import { db } from '@/db';
+import { db } from '@openreader/database';
import { hashForLog, serverLogger } from '@/lib/server/logger';
import { logDegraded } from '@/lib/server/errors/logging';
@@ -7,9 +7,9 @@ import { logDegraded } from '@/lib/server/errors/logging';
// we import them lazily to avoid coupling this module to a single dialect.
function getUserTable() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
- const sqlite = require('@/db/schema_auth_sqlite');
+ const sqlite = require('@openreader/database/schema-auth-sqlite');
// eslint-disable-next-line @typescript-eslint/no-require-imports
- const pg = require('@/db/schema_auth_postgres');
+ const pg = require('@openreader/database/schema-auth-postgres');
return process.env.POSTGRES_URL ? pg.user : sqlite.user;
}
diff --git a/src/lib/server/admin/providers.ts b/src/lib/server/admin/providers.ts
index a562c9c..9befbb9 100644
--- a/src/lib/server/admin/providers.ts
+++ b/src/lib/server/admin/providers.ts
@@ -1,7 +1,7 @@
import { randomUUID } from 'node:crypto';
import { and, asc, desc, eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { adminProviders } from '@/db/schema';
+import { db } from '@openreader/database';
+import { adminProviders } from '@openreader/database/schema';
import { apiKeyLast4, decryptSecret, encryptSecret } from '@/lib/server/crypto/secrets';
import { type TtsProviderId } from '@/lib/shared/tts-provider-catalog';
import { resolveTtsProviderModelPolicy } from '@/lib/shared/tts-provider-policy';
diff --git a/src/lib/server/admin/seed.ts b/src/lib/server/admin/seed.ts
index 0409299..f120f62 100644
--- a/src/lib/server/admin/seed.ts
+++ b/src/lib/server/admin/seed.ts
@@ -1,5 +1,5 @@
-import { db } from '@/db';
-import { adminProviders, adminSettings } from '@/db/schema';
+import { db } from '@openreader/database';
+import { adminProviders, adminSettings } from '@openreader/database/schema';
import { encryptSecret, apiKeyLast4 } from '@/lib/server/crypto/secrets';
import { randomUUID } from 'node:crypto';
import { and, eq } from 'drizzle-orm';
diff --git a/src/lib/server/admin/settings.ts b/src/lib/server/admin/settings.ts
index 99e1b87..4da8900 100644
--- a/src/lib/server/admin/settings.ts
+++ b/src/lib/server/admin/settings.ts
@@ -1,6 +1,6 @@
import { asc, desc, eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { adminProviders, adminSettings } from '@/db/schema';
+import { db } from '@openreader/database';
+import { adminProviders, adminSettings } from '@openreader/database/schema';
import { serverLogger } from '@/lib/server/logger';
import { logDegraded } from '@/lib/server/errors/logging';
diff --git a/src/lib/server/audiobooks/prune.ts b/src/lib/server/audiobooks/prune.ts
index 35d6669..6f1e3bb 100644
--- a/src/lib/server/audiobooks/prune.ts
+++ b/src/lib/server/audiobooks/prune.ts
@@ -1,7 +1,7 @@
import { and, eq, notInArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { audiobooks, audiobookChapters } from '@/db/schema';
+import { db } from '@openreader/database';
+import { audiobooks, audiobookChapters } from '@openreader/database/schema';
export async function pruneAudiobookIfMissingDir(bookId: string, userId: string, intermediateDirExists: boolean): Promise {
if (intermediateDirExists) return;
diff --git a/src/lib/server/auth/auth.ts b/src/lib/server/auth/auth.ts
index d0359d1..c9345c5 100644
--- a/src/lib/server/auth/auth.ts
+++ b/src/lib/server/auth/auth.ts
@@ -4,13 +4,13 @@ import { anonymous } from "better-auth/plugins";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
-import { db } from "@/db";
+import { db } from "@openreader/database";
import { getRequiredAuthEnv, isAnonymousAuthSessionsEnabled } from "@/lib/server/auth/config";
import { isAdminEmail, syncAdminFlag } from "@/lib/server/admin/email-sync";
import { getResolvedRuntimeConfig } from '@/lib/server/runtime-config';
import { assertUserSignupAllowed } from '@/lib/server/auth/signup-policy';
-import * as authSchemaSqlite from "@/db/schema_auth_sqlite";
-import * as authSchemaPostgres from "@/db/schema_auth_postgres";
+import * as authSchemaSqlite from "@openreader/database/schema-auth-sqlite";
+import * as authSchemaPostgres from "@openreader/database/schema-auth-postgres";
import { hashForLog, serverLogger } from '@/lib/server/logger';
import { logDegraded, logServerError } from '@/lib/server/errors/logging';
import { tryGetOrigin } from "@/lib/shared/urls";
diff --git a/src/lib/server/documents/blob-lease.ts b/src/lib/server/documents/blob-lease.ts
index c5b82bf..162bd99 100644
--- a/src/lib/server/documents/blob-lease.ts
+++ b/src/lib/server/documents/blob-lease.ts
@@ -1,7 +1,7 @@
import { randomUUID } from 'node:crypto';
import { and, eq, lt } from 'drizzle-orm';
-import { db } from '@/db';
-import { documentBlobLeases } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documentBlobLeases } from '@openreader/database/schema';
import { errorToLog, serverLogger } from '@/lib/server/logger';
const DEFAULT_LEASE_MS = 15 * 60 * 1000;
diff --git a/src/lib/server/documents/delete-owned.ts b/src/lib/server/documents/delete-owned.ts
index 4b2d14e..1686a51 100644
--- a/src/lib/server/documents/delete-owned.ts
+++ b/src/lib/server/documents/delete-owned.ts
@@ -1,6 +1,6 @@
import { and, eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { deleteDocumentTtsSegmentCache } from '@/lib/server/tts/segments-cache';
import { hashForLog, serverLogger } from '@/lib/server/logger';
import { logDegraded } from '@/lib/server/errors/logging';
diff --git a/src/lib/server/documents/previews.ts b/src/lib/server/documents/previews.ts
index 7c60c98..c127605 100644
--- a/src/lib/server/documents/previews.ts
+++ b/src/lib/server/documents/previews.ts
@@ -1,7 +1,7 @@
import { randomUUID } from 'crypto';
import { and, eq, inArray, lt, or, sql } from 'drizzle-orm';
-import { db } from '@/db';
-import { documentPreviews } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documentPreviews } from '@openreader/database/schema';
import { serverLogger } from '@/lib/server/logger';
import { logServerError } from '@/lib/server/errors/logging';
import {
diff --git a/src/lib/server/documents/register-upload.ts b/src/lib/server/documents/register-upload.ts
index a29feb8..073c048 100644
--- a/src/lib/server/documents/register-upload.ts
+++ b/src/lib/server/documents/register-upload.ts
@@ -1,5 +1,5 @@
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { and, eq } from 'drizzle-orm';
import {
enqueueDocumentPreview,
diff --git a/src/lib/server/rate-limit/job-rate-limiter.ts b/src/lib/server/rate-limit/job-rate-limiter.ts
index 9a886ce..962d46e 100644
--- a/src/lib/server/rate-limit/job-rate-limiter.ts
+++ b/src/lib/server/rate-limit/job-rate-limiter.ts
@@ -1,6 +1,6 @@
import { and, eq, gte, sql } from 'drizzle-orm';
-import { db } from '@/db';
-import { userJobEvents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { userJobEvents } from '@openreader/database/schema';
import { nowTimestampMs } from '@/lib/shared/timestamps';
import type { RuntimeConfig } from '@/lib/server/admin/settings';
diff --git a/src/lib/server/rate-limit/rate-limiter.ts b/src/lib/server/rate-limit/rate-limiter.ts
index a526d0d..cbbe335 100644
--- a/src/lib/server/rate-limit/rate-limiter.ts
+++ b/src/lib/server/rate-limit/rate-limiter.ts
@@ -1,6 +1,6 @@
-import { db } from '@/db';
-import { runInDbTransaction } from '@/db/run-in-transaction';
-import { userTtsChars } from '@/db/schema';
+import { db } from '@openreader/database';
+import { runInDbTransaction } from '@openreader/database/run-in-transaction';
+import { userTtsChars } from '@openreader/database/schema';
import { eq, and, lt, sql } from 'drizzle-orm';
import { nextUtcMidnightTimestampMs, nowTimestampMs } from '@/lib/shared/timestamps';
diff --git a/src/lib/server/tasks/engine.ts b/src/lib/server/tasks/engine.ts
index 93822df..24ffa62 100644
--- a/src/lib/server/tasks/engine.ts
+++ b/src/lib/server/tasks/engine.ts
@@ -1,7 +1,7 @@
import { randomUUID } from 'node:crypto';
import { and, eq, lt, ne, or, sql } from 'drizzle-orm';
-import { db } from '@/db';
-import { scheduledTasks } from '@/db/schema';
+import { db } from '@openreader/database';
+import { scheduledTasks } from '@openreader/database/schema';
import { serverLogger } from '@/lib/server/logger';
import { logDegraded } from '@/lib/server/errors/logging';
import { TASK_REGISTRY } from './registry';
diff --git a/src/lib/server/tasks/handlers/prune-job-events.ts b/src/lib/server/tasks/handlers/prune-job-events.ts
index f0ddf18..03938dc 100644
--- a/src/lib/server/tasks/handlers/prune-job-events.ts
+++ b/src/lib/server/tasks/handlers/prune-job-events.ts
@@ -1,6 +1,6 @@
import { lt } from 'drizzle-orm';
-import { db } from '@/db';
-import { userJobEvents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { userJobEvents } from '@openreader/database/schema';
import type { TaskResult } from '../types';
// Retention far exceeds the largest rate-limit window, so pruning never removes
diff --git a/src/lib/server/tasks/handlers/reap-orphaned-blobs.ts b/src/lib/server/tasks/handlers/reap-orphaned-blobs.ts
index a692370..0f88454 100644
--- a/src/lib/server/tasks/handlers/reap-orphaned-blobs.ts
+++ b/src/lib/server/tasks/handlers/reap-orphaned-blobs.ts
@@ -1,6 +1,6 @@
import { inArray } from 'drizzle-orm';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { isS3Configured } from '@/lib/server/storage/s3';
import { deleteDocumentBlob, listDocumentSourceBlobs } from '@/lib/server/documents/blobstore';
import { deleteDocumentPreviewArtifacts } from '@/lib/server/documents/previews-blobstore';
diff --git a/src/lib/server/testing/test-namespace.ts b/src/lib/server/testing/test-namespace.ts
index 044e46f..834d7cd 100644
--- a/src/lib/server/testing/test-namespace.ts
+++ b/src/lib/server/testing/test-namespace.ts
@@ -1,6 +1,6 @@
import path from 'path';
import { UNCLAIMED_USER_ID } from '@/lib/server/storage/docstore-legacy';
-import { ensureSystemUserExists } from '@/db';
+import { ensureSystemUserExists } from '@openreader/database';
const TEST_NAMESPACE_HEADER = 'x-openreader-test-namespace';
const SAFE_NAMESPACE_REGEX = /^[a-zA-Z0-9._-]{1,128}$/;
diff --git a/src/lib/server/tts/segments-audio.ts b/src/lib/server/tts/segments-audio.ts
index 779c79e..1959baa 100644
--- a/src/lib/server/tts/segments-audio.ts
+++ b/src/lib/server/tts/segments-audio.ts
@@ -1,8 +1,8 @@
import { and, eq } from 'drizzle-orm';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
-import { db } from '@/db';
-import { ttsSegmentEntries, ttsSegmentVariants } from '@/db/schema';
+import { db } from '@openreader/database';
+import { ttsSegmentEntries, ttsSegmentVariants } from '@openreader/database/schema';
export type ResolvedSegmentAudio = {
documentId: string;
diff --git a/src/lib/server/tts/segments-auth.ts b/src/lib/server/tts/segments-auth.ts
index ff19de3..cd00f71 100644
--- a/src/lib/server/tts/segments-auth.ts
+++ b/src/lib/server/tts/segments-auth.ts
@@ -1,7 +1,7 @@
import { and, eq, inArray } from 'drizzle-orm';
import type { NextRequest } from 'next/server';
-import { db } from '@/db';
-import { documents } from '@/db/schema';
+import { db } from '@openreader/database';
+import { documents } from '@openreader/database/schema';
import { requireAuthContext } from '@/lib/server/auth/auth';
import { getOpenReaderTestNamespace } from '@/lib/server/testing/test-namespace';
import type { ReaderType } from '@/types/user-state';
diff --git a/src/lib/server/tts/segments-cache.ts b/src/lib/server/tts/segments-cache.ts
index cc63791..f0f7c0a 100644
--- a/src/lib/server/tts/segments-cache.ts
+++ b/src/lib/server/tts/segments-cache.ts
@@ -1,6 +1,6 @@
import { and, eq } from 'drizzle-orm';
-import { db } from '@/db';
-import { ttsSegmentEntries, ttsSegmentVariants } from '@/db/schema';
+import { db } from '@openreader/database';
+import { ttsSegmentEntries, ttsSegmentVariants } from '@openreader/database/schema';
import {
deleteTtsSegmentAudioObjects,
deleteTtsSegmentPrefix,
diff --git a/src/lib/server/user/claim-data.ts b/src/lib/server/user/claim-data.ts
index 47a52c9..259425e 100644
--- a/src/lib/server/user/claim-data.ts
+++ b/src/lib/server/user/claim-data.ts
@@ -1,4 +1,4 @@
-import { db } from '@/db';
+import { db } from '@openreader/database';
import {
documents,
audiobooks,
@@ -8,7 +8,7 @@ import {
ttsSegmentVariants,
userPreferences,
userDocumentProgress,
-} from '@/db/schema';
+} from '@openreader/database/schema';
import { eq, and, inArray } from 'drizzle-orm';
import { UNCLAIMED_USER_ID } from '../storage/docstore-legacy';
import { cleanupClaimedLegacyFsSources } from './legacy-fs-claim-cleanup';
diff --git a/src/lib/server/user/data-cleanup.ts b/src/lib/server/user/data-cleanup.ts
index fd3845f..b096ab2 100644
--- a/src/lib/server/user/data-cleanup.ts
+++ b/src/lib/server/user/data-cleanup.ts
@@ -10,10 +10,10 @@
* and failures block the deletion.
*/
-import { db } from '@/db';
-import { documents, audiobooks, userJobEvents, userTtsChars } from '@/db/schema';
-import * as authSchemaSqlite from '@/db/schema_auth_sqlite';
-import * as authSchemaPostgres from '@/db/schema_auth_postgres';
+import { db } from '@openreader/database';
+import { documents, audiobooks, userJobEvents, userTtsChars } from '@openreader/database/schema';
+import * as authSchemaSqlite from '@openreader/database/schema-auth-sqlite';
+import * as authSchemaPostgres from '@openreader/database/schema-auth-postgres';
import { eq } from 'drizzle-orm';
import { getS3Config, isS3Configured } from '@/lib/server/storage/s3';
import {
diff --git a/tests/global-teardown.ts b/tests/global-teardown.ts
index 826a85e..9a4d339 100644
--- a/tests/global-teardown.ts
+++ b/tests/global-teardown.ts
@@ -1,13 +1,13 @@
import { ListObjectsV2Command } from '@aws-sdk/client-s3';
import { and, eq, inArray, like, ne } from 'drizzle-orm';
-import { db } from '../src/db';
-import { audiobooks, audiobookChapters, documentPreviews, documents } from '../src/db/schema';
+import { db } from '@openreader/database';
+import { audiobooks, audiobookChapters, documentPreviews, documents } from '@openreader/database/schema';
import { deleteDocumentPrefix } from '../src/lib/server/documents/blobstore';
import { deleteAudiobookPrefix } from '../src/lib/server/audiobooks/blobstore';
import { deleteTtsSegmentPrefix } from '../src/lib/server/tts/segments-blobstore';
import { getS3Client, getS3Config, isS3Configured } from '../src/lib/server/storage/s3';
-import * as authSchemaSqlite from '../src/db/schema_auth_sqlite';
-import * as authSchemaPostgres from '../src/db/schema_auth_postgres';
+import * as authSchemaSqlite from '@openreader/database/schema-auth-sqlite';
+import * as authSchemaPostgres from '@openreader/database/schema-auth-postgres';
function chunk(items: T[], size: number): T[][] {
if (items.length === 0) return [];
diff --git a/tests/unit/admin-providers-validation.vitest.spec.ts b/tests/unit/admin-providers-validation.vitest.spec.ts
index 6511e0d..6638316 100644
--- a/tests/unit/admin-providers-validation.vitest.spec.ts
+++ b/tests/unit/admin-providers-validation.vitest.spec.ts
@@ -1,7 +1,7 @@
import { describe, expect, test } from 'vitest';
import { inArray } from 'drizzle-orm';
-import { db } from '../../src/db';
-import { adminProviders } from '../../src/db/schema';
+import { db } from '@openreader/database';
+import { adminProviders } from '@openreader/database/schema';
import {
AdminProviderError,
diff --git a/tests/unit/compute-worker-client-contract.vitest.spec.ts b/tests/unit/compute-worker-client-contract.vitest.spec.ts
index 39adac6..3eaa1e2 100644
--- a/tests/unit/compute-worker-client-contract.vitest.spec.ts
+++ b/tests/unit/compute-worker-client-contract.vitest.spec.ts
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, test } from 'vitest';
-import { createComputeWorkerApp, type ComputeWorkerApp } from '../../compute-worker/src/api/app';
-import { FakeControlPlane } from '../../compute-worker/tests/fixtures/fake-control-plane';
+import { createComputeWorkerApp, type ComputeWorkerApp } from '../../packages/compute-worker/src/api/app';
+import { FakeControlPlane } from '../../packages/compute-worker/tests/fixtures/fake-control-plane';
import { ComputeWorkerClient } from '../../src/lib/server/compute-worker/client';
describe('ComputeWorkerClient contract', () => {
diff --git a/tests/unit/document-blob-lease.vitest.spec.ts b/tests/unit/document-blob-lease.vitest.spec.ts
index 684d75b..c6aede2 100644
--- a/tests/unit/document-blob-lease.vitest.spec.ts
+++ b/tests/unit/document-blob-lease.vitest.spec.ts
@@ -1,10 +1,10 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
-import * as sqliteSchema from '../../src/db/schema_sqlite';
+import * as sqliteSchema from '@openreader/database/schema-sqlite';
const holder = vi.hoisted(() => ({ db: null as unknown as ReturnType }));
-vi.mock('@/db', () => ({
+vi.mock('@openreader/database', () => ({
get db() {
return holder.db;
},
diff --git a/tests/unit/document-delete-cleanup.vitest.spec.ts b/tests/unit/document-delete-cleanup.vitest.spec.ts
index 962905e..618f1fe 100644
--- a/tests/unit/document-delete-cleanup.vitest.spec.ts
+++ b/tests/unit/document-delete-cleanup.vitest.spec.ts
@@ -12,7 +12,7 @@ function resultBuilder(result: unknown[]) {
};
}
-vi.mock('@/db', () => {
+vi.mock('@openreader/database', () => {
const database = {
delete: vi.fn(() => ({
where: vi.fn(() => ({
diff --git a/tests/unit/pdf-parsed-events-route-worker-proxy.vitest.spec.ts b/tests/unit/pdf-parsed-events-route-worker-proxy.vitest.spec.ts
index 85b49cb..a6d2822 100644
--- a/tests/unit/pdf-parsed-events-route-worker-proxy.vitest.spec.ts
+++ b/tests/unit/pdf-parsed-events-route-worker-proxy.vitest.spec.ts
@@ -15,7 +15,7 @@ const hoisted = vi.hoisted(() => ({
openOperationEvents: vi.fn(),
}));
-vi.mock('@/db', () => ({
+vi.mock('@openreader/database', () => ({
get db() {
return hoisted.db;
},
diff --git a/tests/unit/pdf-parsed-route-worker-flow.vitest.spec.ts b/tests/unit/pdf-parsed-route-worker-flow.vitest.spec.ts
index 7160c92..055bd3c 100644
--- a/tests/unit/pdf-parsed-route-worker-flow.vitest.spec.ts
+++ b/tests/unit/pdf-parsed-route-worker-flow.vitest.spec.ts
@@ -20,7 +20,7 @@ const hoisted = vi.hoisted(() => ({
buildComputeRateLimitedResponse: vi.fn(),
}));
-vi.mock('@/db', () => ({
+vi.mock('@openreader/database', () => ({
get db() {
return hoisted.db;
},
diff --git a/tests/unit/reap-orphaned-blobs.vitest.spec.ts b/tests/unit/reap-orphaned-blobs.vitest.spec.ts
index 667a242..7b5b8b5 100644
--- a/tests/unit/reap-orphaned-blobs.vitest.spec.ts
+++ b/tests/unit/reap-orphaned-blobs.vitest.spec.ts
@@ -26,7 +26,7 @@ vi.mock('@/lib/server/documents/blob-lease', () => ({
})),
}));
vi.mock('@/lib/server/errors/logging', () => ({ logDegraded: vi.fn() }));
-vi.mock('@/db', () => ({
+vi.mock('@openreader/database', () => ({
db: {
select: () => ({
from: () => ({
diff --git a/tests/unit/runtime-seed-json.vitest.spec.ts b/tests/unit/runtime-seed-json.vitest.spec.ts
index fd710bf..4dfc520 100644
--- a/tests/unit/runtime-seed-json.vitest.spec.ts
+++ b/tests/unit/runtime-seed-json.vitest.spec.ts
@@ -1,8 +1,8 @@
import { and, eq, inArray, not } from 'drizzle-orm';
import { describe, expect, test } from 'vitest';
-import { db } from '../../src/db';
-import { adminProviders, adminSettings } from '../../src/db/schema';
+import { db } from '@openreader/database';
+import { adminProviders, adminSettings } from '@openreader/database/schema';
import { RUNTIME_KEYS, seedRuntimeConfigFromValues } from '../../src/lib/server/admin/settings';
import { __seedInternals } from '../../src/lib/server/admin/seed';
diff --git a/tests/unit/scheduled-tasks-engine.vitest.spec.ts b/tests/unit/scheduled-tasks-engine.vitest.spec.ts
index 715bac4..124fd1e 100644
--- a/tests/unit/scheduled-tasks-engine.vitest.spec.ts
+++ b/tests/unit/scheduled-tasks-engine.vitest.spec.ts
@@ -2,12 +2,12 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { eq } from 'drizzle-orm';
-import * as sqliteSchema from '../../src/db/schema_sqlite';
+import * as sqliteSchema from '@openreader/database/schema-sqlite';
// Back the engine with a real in-memory SQLite so the CAS claim, due-detection,
// and nextRunAt arithmetic are exercised against real SQL rather than mocks.
const holder = vi.hoisted(() => ({ db: null as unknown as ReturnType }));
-vi.mock('@/db', () => ({
+vi.mock('@openreader/database', () => ({
get db() {
return holder.db;
},
diff --git a/tests/unit/transfer-user-documents.vitest.spec.ts b/tests/unit/transfer-user-documents.vitest.spec.ts
index 1444d1d..11c93d7 100644
--- a/tests/unit/transfer-user-documents.vitest.spec.ts
+++ b/tests/unit/transfer-user-documents.vitest.spec.ts
@@ -7,7 +7,7 @@ import {
documents,
ttsSegmentEntries,
ttsSegmentVariants,
-} from '../../src/db/schema_sqlite';
+} from '@openreader/database/schema-sqlite';
import { transferUserDocumentSettings, transferUserDocuments } from '../../src/lib/server/user/claim-data';
describe('transferUserDocuments', () => {
diff --git a/tests/unit/tts-segments-cache.vitest.spec.ts b/tests/unit/tts-segments-cache.vitest.spec.ts
index 18ce89b..70fdf0c 100644
--- a/tests/unit/tts-segments-cache.vitest.spec.ts
+++ b/tests/unit/tts-segments-cache.vitest.spec.ts
@@ -15,7 +15,7 @@ function resultBuilder(result: unknown[]) {
};
}
-vi.mock('@/db', () => ({
+vi.mock('@openreader/database', () => ({
db: {
select: vi.fn(() => ({
from: vi.fn(() => resultBuilder(mocks.selectResults.shift() ?? [])),
diff --git a/tests/unit/user-data-cleanup.vitest.spec.ts b/tests/unit/user-data-cleanup.vitest.spec.ts
index ff8496b..af1cdad 100644
--- a/tests/unit/user-data-cleanup.vitest.spec.ts
+++ b/tests/unit/user-data-cleanup.vitest.spec.ts
@@ -18,7 +18,7 @@ function resultBuilder(result: unknown[]) {
};
}
-vi.mock('@/db', () => {
+vi.mock('@openreader/database', () => {
const database = {
select: vi.fn(() => ({
from: vi.fn(() => ({
diff --git a/vitest.config.ts b/vitest.config.ts
index 99417d3..b04ccea 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -38,8 +38,8 @@ export default defineConfig({
test: {
name: 'compute-worker',
environment: 'node',
- include: ['compute-worker/tests/{unit,api,compute}/**/*.test.ts'],
- setupFiles: ['compute-worker/tests/setup-env.ts'],
+ include: ['packages/compute-worker/tests/{unit,api,compute}/**/*.test.ts'],
+ setupFiles: ['packages/compute-worker/tests/setup-env.ts'],
},
},
],