Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/examples-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
- uses: pnpm/action-setup@v2
with:
version: 10
Expand Down
61 changes: 40 additions & 21 deletions __fixtures__/seed/scoped/test-data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@
--
-- Scoped routing plane test data: models the "simple-pets" tenant as rows in
-- the published modules
-- - @constructive-db/catalog -> constructive_catalog_public
-- - @constructive-db/routing -> constructive_routing_public
-- - @constructive-db/apps -> constructive_apps_public
-- - @constructive-db/routing-functions -> resolve_route() + binding-sync triggers
-- - @constructive-db/catalog -> catalog_public
-- - @constructive-db/routing -> routing_public (incl. resolve_route())
-- - @constructive-db/apps -> apps_public
--
-- The metaschema block below seeds metaschema_public directly so this file can
-- be composed standalone (routing.api_schemas.schema_id FKs
-- metaschema_public.schema).
--
-- `constructive_routing_public.{hostname_bindings,route_bindings}` are the
-- compiled indexes read by resolve_route(). They are maintained by the
-- binding-sync triggers shipped in @constructive-db/routing-functions, so this
-- fixture writes only the source rows (domains/routes) with triggers enabled.
-- `routing_public.{hostname_bindings,route_bindings}` are the
-- compiled indexes read by resolve_route(). The published schema+grants
-- modules ship no binding-sync triggers, so this fixture seeds the compiled
-- binding rows directly alongside the source rows (domains/routes).
--
-- NOTE: does NOT include app-level schemas/tables or row data.
-- Compose with app-schemas/* seeds for that.
Expand Down Expand Up @@ -73,15 +72,15 @@ VALUES (
) ON CONFLICT (id) DO NOTHING;

-- =====================================================
-- CATALOG DATA (constructive_catalog_public)
-- CATALOG DATA (catalog_public)
-- =====================================================
--
-- `apis.config` carries the api surface projection the server folds into
-- ApiStructure via resolve_route().resolved_config (api_id, database_id,
-- is_public, schemas); name/dbname/role_name/anon_role are merged in by the
-- resolver from the columns below.

INSERT INTO constructive_catalog_public.apis
INSERT INTO catalog_public.apis
(id, owner_scope, owner_key, is_visible, database_id, name, dbname, role_name, anon_role, config)
VALUES
(
Expand Down Expand Up @@ -137,7 +136,7 @@ VALUES
ON CONFLICT (id) DO NOTHING;

-- Catalog domains: globally-claimed hostnames (same ids as services domains)
INSERT INTO constructive_catalog_public.domains
INSERT INTO catalog_public.domains
(id, owner_scope, owner_key, is_visible, database_id, hostname, is_wildcard, parent_hostname, managed, verification_status, tls_status)
VALUES
(
Expand All @@ -151,11 +150,11 @@ VALUES
ON CONFLICT (id) DO NOTHING;

-- =====================================================
-- ROUTING DATA (constructive_routing_public)
-- ROUTING DATA (routing_public)
-- =====================================================

-- Routing-side api surfaces (own the api_schemas / settings joins)
INSERT INTO constructive_routing_public.apis
INSERT INTO routing_public.apis
(id, database_id, name, dbname, role_name, anon_role, is_published)
VALUES
('6c9997a4-591b-4cb3-9313-4ef45d6f134e', '80a2eaaf-f77e-4bfe-8506-df929ef1b8d9', 'app', current_database(), 'authenticated', 'anonymous', true),
Expand All @@ -166,7 +165,7 @@ VALUES
ON CONFLICT (id) DO NOTHING;

-- API -> schema linkage
INSERT INTO constructive_routing_public.api_schemas (id, database_id, schema_id, api_id)
INSERT INTO routing_public.api_schemas (id, database_id, schema_id, api_id)
VALUES
-- app API schemas (public + pets_public)
('71181146-890e-4991-9da7-3dddf87d9e01', '80a2eaaf-f77e-4bfe-8506-df929ef1b8d9', '6dbae92a-5450-401b-1ed5-d69e7754940d', '6c9997a4-591b-4cb3-9313-4ef45d6f134e'),
Expand All @@ -178,32 +177,52 @@ VALUES
ON CONFLICT (id) DO NOTHING;

-- Routing-side domains (source rows for the compiled hostname index).
-- Triggers are re-enabled so the binding-sync triggers from
-- @constructive-db/routing-functions compile hostname_bindings/route_bindings.
-- Triggers are re-enabled here: the published modules attach only the
-- catalog-sync propagation triggers, whose upserts into catalog_public are
-- idempotent with the direct catalog seeds above. Binding-sync triggers are
-- NOT published, so the compiled hostname_bindings/route_bindings rows are
-- still seeded directly below.
SET session_replication_role TO DEFAULT;

INSERT INTO constructive_routing_public.domains
INSERT INTO routing_public.domains
(id, database_id, hostname, managed, is_wildcard, parent_hostname, verification_status, tls_status, tls_secret_name, is_published)
VALUES
('41181146-890e-4991-9da7-3dddf87d9e78', '80a2eaaf-f77e-4bfe-8506-df929ef1b8d9', 'app.test.constructive.io', false, false, NULL, 'verified', 'ready', NULL, true),
('51181146-890e-4991-9da7-3dddf87d9e79', '80a2eaaf-f77e-4bfe-8506-df929ef1b8d9', 'private.test.constructive.io', false, false, NULL, 'verified', 'ready', NULL, false)
ON CONFLICT (id) DO NOTHING;

-- Routes: hostname root path -> api surface (source rows)
INSERT INTO constructive_routing_public.routes
INSERT INTO routing_public.routes
(id, database_id, domain_id, target_api_id, path, method, priority, is_active)
VALUES
('91181146-890e-4991-9da7-3dddf87d9e01', '80a2eaaf-f77e-4bfe-8506-df929ef1b8d9', '41181146-890e-4991-9da7-3dddf87d9e78', '6c9997a4-591b-4cb3-9313-4ef45d6f134e', '/', NULL, 0, true),
('91181146-890e-4991-9da7-3dddf87d9e02', '80a2eaaf-f77e-4bfe-8506-df929ef1b8d9', '51181146-890e-4991-9da7-3dddf87d9e79', 'e257c53d-6ba6-40de-b679-61b37188a316', '/', NULL, 0, true)
ON CONFLICT (id) DO NOTHING;

-- Compiled binding indexes read by resolve_route(). The published routing
-- module ships no binding-sync triggers, so seed the compiled rows directly
-- (hostname_bindings.id mirrors the trigger derivation md5(domain_id|hostname)).
INSERT INTO routing_public.hostname_bindings
(id, hostname, domain_id, is_wildcard, parent_hostname, managed, verification_status, tls_status, tls_secret_name, updated_at)
VALUES
((md5(concat('41181146-890e-4991-9da7-3dddf87d9e78', '|', 'app.test.constructive.io')))::uuid, 'app.test.constructive.io', '41181146-890e-4991-9da7-3dddf87d9e78', false, NULL, false, 'verified', 'ready', NULL, now()),
((md5(concat('51181146-890e-4991-9da7-3dddf87d9e79', '|', 'private.test.constructive.io')))::uuid, 'private.test.constructive.io', '51181146-890e-4991-9da7-3dddf87d9e79', false, NULL, false, 'verified', 'ready', NULL, now())
ON CONFLICT (hostname) DO NOTHING;

INSERT INTO routing_public.route_bindings
(id, domain_id, target_api_id, target_site_id, target_function_id, path, method, priority, is_active, updated_at)
VALUES
('91181146-890e-4991-9da7-3dddf87d9e01', '41181146-890e-4991-9da7-3dddf87d9e78', '6c9997a4-591b-4cb3-9313-4ef45d6f134e', NULL, NULL, '/', NULL, 0, true, now()),
('91181146-890e-4991-9da7-3dddf87d9e02', '51181146-890e-4991-9da7-3dddf87d9e79', 'e257c53d-6ba6-40de-b679-61b37188a316', NULL, NULL, '/', NULL, 0, true, now())
ON CONFLICT (id) DO NOTHING;

SET session_replication_role TO replica;

-- =====================================================
-- APPS DATA (constructive_apps_public)
-- APPS DATA (apps_public)
-- =====================================================

INSERT INTO constructive_apps_public.apps
INSERT INTO apps_public.apps
(id, database_id, name, title, description, status, is_published)
VALUES (
'c1181146-890e-4991-9da7-3dddf87d9e01',
Expand All @@ -215,7 +234,7 @@ VALUES (
true
) ON CONFLICT (id) DO NOTHING;

INSERT INTO constructive_apps_public.app_components
INSERT INTO apps_public.app_components
(id, database_id, app_id, component_api_id, component_domain_id, component_type)
VALUES
('d1181146-890e-4991-9da7-3dddf87d9e01', '80a2eaaf-f77e-4bfe-8506-df929ef1b8d9', 'c1181146-890e-4991-9da7-3dddf87d9e01', '6c9997a4-591b-4cb3-9313-4ef45d6f134e', NULL, 'api'),
Expand Down
26 changes: 13 additions & 13 deletions graphql/codegen/src/core/introspect/source/api-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* API Schemas Resolution
*
* Utilities for resolving PostgreSQL schema names from API names
* by querying the constructive_routing_public.api_schemas table.
* by querying the routing_public.api_schemas table.
*/
import { Pool } from 'pg';
import { getPgPool } from 'pg-cache';
Expand All @@ -20,7 +20,7 @@ export interface RoutingSchemaValidation {
* Validate that the required routing schemas exist in the database
*
* Checks for:
* - constructive_routing_public schema with apis and api_schemas tables
* - routing_public schema with apis and api_schemas tables
* - metaschema_public schema with schema table
*
* @param pool - Database connection pool
Expand All @@ -30,31 +30,31 @@ export async function validateRoutingSchemas(
pool: Pool,
): Promise<RoutingSchemaValidation> {
try {
// Check for constructive_routing_public.apis table
// Check for routing_public.apis table
const apisCheck = await pool.query(`
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'constructive_routing_public'
WHERE table_schema = 'routing_public'
AND table_name = 'apis'
`);
if (apisCheck.rows.length === 0) {
return {
valid: false,
error:
'constructive_routing_public.apis table not found. The database must have the routing schema deployed.',
'routing_public.apis table not found. The database must have the routing schema deployed.',
};
}

// Check for constructive_routing_public.api_schemas table
// Check for routing_public.api_schemas table
const apiSchemasCheck = await pool.query(`
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'constructive_routing_public'
WHERE table_schema = 'routing_public'
AND table_name = 'api_schemas'
`);
if (apiSchemasCheck.rows.length === 0) {
return {
valid: false,
error:
'constructive_routing_public.api_schemas table not found. The database must have the routing schema deployed.',
'routing_public.api_schemas table not found. The database must have the routing schema deployed.',
};
}

Expand Down Expand Up @@ -82,9 +82,9 @@ export async function validateRoutingSchemas(
}

/**
* Resolve schema names from API names by querying constructive_routing_public.api_schemas
* Resolve schema names from API names by querying routing_public.api_schemas
*
* Joins constructive_routing_public.apis, constructive_routing_public.api_schemas, and metaschema_public.schema
* Joins routing_public.apis, routing_public.api_schemas, and metaschema_public.schema
* to get the actual PostgreSQL schema names for the given API names.
*
* @param pool - Database connection pool
Expand All @@ -106,8 +106,8 @@ export async function resolveApiSchemas(
const result = await pool.query<{ schema_name: string }>(
`
SELECT DISTINCT ms.schema_name
FROM constructive_routing_public.api_schemas as_tbl
JOIN constructive_routing_public.apis api ON api.id = as_tbl.api_id
FROM routing_public.api_schemas as_tbl
JOIN routing_public.apis api ON api.id = as_tbl.api_id
JOIN metaschema_public.schema ms ON ms.id = as_tbl.schema_id
WHERE api.name = ANY($1)
ORDER BY ms.schema_name
Expand All @@ -118,7 +118,7 @@ export async function resolveApiSchemas(
if (result.rows.length === 0) {
throw new Error(
`No schemas found for API names: ${apiNames.join(', ')}. ` +
'Ensure the APIs exist and have schemas assigned in constructive_routing_public.api_schemas.',
'Ensure the APIs exist and have schemas assigned in routing_public.api_schemas.',
);
}

Expand Down
2 changes: 1 addition & 1 deletion graphql/codegen/src/core/introspect/source/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export interface DatabaseSchemaSourceOptions {

/**
* API names to resolve schemas from
* Queries constructive_routing_public.api_schemas to get schema names
* Queries routing_public.api_schemas to get schema names
* Mutually exclusive with schemas
*/
apiNames?: string[];
Expand Down
4 changes: 2 additions & 2 deletions graphql/codegen/src/core/introspect/source/pgpm-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface PgpmModulePathOptions {

/**
* API names to resolve schemas from
* Queries constructive_routing_public.api_schemas to get schema names
* Queries routing_public.api_schemas to get schema names
* Mutually exclusive with schemas
*/
apiNames?: string[];
Expand Down Expand Up @@ -73,7 +73,7 @@ export interface PgpmWorkspaceOptions {

/**
* API names to resolve schemas from
* Queries constructive_routing_public.api_schemas to get schema names
* Queries routing_public.api_schemas to get schema names
* Mutually exclusive with schemas
*/
apiNames?: string[];
Expand Down
2 changes: 1 addition & 1 deletion graphql/codegen/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export interface DbConfig {

/**
* API names to resolve schemas from
* Queries constructive_routing_public.api_schemas to automatically determine schemas
* Queries routing_public.api_schemas to automatically determine schemas
* Mutually exclusive with `schemas`
* @example ['my_api']
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Proves the Playwright harness can drive the *production*
* `@constructive-io/graphql-server` (not the dev server): every request is
* resolved through the scoped-routing plane
* (`constructive_routing_public.resolve_route()`), so this suite seeds real
* (`routing_public.resolve_route()`), so this suite seeds real
* routing/database records and reaches the api surface via its seeded host.
*
* Contrast with `server.playwright.test.ts`, which uses the single-tenant dev
Expand All @@ -21,9 +21,9 @@ const pgpmWorkspace = path.join(sharedSeedRoot, '..', '..');

const schemas = ['simple-pets-public', 'simple-pets-pets-public'];
const scopedMetaSchemas = [
'constructive_catalog_public',
'constructive_routing_public',
'constructive_apps_public',
'catalog_public',
'routing_public',
'apps_public',
'metaschema_public',
'metaschema_modules_public'
];
Expand All @@ -48,7 +48,6 @@ test.describe('playwright-test scoped server (real graphql-server)', () => {
server: {
useRouting: true,
api: {
routingSchema: 'constructive_routing_public',
isPublic: true,
metaSchemas: scopedMetaSchemas
}
Expand Down
2 changes: 1 addition & 1 deletion graphql/playwright-test/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const createTestServer = async (
*
* Uses the `Server` class from `@constructive-io/graphql-server` directly, so
* every request is resolved through the scoped-routing plane
* (`constructive_routing_public.resolve_route()`). Suites using this must seed
* (`routing_public.resolve_route()`). Suites using this must seed
* real routing/database records so a real database id is resolved.
*/
export const createScopedTestServer = async (
Expand Down
Loading
Loading