From 0df63f52cbeeb3155e31032f3d31b97f2678ef90 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Fri, 27 Feb 2026 17:07:24 +0800 Subject: [PATCH 1/5] export metaschema fn --- functions/example/handler.ts | 11 +- functions/export-metaschema/handler.json | 10 + functions/export-metaschema/handler.ts | 102 ++++++ functions/send-email-link/handler.ts | 7 +- functions/simple-email/handler.ts | 7 +- packages/fn-pgpm-runtime/package.json | 24 ++ packages/fn-pgpm-runtime/src/context.ts | 34 ++ packages/fn-pgpm-runtime/src/index.ts | 3 + packages/fn-pgpm-runtime/src/server.ts | 32 ++ packages/fn-pgpm-runtime/src/types.ts | 24 ++ packages/fn-pgpm-runtime/tsconfig.json | 11 + pnpm-lock.yaml | 332 ++++++++++++++++++- templates/node-pgpm/Dockerfile | 23 ++ templates/node-pgpm/README.md | 3 + templates/node-pgpm/index.ts | 10 + templates/node-pgpm/k8s/knative-service.yaml | 72 ++++ templates/node-pgpm/package.json | 21 ++ templates/node-pgpm/tsconfig.esm.json | 9 + templates/node-pgpm/tsconfig.json | 15 + 19 files changed, 740 insertions(+), 10 deletions(-) create mode 100644 functions/export-metaschema/handler.json create mode 100644 functions/export-metaschema/handler.ts create mode 100644 packages/fn-pgpm-runtime/package.json create mode 100644 packages/fn-pgpm-runtime/src/context.ts create mode 100644 packages/fn-pgpm-runtime/src/index.ts create mode 100644 packages/fn-pgpm-runtime/src/server.ts create mode 100644 packages/fn-pgpm-runtime/src/types.ts create mode 100644 packages/fn-pgpm-runtime/tsconfig.json create mode 100644 templates/node-pgpm/Dockerfile create mode 100644 templates/node-pgpm/README.md create mode 100644 templates/node-pgpm/index.ts create mode 100644 templates/node-pgpm/k8s/knative-service.yaml create mode 100644 templates/node-pgpm/package.json create mode 100644 templates/node-pgpm/tsconfig.esm.json create mode 100644 templates/node-pgpm/tsconfig.json diff --git a/functions/example/handler.ts b/functions/example/handler.ts index dc83dc2..fd9c555 100644 --- a/functions/example/handler.ts +++ b/functions/example/handler.ts @@ -1,6 +1,13 @@ -import type { FunctionHandler } from '@constructive-io/fn-runtime'; +import type { FunctionContext, FunctionHandler } from '@constructive-io/fn-runtime'; -const handler: FunctionHandler = async (params: any) => { +type ExampleParams = { + throw?: boolean; +}; + +const handler: FunctionHandler = async ( + params: ExampleParams, + _context: FunctionContext +) => { if (params.throw) { throw new Error('THROWN_ERROR'); } diff --git a/functions/export-metaschema/handler.json b/functions/export-metaschema/handler.json new file mode 100644 index 0000000..0c3f2b1 --- /dev/null +++ b/functions/export-metaschema/handler.json @@ -0,0 +1,10 @@ +{ + "name": "export-metaschema", + "version": "1.0.0", + "type": "node-pgpm", + "description": "Exports database metaschema migrations via pgpm export", + "dependencies": { + "@pgpmjs/core": "^6.2.0", + "pg-cache": "^3.1.0" + } +} diff --git a/functions/export-metaschema/handler.ts b/functions/export-metaschema/handler.ts new file mode 100644 index 0000000..267c26e --- /dev/null +++ b/functions/export-metaschema/handler.ts @@ -0,0 +1,102 @@ +import type { PgpmFunctionContext, PgpmFunctionHandler } from '@constructive-io/fn-pgpm-runtime'; +import { exportMigrations } from '@pgpmjs/core'; +import { getPgPool } from 'pg-cache'; +import { resolve } from 'path'; + +type ExportMetaschemaParams = { + dbname?: string; + databaseName?: string; + author?: string; + extensionName?: string; + metaExtensionName?: string; + schema_names?: string[]; + outdir?: string; + skipSchemaRenaming?: boolean; +}; + +const handler: PgpmFunctionHandler = async ( + params: ExportMetaschemaParams, + context: PgpmFunctionContext +) => { + const { project, options, log, env } = context; + + // Resolve database name: params > PGDATABASE env > default + const dbname = params.dbname || env.PGDATABASE || 'constructive'; + + log.info('[export-metaschema] Connecting to database', { dbname }); + + const pgPool = getPgPool({ database: dbname }); + + // Discover database_id and name from metaschema + const dbsResult = await pgPool.query( + 'SELECT id, name FROM metaschema_public.database' + ); + + if (!dbsResult.rows.length) { + throw new Error(`No databases found in metaschema_public.database on ${dbname}`); + } + + const targetRow = params.databaseName + ? dbsResult.rows.find((r: any) => r.name === params.databaseName) + : dbsResult.rows[0]; + + if (!targetRow) { + throw new Error(`Database '${params.databaseName}' not found in metaschema_public.database`); + } + + const databaseName = targetRow.name; + const database_ids = [targetRow.id]; + + // Discover schemas if not provided + let schema_names = params.schema_names; + if (!schema_names?.length) { + const schemasResult = await pgPool.query( + 'SELECT schema_name FROM metaschema_public.schema WHERE database_id = $1', + [database_ids[0]] + ); + schema_names = schemasResult.rows.map((r: any) => r.schema_name); + } + + if (!schema_names?.length) { + throw new Error(`No schemas found for database '${databaseName}'`); + } + + const author = params.author || 'Constructive '; + const extensionName = params.extensionName || databaseName; + const metaExtensionName = params.metaExtensionName || `${databaseName}-service`; + + log.info('[export-metaschema] Starting export', { + dbname, + databaseName, + database_ids, + extensionName, + schema_names + }); + + project.ensureWorkspace(); + project.resetCwd(project.workspacePath); + + const outdir = params.outdir ?? resolve(project.workspacePath, 'packages/'); + + await exportMigrations({ + project, + options, + dbInfo: { + dbname, + databaseName, + database_ids + }, + author, + outdir, + schema_names, + extensionName, + metaExtensionName, + skipSchemaRenaming: params.skipSchemaRenaming + }); + + log.info('[export-metaschema] Export complete'); + + return { complete: true }; +}; + +export default handler; diff --git a/functions/send-email-link/handler.ts b/functions/send-email-link/handler.ts index a2305af..8b6988f 100644 --- a/functions/send-email-link/handler.ts +++ b/functions/send-email-link/handler.ts @@ -1,4 +1,4 @@ -import type { FunctionHandler } from '@constructive-io/fn-runtime'; +import type { FunctionContext, FunctionHandler } from '@constructive-io/fn-runtime'; import type { GraphQLClient } from 'graphql-request'; import gql from 'graphql-tag'; import { generate } from '@launchql/mjml'; @@ -269,7 +269,10 @@ const sendEmailLink = async ( }; }; -const handler: FunctionHandler = async (params, context) => { +const handler: FunctionHandler = async ( + params: SendEmailParams, + context: FunctionContext +) => { const { client, meta, job, log, env } = context; const databaseId = job.databaseId; diff --git a/functions/simple-email/handler.ts b/functions/simple-email/handler.ts index 445a8f0..f6954da 100644 --- a/functions/simple-email/handler.ts +++ b/functions/simple-email/handler.ts @@ -1,4 +1,4 @@ -import type { FunctionHandler } from '@constructive-io/fn-runtime'; +import type { FunctionContext, FunctionHandler } from '@constructive-io/fn-runtime'; import { send as sendSmtp } from 'simple-smtp-server'; import { send as sendPostmaster } from '@constructive-io/postmaster'; import { parseEnvBoolean } from '@pgpmjs/env'; @@ -31,7 +31,10 @@ const isDryRun = parseEnvBoolean(process.env.SIMPLE_EMAIL_DRY_RUN) ?? false; const useSmtp = parseEnvBoolean(process.env.EMAIL_SEND_USE_SMTP) ?? false; const logger = createLogger('simple-email'); -const handler: FunctionHandler = async (params) => { +const handler: FunctionHandler = async ( + params: SimpleEmailPayload, + _context: FunctionContext +) => { const to = getRequiredField(params, 'to'); const subject = getRequiredField(params, 'subject'); diff --git a/packages/fn-pgpm-runtime/package.json b/packages/fn-pgpm-runtime/package.json new file mode 100644 index 0000000..24f6233 --- /dev/null +++ b/packages/fn-pgpm-runtime/package.json @@ -0,0 +1,24 @@ +{ + "name": "@constructive-io/fn-pgpm-runtime", + "version": "1.0.0", + "description": "Runtime for pgpm-based Constructive functions — wraps handler in Express app with PgpmPackage, env options, and job callback support", + "author": "Constructive", + "private": true, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc -p tsconfig.json", + "clean": "rimraf dist" + }, + "dependencies": { + "@constructive-io/knative-job-fn": "workspace:^", + "@pgpmjs/core": "^6.2.0", + "@pgpmjs/env": "^2.11.0", + "@pgpmjs/logger": "^2.1.0", + "@pgpmjs/types": "^2.17.0" + }, + "devDependencies": { + "@types/node": "^22.10.4", + "typescript": "^5.1.6" + } +} diff --git a/packages/fn-pgpm-runtime/src/context.ts b/packages/fn-pgpm-runtime/src/context.ts new file mode 100644 index 0000000..86a867c --- /dev/null +++ b/packages/fn-pgpm-runtime/src/context.ts @@ -0,0 +1,34 @@ +import { PgpmPackage } from '@pgpmjs/core'; +import { getEnvOptions } from '@pgpmjs/env'; +import { createLogger } from '@pgpmjs/logger'; +import type { PgpmFunctionContext, PgpmServerOptions } from './types'; + +type RequestHeaders = { + databaseId?: string; + workerId?: string; + jobId?: string; +}; + +export const buildPgpmContext = ( + headers: RequestHeaders, + options: PgpmServerOptions = {} +): PgpmFunctionContext => { + const env = process.env as Record; + const log = createLogger(options.name || 'fn-pgpm'); + + const cwd = options.cwd || env.PGPM_CWD || process.cwd(); + const project = new PgpmPackage(cwd); + const pgpmOptions = getEnvOptions(); + + return { + job: { + jobId: headers.jobId, + workerId: headers.workerId, + databaseId: headers.databaseId + }, + project, + options: pgpmOptions, + log, + env + }; +}; diff --git a/packages/fn-pgpm-runtime/src/index.ts b/packages/fn-pgpm-runtime/src/index.ts new file mode 100644 index 0000000..64f6cdb --- /dev/null +++ b/packages/fn-pgpm-runtime/src/index.ts @@ -0,0 +1,3 @@ +export { createPgpmFunctionServer } from './server'; +export { buildPgpmContext } from './context'; +export type { PgpmFunctionHandler, PgpmFunctionContext, PgpmServerOptions } from './types'; diff --git a/packages/fn-pgpm-runtime/src/server.ts b/packages/fn-pgpm-runtime/src/server.ts new file mode 100644 index 0000000..6de8f63 --- /dev/null +++ b/packages/fn-pgpm-runtime/src/server.ts @@ -0,0 +1,32 @@ +import { createJobApp } from '@constructive-io/knative-job-fn'; +import { buildPgpmContext } from './context'; +import type { PgpmFunctionHandler, PgpmServerOptions } from './types'; + +export const createPgpmFunctionServer = ( + handler: PgpmFunctionHandler, + options: PgpmServerOptions = {} +) => { + const app = createJobApp(); + + app.post('/', async (req: any, res: any, next: any) => { + try { + const context = buildPgpmContext( + { + databaseId: req.get('X-Database-Id') || req.get('x-database-id') || process.env.DEFAULT_DATABASE_ID, + workerId: req.get('X-Worker-Id') || req.get('x-worker-id'), + jobId: req.get('X-Job-Id') || req.get('x-job-id') + }, + options + ); + + const params = req.body || {}; + const result = await handler(params, context); + + res.status(200).json(result); + } catch (err) { + next(err); + } + }); + + return app; +}; diff --git a/packages/fn-pgpm-runtime/src/types.ts b/packages/fn-pgpm-runtime/src/types.ts new file mode 100644 index 0000000..7d829d9 --- /dev/null +++ b/packages/fn-pgpm-runtime/src/types.ts @@ -0,0 +1,24 @@ +import type { PgpmPackage } from '@pgpmjs/core'; +import type { PgpmOptions } from '@pgpmjs/types'; + +export type PgpmFunctionHandler

= ( + params: P, + context: PgpmFunctionContext +) => Promise | R; + +export type PgpmFunctionContext = { + job: { + jobId?: string; + workerId?: string; + databaseId?: string; + }; + project: PgpmPackage; + options: PgpmOptions; + log: { info: (...args: any[]) => void; error: (...args: any[]) => void; warn: (...args: any[]) => void }; + env: Record; +}; + +export type PgpmServerOptions = { + name?: string; + cwd?: string; +}; diff --git a/packages/fn-pgpm-runtime/tsconfig.json b/packages/fn-pgpm-runtime/tsconfig.json new file mode 100644 index 0000000..b2c6b8e --- /dev/null +++ b/packages/fn-pgpm-runtime/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationMap": true + }, + "include": ["src"], + "exclude": ["dist", "node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1864879..20d9564 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,28 @@ importers: specifier: ^5.1.6 version: 5.9.3 + generated/export-metaschema: + dependencies: + '@constructive-io/fn-pgpm-runtime': + specifier: workspace:^ + version: link:../../packages/fn-pgpm-runtime + '@pgpmjs/core': + specifier: ^6.2.0 + version: 6.3.0 + pg-cache: + specifier: ^3.1.0 + version: 3.1.0 + devDependencies: + '@types/node': + specifier: ^22.10.4 + version: 22.19.3 + makage: + specifier: ^0.1.10 + version: 0.1.12 + typescript: + specifier: ^5.1.6 + version: 5.9.3 + generated/send-email-link: dependencies: '@constructive-io/fn-runtime': @@ -283,6 +305,31 @@ importers: specifier: ^5.1.6 version: 5.9.3 + packages/fn-pgpm-runtime: + dependencies: + '@constructive-io/knative-job-fn': + specifier: workspace:^ + version: link:../fn-app + '@pgpmjs/core': + specifier: ^6.2.0 + version: 6.3.0 + '@pgpmjs/env': + specifier: ^2.11.0 + version: 2.13.0 + '@pgpmjs/logger': + specifier: ^2.1.0 + version: 2.2.0 + '@pgpmjs/types': + specifier: ^2.17.0 + version: 2.17.0 + devDependencies: + '@types/node': + specifier: ^22.10.4 + version: 22.19.3 + typescript: + specifier: ^5.1.6 + version: 5.9.3 + packages/fn-runtime: dependencies: '@constructive-io/knative-job-fn': @@ -522,15 +569,36 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@pgpmjs/core@6.3.0': + resolution: {integrity: sha512-Cfg/pOwgL5gWcOlNDaKefyXf97omhyKgIC7K44Wd37WH96Uy75nFD39fYdznkCdTQB0nieCfAHpEFuQpLC9JnQ==} + '@pgpmjs/env@2.11.0': resolution: {integrity: sha512-2UHzFEsYY41d9BEDLAO2SoqUXLZaaI3c4pb3ZXdRQWYss28nQ8f3XPYe5V78sefi8PEM4Lj2G/6ujZgO4lwZQg==} + '@pgpmjs/env@2.13.0': + resolution: {integrity: sha512-b7RDvkfWlhiqsQm8YZ9lfDUqvVjlZ7GBF7jhj+194OuAmNMELp1IdiKbIGmgu125BTr4ucNd0HMGmhNE3Sjmsw==} + '@pgpmjs/logger@2.1.0': resolution: {integrity: sha512-AQHt6BMnb+0iv8MXmb9kuQfe7/PDBqkkiIaGtzV6WFH4i0oNB43rQBwX4oKc6cSGl6bRXH03o4KFutS3duWCaA==} + '@pgpmjs/logger@2.2.0': + resolution: {integrity: sha512-dNfgUiMWzbYYDub0Kg6ERZjRZY+QT8JK5GBNrP8msWjSYoX8GLW5rtcecyF7BuPPrEACM37mol9yYBaSuFZJsQ==} + + '@pgpmjs/server-utils@3.2.0': + resolution: {integrity: sha512-xDQW89aI5KCldrRIsCF+IXxHiw2AvtImnQjBDNNHr+M9Dp73T22CkbTg3nERpvhRKkNu3VKPegkGaBLDCFqtPw==} + '@pgpmjs/types@2.16.0': resolution: {integrity: sha512-be/RIFg2TYB2X9LAVZ4mFkhu3ZZMpzBCBR9umvQUDEfMcb7aUYDFdEw+mc7CHBgifXNliUswXmllZVsrurh6TQ==} + '@pgpmjs/types@2.17.0': + resolution: {integrity: sha512-qMIi67ZNWkzV/oOWf9BvR3aat2hNLCqsCP4YkMOcPYi42WsIsch9mev3K+jRPkTPKrUAVkkEAnTbnCx3vW8y7w==} + + '@pgsql/types@17.6.2': + resolution: {integrity: sha512-1UtbELdbqNdyOShhrVfSz3a1gDi0s9XXiQemx+6QqtsrXe62a6zOGU+vjb2GRfG5jeEokI1zBBcfD42enRv0Rw==} + + '@pgsql/utils@17.8.12': + resolution: {integrity: sha512-J9WZUgHAZdzG5klUSKNoTFv4gH1l/bkKX1vEboz/EjvEVcoIVTE3hI4lFkhfDkVGscDzTRSM6lZuD/AfQhlAOA==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -732,6 +800,12 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + appstash@0.5.0: + resolution: {integrity: sha512-f9CkbNq1UK2aRn7ErcZI4C1ojInalknp+GsjHnlGSM35sKDBYf6lDc3Z6hViH751hOI0tSrNcFunkaYvxWYgKQ==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -768,6 +842,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base-64@1.0.0: resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} @@ -799,6 +877,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.3: + resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -931,6 +1013,10 @@ packages: core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cron-parser@2.18.0: resolution: {integrity: sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==} engines: {node: '>=0.8'} @@ -959,6 +1045,15 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + csv-parser@2.3.5: + resolution: {integrity: sha512-LCHolC4AlNwL+5EuD5LH2VVNKpD8QixZW2zzK1XmrVYUaslFY4c5BooERHOCIubG9iv/DAyFjs4x0HvWNZuyWg==} + engines: {node: '>= 8.16.0'} + hasBin: true + + csv-to-pg@3.8.0: + resolution: {integrity: sha512-suSA2GSd3TFrKCvuIQ2fE8PsQRTfxglFJP68V2vqNmM/LiBG2UUs0sxmx0+MPk8ooYX/BTX5fM1g2jQSp0j86g==} + hasBin: true + dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} @@ -1185,6 +1280,11 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -1246,6 +1346,9 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} + find-and-require-package-json@0.9.1: + resolution: {integrity: sha512-jFpCL0XgjipSk109viUtfp+NyR/oW6a4Xus4tV3UYkmCbsjisEeZD1x5QnD1NDDK/hXas1WFs4yO13L4TPXWlQ==} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -1308,6 +1411,9 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + genomic@5.3.4: + resolution: {integrity: sha512-SmevgRHHaq7rt85k113zEWchUGZRbfk4loppkCK/O73l6PG3IVEbX2qwmeZZaOpS4WudTGkKhH/emZPx9M4pcg==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1345,6 +1451,10 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -1482,6 +1592,9 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inquirerer@4.5.1: + resolution: {integrity: sha512-/Cis0BNeqdgcXJt3loHKt7PbfawPG7fLTQHr29IfpOHCRaLACmf5737PAHakVU1rBflCNNMo4lpdso6t4FHpjg==} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -1541,6 +1654,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -1585,10 +1702,16 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + komoji@0.8.1: + resolution: {integrity: sha512-7wYXVGaHc+MNTyOoOVmgXA08bRXWm5TDoRdQuLCBFnQsR7TGf+q1bth1E8caIHJit0sbYCTeBAdk3QHxnpYzYQ==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libpg-query@17.7.3: + resolution: {integrity: sha512-lHKBvoWRsXt/9bJxpAeFxkLu0CA6tELusqy3o1z6/DwGXSETxhKJDaNlNdrNV8msvXDLBhpg/4RE/fKKs5rYFA==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -1675,6 +1798,10 @@ packages: resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} engines: {node: 20 || >=22} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1686,10 +1813,17 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mjml-accordion@4.7.1: resolution: {integrity: sha512-oYwC/CLOUWJ6pRt2saDHj/HytGOHO5B5lKNqUAhKPye5HFNZykKEV5ChmZ2NfGsGU+9BhQ7H5DaCafp4fDmPAg==} @@ -1812,6 +1946,9 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + nested-obj@0.1.5: + resolution: {integrity: sha512-04Y7qDMlI8RbYTn0cJAKaw/mLrO9UmLj3xbrjTZKDfOn9f3b/RXEQFIIpveJlwn8KfPwdVFWLZUaL5gNuQ7G0w==} + no-case@2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} @@ -1909,6 +2046,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-package-name@1.0.0: + resolution: {integrity: sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==} + parse5-htmlparser2-tree-adapter@7.1.0: resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} @@ -1945,6 +2085,10 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -1954,6 +2098,9 @@ packages: pg-cache@2.1.0: resolution: {integrity: sha512-r3cMPc62l2EHZwbCPS20X0gJPp/wjz66wknN38eiTYzQE7CShXHGAKbS96xDvWxVAcDGEDhiRJrx5eV1Qu+sUA==} + pg-cache@3.1.0: + resolution: {integrity: sha512-mOzEIVlWgqtZ+rcRFki0OVlURyRqTAzL8q5KH+2/Zu0F6DzmP+ejGLU4lKy4MYW0mjgSUJSIg60bPmGpLG34rQ==} + pg-cloudflare@1.3.0: resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} @@ -1963,6 +2110,9 @@ packages: pg-env@1.4.0: resolution: {integrity: sha512-Xl56AT5Gs/38ubNXSekW02n9USfA+UkIrsl/T0jhES/oKLQccsWPYm+tPeXHc0asdwnFhWxoqbDr2K1vvMv5mA==} + pg-env@1.5.0: + resolution: {integrity: sha512-VHtDiIj5ha8+m0WowxOPuKfPqm4srt+/VOFhFdyqXwSpsXu0TKFmkWrmzsypveUXtsASVlCFa7MDWSgezCyExQ==} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -1991,6 +2141,12 @@ packages: pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + pgsql-deparser@17.17.2: + resolution: {integrity: sha512-FCjqKY3Sdmce3VUd3CxCXF0kqaZ0s4a6yIMT5UJ9vETh0cF54A8Tpqjn0qBKaPUD8xqTKeLdS+SfiwjAC64wrA==} + + pgsql-parser@17.9.11: + resolution: {integrity: sha512-Bqp9uLvJK0Qht9PXzI6eC/Fn+lFRL+2eMvXss4D4qt7lxPLIHS8FMKYOHUQNTI3m6ylExSOdNXhx/DL5UGm3xg==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2212,6 +2368,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sshpk@1.18.0: resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} engines: {node: '>=0.10.0'} @@ -2267,6 +2426,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + through2@3.0.2: + resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2597,7 +2759,7 @@ snapshots: '@constructive-io/job-utils@1.1.0': dependencies: - '@pgpmjs/env': 2.11.0 + '@pgpmjs/env': 2.13.0 '@pgpmjs/logger': 2.1.0 '@pgpmjs/types': 2.16.0 pg-cache: 2.1.0 @@ -2749,19 +2911,71 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@pgpmjs/core@6.3.0': + dependencies: + '@pgpmjs/env': 2.13.0 + '@pgpmjs/logger': 2.2.0 + '@pgpmjs/server-utils': 3.2.0 + '@pgpmjs/types': 2.17.0 + csv-to-pg: 3.8.0 + genomic: 5.3.4 + glob: 13.0.6 + komoji: 0.8.1 + minimatch: 10.2.4 + parse-package-name: 1.0.0 + pg: 8.17.1 + pg-cache: 3.1.0 + pg-env: 1.5.0 + pgsql-deparser: 17.17.2 + pgsql-parser: 17.9.11 + yanse: 0.2.1 + transitivePeerDependencies: + - pg-native + - supports-color + '@pgpmjs/env@2.11.0': dependencies: '@pgpmjs/types': 2.16.0 deepmerge: 4.3.1 + '@pgpmjs/env@2.13.0': + dependencies: + '@pgpmjs/types': 2.17.0 + deepmerge: 4.3.1 + '@pgpmjs/logger@2.1.0': dependencies: yanse: 0.2.1 + '@pgpmjs/logger@2.2.0': + dependencies: + yanse: 0.2.1 + + '@pgpmjs/server-utils@3.2.0': + dependencies: + '@pgpmjs/logger': 2.2.0 + '@pgpmjs/types': 2.17.0 + cors: 2.8.6 + express: 5.2.1 + lru-cache: 11.2.5 + transitivePeerDependencies: + - supports-color + '@pgpmjs/types@2.16.0': dependencies: pg-env: 1.4.0 + '@pgpmjs/types@2.17.0': + dependencies: + pg-env: 1.5.0 + + '@pgsql/types@17.6.2': {} + + '@pgsql/utils@17.8.12': + dependencies: + '@pgsql/types': 17.6.2 + nested-obj: 0.1.5 + '@pkgjs/parseargs@0.11.0': optional: true @@ -3012,6 +3226,12 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + appstash@0.5.0: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} asn1@0.2.6: @@ -3057,6 +3277,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base-64@1.0.0: {} baseline-browser-mapping@2.9.11: {} @@ -3107,6 +3329,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.3: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3253,6 +3479,11 @@ snapshots: core-util-is@1.0.2: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cron-parser@2.18.0: dependencies: is-nan: 1.3.2 @@ -3291,6 +3522,20 @@ snapshots: css-what@6.2.2: {} + csv-parser@2.3.5: + dependencies: + minimist: 1.2.8 + through2: 3.0.2 + + csv-to-pg@3.8.0: + dependencies: + '@pgsql/types': 17.6.2 + '@pgsql/utils': 17.8.12 + csv-parser: 2.3.5 + inquirerer: 4.5.1 + js-yaml: 3.14.2 + pgsql-deparser: 17.17.2 + dashdash@1.14.1: dependencies: assert-plus: 1.0.0 @@ -3531,6 +3776,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 + esprima@4.0.1: {} + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -3611,6 +3858,8 @@ snapshots: transitivePeerDependencies: - supports-color + find-and-require-package-json@0.9.1: {} + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -3671,6 +3920,11 @@ snapshots: function-bind@1.1.2: {} + genomic@5.3.4: + dependencies: + appstash: 0.5.0 + inquirerer: 4.5.1 + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -3723,6 +3977,12 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.1 + glob@13.0.6: + dependencies: + minimatch: 10.2.4 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3870,6 +4130,13 @@ snapshots: ini@1.3.8: {} + inquirerer@4.5.1: + dependencies: + deepmerge: 4.3.1 + find-and-require-package-json: 0.9.1 + minimist: 1.2.8 + yanse: 0.2.1 + ipaddr.js@1.9.1: {} is-binary-path@2.1.0: @@ -3921,6 +4188,11 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -3962,11 +4234,17 @@ snapshots: dependencies: json-buffer: 3.0.1 + komoji@0.8.1: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + libpg-query@17.7.3: + dependencies: + '@pgsql/types': 17.6.2 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -4036,6 +4314,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.1 + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.3 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -4048,8 +4330,12 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minimist@1.2.8: {} + minipass@7.1.2: {} + minipass@7.1.3: {} + mjml-accordion@4.7.1: dependencies: '@babel/runtime': 7.28.4 @@ -4357,6 +4643,8 @@ snapshots: negotiator@1.0.0: {} + nested-obj@0.1.5: {} + no-case@2.3.2: dependencies: lower-case: 1.1.4 @@ -4446,6 +4734,8 @@ snapshots: dependencies: callsites: 3.1.0 + parse-package-name@1.0.0: {} + parse5-htmlparser2-tree-adapter@7.1.0: dependencies: domhandler: 5.0.3 @@ -4481,20 +4771,35 @@ snapshots: lru-cache: 11.2.5 minipass: 7.1.2 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.5 + minipass: 7.1.3 + path-to-regexp@8.3.0: {} performance-now@2.1.0: {} pg-cache@2.1.0: dependencies: - '@pgpmjs/logger': 2.1.0 - '@pgpmjs/types': 2.16.0 + '@pgpmjs/logger': 2.2.0 + '@pgpmjs/types': 2.17.0 lru-cache: 11.2.5 pg: 8.17.1 pg-env: 1.4.0 transitivePeerDependencies: - pg-native + pg-cache@3.1.0: + dependencies: + '@pgpmjs/logger': 2.2.0 + '@pgpmjs/types': 2.17.0 + lru-cache: 11.2.5 + pg: 8.17.1 + pg-env: 1.5.0 + transitivePeerDependencies: + - pg-native + pg-cloudflare@1.3.0: optional: true @@ -4502,6 +4807,8 @@ snapshots: pg-env@1.4.0: {} + pg-env@1.5.0: {} + pg-int8@1.0.1: {} pg-pool@3.11.0(pg@8.17.1): @@ -4532,6 +4839,16 @@ snapshots: dependencies: split2: 4.2.0 + pgsql-deparser@17.17.2: + dependencies: + '@pgsql/types': 17.6.2 + + pgsql-parser@17.9.11: + dependencies: + '@pgsql/types': 17.6.2 + libpg-query: 17.7.3 + pgsql-deparser: 17.17.2 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -4763,7 +5080,7 @@ snapshots: simple-smtp-server@0.3.0: dependencies: - '@pgpmjs/env': 2.11.0 + '@pgpmjs/env': 2.13.0 '@pgpmjs/types': 2.16.0 nodemailer: 6.10.1 @@ -4775,6 +5092,8 @@ snapshots: split2@4.2.0: {} + sprintf-js@1.0.3: {} + sshpk@1.18.0: dependencies: asn1: 0.2.6 @@ -4859,6 +5178,11 @@ snapshots: dependencies: has-flag: 4.0.0 + through2@3.0.2: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) diff --git a/templates/node-pgpm/Dockerfile b/templates/node-pgpm/Dockerfile new file mode 100644 index 0000000..d869b15 --- /dev/null +++ b/templates/node-pgpm/Dockerfile @@ -0,0 +1,23 @@ +FROM node:22-alpine AS build +RUN apk add --no-cache git +RUN npm install -g pnpm@10.12.2 +WORKDIR /app +COPY . . +RUN node --experimental-strip-types scripts/generate.ts \ + && pnpm install --frozen-lockfile \ + && pnpm --filter @constructive-io/{{name}}-fn... build + +FROM node:22-alpine AS deploy +RUN apk add --no-cache git +RUN npm install -g pnpm@10.12.2 +COPY --from=build /app /app +WORKDIR /app +RUN pnpm --filter @constructive-io/{{name}}-fn deploy --legacy /deploy --prod + +FROM node:22-alpine +RUN apk add --no-cache git +WORKDIR /app +COPY --from=deploy /deploy . +ENV NODE_ENV=production +EXPOSE 8080 +CMD ["node", "dist/index.js"] diff --git a/templates/node-pgpm/README.md b/templates/node-pgpm/README.md new file mode 100644 index 0000000..d03daa0 --- /dev/null +++ b/templates/node-pgpm/README.md @@ -0,0 +1,3 @@ +# {{name}}-fn + +{{description}} diff --git a/templates/node-pgpm/index.ts b/templates/node-pgpm/index.ts new file mode 100644 index 0000000..35807ed --- /dev/null +++ b/templates/node-pgpm/index.ts @@ -0,0 +1,10 @@ +import { createPgpmFunctionServer } from '@constructive-io/fn-pgpm-runtime'; +import handler from './handler'; + +const app = createPgpmFunctionServer(handler, { name: '{{name}}' }); + +export default app; + +if (require.main === module) { + app.listen(Number(process.env.PORT || 8080)); +} diff --git a/templates/node-pgpm/k8s/knative-service.yaml b/templates/node-pgpm/k8s/knative-service.yaml new file mode 100644 index 0000000..b2cf2ec --- /dev/null +++ b/templates/node-pgpm/k8s/knative-service.yaml @@ -0,0 +1,72 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: {{name}} + labels: + app.kubernetes.io/name: {{name}} + app.kubernetes.io/component: function + app.kubernetes.io/part-of: constructive-jobs + networking.knative.dev/visibility: cluster-local +spec: + template: + metadata: + labels: + app.kubernetes.io/name: {{name}} + app.kubernetes.io/component: function + app.kubernetes.io/part-of: constructive-jobs + annotations: + autoscaling.knative.dev/minScale: "1" + autoscaling.knative.dev/maxScale: "10" + autoscaling.knative.dev/target: "50" + spec: + containerConcurrency: 10 + timeoutSeconds: 300 + containers: + - name: function + image: ghcr.io/constructive-io/{{name}}-fn:latest + imagePullPolicy: Always + ports: + - containerPort: 8080 + protocol: TCP + env: + - name: NODE_ENV + value: "production" + - name: PORT + value: "8080" + - name: PGHOST + valueFrom: + secretKeyRef: + name: constructive-db-credentials + key: host + - name: PGPORT + valueFrom: + secretKeyRef: + name: constructive-db-credentials + key: port + optional: true + - name: PGUSER + valueFrom: + secretKeyRef: + name: constructive-db-credentials + key: user + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: constructive-db-credentials + key: password + - name: PGDATABASE + valueFrom: + secretKeyRef: + name: constructive-db-credentials + key: database + optional: true + resources: + requests: + memory: "256Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1000m" + traffic: + - percent: 100 + latestRevision: true diff --git a/templates/node-pgpm/package.json b/templates/node-pgpm/package.json new file mode 100644 index 0000000..7330919 --- /dev/null +++ b/templates/node-pgpm/package.json @@ -0,0 +1,21 @@ +{ + "name": "@constructive-io/{{name}}-fn", + "version": "{{version}}", + "description": "{{description}}", + "private": true, + "main": "dist/index.js", + "scripts": { + "build": "makage build", + "build:dev": "makage build --dev", + "clean": "makage clean", + "lint": "eslint . --fix" + }, + "dependencies": { + "@constructive-io/fn-pgpm-runtime": "workspace:^" + }, + "devDependencies": { + "@types/node": "^22.10.4", + "makage": "^0.1.10", + "typescript": "^5.1.6" + } +} diff --git a/templates/node-pgpm/tsconfig.esm.json b/templates/node-pgpm/tsconfig.esm.json new file mode 100644 index 0000000..796ea74 --- /dev/null +++ b/templates/node-pgpm/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist/esm", + "module": "es2022", + "rootDir": ".", + "declaration": false + } +} diff --git a/templates/node-pgpm/tsconfig.json b/templates/node-pgpm/tsconfig.json new file mode 100644 index 0000000..6a244e7 --- /dev/null +++ b/templates/node-pgpm/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "." + }, + "include": [ + "index.ts", + "handler.ts" + ], + "exclude": [ + "dist", + "node_modules" + ] +} From f9c517afe9e9fb04347b71879b4316406c4bc1d0 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Fri, 27 Feb 2026 20:00:58 +0800 Subject: [PATCH 2/5] refactored and abstract reusable --- functions/export-metaschema/handler.json | 1 + functions/export-metaschema/handler.ts | 3 +- job/service/src/index.ts | 4 ++ job/service/src/types.ts | 2 +- package.json | 2 +- packages/fn-core/package.json | 20 ++++++++++ packages/fn-core/src/index.ts | 12 ++++++ packages/fn-core/src/server.ts | 36 +++++++++++++++++ packages/fn-core/src/types.ts | 37 ++++++++++++++++++ packages/fn-core/tsconfig.json | 10 +++++ packages/fn-pgpm-runtime/package.json | 2 +- packages/fn-pgpm-runtime/src/context.ts | 50 ++++++++++-------------- packages/fn-pgpm-runtime/src/index.ts | 1 + packages/fn-pgpm-runtime/src/server.ts | 37 +++++++----------- packages/fn-pgpm-runtime/src/types.ts | 20 +++------- packages/fn-runtime/package.json | 2 +- packages/fn-runtime/src/context.ts | 11 ++---- packages/fn-runtime/src/server.ts | 29 +++----------- packages/fn-runtime/src/types.ts | 21 +++------- pnpm-lock.yaml | 22 +++++++++-- templates/node-graphql/Dockerfile | 3 ++ 21 files changed, 203 insertions(+), 122 deletions(-) create mode 100644 packages/fn-core/package.json create mode 100644 packages/fn-core/src/index.ts create mode 100644 packages/fn-core/src/server.ts create mode 100644 packages/fn-core/src/types.ts create mode 100644 packages/fn-core/tsconfig.json diff --git a/functions/export-metaschema/handler.json b/functions/export-metaschema/handler.json index 0c3f2b1..d198a00 100644 --- a/functions/export-metaschema/handler.json +++ b/functions/export-metaschema/handler.json @@ -4,6 +4,7 @@ "type": "node-pgpm", "description": "Exports database metaschema migrations via pgpm export", "dependencies": { + "@constructive-io/fn-core": "workspace:^", "@pgpmjs/core": "^6.2.0", "pg-cache": "^3.1.0" } diff --git a/functions/export-metaschema/handler.ts b/functions/export-metaschema/handler.ts index 267c26e..61681c8 100644 --- a/functions/export-metaschema/handler.ts +++ b/functions/export-metaschema/handler.ts @@ -1,4 +1,5 @@ import type { PgpmFunctionContext, PgpmFunctionHandler } from '@constructive-io/fn-pgpm-runtime'; +import { DEFAULT_DATABASE_NAME } from '@constructive-io/fn-core'; import { exportMigrations } from '@pgpmjs/core'; import { getPgPool } from 'pg-cache'; import { resolve } from 'path'; @@ -21,7 +22,7 @@ const handler: PgpmFunctionHandler = async ( const { project, options, log, env } = context; // Resolve database name: params > PGDATABASE env > default - const dbname = params.dbname || env.PGDATABASE || 'constructive'; + const dbname = params.dbname || env.PGDATABASE || DEFAULT_DATABASE_NAME; log.info('[export-metaschema] Connecting to database', { dbname }); diff --git a/job/service/src/index.ts b/job/service/src/index.ts index f1a8b33..a56c65d 100644 --- a/job/service/src/index.ts +++ b/job/service/src/index.ts @@ -39,6 +39,10 @@ const functionRegistry: Record = { 'send-email-link': { moduleName: '@constructive-io/send-email-link-fn', defaultPort: 8082 + }, + 'export-metaschema': { + moduleName: '@constructive-io/export-metaschema-fn', + defaultPort: 8083 } }; diff --git a/job/service/src/types.ts b/job/service/src/types.ts index 9732f6f..5ed92d7 100644 --- a/job/service/src/types.ts +++ b/job/service/src/types.ts @@ -1,4 +1,4 @@ -export type FunctionName = 'simple-email' | 'send-email-link'; +export type FunctionName = 'simple-email' | 'send-email-link' | 'export-metaschema'; export type FunctionServiceConfig = { name: FunctionName; diff --git a/package.json b/package.json index f87f081..284b0bd 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "access": "restricted" }, "engines": { - "node": ">=18.17.0" + "node": ">=22.0.0" }, "packageManager": "pnpm@10.12.2", "scripts": { diff --git a/packages/fn-core/package.json b/packages/fn-core/package.json new file mode 100644 index 0000000..eccf41d --- /dev/null +++ b/packages/fn-core/package.json @@ -0,0 +1,20 @@ +{ + "name": "@constructive-io/fn-core", + "version": "1.0.0", + "description": "Shared core for Constructive function runtimes — base types, server factory, and request handling", + "author": "Constructive", + "private": true, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc -p tsconfig.json", + "clean": "rimraf dist" + }, + "dependencies": { + "@constructive-io/knative-job-fn": "workspace:^" + }, + "devDependencies": { + "@types/node": "^22.10.4", + "typescript": "^5.1.6" + } +} diff --git a/packages/fn-core/src/index.ts b/packages/fn-core/src/index.ts new file mode 100644 index 0000000..f3d2c4f --- /dev/null +++ b/packages/fn-core/src/index.ts @@ -0,0 +1,12 @@ +export { createServer, extractHeaders } from './server'; +export type { ContextFactory } from './server'; +export type { + JobMeta, + LogFn, + Env, + BaseContext, + BaseServerOptions, + RequestHeaders, + BaseFunctionHandler +} from './types'; +export { DEFAULT_DATABASE_NAME } from './types'; diff --git a/packages/fn-core/src/server.ts b/packages/fn-core/src/server.ts new file mode 100644 index 0000000..78be6d8 --- /dev/null +++ b/packages/fn-core/src/server.ts @@ -0,0 +1,36 @@ +import { createJobApp } from '@constructive-io/knative-job-fn'; +import type { BaseContext, BaseFunctionHandler, RequestHeaders } from './types'; + +export type ContextFactory = ( + headers: RequestHeaders +) => C | Promise; + +export const extractHeaders = (req: any): RequestHeaders => ({ + databaseId: + req.get('X-Database-Id') || + req.get('x-database-id') || + process.env.DEFAULT_DATABASE_ID, + workerId: req.get('X-Worker-Id') || req.get('x-worker-id'), + jobId: req.get('X-Job-Id') || req.get('x-job-id') +}); + +export const createServer = ( + handler: BaseFunctionHandler, + contextFactory: ContextFactory +) => { + const app = createJobApp(); + + app.post('/', async (req: any, res: any, next: any) => { + try { + const headers = extractHeaders(req); + const context = await contextFactory(headers); + const params = req.body || {}; + const result = await handler(params, context); + res.status(200).json(result); + } catch (err) { + next(err); + } + }); + + return app; +}; diff --git a/packages/fn-core/src/types.ts b/packages/fn-core/src/types.ts new file mode 100644 index 0000000..0a056be --- /dev/null +++ b/packages/fn-core/src/types.ts @@ -0,0 +1,37 @@ +export type JobMeta = { + jobId?: string; + workerId?: string; + databaseId?: string; +}; + +export type LogFn = { + info: (...args: any[]) => void; + error: (...args: any[]) => void; + warn: (...args: any[]) => void; +}; + +export type Env = Record; + +export type BaseContext = { + job: JobMeta; + log: LogFn; + env: Env; +}; + +export type BaseServerOptions = { + name?: string; +}; + +export type RequestHeaders = { + databaseId?: string; + workerId?: string; + jobId?: string; +}; + +export type BaseFunctionHandler< + P = unknown, + C extends BaseContext = BaseContext, + R = unknown +> = (params: P, context: C) => Promise | R; + +export const DEFAULT_DATABASE_NAME = 'constructive'; diff --git a/packages/fn-core/tsconfig.json b/packages/fn-core/tsconfig.json new file mode 100644 index 0000000..a45c527 --- /dev/null +++ b/packages/fn-core/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/fn-pgpm-runtime/package.json b/packages/fn-pgpm-runtime/package.json index 24f6233..8772a83 100644 --- a/packages/fn-pgpm-runtime/package.json +++ b/packages/fn-pgpm-runtime/package.json @@ -11,7 +11,7 @@ "clean": "rimraf dist" }, "dependencies": { - "@constructive-io/knative-job-fn": "workspace:^", + "@constructive-io/fn-core": "workspace:^", "@pgpmjs/core": "^6.2.0", "@pgpmjs/env": "^2.11.0", "@pgpmjs/logger": "^2.1.0", diff --git a/packages/fn-pgpm-runtime/src/context.ts b/packages/fn-pgpm-runtime/src/context.ts index 86a867c..90e0de4 100644 --- a/packages/fn-pgpm-runtime/src/context.ts +++ b/packages/fn-pgpm-runtime/src/context.ts @@ -1,34 +1,26 @@ -import { PgpmPackage } from '@pgpmjs/core'; -import { getEnvOptions } from '@pgpmjs/env'; -import { createLogger } from '@pgpmjs/logger'; -import type { PgpmFunctionContext, PgpmServerOptions } from './types'; +import type { Env, LogFn, RequestHeaders } from '@constructive-io/fn-core'; +import type { PgpmPackage } from '@pgpmjs/core'; +import type { PgpmOptions } from '@pgpmjs/types'; +import type { PgpmFunctionContext } from './types'; -type RequestHeaders = { - databaseId?: string; - workerId?: string; - jobId?: string; +export type PgpmServerResources = { + project: PgpmPackage; + options: PgpmOptions; + log: LogFn; + env: Env; }; export const buildPgpmContext = ( headers: RequestHeaders, - options: PgpmServerOptions = {} -): PgpmFunctionContext => { - const env = process.env as Record; - const log = createLogger(options.name || 'fn-pgpm'); - - const cwd = options.cwd || env.PGPM_CWD || process.cwd(); - const project = new PgpmPackage(cwd); - const pgpmOptions = getEnvOptions(); - - return { - job: { - jobId: headers.jobId, - workerId: headers.workerId, - databaseId: headers.databaseId - }, - project, - options: pgpmOptions, - log, - env - }; -}; + resources: PgpmServerResources +): PgpmFunctionContext => ({ + job: { + jobId: headers.jobId, + workerId: headers.workerId, + databaseId: headers.databaseId + }, + project: resources.project, + options: resources.options, + log: resources.log, + env: resources.env +}); diff --git a/packages/fn-pgpm-runtime/src/index.ts b/packages/fn-pgpm-runtime/src/index.ts index 64f6cdb..1883de6 100644 --- a/packages/fn-pgpm-runtime/src/index.ts +++ b/packages/fn-pgpm-runtime/src/index.ts @@ -1,3 +1,4 @@ export { createPgpmFunctionServer } from './server'; export { buildPgpmContext } from './context'; +export type { PgpmServerResources } from './context'; export type { PgpmFunctionHandler, PgpmFunctionContext, PgpmServerOptions } from './types'; diff --git a/packages/fn-pgpm-runtime/src/server.ts b/packages/fn-pgpm-runtime/src/server.ts index 6de8f63..2352c46 100644 --- a/packages/fn-pgpm-runtime/src/server.ts +++ b/packages/fn-pgpm-runtime/src/server.ts @@ -1,4 +1,8 @@ -import { createJobApp } from '@constructive-io/knative-job-fn'; +import { createServer } from '@constructive-io/fn-core'; +import type { Env, RequestHeaders } from '@constructive-io/fn-core'; +import { PgpmPackage } from '@pgpmjs/core'; +import { getEnvOptions } from '@pgpmjs/env'; +import { createLogger } from '@pgpmjs/logger'; import { buildPgpmContext } from './context'; import type { PgpmFunctionHandler, PgpmServerOptions } from './types'; @@ -6,27 +10,16 @@ export const createPgpmFunctionServer = ( handler: PgpmFunctionHandler, options: PgpmServerOptions = {} ) => { - const app = createJobApp(); + // Initialize shared resources once at server startup (not per-request) + const env = process.env as Env; + const cwd = options.cwd || env.PGPM_CWD || process.cwd(); + const project = new PgpmPackage(cwd); + const pgpmOptions = getEnvOptions(); + const log = createLogger(options.name || 'fn-pgpm'); - app.post('/', async (req: any, res: any, next: any) => { - try { - const context = buildPgpmContext( - { - databaseId: req.get('X-Database-Id') || req.get('x-database-id') || process.env.DEFAULT_DATABASE_ID, - workerId: req.get('X-Worker-Id') || req.get('x-worker-id'), - jobId: req.get('X-Job-Id') || req.get('x-job-id') - }, - options - ); + const resources = { project, options: pgpmOptions, log, env }; - const params = req.body || {}; - const result = await handler(params, context); - - res.status(200).json(result); - } catch (err) { - next(err); - } - }); - - return app; + return createServer(handler, (headers: RequestHeaders) => + buildPgpmContext(headers, resources) + ); }; diff --git a/packages/fn-pgpm-runtime/src/types.ts b/packages/fn-pgpm-runtime/src/types.ts index 7d829d9..b45762e 100644 --- a/packages/fn-pgpm-runtime/src/types.ts +++ b/packages/fn-pgpm-runtime/src/types.ts @@ -1,24 +1,14 @@ import type { PgpmPackage } from '@pgpmjs/core'; import type { PgpmOptions } from '@pgpmjs/types'; +import type { BaseContext, BaseFunctionHandler, BaseServerOptions } from '@constructive-io/fn-core'; -export type PgpmFunctionHandler

= ( - params: P, - context: PgpmFunctionContext -) => Promise | R; - -export type PgpmFunctionContext = { - job: { - jobId?: string; - workerId?: string; - databaseId?: string; - }; +export type PgpmFunctionContext = BaseContext & { project: PgpmPackage; options: PgpmOptions; - log: { info: (...args: any[]) => void; error: (...args: any[]) => void; warn: (...args: any[]) => void }; - env: Record; }; -export type PgpmServerOptions = { - name?: string; +export type PgpmFunctionHandler

= BaseFunctionHandler; + +export type PgpmServerOptions = BaseServerOptions & { cwd?: string; }; diff --git a/packages/fn-runtime/package.json b/packages/fn-runtime/package.json index cef31b2..a81175e 100644 --- a/packages/fn-runtime/package.json +++ b/packages/fn-runtime/package.json @@ -11,7 +11,7 @@ "clean": "rimraf dist" }, "dependencies": { - "@constructive-io/knative-job-fn": "workspace:^", + "@constructive-io/fn-core": "workspace:^", "@pgpmjs/logger": "^2.1.0", "graphql-request": "^7.1.2" }, diff --git a/packages/fn-runtime/src/context.ts b/packages/fn-runtime/src/context.ts index 810665d..d8613fa 100644 --- a/packages/fn-runtime/src/context.ts +++ b/packages/fn-runtime/src/context.ts @@ -1,16 +1,11 @@ +import type { RequestHeaders } from '@constructive-io/fn-core'; import { createLogger } from '@pgpmjs/logger'; import { createClients } from './graphql'; -import type { FunctionContext } from './types'; - -type RequestHeaders = { - databaseId?: string; - workerId?: string; - jobId?: string; -}; +import type { FunctionContext, ServerOptions } from './types'; export const buildContext = ( headers: RequestHeaders, - options: { name?: string } = {} + options: ServerOptions = {} ): FunctionContext => { const env = process.env as Record; const log = createLogger(options.name || 'fn-runtime'); diff --git a/packages/fn-runtime/src/server.ts b/packages/fn-runtime/src/server.ts index c559a79..8a10cc1 100644 --- a/packages/fn-runtime/src/server.ts +++ b/packages/fn-runtime/src/server.ts @@ -1,4 +1,5 @@ -import { createJobApp } from '@constructive-io/knative-job-fn'; +import { createServer } from '@constructive-io/fn-core'; +import type { RequestHeaders } from '@constructive-io/fn-core'; import { buildContext } from './context'; import type { FunctionHandler, ServerOptions } from './types'; @@ -6,27 +7,7 @@ export const createFunctionServer = ( handler: FunctionHandler, options: ServerOptions = {} ) => { - const app = createJobApp(); - - app.post('/', async (req: any, res: any, next: any) => { - try { - const context = buildContext( - { - databaseId: req.get('X-Database-Id') || req.get('x-database-id') || process.env.DEFAULT_DATABASE_ID, - workerId: req.get('X-Worker-Id') || req.get('x-worker-id'), - jobId: req.get('X-Job-Id') || req.get('x-job-id') - }, - { name: options.name } - ); - - const params = req.body || {}; - const result = await handler(params, context); - - res.status(200).json(result); - } catch (err) { - next(err); - } - }); - - return app; + return createServer(handler, (headers: RequestHeaders) => + buildContext(headers, options) + ); }; diff --git a/packages/fn-runtime/src/types.ts b/packages/fn-runtime/src/types.ts index 033a70c..f74513e 100644 --- a/packages/fn-runtime/src/types.ts +++ b/packages/fn-runtime/src/types.ts @@ -1,22 +1,11 @@ import type { GraphQLClient } from 'graphql-request'; +import type { BaseContext, BaseFunctionHandler, BaseServerOptions } from '@constructive-io/fn-core'; -export type FunctionHandler

= ( - params: P, - context: FunctionContext -) => Promise | R; - -export type FunctionContext = { - job: { - jobId?: string; - workerId?: string; - databaseId?: string; - }; +export type FunctionContext = BaseContext & { client: GraphQLClient; meta: GraphQLClient; - log: { info: (...args: any[]) => void; error: (...args: any[]) => void; warn: (...args: any[]) => void }; - env: Record; }; -export type ServerOptions = { - name?: string; -}; +export type FunctionHandler

= BaseFunctionHandler; + +export type ServerOptions = BaseServerOptions; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20d9564..7c807ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: generated/export-metaschema: dependencies: + '@constructive-io/fn-core': + specifier: workspace:^ + version: link:../../packages/fn-core '@constructive-io/fn-pgpm-runtime': specifier: workspace:^ version: link:../../packages/fn-pgpm-runtime @@ -305,11 +308,24 @@ importers: specifier: ^5.1.6 version: 5.9.3 - packages/fn-pgpm-runtime: + packages/fn-core: dependencies: '@constructive-io/knative-job-fn': specifier: workspace:^ version: link:../fn-app + devDependencies: + '@types/node': + specifier: ^22.10.4 + version: 22.19.3 + typescript: + specifier: ^5.1.6 + version: 5.9.3 + + packages/fn-pgpm-runtime: + dependencies: + '@constructive-io/fn-core': + specifier: workspace:^ + version: link:../fn-core '@pgpmjs/core': specifier: ^6.2.0 version: 6.3.0 @@ -332,9 +348,9 @@ importers: packages/fn-runtime: dependencies: - '@constructive-io/knative-job-fn': + '@constructive-io/fn-core': specifier: workspace:^ - version: link:../fn-app + version: link:../fn-core '@pgpmjs/logger': specifier: ^2.1.0 version: 2.1.0 diff --git a/templates/node-graphql/Dockerfile b/templates/node-graphql/Dockerfile index 52c9fc7..d869b15 100644 --- a/templates/node-graphql/Dockerfile +++ b/templates/node-graphql/Dockerfile @@ -1,4 +1,5 @@ FROM node:22-alpine AS build +RUN apk add --no-cache git RUN npm install -g pnpm@10.12.2 WORKDIR /app COPY . . @@ -7,12 +8,14 @@ RUN node --experimental-strip-types scripts/generate.ts \ && pnpm --filter @constructive-io/{{name}}-fn... build FROM node:22-alpine AS deploy +RUN apk add --no-cache git RUN npm install -g pnpm@10.12.2 COPY --from=build /app /app WORKDIR /app RUN pnpm --filter @constructive-io/{{name}}-fn deploy --legacy /deploy --prod FROM node:22-alpine +RUN apk add --no-cache git WORKDIR /app COPY --from=deploy /deploy . ENV NODE_ENV=production From fa04d91ace7751a154f74e879a7f3a7df0aafc75 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Fri, 27 Feb 2026 21:05:58 +0800 Subject: [PATCH 3/5] remove git install from Dockerfile --- templates/node-graphql/Dockerfile | 3 --- templates/node-pgpm/Dockerfile | 3 --- 2 files changed, 6 deletions(-) diff --git a/templates/node-graphql/Dockerfile b/templates/node-graphql/Dockerfile index d869b15..52c9fc7 100644 --- a/templates/node-graphql/Dockerfile +++ b/templates/node-graphql/Dockerfile @@ -1,5 +1,4 @@ FROM node:22-alpine AS build -RUN apk add --no-cache git RUN npm install -g pnpm@10.12.2 WORKDIR /app COPY . . @@ -8,14 +7,12 @@ RUN node --experimental-strip-types scripts/generate.ts \ && pnpm --filter @constructive-io/{{name}}-fn... build FROM node:22-alpine AS deploy -RUN apk add --no-cache git RUN npm install -g pnpm@10.12.2 COPY --from=build /app /app WORKDIR /app RUN pnpm --filter @constructive-io/{{name}}-fn deploy --legacy /deploy --prod FROM node:22-alpine -RUN apk add --no-cache git WORKDIR /app COPY --from=deploy /deploy . ENV NODE_ENV=production diff --git a/templates/node-pgpm/Dockerfile b/templates/node-pgpm/Dockerfile index d869b15..52c9fc7 100644 --- a/templates/node-pgpm/Dockerfile +++ b/templates/node-pgpm/Dockerfile @@ -1,5 +1,4 @@ FROM node:22-alpine AS build -RUN apk add --no-cache git RUN npm install -g pnpm@10.12.2 WORKDIR /app COPY . . @@ -8,14 +7,12 @@ RUN node --experimental-strip-types scripts/generate.ts \ && pnpm --filter @constructive-io/{{name}}-fn... build FROM node:22-alpine AS deploy -RUN apk add --no-cache git RUN npm install -g pnpm@10.12.2 COPY --from=build /app /app WORKDIR /app RUN pnpm --filter @constructive-io/{{name}}-fn deploy --legacy /deploy --prod FROM node:22-alpine -RUN apk add --no-cache git WORKDIR /app COPY --from=deploy /deploy . ENV NODE_ENV=production From 8e58c9f2fca9df329ba7a50bbd45d8274cd7c75f Mon Sep 17 00:00:00 2001 From: zetazzz Date: Sat, 28 Feb 2026 18:41:24 +0800 Subject: [PATCH 4/5] fixed export metaschema handler --- functions/export-metaschema/handler.ts | 38 +++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/functions/export-metaschema/handler.ts b/functions/export-metaschema/handler.ts index 61681c8..944a32a 100644 --- a/functions/export-metaschema/handler.ts +++ b/functions/export-metaschema/handler.ts @@ -1,7 +1,7 @@ import type { PgpmFunctionContext, PgpmFunctionHandler } from '@constructive-io/fn-pgpm-runtime'; import { DEFAULT_DATABASE_NAME } from '@constructive-io/fn-core'; import { exportMigrations } from '@pgpmjs/core'; -import { getPgPool } from 'pg-cache'; +import { getPgPool, pgCache } from 'pg-cache'; import { resolve } from 'path'; type ExportMetaschemaParams = { @@ -13,6 +13,8 @@ type ExportMetaschemaParams = { schema_names?: string[]; outdir?: string; skipSchemaRenaming?: boolean; + username?: string; + repoName?: string; }; const handler: PgpmFunctionHandler = async ( @@ -48,6 +50,24 @@ const handler: PgpmFunctionHandler = async ( const databaseName = targetRow.name; const database_ids = [targetRow.id]; + // Check that sql_actions exist for this database before exporting + const actionsResult = await pgPool.query( + 'SELECT count(*)::int AS cnt FROM db_migrate.sql_actions WHERE database_id = $1', + [database_ids[0]] + ); + const actionCount = actionsResult.rows[0]?.cnt ?? 0; + + if (actionCount === 0) { + log.info('[export-metaschema] No sql_actions found, nothing to export', { + databaseName, + database_id: database_ids[0] + }); + return { + complete: false, + reason: `No sql_actions found for database '${databaseName}' (${database_ids[0]}). The database may have been deployed from pre-built packages.` + }; + } + // Discover schemas if not provided let schema_names = params.schema_names; if (!schema_names?.length) { @@ -65,13 +85,17 @@ const handler: PgpmFunctionHandler = async ( const author = params.author || 'Constructive '; const extensionName = params.extensionName || databaseName; const metaExtensionName = params.metaExtensionName || `${databaseName}-service`; + // Default username/repoName to avoid interactive prompts from scaffoldTemplate + const username = params.username || 'constructive-io'; + const repoName = params.repoName || extensionName; log.info('[export-metaschema] Starting export', { dbname, databaseName, database_ids, extensionName, - schema_names + schema_names, + actionCount }); project.ensureWorkspace(); @@ -92,12 +116,18 @@ const handler: PgpmFunctionHandler = async ( schema_names, extensionName, metaExtensionName, + username, + repoName, skipSchemaRenaming: params.skipSchemaRenaming }); - log.info('[export-metaschema] Export complete'); + // exportMigrationsToDisk calls pgPool.end() which kills the cached pool. + // Evict the dead pool from pg-cache so the next request gets a fresh one. + pgCache.delete(dbname); + + log.info('[export-metaschema] Export complete', { outdir }); - return { complete: true }; + return { complete: true, outdir, extensionName, metaExtensionName, actionCount }; }; export default handler; From d97698dc884d9b907651535c0c70621ff0d9edd8 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Wed, 4 Mar 2026 10:54:20 +0800 Subject: [PATCH 5/5] fixed unnecessary sqls usage --- functions/export-metaschema/handler.ts | 40 ++++++-------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/functions/export-metaschema/handler.ts b/functions/export-metaschema/handler.ts index 944a32a..c106230 100644 --- a/functions/export-metaschema/handler.ts +++ b/functions/export-metaschema/handler.ts @@ -6,7 +6,7 @@ import { resolve } from 'path'; type ExportMetaschemaParams = { dbname?: string; - databaseName?: string; + databaseName: string; author?: string; extensionName?: string; metaExtensionName?: string; @@ -30,44 +30,21 @@ const handler: PgpmFunctionHandler = async ( const pgPool = getPgPool({ database: dbname }); - // Discover database_id and name from metaschema + // Discover database_id from metaschema const dbsResult = await pgPool.query( - 'SELECT id, name FROM metaschema_public.database' + 'SELECT id, name FROM metaschema_public.database WHERE name = $1', + [params.databaseName] ); if (!dbsResult.rows.length) { - throw new Error(`No databases found in metaschema_public.database on ${dbname}`); - } - - const targetRow = params.databaseName - ? dbsResult.rows.find((r: any) => r.name === params.databaseName) - : dbsResult.rows[0]; - - if (!targetRow) { throw new Error(`Database '${params.databaseName}' not found in metaschema_public.database`); } + const targetRow = dbsResult.rows[0]; + const databaseName = targetRow.name; const database_ids = [targetRow.id]; - // Check that sql_actions exist for this database before exporting - const actionsResult = await pgPool.query( - 'SELECT count(*)::int AS cnt FROM db_migrate.sql_actions WHERE database_id = $1', - [database_ids[0]] - ); - const actionCount = actionsResult.rows[0]?.cnt ?? 0; - - if (actionCount === 0) { - log.info('[export-metaschema] No sql_actions found, nothing to export', { - databaseName, - database_id: database_ids[0] - }); - return { - complete: false, - reason: `No sql_actions found for database '${databaseName}' (${database_ids[0]}). The database may have been deployed from pre-built packages.` - }; - } - // Discover schemas if not provided let schema_names = params.schema_names; if (!schema_names?.length) { @@ -94,8 +71,7 @@ const handler: PgpmFunctionHandler = async ( databaseName, database_ids, extensionName, - schema_names, - actionCount + schema_names }); project.ensureWorkspace(); @@ -127,7 +103,7 @@ const handler: PgpmFunctionHandler = async ( log.info('[export-metaschema] Export complete', { outdir }); - return { complete: true, outdir, extensionName, metaExtensionName, actionCount }; + return { complete: true, outdir, extensionName, metaExtensionName }; }; export default handler;