diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..9f13513 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @rtasalem \ No newline at end of file diff --git a/.github/workflows/npm-publish-alpha.yaml b/.github/workflows/publish-alpha.yaml similarity index 89% rename from .github/workflows/npm-publish-alpha.yaml rename to .github/workflows/publish-alpha.yaml index 12e92b2..ccd647c 100644 --- a/.github/workflows/npm-publish-alpha.yaml +++ b/.github/workflows/publish-alpha.yaml @@ -1,4 +1,4 @@ -name: Busgres NPM Publish Alpha Version +name: NPM Publish Alpha Version on: push: @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [20.x] + node-version: [22.x] steps: - uses: actions/checkout@v4 @@ -35,4 +35,4 @@ jobs: run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_ACCESS_TOKEN }}" > ~/.npmrc - name: Publish to NPM - run: npm publish --tag alpha + run: npm publish --tag alpha \ No newline at end of file diff --git a/.github/workflows/npm-publish-stable.yaml b/.github/workflows/publish-stable.yaml similarity index 90% rename from .github/workflows/npm-publish-stable.yaml rename to .github/workflows/publish-stable.yaml index 588e331..29bf896 100644 --- a/.github/workflows/npm-publish-stable.yaml +++ b/.github/workflows/publish-stable.yaml @@ -1,11 +1,10 @@ -name: Busgres NPM Publish Stable Version +name: NPM Publish Stable Version on: - push: - branches: - - main pull_request: - branches: + types: + - closed + branches: - main jobs: diff --git a/.gitignore b/.gitignore index 8b63f7b..e922aec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,241 +1,6 @@ -# Created by https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,macos,windows,linux -# Edit at https://www.toptal.com/developers/gitignore?templates=node,visualstudiocode,macos,windows,linux - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -# General .DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### macOS Patch ### -# iCloud generated files -*.icloud - -### Node ### -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul coverage *.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -### Node Patch ### -# Serverless Webpack directories -.webpack/ - -# Optional stylelint cache - -# SvelteKit build / generate output -.svelte-kit - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Other test-output -test -test/test.js - -# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,macos,windows,linux \ No newline at end of file +.vscode/ \ No newline at end of file diff --git a/CONTRIBUTORS-GUIDE.md b/CONTRIBUTORS-GUIDE.md deleted file mode 100644 index 21d5a7b..0000000 --- a/CONTRIBUTORS-GUIDE.md +++ /dev/null @@ -1,17 +0,0 @@ -# Contributor's Guide -This guide is for any developer who wants to contribute to the Busgres Node.js package. -## Reach Out -If you have any ideas for improvements or new features, reach out. Any and all ideas are welcome to be discussed. -## Branch Naming -Branch naming is largely flexible. The only requirement is that it is relevent to the work being completed on the branch. Each branch must include one of the following prefixes: -- `feature/`: Implementation of new features. -- `bugfix/`: Repairing bugs in the codebase. -- `refactor/`: Refactoring/cleaning up the codebase. -- `docs/`: Creating or updating existing documentation. -- `chore/`: Any small miscellaneous tasks. - -Once merged into the main branch, your working branch must be deleted. -## GitHub Workflows -Two GitHub Actions have been configured to automate publishing new versions (stable and alpha) to the NPM registry: -- [npm-publish-alpha.yaml](https://github.com/rtasalem/busgres/blob/main/.github/workflows/npm-publish-alpha.yaml) -- [npm-publish-stable.yaml](https://github.com/rtasalem/busgres/blob/main/.github/workflows/npm-publish-stable.yaml) diff --git a/LICENSE b/LICENSE index 71a72cf..b114b3a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Rana Salem +Copyright (c) 2025 Rana Salem Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 291bcae..0568394 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Busgres +Service BUS + PostGRES = Busgres -![A sarcastic description of the Busgres NPM module](package-definition.png) - -[Busgres](https://www.npmjs.com/package/busgres) is a Node.js package that will receieve a message from an Azure Service Bus queue or topic and save it into a PostgreSQL database. It utilises the [`@azure/service-bus`](https://www.npmjs.com/package/@azure/service-bus) package for Service Bus integration and the [`pg` (node-postgres)](https://www.npmjs.com/package/pg) package for PostgreSQL connectivity. +[Busgres](https://www.npmjs.com/package/busgres) is a Node.js package that will receieve a message from an Azure Service Bus queue or topic and save it into a PostgreSQL database. It abstracts the [`@azure/service-bus`](https://www.npmjs.com/package/@azure/service-bus) the [`pg` (node-postgres)](https://www.npmjs.com/package/pg) package for Service Bus and Postgres integration. ## Installation diff --git a/app/busgres-client.js b/app/busgres-client.js index 1c1d181..54960e5 100644 --- a/app/busgres-client.js +++ b/app/busgres-client.js @@ -1,91 +1,48 @@ -const { ServiceBusClient } = require('@azure/service-bus') -const { Client } = require('pg') - -class BusgresClient { - constructor (sbConnectionString, sbEntityName, sbEntityType, sbEntitySubscription, pgClient) { - this.sbConnectionString = sbConnectionString - this.sbEntityName = sbEntityName - this.sbEntityType = sbEntityType - this.sbEntitySubscription = sbEntitySubscription || null - this.pgClient = new Client(pgClient) +import { ServiceBusHandler } from './service-bus-handler.js' +import { PostgresHandler } from './postgres-handler.js' +import { MessagePersister } from './message-persister.js' + +export class BusgresClient { + constructor ({ serviceBus, postgres }) { + const { connectionString, entity, entityType, subscription } = serviceBus + + this.serviceBusHandler = new ServiceBusHandler( + connectionString, + entity, + entityType, + subscription + ) + + this.postgresHandler = new PostgresHandler(postgres) + this.messagePersister = new MessagePersister(this.postgresHandler) + this.receiver = null } - async connect () { + async start (table, columnNames) { try { - await this.pgClient.connect() + await this.postgresHandler.connect() + this.receiver = this.serviceBusHandler.getReceiver() + + this.receiver.subscribe({ + processMessage: async (message) => { + await this.messagePersister.save(table, columnNames, message.body) + await this.receiver.completeMessage(message) + }, + processError: async (error) => { + console.error(`Error receiving message from Service Bus: ${error.message}`) + } + }) } catch (error) { - console.error('Error connecting to the database:', error) + console.error(`Error starting Busgres connection: ${error.message}`) } } - async saveMessage (tableName, columnNames, message) { + async stop () { try { - const messageContent = message.body - const columns = columnNames - .map((column, index) => `$${index + 1}`) - .join(', ') - const query = `INSERT INTO ${tableName} (${columnNames.join( - ', ' - )}) VALUES (${columns})` - - const values = columnNames.map((column) => messageContent[column]) - - await this.pgClient.query(query, values) - console.log( - 'The following message has been saved to the database:', - messageContent - ) + await this.postgresHandler.disconnect() + await this.serviceBusHandler.disconnect() } catch (error) { - console.error('Error saving message to the database:', error) - } - } - - async receiveMessage (tableName, columnNames) { - this.sbClient = new ServiceBusClient(this.sbConnectionString) - this.receiver = this.sbClient.createReceiver(this.sbEntity) - - if (this.sbEntityType === 'queue') { - this.receiver = this.sbClient.createReceiver(this.sbEntityName) - } else if (this.sbEntityType === 'topic' && this.sbEntitySubscription) { - this.receiver = this.sbClient.createReceiver( - this.sbEntityName, - this.sbEntitySubscription - ) - } else { - throw new Error( - 'Invalid entity type (must be "queue" or "topic" OR missing subscription name for topic' - ) + console.error(`Error stopping Busgres connection: ${error.message}`) } - - this.receiver.subscribe({ - processMessage: async (message) => { - console.log( - 'The following message was received from Service Bus:', - message.body - ) - await this.saveMessage(tableName, columnNames, message) - await this.receiver.completeMessage(message) - }, - processError: async (error) => { - console.error( - 'Error occurred while receiving message from Service Bus:', - error - ) - } - }) } - - async disconnect () { - try { - await this.pgClient.end() - await this.receiver.close() - await this.sbClient.close() - } catch (error) { - console.error('Error disconnecting:', error) - } - } -} - -module.exports = { - BusgresClient } diff --git a/app/index.js b/app/index.js deleted file mode 100644 index 7c4cb67..0000000 --- a/app/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const { BusgresClient } = require('./busgres-client') - -module.exports = { - BusgresClient -} diff --git a/app/message-persister.js b/app/message-persister.js new file mode 100644 index 0000000..a1c0cf3 --- /dev/null +++ b/app/message-persister.js @@ -0,0 +1,20 @@ +export class MessagePersister { + constructor (postgresHandler) { + this.database = postgresHandler + } + + async save (table, columnNames, message) { + try { + const columns = columnNames + .map((column, index) => `$${index + 1}`) + .join(', ') + + const query = `INSERT INTO ${table} (${columnNames.join(', ')}) VALUES (${columns})` + const values = columnNames.map((column) => message[column]) + + await this.database.query(query, values) + } catch (error) { + console.error(`Error persisting message to database: ${error.message}`) + } + } +} diff --git a/app/postgres-handler.js b/app/postgres-handler.js new file mode 100644 index 0000000..61d67dc --- /dev/null +++ b/app/postgres-handler.js @@ -0,0 +1,28 @@ +import pg from 'pg' +const { Client } = pg + +export class PostgresHandler { + constructor (config) { + this.client = new Client(config) + } + + async connect () { + try { + await this.client.connect() + } catch (error) { + console.error(`Error connecting to Postgres: ${error.message}`) + } + } + + async disconnect () { + try { + await this.client.end() + } catch (error) { + console.error(`Error disconnecting from Postgres: ${error.message}`) + } + } + + async query (query, values) { + return this.client.query(query, values) + } +} diff --git a/app/service-bus-handler.js b/app/service-bus-handler.js new file mode 100644 index 0000000..2a595df --- /dev/null +++ b/app/service-bus-handler.js @@ -0,0 +1,32 @@ +import { ServiceBusClient } from '@azure/service-bus' + +export class ServiceBusHandler { + constructor (connectionString, entity, entityType, subscription) { + this.client = new ServiceBusClient(connectionString) + this.entity = entity + this.entityType = entityType + this.subscription = subscription + } + + getReceiver () { + if (this.entityType === 'queue') { + return this.client.createReceiver(this.entity) + } else if (this.entityType === 'topic') { + if (!this.subscription) { + throw new Error('Subscription is missing') + } + + return this.client.createReceiver(this.entity, this.subscription) + } else { + console.error('Entity type must be "queue" or "topic"') + } + } + + async disconnect () { + try { + await this.client.close() + } catch (error) { + console.error(`Error closing Service Bus connection: ${error.message}`) + } + } +} diff --git a/index.js b/index.js index b981a07..f7b53bc 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ -const { BusgresClient } = require('./app') +import { BusgresClient } from './app/busgres-client.js' -module.exports = { +export { BusgresClient -} +} \ No newline at end of file diff --git a/package-definition.png b/package-definition.png deleted file mode 100644 index 50aa19f..0000000 Binary files a/package-definition.png and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 6daea3b..02ef21a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "busgres", - "version": "3.0.12", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "busgres", - "version": "3.0.12", + "version": "3.1.0", "license": "MIT", "dependencies": { - "@azure/service-bus": "^7.9.4", - "pg": "^8.11.5" + "@azure/service-bus": "7.9.4", + "pg": "8.11.5" } }, "node_modules/@azure/abort-controller": { @@ -394,21 +394,18 @@ } }, "node_modules/fast-xml-parser": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz", - "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" } ], + "license": "MIT", "dependencies": { - "strnum": "^1.0.5" + "strnum": "^1.1.1" }, "bin": { "fxparser": "src/cli/cli.js" @@ -842,9 +839,16 @@ } }, "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" }, "node_modules/tslib": { "version": "2.6.2", diff --git a/package.json b/package.json index 3dfebe3..3b44b2f 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,9 @@ { "name": "busgres", - "version": "3.0.12", + "version": "5.0.0", "description": "Busgres is an NPM package for receiving messages from Service Bus and saving them into a PostgreSQL database.", "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, + "type": "module", "repository": { "type": "git", "url": "https://github.com/rtasalem/busgres" @@ -21,7 +19,7 @@ ], "license": "MIT", "dependencies": { - "@azure/service-bus": "^7.9.4", - "pg": "^8.11.5" + "@azure/service-bus": "7.9.4", + "pg": "8.11.5" } }