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 dc83dc2d..fd9c5550 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 00000000..0c3f2b12 --- /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 00000000..267c26ed --- /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 a2305af5..8b6988f4 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 445a8f0c..f6954da9 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 00000000..24f62336 --- /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 00000000..86a867c8 --- /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 00000000..64f6cdb3 --- /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 00000000..6de8f63f --- /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 00000000..7d829d9f --- /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 00000000..b2c6b8e8 --- /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 18648792..20d9564f 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 00000000..d869b15e --- /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 00000000..d03daa07 --- /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 00000000..35807ed7 --- /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 00000000..b2cf2ec6 --- /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 00000000..73309198 --- /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 00000000..796ea74b --- /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 00000000..6a244e75 --- /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 0c3f2b12..d198a009 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 267c26ed..61681c86 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 f1a8b33a..a56c65dc 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 9732f6f3..5ed92d72 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 f87f081e..284b0bda 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 00000000..eccf41db --- /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 00000000..f3d2c4f8 --- /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 00000000..78be6d82 --- /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 00000000..0a056beb --- /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 00000000..a45c5275 --- /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 24f62336..8772a831 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 86a867c8..90e0de48 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 64f6cdb3..1883de6a 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 6de8f63f..2352c46a 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 7d829d9f..b45762ef 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 cef31b26..a81175ef 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 810665d4..d8613faa 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 c559a790..8a10cc10 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 033a70c5..f74513e7 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 20d9564f..7c807ce0 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 52c9fc71..d869b15e 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 d869b15e..52c9fc71 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 d869b15e..52c9fc71 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 61681c86..944a32a4 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 944a32a4..c1062308 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;