diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1035223..6121790a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 9 + version: '8.14.1' # This enables task distribution via Nx Cloud # Run this command as early as possible, before dependencies are installed @@ -31,13 +31,13 @@ jobs: # Cache node_modules - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: '20.18.1' cache: 'pnpm' - name: Setup Deno uses: denoland/setup-deno@v2 with: - deno-version: 1.x + deno-version: '1.45.2' - name: Install sqruff uses: quarylabs/install-sqruff-cli-action@main diff --git a/eslint.config.cjs b/eslint.config.cjs index c138fd04..c237dda9 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -3,8 +3,18 @@ const nx = require('@nx/eslint-plugin'); module.exports = [ { files: ['**/*.json'], - // Override or add rules here - rules: {}, + rules: { + '@nx/dependency-checks': [ + 'error', + { + ignoredFiles: [ + '{projectRoot}/eslint.config.{js,cjs,mjs}', + '{projectRoot}/vite.config.{js,ts,mjs,mts}', + ], + ignoredDependencies: ['tslib'], + }, + ], + }, languageOptions: { parser: require('jsonc-eslint-parser'), }, diff --git a/package.json b/package.json index 9ece312d..c394739f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "jsr": "^0.13.4", "nx": "20.3.0", "prettier": "^2.6.2", - "supabase": "^2.6.8", "tslib": "^2.3.0", "typescript": "~5.6.2", "typescript-eslint": "^8.13.0", diff --git a/pkgs/core/eslint.config.cjs b/pkgs/core/eslint.config.cjs index aa7aea54..87adc5de 100644 --- a/pkgs/core/eslint.config.cjs +++ b/pkgs/core/eslint.config.cjs @@ -1,22 +1,3 @@ const baseConfig = require('../../eslint.config.cjs'); -module.exports = [ - ...baseConfig, - { - files: ['**/*.json'], - rules: { - '@nx/dependency-checks': [ - 'error', - { - ignoredFiles: [ - '{projectRoot}/eslint.config.{js,cjs,mjs}', - '{projectRoot}/vite.config.{js,ts,mjs,mts}', - ], - }, - ], - }, - languageOptions: { - parser: require('jsonc-eslint-parser'), - }, - }, -]; +module.exports = [...baseConfig]; diff --git a/pkgs/core/tsconfig.lib.json b/pkgs/core/tsconfig.lib.json index e42e43c2..1c4e9b53 100644 --- a/pkgs/core/tsconfig.lib.json +++ b/pkgs/core/tsconfig.lib.json @@ -3,7 +3,8 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo" + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "types": ["node"] }, "include": ["src/**/*.ts"], "references": [ diff --git a/pkgs/dsl/eslint.config.cjs b/pkgs/dsl/eslint.config.cjs index 61331ceb..87adc5de 100644 --- a/pkgs/dsl/eslint.config.cjs +++ b/pkgs/dsl/eslint.config.cjs @@ -1,22 +1,3 @@ const baseConfig = require('../../eslint.config.cjs'); -module.exports = [ - ...baseConfig, - { - files: ['**/*.json'], - rules: { - '@nx/dependency-checks': [ - 'error', - { - ignoredFiles: [ - '{projectRoot}/eslint.config.{js,cjs,mjs}', - '{projectRoot}/vite.config.{js,ts,mjs,mts}*', - ], - }, - ], - }, - languageOptions: { - parser: require('jsonc-eslint-parser'), - }, - }, -]; +module.exports = [...baseConfig]; diff --git a/pkgs/dsl/tsconfig.lib.json b/pkgs/dsl/tsconfig.lib.json index ac7836c2..e7a1b3cb 100644 --- a/pkgs/dsl/tsconfig.lib.json +++ b/pkgs/dsl/tsconfig.lib.json @@ -3,7 +3,8 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo" + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "types": ["node"] }, "include": ["src/**/*.ts"], "references": [], diff --git a/pkgs/edge-worker/.gitignore b/pkgs/edge-worker/.gitignore index d8a87a78..2a086312 100644 --- a/pkgs/edge-worker/.gitignore +++ b/pkgs/edge-worker/.gitignore @@ -1,4 +1,2 @@ supabase/migrations/* -supabase/functions/_src/* -supabase/functions/**/deno.json -supabase/functions/**/deno.lock +supabase/functions/_dist/ diff --git a/pkgs/edge-worker/deno.lock b/pkgs/edge-worker/deno.lock index 81a500e5..a1e9eb06 100644 --- a/pkgs/edge-worker/deno.lock +++ b/pkgs/edge-worker/deno.lock @@ -13,7 +13,7 @@ "jsr:@std/io@0.225.0": "jsr:@std/io@0.225.0", "jsr:@std/io@^0.225.0": "jsr:@std/io@0.225.0", "npm:@henrygd/queue@^1.0.7": "npm:@henrygd/queue@1.0.7", - "npm:pino@^9.6.0": "npm:pino@9.6.0", + "npm:@teidesu/deno-types@1.42.4": "npm:@teidesu/deno-types@1.42.4", "npm:postgres@3.4.5": "npm:postgres@3.4.5" }, "jsr": { @@ -58,79 +58,13 @@ "integrity": "sha512-Jmt/iO6yDlz9UYGILkm/Qzi/ckkEiTNZcqDvt3QFLE4OThPeiCj6tKsynHFm/ppl8RumWXAx1dZPBPiRPaaGig==", "dependencies": {} }, - "atomic-sleep@1.0.0": { - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "@teidesu/deno-types@1.42.4": { + "integrity": "sha512-MMDkfWOsfedYWy+aPK4fAYyZfrBsY6+DeC7DGg6eESzh90zxuf1fSvXRsY8y09Hh4mm04tAf1632S2/JLaTXQg==", "dependencies": {} }, - "fast-redact@3.5.0": { - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "dependencies": {} - }, - "on-exit-leak-free@2.1.2": { - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "dependencies": {} - }, - "pino-abstract-transport@2.0.0": { - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "dependencies": { - "split2": "split2@4.2.0" - } - }, - "pino-std-serializers@7.0.0": { - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "dependencies": {} - }, - "pino@9.6.0": { - "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", - "dependencies": { - "atomic-sleep": "atomic-sleep@1.0.0", - "fast-redact": "fast-redact@3.5.0", - "on-exit-leak-free": "on-exit-leak-free@2.1.2", - "pino-abstract-transport": "pino-abstract-transport@2.0.0", - "pino-std-serializers": "pino-std-serializers@7.0.0", - "process-warning": "process-warning@4.0.1", - "quick-format-unescaped": "quick-format-unescaped@4.0.4", - "real-require": "real-require@0.2.0", - "safe-stable-stringify": "safe-stable-stringify@2.5.0", - "sonic-boom": "sonic-boom@4.2.0", - "thread-stream": "thread-stream@3.1.0" - } - }, "postgres@3.4.5": { "integrity": "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==", "dependencies": {} - }, - "process-warning@4.0.1": { - "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", - "dependencies": {} - }, - "quick-format-unescaped@4.0.4": { - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "dependencies": {} - }, - "real-require@0.2.0": { - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "dependencies": {} - }, - "safe-stable-stringify@2.5.0": { - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "dependencies": {} - }, - "sonic-boom@4.2.0": { - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", - "dependencies": { - "atomic-sleep": "atomic-sleep@1.0.0" - } - }, - "split2@4.2.0": { - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dependencies": {} - }, - "thread-stream@3.1.0": { - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "dependencies": { - "real-require": "real-require@0.2.0" - } } } }, @@ -422,13 +356,15 @@ "jsr:@std/assert@^0.224.0", "jsr:@std/async@^0.224.0", "jsr:@std/log@^0.224.13", + "npm:@teidesu/deno-types@1.42.4", "npm:postgres@3.4.5" ], "packageJson": { "dependencies": [ "npm:@henrygd/queue@^1.0.7", - "npm:pino@^9.6.0", - "npm:postgres@3.4.5" + "npm:@teidesu/deno-types@1.45.2", + "npm:postgres@3.4.5", + "npm:supabase@2.21.1" ] } } diff --git a/pkgs/edge-worker/deno.test.json b/pkgs/edge-worker/deno.test.json index fbefaba4..a0c165b9 100644 --- a/pkgs/edge-worker/deno.test.json +++ b/pkgs/edge-worker/deno.test.json @@ -2,7 +2,8 @@ "name": "@pgflow/edge-worker", "version": "0.0.9", "license": "AGPL-3.0", - "exports": "./mod.ts", + "exports": "./src/index.ts", + "unstable": ["sloppy-imports"], "imports": { "@henrygd/queue": "jsr:@henrygd/queue@^1.0.7", "@std/assert": "jsr:@std/assert@^0.224.0", @@ -11,31 +12,26 @@ "@std/testing/mock": "jsr:@std/testing/mock@^0.224.0", "postgres": "npm:postgres@3.4.5", "@pgflow/core": "../core/src/index.ts", - "@pgflow/dsl": "../dsl/src/index.ts" + "@pgflow/dsl": "../dsl/src/index.ts", + "deno/full.d.ts": "npm:@teidesu/deno-types@1.42.4/full.d.ts" }, "tasks": { "db:down": "docker compose -f tests/db/compose.yaml down --volumes --remove-orphans", "db:clean": "rm tests/db/migrations/*.sql || true", "db:copy-migrations": "./scripts/concatenate-migrations.sh", "db:up": "docker compose -f tests/db/compose.yaml up --detach && ./scripts/wait-for-localhost 5432", - "db:ensure": "deno task db:down && deno task db:clean && deno task db:copy-migrations && deno task db:up && sleep 5", - "test:integration": "deno task db:ensure && deno test --allow-net --allow-env tests/integration", + "db:ensure": "deno task --config deno.test.json db:down && deno task --config deno.test.json db:clean && deno task --config deno.test.json db:copy-migrations && deno task --config deno.test.json db:up && sleep 5", + "test:integration": "deno task --config deno.test.json db:ensure && deno test --allow-net --allow-env tests/integration", "jsr:download-count": "deno run --allow-net jsr:@inbestigator/saves @pgflow/edge-worker" }, "lint": { - "exclude": ["supabase/functions/_src/", "dist/", "build/", "node_modules/"], + "exclude": ["supabase/functions/", "dist/", "node_modules/"], "rules": { "exclude": ["no-slow-types"] } }, "publish": { - "include": [ - "README.md", - "LICENSE.md", - "CHANGELOG.md", - "mod.ts", - "src/**/*.ts" - ], + "include": ["README.md", "LICENSE.md", "CHANGELOG.md", "src/**/*.ts"], "exclude": ["__tests__/**/*"] } } diff --git a/pkgs/edge-worker/eslint.config.cjs b/pkgs/edge-worker/eslint.config.cjs index 42f9e09d..61a9b779 100644 --- a/pkgs/edge-worker/eslint.config.cjs +++ b/pkgs/edge-worker/eslint.config.cjs @@ -3,23 +3,6 @@ const baseConfig = require('../../eslint.config.cjs'); module.exports = [ ...baseConfig, { - files: ['**/*.json'], - rules: { - '@nx/dependency-checks': [ - 'error', - { - ignoredFiles: [ - '{projectRoot}/eslint.config.{js,cjs,mjs}', - '{projectRoot}/vite.config.{js,ts,mjs,mts}', - ], - }, - ], - }, - languageOptions: { - parser: require('jsonc-eslint-parser'), - }, - }, - { - ignores: ['**/supabase/functions/_src/**/*'], + ignores: ['**/supabase/functions/_dist/**/*'], }, ]; diff --git a/pkgs/edge-worker/mod.ts b/pkgs/edge-worker/mod.ts deleted file mode 100644 index dfa8a204..00000000 --- a/pkgs/edge-worker/mod.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src/index.ts'; diff --git a/pkgs/edge-worker/package.json b/pkgs/edge-worker/package.json index 92360743..7f96b308 100644 --- a/pkgs/edge-worker/package.json +++ b/pkgs/edge-worker/package.json @@ -9,8 +9,8 @@ "exports": { "./package.json": "./package.json", ".": { - "types": "./index.d.ts", - "import": "./index.js" + "types": "./dist/index.d.ts", + "import": "./dist/index.js" } }, "files": [ @@ -18,10 +18,14 @@ ], "dependencies": { "@henrygd/queue": "^1.0.7", - "pino": "^9.6.0", - "postgres": "3.4.5", "@pgflow/core": "workspace:*", - "@pgflow/dsl": "workspace:*" + "@pgflow/dsl": "workspace:*", + "postgres": "3.4.5" + }, + "devDependencies": { + "@types/deno": "npm:@teidesu/deno-types@1.45.2", + "@types/node": "~18.16.9", + "supabase": "2.21.1" }, "publishConfig": { "access": "public" diff --git a/pkgs/edge-worker/project.json b/pkgs/edge-worker/project.json index 31ba02eb..6feb2bed 100644 --- a/pkgs/edge-worker/project.json +++ b/pkgs/edge-worker/project.json @@ -14,6 +14,16 @@ "rootDir": "pkgs/edge-worker/src" } }, + "build:bundle": { + "executor": "@nx/esbuild:esbuild", + "options": { + "main": "pkgs/edge-worker/src/index.ts", + "outputPath": "supabase/functions/_dist", + "tsConfig": "pkgs/edge-worker/tsconfig.lib.json", + "bundle": true, + "format": ["esm"] + } + }, "jsr:publish": { "executor": "nx:run-commands", "outputs": ["{workspaceRoot}/pkgs/edge-worker/"], @@ -87,20 +97,8 @@ "parallel": false } }, - "supabase:prepare-edge-fn": { - "executor": "nx:run-commands", - "options": { - "cwd": "pkgs/edge-worker", - "commands": [ - "find supabase/functions -maxdepth 1 -mindepth 1 -type d -exec cp deno.test.json {}/deno.json \\;", - "find supabase/functions -maxdepth 1 -mindepth 1 -type d -exec cp deno.lock {}/deno.lock \\;" - ], - "parallel": false - }, - "dependsOn": ["build"] - }, "supabase:functions-serve": { - "dependsOn": ["supabase:start", "supabase:prepare-edge-fn"], + "dependsOn": ["supabase:start", "build:bundle"], "executor": "nx:run-commands", "options": { "cwd": "pkgs/edge-worker", @@ -114,34 +112,34 @@ "executor": "nx:run-commands", "options": { "cwd": "pkgs/edge-worker", - "commands": ["deno task db:ensure"], + "commands": ["deno task --config deno.test.json db:ensure"], "parallel": false } }, "test:unit": { - "dependsOn": ["db:ensure"], + "dependsOn": ["db:ensure", "build"], "executor": "nx:run-commands", "options": { "cwd": "pkgs/edge-worker", "commands": [ - "deno test --allow-all --env=supabase/functions/.env tests/unit/" + "deno test --config deno.test.json --allow-all --env=supabase/functions/.env tests/unit/" ], "parallel": false } }, "test:integration": { - "dependsOn": ["db:ensure"], + "dependsOn": ["db:ensure", "build"], "executor": "nx:run-commands", "options": { "cwd": "pkgs/edge-worker", "commands": [ - "deno test --allow-all --env=supabase/functions/.env tests/integration/" + "deno test --config deno.test.json --allow-all --env=supabase/functions/.env tests/integration/" ], "parallel": false } }, "test:e2e": { - "dependsOn": ["supabase:prepare-edge-fn"], + "dependsOn": ["build:bundle"], "executor": "nx:run-commands", "options": { "cwd": "pkgs/edge-worker", diff --git a/pkgs/edge-worker/src/EdgeWorker.ts b/pkgs/edge-worker/src/EdgeWorker.ts index c05185ac..43fb2e62 100644 --- a/pkgs/edge-worker/src/EdgeWorker.ts +++ b/pkgs/edge-worker/src/EdgeWorker.ts @@ -1,11 +1,11 @@ -import type { Worker } from './core/Worker.js'; -import spawnNewEdgeFunction from './spawnNewEdgeFunction.js'; import type { Json } from './core/types.js'; -import { getLogger, setupLogger } from './core/Logger.js'; import { createQueueWorker, type QueueWorkerConfig, } from './queue/createQueueWorker.js'; +import { createAdapter } from './platform/createAdapter.js'; +import type { PlatformAdapter } from './platform/types.js'; +import { MessageHandlerFn } from './queue/types.js'; /** * Configuration options for the EdgeWorker. @@ -32,7 +32,7 @@ export type EdgeWorkerConfig = QueueWorkerConfig; * ``` */ export class EdgeWorker { - private static logger = getLogger('EdgeWorker'); + private static platform: PlatformAdapter | null = null; private static wasCalled = false; /** @@ -71,22 +71,18 @@ export class EdgeWorker { * }); * ``` */ - static start( - handler: (message: TPayload) => Promise | void, + static async start( + handler: MessageHandlerFn, config: EdgeWorkerConfig = {} ) { this.ensureFirstCall(); - // Get connection string from config or environment - const connectionString = - config.connectionString || this.getConnectionString(); + // First, create the adapter + this.platform = await createAdapter(); - // Create a complete configuration object with defaults - const completeConfig: EdgeWorkerConfig = { - // Pass through any config options first + // Apply default values to the config + const workerConfig: EdgeWorkerConfig = { ...config, - - // Then override with defaults for missing values queueName: config.queueName || 'tasks', maxConcurrent: config.maxConcurrent ?? 10, maxPgConnections: config.maxPgConnections ?? 4, @@ -95,12 +91,26 @@ export class EdgeWorker { retryDelay: config.retryDelay ?? 5, retryLimit: config.retryLimit ?? 5, visibilityTimeout: config.visibilityTimeout ?? 3, - - // Ensure connectionString is always set - connectionString, + connectionString: + config.connectionString || this.platform.getConnectionString(), }; - this.setupRequestHandler(handler, completeConfig); + await this.platform.startWorker((createLoggerFn) => + createQueueWorker(handler, workerConfig, createLoggerFn) + ); + + return this.platform; + } + + /** + * Stop the EdgeWorker and clean up resources. + */ + static async stop() { + if (this.platform) { + await this.platform.stopWorker(); + } else { + throw new Error('EdgeWorker.start() must be called first'); + } } private static ensureFirstCall() { @@ -109,64 +119,4 @@ export class EdgeWorker { } this.wasCalled = true; } - - private static getConnectionString(): string { - const connectionString = Deno.env.get('EDGE_WORKER_DB_URL'); - if (!connectionString) { - const message = - 'EDGE_WORKER_DB_URL is not set!\n' + - 'See https://pgflow.pages.dev/edge-worker/prepare-environment/#prepare-connection-string'; - throw new Error(message); - } - return connectionString; - } - - private static setupShutdownHandler(worker: Worker) { - globalThis.onbeforeunload = async () => { - if (worker.edgeFunctionName) { - await spawnNewEdgeFunction(worker.edgeFunctionName); - } - - worker.stop(); - }; - - // use waitUntil to prevent the function from exiting - // For Supabase Edge Functions environment - const promiseThatNeverResolves = new Promise(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function - EdgeRuntime.waitUntil(promiseThatNeverResolves); - } - - private static setupRequestHandler( - handler: (message: TPayload) => Promise | void, - workerConfig: EdgeWorkerConfig - ) { - let worker: Worker | null = null; - - Deno.serve({}, (req) => { - if (!worker) { - const edgeFunctionName = this.extractFunctionName(req); - const sbExecutionId = Deno.env.get('SB_EXECUTION_ID')!; - setupLogger(sbExecutionId); - - this.logger.info(`HTTP Request: ${edgeFunctionName}`); - // Create the worker with all configuration options - - worker = createQueueWorker(handler, workerConfig); - worker.startOnlyOnce({ - edgeFunctionName, - workerId: sbExecutionId, - }); - - this.setupShutdownHandler(worker); - } - - return new Response('ok', { - headers: { 'Content-Type': 'application/json' }, - }); - }); - } - - private static extractFunctionName(req: Request): string { - return new URL(req.url).pathname.replace(/^\/+|\/+$/g, ''); - } } diff --git a/pkgs/edge-worker/src/core/BatchProcessor.ts b/pkgs/edge-worker/src/core/BatchProcessor.ts index 3358da36..139d8b15 100644 --- a/pkgs/edge-worker/src/core/BatchProcessor.ts +++ b/pkgs/edge-worker/src/core/BatchProcessor.ts @@ -1,18 +1,20 @@ import type { ExecutionController } from './ExecutionController.js'; import type { IMessage, IPoller } from './types.js'; -import { getLogger } from './Logger.js'; +import type { Logger } from '../platform/types.js'; export class BatchProcessor { - private logger = getLogger('BatchProcessor'); + private logger: Logger; constructor( private executionController: ExecutionController, private poller: IPoller, - private signal: AbortSignal + private signal: AbortSignal, + logger: Logger ) { this.executionController = executionController; this.signal = signal; this.poller = poller; + this.logger = logger; } async processBatch() { diff --git a/pkgs/edge-worker/src/core/ExecutionController.ts b/pkgs/edge-worker/src/core/ExecutionController.ts index b32d9e41..c98b6323 100644 --- a/pkgs/edge-worker/src/core/ExecutionController.ts +++ b/pkgs/edge-worker/src/core/ExecutionController.ts @@ -1,13 +1,13 @@ import { newQueue, type Queue as PromiseQueue } from '@henrygd/queue'; import type { IExecutor, IMessage } from './types.js'; -import { getLogger } from './Logger.js'; +import type { Logger } from '../platform/types.js'; export interface ExecutionConfig { maxConcurrent: number; } export class ExecutionController { - private logger = getLogger('ExecutionController'); + private logger: Logger; private promiseQueue: PromiseQueue; private signal: AbortSignal; private createExecutor: (record: TMessage, signal: AbortSignal) => IExecutor; @@ -15,11 +15,13 @@ export class ExecutionController { constructor( executorFactory: (record: TMessage, signal: AbortSignal) => IExecutor, abortSignal: AbortSignal, - config: ExecutionConfig + config: ExecutionConfig, + logger: Logger ) { this.signal = abortSignal; this.createExecutor = executorFactory; this.promiseQueue = newQueue(config.maxConcurrent); + this.logger = logger; } async start(record: TMessage) { diff --git a/pkgs/edge-worker/src/core/Heartbeat.ts b/pkgs/edge-worker/src/core/Heartbeat.ts index b5deeb3b..09e33bab 100644 --- a/pkgs/edge-worker/src/core/Heartbeat.ts +++ b/pkgs/edge-worker/src/core/Heartbeat.ts @@ -1,16 +1,19 @@ import type { Queries } from './Queries.js'; import type { WorkerRow } from './types.js'; -import { getLogger } from './Logger.js'; +import type { Logger } from '../platform/types.js'; export class Heartbeat { - private logger = getLogger('Heartbeat'); + private logger: Logger; private lastHeartbeat = 0; constructor( private interval: number, private queries: Queries, - private workerRow: WorkerRow - ) {} + private workerRow: WorkerRow, + logger: Logger + ) { + this.logger = logger; + } async send(): Promise { const now = Date.now(); diff --git a/pkgs/edge-worker/src/core/Logger.ts b/pkgs/edge-worker/src/core/Logger.ts deleted file mode 100644 index 03a69fb4..00000000 --- a/pkgs/edge-worker/src/core/Logger.ts +++ /dev/null @@ -1,46 +0,0 @@ -import pino from 'pino'; - -function getLogLevelFromEnv(): pino.LevelWithSilent { - const validLevels = ['DEBUG', 'INFO', 'ERROR']; - const logLevel = Deno.env.get('EDGE_WORKER_LOG_LEVEL')?.toUpperCase(); - - if (logLevel && !validLevels.includes(logLevel)) { - console.warn(`Invalid log level "${logLevel}". Using "INFO" instead.`); - return 'info'; - } - - return (logLevel?.toLowerCase() as pino.LevelWithSilent) || 'info'; -} - -// Store the root logger instance -let rootLogger: pino.Logger; - -export function setupLogger(workerId: string) { - const level = getLogLevelFromEnv(); - - const loggerOptions: pino.LoggerOptions = { - level, - formatters: { - bindings: () => ({ worker_id: workerId }), - }, - serializers: pino.stdSerializers, - // Remove the transport configuration that's causing issues in Deno - // transport: { - // target: 'pino-pretty', - // options: { - // colorize: true, - // messageFormat: '[{module}] {msg}', - // } - // } - }; - - // Create and store the root logger - rootLogger = pino(loggerOptions); - return rootLogger; -} - -export function getLogger(module: string) { - // Use the root logger if it exists, otherwise create a new one - const logger = rootLogger || pino(); - return logger.child({ module }); -} diff --git a/pkgs/edge-worker/src/core/Worker.ts b/pkgs/edge-worker/src/core/Worker.ts index 95c32dca..fc2d91bf 100644 --- a/pkgs/edge-worker/src/core/Worker.ts +++ b/pkgs/edge-worker/src/core/Worker.ts @@ -1,10 +1,10 @@ import type postgres from 'postgres'; import type { IBatchProcessor, ILifecycle, WorkerBootstrap } from './types.js'; -import { getLogger, setupLogger } from './Logger.js'; +import type { Logger } from '../platform/types.js'; export class Worker { private lifecycle: ILifecycle; - private logger = getLogger('Worker'); + private logger: Logger; private abortController = new AbortController(); private batchProcessor: IBatchProcessor; @@ -13,13 +13,13 @@ export class Worker { constructor( batchProcessor: IBatchProcessor, lifecycle: ILifecycle, - sql: postgres.Sql + sql: postgres.Sql, + logger: Logger ) { this.sql = sql; - this.lifecycle = lifecycle; - this.batchProcessor = batchProcessor; + this.logger = logger; } async startOnlyOnce(workerBootstrap: WorkerBootstrap) { @@ -32,8 +32,6 @@ export class Worker { } private async start(workerBootstrap: WorkerBootstrap) { - setupLogger(workerBootstrap.workerId); - try { await this.lifecycle.acknowledgeStart(workerBootstrap); diff --git a/pkgs/edge-worker/src/core/WorkerLifecycle.ts b/pkgs/edge-worker/src/core/WorkerLifecycle.ts index aebfae5d..925c0bdf 100644 --- a/pkgs/edge-worker/src/core/WorkerLifecycle.ts +++ b/pkgs/edge-worker/src/core/WorkerLifecycle.ts @@ -1,25 +1,27 @@ import { Heartbeat } from './Heartbeat.js'; -import { getLogger } from './Logger.js'; import type { Queries } from './Queries.js'; import type { Queue } from '../queue/Queue.js'; import type { ILifecycle, Json, WorkerBootstrap, WorkerRow } from './types.js'; import { States, WorkerState } from './WorkerState.js'; +import type { Logger } from '../platform/types.js'; export interface LifecycleConfig { queueName: string; } export class WorkerLifecycle implements ILifecycle { - private workerState: WorkerState = new WorkerState(); + private workerState: WorkerState; private heartbeat?: Heartbeat; - private logger = getLogger('WorkerLifecycle'); + private logger: Logger; private queries: Queries; private queue: Queue; private workerRow?: WorkerRow; - constructor(queries: Queries, queue: Queue) { + constructor(queries: Queries, queue: Queue, logger: Logger) { this.queries = queries; this.queue = queue; + this.logger = logger; + this.workerState = new WorkerState(logger); } async acknowledgeStart(workerBootstrap: WorkerBootstrap): Promise { @@ -33,7 +35,7 @@ export class WorkerLifecycle implements ILifecycle { ...workerBootstrap, }); - this.heartbeat = new Heartbeat(5000, this.queries, this.workerRow); + this.heartbeat = new Heartbeat(5000, this.queries, this.workerRow, this.logger); this.workerState.transitionTo(States.Running); } diff --git a/pkgs/edge-worker/src/core/WorkerState.ts b/pkgs/edge-worker/src/core/WorkerState.ts index 4a397ce4..8d15fccb 100644 --- a/pkgs/edge-worker/src/core/WorkerState.ts +++ b/pkgs/edge-worker/src/core/WorkerState.ts @@ -1,4 +1,4 @@ -import { getLogger } from './Logger.js'; +import type { Logger } from '../platform/types.js'; export enum States { /** The worker has been created but has not yet started. */ @@ -36,8 +36,12 @@ export class TransitionError extends Error { * Represents the state of a worker and exposes method for doing allowed transitions */ export class WorkerState { - private logger = getLogger('WorkerState'); + private logger: Logger; private state: States = States.Created; + + constructor(logger: Logger) { + this.logger = logger; + } get current() { return this.state; diff --git a/pkgs/edge-worker/src/flow/FlowWorkerLifecycle.ts b/pkgs/edge-worker/src/flow/FlowWorkerLifecycle.ts index 22af73fe..693273f9 100644 --- a/pkgs/edge-worker/src/flow/FlowWorkerLifecycle.ts +++ b/pkgs/edge-worker/src/flow/FlowWorkerLifecycle.ts @@ -1,7 +1,7 @@ import { Heartbeat } from '../core/Heartbeat.js'; -import { getLogger } from '../core/Logger.js'; import type { Queries } from '../core/Queries.js'; import type { ILifecycle, WorkerBootstrap, WorkerRow } from '../core/types.js'; +import type { Logger } from '../platform/types.js'; import { States, WorkerState } from '../core/WorkerState.js'; import type { AnyFlow } from '@pgflow/dsl'; @@ -9,16 +9,18 @@ import type { AnyFlow } from '@pgflow/dsl'; * A specialized WorkerLifecycle for Flow-based workers that is aware of the Flow's step types */ export class FlowWorkerLifecycle implements ILifecycle { - private workerState: WorkerState = new WorkerState(); + private workerState: WorkerState; private heartbeat?: Heartbeat; - private logger = getLogger('FlowWorkerLifecycle'); + private logger: Logger; private queries: Queries; private workerRow?: WorkerRow; private flow: TFlow; - constructor(queries: Queries, flow: TFlow) { + constructor(queries: Queries, flow: TFlow, logger: Logger) { this.queries = queries; this.flow = flow; + this.logger = logger; + this.workerState = new WorkerState(logger); } async acknowledgeStart(workerBootstrap: WorkerBootstrap): Promise { @@ -29,7 +31,12 @@ export class FlowWorkerLifecycle implements ILifecycle { ...workerBootstrap, }); - this.heartbeat = new Heartbeat(5000, this.queries, this.workerRow); + this.heartbeat = new Heartbeat( + 5000, + this.queries, + this.workerRow, + this.logger + ); this.workerState.transitionTo(States.Running); } diff --git a/pkgs/edge-worker/src/flow/StepTaskExecutor.ts b/pkgs/edge-worker/src/flow/StepTaskExecutor.ts index 27722f23..4fc293e4 100644 --- a/pkgs/edge-worker/src/flow/StepTaskExecutor.ts +++ b/pkgs/edge-worker/src/flow/StepTaskExecutor.ts @@ -1,7 +1,7 @@ import type { AnyFlow } from '@pgflow/dsl'; import type { StepTaskRecord, IPgflowClient } from './types.js'; import type { IExecutor } from '../core/types.js'; -import { getLogger } from '../core/Logger.js'; +import type { Logger } from '../platform/types.js'; class AbortError extends Error { constructor() { @@ -15,14 +15,17 @@ class AbortError extends Error { * with strong typing for the flow's step handlers */ export class StepTaskExecutor implements IExecutor { - private logger = getLogger('StepTaskExecutor'); + private logger: Logger; constructor( private readonly flow: TFlow, private readonly task: StepTaskRecord, private readonly adapter: IPgflowClient, - private readonly signal: AbortSignal - ) {} + private readonly signal: AbortSignal, + logger: Logger + ) { + this.logger = logger; + } get msgId() { return this.task.msg_id; diff --git a/pkgs/edge-worker/src/flow/StepTaskPoller.ts b/pkgs/edge-worker/src/flow/StepTaskPoller.ts index 9cebbab1..0854b4e4 100644 --- a/pkgs/edge-worker/src/flow/StepTaskPoller.ts +++ b/pkgs/edge-worker/src/flow/StepTaskPoller.ts @@ -1,6 +1,6 @@ import type { StepTaskRecord, IPgflowClient } from './types.js'; import type { IPoller } from '../core/types.js'; -import { getLogger } from '../core/Logger.js'; +import type { Logger } from '../platform/types.js'; import type { AnyFlow } from '@pgflow/dsl'; export interface StepTaskPollerConfig { @@ -14,13 +14,16 @@ export interface StepTaskPollerConfig { export class StepTaskPoller implements IPoller> { - private logger = getLogger('StepTaskPoller'); + private logger: Logger; constructor( private readonly adapter: IPgflowClient, private readonly signal: AbortSignal, - private readonly config: StepTaskPollerConfig - ) {} + private readonly config: StepTaskPollerConfig, + logger: Logger + ) { + this.logger = logger; + } async poll(): Promise[]> { if (this.isAborted()) { diff --git a/pkgs/edge-worker/src/flow/createFlowWorker.ts b/pkgs/edge-worker/src/flow/createFlowWorker.ts index 4cae36ef..a80bed7f 100644 --- a/pkgs/edge-worker/src/flow/createFlowWorker.ts +++ b/pkgs/edge-worker/src/flow/createFlowWorker.ts @@ -7,11 +7,11 @@ import { PgflowSqlClient } from '@pgflow/core'; import { Queries } from '../core/Queries.js'; import type { StepTaskRecord } from './types.js'; import type { IExecutor } from '../core/types.js'; +import type { Logger } from '../platform/types.js'; import { Worker } from '../core/Worker.js'; import postgres from 'postgres'; import { FlowWorkerLifecycle } from './FlowWorkerLifecycle.js'; import { BatchProcessor } from '../core/BatchProcessor.js'; -import { getLogger } from '../core/Logger.js'; /** * Configuration for the flow worker @@ -33,9 +33,10 @@ export type FlowWorkerConfig = EdgeWorkerConfig & { */ export function createFlowWorker( flow: TFlow, - config: FlowWorkerConfig + config: FlowWorkerConfig, + createLogger: (module: string) => Logger ): Worker { - const logger = getLogger('createFlowWorker'); + const logger = createLogger('createFlowWorker'); // Create abort controller for graceful shutdown const abortController = new AbortController(); @@ -63,7 +64,11 @@ export function createFlowWorker( // Create specialized FlowWorkerLifecycle with the proxied queue and flow const queries = new Queries(sql); - const lifecycle = new FlowWorkerLifecycle(queries, flow); + const lifecycle = new FlowWorkerLifecycle( + queries, + flow, + createLogger('FlowWorkerLifecycle') + ); // Create StepTaskPoller const pollerConfig: StepTaskPollerConfig = { @@ -73,7 +78,8 @@ export function createFlowWorker( const poller = new StepTaskPoller( pgflowAdapter, abortSignal, - pollerConfig + pollerConfig, + createLogger('StepTaskPoller') ); // Create executor factory with proper typing @@ -81,7 +87,13 @@ export function createFlowWorker( record: StepTaskRecord, signal: AbortSignal ): IExecutor => { - return new StepTaskExecutor(flow, record, pgflowAdapter, signal); + return new StepTaskExecutor( + flow, + record, + pgflowAdapter, + signal, + createLogger('StepTaskExecutor') + ); }; // Create ExecutionController @@ -90,16 +102,23 @@ export function createFlowWorker( abortSignal, { maxConcurrent: config.maxConcurrent || 10, - } + }, + createLogger('ExecutionController') ); // Create BatchProcessor const batchProcessor = new BatchProcessor>( executionController, poller, - abortSignal + abortSignal, + createLogger('BatchProcessor') ); // Return Worker - return new Worker(batchProcessor, lifecycle, sql); + return new Worker( + batchProcessor, + lifecycle, + sql, + createLogger('Worker') + ); } diff --git a/pkgs/edge-worker/src/index.ts b/pkgs/edge-worker/src/index.ts index d3478d57..a328afd1 100644 --- a/pkgs/edge-worker/src/index.ts +++ b/pkgs/edge-worker/src/index.ts @@ -6,6 +6,9 @@ export { EdgeWorker } from './EdgeWorker.js'; export { createFlowWorker } from './flow/createFlowWorker.js'; export { FlowWorkerLifecycle } from './flow/FlowWorkerLifecycle.js'; +// Export platform adapters +export * from './platform/index.js'; + // Export types export type { StepTaskRecord } from './flow/types.js'; export type { FlowWorkerConfig } from './flow/createFlowWorker.js'; diff --git a/pkgs/edge-worker/src/platform/DenoAdapter.ts b/pkgs/edge-worker/src/platform/DenoAdapter.ts new file mode 100644 index 00000000..7110e925 --- /dev/null +++ b/pkgs/edge-worker/src/platform/DenoAdapter.ts @@ -0,0 +1,163 @@ +/// + +import type { CreateWorkerFn, Logger, PlatformAdapter } from './types.js'; +import type { Worker } from '../core/Worker.js'; +import { createLoggingFactory } from './logging.js'; + +/** + * Adapter for Deno runtime environment + */ +export class DenoAdapter implements PlatformAdapter { + private edgeFunctionName: string | null = null; + private worker: Worker | null = null; + private logger: Logger; + + // Logging factory with dynamic workerId support + private loggingFactory = createLoggingFactory(); + + constructor() { + // Guard clause to ensure we're in a Deno environment + // This is just for type checking during build + // At runtime, this class should only be instantiated in Deno + if (typeof Deno === 'undefined' || typeof EdgeRuntime === 'undefined') { + throw new Error( + 'DenoAdapter created in non-Deno environment - this is expected during build only' + ); + } + + // Set initial log level + const logLevel = this.getEnvVarOrThrow('EDGE_WORKER_LOG_LEVEL') || 'info'; + this.loggingFactory.setLogLevel(logLevel); + + // startWorker logger with a default module name + this.logger = this.loggingFactory.createLogger('DenoAdapter'); + } + + /** + * startWorker the platform adapter with a worker factory function + * @param createWorkerFn Function that creates a worker instance when called with a logger + */ + async startWorker(createWorkerFn: CreateWorkerFn): Promise { + this.extendLifetimeOfEdgeFunction(); + this.setupShutdownHandler(); + this.setupStartupHandler(createWorkerFn); + } + + async stopWorker(): Promise { + if (this.worker) { + await this.worker.stop(); + } + } + + createLogger(module: string): Logger { + return this.loggingFactory.createLogger(module); + } + + /** + * Ensures the config has a connectionString by using the environment value if needed + */ + getConnectionString(): string { + return this.getEnvVarOrThrow('EDGE_WORKER_DB_URL'); + } + + /** + * Get the Supabase URL for the current environment + */ + private getEnvVarOrThrow(name: string): string { + const envVar = Deno.env.get(name); + + if (!envVar) { + const message = + `${name} is not set!\n` + + 'See docs to learn how to prepare the environment:\n' + + 'https://pgflow.pages.dev/edge-worker/prepare-environment'; + throw new Error(message); + } + + return envVar; + } + + private async spawnNewEdgeFunction(): Promise { + if (!this.edgeFunctionName) { + throw new Error('functionName cannot be null or empty'); + } + + const supabaseUrl = this.getEnvVarOrThrow('SUPABASE_URL'); + const supabaseAnonKey = this.getEnvVarOrThrow('SUPABASE_ANON_KEY'); + + this.logger.debug('Spawning a new Edge Function...'); + + const response = await fetch( + `${supabaseUrl}/functions/v1/${this.edgeFunctionName}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${supabaseAnonKey}`, + 'Content-Type': 'application/json', + }, + } + ); + + this.logger.debug('Edge Function spawned successfully!'); + + if (!response.ok) { + throw new Error( + `Edge function returned non-OK status: ${response.status} ${response.statusText}` + ); + } + } + + private extractFunctionName(req: Request): string { + return new URL(req.url).pathname.replace(/^\/+|\/+$/g, ''); + } + + private setupShutdownHandler(): void { + globalThis.onbeforeunload = async () => { + this.logger.info('Shutting down...'); + + if (this.worker) { + await this.spawnNewEdgeFunction(); + } + + await this.stopWorker(); + }; + } + + /** + * Supabase EdgeRuntime exposes waitUntil method as a way to extend + * the lifetime of the function until the promise resolves. + * + * We leverage this to extend the lifetime to the absolute maximum, + * by passing a promise that never resolves. + */ + private extendLifetimeOfEdgeFunction(): void { + // eslint-disable-next-line @typescript-eslint/no-empty-function + const promiseThatNeverResolves = new Promise(() => {}); + EdgeRuntime.waitUntil(promiseThatNeverResolves); + } + + private setupStartupHandler(createWorkerFn: CreateWorkerFn): void { + Deno.serve({}, (req: Request) => { + this.logger.info(`HTTP Request: ${this.edgeFunctionName}`); + + if (!this.worker) { + this.edgeFunctionName = this.extractFunctionName(req); + + const workerId = this.getEnvVarOrThrow('SB_EXECUTION_ID'); + + this.loggingFactory.setWorkerId(workerId); + + // Create the worker using the factory function and the logger + this.worker = createWorkerFn(this.loggingFactory.createLogger); + this.worker.startOnlyOnce({ + edgeFunctionName: this.edgeFunctionName, + workerId, + }); + } + + return new Response('ok', { + headers: { 'Content-Type': 'application/json' }, + }); + }); + } +} diff --git a/pkgs/edge-worker/src/platform/createAdapter.ts b/pkgs/edge-worker/src/platform/createAdapter.ts new file mode 100644 index 00000000..bc6b7b65 --- /dev/null +++ b/pkgs/edge-worker/src/platform/createAdapter.ts @@ -0,0 +1,20 @@ +import type { PlatformAdapter } from './types.js'; +import { DenoAdapter } from './DenoAdapter.js'; + +/** + * Creates the appropriate platform adapter based on the runtime environment + */ +export async function createAdapter(): Promise { + if (isDenoEnvironment()) { + const adapter = new DenoAdapter(); + return adapter; + } + + // For now, only support Deno + // Later add NodeAdapter, BrowserAdapter, etc. + throw new Error('Unsupported environment'); +} + +function isDenoEnvironment(): boolean { + return typeof Deno !== 'undefined'; +} diff --git a/pkgs/edge-worker/src/platform/deno-types.d.ts b/pkgs/edge-worker/src/platform/deno-types.d.ts new file mode 100644 index 00000000..57a9e06d --- /dev/null +++ b/pkgs/edge-worker/src/platform/deno-types.d.ts @@ -0,0 +1,24 @@ +/// + +/** + * Minimal type definitions for Deno APIs used in our codebase. + * These are used for type checking during build in Node.js environment. + * At runtime in Deno, the actual Deno implementations will be used. + */ + +// Define minimal Deno namespace interface +declare global { + // Define EdgeRuntime interface + interface EdgeRuntimeNamespace { + waitUntil(promise: Promise): void; + } + + // In DenoAdapter.ts, we assume these are always available + // This makes TypeScript happy without requiring non-null assertions + // We need to use 'var' here for proper global declaration in TypeScript + // eslint-disable-next-line no-var + var EdgeRuntime: EdgeRuntimeNamespace; +} + +// Export empty object to make this a module +export {}; diff --git a/pkgs/edge-worker/src/platform/index.ts b/pkgs/edge-worker/src/platform/index.ts new file mode 100644 index 00000000..f6974346 --- /dev/null +++ b/pkgs/edge-worker/src/platform/index.ts @@ -0,0 +1,3 @@ +export * from './types.js'; +export { createAdapter } from './createAdapter.js'; +export { DenoAdapter } from './DenoAdapter.js'; diff --git a/pkgs/edge-worker/src/platform/logging.ts b/pkgs/edge-worker/src/platform/logging.ts new file mode 100644 index 00000000..5a1e6dea --- /dev/null +++ b/pkgs/edge-worker/src/platform/logging.ts @@ -0,0 +1,91 @@ +import type { Logger } from './types.js'; + +/** + * Creates a logging factory with dynamic workerId support + */ +export function createLoggingFactory() { + // Shared state for all loggers + let sharedWorkerId = 'unknown'; + let logLevel = 'info'; + + // All created logger instances - using Map for efficient lookup + const loggers: Map = new Map(); + + // Simple level filtering + const levels = { error: 0, warn: 1, info: 2, debug: 3 }; + + /** + * Creates a new logger for a specific module + */ + const createLogger = (module: string): Logger => { + // Create a logger that directly references the shared state + const logger: Logger = { + debug: (message, ...args) => { + const levelValue = + levels[logLevel as keyof typeof levels] ?? levels.info; + if (levelValue >= levels.debug) { + console.debug( + `worker_id=${sharedWorkerId} module=${module} ${message}`, + ...args + ); + } + }, + info: (message, ...args) => { + const levelValue = + levels[logLevel as keyof typeof levels] ?? levels.info; + if (levelValue >= levels.info) { + console.info( + `worker_id=${sharedWorkerId} module=${module} ${message}`, + ...args + ); + } + }, + warn: (message, ...args) => { + const levelValue = + levels[logLevel as keyof typeof levels] ?? levels.info; + if (levelValue >= levels.warn) { + console.warn( + `worker_id=${sharedWorkerId} module=${module} ${message}`, + ...args + ); + } + }, + error: (message, ...args) => { + const levelValue = + levels[logLevel as keyof typeof levels] ?? levels.info; + if (levelValue >= levels.error) { + console.error( + `worker_id=${sharedWorkerId} module=${module} ${message}`, + ...args + ); + } + }, + }; + + // Store the logger in our registry using module as key + loggers.set(module, logger); + + // Return the logger + return logger; + }; + + /** + * Updates the workerId for all loggers + */ + const setWorkerId = (workerId: string): void => { + sharedWorkerId = workerId; + }; + + /** + * Updates the log level for all loggers + */ + const setLogLevel = (newLogLevel: string): void => { + logLevel = newLogLevel; + }; + + return { + createLogger, + setWorkerId, + setLogLevel, + }; +} diff --git a/pkgs/edge-worker/src/platform/types.ts b/pkgs/edge-worker/src/platform/types.ts new file mode 100644 index 00000000..7f26da78 --- /dev/null +++ b/pkgs/edge-worker/src/platform/types.ts @@ -0,0 +1,41 @@ +import type { Worker } from '../core/Worker.js'; +/** + * Basic logger interface used throughout the application + */ +export interface Logger { + debug(message: string, ...args: any[]): void; + info(message: string, ...args: any[]): void; + warn(message: string, ...args: any[]): void; + error(message: string, ...args: any[]): void; +} + +/** + * Logger factory function + */ +export type CreateLoggerFn = (module: string) => Logger; + +/** + * Logger factory function + */ +export type CreateWorkerFn = (createLoggerFn: CreateLoggerFn) => Worker; + +/** + * Common interface for all platform adapters + */ +export interface PlatformAdapter { + /** + * startWorker the platform adapter with a worker factory function + * @param createWorkerFn Function that creates a worker instance when called with a logger + */ + startWorker(createWorkerFn: CreateWorkerFn): Promise; + + /** + * Clean up resources when shutting down + */ + stopWorker(): Promise; + + /** + * Get the connection string for the database + */ + getConnectionString(): string; +} diff --git a/pkgs/edge-worker/src/queue/MessageExecutor.ts b/pkgs/edge-worker/src/queue/MessageExecutor.ts index 4c49abec..0d5b554d 100644 --- a/pkgs/edge-worker/src/queue/MessageExecutor.ts +++ b/pkgs/edge-worker/src/queue/MessageExecutor.ts @@ -1,7 +1,7 @@ import type { Json } from '../core/types.js'; -import type { PgmqMessageRecord } from './types.js'; +import type { MessageHandlerFn, PgmqMessageRecord } from './types.js'; import type { Queue } from './Queue.js'; -import { getLogger } from '../core/Logger.js'; +import type { Logger } from '../platform/types.js'; class AbortError extends Error { constructor() { @@ -19,18 +19,19 @@ class AbortError extends Error { * It also handles the abort signal and logs the error. */ export class MessageExecutor { - private logger = getLogger('MessageExecutor'); + private logger: Logger; constructor( private readonly queue: Queue, private readonly record: PgmqMessageRecord, - private readonly messageHandler: ( - message: TPayload - ) => Promise | void, + private readonly messageHandler: MessageHandlerFn, private readonly signal: AbortSignal, private readonly retryLimit: number, - private readonly retryDelay: number - ) {} + private readonly retryDelay: number, + logger: Logger + ) { + this.logger = logger; + } get msgId() { return this.record.msg_id; diff --git a/pkgs/edge-worker/src/queue/Queue.ts b/pkgs/edge-worker/src/queue/Queue.ts index 044b802d..ece31356 100644 --- a/pkgs/edge-worker/src/queue/Queue.ts +++ b/pkgs/edge-worker/src/queue/Queue.ts @@ -1,15 +1,25 @@ import type postgres from 'postgres'; import type { PgmqMessageRecord } from './types.js'; import type { Json } from '../core/types.js'; +import type { Logger } from '../platform/types.js'; export class Queue { - constructor(private readonly sql: postgres.Sql, readonly queueName: string) {} + private logger: Logger; + + constructor( + private readonly sql: postgres.Sql, + readonly queueName: string, + logger: Logger + ) { + this.logger = logger; + } /** * Creates a queue if it doesn't exist. * If the queue already exists, this method does nothing. */ async safeCreate() { + this.logger.debug(`Creating queue '${this.queueName}' if it doesn't exist`); return await this.sql` select * from pgmq.create(${this.queueName}) where not exists ( @@ -23,6 +33,7 @@ export class Queue { * If the queue doesn't exist, this method does nothing. */ async safeDrop() { + this.logger.debug(`Dropping queue '${this.queueName}' if it exists`); return await this.sql` select * from pgmq.drop_queue(${this.queueName}) where exists ( @@ -32,18 +43,21 @@ export class Queue { } async archive(msgId: number): Promise { + this.logger.debug(`Archiving message ${msgId} from queue '${this.queueName}'`); await this.sql` SELECT pgmq.archive(queue_name => ${this.queueName}, msg_id => ${msgId}::bigint); `; } async archiveBatch(msgIds: number[]): Promise { + this.logger.debug(`Archiving ${msgIds.length} messages from queue '${this.queueName}'`); await this.sql` SELECT pgmq.archive(queue_name => ${this.queueName}, msg_ids => ${msgIds}::bigint[]); `; } async send(message: TPayload): Promise { + this.logger.debug(`Sending message to queue '${this.queueName}'`); const msgJson = JSON.stringify(message); await this.sql` SELECT pgmq.send(queue_name => ${this.queueName}, msg => ${msgJson}::jsonb) @@ -56,6 +70,7 @@ export class Queue { maxPollSeconds = 5, pollIntervalMs = 200 ) { + this.logger.debug(`Reading messages from queue '${this.queueName}' with poll`); return await this.sql[]>` SELECT * FROM edge_worker.read_with_poll( @@ -81,6 +96,7 @@ export class Queue { msgId: number, vtOffsetSeconds: number ): Promise> { + this.logger.debug(`Setting visibility timeout for message ${msgId} to ${vtOffsetSeconds} seconds`); const records = await this.sql[]>` UPDATE ${this.sql('pgmq.q_' + this.queueName)} SET vt = (clock_timestamp() + make_interval(secs => ${vtOffsetSeconds})) diff --git a/pkgs/edge-worker/src/queue/ReadWithPollPoller.ts b/pkgs/edge-worker/src/queue/ReadWithPollPoller.ts index 92feb6be..e4917009 100644 --- a/pkgs/edge-worker/src/queue/ReadWithPollPoller.ts +++ b/pkgs/edge-worker/src/queue/ReadWithPollPoller.ts @@ -1,6 +1,7 @@ import type { Queue } from './Queue.js'; import type { PgmqMessageRecord } from './types.js'; import type { Json } from '../core/types.js'; +import type { Logger } from '../platform/types.js'; export interface PollerConfig { batchSize: number; @@ -10,23 +11,33 @@ export interface PollerConfig { } export class ReadWithPollPoller { + private logger: Logger; + constructor( protected readonly queue: Queue, protected readonly signal: AbortSignal, - protected readonly config: PollerConfig - ) {} + protected readonly config: PollerConfig, + logger: Logger + ) { + this.logger = logger; + } async poll(): Promise[]> { if (this.isAborted()) { + this.logger.debug('Polling aborted, returning empty array'); return []; } - return await this.queue.readWithPoll( + this.logger.debug(`Polling queue '${this.queue.queueName}' with batch size ${this.config.batchSize}`); + const messages = await this.queue.readWithPoll( this.config.batchSize, this.config.visibilityTimeout, this.config.maxPollSeconds, this.config.pollIntervalMs ); + + this.logger.debug(`Received ${messages.length} messages from queue '${this.queue.queueName}'`); + return messages; } private isAborted(): boolean { diff --git a/pkgs/edge-worker/src/queue/createQueueWorker.ts b/pkgs/edge-worker/src/queue/createQueueWorker.ts index 0beb1802..a4e02c21 100644 --- a/pkgs/edge-worker/src/queue/createQueueWorker.ts +++ b/pkgs/edge-worker/src/queue/createQueueWorker.ts @@ -4,11 +4,12 @@ import { Queries } from '../core/Queries.js'; import { Queue } from './Queue.js'; import { ReadWithPollPoller } from './ReadWithPollPoller.js'; import type { Json } from '../core/types.js'; -import type { PgmqMessageRecord } from './types.js'; +import type { PgmqMessageRecord, MessageHandlerFn } from './types.js'; import { Worker } from '../core/Worker.js'; import postgres from 'postgres'; import { WorkerLifecycle } from '../core/WorkerLifecycle.js'; import { BatchProcessor } from '../core/BatchProcessor.js'; +import type { Logger } from '../platform/types.js'; /** * Configuration for the queue worker @@ -86,14 +87,20 @@ export type QueueWorkerConfig = { * * @param handler - The message handler function that processes each message from the queue * @param config - Configuration options for the worker + * @param createLogger - Function to create loggers for different components * @returns A configured Worker instance ready to be started */ export function createQueueWorker( - handler: (message: TPayload) => Promise | void, - config: QueueWorkerConfig + handler: MessageHandlerFn, + config: QueueWorkerConfig, + createLogger: (module: string) => Logger ): Worker { type QueueMessage = PgmqMessageRecord; + // Create component-specific loggers + const logger = createLogger('QueueWorker'); + logger.info(`Creating queue worker for ${config.queueName || 'tasks'}`); + const abortController = new AbortController(); const abortSignal = abortController.signal; @@ -105,10 +112,19 @@ export function createQueueWorker( prepare: false, }); - const queue = new Queue(sql, config.queueName || 'tasks'); + const queue = new Queue( + sql, + config.queueName || 'tasks', + createLogger('Queue') + ); + const queries = new Queries(sql); - const lifecycle = new WorkerLifecycle(queries, queue); + const lifecycle = new WorkerLifecycle( + queries, + queue, + createLogger('WorkerLifecycle') + ); const executorFactory = (record: QueueMessage, signal: AbortSignal) => { return new MessageExecutor( @@ -117,29 +133,38 @@ export function createQueueWorker( handler, signal, config.retryLimit || 5, - config.retryDelay || 3 + config.retryDelay || 3, + createLogger('MessageExecutor') ); }; - const poller = new ReadWithPollPoller(queue, abortSignal, { - batchSize: config.batchSize || config.maxConcurrent || 10, - maxPollSeconds: config.maxPollSeconds || 5, - pollIntervalMs: config.pollIntervalMs || 200, - visibilityTimeout: config.visibilityTimeout || 3, - }); + const poller = new ReadWithPollPoller( + queue, + abortSignal, + { + batchSize: config.batchSize || config.maxConcurrent || 10, + maxPollSeconds: config.maxPollSeconds || 5, + pollIntervalMs: config.pollIntervalMs || 200, + visibilityTimeout: config.visibilityTimeout || 3, + }, + createLogger('ReadWithPollPoller') + ); const executionController = new ExecutionController( executorFactory, abortSignal, { maxConcurrent: config.maxConcurrent || 10, - } + }, + createLogger('ExecutionController') ); + const batchProcessor = new BatchProcessor( executionController, poller, - abortSignal + abortSignal, + createLogger('BatchProcessor') ); - return new Worker(batchProcessor, lifecycle, sql); + return new Worker(batchProcessor, lifecycle, sql, createLogger('Worker')); } diff --git a/pkgs/edge-worker/src/queue/types.ts b/pkgs/edge-worker/src/queue/types.ts index 9eee78da..6c0d9674 100644 --- a/pkgs/edge-worker/src/queue/types.ts +++ b/pkgs/edge-worker/src/queue/types.ts @@ -12,3 +12,10 @@ export interface PgmqMessageRecord vt: string; message: TPayload; } + +/** + * User-provided handler function, called for each message in the queue + */ +export type MessageHandlerFn = ( + message: TPayload +) => Promise | void; diff --git a/pkgs/edge-worker/src/spawnNewEdgeFunction.ts b/pkgs/edge-worker/src/spawnNewEdgeFunction.ts deleted file mode 100644 index d9cae71d..00000000 --- a/pkgs/edge-worker/src/spawnNewEdgeFunction.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { getLogger } from './core/Logger.js'; - -const SUPABASE_URL = Deno.env.get('SUPABASE_URL') as string; -const SUPABASE_ANON_KEY = Deno.env.get('SUPABASE_ANON_KEY') as string; - -const logger = getLogger('spawnNewEdgeFunction'); - -export default async function spawnNewEdgeFunction( - functionName = 'edge-worker' -): Promise { - if (!functionName) { - throw new Error('functionName cannot be null or empty'); - } - - logger.debug('Spawning a new Edge Function...'); - - const response = await fetch(`${SUPABASE_URL}/functions/v1/${functionName}`, { - method: 'POST', - headers: { - Authorization: `Bearer ${SUPABASE_ANON_KEY}`, - 'Content-Type': 'application/json', - }, - }); - - logger.debug('Edge Function spawned successfully!'); - - if (!response.ok) { - throw new Error( - `Edge function returned non-OK status: ${response.status} ${response.statusText}` - ); - } -} diff --git a/pkgs/edge-worker/supabase/functions/cpu_intensive/deno.json b/pkgs/edge-worker/supabase/functions/cpu_intensive/deno.json new file mode 100644 index 00000000..af58fa13 --- /dev/null +++ b/pkgs/edge-worker/supabase/functions/cpu_intensive/deno.json @@ -0,0 +1,13 @@ +{ + "unstable": ["sloppy-imports"], + "imports": { + "@henrygd/queue": "jsr:@henrygd/queue@^1.0.7", + "@std/assert": "jsr:@std/assert@^0.224.0", + "@std/async": "jsr:@std/async@^0.224.0", + "@std/log": "jsr:@std/log@^0.224.13", + "@std/testing/mock": "jsr:@std/testing/mock@^0.224.0", + "postgres": "npm:postgres@3.4.5", + "@pgflow/core": "../../../../core/dist/index.js", + "@pgflow/dsl": "../../../../dsl/dist/index.js" + } +} diff --git a/pkgs/edge-worker/supabase/functions/cpu_intensive/deno.lock b/pkgs/edge-worker/supabase/functions/cpu_intensive/deno.lock new file mode 100644 index 00000000..658bd3b2 --- /dev/null +++ b/pkgs/edge-worker/supabase/functions/cpu_intensive/deno.lock @@ -0,0 +1,38 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@henrygd/queue@^1.0.7": "jsr:@henrygd/queue@1.0.7", + "jsr:@std/async@^0.224.0": "jsr:@std/async@0.224.2", + "jsr:@std/crypto": "jsr:@std/crypto@1.0.3", + "npm:postgres@3.4.5": "npm:postgres@3.4.5" + }, + "jsr": { + "@henrygd/queue@1.0.7": { + "integrity": "98cade132744bb420957c5413393f76eb8ba7261826f026c8a89a562b8fa2961" + }, + "@std/async@0.224.2": { + "integrity": "4d277d6e165df43d5e061ba0ef3edfddb8e8d558f5b920e3e6b1d2614b44d074" + }, + "@std/crypto@1.0.3": { + "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" + } + }, + "npm": { + "postgres@3.4.5": { + "integrity": "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==", + "dependencies": {} + } + } + }, + "remote": {}, + "workspace": { + "dependencies": [ + "jsr:@henrygd/queue@^1.0.7", + "jsr:@std/assert@^0.224.0", + "jsr:@std/async@^0.224.0", + "jsr:@std/log@^0.224.13", + "npm:postgres@3.4.5" + ] + } +} diff --git a/pkgs/edge-worker/supabase/functions/cpu_intensive/index.ts b/pkgs/edge-worker/supabase/functions/cpu_intensive/index.ts index fa133769..bb6a199d 100644 --- a/pkgs/edge-worker/supabase/functions/cpu_intensive/index.ts +++ b/pkgs/edge-worker/supabase/functions/cpu_intensive/index.ts @@ -1,4 +1,4 @@ -import { EdgeWorker } from '../../../dist/index.js'; +import { EdgeWorker } from '../_dist/index.js'; import { crypto } from 'jsr:@std/crypto'; import { sql } from '../utils.ts'; diff --git a/pkgs/edge-worker/supabase/functions/max_concurrency/deno.json b/pkgs/edge-worker/supabase/functions/max_concurrency/deno.json new file mode 100644 index 00000000..af58fa13 --- /dev/null +++ b/pkgs/edge-worker/supabase/functions/max_concurrency/deno.json @@ -0,0 +1,13 @@ +{ + "unstable": ["sloppy-imports"], + "imports": { + "@henrygd/queue": "jsr:@henrygd/queue@^1.0.7", + "@std/assert": "jsr:@std/assert@^0.224.0", + "@std/async": "jsr:@std/async@^0.224.0", + "@std/log": "jsr:@std/log@^0.224.13", + "@std/testing/mock": "jsr:@std/testing/mock@^0.224.0", + "postgres": "npm:postgres@3.4.5", + "@pgflow/core": "../../../../core/dist/index.js", + "@pgflow/dsl": "../../../../dsl/dist/index.js" + } +} diff --git a/pkgs/edge-worker/supabase/functions/max_concurrency/deno.lock b/pkgs/edge-worker/supabase/functions/max_concurrency/deno.lock new file mode 100644 index 00000000..fbcc9d69 --- /dev/null +++ b/pkgs/edge-worker/supabase/functions/max_concurrency/deno.lock @@ -0,0 +1,34 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@henrygd/queue@^1.0.7": "jsr:@henrygd/queue@1.0.7", + "jsr:@std/async@^0.224.0": "jsr:@std/async@0.224.2", + "npm:postgres@3.4.5": "npm:postgres@3.4.5" + }, + "jsr": { + "@henrygd/queue@1.0.7": { + "integrity": "98cade132744bb420957c5413393f76eb8ba7261826f026c8a89a562b8fa2961" + }, + "@std/async@0.224.2": { + "integrity": "4d277d6e165df43d5e061ba0ef3edfddb8e8d558f5b920e3e6b1d2614b44d074" + } + }, + "npm": { + "postgres@3.4.5": { + "integrity": "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==", + "dependencies": {} + } + } + }, + "remote": {}, + "workspace": { + "dependencies": [ + "jsr:@henrygd/queue@^1.0.7", + "jsr:@std/assert@^0.224.0", + "jsr:@std/async@^0.224.0", + "jsr:@std/log@^0.224.13", + "npm:postgres@3.4.5" + ] + } +} diff --git a/pkgs/edge-worker/supabase/functions/max_concurrency/index.ts b/pkgs/edge-worker/supabase/functions/max_concurrency/index.ts index 52729839..0a5cf9f9 100644 --- a/pkgs/edge-worker/supabase/functions/max_concurrency/index.ts +++ b/pkgs/edge-worker/supabase/functions/max_concurrency/index.ts @@ -1,4 +1,4 @@ -import { EdgeWorker } from '../../../dist/index.js'; +import { EdgeWorker } from '../_dist/index.js'; import { sleep, sql } from '../utils.ts'; async function incrementSeq() { diff --git a/pkgs/edge-worker/tests/e2e/_helpers.ts b/pkgs/edge-worker/tests/e2e/_helpers.ts index 51c894a2..251abe61 100644 --- a/pkgs/edge-worker/tests/e2e/_helpers.ts +++ b/pkgs/edge-worker/tests/e2e/_helpers.ts @@ -54,7 +54,7 @@ export async function sendBatch(count: number, queueName: string) { export async function seqLastValue( seqName = 'test_seq' ): Promise { - // Postgres sequences are initialized with a value of 1, + // Postgres sequences are startWorkerd with a value of 1, // but incrementing them for the first time does not increment the last_value, // only sets is_called to true const seqResult = await sql` diff --git a/pkgs/edge-worker/tests/fakes.ts b/pkgs/edge-worker/tests/fakes.ts new file mode 100644 index 00000000..1235c121 --- /dev/null +++ b/pkgs/edge-worker/tests/fakes.ts @@ -0,0 +1,13 @@ +import type { CreateLoggerFn, Logger } from '../src/platform/types.js'; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const noop = () => {}; + +export const fakeLogger: Logger = { + debug: noop, + info: noop, + warn: noop, + error: noop, +}; + +export const createFakeLogger: CreateLoggerFn = () => fakeLogger; diff --git a/pkgs/edge-worker/tests/integration/_helpers.ts b/pkgs/edge-worker/tests/integration/_helpers.ts index 734ad16b..393eb25f 100644 --- a/pkgs/edge-worker/tests/integration/_helpers.ts +++ b/pkgs/edge-worker/tests/integration/_helpers.ts @@ -4,6 +4,7 @@ import { type FlowWorkerConfig, } from '../../src/flow/createFlowWorker.ts'; import type { postgres } from '../sql.ts'; +import { createFakeLogger } from '../fakes.ts'; import { PgflowSqlClient } from '@pgflow/core'; export async function startFlow( @@ -32,7 +33,7 @@ export function startWorker< ...options, }; - const worker = createFlowWorker(flow, mergedOptions); + const worker = createFlowWorker(flow, mergedOptions, createFakeLogger); worker.startOnlyOnce({ edgeFunctionName: 'test_flow', diff --git a/pkgs/edge-worker/tests/integration/creating_queue.test.ts b/pkgs/edge-worker/tests/integration/creating_queue.test.ts index eeee2682..6053a790 100644 --- a/pkgs/edge-worker/tests/integration/creating_queue.test.ts +++ b/pkgs/edge-worker/tests/integration/creating_queue.test.ts @@ -1,32 +1,41 @@ -import { assertEquals } from "@std/assert"; +import { assertEquals } from '@std/assert'; import { createQueueWorker } from '../../src/queue/createQueueWorker.ts'; -import { withTransaction } from "../db.ts"; -import { delay } from "@std/async"; +import { withTransaction } from '../db.ts'; +import { createFakeLogger } from '../fakes.ts'; +import { delay } from '@std/async'; -Deno.test('creates queue when starting worker', withTransaction(async (sql) => { - const worker = createQueueWorker(console.log, { - sql, - maxPollSeconds: 1, - queueName: 'custom_queue' - }); +Deno.test( + 'creates queue when starting worker', + withTransaction(async (sql) => { + const worker = createQueueWorker( + console.log, + { + sql, + maxPollSeconds: 1, + queueName: 'custom_queue', + }, + createFakeLogger + ); - worker.startOnlyOnce({ - edgeFunctionName: 'test', - // random uuid - workerId: crypto.randomUUID(), - }); + worker.startOnlyOnce({ + edgeFunctionName: 'test', + // random uuid + workerId: crypto.randomUUID(), + }); - await delay(100); + await delay(100); - try { - const result: {queue_name: string}[] = await sql`select queue_name from pgmq.list_queues();`; + try { + const result: { queue_name: string }[] = + await sql`select queue_name from pgmq.list_queues();`; - assertEquals( - [...result], - [{ queue_name: 'custom_queue' }], - 'queue "custom_queue" was created' - ); - } finally { - await worker.stop(); - } -})); + assertEquals( + [...result], + [{ queue_name: 'custom_queue' }], + 'queue "custom_queue" was created' + ); + } finally { + await worker.stop(); + } + }) +); diff --git a/pkgs/edge-worker/tests/integration/maxConcurrent.test.ts b/pkgs/edge-worker/tests/integration/maxConcurrent.test.ts index 830900bd..c4cbc82b 100644 --- a/pkgs/edge-worker/tests/integration/maxConcurrent.test.ts +++ b/pkgs/edge-worker/tests/integration/maxConcurrent.test.ts @@ -1,6 +1,7 @@ import { assertEquals, assertGreaterOrEqual } from '@std/assert'; import { createQueueWorker } from '../../src/queue/createQueueWorker.ts'; import { withTransaction } from '../db.ts'; +import { createFakeLogger } from '../fakes.ts'; import { waitFor } from '../e2e/_helpers.ts'; import type { PgmqMessageRecord } from '../../src/queue/types.ts'; import { delay } from '@std/async'; @@ -16,13 +17,17 @@ async function sleepFor1s() { Deno.test( 'maxConcurrent option is respected', withTransaction(async (sql) => { - const worker = createQueueWorker(sleepFor1s, { - sql, - maxConcurrent: 1, - maxPollSeconds: 1, - visibilityTimeout: 5, - queueName: QUEUE_NAME, - }); + const worker = createQueueWorker( + sleepFor1s, + { + sql, + maxConcurrent: 1, + maxPollSeconds: 1, + visibilityTimeout: 5, + queueName: QUEUE_NAME, + }, + createFakeLogger + ); try { worker.startOnlyOnce({ diff --git a/pkgs/edge-worker/tests/integration/retries.test.ts b/pkgs/edge-worker/tests/integration/retries.test.ts index a21892f0..edfd905f 100644 --- a/pkgs/edge-worker/tests/integration/retries.test.ts +++ b/pkgs/edge-worker/tests/integration/retries.test.ts @@ -1,13 +1,14 @@ import { assertEquals, assertGreaterOrEqual } from '@std/assert'; import { createQueueWorker } from '../../src/queue/createQueueWorker.ts'; -import { withTransaction } from "../db.ts"; -import { log, waitFor } from "../e2e/_helpers.ts"; -import { getArchivedMessages, sendBatch } from "../helpers.ts"; +import { withTransaction } from '../db.ts'; +import { createFakeLogger } from '../fakes.ts'; +import { log, waitFor } from '../e2e/_helpers.ts'; +import { getArchivedMessages, sendBatch } from '../helpers.ts'; const workerConfig = { maxPollSeconds: 1, - retryDelay: 2, // seconds between retries - retryLimit: 2, // number of retries + retryDelay: 2, // seconds between retries + retryLimit: 2, // number of retries queueName: 'failing_always', } as const; @@ -28,51 +29,62 @@ function createFailingHandler(startTime: number) { * 1. Message processing takes at least RETRY_LIMIT * RETRY_DELAY seconds * 2. Message is read exactly RETRY_LIMIT + 1 times (initial + retries) */ -Deno.test('message retry mechanism works correctly', withTransaction(async (sql) => { - const startTime = Date.now(); - const worker = createQueueWorker(createFailingHandler(startTime), { - sql, - ...workerConfig - }); +Deno.test( + 'message retry mechanism works correctly', + withTransaction(async (sql) => { + const startTime = Date.now(); + const worker = createQueueWorker( + createFailingHandler(startTime), + { + sql, + ...workerConfig, + }, + createFakeLogger + ); - try { - // Start worker and send test message - worker.startOnlyOnce({ - edgeFunctionName: 'test', - workerId: crypto.randomUUID(), - }); - await sendBatch(1, workerConfig.queueName, sql); + try { + // Start worker and send test message + worker.startOnlyOnce({ + edgeFunctionName: 'test', + workerId: crypto.randomUUID(), + }); + await sendBatch(1, workerConfig.queueName, sql); - // Calculate expected processing time - const expectedMinimumMs = workerConfig.retryLimit * workerConfig.retryDelay * 1000; + // Calculate expected processing time + const expectedMinimumMs = + workerConfig.retryLimit * workerConfig.retryDelay * 1000; - // Wait for message to be archived - const [message] = await waitFor( - async () => { - const messages = await getArchivedMessages(sql, workerConfig.queueName); - return messages.length >= 1 && messages; - }, - { - timeoutMs: expectedMinimumMs + 500, - } - ); + // Wait for message to be archived + const [message] = await waitFor( + async () => { + const messages = await getArchivedMessages( + sql, + workerConfig.queueName + ); + return messages.length >= 1 && messages; + }, + { + timeoutMs: expectedMinimumMs + 500, + } + ); - // Verify timing - const totalMs = Date.now() - startTime; - assertGreaterOrEqual( - totalMs, - expectedMinimumMs, - `Processing time ${totalMs}ms was shorter than minimum ${expectedMinimumMs}ms` - ); + // Verify timing + const totalMs = Date.now() - startTime; + assertGreaterOrEqual( + totalMs, + expectedMinimumMs, + `Processing time ${totalMs}ms was shorter than minimum ${expectedMinimumMs}ms` + ); - // Verify retry count - const expectedReads = workerConfig.retryLimit + 1; - assertEquals( - message.read_ct, - expectedReads, - `Message should be read ${expectedReads} times (1 initial + ${workerConfig.retryLimit} retries)` - ); - } finally { - await worker.stop(); - } -})); + // Verify retry count + const expectedReads = workerConfig.retryLimit + 1; + assertEquals( + message.read_ct, + expectedReads, + `Message should be read ${expectedReads} times (1 initial + ${workerConfig.retryLimit} retries)` + ); + } finally { + await worker.stop(); + } + }) +); diff --git a/pkgs/edge-worker/tests/integration/starting_worker.test.ts b/pkgs/edge-worker/tests/integration/starting_worker.test.ts index 268bff35..3cee5fc2 100644 --- a/pkgs/edge-worker/tests/integration/starting_worker.test.ts +++ b/pkgs/edge-worker/tests/integration/starting_worker.test.ts @@ -1,35 +1,46 @@ import { createQueueWorker } from '../../src/queue/createQueueWorker.ts'; -import { withTransaction } from "../db.ts"; -import { delay } from "@std/async"; +import { withTransaction } from '../db.ts'; +import { createFakeLogger } from '../fakes.ts'; +import { delay } from '@std/async'; -Deno.test('Starting worker', withTransaction(async (sql) => { - const worker = createQueueWorker(console.log, { - sql, - maxPollSeconds: 1 - }); +Deno.test( + 'Starting worker', + withTransaction(async (sql) => { + const worker = createQueueWorker( + console.log, + { + sql, + maxPollSeconds: 1, + }, + createFakeLogger + ); - worker.startOnlyOnce({ - edgeFunctionName: 'test', - // random uuid - workerId: crypto.randomUUID(), - }); + worker.startOnlyOnce({ + edgeFunctionName: 'test', + // random uuid + workerId: crypto.randomUUID(), + }); - await delay(100); + await delay(100); - try { - const workers = await sql`select * from edge_worker.workers`; + try { + const workers = await sql`select * from edge_worker.workers`; - console.log(workers); - } finally { - await worker.stop(); - } -})); + console.log(workers); + } finally { + await worker.stop(); + } + }) +); -Deno.test('check pgmq version', withTransaction(async (sql) => { - const result = await sql` +Deno.test( + 'check pgmq version', + withTransaction(async (sql) => { + const result = await sql` SELECT extversion FROM pg_extension WHERE extname = 'pgmq' `; - console.log('pgmq version:', result); -})); + console.log('pgmq version:', result); + }) +); diff --git a/pkgs/edge-worker/tests/unit/WorkerState.test.ts b/pkgs/edge-worker/tests/unit/WorkerState.test.ts index 05816af2..d921b13a 100644 --- a/pkgs/edge-worker/tests/unit/WorkerState.test.ts +++ b/pkgs/edge-worker/tests/unit/WorkerState.test.ts @@ -1,14 +1,23 @@ import { assertEquals, assertThrows } from '@std/assert'; -import { WorkerState, States, TransitionError } from '../../src/core/WorkerState.ts'; +import { + WorkerState, + States, + TransitionError, +} from '../../src/core/WorkerState.ts'; +import { createLoggingFactory } from '../../src/platform/logging.ts'; + +const loggingFactory = createLoggingFactory(); +loggingFactory.setLogLevel('info'); +const logger = loggingFactory.createLogger('WorkerState'); Deno.test('WorkerState - initial state should be Created', () => { - const state = new WorkerState(); + const state = new WorkerState(logger); assertEquals(state.current, States.Created); assertEquals(state.isCreated, true); }); Deno.test('WorkerState - valid state transitions', () => { - const state = new WorkerState(); + const state = new WorkerState(logger); // Created -> Starting state.transitionTo(States.Starting); @@ -31,7 +40,7 @@ Deno.test('WorkerState - valid state transitions', () => { }); Deno.test('WorkerState - invalid state transitions should throw', () => { - const state = new WorkerState(); + const state = new WorkerState(logger); // Cannot transition from Created to Running assertThrows( @@ -53,7 +62,7 @@ Deno.test('WorkerState - invalid state transitions should throw', () => { }); Deno.test('WorkerState - transitioning to same state should be no-op', () => { - const state = new WorkerState(); + const state = new WorkerState(logger); // Transition to Starting first state.transitionTo(States.Starting); @@ -65,7 +74,7 @@ Deno.test('WorkerState - transitioning to same state should be no-op', () => { }); Deno.test('WorkerState - state getters', () => { - const state = new WorkerState(); + const state = new WorkerState(logger); assertEquals(state.isCreated, true); assertEquals(state.isStarting, false); diff --git a/pkgs/edge-worker/tsconfig.json b/pkgs/edge-worker/tsconfig.json new file mode 100644 index 00000000..374c8ba1 --- /dev/null +++ b/pkgs/edge-worker/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "typeRoots": ["./node_modules/@types", "."], + "types": [ + "node", + "deno/ext/lib.deno.fetch.d.ts", + "deno/ext/lib.deno.window.d.ts", + "src/platform/deno-types.d.ts" + ] + }, + "references": [ + { + "path": "../dsl" + }, + { + "path": "../core" + } + ] +} diff --git a/pkgs/edge-worker/tsconfig.lib.json b/pkgs/edge-worker/tsconfig.lib.json index 85ccc0a2..07928972 100644 --- a/pkgs/edge-worker/tsconfig.lib.json +++ b/pkgs/edge-worker/tsconfig.lib.json @@ -1,10 +1,5 @@ { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo" - }, + "extends": "./tsconfig.json", "include": ["src/**/*.ts"], "references": [ { diff --git a/pkgs/example-flows/eslint.config.cjs b/pkgs/example-flows/eslint.config.cjs index aa7aea54..87adc5de 100644 --- a/pkgs/example-flows/eslint.config.cjs +++ b/pkgs/example-flows/eslint.config.cjs @@ -1,22 +1,3 @@ const baseConfig = require('../../eslint.config.cjs'); -module.exports = [ - ...baseConfig, - { - files: ['**/*.json'], - rules: { - '@nx/dependency-checks': [ - 'error', - { - ignoredFiles: [ - '{projectRoot}/eslint.config.{js,cjs,mjs}', - '{projectRoot}/vite.config.{js,ts,mjs,mts}', - ], - }, - ], - }, - languageOptions: { - parser: require('jsonc-eslint-parser'), - }, - }, -]; +module.exports = [...baseConfig]; diff --git a/pkgs/example-flows/project.json b/pkgs/example-flows/project.json index 0eac4918..d2fd7e79 100644 --- a/pkgs/example-flows/project.json +++ b/pkgs/example-flows/project.json @@ -13,7 +13,8 @@ "outputPath": "pkgs/example-flows/dist", "main": "pkgs/example-flows/src/index.ts", "tsConfig": "pkgs/example-flows/tsconfig.lib.json", - "assets": ["pkgs/example-flows/*.md"] + "assets": ["pkgs/example-flows/*.md"], + "rootDir": "pkgs/example-flows/src" } } } diff --git a/pkgs/example-flows/src/index.ts b/pkgs/example-flows/src/index.ts index 786130f4..95b5e1f6 100644 --- a/pkgs/example-flows/src/index.ts +++ b/pkgs/example-flows/src/index.ts @@ -1 +1 @@ -export * from './example-flow.ts'; +export * from './example-flow.js'; diff --git a/pkgs/example-flows/tsconfig.lib.json b/pkgs/example-flows/tsconfig.lib.json index ea0b220c..19a7e3df 100644 --- a/pkgs/example-flows/tsconfig.lib.json +++ b/pkgs/example-flows/tsconfig.lib.json @@ -3,7 +3,8 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo" + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "types": ["node"] }, "include": ["src/**/*.ts"], "references": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbc2b453..ff0406e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ importers: version: 20.3.0(@swc-node/register@1.9.2)(@swc/core@1.5.29)(@types/node@18.16.20)(eslint@9.17.0)(nx@20.3.0) '@nx/eslint-plugin': specifier: 20.3.0 - version: 20.3.0(@swc-node/register@1.9.2)(@swc/core@1.5.29)(@types/node@18.16.20)(@typescript-eslint/parser@8.21.0)(eslint-config-prettier@9.1.0)(eslint@9.17.0)(nx@20.3.0)(typescript@5.6.3) + version: 20.3.0(@swc-node/register@1.9.2)(@swc/core@1.5.29)(@types/node@18.16.20)(@typescript-eslint/parser@8.29.1)(eslint-config-prettier@9.1.0)(eslint@9.17.0)(nx@20.3.0)(typescript@5.6.3) '@nx/js': specifier: 20.3.0 version: 20.3.0(@swc-node/register@1.9.2)(@swc/core@1.5.29)(@types/node@18.16.20)(nx@20.3.0)(typescript@5.6.3) @@ -47,7 +47,7 @@ importers: version: 20.3.0(@swc-node/register@1.9.2)(@swc/core@1.5.29)(@types/node@18.16.20)(nx@20.3.0)(typescript@5.6.3) '@swc-node/register': specifier: ~1.9.1 - version: 1.9.2(@swc/core@1.5.29)(@swc/types@0.1.17)(typescript@5.6.3) + version: 1.9.2(@swc/core@1.5.29)(@swc/types@0.1.21)(typescript@5.6.3) '@swc/cli': specifier: ~0.3.12 version: 0.3.14(@swc/core@1.5.29) @@ -87,9 +87,6 @@ importers: prettier: specifier: ^2.6.2 version: 2.8.8 - supabase: - specifier: ^2.6.8 - version: 2.6.8 tslib: specifier: ^2.3.0 version: 2.8.1 @@ -153,12 +150,19 @@ importers: '@pgflow/dsl': specifier: workspace:* version: link:../dsl/dist - pino: - specifier: ^9.6.0 - version: 9.6.0 postgres: specifier: 3.4.5 version: 3.4.5 + devDependencies: + '@types/deno': + specifier: npm:@teidesu/deno-types@1.45.2 + version: /@teidesu/deno-types@1.45.2 + '@types/node': + specifier: ~18.16.9 + version: 18.16.20 + supabase: + specifier: 2.21.1 + version: 2.21.1 pkgs/edge-worker/dist: dependencies: @@ -171,12 +175,16 @@ importers: '@pgflow/dsl': specifier: workspace:* version: link:../../dsl/dist - pino: - specifier: ^9.6.0 - version: 9.6.0 postgres: specifier: 3.4.5 version: 3.4.5 + devDependencies: + '@types/deno': + specifier: npm:@teidesu/deno-types@1.45.2 + version: /@teidesu/deno-types@1.45.2 + supabase: + specifier: 2.21.1 + version: 2.21.1 pkgs/example-flows: dependencies: @@ -1930,6 +1938,14 @@ packages: dependencies: tslib: 2.8.1 + /@emnapi/runtime@1.4.0: + resolution: {integrity: sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==} + requiresBuild: true + dependencies: + tslib: 2.8.1 + dev: false + optional: true + /@emnapi/wasi-threads@1.0.1: resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} dependencies: @@ -2864,7 +2880,7 @@ packages: cpu: [wasm32] requiresBuild: true dependencies: - '@emnapi/runtime': 1.3.1 + '@emnapi/runtime': 1.4.0 dev: false optional: true @@ -3552,7 +3568,7 @@ packages: - verdaccio dev: true - /@nx/eslint-plugin@20.3.0(@swc-node/register@1.9.2)(@swc/core@1.5.29)(@types/node@18.16.20)(@typescript-eslint/parser@8.21.0)(eslint-config-prettier@9.1.0)(eslint@9.17.0)(nx@20.3.0)(typescript@5.6.3): + /@nx/eslint-plugin@20.3.0(@swc-node/register@1.9.2)(@swc/core@1.5.29)(@types/node@18.16.20)(@typescript-eslint/parser@8.29.1)(eslint-config-prettier@9.1.0)(eslint@9.17.0)(nx@20.3.0)(typescript@5.6.3): resolution: {integrity: sha512-U9DvbR7quyfnWk8ZCJlwKbIInZ5gd4be93X5gii966vM81n3lbWLc7y4avU4r3732X2pnpFGJqBgP8ov8JE/fw==} peerDependencies: '@typescript-eslint/parser': ^6.13.2 || ^7.0.0 || ^8.0.0 @@ -3563,7 +3579,7 @@ packages: dependencies: '@nx/devkit': 20.3.0(nx@20.3.0) '@nx/js': 20.3.0(@swc-node/register@1.9.2)(@swc/core@1.5.29)(@types/node@18.16.20)(nx@20.3.0)(typescript@5.6.3) - '@typescript-eslint/parser': 8.21.0(eslint@9.17.0)(typescript@5.6.3) + '@typescript-eslint/parser': 8.29.1(eslint@9.17.0)(typescript@5.6.3) '@typescript-eslint/type-utils': 8.19.1(eslint@9.17.0)(typescript@5.6.3) '@typescript-eslint/utils': 8.19.1(eslint@9.17.0)(typescript@5.6.3) chalk: 4.1.2 @@ -4382,7 +4398,7 @@ packages: '@sinonjs/commons': 3.0.1 dev: true - /@swc-node/core@1.13.3(@swc/core@1.5.29)(@swc/types@0.1.17): + /@swc-node/core@1.13.3(@swc/core@1.5.29)(@swc/types@0.1.21): resolution: {integrity: sha512-OGsvXIid2Go21kiNqeTIn79jcaX4l0G93X2rAnas4LFoDyA9wAwVK7xZdm+QsKoMn5Mus2yFLCc4OtX2dD/PWA==} engines: {node: '>= 10'} peerDependencies: @@ -4390,15 +4406,15 @@ packages: '@swc/types': '>= 0.1' dependencies: '@swc/core': 1.5.29(@swc/helpers@0.5.15) - '@swc/types': 0.1.17 + '@swc/types': 0.1.21 - /@swc-node/register@1.9.2(@swc/core@1.5.29)(@swc/types@0.1.17)(typescript@5.6.3): + /@swc-node/register@1.9.2(@swc/core@1.5.29)(@swc/types@0.1.21)(typescript@5.6.3): resolution: {integrity: sha512-BBjg0QNuEEmJSoU/++JOXhrjWdu3PTyYeJWsvchsI0Aqtj8ICkz/DqlwtXbmZVZ5vuDPpTfFlwDBZe81zgShMA==} peerDependencies: '@swc/core': '>= 1.4.13' typescript: '>= 4.3' dependencies: - '@swc-node/core': 1.13.3(@swc/core@1.5.29)(@swc/types@0.1.17) + '@swc-node/core': 1.13.3(@swc/core@1.5.29)(@swc/types@0.1.21) '@swc-node/sourcemap-support': 0.5.1 '@swc/core': 1.5.29(@swc/helpers@0.5.15) colorette: 2.0.20 @@ -4557,6 +4573,11 @@ packages: dependencies: '@swc/counter': 0.1.3 + /@swc/types@0.1.21: + resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==} + dependencies: + '@swc/counter': 0.1.3 + /@szmarczak/http-timer@4.0.6: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -4564,6 +4585,10 @@ packages: defer-to-connect: 2.0.1 dev: true + /@teidesu/deno-types@1.45.2: + resolution: {integrity: sha512-ELSqIZoR+nsH1t/Qb5WypRBVdW46jA8lQrMUXQo8Un0MwlYlVnwyfQOXQdMUepKvmFJfOJfPWgNDXn7+SOzpKg==} + dev: true + /@tokenizer/token@0.3.0: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} dev: true @@ -4832,17 +4857,17 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@8.21.0(eslint@9.17.0)(typescript@5.6.3): - resolution: {integrity: sha512-Wy+/sdEH9kI3w9civgACwabHbKl+qIOu0uFZ9IMKzX3Jpv9og0ZBJrZExGrPpFAY7rWsXuxs5e7CPPP17A4eYA==} + /@typescript-eslint/parser@8.29.1(eslint@9.17.0)(typescript@5.6.3): + resolution: {integrity: sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' dependencies: - '@typescript-eslint/scope-manager': 8.21.0 - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/scope-manager': 8.29.1 + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/typescript-estree': 8.29.1(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.29.1 debug: 4.4.0 eslint: 9.17.0 typescript: 5.6.3 @@ -4858,12 +4883,12 @@ packages: '@typescript-eslint/visitor-keys': 8.19.1 dev: true - /@typescript-eslint/scope-manager@8.21.0: - resolution: {integrity: sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==} + /@typescript-eslint/scope-manager@8.29.1: + resolution: {integrity: sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/visitor-keys': 8.29.1 dev: true /@typescript-eslint/type-utils@8.19.1(eslint@9.17.0)(typescript@5.6.3): @@ -4888,8 +4913,8 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@typescript-eslint/types@8.21.0: - resolution: {integrity: sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==} + /@typescript-eslint/types@8.29.1: + resolution: {integrity: sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true @@ -4912,20 +4937,20 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree@8.21.0(typescript@5.6.3): - resolution: {integrity: sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==} + /@typescript-eslint/typescript-estree@8.29.1(typescript@5.6.3): + resolution: {integrity: sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' dependencies: - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/visitor-keys': 8.29.1 debug: 4.4.0 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 2.0.0(typescript@5.6.3) + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -4956,11 +4981,11 @@ packages: eslint-visitor-keys: 4.2.0 dev: true - /@typescript-eslint/visitor-keys@8.21.0: - resolution: {integrity: sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==} + /@typescript-eslint/visitor-keys@8.29.1: + resolution: {integrity: sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/types': 8.29.1 eslint-visitor-keys: 4.2.0 dev: true @@ -5510,11 +5535,6 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - /atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - dev: false - /axios@1.7.9: resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} dependencies: @@ -5691,16 +5711,19 @@ packages: dev: false optional: true - /bare-fs@4.0.1: - resolution: {integrity: sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==} - engines: {bare: '>=1.7.0'} + /bare-fs@4.1.2: + resolution: {integrity: sha512-8wSeOia5B7LwD4+h465y73KOdj5QHsbbuoUfPBi+pXgFJIPuG7SsiOdJuijWMyfid49eD+WivpfY7KT8gbAzBA==} + engines: {bare: '>=1.16.0'} requiresBuild: true + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true dependencies: bare-events: 2.5.4 bare-path: 3.0.0 bare-stream: 2.6.4(bare-events@2.5.4) - transitivePeerDependencies: - - bare-buffer dev: false optional: true @@ -7063,11 +7086,6 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fast-redact@3.5.0: - resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} - engines: {node: '>=6'} - dev: false - /fast-uri@3.0.6: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} dev: false @@ -9708,7 +9726,7 @@ packages: optional: true dependencies: '@nrwl/tao': 17.3.2(@swc-node/register@1.9.2)(@swc/core@1.5.29) - '@swc-node/register': 1.9.2(@swc/core@1.5.29)(@swc/types@0.1.17)(typescript@5.6.3) + '@swc-node/register': 1.9.2(@swc/core@1.5.29)(@swc/types@0.1.21)(typescript@5.6.3) '@swc/core': 1.5.29(@swc/helpers@0.5.15) '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.0-rc.46 @@ -9772,7 +9790,7 @@ packages: optional: true dependencies: '@napi-rs/wasm-runtime': 0.2.4 - '@swc-node/register': 1.9.2(@swc/core@1.5.29)(@swc/types@0.1.17)(typescript@5.6.3) + '@swc-node/register': 1.9.2(@swc/core@1.5.29)(@swc/types@0.1.21)(typescript@5.6.3) '@swc/core': 1.5.29(@swc/helpers@0.5.15) '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.2 @@ -9838,11 +9856,6 @@ packages: resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} dev: false - /on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - dev: false - /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -10143,33 +10156,6 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - /pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - dependencies: - split2: 4.2.0 - dev: false - - /pino-std-serializers@7.0.0: - resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - dev: false - - /pino@9.6.0: - resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==} - hasBin: true - dependencies: - atomic-sleep: 1.0.0 - fast-redact: 3.5.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.0.0 - process-warning: 4.0.1 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.0 - thread-stream: 3.1.0 - dev: false - /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} @@ -10319,10 +10305,6 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} dev: true - /process-warning@4.0.1: - resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} - dev: false - /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -10385,10 +10367,6 @@ packages: resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} dev: false - /quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - dev: false - /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -10453,11 +10431,6 @@ packages: engines: {node: '>= 14.18.0'} dev: false - /real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - dev: false - /recma-build-jsx@1.0.0: resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} dependencies: @@ -10861,11 +10834,6 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - /safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - dev: false - /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true @@ -10925,6 +10893,12 @@ packages: engines: {node: '>=10'} hasBin: true + /semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + dev: true + /sharp@0.32.6: resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} engines: {node: '>=14.15.0'} @@ -11109,12 +11083,6 @@ packages: engines: {node: '>=8'} dev: true - /sonic-boom@4.2.0: - resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} - dependencies: - atomic-sleep: 1.0.0 - dev: false - /sort-keys-length@1.0.1: resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} engines: {node: '>=0.10.0'} @@ -11172,11 +11140,6 @@ packages: signal-exit: 4.1.0 dev: true - /split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - dev: false - /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -11398,6 +11361,20 @@ packages: dependencies: s.color: 0.0.15 + /supabase@2.21.1: + resolution: {integrity: sha512-xef5mK2vrs/ApaHMOCeL/0rooq2M8xdV632/3VFY2gaoQqYiSGQlh1Yd/Yqa1TPUoPcsszuK6YsjkOdGs+e1iQ==} + engines: {npm: '>=8'} + hasBin: true + requiresBuild: true + dependencies: + bin-links: 5.0.0 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + tar: 7.4.3 + transitivePeerDependencies: + - supports-color + dev: true + /supabase@2.6.8: resolution: {integrity: sha512-rgz9DVLso6LQ9VLKYXPDkvKgyFCYi6os1+3QS439yKkq4etbYn3KsedFBen0m4lgrHylBNL6W2qOB4+J/kgOLQ==} engines: {npm: '>=8'} @@ -11449,7 +11426,7 @@ packages: pump: 3.0.2 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 4.0.1 + bare-fs: 4.1.2 bare-path: 3.0.0 transitivePeerDependencies: - bare-buffer @@ -11505,12 +11482,6 @@ packages: b4a: 1.6.7 dev: false - /thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - dependencies: - real-require: 0.2.0 - dev: false - /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true @@ -11620,6 +11591,15 @@ packages: typescript: 5.6.3 dev: true + /ts-api-utils@2.1.0(typescript@5.6.3): + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + dependencies: + typescript: 5.6.3 + dev: true + /ts-node@10.9.1(@swc/core@1.5.29)(@types/node@18.16.20)(typescript@5.6.3): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true diff --git a/tsconfig.base.json b/tsconfig.base.json index 350e2341..12bf61a8 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,7 +31,6 @@ "sourceMap": false, "strict": true, "target": "es2022", - "types": ["node"], "verbatimModuleSyntax": false, "baseUrl": ".", "paths": { diff --git a/tsconfig.json b/tsconfig.json index 67c2bd3f..c9de4f0b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,9 @@ }, { "path": "./pkgs/example-flows" + }, + { + "path": "./pkgs/edge-worker" } ] }