From 28d84c623e4a3cfc963b63662d40c9469e3eb9be Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 25 Jul 2026 08:36:17 +0000 Subject: [PATCH 1/3] refactor(misc): drop services_public from graphile plugins, jobs fixtures, compose files --- docker-compose.jobs.yml | 8 +-- docker-compose.local-email.yml | 4 +- graphile/graphile-llm/README.md | 2 +- graphile/graphile-llm/src/chat.ts | 2 +- graphile/graphile-llm/src/embedder.ts | 2 +- .../src/plugins/llm-module-plugin.ts | 2 +- graphile/graphile-llm/src/types.ts | 2 +- .../src/plugins/custom-inflector.ts | 2 +- jobs/DEVELOPMENT_JOBS.md | 5 +- .../__fixtures__/jobs.seed.sql | 20 +++++--- .../__tests__/jobs.e2e.test.ts | 49 ++++--------------- .../__snapshots__/export.test.ts.snap | 6 +-- packages/csv-to-pg/__tests__/export.test.ts | 6 +-- 13 files changed, 42 insertions(+), 68 deletions(-) diff --git a/docker-compose.jobs.yml b/docker-compose.jobs.yml index 8a00233aba..d6ee17243e 100644 --- a/docker-compose.jobs.yml +++ b/docker-compose.jobs.yml @@ -23,11 +23,11 @@ services: PGDATABASE: constructive # Api configuration API_ENABLE_SERVICES: "true" - API_EXPOSED_SCHEMAS: "metaschema_public,services_public,constructive_auth_public" + API_EXPOSED_SCHEMAS: "metaschema_public,constructive_routing_public,constructive_auth_public" # API_IS_PUBLIC=false enables header-based routing (X-Api-Name, X-Database-Id, X-Meta-Schema) API_IS_PUBLIC: "false" # Meta schemas used for schema validation and X-Meta-Schema routing - API_META_SCHEMAS: "metaschema_public,services_public,metaschema_modules_public,constructive_auth_public" + API_META_SCHEMAS: "metaschema_public,constructive_catalog_public,constructive_routing_public,constructive_apps_public,metaschema_modules_public,constructive_auth_public" API_ANON_ROLE: "administrator" API_ROLE_NAME: "administrator" ports: @@ -58,11 +58,11 @@ services: PGDATABASE: constructive # Api configuration API_ENABLE_SERVICES: "false" - API_EXPOSED_SCHEMAS: "metaschema_public,services_public,constructive_auth_public" + API_EXPOSED_SCHEMAS: "metaschema_public,constructive_routing_public,constructive_auth_public" # Public-facing server API_IS_PUBLIC: "true" # Meta schemas used for schema validation - API_META_SCHEMAS: "metaschema_public,services_public,metaschema_modules_public,constructive_auth_public" + API_META_SCHEMAS: "metaschema_public,constructive_catalog_public,constructive_routing_public,constructive_apps_public,metaschema_modules_public,constructive_auth_public" API_ANON_ROLE: "anonymous" API_ROLE_NAME: "authenticated" ports: diff --git a/docker-compose.local-email.yml b/docker-compose.local-email.yml index 0fa36746e9..20a055fbff 100644 --- a/docker-compose.local-email.yml +++ b/docker-compose.local-email.yml @@ -48,8 +48,8 @@ services: # API configuration API_ENABLE_SERVICES: "true" API_IS_PUBLIC: "false" - API_EXPOSED_SCHEMAS: "metaschema_public,services_public,constructive_auth_public" - API_META_SCHEMAS: "metaschema_public,services_public,metaschema_modules_public,constructive_auth_public" + API_EXPOSED_SCHEMAS: "metaschema_public,constructive_routing_public,constructive_auth_public" + API_META_SCHEMAS: "metaschema_public,constructive_catalog_public,constructive_routing_public,constructive_apps_public,metaschema_modules_public,constructive_auth_public" API_ANON_ROLE: "administrator" API_ROLE_NAME: "administrator" ports: diff --git a/graphile/graphile-llm/README.md b/graphile/graphile-llm/README.md index 03816aac61..b40e826af8 100644 --- a/graphile/graphile-llm/README.md +++ b/graphile/graphile-llm/README.md @@ -56,7 +56,7 @@ The preset bundles all plugins listed below. You can also import each plugin ind - **Text mutation fields** — adds `{column}Text: String` companion fields on create/update inputs for vector columns - **RAG queries** — adds `ragQuery` and `embedText` root query fields; detects `@hasChunks` smart tags for chunk-aware retrieval - **Pluggable providers** — provider-based architecture for both embedding and chat completion (Ollama via `@agentic-kit/ollama`, extensible to OpenAI, etc.) -- **Per-database configuration** — reads `llm_module` from `services_public.api_modules` for per-API provider config +- **Per-database configuration** — reads `llm_module` from `constructive_routing_public.api_modules` for per-API provider config - **Toggleable** — each capability (`enableTextSearch`, `enableTextMutations`, `enableRag`) can be independently enabled or disabled - **Plugin-conditional** — fields only appear in the schema when the plugin is loaded diff --git a/graphile/graphile-llm/src/chat.ts b/graphile/graphile-llm/src/chat.ts index ea973d47d7..5962e900a1 100644 --- a/graphile/graphile-llm/src/chat.ts +++ b/graphile/graphile-llm/src/chat.ts @@ -96,7 +96,7 @@ export function buildChatCompleter(config: ChatConfig): ChatFunction | null { /** * Build a chat completer from an `llm_module` api_modules row. * - * @param data - The llm_module data from services_public.api_modules + * @param data - The llm_module data from constructive_routing_public.api_modules * @returns A ChatFunction, or null if the chat provider is not configured */ export function buildChatCompleterFromModule(data: LlmModuleData): ChatFunction | null { diff --git a/graphile/graphile-llm/src/embedder.ts b/graphile/graphile-llm/src/embedder.ts index 99bac24c33..f3572855eb 100644 --- a/graphile/graphile-llm/src/embedder.ts +++ b/graphile/graphile-llm/src/embedder.ts @@ -102,7 +102,7 @@ export function buildEmbedder(config: EmbedderConfig): EmbedderFunction | null { /** * Build an embedder from an `llm_module` api_modules row. * - * @param data - The llm_module data from services_public.api_modules + * @param data - The llm_module data from constructive_routing_public.api_modules * @returns An EmbedderFunction, or null if the provider is not supported */ export function buildEmbedderFromModule(data: LlmModuleData): EmbedderFunction | null { diff --git a/graphile/graphile-llm/src/plugins/llm-module-plugin.ts b/graphile/graphile-llm/src/plugins/llm-module-plugin.ts index 0b24e48f74..04ea68efaa 100644 --- a/graphile/graphile-llm/src/plugins/llm-module-plugin.ts +++ b/graphile/graphile-llm/src/plugins/llm-module-plugin.ts @@ -1,7 +1,7 @@ /** * LlmModulePlugin * - * Detects and loads the `llm_module` configuration from `services_public.api_modules`. + * Detects and loads the `llm_module` configuration from `constructive_routing_public.api_modules`. * Makes the resolved embedder and chat completer available to other plugins * via the build context. * diff --git a/graphile/graphile-llm/src/types.ts b/graphile/graphile-llm/src/types.ts index 21fafa0dff..d121c87a84 100644 --- a/graphile/graphile-llm/src/types.ts +++ b/graphile/graphile-llm/src/types.ts @@ -103,7 +103,7 @@ export interface ChatConfig { // ─── LLM Module Types ─────────────────────────────────────────────────────── /** - * The shape of the `llm_module` data stored in `services_public.api_modules`. + * The shape of the `llm_module` data stored in `constructive_routing_public.api_modules`. * * This is the per-database configuration that controls which LLM provider * and models are available for that API. diff --git a/graphile/graphile-settings/src/plugins/custom-inflector.ts b/graphile/graphile-settings/src/plugins/custom-inflector.ts index f205f3cc0f..8609a89d46 100644 --- a/graphile/graphile-settings/src/plugins/custom-inflector.ts +++ b/graphile/graphile-settings/src/plugins/custom-inflector.ts @@ -181,7 +181,7 @@ export const InflektPlugin: GraphileConfig.Plugin = { * WHY THIS EXISTS: * PostGraphile v5's default `_schemaPrefix` inflector only removes the prefix * for the FIRST schema in the pgServices.schemas array. All other schemas get - * prefixed with their schema name (e.g., "services_public_api" -> "servicesPublicApi"). + * prefixed with their schema name (e.g., "constructive_routing_public_api" -> "constructiveRoutingPublicApi"). * * This is problematic for multi-schema setups where you want clean, consistent * naming across all schemas. diff --git a/jobs/DEVELOPMENT_JOBS.md b/jobs/DEVELOPMENT_JOBS.md index df42835a75..01b453c6c6 100644 --- a/jobs/DEVELOPMENT_JOBS.md +++ b/jobs/DEVELOPMENT_JOBS.md @@ -349,8 +349,9 @@ To switch back to dry-run, set `SEND_VERIFICATION_LINK_DRY_RUN=true` (or delete - need to run some manual migrations like: ```sql BEGIN; - UPDATE services_public.domains SET domain = 'localhost'; - UPDATE services_public.apis SET dbname = 'constructive'; + UPDATE constructive_routing_public.domains SET hostname = 'localhost'; + UPDATE constructive_routing_public.hostname_bindings SET hostname = 'localhost'; + UPDATE constructive_routing_public.apis SET dbname = 'constructive'; COMMIT; ------ Some more diff --git a/jobs/knative-job-service/__fixtures__/jobs.seed.sql b/jobs/knative-job-service/__fixtures__/jobs.seed.sql index 49705e8e89..f35a80aa90 100644 --- a/jobs/knative-job-service/__fixtures__/jobs.seed.sql +++ b/jobs/knative-job-service/__fixtures__/jobs.seed.sql @@ -22,27 +22,31 @@ INSERT INTO metaschema_public.database (id, name) VALUES ('0b22e268-16d6-582b-950a-24e108688849', 'jobs-test') ON CONFLICT (id) DO NOTHING; -INSERT INTO services_public.sites (id, database_id, title, logo, dbname) +-- Site surface: the scoped `sites` table has no `logo`/`dbname` columns; the +-- logo lives in `config` and the database is implied by `database_id`. +INSERT INTO constructive_routing_public.sites (id, database_id, name, title, config, is_published) VALUES ( '11111111-1111-1111-1111-111111111111', '0b22e268-16d6-582b-950a-24e108688849', + 'jobs-test', 'Jobs Test', - '{"url":"https://example.com/logo.png","mime":"image/png"}'::jsonb, - current_database() + '{"logo":{"url":"https://example.com/logo.png","mime":"image/png"}}'::jsonb, + true ) ON CONFLICT (id) DO NOTHING; -INSERT INTO services_public.domains (id, database_id, site_id, domain, subdomain) +-- Scoped domains are hostname-keyed; surfaces are bound through +-- routes/route_bindings, which this suite does not exercise. +INSERT INTO constructive_routing_public.domains (id, database_id, hostname, is_published) VALUES ( '22222222-2222-2222-2222-222222222222', '0b22e268-16d6-582b-950a-24e108688849', - '11111111-1111-1111-1111-111111111111', 'localhost', - NULL + true ) ON CONFLICT (id) DO NOTHING; -INSERT INTO services_public.site_themes (id, database_id, site_id, theme) +INSERT INTO constructive_routing_public.site_themes (id, database_id, site_id, theme) VALUES ( '33333333-3333-3333-3333-333333333333', '0b22e268-16d6-582b-950a-24e108688849', @@ -51,7 +55,7 @@ VALUES ( ) ON CONFLICT (id) DO NOTHING; -INSERT INTO services_public.site_modules (id, database_id, site_id, name, data) +INSERT INTO constructive_routing_public.site_modules (id, database_id, site_id, name, data) VALUES ( '44444444-4444-4444-4444-444444444444', '0b22e268-16d6-582b-950a-24e108688849', diff --git a/jobs/knative-job-service/__tests__/jobs.e2e.test.ts b/jobs/knative-job-service/__tests__/jobs.e2e.test.ts index ea21357aed..1ddc9b6ed8 100644 --- a/jobs/knative-job-service/__tests__/jobs.e2e.test.ts +++ b/jobs/knative-job-service/__tests__/jobs.e2e.test.ts @@ -1,4 +1,4 @@ -import { dirname, join } from 'path'; +import { join } from 'path'; import { createServer, type Server as HttpServer } from 'http'; import type { AddressInfo } from 'net'; import supertest from 'supertest'; @@ -6,7 +6,7 @@ import { Server as GraphQLServer } from '@constructive-io/graphql-server'; import type { ConstructiveOptions } from '@constructive-io/graphql-types'; import { createJobApp } from '@constructive-io/knative-job-fn'; -import { PgpmInit, PgpmMigrate } from '@pgpmjs/core'; +import { PgpmInit } from '@pgpmjs/core'; import { getConnections, seed, type PgTestClient } from 'pgsql-test'; import type { KnativeJobsSvc as KnativeJobsSvcType } from '../src'; @@ -264,18 +264,11 @@ let SEND_EMAIL_PORT: number; let SEND_VERIFICATION_LINK_PORT: number; let MAILGUN_FAILURE_PORT: number; -const getPgpmModulePath = (pkgName: string): string => - dirname(require.resolve(`${pkgName}/pgpm.plan`)); - -const metaSeedModules = [ - getPgpmModulePath('@pgpm/verify'), - getPgpmModulePath('@pgpm/types'), - getPgpmModulePath('@pgpm/inflection'), - getPgpmModulePath('@pgpm/database-jobs'), - getPgpmModulePath('@pgpm/metaschema-schema'), - getPgpmModulePath('@pgpm/services'), - getPgpmModulePath('@pgpm/metaschema-modules') -]; +// The scoped catalog/routing/apps modules and the metaschema modules are +// declared in the workspace `pgpm.json`; deploying the workspace keeps this +// suite in lockstep with the rest of the monorepo fixtures (requires +// `pnpm fixtures:install`). +const pgpmWorkspace = join(__dirname, '..', '..', '..'); const sql = (f: string) => join(__dirname, '..', '__fixtures__', f); @@ -285,18 +278,6 @@ type SeededConnections = { teardown: () => Promise; }; -type PgConfigLike = PgTestClient['config']; - -const runMetaMigrations = async (config: PgConfigLike) => { - const migrator = new PgpmMigrate(config); - for (const modulePath of metaSeedModules) { - const result = await migrator.deploy({ modulePath, usePlan: true }); - if (result.failed) { - throw new Error(`Failed to deploy ${modulePath}: ${result.failed}`); - } - } -}; - const bootstrapAdminUsers = seed.fn(async ({ admin, config, connect }) => { const roles = connect?.roles; const connections = connect?.connections; @@ -319,16 +300,12 @@ const bootstrapAdminUsers = seed.fn(async ({ admin, config, connect }) => { } }); -const deployMetaModules = seed.fn(async ({ config }) => { - await runMetaMigrations(config); -}); - const createTestDb = async (): Promise => { const { db, pg, teardown } = await getConnections( { db: { extensions: metaDbExtensions } }, [ bootstrapAdminUsers, - deployMetaModules, + seed.pgpm(pgpmWorkspace), seed.sqlfile([sql('jobs.seed.sql')]) ] ); @@ -473,7 +450,7 @@ describe('jobs e2e', () => { 'app_public', 'metaschema_modules_public', 'metaschema_public', - 'services_public' + 'constructive_routing_public' ], anonRole: 'administrator', roleName: 'administrator', @@ -538,7 +515,6 @@ describe('jobs e2e', () => { it('creates and processes a send-email job', async () => { const jobInput = { - dbId: databaseId, identifier: 'send-email', payload: { to: 'user@example.com', @@ -563,7 +539,6 @@ describe('jobs e2e', () => { it('creates and processes a send-verification-link job', async () => { const jobInput = { - dbId: databaseId, identifier: 'send-verification-link', payload: { email_type: 'invite_email', @@ -589,7 +564,6 @@ describe('jobs e2e', () => { it('creates and processes a send-verification-link forgot_password job', async () => { const jobInput = { - dbId: databaseId, identifier: 'send-verification-link', payload: { email_type: 'forgot_password', @@ -615,7 +589,6 @@ describe('jobs e2e', () => { it('creates and processes a send-verification-link email_verification job', async () => { const jobInput = { - dbId: databaseId, identifier: 'send-verification-link', payload: { email_type: 'email_verification', @@ -641,7 +614,6 @@ describe('jobs e2e', () => { it('fails send-verification-link job when required fields are missing', async () => { const jobInput = { - dbId: databaseId, identifier: 'send-verification-link', maxAttempts: 1, payload: { @@ -674,7 +646,6 @@ describe('jobs e2e', () => { it('records failed jobs when a function throws', async () => { const jobInput = { - dbId: databaseId, identifier: 'send-email', maxAttempts: 1, payload: { @@ -706,7 +677,6 @@ describe('jobs e2e', () => { it('retries failed jobs until max attempts is reached', async () => { const jobInput = { - dbId: databaseId, identifier: 'send-email', maxAttempts: 2, payload: { @@ -748,7 +718,6 @@ describe('jobs e2e', () => { process.env.MAILGUN_KEY = 'invalid-mailgun-api-key'; const jobInput = { - dbId: databaseId, identifier: 'mailgun-failure', maxAttempts: 1, payload: { diff --git a/packages/csv-to-pg/__tests__/__snapshots__/export.test.ts.snap b/packages/csv-to-pg/__tests__/__snapshots__/export.test.ts.snap index 11678d166a..3ac86d94c1 100644 --- a/packages/csv-to-pg/__tests__/__snapshots__/export.test.ts.snap +++ b/packages/csv-to-pg/__tests__/__snapshots__/export.test.ts.snap @@ -95,7 +95,7 @@ exports[`test case test case parser 1`] = ` `; exports[`test case text fields convert empty strings to NULL when preserveEmptyStrings is false 1`] = ` -"INSERT INTO services_public.webauthn_settings ( +"INSERT INTO app_public.webauthn_settings ( id, database_id, rp_id, @@ -105,7 +105,7 @@ exports[`test case text fields convert empty strings to NULL when preserveEmptyS `; exports[`test case text fields preserve empty strings by default 1`] = ` -"INSERT INTO services_public.webauthn_settings ( +"INSERT INTO app_public.webauthn_settings ( id, database_id, rp_id, @@ -115,7 +115,7 @@ exports[`test case text fields preserve empty strings by default 1`] = ` `; exports[`test case text fields with null values produce NULL 1`] = ` -"INSERT INTO services_public.webauthn_settings ( +"INSERT INTO app_public.webauthn_settings ( id, database_id, rp_id, diff --git a/packages/csv-to-pg/__tests__/export.test.ts b/packages/csv-to-pg/__tests__/export.test.ts index e9e037f58a..eaa8ad4729 100644 --- a/packages/csv-to-pg/__tests__/export.test.ts +++ b/packages/csv-to-pg/__tests__/export.test.ts @@ -278,7 +278,7 @@ it('empty array fields emit empty array literal', async () => { it('text fields preserve empty strings by default', async () => { const parser = new Parser({ - schema: 'services_public', + schema: 'app_public', singleStmts: true, table: 'webauthn_settings', fields: { @@ -305,7 +305,7 @@ it('text fields preserve empty strings by default', async () => { it('text fields convert empty strings to NULL when preserveEmptyStrings is false', async () => { const parser = new Parser({ - schema: 'services_public', + schema: 'app_public', singleStmts: true, table: 'webauthn_settings', preserveEmptyStrings: false, @@ -333,7 +333,7 @@ it('text fields convert empty strings to NULL when preserveEmptyStrings is false it('text fields with null values produce NULL', async () => { const parser = new Parser({ - schema: 'services_public', + schema: 'app_public', singleStmts: true, table: 'webauthn_settings', fields: { From 06ca9fc0fc2d01bcc738d398a47a8bc4d2eb4660 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 25 Jul 2026 09:25:19 +0000 Subject: [PATCH 2/3] test(jobs): skip send-verification-link cases pending functions migration --- .../__tests__/jobs.e2e.test.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/jobs/knative-job-service/__tests__/jobs.e2e.test.ts b/jobs/knative-job-service/__tests__/jobs.e2e.test.ts index 1ddc9b6ed8..ddeaa02b35 100644 --- a/jobs/knative-job-service/__tests__/jobs.e2e.test.ts +++ b/jobs/knative-job-service/__tests__/jobs.e2e.test.ts @@ -537,7 +537,11 @@ describe('jobs e2e', () => { await waitForJobCompletion(graphqlClient, jobId); }); - it('creates and processes a send-verification-link job', async () => { + // TODO(services_public hard break): `functions/send-verification-link` still + // queries the legacy shape `databases { sites { domains { subdomain domain } logo } }`, + // which does not exist under `constructive_routing_public` (hostname-keyed domains, + // logo lives in `sites.config`). Re-enable once the runtime/functions migration lands. + it.skip('creates and processes a send-verification-link job', async () => { const jobInput = { identifier: 'send-verification-link', payload: { @@ -562,7 +566,11 @@ describe('jobs e2e', () => { await waitForJobCompletion(graphqlClient, jobId); }); - it('creates and processes a send-verification-link forgot_password job', async () => { + // TODO(services_public hard break): `functions/send-verification-link` still + // queries the legacy shape `databases { sites { domains { subdomain domain } logo } }`, + // which does not exist under `constructive_routing_public` (hostname-keyed domains, + // logo lives in `sites.config`). Re-enable once the runtime/functions migration lands. + it.skip('creates and processes a send-verification-link forgot_password job', async () => { const jobInput = { identifier: 'send-verification-link', payload: { @@ -587,7 +595,11 @@ describe('jobs e2e', () => { await waitForJobCompletion(graphqlClient, jobId); }); - it('creates and processes a send-verification-link email_verification job', async () => { + // TODO(services_public hard break): `functions/send-verification-link` still + // queries the legacy shape `databases { sites { domains { subdomain domain } logo } }`, + // which does not exist under `constructive_routing_public` (hostname-keyed domains, + // logo lives in `sites.config`). Re-enable once the runtime/functions migration lands. + it.skip('creates and processes a send-verification-link email_verification job', async () => { const jobInput = { identifier: 'send-verification-link', payload: { From d2809c60372df31c47c99e9b10b5ca7ec2bf2e2e Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 25 Jul 2026 09:28:36 +0000 Subject: [PATCH 3/3] revert(jobs): move jobs services_public migration to core PR #1447 --- jobs/DEVELOPMENT_JOBS.md | 5 +- .../__fixtures__/jobs.seed.sql | 20 +++--- .../__tests__/jobs.e2e.test.ts | 67 ++++++++++++------- 3 files changed, 53 insertions(+), 39 deletions(-) diff --git a/jobs/DEVELOPMENT_JOBS.md b/jobs/DEVELOPMENT_JOBS.md index 01b453c6c6..df42835a75 100644 --- a/jobs/DEVELOPMENT_JOBS.md +++ b/jobs/DEVELOPMENT_JOBS.md @@ -349,9 +349,8 @@ To switch back to dry-run, set `SEND_VERIFICATION_LINK_DRY_RUN=true` (or delete - need to run some manual migrations like: ```sql BEGIN; - UPDATE constructive_routing_public.domains SET hostname = 'localhost'; - UPDATE constructive_routing_public.hostname_bindings SET hostname = 'localhost'; - UPDATE constructive_routing_public.apis SET dbname = 'constructive'; + UPDATE services_public.domains SET domain = 'localhost'; + UPDATE services_public.apis SET dbname = 'constructive'; COMMIT; ------ Some more diff --git a/jobs/knative-job-service/__fixtures__/jobs.seed.sql b/jobs/knative-job-service/__fixtures__/jobs.seed.sql index f35a80aa90..49705e8e89 100644 --- a/jobs/knative-job-service/__fixtures__/jobs.seed.sql +++ b/jobs/knative-job-service/__fixtures__/jobs.seed.sql @@ -22,31 +22,27 @@ INSERT INTO metaschema_public.database (id, name) VALUES ('0b22e268-16d6-582b-950a-24e108688849', 'jobs-test') ON CONFLICT (id) DO NOTHING; --- Site surface: the scoped `sites` table has no `logo`/`dbname` columns; the --- logo lives in `config` and the database is implied by `database_id`. -INSERT INTO constructive_routing_public.sites (id, database_id, name, title, config, is_published) +INSERT INTO services_public.sites (id, database_id, title, logo, dbname) VALUES ( '11111111-1111-1111-1111-111111111111', '0b22e268-16d6-582b-950a-24e108688849', - 'jobs-test', 'Jobs Test', - '{"logo":{"url":"https://example.com/logo.png","mime":"image/png"}}'::jsonb, - true + '{"url":"https://example.com/logo.png","mime":"image/png"}'::jsonb, + current_database() ) ON CONFLICT (id) DO NOTHING; --- Scoped domains are hostname-keyed; surfaces are bound through --- routes/route_bindings, which this suite does not exercise. -INSERT INTO constructive_routing_public.domains (id, database_id, hostname, is_published) +INSERT INTO services_public.domains (id, database_id, site_id, domain, subdomain) VALUES ( '22222222-2222-2222-2222-222222222222', '0b22e268-16d6-582b-950a-24e108688849', + '11111111-1111-1111-1111-111111111111', 'localhost', - true + NULL ) ON CONFLICT (id) DO NOTHING; -INSERT INTO constructive_routing_public.site_themes (id, database_id, site_id, theme) +INSERT INTO services_public.site_themes (id, database_id, site_id, theme) VALUES ( '33333333-3333-3333-3333-333333333333', '0b22e268-16d6-582b-950a-24e108688849', @@ -55,7 +51,7 @@ VALUES ( ) ON CONFLICT (id) DO NOTHING; -INSERT INTO constructive_routing_public.site_modules (id, database_id, site_id, name, data) +INSERT INTO services_public.site_modules (id, database_id, site_id, name, data) VALUES ( '44444444-4444-4444-4444-444444444444', '0b22e268-16d6-582b-950a-24e108688849', diff --git a/jobs/knative-job-service/__tests__/jobs.e2e.test.ts b/jobs/knative-job-service/__tests__/jobs.e2e.test.ts index ddeaa02b35..ea21357aed 100644 --- a/jobs/knative-job-service/__tests__/jobs.e2e.test.ts +++ b/jobs/knative-job-service/__tests__/jobs.e2e.test.ts @@ -1,4 +1,4 @@ -import { join } from 'path'; +import { dirname, join } from 'path'; import { createServer, type Server as HttpServer } from 'http'; import type { AddressInfo } from 'net'; import supertest from 'supertest'; @@ -6,7 +6,7 @@ import { Server as GraphQLServer } from '@constructive-io/graphql-server'; import type { ConstructiveOptions } from '@constructive-io/graphql-types'; import { createJobApp } from '@constructive-io/knative-job-fn'; -import { PgpmInit } from '@pgpmjs/core'; +import { PgpmInit, PgpmMigrate } from '@pgpmjs/core'; import { getConnections, seed, type PgTestClient } from 'pgsql-test'; import type { KnativeJobsSvc as KnativeJobsSvcType } from '../src'; @@ -264,11 +264,18 @@ let SEND_EMAIL_PORT: number; let SEND_VERIFICATION_LINK_PORT: number; let MAILGUN_FAILURE_PORT: number; -// The scoped catalog/routing/apps modules and the metaschema modules are -// declared in the workspace `pgpm.json`; deploying the workspace keeps this -// suite in lockstep with the rest of the monorepo fixtures (requires -// `pnpm fixtures:install`). -const pgpmWorkspace = join(__dirname, '..', '..', '..'); +const getPgpmModulePath = (pkgName: string): string => + dirname(require.resolve(`${pkgName}/pgpm.plan`)); + +const metaSeedModules = [ + getPgpmModulePath('@pgpm/verify'), + getPgpmModulePath('@pgpm/types'), + getPgpmModulePath('@pgpm/inflection'), + getPgpmModulePath('@pgpm/database-jobs'), + getPgpmModulePath('@pgpm/metaschema-schema'), + getPgpmModulePath('@pgpm/services'), + getPgpmModulePath('@pgpm/metaschema-modules') +]; const sql = (f: string) => join(__dirname, '..', '__fixtures__', f); @@ -278,6 +285,18 @@ type SeededConnections = { teardown: () => Promise; }; +type PgConfigLike = PgTestClient['config']; + +const runMetaMigrations = async (config: PgConfigLike) => { + const migrator = new PgpmMigrate(config); + for (const modulePath of metaSeedModules) { + const result = await migrator.deploy({ modulePath, usePlan: true }); + if (result.failed) { + throw new Error(`Failed to deploy ${modulePath}: ${result.failed}`); + } + } +}; + const bootstrapAdminUsers = seed.fn(async ({ admin, config, connect }) => { const roles = connect?.roles; const connections = connect?.connections; @@ -300,12 +319,16 @@ const bootstrapAdminUsers = seed.fn(async ({ admin, config, connect }) => { } }); +const deployMetaModules = seed.fn(async ({ config }) => { + await runMetaMigrations(config); +}); + const createTestDb = async (): Promise => { const { db, pg, teardown } = await getConnections( { db: { extensions: metaDbExtensions } }, [ bootstrapAdminUsers, - seed.pgpm(pgpmWorkspace), + deployMetaModules, seed.sqlfile([sql('jobs.seed.sql')]) ] ); @@ -450,7 +473,7 @@ describe('jobs e2e', () => { 'app_public', 'metaschema_modules_public', 'metaschema_public', - 'constructive_routing_public' + 'services_public' ], anonRole: 'administrator', roleName: 'administrator', @@ -515,6 +538,7 @@ describe('jobs e2e', () => { it('creates and processes a send-email job', async () => { const jobInput = { + dbId: databaseId, identifier: 'send-email', payload: { to: 'user@example.com', @@ -537,12 +561,9 @@ describe('jobs e2e', () => { await waitForJobCompletion(graphqlClient, jobId); }); - // TODO(services_public hard break): `functions/send-verification-link` still - // queries the legacy shape `databases { sites { domains { subdomain domain } logo } }`, - // which does not exist under `constructive_routing_public` (hostname-keyed domains, - // logo lives in `sites.config`). Re-enable once the runtime/functions migration lands. - it.skip('creates and processes a send-verification-link job', async () => { + it('creates and processes a send-verification-link job', async () => { const jobInput = { + dbId: databaseId, identifier: 'send-verification-link', payload: { email_type: 'invite_email', @@ -566,12 +587,9 @@ describe('jobs e2e', () => { await waitForJobCompletion(graphqlClient, jobId); }); - // TODO(services_public hard break): `functions/send-verification-link` still - // queries the legacy shape `databases { sites { domains { subdomain domain } logo } }`, - // which does not exist under `constructive_routing_public` (hostname-keyed domains, - // logo lives in `sites.config`). Re-enable once the runtime/functions migration lands. - it.skip('creates and processes a send-verification-link forgot_password job', async () => { + it('creates and processes a send-verification-link forgot_password job', async () => { const jobInput = { + dbId: databaseId, identifier: 'send-verification-link', payload: { email_type: 'forgot_password', @@ -595,12 +613,9 @@ describe('jobs e2e', () => { await waitForJobCompletion(graphqlClient, jobId); }); - // TODO(services_public hard break): `functions/send-verification-link` still - // queries the legacy shape `databases { sites { domains { subdomain domain } logo } }`, - // which does not exist under `constructive_routing_public` (hostname-keyed domains, - // logo lives in `sites.config`). Re-enable once the runtime/functions migration lands. - it.skip('creates and processes a send-verification-link email_verification job', async () => { + it('creates and processes a send-verification-link email_verification job', async () => { const jobInput = { + dbId: databaseId, identifier: 'send-verification-link', payload: { email_type: 'email_verification', @@ -626,6 +641,7 @@ describe('jobs e2e', () => { it('fails send-verification-link job when required fields are missing', async () => { const jobInput = { + dbId: databaseId, identifier: 'send-verification-link', maxAttempts: 1, payload: { @@ -658,6 +674,7 @@ describe('jobs e2e', () => { it('records failed jobs when a function throws', async () => { const jobInput = { + dbId: databaseId, identifier: 'send-email', maxAttempts: 1, payload: { @@ -689,6 +706,7 @@ describe('jobs e2e', () => { it('retries failed jobs until max attempts is reached', async () => { const jobInput = { + dbId: databaseId, identifier: 'send-email', maxAttempts: 2, payload: { @@ -730,6 +748,7 @@ describe('jobs e2e', () => { process.env.MAILGUN_KEY = 'invalid-mailgun-api-key'; const jobInput = { + dbId: databaseId, identifier: 'mailgun-failure', maxAttempts: 1, payload: {