diff --git a/graphile/graphile-bucket-provisioner-plugin/src/plugin.ts b/graphile/graphile-bucket-provisioner-plugin/src/plugin.ts index 16714cf632..d66eea264f 100644 --- a/graphile/graphile-bucket-provisioner-plugin/src/plugin.ts +++ b/graphile/graphile-bucket-provisioner-plugin/src/plugin.ts @@ -218,7 +218,21 @@ async function resolveEntityContext( * `packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/job_trigger.sql`. * A callable enqueue function beside the trigger would be the durable fix for * this deliberate mirroring, but is out of scope here. + * + * The three scope shapes differ only in the payload entries and the identity + * arguments, so those are the only parts a caller supplies — the identifier, + * queue, retry, and priority policy exist exactly once. */ +function addJobStatement(payloadEntries: string, identityArgs: string): string { + return `SELECT (app_jobs.add_job( + identifier => 'storage:provision_bucket', + payload => json_build_object(${payloadEntries}), + queue_name => 'bucket:' || $1::text, + max_attempts => 25, + priority => 0${identityArgs} + )).id AS id`; +} + async function enqueueReconciliationJob( pgClient: any, storageModule: StorageModuleRow, @@ -231,37 +245,27 @@ async function enqueueReconciliationJob( let values: unknown[]; if (entityField === null) { - text = `SELECT (app_jobs.add_job( - identifier => 'storage:provision_bucket', - payload => json_build_object( - 'id', $1::uuid, - 'scope', $2::text - ), - queue_name => 'bucket:' || $1::text, - max_attempts => 25, - priority => 0 - )).id AS id`; + text = addJobStatement( + `'id', $1::uuid, + 'scope', $2::text`, + '', + ); values = [bucket.id, scope]; } else if (entityField === 'database_id') { const databaseId = bucket.scope_key; if (!databaseId) { throw new Error(`STORAGE_BUCKET_SCOPE_KEY_MISSING: bucket ${bucket.id} has no database_id`); } - text = `SELECT (app_jobs.add_job( - identifier => 'storage:provision_bucket', - payload => json_build_object( - 'database_id', $2::uuid, + text = addJobStatement( + `'database_id', $2::uuid, 'id', $1::uuid, - 'scope', $3::text - ), + 'scope', $3::text`, + `, db_id => $2, - queue_name => 'bucket:' || $1::text, - max_attempts => 25, - priority => 0, entity_id => $2, organization_id => NULL, - entity_type => $3 - )).id AS id`; + entity_type => $3`, + ); values = [bucket.id, databaseId, scope]; } else if (entityField === 'owner_id') { const ownerId = bucket.scope_key; @@ -272,20 +276,15 @@ async function enqueueReconciliationJob( const orgFunction = context.get_org_fn_schema && context.get_org_fn ? `${QuoteUtils.quoteQualifiedIdentifier(context.get_org_fn_schema, context.get_org_fn)}($3::text, $2::uuid)` : 'NULL'; - text = `SELECT (app_jobs.add_job( - identifier => 'storage:provision_bucket', - payload => json_build_object( - 'id', $1::uuid, + text = addJobStatement( + `'id', $1::uuid, 'owner_id', $2::uuid, - 'scope', $3::text - ), - queue_name => 'bucket:' || $1::text, - max_attempts => 25, - priority => 0, + 'scope', $3::text`, + `, entity_id => $2, organization_id => ${orgFunction}, - entity_type => $3 - )).id AS id`; + entity_type => $3`, + ); values = [bucket.id, ownerId, scope]; } else { throw new Error( diff --git a/graphile/graphile-presigned-url-plugin/src/download-url-field.ts b/graphile/graphile-presigned-url-plugin/src/download-url-field.ts index ab258aa35b..ff6abb9979 100644 --- a/graphile/graphile-presigned-url-plugin/src/download-url-field.ts +++ b/graphile/graphile-presigned-url-plugin/src/download-url-field.ts @@ -26,10 +26,11 @@ import { context as grafastContext, lambda, object } from 'grafast'; import type { GraphileConfig } from 'graphile-config'; import { DOWNLOAD_URL_FIELD } from 'graphile-storage-registry'; +import { resolveS3, resolveS3ForDatabase } from './physical-bucket'; import { withRequestPgClient } from './request-pg-client'; import { generatePresignedGetUrl } from './s3-signer'; import { loadAllStorageModules, resolveStorageConfigFromCodec, storedPhysicalName } from './storage-module-cache'; -import type { PresignedUrlPluginOptions, S3Config, StorageModuleConfig } from './types'; +import type { PresignedUrlPluginOptions } from './types'; const log = new Logger('graphile-presigned-url:download-url'); @@ -42,45 +43,6 @@ const log = new Logger('graphile-presigned-url:download-url'); * the storage module's files table, which we discover at schema-build time * via the `@storageFiles` smart tag. */ -/** - * Resolve the S3 config from the options. If the option is a lazy getter - * function, call it (and cache the result). - */ -function resolveS3(options: PresignedUrlPluginOptions): S3Config { - if (typeof options.s3 === 'function') { - const resolved = options.s3(); - options.s3 = resolved; - return resolved; - } - return options.s3; -} - -/** - * Build a per-database S3Config for a *known* physical bucket. `physicalName` - * is required — the stored coordinate on the bucket row is the only source; - * no name is ever recomputed here. Same logic as plugin.ts resolveS3ForDatabase. - */ -function resolveS3ForDatabase( - options: PresignedUrlPluginOptions, - storageConfig: StorageModuleConfig, - physicalName: string, -): S3Config { - const globalS3 = resolveS3(options); - const publicUrlPrefix = storageConfig.publicUrlPrefix != null - ? storageConfig.publicUrlPrefix - : globalS3.publicUrlPrefix; - - if (physicalName === globalS3.bucket && publicUrlPrefix === globalS3.publicUrlPrefix) { - return globalS3; - } - - return { - ...globalS3, - bucket: physicalName, - ...(publicUrlPrefix != null ? { publicUrlPrefix } : {}), - }; -} - export function createDownloadUrlPlugin( options: PresignedUrlPluginOptions, ): GraphileConfig.Plugin { diff --git a/pgpm/cli/__tests__/__snapshots__/extensions.test.ts.snap b/pgpm/cli/__tests__/__snapshots__/extensions.test.ts.snap index cbbccd6453..70cefff49f 100644 --- a/pgpm/cli/__tests__/__snapshots__/extensions.test.ts.snap +++ b/pgpm/cli/__tests__/__snapshots__/extensions.test.ts.snap @@ -2,6 +2,8 @@ exports[`cmds:extension runs \`extension\` command after workspace and module setup: extension-update - files 1`] = ` [ + "safegres.config.js", + "safegres-perf-baseline.json", "pgpm.plan", "package.json", "my-module.control", diff --git a/pgpm/cli/__tests__/__snapshots__/init.install.test.ts.snap b/pgpm/cli/__tests__/__snapshots__/init.install.test.ts.snap index b197f55d21..87981f55d3 100644 --- a/pgpm/cli/__tests__/__snapshots__/init.install.test.ts.snap +++ b/pgpm/cli/__tests__/__snapshots__/init.install.test.ts.snap @@ -13,6 +13,7 @@ exports[`cmds:install - with initialized workspace and module installs a module "devDependencies": { "makage": "", "pgsql-test": "", + "safegres": "", }, "homepage": "https://github.com/tester/my-module", "keywords": [], @@ -27,6 +28,8 @@ exports[`cmds:install - with initialized workspace and module installs a module "url": "https://github.com/tester/my-module", }, "scripts": { + "audit:db": "safegres audit", + "audit:db:baseline": "safegres audit --write-perf-baseline safegres-perf-baseline.json", "lint": "eslint . --fix", "test": "jest", "test:watch": "jest --watchAll", @@ -152,6 +155,7 @@ exports[`cmds:install - with initialized workspace and module installs two modul "devDependencies": { "makage": "", "pgsql-test": "", + "safegres": "", }, "homepage": "https://github.com/tester/my-module", "keywords": [], @@ -166,6 +170,8 @@ exports[`cmds:install - with initialized workspace and module installs two modul "url": "https://github.com/tester/my-module", }, "scripts": { + "audit:db": "safegres audit", + "audit:db:baseline": "safegres audit --write-perf-baseline safegres-perf-baseline.json", "lint": "eslint . --fix", "test": "jest", "test:watch": "jest --watchAll", diff --git a/pgpm/cli/__tests__/__snapshots__/init.test.ts.snap b/pgpm/cli/__tests__/__snapshots__/init.test.ts.snap index 822319ea4e..879eae6f09 100644 --- a/pgpm/cli/__tests__/__snapshots__/init.test.ts.snap +++ b/pgpm/cli/__tests__/__snapshots__/init.test.ts.snap @@ -45,6 +45,8 @@ exports[`cmds:init initializes module with no extensions by default: module-no-e ".prettierrc.json", ".gitignore", ".eslintrc.json", + "packages/no-ext-module/safegres.config.js", + "packages/no-ext-module/safegres-perf-baseline.json", "packages/no-ext-module/pgpm.plan", "packages/no-ext-module/package.json", "packages/no-ext-module/no-ext-module.control", @@ -53,6 +55,8 @@ exports[`cmds:init initializes module with no extensions by default: module-no-e "packages/no-ext-module/Makefile", "packages/no-ext-module/LICENSE", "packages/no-ext-module/__tests__/basic.test.ts", + "packages/my-module/safegres.config.js", + "packages/my-module/safegres-perf-baseline.json", "packages/my-module/pgpm.plan", "packages/my-module/package.json", "packages/my-module/my-module.control", @@ -128,6 +132,8 @@ exports[`cmds:init initializes module: module-only - files 1`] = ` ".prettierrc.json", ".gitignore", ".eslintrc.json", + "packages/my-module/safegres.config.js", + "packages/my-module/safegres-perf-baseline.json", "packages/my-module/pgpm.plan", "packages/my-module/package.json", "packages/my-module/my-module.control",