From 358a56cc9a57337ed858bebf1474e2e652bd5414 Mon Sep 17 00:00:00 2001 From: Richard R Date: Sat, 13 Jun 2026 13:51:18 -0600 Subject: [PATCH] refactor: implement modular monorepo packages - Extract database schema and migrations into @openreader/database - Extract startup and orchestration scripts into @openreader/bootstrap - Move compute-worker into packages/compute-worker - Remove runtime dependency on drizzle-kit - Update Dockerfile to deploy isolated packages without merging node_modules --- .github/workflows/docker-publish.yml | 4 +- Dockerfile | 41 ++-- .../entrypoint-migration-tools/package.json | 14 -- docker/examples/compose.local.yml | 2 +- docs-site/docs/configure/migrations.md | 57 +++--- docs-site/docs/deploy/vercel-deployment.md | 2 +- drizzle/scripts/migrate.mjs | 56 ----- next.config.ts | 2 +- package.json | 22 +- packages/bootstrap/package.json | 25 +++ .../bootstrap/src/cli.mjs | 192 ++---------------- packages/bootstrap/src/embedded-nats.mjs | 5 + packages/bootstrap/src/embedded-seaweedfs.mjs | 95 +++++++++ packages/bootstrap/src/embedded-worker.mjs | 30 +++ .../bootstrap/src/storage-migration.mjs | 0 .../compute-worker}/.env.example | 0 .../compute-worker}/Dockerfile | 8 +- .../compute-worker}/openapi.json | 0 .../compute-worker}/package.json | 0 .../scripts/generate-openapi.ts | 0 .../compute-worker}/src/api/app.ts | 0 .../src/api/compute-operation.ts | 0 .../compute-worker}/src/api/http-hooks.ts | 0 .../compute-worker}/src/api/routes.ts | 0 .../compute-worker}/src/api/schemas.ts | 0 .../compute-worker}/src/api/types.ts | 0 .../src/inference/pdf/assets/LICENSE.txt | 0 .../src/inference/pdf/assets/manifest.json | 0 .../src/inference/pdf/document-layout.ts | 0 .../src/inference/pdf/layout-model.ts | 0 .../src/inference/pdf/model.ts | 0 .../src/inference/pdf/normalize-text.ts | 0 .../src/inference/pdf/parse.ts | 0 .../src/inference/pdf/pdfjs.ts | 0 .../src/inference/pdf/render.ts | 0 .../src/inference/pdf/stitch.ts | 0 .../compute-worker}/src/inference/runtime.ts | 0 .../src/inference/whisper/align.ts | 0 .../src/inference/whisper/assets/LICENSE.txt | 0 .../inference/whisper/assets/manifest.json | 0 .../inference/whisper/assets/mel_filters.npz | Bin .../src/inference/whisper/decoder.ts | 0 .../src/inference/whisper/model.ts | 0 .../src/inference/whisper/timestamps.ts | 0 .../src/infrastructure/config.ts | 0 .../src/infrastructure/json-codec.ts | 0 .../src/infrastructure/nats-adapters.ts | 0 .../src/infrastructure/nats-session.ts | 0 .../src/infrastructure/nats.ts | 0 .../src/infrastructure/platform.ts | 0 .../src/infrastructure/storage.ts | 0 .../compute-worker}/src/jobs/handlers.ts | 0 .../src/jobs/pdf-artifact-persistence.ts | 0 .../compute-worker}/src/jobs/pdf-progress.ts | 0 .../src/jobs/worker-loop-policy.ts | 0 .../compute-worker}/src/jobs/worker-loop.ts | 0 .../src/operations/contracts.ts | 0 .../compute-worker}/src/operations/index.ts | 0 .../compute-worker}/src/operations/keys.ts | 0 .../src/operations/reconciliation.ts | 0 .../src/operations/recovery.ts | 0 .../compute-worker}/src/operations/service.ts | 0 .../compute-worker}/src/operations/sse.ts | 0 .../src/operations/state-machine.ts | 0 .../compute-worker}/src/operations/types.ts | 0 .../compute-worker}/src/server.ts | 0 .../src/storage/artifact-addressing.ts | 0 .../compute-worker}/tests/api/routes.test.ts | 0 .../pdf-merge-text-with-regions.test.ts | 0 .../pdf-parse-normalize-text-items.test.ts | 0 .../pdf-stitch-cross-page-blocks.test.ts | 0 .../algorithms/support/document-fixtures.ts | 2 +- .../whisper-alignment-mapping.test.ts | 0 .../algorithms/whisper-decoder.test.ts | 0 .../algorithms/whisper-spectral.test.ts | 0 .../whisper-token-timestamps.test.ts | 0 .../control-plane/orchestrator.test.ts | 0 .../control-plane/run-layout-model.test.ts | 0 .../tests/compute/control-plane/sse.test.ts | 0 .../control-plane/state-machine.test.ts | 0 .../helpers/in-memory-control-plane.ts | 0 .../tests/fixtures/fake-control-plane.ts | 0 .../compute-worker}/tests/setup-env.ts | 0 .../compute-worker}/tests/tsconfig.json | 0 .../tests/unit/jetstream-adapters.test.ts | 11 + .../tests/unit/key-and-progress.test.ts | 0 .../tests/unit/orphan-recovery.test.ts | 8 +- .../unit/pdf-artifact-persistence.test.ts | 0 .../tests/unit/worker-loop-policy.test.ts | 0 .../tests/unit/worker-loop.test.ts | 0 .../compute-worker}/tsconfig.json | 2 +- packages/database/bin/migrate.mjs | 9 + .../database/drizzle.config.pg.ts | 4 +- .../database/drizzle.config.sqlite.ts | 4 +- .../postgres/0000_black_lucky_pierre.sql | 0 .../postgres/0001_tts_segments.sql | 0 .../0002_add_segment_key_to_tts_segments.sql | 0 .../postgres/0003_tts_segments_v2_split.sql | 0 .../migrations}/postgres/0004_admin_panel.sql | 0 .../postgres/0005_pdf_layout_and_compute.sql | 0 .../0006_preview-defaults-cleanup.sql | 0 .../postgres/0007_pdf_parse_progress.sql | 0 .../postgres/0008_user_job_events.sql | 0 .../postgres/0009_drop_pdf_parse.sql | 0 .../0010_user-data-cleanup-cascades.sql | 0 .../postgres/0011_scheduled-tasks.sql | 0 .../postgres/meta/0000_snapshot.json | 0 .../postgres/meta/0001_snapshot.json | 0 .../postgres/meta/0002_snapshot.json | 0 .../postgres/meta/0003_snapshot.json | 0 .../postgres/meta/0004_snapshot.json | 0 .../postgres/meta/0005_snapshot.json | 0 .../postgres/meta/0006_snapshot.json | 0 .../postgres/meta/0007_snapshot.json | 0 .../postgres/meta/0008_snapshot.json | 0 .../postgres/meta/0009_snapshot.json | 0 .../postgres/meta/0010_snapshot.json | 0 .../postgres/meta/0011_snapshot.json | 0 .../migrations}/postgres/meta/_journal.json | 0 .../sqlite/0000_familiar_caretaker.sql | 0 .../migrations}/sqlite/0001_tts_segments.sql | 0 .../0002_add_segment_key_to_tts_segments.sql | 0 .../sqlite/0003_tts_segments_v2_split.sql | 0 .../migrations}/sqlite/0004_admin_panel.sql | 0 .../sqlite/0005_pdf_layout_and_compute.sql | 0 .../sqlite/0006_preview-defaults-cleanup.sql | 0 .../sqlite/0007_pdf_parse_progress.sql | 0 .../sqlite/0008_user_job_events.sql | 0 .../sqlite/0009_drop_pdf_parse.sql | 0 .../0010_user-data-cleanup-cascades.sql | 0 .../sqlite/0011_scheduled-tasks.sql | 0 .../sqlite/meta/0000_snapshot.json | 0 .../sqlite/meta/0001_snapshot.json | 0 .../sqlite/meta/0002_snapshot.json | 0 .../sqlite/meta/0003_snapshot.json | 0 .../sqlite/meta/0004_snapshot.json | 0 .../sqlite/meta/0005_snapshot.json | 0 .../sqlite/meta/0006_snapshot.json | 0 .../sqlite/meta/0007_snapshot.json | 0 .../sqlite/meta/0008_snapshot.json | 0 .../sqlite/meta/0009_snapshot.json | 0 .../sqlite/meta/0010_snapshot.json | 0 .../sqlite/meta/0011_snapshot.json | 0 .../migrations}/sqlite/meta/_journal.json | 0 packages/database/package.json | 33 +++ .../database}/scripts/generate.mjs | 9 +- {src/db => packages/database/src}/index.ts | 0 packages/database/src/migrate.mjs | 51 +++++ .../database/src}/run-in-transaction.ts | 2 +- {src/db => packages/database/src}/schema.ts | 0 .../database/src}/schema_auth_postgres.ts | 0 .../database/src}/schema_auth_sqlite.ts | 0 .../database/src}/schema_postgres.ts | 0 .../database/src}/schema_sqlite.ts | 0 pnpm-lock.yaml | 52 +++-- pnpm-workspace.yaml | 3 +- scripts/check-compute-boundary.mjs | 2 +- scripts/check-next-server-bundle.mjs | 2 +- src/app/api/audiobook/chapter/route.ts | 4 +- src/app/api/audiobook/route.ts | 4 +- src/app/api/audiobook/status/route.ts | 4 +- .../api/documents/[id]/parsed/events/route.ts | 4 +- src/app/api/documents/[id]/parsed/route.ts | 4 +- src/app/api/documents/[id]/settings/route.ts | 4 +- .../api/documents/blob/get/fallback/route.ts | 4 +- .../api/documents/blob/get/presign/route.ts | 4 +- .../documents/blob/preview/fallback/route.ts | 4 +- src/app/api/documents/blob/preview/utils.ts | 4 +- src/app/api/documents/route.ts | 4 +- src/app/api/tts/segments/ensure/route.ts | 4 +- src/app/api/tts/segments/manifest/route.ts | 4 +- src/app/api/user/claim/route.ts | 4 +- src/app/api/user/export/route.ts | 8 +- .../state/changelog/version-check/route.ts | 4 +- src/app/api/user/state/preferences/route.ts | 4 +- src/app/api/user/state/progress/route.ts | 4 +- src/lib/server/admin/email-sync.ts | 6 +- src/lib/server/admin/providers.ts | 4 +- src/lib/server/admin/seed.ts | 4 +- src/lib/server/admin/settings.ts | 4 +- src/lib/server/audiobooks/prune.ts | 4 +- src/lib/server/auth/auth.ts | 6 +- src/lib/server/documents/blob-lease.ts | 4 +- src/lib/server/documents/delete-owned.ts | 4 +- src/lib/server/documents/previews.ts | 4 +- src/lib/server/documents/register-upload.ts | 4 +- src/lib/server/rate-limit/job-rate-limiter.ts | 4 +- src/lib/server/rate-limit/rate-limiter.ts | 6 +- src/lib/server/tasks/engine.ts | 4 +- .../server/tasks/handlers/prune-job-events.ts | 4 +- .../tasks/handlers/reap-orphaned-blobs.ts | 4 +- src/lib/server/testing/test-namespace.ts | 2 +- src/lib/server/tts/segments-audio.ts | 4 +- src/lib/server/tts/segments-auth.ts | 4 +- src/lib/server/tts/segments-cache.ts | 4 +- src/lib/server/user/claim-data.ts | 4 +- src/lib/server/user/data-cleanup.ts | 8 +- tests/global-teardown.ts | 8 +- .../admin-providers-validation.vitest.spec.ts | 4 +- ...pute-worker-client-contract.vitest.spec.ts | 4 +- tests/unit/document-blob-lease.vitest.spec.ts | 4 +- .../document-delete-cleanup.vitest.spec.ts | 2 +- ...d-events-route-worker-proxy.vitest.spec.ts | 2 +- ...df-parsed-route-worker-flow.vitest.spec.ts | 2 +- tests/unit/reap-orphaned-blobs.vitest.spec.ts | 2 +- tests/unit/runtime-seed-json.vitest.spec.ts | 4 +- .../scheduled-tasks-engine.vitest.spec.ts | 4 +- .../transfer-user-documents.vitest.spec.ts | 2 +- tests/unit/tts-segments-cache.vitest.spec.ts | 2 +- tests/unit/user-data-cleanup.vitest.spec.ts | 2 +- vitest.config.ts | 4 +- 211 files changed, 497 insertions(+), 466 deletions(-) delete mode 100644 docker/entrypoint-migration-tools/package.json delete mode 100644 drizzle/scripts/migrate.mjs create mode 100644 packages/bootstrap/package.json rename scripts/openreader-entrypoint.mjs => packages/bootstrap/src/cli.mjs (77%) create mode 100644 packages/bootstrap/src/embedded-nats.mjs create mode 100644 packages/bootstrap/src/embedded-seaweedfs.mjs create mode 100644 packages/bootstrap/src/embedded-worker.mjs rename scripts/migrate-fs-v2.mjs => packages/bootstrap/src/storage-migration.mjs (100%) rename {compute-worker => packages/compute-worker}/.env.example (100%) rename {compute-worker => packages/compute-worker}/Dockerfile (72%) rename {compute-worker => packages/compute-worker}/openapi.json (100%) rename {compute-worker => packages/compute-worker}/package.json (100%) rename {compute-worker => packages/compute-worker}/scripts/generate-openapi.ts (100%) rename {compute-worker => packages/compute-worker}/src/api/app.ts (100%) rename {compute-worker => packages/compute-worker}/src/api/compute-operation.ts (100%) rename {compute-worker => packages/compute-worker}/src/api/http-hooks.ts (100%) rename {compute-worker => packages/compute-worker}/src/api/routes.ts (100%) rename {compute-worker => packages/compute-worker}/src/api/schemas.ts (100%) rename {compute-worker => packages/compute-worker}/src/api/types.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/assets/LICENSE.txt (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/assets/manifest.json (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/document-layout.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/layout-model.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/model.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/normalize-text.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/parse.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/pdfjs.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/render.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/pdf/stitch.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/runtime.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/whisper/align.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/whisper/assets/LICENSE.txt (100%) rename {compute-worker => packages/compute-worker}/src/inference/whisper/assets/manifest.json (100%) rename {compute-worker => packages/compute-worker}/src/inference/whisper/assets/mel_filters.npz (100%) rename {compute-worker => packages/compute-worker}/src/inference/whisper/decoder.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/whisper/model.ts (100%) rename {compute-worker => packages/compute-worker}/src/inference/whisper/timestamps.ts (100%) rename {compute-worker => packages/compute-worker}/src/infrastructure/config.ts (100%) rename {compute-worker => packages/compute-worker}/src/infrastructure/json-codec.ts (100%) rename {compute-worker => packages/compute-worker}/src/infrastructure/nats-adapters.ts (100%) rename {compute-worker => packages/compute-worker}/src/infrastructure/nats-session.ts (100%) rename {compute-worker => packages/compute-worker}/src/infrastructure/nats.ts (100%) rename {compute-worker => packages/compute-worker}/src/infrastructure/platform.ts (100%) rename {compute-worker => packages/compute-worker}/src/infrastructure/storage.ts (100%) rename {compute-worker => packages/compute-worker}/src/jobs/handlers.ts (100%) rename {compute-worker => packages/compute-worker}/src/jobs/pdf-artifact-persistence.ts (100%) rename {compute-worker => packages/compute-worker}/src/jobs/pdf-progress.ts (100%) rename {compute-worker => packages/compute-worker}/src/jobs/worker-loop-policy.ts (100%) rename {compute-worker => packages/compute-worker}/src/jobs/worker-loop.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/contracts.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/index.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/keys.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/reconciliation.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/recovery.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/service.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/sse.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/state-machine.ts (100%) rename {compute-worker => packages/compute-worker}/src/operations/types.ts (100%) rename {compute-worker => packages/compute-worker}/src/server.ts (100%) rename {compute-worker => packages/compute-worker}/src/storage/artifact-addressing.ts (100%) rename {compute-worker => packages/compute-worker}/tests/api/routes.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/algorithms/pdf-merge-text-with-regions.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/algorithms/pdf-parse-normalize-text-items.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/algorithms/pdf-stitch-cross-page-blocks.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/algorithms/support/document-fixtures.ts (93%) rename {compute-worker => packages/compute-worker}/tests/compute/algorithms/whisper-alignment-mapping.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/algorithms/whisper-decoder.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/algorithms/whisper-spectral.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/algorithms/whisper-token-timestamps.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/control-plane/orchestrator.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/control-plane/run-layout-model.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/control-plane/sse.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/control-plane/state-machine.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/compute/helpers/in-memory-control-plane.ts (100%) rename {compute-worker => packages/compute-worker}/tests/fixtures/fake-control-plane.ts (100%) rename {compute-worker => packages/compute-worker}/tests/setup-env.ts (100%) rename {compute-worker => packages/compute-worker}/tests/tsconfig.json (100%) rename {compute-worker => packages/compute-worker}/tests/unit/jetstream-adapters.test.ts (95%) rename {compute-worker => packages/compute-worker}/tests/unit/key-and-progress.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/unit/orphan-recovery.test.ts (88%) rename {compute-worker => packages/compute-worker}/tests/unit/pdf-artifact-persistence.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/unit/worker-loop-policy.test.ts (100%) rename {compute-worker => packages/compute-worker}/tests/unit/worker-loop.test.ts (100%) rename {compute-worker => packages/compute-worker}/tsconfig.json (73%) create mode 100755 packages/database/bin/migrate.mjs rename drizzle.config.pg.ts => packages/database/drizzle.config.pg.ts (72%) rename drizzle.config.sqlite.ts => packages/database/drizzle.config.sqlite.ts (57%) rename {drizzle => packages/database/migrations}/postgres/0000_black_lucky_pierre.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0001_tts_segments.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0002_add_segment_key_to_tts_segments.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0003_tts_segments_v2_split.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0004_admin_panel.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0005_pdf_layout_and_compute.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0006_preview-defaults-cleanup.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0007_pdf_parse_progress.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0008_user_job_events.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0009_drop_pdf_parse.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0010_user-data-cleanup-cascades.sql (100%) rename {drizzle => packages/database/migrations}/postgres/0011_scheduled-tasks.sql (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0000_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0001_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0002_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0003_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0004_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0005_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0006_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0007_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0008_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0009_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0010_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/0011_snapshot.json (100%) rename {drizzle => packages/database/migrations}/postgres/meta/_journal.json (100%) rename {drizzle => packages/database/migrations}/sqlite/0000_familiar_caretaker.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0001_tts_segments.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0002_add_segment_key_to_tts_segments.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0003_tts_segments_v2_split.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0004_admin_panel.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0005_pdf_layout_and_compute.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0006_preview-defaults-cleanup.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0007_pdf_parse_progress.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0008_user_job_events.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0009_drop_pdf_parse.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0010_user-data-cleanup-cascades.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/0011_scheduled-tasks.sql (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0000_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0001_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0002_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0003_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0004_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0005_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0006_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0007_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0008_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0009_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0010_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/0011_snapshot.json (100%) rename {drizzle => packages/database/migrations}/sqlite/meta/_journal.json (100%) create mode 100644 packages/database/package.json rename {drizzle => packages/database}/scripts/generate.mjs (91%) rename {src/db => packages/database/src}/index.ts (100%) create mode 100644 packages/database/src/migrate.mjs rename {src/db => packages/database/src}/run-in-transaction.ts (96%) rename {src/db => packages/database/src}/schema.ts (100%) rename {src/db => packages/database/src}/schema_auth_postgres.ts (100%) rename {src/db => packages/database/src}/schema_auth_sqlite.ts (100%) rename {src/db => packages/database/src}/schema_postgres.ts (100%) rename {src/db => packages/database/src}/schema_sqlite.ts (100%) 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'], }, }, ],