Compare commits

..

70 Commits

Author SHA1 Message Date
d7d2c2d626 FIX: Rodnado em prod como desenvolvimento
All checks were successful
Deploy Production / deploy (push) Successful in 14s
2026-05-07 15:14:16 -03:00
7b44180de5 FIX: executa deploy de produção com usuário desenvolvimento
Some checks failed
Deploy Production / deploy (push) Failing after 0s
2026-05-07 14:56:58 -03:00
495b32a6a4 FEAT: Add lookback days configuration for responsible users in ticket repository
All checks were successful
Deploy Production / deploy (push) Successful in 17s
2026-05-04 11:14:05 -03:00
a7aba2f665 CI/CD: Prevenção de falhas no deploy para produção
All checks were successful
Deploy Production / deploy (push) Successful in 11s
2026-05-04 09:22:44 -03:00
04846e1d72 FIX: Atualização de branch
Some checks failed
Deploy Production / deploy (push) Failing after 0s
2026-05-04 09:16:26 -03:00
6f75b1645b CI/CD: Atualização do workflow de deploy para produção
- Troca de branch main para master
2026-05-04 09:14:27 -03:00
e4a736fc59 CI/CD: Adicionado o workflow de deploy para produção 2026-05-04 09:06:34 -03:00
8363bde237 FEAT: Adicionado variáveis de ambiente para definir os responsáveis por implantação, cancelamento e titularidade no HubSoft
- Adicionadas as seguintes variáveis de ambiente no arquivo .env.development:
  - HUBSOFT_IMPLANTACAO_RESPONSAVEL_USER_IDS: IDs dos usuários responsáveis pela implantação no HubSoft.
  - HUBSOFT_CANCELAMENTO_RESPONSAVEL_USER_IDS: IDs dos usuários responsáveis pelo cancelamento no HubSoft.
  - HUBSOFT_TITULARIDADE_RESPONSAVEL_USER_IDS: IDs dos usuários responsáveis pela titularidade no HubSoft.
- Atualizadas as implementações relacionadas à implantação, cancelamento e titularidade para utilizar as novas variáveis de ambiente e garantir que as ações sejam atribuídas aos usuários corretos.
2026-04-30 16:41:28 -03:00
23e134e7a3 CHORE
: ajustar intervalo de execução do cron do watchdog
2026-02-02 16:28:53 -03:00
0b1d5f5a30 HOTFIX: Alinhado a exportação do JOB 2026-02-02 16:26:50 -03:00
Desenvolvimento
55ef702141 HOTFIX: Ajustado para utilização da .env correta 2026-02-02 14:33:45 -03:00
1341fb6785 FEAT Atualiazado notifiação de emails para suportar diferentes usuarios para ADM e para Coloaboradores, configurando uma lista a partir das variaveis de ambiente. 2026-01-23 10:13:54 -03:00
fa6931a5bb FIX: Ajustado status de completed para uso correto do banco de dados 2026-01-23 09:03:42 -03:00
53bdd1c5a5 FEATURE: Implementado serviço de notificação para Admin e colaboradores, Adicionado validador de ticket updade, e modificado email para envio bonito 2026-01-20 17:36:28 -03:00
ad28159185 REFACTOR: Preparando WatchDog para nova feature 2026-01-19 17:55:15 -03:00
c1f52d741f Merge branch 'master' into feature/entity-creator 2026-01-15 10:41:01 -03:00
f0ff2ba129 REMOVE: Removendo conteudo de commits antigos 2026-01-15 09:41:25 -03:00
3ea1511734 Merge branch 'feature/watchdog-mundiale-closure' 2026-01-15 09:33:36 -03:00
4833310ec1 FEATURE: Finalizado Feature Watchdog, responsável por coletar chamados pendentes de fechamento no GLPI e notificar algum grupo 2026-01-14 17:51:49 -03:00
30be46bbd3 WIP: Criado conexão com API do GLPI 2026-01-12 17:53:58 -03:00
1bf3f744ab WIP: Criado Watchdog/Goleito para detectar chamados que estao fechados no hubsoft mas nao no GLPI, testes pendentes 2026-01-12 16:42:08 -03:00
08b3bf2639 CHORE: Preparando ambiente PM2 para novas Features 2026-01-12 16:40:47 -03:00
b398b22286 RAW: Comentários regra temporaria
Adiciona comentários para impedir abertura de chamados de:

- cancelamento
- SAC
- troca de titularidade

Regra temporária válida até 19/01, conforme definição da gerência.
2026-01-08 16:41:35 -03:00
Desenvolvimento
d610f32575 CONFIG: Alterado config PM2 2026-01-08 16:30:54 -03:00
b2b7364418 HOTFIX: Deletado arquivos legado que entravam em conflito 2026-01-07 08:31:39 -03:00
dd7e5c20e8 CONFIG: Alterado o caminho que aponta para os arquivos de inicialização 2026-01-07 07:35:57 -03:00
ad3d7bdd6e CONFIG: Alterado o caminho que aponta para os arquivos de inicialização 2026-01-07 07:32:34 -03:00
53f5a86d47 FIX: Adicionado porta para API funcionar externamente 2026-01-07 07:25:11 -03:00
3d08bf678d HOTFIX: Resolvido bug que impedia criação de ticket 2026-01-06 17:57:36 -03:00
89426f4371 CLEANUP: Descomentado todo tipo de atendimento para busca 2026-01-06 17:46:09 -03:00
6a22bf652a FEAT: Chamados de troca de titularidade também são coletados 2026-01-06 17:43:09 -03:00
37205d7e23 DOC: Documentação ajustada praa se adequar a mudanças 2026-01-06 14:50:26 -03:00
2382d2f8c0 merge refactor/clean-architecture 2026-01-06 14:17:16 -03:00
f64dafa2e1 FIX: Bug de duplicação de mensganes no HubSoft consertado 2026-01-06 11:20:57 -03:00
0b66f26f60 FEATURE: Adicionado /health para verificar saude da API e rotas 2026-01-06 08:37:47 -03:00
783eb2f081 REFACTOR: Integração refatorada utilizando conceitos de clean architeture e monolito modular 2026-01-02 18:19:11 -03:00
2cd00c65c5 refactor: restaura alterações do createTickets após hotfix 2025-12-16 14:37:14 -03:00
2d1c8647da FIX: Refatorar lógica de verificação de escopo em handleTicket para usar array de palavras-chave 2025-12-16 11:50:50 -03:00
809acae4b6 FIX: Renomear função handleMundialeTicket para handleTicket e atualizar lógica de verificação de escopo, permitindo que chamados fora do tipo mundiale sejam fechados 2025-12-16 10:50:04 -03:00
dcdde87544 FIX: Bug ao adicionar grupo resolvido 2025-12-11 11:48:24 -03:00
a75bfadec8 FIX: Chamados de implantações são abertos corretamente e einseridos no grupo Implantação 2025-12-10 13:43:08 -03:00
692ad0b583 FEAT: Chamados de implantação agora são criados automaticamente 2025-12-10 07:10:37 -03:00
4b2d26e469 WIP: Refactoring CreateTickets e solidificando estrutura para Feature de trazer chamados provenientes do SAC e de Implantacoes 2025-12-04 18:05:45 -03:00
14949bf4df FEAT: Sincronia de comentarios bidirecional finalizada
- Comentários oriundos do GLPI estão sincronizando com Hub através de um webhook
- Comentários oriundos do Hub estão sincronizando com GLPI através de um CronJob
- Validação de comentário existente sendo realizada através do banco de dados intermediário
- Ainda há a necessidade função via nodecron para que verifique os chamados syncError com retry menor igual a 3
2025-12-02 17:43:51 -03:00
d359880f10 REFACTOR: Ajustado Configurações de execução da aplicação 2025-12-01 11:39:04 -03:00
7972479094 CHORE: Adicionado node_modules ao .gitignore 2025-11-21 11:29:37 -03:00
0aeb8cf57e CHORE: Adicionado o .env.example e arquivos e adicionado as variaveis de ambiente de dev e prod ao .git.ignore 2025-11-21 11:27:11 -03:00
74d6fbec17 CHORE: Removendo arquivos que estavam sendo trackeados 2025-11-21 11:25:53 -03:00
fe4462c323 WIP: Lógica de sincronização bidirecional criada.
-  Tabelas de updates criada e adicionado ao script
-  Tabela de marca d'água criada
-  Fluxo de coleta de mensagens do HubSoft criado
-  Fluxo de coleta de comentários do GLPI criado.
2025-11-14 10:34:38 -03:00
4d931bdddf CHORE: Adicionada configuração para rodar o PM2 com cluster e CronJobs com FORK 2025-11-11 15:48:44 -03:00
0d4128df3d RAW: Adicionado arquivo para Deploy via PM2 2025-11-11 13:46:33 -03:00
3caef30a06 DOCS: Documentação atualizada 2025-11-11 10:54:50 -03:00
b969e5875d FEAT: Adicionada a validação para que uma run do processAtendimento não sobreponha a outra 2025-11-11 09:27:38 -03:00
9019dbe58f FEAT: Arquivos de logs possuem ciclo de vida de 10 dias 2025-11-11 09:17:08 -03:00
77d07eadcc TEST: Realizado testes de fechamento de Tickets GLPI - > HubSoft 2025-11-10 17:31:55 -03:00
6acd5369da FEAT/DOC: Feature finalizada - Branch Finalizada
- A aplicação escuta webhooks do GLPI para eventos de fechamento de tickets.
   - Ao receber um webhook para um ticket "Mundiale", o serviço fecha o atendimento correspondente na API do HubSoft e atualiza o status no banco de dados local.
   - Regra de negócio: Caso o status de sincronia seja (`status_sync = 'processing_close'`). O middleware irá se resguarda para condições de corrida causadas por webhooks duplicados do GLPI, garantindo que um ticket seja processado para fechamento apenas uma vez.
   - Documentação do projeto adicionado ao Readme.md
2025-11-10 17:01:33 -03:00
tulioperdigao
f8095c80e5 WIP: Atualiza bancos de dados em situação de fechamento de chamado. 2025-11-06 14:31:26 -03:00
tulioperdigao
8e47f46459 FEAT: Função que fecha um ticket no Hubsoft. 2025-11-06 12:42:04 -03:00
a1be9223a5 BUILD: Atualizado o .env para separar ambientes de produção e desenvolvimento 2025-11-06 11:48:45 -03:00
69798e84e4 FIX: API traz WebHook do GLPI como JSON, configurado request que era carregada como RAW 2025-11-05 18:00:45 -03:00
140212f321 RAW: Alterado configurações para coleta de dados da API 2025-11-05 15:49:37 -03:00
b7f4ef92f2 BUILD: Adicionado as dependências do express 2025-11-05 11:25:50 -03:00
tulioperdigao
c6e8162bfd FEAT: Criação da API genérica e reorganização dos arquivos em uma pasta ./src 2025-11-04 12:00:23 -03:00
c5bee3de55 REFACTOR: Refatorado de novo, confia! 2025-10-15 10:07:20 -03:00
9bc9dfb06f REFACTOR: IF das entidades refatorado. 2025-10-15 09:51:52 -03:00
c41c099e92 REFACTOR: IF das entidades refatorado para receber id e name.
- processController e glpiModel alterados para receber id e name.
2025-10-15 08:41:24 -03:00
b88c6fdb9e REFACTOR: Refatorado if que consulta entidade.
- Agora só aceita a entidade com codServico se ela também tiver codCliente.
2025-10-15 08:05:35 -03:00
25f2c62157 REFACTOR: Criação de entidades.
- Corrida a lógica de comparação do id.
2025-10-14 15:47:51 -03:00
Midway Hub Glpi
3aa48326b1 ERROR: Entidade retornando null nao insere chamado 2025-10-14 18:38:16 +00:00
9c31d2e8c9 FEAT: Dicionario de categorias adicionado 2025-10-13 16:20:11 -03:00
224 changed files with 7141 additions and 14705 deletions

31
.env
View File

@ -1,31 +0,0 @@
HUBSOFT_AUTH_URL='https://api.sothis.hubsoft.com.br/oauth/token'
HUBSOFT_CLIENT_ID=35
HUBSOFT_CLIENT_SECRET=1s62YsDijmzLz17NaKbnK1AUCKAx10RSDPMOtVcN
HUBSOFT_USERNAME=contato@vexvigilancia.com.br
HUBSOFT_PASSWORD="19T(6Jnp*"
HUBSOFT_GRANT_TYPE=password
HUBSOFT_CONSULTAR_ATENDIMENTO_URL="https://api.sothis.hubsoft.com.br/api/v1/integracao/atendimento/"
HUBSOFT_DATABASE_HOST=sothis.hubsoft.com.br
HUBSOFT_DATABASE_PORT=9432
HUBSOFT_DATABASE_NAME=hubsoft
HUBSOFT_DATABASE_USER=sothis_leitura
HUBSOFT_DATABASE_PASSWORD=2d0f4e138ba533693a1fc244d08a7f8596d8b472
# Banco HUBGLPI (PostgreSQL)
HUBGLPI_DB_HOST=10.0.120.75
HUBGLPI_DB_PORT=5432
HUBGLPI_DB_NAME=hubglpi
HUBGLPI_DB_USER=desenvolvimento
HUBGLPI_DB_PASSWORD=Ut@2S@$M9Xs@@W
# Banco GLPI (MySQL)
GLPI_DB_TYPE=mysql2/promise
GLPI_DB_HOST=177.73.177.32
GLPI_DB_PORT=3306
GLPI_DB_USER=snglpi
GLPI_DB_PASSWORD=j2633669
GLPI_DB_NAME=glpi_data
GLPI_DB_CHARSET=utf8mb4
NODE_ENV=development

79
.env.example Normal file
View File

@ -0,0 +1,79 @@
# ==============================================================================
# AMBIENTE DE DESENVOLVIMENTO E PRODUÇÃO (development, production, etc.)
# ==============================================================================
NODE_ENV= #development #production
# ==============================================================================
# CONFIGURAÇÕES DA APLICAÇÃO
# ==============================================================================
PORT=3000
# ==============================================================================
# HUBSOFT - API & DATABASE (Leitura)
# ==============================================================================
# API
HUBSOFT_BASE_URL='https://api.sothis.hubsoft.com.br/'
HUBSOFT_AUTH_URL='https://api.sothis.hubsoft.com.br/oauth/token'
HUBSOFT_CLIENT_ID=
HUBSOFT_CLIENT_SECRET=
HUBSOFT_USERNAME=
HUBSOFT_PASSWORD=
HUBSOFT_GRANT_TYPE=
HUBSOFT_CONSULTAR_ATENDIMENTO_URL="https://api.sothis.hubsoft.com.br/api/v1/integracao/atendimento/"
# Database (Leitura)
HUBSOFT_DATABASE_HOST=sothis.hubsoft.com.br
HUBSOFT_DATABASE_PORT=9432
HUBSOFT_DATABASE_NAME=hubsoft
HUBSOFT_DATABASE_USER=
HUBSOFT_DATABASE_PASSWORD=
HUBSOFT_MUNDIALE_USER_ID=248
HUBSOFT_IMPLANTACAO_RESPONSAVEL_USER_IDS=142
HUBSOFT_CANCELAMENTO_RESPONSAVEL_USER_IDS=142
HUBSOFT_TITULARIDADE_RESPONSAVEL_USER_IDS=142
HUBSOFT_RESPONSAVEL_LOOKBACK_DAYS=365
# ==============================================================================
# BANCO DE DADOS INTERMEDIÁRIO - HUBGLPI (PostgreSQL - Desenvolvimento)
# ==============================================================================
HUBGLPI_DB_HOST=
HUBGLPI_DB_PORT=5432
HUBGLPI_DB_NAME=hubglpi-development
HUBGLPI_DB_USER=
HUBGLPI_DB_PASSWORD=
# ==============================================================================
# BANCO DE DADOS & API FINAL - GLPI (MySQL - Desenvolvimento)
# ==============================================================================
GLPI_DB_TYPE=mysql
GLPI_DB_HOST=177
GLPI_DB_PORT=3306
GLPI_DB_USER=
GLPI_DB_PASSWORD=
GLPI_DB_NAME=glpi_data
GLPI_DB_CHARSET=utf8mb4
GLPI_USER_ID=11111
GLPI_AUTH_URL='https://ticket.hmg2.sothistelecom.com/api.php/token'
GLPI_BASE_URL='https://ticket.hmg2.sothistelecom.com/api.php/v2'
GLPI_CLIENT_ID=
GLPI_CLIENT_SECRET=
GLPI_USERNAME=
GLPI_PASSWORD=
# ==============================================================================
# CONFIGURAÇÕES DE EMAIL (Desenvolvimento)
# ==============================================================================
MAIL_HOST=
MAIL_PORT=25
MAIL_FROM="Goleiro <goalkeeper@sothis.com.br>"
WATCHDOG_RECIPIENT_EMAILS="example@example.com, example@example.com"
WATCHDOG_CC_EMAILS="example@example.com, example@example.com"
WATCHDOG_ADM_CC_EMAILS="example@example.com, example@example.com"
WATCHDOG_ADM_RECIPIENT_EMAILS="example@example.com"
# ==============================================================================
# HABILITAR MOCKS PARA TESTES CASO NÃO QUEIRA UTILIZAR A API REAL
# =============================================================================
HUBSOFT_MOCK_ENABLED=false

View File

@ -0,0 +1,54 @@
name: Deploy Production
on:
push:
branches:
- master
jobs:
deploy:
runs-on: vm-prod
steps:
- name: Garantindo Safe Directory
run: |
cd /tmp
sudo -H -u desenvolvimento git config --global --add safe.directory /opt/HUBXGLPI
- name: Deploy da aplicação
run: |
set -euo pipefail
sudo chown -R desenvolvimento:desenvolvimento /opt/HUBXGLPI
cd /tmp
sudo -H -u desenvolvimento bash -lc '
set -euo pipefail
cd /opt/HUBXGLPI
git fetch origin master
git checkout master
git pull --ff-only origin master
npm ci --omit=dev
./node_modules/.bin/pm2 reload ecosystem.config.js --env production || ./node_modules/.bin/pm2 start ecosystem.config.js --env production
./node_modules/.bin/pm2 save
'
- name: Exibir logs da aplicação
if: success()
run: |
sleep 3
cd /tmp
sudo -H -u desenvolvimento bash -lc '
cd /opt/HUBXGLPI
echo "=== API ==="
./node_modules/.bin/pm2 logs hubxglpi-api --lines 15 --nostream
echo "=== CRON ==="
./node_modules/.bin/pm2 logs hubxglpi-cron --lines 10 --nostream
'

4
.gitignore vendored
View File

@ -1 +1,5 @@
node_modules/
logs/
.env.development
.env.production
.idea/

279
README.md Normal file
View File

@ -0,0 +1,279 @@
# Serviço de Integração HubSoft ⇄ GLPI
![Node.js](https://img.shields.io/badge/Node.js-18.x-green)
![Express.js](https://img.shields.io/badge/Express.js-4.x-blue)
![PostgreSQL](https://img.shields.io/badge/Database-PostgreSQL-blue)
![PM2](https://img.shields.io/badge/Process%20Manager-PM2-blue)
![License](https://img.shields.io/badge/License-MIT-yellow.svg)
Este projeto é um **serviço de integração entre HubSoft e GLPI**, responsável por sincronizar **tickets, fechamentos e comentários**, utilizando **Node.js**, **PostgreSQL** e uma arquitetura baseada em **Clean Architecture**.
O foco do projeto é:
- confiabilidade
- idempotência
- isolamento de responsabilidades
- facilidade de manutenção e evolução
---
## ✨ Funcionalidades
- Criação automática de tickets no **GLPI** a partir de atendimentos do **HubSoft**
- Fechamento automático de atendimentos no **HubSoft** a partir de tickets encerrados no **GLPI**
- Sincronização de **comentários** do GLPI para o HubSoft
- Processamento assíncrono via **cron/worker**
- Controle de estado e idempotência via **banco intermediário**
- Prevenção de concorrência e duplicidade de eventos
- Execução separada de **API** e **Worker**
- Suporte a execução local, desenvolvimento e produção
---
## 🧱 Arquitetura
O projeto segue princípios da **Clean Architecture**, separando claramente:
```
Controller → UseCase → Repository → Infra
```
### Camadas
- **Controller**
- Entrada HTTP (webhooks, endpoints)
- Não contém regra de negócio
- **UseCases**
- Regras de negócio
- Orquestração dos fluxos
- **Repositories**
- Contratos de acesso a dados e integrações
- **Infra**
- Implementações técnicas (DB, APIs, cron, HTTP)
---
## 🔄 Fluxos de Integração
### 1. Criação de Tickets (HubSoft → GLPI)
Fluxo assíncrono executado por **worker (cron)**.
```
HubSoft DB
Worker (Cron)
Banco Intermediário
API GLPI
```
**Passo a passo:**
1. Worker consulta novos atendimentos no HubSoft
2. Use case valida e transforma os dados
3. Registro é salvo no banco intermediário
4. Ticket é criado no GLPI
5. Status de sincronização é atualizado
---
### 2. Fechamento de Tickets (GLPI → HubSoft)
Fluxo síncrono iniciado via webhook.
```
GLPI Webhook
API (Controller)
UseCase
API HubSoft
Banco Intermediário
```
**Regras importantes:**
- Apenas tickets elegíveis (ex: tipo *Mundiale*)
- Controle de lock para evitar duplicidade
- Atualização de status após sucesso
---
### 3. Sincronização de Comentários (GLPI → HubSoft)
Fluxo acionado por webhook do GLPI.
```
GLPI Webhook
API (Controller)
UseCase
API HubSoft
Banco Intermediário
```
**Detalhes:**
- Sanitização de HTML/texto
- Controle por `comment_id` e `source_comment_id`
- Garantia de idempotência
- Comentários duplicados são ignorados
---
## 🗄️ Banco de Dados Intermediário
O banco intermediário é **fundamental** para a integração.
Ele é responsável por:
- Controle de estado dos tickets
- Idempotência de webhooks
- Lock de processamento
- Auditoria e rastreabilidade
### Exemplos de status
- `pending_create`
- `created`
- `processing_close`
- `closed`
- `synced`
---
## 📂 Estrutura do Projeto
```
src/
├── config/ # Configurações gerais
├── infra/ # Infraestrutura
│ ├── api/ # Clientes de APIs externas
│ ├── cron/ # Workers / Jobs agendados
│ ├── db/
│ │ ├── connections/
│ │ └── repositories/
│ └── http/
│ └── routes/
├── modules/ # Domínio (Clean Architecture)
│ ├── close/
│ ├── comments/
│ ├── createTickets/
│ └── tickets/
├── shared/ # Código compartilhado
│ └── utils/
├── logs/ # Logs da aplicação
└── server.js # Bootstrap da aplicação
```
---
## 🚀 Execução do Projeto
### Pré-requisitos
- Node.js 18+
- NPM
- PostgreSQL
- PM2 (produção)
```bash
npm install -g pm2
```
---
### Variáveis de Ambiente
Criar arquivos:
```bash
.env.development
.env.production
```
Exemplo:
```ini
PORT=3000
NODE_ENV=development
HUBGLPI_DB_HOST=localhost
HUBGLPI_DB_PORT=5432
HUBGLPI_DB_USER=postgres
HUBGLPI_DB_PASSWORD=senha
HUBGLPI_DB_NAME=hubglpi
HUBSOFT_DB_HOST=ip
HUBSOFT_DB_PORT=5432
HUBSOFT_DB_USER=usuario
HUBSOFT_DB_PASSWORD=senha
HUBSOFT_DB_NAME=hubsoft
GLPI_API_URL=https://seu-glpi/apirest.php
GLPI_APP_TOKEN=token
GLPI_USER_TOKEN=token
HUBSOFT_API_URL=https://seu-hubsoft/api
HUBSOFT_API_TOKEN=token
```
---
## ▶️ Scripts NPM
### Desenvolvimento
Rodar **apenas a API**:
```bash
npm run dev:api
```
Rodar **apenas o worker (cron)**:
```bash
npm run dev:worker
```
---
### Produção (PM2)
Rodar apenas a **API**:
```bash
pm2 start npm --name hubxglpi-api -- start:api
```
Rodar apenas o **Worker**:
```bash
pm2 start npm --name hubxglpi-worker -- start:worker
```
Rodarr **API e Worker**:
```bash
pm2 start ecosystem.config.js --env production
Ver status:
```bash
pm2 status
```
Logs:
```bash
pm2 logs
```
---
## 🧠 Boas Práticas
- Sempre utilizar o banco intermediário para controle de estado
- Controllers não devem conter lógica de negócio
- UseCases não devem conhecer detalhes de infraestrutura
---
## 📜 Licença
MIT

9
app.js
View File

@ -1,9 +0,0 @@
const hubsoftController = require('./controller/processController.js');
logInfo('Aplicação iniciada', {
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || 'development'
});
// Inicia o processamento dos atendimentos
hubsoftController.processaAtendimentos();

View File

@ -1,251 +0,0 @@
// controller/processController.js
const hubsoftModel = require('../model/hubsoftModel.js');
const hubglpiModel = require('../model/hubglpiModel.js');
const glpiModel = require('../model/glpiModel.js');
const { logError, logInfo } = require('../utils/logger');
// ================================================================================
// Constantes e Configurações
// ================================================================================
const statusAtendimentoHubGlpi = {
1: 'Pendente',
2: 'Em atendimento',
3: 'Resolvido',
31: 'Pendente',
32: 'Pendente',
33: 'Novo'
};
const statusAtendimentoGLPI = {
'Novo': 1,
'Pendente': 4,
'Em atendimento': 2,
'Resolvido': 5
};
// ================================================================================
// Funções Utilitárias
// ================================================================================
// Formata a descrição do ticket em HTML
const formatDescription = (ticketData) => {
let htmlDescription = `
<table style="width:100%; border-collapse: collapse;">
<tr style="background-color:#f2f2f2;">
<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Campo</th>
<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Valor</th>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Nome:</strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${ticketData.cliente_nome}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Codigo:</strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${ticketData.codigo_cliente}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Serviço:</strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${ticketData.servico_nome}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Ticket Mundiale</strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${ticketData.ticket_mundiale}</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Protocolo Hub:</strong></td>
<td style="padding: 8px; border: 1px solid #ddd;">${ticketData.protocolo_hub || 'N/A'}</td>
</tr>
</table>
`;
return htmlDescription;
};
// Formata os dados do ticket para o GLPI
const formatTicketDataForGlpi = async (ticketData) => {
const formattedData = {
...ticketData,
status_atendimento: statusAtendimentoGLPI[ticketData.status_atendimento] || 1,
date_mod: new Date(),
user_id_recipient: 971,
descricao_abertura: formatDescription(ticketData),
urgency: 3,
impact: 3,
priority: 3,
type: 1,
itilcategories_id: 1,
date_creation: new Date(),
// entidades_id: 0 //await glpiModel.selectEntityId() //TODO: Implementar a busca da entidade
};
return formattedData;
};
// ================================================================================
// Funções de Integração
// ================================================================================
const createGlpiTicket = async (ticketData) => {
try {
const formattedTicketData = await formatTicketDataForGlpi(ticketData);
const glpiTicket = await glpiModel.insertTicket(formattedTicketData);
logInfo(`Ticket criado no GLPI: ${glpiTicket.insertId} `);
//Atualiza que ticket foi criado
ticketData.status_sync = 'created_glpi';
ticketData.glpi_ticket_id = glpiTicket.insertId;
ticketData.created_at = new Date();
ticketData.updated_at = new Date();
const updateSyncData = await hubglpiModel.update_syncData(ticketData);
logInfo(`Sync Data atualizado com o ID do ticket do GLPI: ${updateSyncData.glpi_ticket_id}`);
try {
//Insetingo ao grupo Operacao NOC
await glpiModel.insertGroupTickets(glpiTicket.insertId);
logInfo(`Atribuido grupo Operação NOC ao ticket no GLPI: ${glpiTicket.insertId} `)
} catch (error) {
const updateSyncDataError = await hubglpiModel.update_syncaDataError(error.message, ticketData.id_atendimento)
logError(`Erro ao criar ticket no GLPI. Sync Data atualizado com a mensagem de erro: ${error}`);
}
} catch (error) {
const updateSyncDataError = await hubglpiModel.update_syncaDataError(error.message, ticketData.id_atendimento)
logError(`Erro ao criar ticket no GLPI. Sync Data atualizado com a mensagem de erro: ${error}`);
}
};
const processTicketFromHubSoft = async (atendimento) => {
const ticketData = {
id_atendimento: atendimento.id_atendimento,
id_atendimento_status: atendimento.id_atendimento_status,
codigo_servico: String(atendimento.id_cliente_servico || ''), // Garante que seja uma string
protocolo_hub: atendimento.protocolo,
servico_nome: atendimento.descricao,
descricao_abertura: atendimento.descricao_abertura,
data_cadastro: atendimento.data_cadastro,
cliente_nome: atendimento.nome_contato,
codigo_cliente: atendimento.codigo_cliente,
descricao: atendimento.descricao
};
ticketData.status_atendimento = statusAtendimentoHubGlpi[ticketData.id_atendimento_status] || ticketData.id_atendimento_status;
ticketData.descricao_abertura = ticketData.descricao_abertura.replace(/[^0-9]/g, '');
ticketData.ticket_mundiale = parseInt(ticketData.descricao_abertura) || null;
return ticketData;
};
const saveTicketToHubGlpi = async (ticketData) => {
try {
const insertedTicket = await hubglpiModel.insertTicket(ticketData);
logInfo(`Ticket inserido/atualizado na tabela hubsoft_tickets: ${insertedTicket.protocolo_hub}`);
await hubglpiModel.insertSyncData(ticketData.id_atendimento);
logInfo('Dados inseridos/atualizados na tabela sync_data');
return insertedTicket;
} catch (error) {
logError(`Erro ao salvar ticket no hubglpi: ${error}`);
throw error; // Rejeita a promise para que o erro seja tratado no nível superior
}
};
const processAtendimento = async (ticketData) => {
try {
const glpiTicketId = await hubglpiModel.getGlpiTicketIdByAtendimentoId(ticketData.id_atendimento);
if (glpiTicketId) {
logInfo(`Ticket já inserido no GLPI para id_atendimento ${ticketData.id_atendimento}, pulando criação.`);
ticketData.status_sync = 'created_glpi';
ticketData.glpi_ticket_id = glpiTicketId;
await hubglpiModel.update_syncData(ticketData);
return;
}
const titulo = `Mundiale - Protocolo: ${ticketData.ticket_mundiale} - ${ticketData.cliente_nome}`;
ticketData.titulo = titulo;
const selectedEntityCodServico = await glpiModel.selectEntityId(ticketData.codigo_servico);
if (!selectedEntityCodServico) {
const selectedEntityCodCliente = await glpiModel.selectEntityId(ticketData.codigo_cliente);
ticketData.entidades_id = selectedEntityCodCliente;
} else if (selectedEntityCodServico) {
ticketData.entidades_id = selectedEntityCodServico;
} else {
ticketData.entidades_id = 0;
}
await createGlpiTicket(ticketData);
} catch (error) {
console.error(`Erro ao processar atendimento ${ticketData.id_atendimento}:`, error);
}
};
// ================================================================================
// Função Principal (com opção de pular a etapa do HubSoft)
// ================================================================================
const processaAtendimentos = async (skipHubSoft = false) => {
try {
let atendimentosDB = [];
if (!skipHubSoft) {
// Esta parte depende da VPN
logInfo('Buscando atendimentos do HubSoft...');
atendimentosDB = await hubsoftModel.getAtendimentosFromDB();
logInfo(`Total de atendimentos obtidos do HubSoft: ${atendimentosDB.length}`);
for (const atendimento of atendimentosDB) {
try {
// Processa o ticket do HubSoft
const ticketData = await processTicketFromHubSoft(atendimento);
// Salva no banco intermediário (hubglpi)
await saveTicketToHubGlpi(ticketData);
}
catch (error) {
logError(`Erro ao processar atendimento ${atendimento.id_atendimento}:`, error);
}
}
} else {
logInfo('Pulando a busca de atendimentos do HubSoft (skipHubSoft = true)');
}
// Coletando atendimentos do banco intermediário (hubglpi)
atendimentosDB = await hubglpiModel.getTicketDataPending();
for (const atendimento of atendimentosDB) {
try {
// Processa o atendimento para o GLPI
await processAtendimento(atendimento);
} catch (error) {
logError(`Erro ao processar atendimento ${atendimento.id_atendimento}:, ${error}`);
}
}
} catch (error) {
logError(`Erro ao processar atendimentos: ${error}`);
}
};
module.exports = {
processaAtendimentos,
processAtendimento,
formatTicketDataForGlpi,
formatDescription
};

View File

@ -1,31 +0,0 @@
// src/data/hubglpiDataBase.js
// Configuração da conexão com o banco de dados PostgreSQL
const { logInfo, logError } = require('../utils/logger');
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.HUBGLPI_DB_HOST,
port: process.env.HUBGLPI_DB_PORT,
database: process.env.HUBGLPI_DB_NAME,
user: process.env.HUBGLPI_DB_USER,
password: process.env.HUBGLPI_DB_PASSWORD,
});
// Teste de conexão
pool.on('connect', () => {
logInfo('Conexão com o banco de dados PostgreSQL estabelecida com sucesso.');
});
pool.on('error', (err) => {
logError('Erro na conexão com o banco de dados PostgreSQL', err);
});
module.exports = pool;
/**
* @module hubglpiDataBase
* @description Este módulo configura e exporta a conexão com o banco de dados PostgreSQL usado para armazenar dados sincronizados entre HubSoft e GLPI.
*/

58
ecosystem.config.js Normal file
View File

@ -0,0 +1,58 @@
// ecosystem.config.js
module.exports = {
apps: [
// 🟢 API PRINCIPAL
{
name: "hubxglpi-api",
script: "src/infra/http/server.js",
exec_mode: "cluster",
instances: "max",
env: {
NODE_ENV: "development",
PORT: 3000
},
env_production: {
NODE_ENV: "production",
PORT: 3000
}
},
// 🕒 CRON DE SINCRONIZAÇÃO
{
name: "hubxglpi-cron",
script: "src/infra/cron/sync.cron.js",
exec_mode: "fork",
instances: 1,
env: {
NODE_ENV: "development"
},
env_production: {
NODE_ENV: "production"
}
},
// 🐶 WATCHDOG
{
name: "hubxglpi-watchdog",
script: "src/infra/cron/observer.cron.js",
exec_mode: "fork",
instances: 1,
env: {
NODE_ENV: "development"
},
env_production: {
NODE_ENV: "production"
}
}
]
};

232
fluxoEntidade.md Normal file
View File

@ -0,0 +1,232 @@
# Fluxo de Resolução e Criação de Entidades (GLPI)
## Contexto
Quando um chamado de **Implantação** chega no Hub x GLPI, precisamos garantir que exista uma **Entidade** correta no GLPI para vincular o ticket.
Hoje, a resolução tenta encontrar a entidade pelo padrão de nome baseado em:
- `codigoCliente`
- `codigoServico`
- `nomeCliente`
Caso não encontre, entra a feature de **criação automática via API (GLPI v2 + OAuth)**.
> Regra importante: **criação/alteração de entidades sempre via API**, nunca via banco do GLPI.
---
## Convenções de Nome
### Entidade mãe (Cliente)
- **Formato:** `{codigoCliente} - {nomeCliente}`
- **Exemplo:** `12345 - ACME Telecom`
### Entidade filha (Serviço)
- **Formato:** `{codigoCliente} - {codigoServico} - {nomeCliente}`
- **Exemplo:** `12345 - 67890 - ACME Telecom`
> Observação: O `{nomeCliente}` é repetido na filha para manter legibilidade e facilitar busca manual no GLPI.
---
## Busca de Entidades
### Busca por Serviço (Entidade filha)
Procura por entidade que contenha:
- `{codigoCliente} - {codigoServico}` (prefixo/like)
### Busca por Cliente (Entidade mãe)
Procura por entidade que contenha:
- `{codigoCliente} -` (prefixo/like)
---
## Opção A — Criação hierárquica (Mãe + Filha quando necessário)
### Objetivo
Garantir hierarquia consistente no GLPI:
- Cliente como entidade mãe
- Serviço como entidade filha
### Fluxo (passo a passo)
1. **Tentar encontrar entidade por Serviço**
- Busca: `{codigoCliente} - {codigoServico}*`
- Se encontrou:
- ✅ usar essa entidade (filha) e finalizar
2. **Se não encontrou por Serviço: tentar encontrar entidade por Cliente**
- Busca: `{codigoCliente} -*`
- Se encontrou:
- ✅ criar **entidade filha** abaixo dessa mãe:
- Nome: `{codigoCliente} - {codigoServico} - {nomeCliente}`
- Parent: `{codigoCliente} - {nomeCliente}`
- ✅ usar a entidade criada (filha) e finalizar
3. **Se não encontrou nem por Serviço nem por Cliente**
- Criar **entidade mãe**:
- Nome: `{codigoCliente} - {nomeCliente}`
- Parent: raiz (definir entidade pai padrão, ex.: Root)
- Criar **entidade filha** abaixo da mãe:
- Nome: `{codigoCliente} - {codigoServico} - {nomeCliente}`
- Parent: `{codigoCliente} - {nomeCliente}`
- ✅ usar a entidade criada (filha) e finalizar
### Resultado final (Opção A)
- Ticket sempre fica na **entidade filha** (serviço)
- Sempre tenta manter **estrutura hierárquica** consistente
---
### Diagrama de Fluxo — Opção A (hierárquica: Mãe + Filha quando necessário)
```mermaid
flowchart TD
A[Chamado de Implantação chega do Hub] --> B[Extrair dados<br/>codigoCliente<br/>codigoServico<br/>nomeCliente]
B --> C{Existe entidade<br/>por Serviço?}
C -->|Sim| D[Usar entidade de Serviço]
D --> Z[Fim / Continua fluxo do ticket]
C -->|Não| E{Existe entidade<br/>por Cliente?}
E -->|Sim| F[Criar entidade de Serviço<br/>via API GLPI]
F --> G[Parent = Entidade Cliente<br/>Nome = codigoCliente-codigoServico-nomeCliente]
G --> Z
E -->|Não| H[Criar entidade Cliente<br/>via API GLPI]
H --> I[Parent = Contratos Ativos<br/>Nome = codigoCliente-nomeCliente]
I --> J[Criar entidade de Serviço<br/>via API GLPI]
J --> K[Parent = Entidade Cliente<br/>Nome = codigoCliente-codigoServico-nomeCliente]
K --> Z
```
## Opção B — Criação simplificada (sem forçar hierarquia)
### Objetivo
Criar o mínimo possível e evitar duplicação de entidades.
### Fluxo (passo a passo)
1. **Tentar encontrar entidade por Serviço**
- Busca: `{codigoCliente} - {codigoServico}*`
- Se encontrou:
- ✅ usar essa entidade e finalizar
2. **Se não encontrou por Serviço: tentar encontrar entidade por Cliente**
- Busca: `{codigoCliente} -*`
- Se encontrou:
- ✅ usar essa entidade mãe
- ❌ não cria entidade filha
- Finaliza
3. **Se não encontrou nem por Serviço nem por Cliente**
- Criar **uma entidade única (sem mãe)**:
- Nome: `{codigoCliente} - {codigoServico} - {nomeCliente}`
- Parent: raiz (ou padrão definido)
- ✅ usar essa entidade e finalizar
### Resultado final (Opção B)
- Ticket pode ficar:
- na entidade de serviço (se existir)
- ou na entidade de cliente (se existir)
- ou numa entidade única criada
- Não garante hierarquia “mãe → filha”
---
### Fluxo Opção B
```mermaid
flowchart TD
A[Chamado de Implantação chega do Hub] --> B[Extrair dados<br/>codigoCliente<br/>codigoServico<br/>nomeCliente]
B --> C{Existe entidade<br/>por Serviço?}
C -->|Sim| D[Usar entidade de Serviço]
D --> Z[Fim / Continua fluxo do ticket]
C -->|Não| E{Existe entidade<br/>por Cliente?}
E -->|Sim| F[Usar entidade de Cliente]
F --> Z
E -->|Não| G[Criar entidade Única<br/>via API GLPI]
G --> H[Parent = Root<br/>Nome = codigoCliente-codigoServico-nomeCliente]
H --> Z
```
## Opção C — Híbrida (Implantação cria; demais apenas resolvem)
### Objetivo
- Para **Implantação**: garantir estrutura correta criando entidades quando necessário (igual Opção A).
- Para **demais tipos de chamado**: **não criar entidades**, apenas resolver a melhor entidade existente; se não houver, usar fallback **Contratos Ativos**.
---
### Fluxo (passo a passo)
#### 1) Identificar tipo do chamado
- Se `tipo == Implantação` → executar **Fluxo A (criação hierárquica)**.
- Se `tipo != Implantação` → executar **Fluxo de Resolução sem criação**.
---
### Fluxo A (quando Implantação)
Segue exatamente a **Opção A**:
1. Buscar entidade por Serviço (`{codigoCliente} - {codigoServico}%`)
2. Se não achar, buscar por Cliente (`{codigoCliente} -%`)
3. Se achar Cliente, criar Serviço abaixo do Cliente
4. Se não achar nenhum, criar Cliente (embaixo de Contratos Ativos) e depois criar Serviço abaixo do Cliente
5. Retornar sempre a **entidade de Serviço**
---
### Fluxo de Resolução (quando NÃO é Implantação)
1. Buscar entidade por Serviço (`{codigoCliente} - {codigoServico}%`)
- Se achou: ✅ usar e finalizar
2. Se não achou, buscar entidade por Cliente (`{codigoCliente} -%`)
- Se achou: ✅ usar e finalizar
3. Se não achou nenhum dos dois:
- ✅ usar entidade **Contratos Ativos** (fallback)
- ❌ não criar nada
---
### Resultado final (Opção C)
- **Implantação**: sempre aponta para entidade de **Serviço**, criando Cliente/Serviço quando faltar.
- **Demais chamados**: nunca criam entidades; ficam na melhor existente (Serviço → Cliente → Contratos Ativos).
---
### Diagrama de Fluxo — Opção C (Híbrida)
```mermaid
flowchart TD
A[Chega chamado do Hub] --> B[Extrair dados<br/>codigoCliente<br/>codigoServico<br/>nomeCliente<br/>tipoChamado]
B --> C{Tipo é<br/>Implantação?}
%% ========== RAMO IMPLANTAÇÃO (OPÇÃO A) ==========
C -->|Sim| IA{Existe entidade<br/>por Serviço?}
IA -->|Sim| I1[Usar entidade de Serviço] --> Z[Fim / Continua fluxo do ticket]
IA -->|Não| IB{Existe entidade<br/>por Cliente?}
IB -->|Sim| I2[Criar entidade de Serviço<br/>via API GLPI<br/>Parent = Cliente] --> I3[Usar entidade criada] --> Z
IB -->|Não| I4[Criar entidade Cliente<br/>via API GLPI<br/>Parent = Contratos Ativos] --> I5[Criar entidade de Serviço<br/>via API GLPI<br/>Parent = Cliente] --> I6[Usar entidade criada] --> Z
%% ========== RAMO NÃO IMPLANTAÇÃO (RESOLVE ONLY) ==========
C -->|Não| NA{Existe entidade<br/>por Serviço?}
NA -->|Sim| N1[Usar entidade de Serviço] --> Z
NA -->|Não| NB{Existe entidade<br/>por Cliente?}
NB -->|Sim| N2[Usar entidade de Cliente] --> Z
NB -->|Não| N3[Usar entidade fallback<br/>Contratos Ativos] --> Z
```

View File

@ -1,107 +0,0 @@
// src/models/glpiModel.js
const dbConfig = require('../config/dbConfig.js');
const { logError, logInfo} = require('../utils/logger');
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: dbConfig.glpi.databaseHost,
port: dbConfig.glpi.databasePort,
database: dbConfig.glpi.databaseName,
user: dbConfig.glpi.databaseUser,
password: dbConfig.glpi.databasePassword,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
class GlpiModel {
static async insertTicket(ticketData) {
const query = `
INSERT INTO glpi_tickets(
entities_id,
name,
date,
date_mod,
status,
users_id_recipient,
content,
urgency,
impact,
priority,
type,
itilcategories_id,
date_creation,
slas_id_ttr
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`;
const values = [
ticketData.entidades_id,
ticketData.titulo,
ticketData.created_at,
ticketData.date_mod,
ticketData.status_atendimento,
ticketData.user_id_recipient,
ticketData.descricao_abertura,
ticketData.urgency,
ticketData.impact,
ticketData.priority,
2,
ticketData.itilcategories_id,
ticketData.date_creation,
37
];
try {
const [rows] = await pool.execute(query, values)
return rows;
} catch (error) {
logError('Erro ao inserir ticket:', error);
throw error;
}
}
static async selectEntityId(id) {
const query = `SELECT id FROM glpi_entities WHERE name LIKE ? OR name LIKE ? LIMIT 1;`;
const values = [`%${id} -%`, `%${id}-%`];
try {
const [rows] = await pool.execute(query, values);
if (!rows || rows.length === 0) {
logInfo(`Entidade não encontrada para: ${id} `);
return null;
}
logInfo(`Entidade encontrada para: ${id}`);
return Number(rows[0].id);
} catch (err) {
logError(`Erro ao buscar entidade: ${err}`);
throw err;
}
}
static async insertGroupTickets(glpiTicketId) {
const query = `
INSERT INTO glpi_groups_tickets (tickets_id, groups_id, type)
VALUES (?, ?, ?)
`;
const values = [glpiTicketId, 25, 2];
try {
const [rows] = await pool.query(query, values);
return rows && rows.insertId ? rows.insertId : null;
}
catch (err) {
logError(`Erro ao Adicionar Grupo Operação NOC: ${err}`);
throw err;
}
}
}
module.exports = GlpiModel;

View File

@ -1,223 +0,0 @@
// src/models/hubsoft_ticketsModel.js
const { log } = require('winston');
const dbConfig = require('../config/dbConfig.js');
const { logError, logInfo} = require('../utils/logger');
const { Pool } = require('pg');
const pool = new Pool({
host: dbConfig.hubglpi.databaseHost,
port: dbConfig.hubglpi.databasePort,
database: dbConfig.hubglpi.databaseName,
user: dbConfig.hubglpi.databaseUser,
password: dbConfig.hubglpi.databasePassword
});
class HubglpiModel {
static async insertTicket(ticketData) {
const query = `
INSERT INTO hubsoft_tickets (
id_atendimento,
codigo_cliente,
status_atendimento,
codigo_servico,
servico_nome,
protocolo_hub,
ticket_mundiale,
cliente_nome,
created_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (id_atendimento)
DO UPDATE SET
codigo_cliente = $2,
status_atendimento = $3,
codigo_servico = $4,
servico_nome = $5,
protocolo_hub = $6,
ticket_mundiale = $7,
cliente_nome = $8,
created_at = $9
RETURNING *;
`;
const values = [
ticketData.id_atendimento,
ticketData.codigo_cliente,
ticketData.status_atendimento,
ticketData.codigo_servico,
ticketData.servico_nome,
ticketData.protocolo_hub,
ticketData.ticket_mundiale,
ticketData.cliente_nome,
ticketData.data_cadastro
];
try {
const res = await pool.query(query, values);
return res.rows[0];
} catch (err) {
logError(`Erro ao inserir/atualizar ticket na tabela hubsoft_tickets: ${err}`);
throw err;
}
}
static async insertSyncData(syncData) {
const query = `
INSERT INTO sync_data (
hubsoft_ticket_id
)
VALUES ($1)
ON CONFLICT (hubsoft_ticket_id)
DO UPDATE SET
hubsoft_ticket_id = $1
RETURNING *;
`;
const values = [
syncData
];
try {
const res = await pool.query(query, values);
return res.rows[0];
} catch (err) {
logError(`Erro ao inserir/atualizar dados na tabela sync_data: ${err}`);
throw err;
}
}
static async update_syncData(sync_update) {
const query = `
UPDATE sync_data
set glpi_ticket_id = $1,
status_sync = $2,
sync_metadata = $3,
last_sync_attempt = $4,
sync_error_message = $5,
created_at = $6,
updated_at = $7
WHERE id = $8
RETURNING *;
`;
const values = [
sync_update.glpi_ticket_id,
sync_update.status_sync,
sync_update.sync_metadata,
new Date(),
sync_update.sync_error_message,
sync_update.created_at,
sync_update.updated_at,
sync_update.sync_data_id
]; //Todo colocar parametros dinamicos || null se não tiver
try {
const res = await pool.query(query, values);
return res.rows[0];
} catch (err) {
logError(`Erro ao atualizar dados na tabela sync_data: ${err}`);
throw err;
}
}
static async get_idSyncByHubsoftId(hubsoft_ticket_id) {
const query = `
SELECT id FROM sync_data
WHERE hubsoft_ticket_id = $1;
`;
const values = [hubsoft_ticket_id];
try {
const res = await pool.query(query, values);
logInfo('ID de sync_data obtido com sucesso:', res.rows[0]);
return res.rows[0] ? res.rows[0].id : null;
} catch (err) {
logError('Erro ao obter ID de sync_data', err);
throw err;
}
}
static async get_idSyncByGlpiID(glpi_ticket_id) {
const query = `
SELECT id FROM sync_data
WHERE glpi_ticket_id = $1;
`;
const values = [glpi_ticket_id];
try {
const res = await pool.query(query, values);
logInfo('ID de sync_data obtido com sucesso:', res.rows[0]);
return res.rows[0] ? res.rows[0].id : null;
}
catch (err) {
logError('Erro ao obter ID de sync_data', err);
throw err;
}
}
static async getTicketDataPending() {
const query = ` SELECT ht.id_atendimento, ht.codigo_cliente, ht.status_atendimento, ht.codigo_servico, ht.servico_nome, ht.protocolo_hub, ht.ticket_mundiale, ht.cliente_nome, ht.created_at, sd.id AS sync_data_id, sd.glpi_ticket_id, sd.status_sync, sd.sync_metadata, sd.last_sync_attempt, sd.sync_error_message, sd.created_at AS sync_created_at, sd.updated_at AS sync_updated_at
FROM hubsoft_tickets AS ht
LEFT JOIN sync_data AS sd ON ht.id_atendimento = sd.hubsoft_ticket_id
WHERE sd.status_sync IS NULL OR sd.status_sync = 'pending_create'
ORDER BY ht.created_at ASC; `;
try {
const res = await pool.query(query);
return res.rows;
} catch (err) {
logError(`Erro ao obter tickets pendentes ${err}`);
throw err;
}
}
static async getGlpiTicketIdByAtendimentoId(id_atendimento) {
const query = `
SELECT glpi_ticket_id FROM sync_data
WHERE hubsoft_ticket_id = $1;
`;
const values = [id_atendimento];
try {
const res = await pool.query(query, values);
return res.rows[0] ? res.rows[0].glpi_ticket_id : null;
} catch (err) {
logError(`Erro ao obter glpi_ticket_id por id_atendimento, ${err}`);
throw err;
}
}
static async update_syncaDataError(sync_error_message, id_atendimento) {
const query = `
UPDATE sync_data
set sync_error_message = $1,
status_sync = 'sync_error',
last_sync_attempt = $2
WHERE hubsoft_ticket_id = $3
RETURNING *;
`;
const values = [
sync_error_message,
new Date(),
id_atendimento
];
try {
const res = await pool.query(query, values);
return res.rows[0];
} catch (err){
logError(`Erro ao inserir mensagem de erro na tabela sync_data: ${err}`);
throw err;
}
}
}
module.exports = HubglpiModel;

View File

@ -1,31 +0,0 @@
const dbConfig = require('../config/dbConfig.js');
const { Pool } = require('pg');
const pool = new Pool({
host: dbConfig.hubsoft.databaseHost,
port: dbConfig.hubsoft.databasePort,
database: dbConfig.hubsoft.databaseName,
user: dbConfig.hubsoft.databaseUser,
password: dbConfig.hubsoft.databasePassword
});
const getAtendimentosFromDB = async () => {
const query = 'SELECT a.id_atendimento, a.id_usuario_abertura, a.id_atendimento_status, a.protocolo, a.descricao_abertura, a.data_cadastro, a.nome_contato, c.codigo_cliente, s.descricao, cs.id_cliente_servico FROM atendimento AS a INNER JOIN cliente_servico AS cs ON a.id_cliente_servico = cs.id_cliente_servico INNER JOIN cliente AS c ON cs.id_cliente = c.id_cliente INNER JOIN servico AS s ON cs.id_servico = s.id_servico WHERE a.id_tipo_atendimento = 4 AND a.id_usuario_abertura = 248 AND a.id_atendimento_status IN (1, 2, 33) AND s.ativo = true;';
const { rows } = await pool.query(query);
return rows;
}
const validateAtendimentoStatus = (id_atendimento) => {
const query = `SELECT id_atendimento_status FROM atendimento WHERE id_atendimento = ${id_atendimento};`;
return pool.query(query);
}
const validateMensagensByAtendimento = (id_atendimento) => {
const query = `SELECT id_atendimento_mensagem, id_atendimento, mensagem, data_cadastro FROM atendimento_mensagem WHERE id_atendimento = ${id_atendimento} ;`
return pool.query(query);
}
module.exports = {
getAtendimentosFromDB, validateAtendimentoStatus, validateMensagensByAtendimento
};

840
node_modules/.package-lock.json generated vendored
View File

@ -1,840 +0,0 @@
{
"name": "integra-hubsoft-glpi",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
"license": "MIT",
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/@dabh/diagnostics": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
"integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
"license": "MIT",
"dependencies": {
"@so-ric/colorspace": "^1.1.6",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"node_modules/@so-ric/colorspace": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
"integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
"license": "MIT",
"dependencies": {
"color": "^5.0.2",
"text-hex": "1.0.x"
}
},
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/color": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/color/-/color-5.0.2.tgz",
"integrity": "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==",
"license": "MIT",
"dependencies": {
"color-convert": "^3.0.1",
"color-string": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/color-convert": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz",
"integrity": "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==",
"license": "MIT",
"dependencies": {
"color-name": "^2.0.0"
},
"engines": {
"node": ">=14.6"
}
},
"node_modules/color-name": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz",
"integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==",
"license": "MIT",
"engines": {
"node": ">=12.20"
}
},
"node_modules/color-string": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.2.tgz",
"integrity": "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==",
"license": "MIT",
"dependencies": {
"color-name": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/dotenv": {
"version": "17.2.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
"license": "MIT"
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
"license": "MIT"
},
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
"license": "MIT"
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"license": "MIT",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/iconv-lite": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
"license": "MIT"
},
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
"license": "MIT",
"dependencies": {
"@colors/colors": "1.6.0",
"@types/triple-beam": "^1.3.2",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"safe-stable-stringify": "^2.3.1",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/lru.min": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/mysql2": {
"version": "3.15.2",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.2.tgz",
"integrity": "sha512-kFm5+jbwR5mC+lo+3Cy46eHiykWSpUtTLOH3GE+AR7GeLq8PgfJcvpMiyVWk9/O53DjQsqm6a3VOOfq7gYWFRg==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.7.0",
"long": "^5.2.1",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/named-placeholders": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
"license": "MIT",
"dependencies": {
"lru-cache": "^7.14.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"license": "MIT",
"dependencies": {
"fn.name": "1.x.x"
}
},
"node_modules/pg": {
"version": "8.16.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1",
"pg-protocol": "^1.10.3",
"pg-types": "2.2.0",
"pgpass": "1.0.5"
},
"engines": {
"node": ">= 16.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.2.7"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
"license": "MIT",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
"license": "MIT"
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
"license": "MIT",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
"license": "MIT"
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"license": "MIT",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
"license": "MIT"
},
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
"license": "MIT",
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/winston": {
"version": "3.18.3",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz",
"integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==",
"license": "MIT",
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.8",
"async": "^3.2.3",
"is-stream": "^2.0.0",
"logform": "^2.7.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"safe-stable-stringify": "^2.3.1",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.9.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/winston-transport": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
"license": "MIT",
"dependencies": {
"logform": "^2.7.0",
"readable-stream": "^3.6.2",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
}
}
}

21
node_modules/pg-cloudflare/LICENSE generated vendored
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2010 - 2021 Brian Carlson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

112
node_modules/pg-cloudflare/README.md generated vendored
View File

@ -1,112 +0,0 @@
# pg-cloudflare
`pg-cloudflare` makes it easier to take an existing package that relies on `tls` and `net`, and make it work in environments where only `connect()` is supported, such as Cloudflare Workers.
`pg-cloudflare` wraps `connect()`, the [TCP Socket API](https://github.com/wintercg/proposal-sockets-api) proposed within WinterCG, and implemented in [Cloudflare Workers](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/), and exposes an interface with methods similar to what the `net` and `tls` modules in Node.js expose. (ex: `net.connect(path[, options][, callback])`). This minimizes the number of changes needed in order to make an existing package work across JavaScript runtimes.
## Installation
```
npm i --save-dev pg-cloudflare
```
The package uses conditional exports to support bundlers that don't know about
`cloudflare:sockets`, so the consumer code by default imports an empty file. To
enable the package, resolve to the `cloudflare` condition in your bundler's
config. For example:
- `webpack.config.js`
```js
export default {
...,
resolve: { conditionNames: [..., "workerd"] },
plugins: [
// ignore cloudflare:sockets imports
new webpack.IgnorePlugin({
resourceRegExp: /^cloudflare:sockets$/,
}),
],
}
```
- `vite.config.js`
> [!NOTE]
> If you are using the [Cloudflare Vite plugin](https://www.npmjs.com/package/@cloudflare/vite-plugin) then the following configuration is not necessary.
```js
export default defineConfig({
...,
resolve: {
conditions: [..., "workerd"],
},
build: {
...,
// don't try to bundle cloudflare:sockets
rollupOptions: {
external: [..., 'cloudflare:sockets'],
},
},
})
```
- `rollup.config.js`
```js
export default defineConfig({
...,
plugins: [..., nodeResolve({ exportConditions: [..., 'workerd'] })],
// don't try to bundle cloudflare:sockets
external: [..., 'cloudflare:sockets'],
})
```
- `esbuild.config.js`
```js
await esbuild.build({
...,
conditions: [..., 'workerd'],
})
```
The concrete examples can be found in `packages/pg-bundler-test`.
## How to use conditionally, in non-Node.js environments
As implemented in `pg` [here](https://github.com/brianc/node-postgres/commit/07553428e9c0eacf761a5d4541a3300ff7859578#diff-34588ad868ebcb232660aba7ee6a99d1e02f4bc93f73497d2688c3f074e60533R5-R13), a typical use case might look as follows, where in a Node.js environment the `net` module is used, while in a non-Node.js environment, where `net` is unavailable, `pg-cloudflare` is used instead, providing an equivalent interface:
```js
module.exports.getStream = function getStream(ssl = false) {
const net = require('net')
if (typeof net.Socket === 'function') {
return net.Socket()
}
const { CloudflareSocket } = require('pg-cloudflare')
return new CloudflareSocket(ssl)
}
```
## Node.js implementation of the Socket API proposal
If you're looking for a way to rely on `connect()` as the interface you use to interact with raw sockets, but need this interface to be available in a Node.js environment, [`@arrowood.dev/socket`](https://github.com/Ethan-Arrowood/socket) provides a Node.js implementation of the Socket API.
### license
The MIT License (MIT)
Copyright (c) 2023 Brian M. Carlson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,2 +0,0 @@
declare const _default: {};
export default _default;

View File

@ -1,6 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// This is an empty module that is served up when outside of a workerd environment
// See the `exports` field in package.json
exports.default = {};
//# sourceMappingURL=empty.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"empty.js","sourceRoot":"","sources":["../src/empty.ts"],"names":[],"mappings":";;AAAA,kFAAkF;AAClF,0CAA0C;AAC1C,kBAAe,EAAE,CAAA"}

View File

@ -1,31 +0,0 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { TlsOptions } from 'cloudflare:sockets';
import { EventEmitter } from 'events';
/**
* Wrapper around the Cloudflare built-in socket that can be used by the `Connection`.
*/
export declare class CloudflareSocket extends EventEmitter {
readonly ssl: boolean;
writable: boolean;
destroyed: boolean;
private _upgrading;
private _upgraded;
private _cfSocket;
private _cfWriter;
private _cfReader;
constructor(ssl: boolean);
setNoDelay(): this;
setKeepAlive(): this;
ref(): this;
unref(): this;
connect(port: number, host: string, connectListener?: (...args: unknown[]) => void): Promise<this | undefined>;
_listen(): Promise<void>;
_listenOnce(): Promise<void>;
write(data: Uint8Array | string, encoding?: BufferEncoding, callback?: (...args: unknown[]) => void): true | void;
end(data?: Buffer, encoding?: BufferEncoding, callback?: (...args: unknown[]) => void): this;
destroy(reason: string): this;
startTls(options: TlsOptions): void;
_addClosedHandler(): void;
}

View File

@ -1,152 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CloudflareSocket = void 0;
const events_1 = require("events");
/**
* Wrapper around the Cloudflare built-in socket that can be used by the `Connection`.
*/
class CloudflareSocket extends events_1.EventEmitter {
constructor(ssl) {
super();
this.ssl = ssl;
this.writable = false;
this.destroyed = false;
this._upgrading = false;
this._upgraded = false;
this._cfSocket = null;
this._cfWriter = null;
this._cfReader = null;
}
setNoDelay() {
return this;
}
setKeepAlive() {
return this;
}
ref() {
return this;
}
unref() {
return this;
}
async connect(port, host, connectListener) {
try {
log('connecting');
if (connectListener)
this.once('connect', connectListener);
const options = this.ssl ? { secureTransport: 'starttls' } : {};
const mod = await import('cloudflare:sockets');
const connect = mod.connect;
this._cfSocket = connect(`${host}:${port}`, options);
this._cfWriter = this._cfSocket.writable.getWriter();
this._addClosedHandler();
this._cfReader = this._cfSocket.readable.getReader();
if (this.ssl) {
this._listenOnce().catch((e) => this.emit('error', e));
}
else {
this._listen().catch((e) => this.emit('error', e));
}
await this._cfWriter.ready;
log('socket ready');
this.writable = true;
this.emit('connect');
return this;
}
catch (e) {
this.emit('error', e);
}
}
async _listen() {
// eslint-disable-next-line no-constant-condition
while (true) {
log('awaiting receive from CF socket');
const { done, value } = await this._cfReader.read();
log('CF socket received:', done, value);
if (done) {
log('done');
break;
}
this.emit('data', Buffer.from(value));
}
}
async _listenOnce() {
log('awaiting first receive from CF socket');
const { done, value } = await this._cfReader.read();
log('First CF socket received:', done, value);
this.emit('data', Buffer.from(value));
}
write(data, encoding = 'utf8', callback = () => { }) {
if (data.length === 0)
return callback();
if (typeof data === 'string')
data = Buffer.from(data, encoding);
log('sending data direct:', data);
this._cfWriter.write(data).then(() => {
log('data sent');
callback();
}, (err) => {
log('send error', err);
callback(err);
});
return true;
}
end(data = Buffer.alloc(0), encoding = 'utf8', callback = () => { }) {
log('ending CF socket');
this.write(data, encoding, (err) => {
this._cfSocket.close();
if (callback)
callback(err);
});
return this;
}
destroy(reason) {
log('destroying CF socket', reason);
this.destroyed = true;
return this.end();
}
startTls(options) {
if (this._upgraded) {
// Don't try to upgrade again.
this.emit('error', 'Cannot call `startTls()` more than once on a socket');
return;
}
this._cfWriter.releaseLock();
this._cfReader.releaseLock();
this._upgrading = true;
this._cfSocket = this._cfSocket.startTls(options);
this._cfWriter = this._cfSocket.writable.getWriter();
this._cfReader = this._cfSocket.readable.getReader();
this._addClosedHandler();
this._listen().catch((e) => this.emit('error', e));
}
_addClosedHandler() {
this._cfSocket.closed.then(() => {
if (!this._upgrading) {
log('CF socket closed');
this._cfSocket = null;
this.emit('close');
}
else {
this._upgrading = false;
this._upgraded = true;
}
}).catch((e) => this.emit('error', e));
}
}
exports.CloudflareSocket = CloudflareSocket;
const debug = false;
function dump(data) {
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
const hex = Buffer.from(data).toString('hex');
const str = new TextDecoder().decode(data);
return `\n>>> STR: "${str.replace(/\n/g, '\\n')}"\n>>> HEX: ${hex}\n`;
}
else {
return data;
}
}
function log(...args) {
debug && console.log(...args.map(dump));
}
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,mCAAqC;AAErC;;GAEG;AACH,MAAa,gBAAiB,SAAQ,qBAAY;IAUhD,YAAqB,GAAY;QAC/B,KAAK,EAAE,CAAA;QADY,QAAG,GAAH,GAAG,CAAS;QATjC,aAAQ,GAAG,KAAK,CAAA;QAChB,cAAS,GAAG,KAAK,CAAA;QAET,eAAU,GAAG,KAAK,CAAA;QAClB,cAAS,GAAG,KAAK,CAAA;QACjB,cAAS,GAAkB,IAAI,CAAA;QAC/B,cAAS,GAAuC,IAAI,CAAA;QACpD,cAAS,GAAuC,IAAI,CAAA;IAI5D,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAA;IACb,CAAC;IACD,YAAY;QACV,OAAO,IAAI,CAAA;IACb,CAAC;IACD,GAAG;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,KAAK;QACH,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,IAAY,EAAE,eAA8C;QACtF,IAAI;YACF,GAAG,CAAC,YAAY,CAAC,CAAA;YACjB,IAAI,eAAe;gBAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAA;YAE1D,MAAM,OAAO,GAAkB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAC9E,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAA;YAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;YAC3B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,CAAA;YACpD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;YACpD,IAAI,CAAC,iBAAiB,EAAE,CAAA;YAExB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;YACpD,IAAI,IAAI,CAAC,GAAG,EAAE;gBACZ,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;aACvD;iBAAM;gBACL,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;aACnD;YAED,MAAM,IAAI,CAAC,SAAU,CAAC,KAAK,CAAA;YAC3B,GAAG,CAAC,cAAc,CAAC,CAAA;YACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;YACpB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YAEpB,OAAO,IAAI,CAAA;SACZ;QAAC,OAAO,CAAC,EAAE;YACV,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;SACtB;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,iDAAiD;QACjD,OAAO,IAAI,EAAE;YACX,GAAG,CAAC,iCAAiC,CAAC,CAAA;YACtC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,SAAU,CAAC,IAAI,EAAE,CAAA;YACpD,GAAG,CAAC,qBAAqB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;YACvC,IAAI,IAAI,EAAE;gBACR,GAAG,CAAC,MAAM,CAAC,CAAA;gBACX,MAAK;aACN;YACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;SACtC;IACH,CAAC;IAED,KAAK,CAAC,WAAW;QACf,GAAG,CAAC,uCAAuC,CAAC,CAAA;QAC5C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,SAAU,CAAC,IAAI,EAAE,CAAA;QACpD,GAAG,CAAC,2BAA2B,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;QAC7C,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACvC,CAAC;IAED,KAAK,CACH,IAAyB,EACzB,WAA2B,MAAM,EACjC,WAAyC,GAAG,EAAE,GAAE,CAAC;QAEjD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,EAAE,CAAA;QACxC,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAEhE,GAAG,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,SAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAC9B,GAAG,EAAE;YACH,GAAG,CAAC,WAAW,CAAC,CAAA;YAChB,QAAQ,EAAE,CAAA;QACZ,CAAC,EACD,CAAC,GAAG,EAAE,EAAE;YACN,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,CAAA;YACtB,QAAQ,CAAC,GAAG,CAAC,CAAA;QACf,CAAC,CACF,CAAA;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,WAA2B,MAAM,EAAE,WAAyC,GAAG,EAAE,GAAE,CAAC;QAC9G,GAAG,CAAC,kBAAkB,CAAC,CAAA;QACvB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;YACjC,IAAI,CAAC,SAAU,CAAC,KAAK,EAAE,CAAA;YACvB,IAAI,QAAQ;gBAAE,QAAQ,CAAC,GAAG,CAAC,CAAA;QAC7B,CAAC,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAA;QACnC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAA;IACnB,CAAC;IAED,QAAQ,CAAC,OAAmB;QAC1B,IAAI,IAAI,CAAC,SAAS,EAAE;YAClB,8BAA8B;YAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,qDAAqD,CAAC,CAAA;YACzE,OAAM;SACP;QACD,IAAI,CAAC,SAAU,CAAC,WAAW,EAAE,CAAA;QAC7B,IAAI,CAAC,SAAU,CAAC,WAAW,EAAE,CAAA;QAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;QACpD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;QACpD,IAAI,CAAC,iBAAiB,EAAE,CAAA;QACxB,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;IACpD,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,SAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YAC/B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;gBACpB,GAAG,CAAC,kBAAkB,CAAC,CAAA;gBACvB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;gBACrB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;aACnB;iBAAM;gBACL,IAAI,CAAC,UAAU,GAAG,KAAK,CAAA;gBACvB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;aACtB;QACH,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;IACxC,CAAC;CACF;AA/ID,4CA+IC;AAED,MAAM,KAAK,GAAG,KAAK,CAAA;AAEnB,SAAS,IAAI,CAAC,IAAa;IACzB,IAAI,IAAI,YAAY,UAAU,IAAI,IAAI,YAAY,WAAW,EAAE;QAC7D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC7C,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC1C,OAAO,eAAe,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,eAAe,GAAG,IAAI,CAAA;KACtE;SAAM;QACL,OAAO,IAAI,CAAA;KACZ;AACH,CAAC;AAED,SAAS,GAAG,CAAC,GAAG,IAAe;IAC7B,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;AACzC,CAAC"}

View File

@ -1,3 +0,0 @@
import cf from '../dist/index.js'
export const CloudflareSocket = cf.CloudflareSocket

View File

@ -1,38 +0,0 @@
{
"name": "pg-cloudflare",
"version": "1.2.7",
"description": "A socket implementation that can run on Cloudflare Workers using native TCP connections.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"devDependencies": {
"ts-node": "^8.5.4",
"typescript": "^4.0.3"
},
"exports": {
".": {
"workerd": {
"import": "./esm/index.mjs",
"require": "./dist/index.js"
},
"default": "./dist/empty.js"
}
},
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"prepublish": "yarn build",
"test": "echo e2e test in pg package"
},
"repository": {
"type": "git",
"url": "git://github.com/brianc/node-postgres.git",
"directory": "packages/pg-cloudflare"
},
"files": [
"/dist/*{js,ts,map}",
"/src",
"/esm"
],
"gitHead": "8f8e7315e8f7c1bb01e98fdb41c8c92585510782"
}

View File

@ -1,3 +0,0 @@
// This is an empty module that is served up when outside of a workerd environment
// See the `exports` field in package.json
export default {}

View File

@ -1,166 +0,0 @@
import { SocketOptions, Socket, TlsOptions } from 'cloudflare:sockets'
import { EventEmitter } from 'events'
/**
* Wrapper around the Cloudflare built-in socket that can be used by the `Connection`.
*/
export class CloudflareSocket extends EventEmitter {
writable = false
destroyed = false
private _upgrading = false
private _upgraded = false
private _cfSocket: Socket | null = null
private _cfWriter: WritableStreamDefaultWriter | null = null
private _cfReader: ReadableStreamDefaultReader | null = null
constructor(readonly ssl: boolean) {
super()
}
setNoDelay() {
return this
}
setKeepAlive() {
return this
}
ref() {
return this
}
unref() {
return this
}
async connect(port: number, host: string, connectListener?: (...args: unknown[]) => void) {
try {
log('connecting')
if (connectListener) this.once('connect', connectListener)
const options: SocketOptions = this.ssl ? { secureTransport: 'starttls' } : {}
const mod = await import('cloudflare:sockets')
const connect = mod.connect
this._cfSocket = connect(`${host}:${port}`, options)
this._cfWriter = this._cfSocket.writable.getWriter()
this._addClosedHandler()
this._cfReader = this._cfSocket.readable.getReader()
if (this.ssl) {
this._listenOnce().catch((e) => this.emit('error', e))
} else {
this._listen().catch((e) => this.emit('error', e))
}
await this._cfWriter!.ready
log('socket ready')
this.writable = true
this.emit('connect')
return this
} catch (e) {
this.emit('error', e)
}
}
async _listen() {
// eslint-disable-next-line no-constant-condition
while (true) {
log('awaiting receive from CF socket')
const { done, value } = await this._cfReader!.read()
log('CF socket received:', done, value)
if (done) {
log('done')
break
}
this.emit('data', Buffer.from(value))
}
}
async _listenOnce() {
log('awaiting first receive from CF socket')
const { done, value } = await this._cfReader!.read()
log('First CF socket received:', done, value)
this.emit('data', Buffer.from(value))
}
write(
data: Uint8Array | string,
encoding: BufferEncoding = 'utf8',
callback: (...args: unknown[]) => void = () => {}
) {
if (data.length === 0) return callback()
if (typeof data === 'string') data = Buffer.from(data, encoding)
log('sending data direct:', data)
this._cfWriter!.write(data).then(
() => {
log('data sent')
callback()
},
(err) => {
log('send error', err)
callback(err)
}
)
return true
}
end(data = Buffer.alloc(0), encoding: BufferEncoding = 'utf8', callback: (...args: unknown[]) => void = () => {}) {
log('ending CF socket')
this.write(data, encoding, (err) => {
this._cfSocket!.close()
if (callback) callback(err)
})
return this
}
destroy(reason: string) {
log('destroying CF socket', reason)
this.destroyed = true
return this.end()
}
startTls(options: TlsOptions) {
if (this._upgraded) {
// Don't try to upgrade again.
this.emit('error', 'Cannot call `startTls()` more than once on a socket')
return
}
this._cfWriter!.releaseLock()
this._cfReader!.releaseLock()
this._upgrading = true
this._cfSocket = this._cfSocket!.startTls(options)
this._cfWriter = this._cfSocket.writable.getWriter()
this._cfReader = this._cfSocket.readable.getReader()
this._addClosedHandler()
this._listen().catch((e) => this.emit('error', e))
}
_addClosedHandler() {
this._cfSocket!.closed.then(() => {
if (!this._upgrading) {
log('CF socket closed')
this._cfSocket = null
this.emit('close')
} else {
this._upgrading = false
this._upgraded = true
}
}).catch((e) => this.emit('error', e))
}
}
const debug = false
function dump(data: unknown) {
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
const hex = Buffer.from(data).toString('hex')
const str = new TextDecoder().decode(data)
return `\n>>> STR: "${str.replace(/\n/g, '\\n')}"\n>>> HEX: ${hex}\n`
} else {
return data
}
}
function log(...args: unknown[]) {
debug && console.log(...args.map(dump))
}

View File

@ -1,25 +0,0 @@
declare module 'cloudflare:sockets' {
export class Socket {
public readonly readable: any
public readonly writable: any
public readonly closed: Promise<void>
public close(): Promise<void>
public startTls(options: TlsOptions): Socket
}
export type TlsOptions = {
expectedServerHostname?: string
}
export type SocketAddress = {
hostname: string
port: number
}
export type SocketOptions = {
secureTransport?: 'off' | 'on' | 'starttls'
allowHalfOpen?: boolean
}
export function connect(address: string | SocketAddress, options?: SocketOptions): Socket
}

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Iced Development
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,105 +0,0 @@
pg-connection-string
====================
[![NPM](https://nodei.co/npm/pg-connection-string.png?compact=true)](https://nodei.co/npm/pg-connection-string/)
Functions for dealing with a PostgresSQL connection string
`parse` method taken from [node-postgres](https://github.com/brianc/node-postgres.git)
Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com)
MIT License
## Usage
```js
const parse = require('pg-connection-string').parse;
const config = parse('postgres://someuser:somepassword@somehost:381/somedatabase')
```
The resulting config contains a subset of the following properties:
* `user` - User with which to authenticate to the server
* `password` - Corresponding password
* `host` - Postgres server hostname or, for UNIX domain sockets, the socket filename
* `port` - port on which to connect
* `database` - Database name within the server
* `client_encoding` - string encoding the client will use
* `ssl`, either a boolean or an object with properties
* `rejectUnauthorized`
* `cert`
* `key`
* `ca`
* any other query parameters (for example, `application_name`) are preserved intact.
### ClientConfig Compatibility for TypeScript
The pg-connection-string `ConnectionOptions` interface is not compatible with the `ClientConfig` interface that [pg.Client](https://node-postgres.com/apis/client) expects. To remedy this, use the `parseIntoClientConfig` function instead of `parse`:
```ts
import { ClientConfig } from 'pg';
import { parseIntoClientConfig } from 'pg-connection-string';
const config: ClientConfig = parseIntoClientConfig('postgres://someuser:somepassword@somehost:381/somedatabase')
```
You can also use `toClientConfig` to convert an existing `ConnectionOptions` interface into a `ClientConfig` interface:
```ts
import { ClientConfig } from 'pg';
import { parse, toClientConfig } from 'pg-connection-string';
const config = parse('postgres://someuser:somepassword@somehost:381/somedatabase')
const clientConfig: ClientConfig = toClientConfig(config)
```
## Connection Strings
The short summary of acceptable URLs is:
* `socket:<path>?<query>` - UNIX domain socket
* `postgres://<user>:<password>@<host>:<port>/<database>?<query>` - TCP connection
But see below for more details.
### UNIX Domain Sockets
When user and password are not given, the socket path follows `socket:`, as in `socket:/var/run/pgsql`.
This form can be shortened to just a path: `/var/run/pgsql`.
When user and password are given, they are included in the typical URL positions, with an empty `host`, as in `socket://user:pass@/var/run/pgsql`.
Query parameters follow a `?` character, including the following special query parameters:
* `db=<database>` - sets the database name (urlencoded)
* `encoding=<encoding>` - sets the `client_encoding` property
### TCP Connections
TCP connections to the Postgres server are indicated with `pg:` or `postgres:` schemes (in fact, any scheme but `socket:` is accepted).
If username and password are included, they should be urlencoded.
The database name, however, should *not* be urlencoded.
Query parameters follow a `?` character, including the following special query parameters:
* `host=<host>` - sets `host` property, overriding the URL's host
* `encoding=<encoding>` - sets the `client_encoding` property
* `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly
* `uselibpqcompat=true` - use libpq semantics
* `sslmode=<sslmode>` when `uselibpqcompat=true` is not set
* `sslmode=disable` - sets `ssl` to false
* `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }`
* `sslmode=prefer`, `sslmode=require`, `sslmode=verify-ca`, `sslmode=verify-full` - sets `ssl` to true
* `sslmode=<sslmode>` when `uselibpqcompat=true`
* `sslmode=disable` - sets `ssl` to false
* `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }`
* `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca`
* `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity). This verifies the presented certificate against the effective CA specified in sslrootcert.
* `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity)
* `sslcert=<filename>` - reads data from the given file and includes the result as `ssl.cert`
* `sslkey=<filename>` - reads data from the given file and includes the result as `ssl.key`
* `sslrootcert=<filename>` - reads data from the given file and includes the result as `ssl.ca`
A bare relative URL, such as `salesdata`, will indicate a database name while leaving other properties empty.
> [!CAUTION]
> Choosing an sslmode other than verify-full has serious security implications. Please read https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS to understand the trade-offs.

View File

@ -1,8 +0,0 @@
// ESM wrapper for pg-connection-string
import connectionString from '../index.js'
// Re-export the parse function
export default connectionString.parse
export const parse = connectionString.parse
export const toClientConfig = connectionString.toClientConfig
export const parseIntoClientConfig = connectionString.parseIntoClientConfig

View File

@ -1,36 +0,0 @@
import { ClientConfig } from 'pg'
export function parse(connectionString: string, options?: Options): ConnectionOptions
export interface Options {
// Use libpq semantics when interpreting the connection string
useLibpqCompat?: boolean
}
interface SSLConfig {
ca?: string
cert?: string | null
key?: string
rejectUnauthorized?: boolean
}
export interface ConnectionOptions {
host: string | null
password?: string
user?: string
port?: string | null
database: string | null | undefined
client_encoding?: string
ssl?: boolean | string | SSLConfig
application_name?: string
fallback_application_name?: string
options?: string
keepalives?: number
// We allow any other options to be passed through
[key: string]: unknown
}
export function toClientConfig(config: ConnectionOptions): ClientConfig
export function parseIntoClientConfig(connectionString: string): ClientConfig

View File

@ -1,213 +0,0 @@
'use strict'
//Parse method copied from https://github.com/brianc/node-postgres
//Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com)
//MIT License
//parses a connection string
function parse(str, options = {}) {
//unix socket
if (str.charAt(0) === '/') {
const config = str.split(' ')
return { host: config[0], database: config[1] }
}
// Check for empty host in URL
const config = {}
let result
let dummyHost = false
if (/ |%[^a-f0-9]|%[a-f0-9][^a-f0-9]/i.test(str)) {
// Ensure spaces are encoded as %20
str = encodeURI(str).replace(/%25(\d\d)/g, '%$1')
}
try {
try {
result = new URL(str, 'postgres://base')
} catch (e) {
// The URL is invalid so try again with a dummy host
result = new URL(str.replace('@/', '@___DUMMY___/'), 'postgres://base')
dummyHost = true
}
} catch (err) {
// Remove the input from the error message to avoid leaking sensitive information
err.input && (err.input = '*****REDACTED*****')
}
// We'd like to use Object.fromEntries() here but Node.js 10 does not support it
for (const entry of result.searchParams.entries()) {
config[entry[0]] = entry[1]
}
config.user = config.user || decodeURIComponent(result.username)
config.password = config.password || decodeURIComponent(result.password)
if (result.protocol == 'socket:') {
config.host = decodeURI(result.pathname)
config.database = result.searchParams.get('db')
config.client_encoding = result.searchParams.get('encoding')
return config
}
const hostname = dummyHost ? '' : result.hostname
if (!config.host) {
// Only set the host if there is no equivalent query param.
config.host = decodeURIComponent(hostname)
} else if (hostname && /^%2f/i.test(hostname)) {
// Only prepend the hostname to the pathname if it is not a URL encoded Unix socket host.
result.pathname = hostname + result.pathname
}
if (!config.port) {
// Only set the port if there is no equivalent query param.
config.port = result.port
}
const pathname = result.pathname.slice(1) || null
config.database = pathname ? decodeURI(pathname) : null
if (config.ssl === 'true' || config.ssl === '1') {
config.ssl = true
}
if (config.ssl === '0') {
config.ssl = false
}
if (config.sslcert || config.sslkey || config.sslrootcert || config.sslmode) {
config.ssl = {}
}
// Only try to load fs if we expect to read from the disk
const fs = config.sslcert || config.sslkey || config.sslrootcert ? require('fs') : null
if (config.sslcert) {
config.ssl.cert = fs.readFileSync(config.sslcert).toString()
}
if (config.sslkey) {
config.ssl.key = fs.readFileSync(config.sslkey).toString()
}
if (config.sslrootcert) {
config.ssl.ca = fs.readFileSync(config.sslrootcert).toString()
}
if (options.useLibpqCompat && config.uselibpqcompat) {
throw new Error('Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.')
}
if (config.uselibpqcompat === 'true' || options.useLibpqCompat) {
switch (config.sslmode) {
case 'disable': {
config.ssl = false
break
}
case 'prefer': {
config.ssl.rejectUnauthorized = false
break
}
case 'require': {
if (config.sslrootcert) {
// If a root CA is specified, behavior of `sslmode=require` will be the same as that of `verify-ca`
config.ssl.checkServerIdentity = function () {}
} else {
config.ssl.rejectUnauthorized = false
}
break
}
case 'verify-ca': {
if (!config.ssl.ca) {
throw new Error(
'SECURITY WARNING: Using sslmode=verify-ca requires specifying a CA with sslrootcert. If a public CA is used, verify-ca allows connections to a server that somebody else may have registered with the CA, making you vulnerable to Man-in-the-Middle attacks. Either specify a custom CA certificate with sslrootcert parameter or use sslmode=verify-full for proper security.'
)
}
config.ssl.checkServerIdentity = function () {}
break
}
case 'verify-full': {
break
}
}
} else {
switch (config.sslmode) {
case 'disable': {
config.ssl = false
break
}
case 'prefer':
case 'require':
case 'verify-ca':
case 'verify-full': {
break
}
case 'no-verify': {
config.ssl.rejectUnauthorized = false
break
}
}
}
return config
}
// convert pg-connection-string ssl config to a ClientConfig.ConnectionOptions
function toConnectionOptions(sslConfig) {
const connectionOptions = Object.entries(sslConfig).reduce((c, [key, value]) => {
// we explicitly check for undefined and null instead of `if (value)` because some
// options accept falsy values. Example: `ssl.rejectUnauthorized = false`
if (value !== undefined && value !== null) {
c[key] = value
}
return c
}, {})
return connectionOptions
}
// convert pg-connection-string config to a ClientConfig
function toClientConfig(config) {
const poolConfig = Object.entries(config).reduce((c, [key, value]) => {
if (key === 'ssl') {
const sslConfig = value
if (typeof sslConfig === 'boolean') {
c[key] = sslConfig
}
if (typeof sslConfig === 'object') {
c[key] = toConnectionOptions(sslConfig)
}
} else if (value !== undefined && value !== null) {
if (key === 'port') {
// when port is not specified, it is converted into an empty string
// we want to avoid NaN or empty string as a values in ClientConfig
if (value !== '') {
const v = parseInt(value, 10)
if (isNaN(v)) {
throw new Error(`Invalid ${key}: ${value}`)
}
c[key] = v
}
} else {
c[key] = value
}
}
return c
}, {})
return poolConfig
}
// parses a connection string into ClientConfig
function parseIntoClientConfig(str) {
return toClientConfig(parse(str))
}
module.exports = parse
parse.parse = parse
parse.toClientConfig = toClientConfig
parse.parseIntoClientConfig = parseIntoClientConfig

View File

@ -1,52 +0,0 @@
{
"name": "pg-connection-string",
"version": "2.9.1",
"description": "Functions for dealing with a PostgresSQL connection string",
"main": "./index.js",
"types": "./index.d.ts",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./esm/index.mjs",
"require": "./index.js",
"default": "./index.js"
}
},
"scripts": {
"test": "nyc --reporter=lcov mocha && npm run check-coverage",
"check-coverage": "nyc check-coverage --statements 100 --branches 100 --lines 100 --functions 100"
},
"repository": {
"type": "git",
"url": "git://github.com/brianc/node-postgres.git",
"directory": "packages/pg-connection-string"
},
"keywords": [
"pg",
"connection",
"string",
"parse"
],
"author": "Blaine Bublitz <blaine@iceddev.com> (http://iceddev.com/)",
"license": "MIT",
"bugs": {
"url": "https://github.com/brianc/node-postgres/issues"
},
"homepage": "https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string",
"devDependencies": {
"@types/pg": "^8.12.0",
"chai": "^4.1.1",
"coveralls": "^3.0.4",
"istanbul": "^0.4.5",
"mocha": "^10.5.2",
"nyc": "^15",
"tsx": "^4.19.4",
"typescript": "^4.0.3"
},
"files": [
"index.js",
"index.d.ts",
"esm"
],
"gitHead": "cd877a57612a39335a97b593111710d26126279d"
}

13
node_modules/pg-int8/LICENSE generated vendored
View File

@ -1,13 +0,0 @@
Copyright © 2017, Charmander <~@charmander.me>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

16
node_modules/pg-int8/README.md generated vendored
View File

@ -1,16 +0,0 @@
[![Build status][ci image]][ci]
64-bit big-endian signed integer-to-string conversion designed for [pg][].
```js
const readInt8 = require('pg-int8');
readInt8(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]))
// '283686952306183'
```
[pg]: https://github.com/brianc/node-postgres
[ci]: https://travis-ci.org/charmander/pg-int8
[ci image]: https://api.travis-ci.org/charmander/pg-int8.svg

100
node_modules/pg-int8/index.js generated vendored
View File

@ -1,100 +0,0 @@
'use strict';
// selected so (BASE - 1) * 0x100000000 + 0xffffffff is a safe integer
var BASE = 1000000;
function readInt8(buffer) {
var high = buffer.readInt32BE(0);
var low = buffer.readUInt32BE(4);
var sign = '';
if (high < 0) {
high = ~high + (low === 0);
low = (~low + 1) >>> 0;
sign = '-';
}
var result = '';
var carry;
var t;
var digits;
var pad;
var l;
var i;
{
carry = high % BASE;
high = high / BASE >>> 0;
t = 0x100000000 * carry + low;
low = t / BASE >>> 0;
digits = '' + (t - BASE * low);
if (low === 0 && high === 0) {
return sign + digits + result;
}
pad = '';
l = 6 - digits.length;
for (i = 0; i < l; i++) {
pad += '0';
}
result = pad + digits + result;
}
{
carry = high % BASE;
high = high / BASE >>> 0;
t = 0x100000000 * carry + low;
low = t / BASE >>> 0;
digits = '' + (t - BASE * low);
if (low === 0 && high === 0) {
return sign + digits + result;
}
pad = '';
l = 6 - digits.length;
for (i = 0; i < l; i++) {
pad += '0';
}
result = pad + digits + result;
}
{
carry = high % BASE;
high = high / BASE >>> 0;
t = 0x100000000 * carry + low;
low = t / BASE >>> 0;
digits = '' + (t - BASE * low);
if (low === 0 && high === 0) {
return sign + digits + result;
}
pad = '';
l = 6 - digits.length;
for (i = 0; i < l; i++) {
pad += '0';
}
result = pad + digits + result;
}
{
carry = high % BASE;
t = 0x100000000 * carry + low;
digits = '' + t % BASE;
return sign + digits + result;
}
}
module.exports = readInt8;

24
node_modules/pg-int8/package.json generated vendored
View File

@ -1,24 +0,0 @@
{
"name": "pg-int8",
"version": "1.0.1",
"description": "64-bit big-endian signed integer-to-string conversion",
"bugs": "https://github.com/charmander/pg-int8/issues",
"license": "ISC",
"files": [
"index.js"
],
"repository": {
"type": "git",
"url": "https://github.com/charmander/pg-int8"
},
"scripts": {
"test": "tap test"
},
"devDependencies": {
"@charmander/eslint-config-base": "1.0.2",
"tap": "10.7.3"
},
"engines": {
"node": ">=4.0.0"
}
}

21
node_modules/pg-pool/LICENSE generated vendored
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Brian M. Carlson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

376
node_modules/pg-pool/README.md generated vendored
View File

@ -1,376 +0,0 @@
# pg-pool
[![Build Status](https://travis-ci.org/brianc/node-pg-pool.svg?branch=master)](https://travis-ci.org/brianc/node-pg-pool)
A connection pool for node-postgres
## install
```sh
npm i pg-pool pg
```
## use
### create
to use pg-pool you must first create an instance of a pool
```js
const Pool = require('pg-pool')
// by default the pool uses the same
// configuration as whatever `pg` version you have installed
const pool = new Pool()
// you can pass properties to the pool
// these properties are passed unchanged to both the node-postgres Client constructor
// and the node-pool (https://github.com/coopernurse/node-pool) constructor
// allowing you to fully configure the behavior of both
const pool2 = new Pool({
database: 'postgres',
user: 'brianc',
password: 'secret!',
port: 5432,
ssl: true,
max: 20, // set pool max size to 20
idleTimeoutMillis: 1000, // close idle clients after 1 second
connectionTimeoutMillis: 1000, // return an error after 1 second if connection could not be established
maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion)
})
// you can supply a custom client constructor
// if you want to use the native postgres client
const NativeClient = require('pg').native.Client
const nativePool = new Pool({ Client: NativeClient })
// you can even pool pg-native clients directly
const PgNativeClient = require('pg-native')
const pgNativePool = new Pool({ Client: PgNativeClient })
```
##### Note:
The Pool constructor does not support passing a Database URL as the parameter. To use pg-pool on heroku, for example, you need to parse the URL into a config object. Here is an example of how to parse a Database URL.
```js
const Pool = require('pg-pool');
const url = require('url')
const params = url.parse(process.env.DATABASE_URL);
const auth = params.auth.split(':');
const config = {
user: auth[0],
password: auth[1],
host: params.hostname,
port: params.port,
database: params.pathname.split('/')[1],
ssl: true
};
const pool = new Pool(config);
/*
Transforms, 'postgres://DBuser:secret@DBHost:#####/myDB', into
config = {
user: 'DBuser',
password: 'secret',
host: 'DBHost',
port: '#####',
database: 'myDB',
ssl: true
}
*/
```
### acquire clients with a promise
pg-pool supports a fully promise-based api for acquiring clients
```js
const pool = new Pool()
pool.connect().then(client => {
client.query('select $1::text as name', ['pg-pool']).then(res => {
client.release()
console.log('hello from', res.rows[0].name)
})
.catch(e => {
client.release()
console.error('query error', e.message, e.stack)
})
})
```
### plays nice with async/await
this ends up looking much nicer if you're using [co](https://github.com/tj/co) or async/await:
```js
// with async/await
(async () => {
const pool = new Pool()
const client = await pool.connect()
try {
const result = await client.query('select $1::text as name', ['brianc'])
console.log('hello from', result.rows[0])
} finally {
client.release()
}
})().catch(e => console.error(e.message, e.stack))
// with co
co(function * () {
const client = yield pool.connect()
try {
const result = yield client.query('select $1::text as name', ['brianc'])
console.log('hello from', result.rows[0])
} finally {
client.release()
}
}).catch(e => console.error(e.message, e.stack))
```
### your new favorite helper method
because its so common to just run a query and return the client to the pool afterward pg-pool has this built-in:
```js
const pool = new Pool()
const time = await pool.query('SELECT NOW()')
const name = await pool.query('select $1::text as name', ['brianc'])
console.log(name.rows[0].name, 'says hello at', time.rows[0].now)
```
you can also use a callback here if you'd like:
```js
const pool = new Pool()
pool.query('SELECT $1::text as name', ['brianc'], function (err, res) {
console.log(res.rows[0].name) // brianc
})
```
__pro tip:__ unless you need to run a transaction (which requires a single client for multiple queries) or you
have some other edge case like [streaming rows](https://github.com/brianc/node-pg-query-stream) or using a [cursor](https://github.com/brianc/node-pg-cursor)
you should almost always just use `pool.query`. Its easy, it does the right thing :tm:, and wont ever forget to return
clients back to the pool after the query is done.
### drop-in backwards compatible
pg-pool still and will always support the traditional callback api for acquiring a client. This is the exact API node-postgres has shipped with for years:
```js
const pool = new Pool()
pool.connect((err, client, done) => {
if (err) return done(err)
client.query('SELECT $1::text as name', ['pg-pool'], (err, res) => {
done()
if (err) {
return console.error('query error', err.message, err.stack)
}
console.log('hello from', res.rows[0].name)
})
})
```
### shut it down
When you are finished with the pool if all the clients are idle the pool will close them after `config.idleTimeoutMillis` and your app
will shutdown gracefully. If you don't want to wait for the timeout you can end the pool as follows:
```js
const pool = new Pool()
const client = await pool.connect()
console.log(await client.query('select now()'))
client.release()
await pool.end()
```
### a note on instances
The pool should be a __long-lived object__ in your application. Generally you'll want to instantiate one pool when your app starts up and use the same instance of the pool throughout the lifetime of your application. If you are frequently creating a new pool within your code you likely don't have your pool initialization code in the correct place. Example:
```js
// assume this is a file in your program at ./your-app/lib/db.js
// correct usage: create the pool and let it live
// 'globally' here, controlling access to it through exported methods
const pool = new pg.Pool()
// this is the right way to export the query method
module.exports.query = (text, values) => {
console.log('query:', text, values)
return pool.query(text, values)
}
// this would be the WRONG way to export the connect method
module.exports.connect = () => {
// notice how we would be creating a pool instance here
// every time we called 'connect' to get a new client?
// that's a bad thing & results in creating an unbounded
// number of pools & therefore connections
const aPool = new pg.Pool()
return aPool.connect()
}
```
### events
Every instance of a `Pool` is an event emitter. These instances emit the following events:
#### error
Emitted whenever an idle client in the pool encounters an error. This is common when your PostgreSQL server shuts down, reboots, or a network partition otherwise causes it to become unavailable while your pool has connected clients.
Example:
```js
const Pool = require('pg-pool')
const pool = new Pool()
// attach an error handler to the pool for when a connected, idle client
// receives an error by being disconnected, etc
pool.on('error', function(error, client) {
// handle this in the same way you would treat process.on('uncaughtException')
// it is supplied the error as well as the idle client which received the error
})
```
#### connect
Fired whenever the pool creates a __new__ `pg.Client` instance and successfully connects it to the backend.
Example:
```js
const Pool = require('pg-pool')
const pool = new Pool()
const count = 0
pool.on('connect', client => {
client.count = count++
})
pool
.connect()
.then(client => {
return client
.query('SELECT $1::int AS "clientCount"', [client.count])
.then(res => console.log(res.rows[0].clientCount)) // outputs 0
.then(() => client)
})
.then(client => client.release())
```
#### acquire
Fired whenever a client is acquired from the pool
Example:
This allows you to count the number of clients which have ever been acquired from the pool.
```js
const Pool = require('pg-pool')
const pool = new Pool()
const acquireCount = 0
pool.on('acquire', function (client) {
acquireCount++
})
const connectCount = 0
pool.on('connect', function () {
connectCount++
})
for (let i = 0; i < 200; i++) {
pool.query('SELECT NOW()')
}
setTimeout(function () {
console.log('connect count:', connectCount) // output: connect count: 10
console.log('acquire count:', acquireCount) // output: acquire count: 200
}, 100)
```
### environment variables
pg-pool & node-postgres support some of the same environment variables as `psql` supports. The most common are:
```
PGDATABASE=my_db
PGUSER=username
PGPASSWORD="my awesome password"
PGPORT=5432
PGSSLMODE=require
```
Usually I will export these into my local environment via a `.env` file with environment settings or export them in `~/.bash_profile` or something similar. This way I get configurability which works with both the postgres suite of tools (`psql`, `pg_dump`, `pg_restore`) and node, I can vary the environment variables locally and in production, and it supports the concept of a [12-factor app](http://12factor.net/) out of the box.
## bring your own promise
In versions of node `<=0.12.x` there is no native promise implementation available globally. You can polyfill the promise globally like this:
```js
// first run `npm install promise-polyfill --save
if (typeof Promise == 'undefined') {
global.Promise = require('promise-polyfill')
}
```
You can use any other promise implementation you'd like. The pool also allows you to configure the promise implementation on a per-pool level:
```js
const bluebirdPool = new Pool({
Promise: require('bluebird')
})
```
__please note:__ in node `<=0.12.x` the pool will throw if you do not provide a promise constructor in one of the two ways mentioned above. In node `>=4.0.0` the pool will use the native promise implementation by default; however, the two methods above still allow you to "bring your own."
## maxUses and read-replica autoscaling (e.g. AWS Aurora)
The maxUses config option can help an application instance rebalance load against a replica set that has been auto-scaled after the connection pool is already full of healthy connections.
The mechanism here is that a connection is considered "expended" after it has been acquired and released `maxUses` number of times. Depending on the load on your system, this means there will be an approximate time in which any given connection will live, thus creating a window for rebalancing.
Imagine a scenario where you have 10 app instances providing an API running against a replica cluster of 3 that are accessed via a round-robin DNS entry. Each instance runs a connection pool size of 20. With an ambient load of 50 requests per second, the connection pool will likely fill up in a few minutes with healthy connections.
If you have weekly bursts of traffic which peak at 1,000 requests per second, you might want to grow your replicas to 10 during this period. Without setting `maxUses`, the new replicas will not be adopted by the app servers without an intervention -- namely, restarting each in turn in order to build up new connection pools that are balanced against all the replicas. Adding additional app server instances will help to some extent because they will adopt all the replicas in an even way, but the initial app servers will continue to focus additional load on the original replicas.
This is where the `maxUses` configuration option comes into play. Setting `maxUses` to 7500 will ensure that over a period of 30 minutes or so the new replicas will be adopted as the pre-existing connections are closed and replaced with new ones, thus creating a window for eventual balance.
You'll want to test based on your own scenarios, but one way to make a first guess at `maxUses` is to identify an acceptable window for rebalancing and then solve for the value:
```
maxUses = rebalanceWindowSeconds * totalRequestsPerSecond / numAppInstances / poolSize
```
In the example above, assuming we acquire and release 1 connection per request and we are aiming for a 30 minute rebalancing window:
```
maxUses = rebalanceWindowSeconds * totalRequestsPerSecond / numAppInstances / poolSize
7200 = 1800 * 1000 / 10 / 25
```
## tests
To run tests clone the repo, `npm i` in the working dir, and then run `npm test`
## contributions
I love contributions. Please make sure they have tests, and submit a PR. If you're not sure if the issue is worth it or will be accepted it never hurts to open an issue to begin the conversation. If you're interested in keeping up with node-postgres releated stuff, you can follow me on twitter at [@briancarlson](https://twitter.com/briancarlson) - I generally announce any noteworthy updates there.
## license
The MIT License (MIT)
Copyright (c) 2016 Brian M. Carlson
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

5
node_modules/pg-pool/esm/index.mjs generated vendored
View File

@ -1,5 +0,0 @@
// ESM wrapper for pg-pool
import Pool from '../index.js'
// Export as default only to match CJS module
export default Pool

479
node_modules/pg-pool/index.js generated vendored
View File

@ -1,479 +0,0 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const NOOP = function () {}
const removeWhere = (list, predicate) => {
const i = list.findIndex(predicate)
return i === -1 ? undefined : list.splice(i, 1)[0]
}
class IdleItem {
constructor(client, idleListener, timeoutId) {
this.client = client
this.idleListener = idleListener
this.timeoutId = timeoutId
}
}
class PendingItem {
constructor(callback) {
this.callback = callback
}
}
function throwOnDoubleRelease() {
throw new Error('Release called on client which has already been released to the pool.')
}
function promisify(Promise, callback) {
if (callback) {
return { callback: callback, result: undefined }
}
let rej
let res
const cb = function (err, client) {
err ? rej(err) : res(client)
}
const result = new Promise(function (resolve, reject) {
res = resolve
rej = reject
}).catch((err) => {
// replace the stack trace that leads to `TCP.onStreamRead` with one that leads back to the
// application that created the query
Error.captureStackTrace(err)
throw err
})
return { callback: cb, result: result }
}
function makeIdleListener(pool, client) {
return function idleListener(err) {
err.client = client
client.removeListener('error', idleListener)
client.on('error', () => {
pool.log('additional client error after disconnection due to error', err)
})
pool._remove(client)
// TODO - document that once the pool emits an error
// the client has already been closed & purged and is unusable
pool.emit('error', err, client)
}
}
class Pool extends EventEmitter {
constructor(options, Client) {
super()
this.options = Object.assign({}, options)
if (options != null && 'password' in options) {
// "hiding" the password so it doesn't show up in stack traces
// or if the client is console.logged
Object.defineProperty(this.options, 'password', {
configurable: true,
enumerable: false,
writable: true,
value: options.password,
})
}
if (options != null && options.ssl && options.ssl.key) {
// "hiding" the ssl->key so it doesn't show up in stack traces
// or if the client is console.logged
Object.defineProperty(this.options.ssl, 'key', {
enumerable: false,
})
}
this.options.max = this.options.max || this.options.poolSize || 10
this.options.min = this.options.min || 0
this.options.maxUses = this.options.maxUses || Infinity
this.options.allowExitOnIdle = this.options.allowExitOnIdle || false
this.options.maxLifetimeSeconds = this.options.maxLifetimeSeconds || 0
this.log = this.options.log || function () {}
this.Client = this.options.Client || Client || require('pg').Client
this.Promise = this.options.Promise || global.Promise
if (typeof this.options.idleTimeoutMillis === 'undefined') {
this.options.idleTimeoutMillis = 10000
}
this._clients = []
this._idle = []
this._expired = new WeakSet()
this._pendingQueue = []
this._endCallback = undefined
this.ending = false
this.ended = false
}
_isFull() {
return this._clients.length >= this.options.max
}
_isAboveMin() {
return this._clients.length > this.options.min
}
_pulseQueue() {
this.log('pulse queue')
if (this.ended) {
this.log('pulse queue ended')
return
}
if (this.ending) {
this.log('pulse queue on ending')
if (this._idle.length) {
this._idle.slice().map((item) => {
this._remove(item.client)
})
}
if (!this._clients.length) {
this.ended = true
this._endCallback()
}
return
}
// if we don't have any waiting, do nothing
if (!this._pendingQueue.length) {
this.log('no queued requests')
return
}
// if we don't have any idle clients and we have no more room do nothing
if (!this._idle.length && this._isFull()) {
return
}
const pendingItem = this._pendingQueue.shift()
if (this._idle.length) {
const idleItem = this._idle.pop()
clearTimeout(idleItem.timeoutId)
const client = idleItem.client
client.ref && client.ref()
const idleListener = idleItem.idleListener
return this._acquireClient(client, pendingItem, idleListener, false)
}
if (!this._isFull()) {
return this.newClient(pendingItem)
}
throw new Error('unexpected condition')
}
_remove(client, callback) {
const removed = removeWhere(this._idle, (item) => item.client === client)
if (removed !== undefined) {
clearTimeout(removed.timeoutId)
}
this._clients = this._clients.filter((c) => c !== client)
const context = this
client.end(() => {
context.emit('remove', client)
if (typeof callback === 'function') {
callback()
}
})
}
connect(cb) {
if (this.ending) {
const err = new Error('Cannot use a pool after calling end on the pool')
return cb ? cb(err) : this.Promise.reject(err)
}
const response = promisify(this.Promise, cb)
const result = response.result
// if we don't have to connect a new client, don't do so
if (this._isFull() || this._idle.length) {
// if we have idle clients schedule a pulse immediately
if (this._idle.length) {
process.nextTick(() => this._pulseQueue())
}
if (!this.options.connectionTimeoutMillis) {
this._pendingQueue.push(new PendingItem(response.callback))
return result
}
const queueCallback = (err, res, done) => {
clearTimeout(tid)
response.callback(err, res, done)
}
const pendingItem = new PendingItem(queueCallback)
// set connection timeout on checking out an existing client
const tid = setTimeout(() => {
// remove the callback from pending waiters because
// we're going to call it with a timeout error
removeWhere(this._pendingQueue, (i) => i.callback === queueCallback)
pendingItem.timedOut = true
response.callback(new Error('timeout exceeded when trying to connect'))
}, this.options.connectionTimeoutMillis)
if (tid.unref) {
tid.unref()
}
this._pendingQueue.push(pendingItem)
return result
}
this.newClient(new PendingItem(response.callback))
return result
}
newClient(pendingItem) {
const client = new this.Client(this.options)
this._clients.push(client)
const idleListener = makeIdleListener(this, client)
this.log('checking client timeout')
// connection timeout logic
let tid
let timeoutHit = false
if (this.options.connectionTimeoutMillis) {
tid = setTimeout(() => {
this.log('ending client due to timeout')
timeoutHit = true
// force kill the node driver, and let libpq do its teardown
client.connection ? client.connection.stream.destroy() : client.end()
}, this.options.connectionTimeoutMillis)
}
this.log('connecting new client')
client.connect((err) => {
if (tid) {
clearTimeout(tid)
}
client.on('error', idleListener)
if (err) {
this.log('client failed to connect', err)
// remove the dead client from our list of clients
this._clients = this._clients.filter((c) => c !== client)
if (timeoutHit) {
err = new Error('Connection terminated due to connection timeout', { cause: err })
}
// this client wont be released, so move on immediately
this._pulseQueue()
if (!pendingItem.timedOut) {
pendingItem.callback(err, undefined, NOOP)
}
} else {
this.log('new client connected')
if (this.options.maxLifetimeSeconds !== 0) {
const maxLifetimeTimeout = setTimeout(() => {
this.log('ending client due to expired lifetime')
this._expired.add(client)
const idleIndex = this._idle.findIndex((idleItem) => idleItem.client === client)
if (idleIndex !== -1) {
this._acquireClient(
client,
new PendingItem((err, client, clientRelease) => clientRelease()),
idleListener,
false
)
}
}, this.options.maxLifetimeSeconds * 1000)
maxLifetimeTimeout.unref()
client.once('end', () => clearTimeout(maxLifetimeTimeout))
}
return this._acquireClient(client, pendingItem, idleListener, true)
}
})
}
// acquire a client for a pending work item
_acquireClient(client, pendingItem, idleListener, isNew) {
if (isNew) {
this.emit('connect', client)
}
this.emit('acquire', client)
client.release = this._releaseOnce(client, idleListener)
client.removeListener('error', idleListener)
if (!pendingItem.timedOut) {
if (isNew && this.options.verify) {
this.options.verify(client, (err) => {
if (err) {
client.release(err)
return pendingItem.callback(err, undefined, NOOP)
}
pendingItem.callback(undefined, client, client.release)
})
} else {
pendingItem.callback(undefined, client, client.release)
}
} else {
if (isNew && this.options.verify) {
this.options.verify(client, client.release)
} else {
client.release()
}
}
}
// returns a function that wraps _release and throws if called more than once
_releaseOnce(client, idleListener) {
let released = false
return (err) => {
if (released) {
throwOnDoubleRelease()
}
released = true
this._release(client, idleListener, err)
}
}
// release a client back to the poll, include an error
// to remove it from the pool
_release(client, idleListener, err) {
client.on('error', idleListener)
client._poolUseCount = (client._poolUseCount || 0) + 1
this.emit('release', err, client)
// TODO(bmc): expose a proper, public interface _queryable and _ending
if (err || this.ending || !client._queryable || client._ending || client._poolUseCount >= this.options.maxUses) {
if (client._poolUseCount >= this.options.maxUses) {
this.log('remove expended client')
}
return this._remove(client, this._pulseQueue.bind(this))
}
const isExpired = this._expired.has(client)
if (isExpired) {
this.log('remove expired client')
this._expired.delete(client)
return this._remove(client, this._pulseQueue.bind(this))
}
// idle timeout
let tid
if (this.options.idleTimeoutMillis && this._isAboveMin()) {
tid = setTimeout(() => {
this.log('remove idle client')
this._remove(client, this._pulseQueue.bind(this))
}, this.options.idleTimeoutMillis)
if (this.options.allowExitOnIdle) {
// allow Node to exit if this is all that's left
tid.unref()
}
}
if (this.options.allowExitOnIdle) {
client.unref()
}
this._idle.push(new IdleItem(client, idleListener, tid))
this._pulseQueue()
}
query(text, values, cb) {
// guard clause against passing a function as the first parameter
if (typeof text === 'function') {
const response = promisify(this.Promise, text)
setImmediate(function () {
return response.callback(new Error('Passing a function as the first parameter to pool.query is not supported'))
})
return response.result
}
// allow plain text query without values
if (typeof values === 'function') {
cb = values
values = undefined
}
const response = promisify(this.Promise, cb)
cb = response.callback
this.connect((err, client) => {
if (err) {
return cb(err)
}
let clientReleased = false
const onError = (err) => {
if (clientReleased) {
return
}
clientReleased = true
client.release(err)
cb(err)
}
client.once('error', onError)
this.log('dispatching query')
try {
client.query(text, values, (err, res) => {
this.log('query dispatched')
client.removeListener('error', onError)
if (clientReleased) {
return
}
clientReleased = true
client.release(err)
if (err) {
return cb(err)
}
return cb(undefined, res)
})
} catch (err) {
client.release(err)
return cb(err)
}
})
return response.result
}
end(cb) {
this.log('ending')
if (this.ending) {
const err = new Error('Called end on pool more than once')
return cb ? cb(err) : this.Promise.reject(err)
}
this.ending = true
const promised = promisify(this.Promise, cb)
this._endCallback = promised.callback
this._pulseQueue()
return promised.result
}
get waitingCount() {
return this._pendingQueue.length
}
get idleCount() {
return this._idle.length
}
get expiredCount() {
return this._clients.reduce((acc, client) => acc + (this._expired.has(client) ? 1 : 0), 0)
}
get totalCount() {
return this._clients.length
}
}
module.exports = Pool

51
node_modules/pg-pool/package.json generated vendored
View File

@ -1,51 +0,0 @@
{
"name": "pg-pool",
"version": "3.10.1",
"description": "Connection pool for node-postgres",
"main": "index.js",
"exports": {
".": {
"import": "./esm/index.mjs",
"require": "./index.js",
"default": "./index.js"
}
},
"directories": {
"test": "test"
},
"scripts": {
"test": " node_modules/.bin/mocha"
},
"repository": {
"type": "git",
"url": "git://github.com/brianc/node-postgres.git",
"directory": "packages/pg-pool"
},
"keywords": [
"pg",
"postgres",
"pool",
"database"
],
"author": "Brian M. Carlson",
"license": "MIT",
"bugs": {
"url": "https://github.com/brianc/node-pg-pool/issues"
},
"homepage": "https://github.com/brianc/node-pg-pool#readme",
"devDependencies": {
"bluebird": "3.7.2",
"co": "4.6.0",
"expect.js": "0.3.1",
"lodash": "^4.17.11",
"mocha": "^10.5.2"
},
"peerDependencies": {
"pg": ">=8.0"
},
"files": [
"index.js",
"esm"
],
"gitHead": "cd877a57612a39335a97b593111710d26126279d"
}

21
node_modules/pg-protocol/LICENSE generated vendored
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2010 - 2021 Brian Carlson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
node_modules/pg-protocol/README.md generated vendored
View File

@ -1,3 +0,0 @@
# pg-protocol
Low level postgres wire protocol parser and serializer written in Typescript. Used by node-postgres. Needs more documentation. :smile:

View File

@ -1 +0,0 @@
export {};

23
node_modules/pg-protocol/dist/b.js generated vendored
View File

@ -1,23 +0,0 @@
"use strict";
// file for microbenchmarking
Object.defineProperty(exports, "__esModule", { value: true });
const buffer_reader_1 = require("./buffer-reader");
const LOOPS = 1000;
let count = 0;
const start = performance.now();
const reader = new buffer_reader_1.BufferReader();
const buffer = Buffer.from([33, 33, 33, 33, 33, 33, 33, 0]);
const run = () => {
if (count > LOOPS) {
console.log(performance.now() - start);
return;
}
count++;
for (let i = 0; i < LOOPS; i++) {
reader.setBuffer(0, buffer);
reader.cstring();
}
setImmediate(run);
};
run();
//# sourceMappingURL=b.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"b.js","sourceRoot":"","sources":["../src/b.ts"],"names":[],"mappings":";AAAA,6BAA6B;;AAE7B,mDAA8C;AAE9C,MAAM,KAAK,GAAG,IAAI,CAAA;AAClB,IAAI,KAAK,GAAG,CAAC,CAAA;AACb,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;AAE/B,MAAM,MAAM,GAAG,IAAI,4BAAY,EAAE,CAAA;AACjC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;AAE3D,MAAM,GAAG,GAAG,GAAG,EAAE;IACf,IAAI,KAAK,GAAG,KAAK,EAAE;QACjB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAA;QACtC,OAAM;KACP;IACD,KAAK,EAAE,CAAA;IACP,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE;QAC9B,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;QAC3B,MAAM,CAAC,OAAO,EAAE,CAAA;KACjB;IACD,YAAY,CAAC,GAAG,CAAC,CAAA;AACnB,CAAC,CAAA;AAED,GAAG,EAAE,CAAA"}

View File

@ -1,15 +0,0 @@
/// <reference types="node" />
export declare class BufferReader {
private offset;
private buffer;
private encoding;
constructor(offset?: number);
setBuffer(offset: number, buffer: Buffer): void;
int16(): number;
byte(): number;
int32(): number;
uint32(): number;
string(length: number): string;
cstring(): string;
bytes(length: number): Buffer;
}

View File

@ -1,56 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BufferReader = void 0;
const emptyBuffer = Buffer.allocUnsafe(0);
class BufferReader {
constructor(offset = 0) {
this.offset = offset;
this.buffer = emptyBuffer;
// TODO(bmc): support non-utf8 encoding?
this.encoding = 'utf-8';
}
setBuffer(offset, buffer) {
this.offset = offset;
this.buffer = buffer;
}
int16() {
const result = this.buffer.readInt16BE(this.offset);
this.offset += 2;
return result;
}
byte() {
const result = this.buffer[this.offset];
this.offset++;
return result;
}
int32() {
const result = this.buffer.readInt32BE(this.offset);
this.offset += 4;
return result;
}
uint32() {
const result = this.buffer.readUInt32BE(this.offset);
this.offset += 4;
return result;
}
string(length) {
const result = this.buffer.toString(this.encoding, this.offset, this.offset + length);
this.offset += length;
return result;
}
cstring() {
const start = this.offset;
let end = start;
// eslint-disable-next-line no-empty
while (this.buffer[end++] !== 0) { }
this.offset = end;
return this.buffer.toString(this.encoding, start, end - 1);
}
bytes(length) {
const result = this.buffer.slice(this.offset, this.offset + length);
this.offset += length;
return result;
}
}
exports.BufferReader = BufferReader;
//# sourceMappingURL=buffer-reader.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"buffer-reader.js","sourceRoot":"","sources":["../src/buffer-reader.ts"],"names":[],"mappings":";;;AAAA,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;AAEzC,MAAa,YAAY;IAMvB,YAAoB,SAAiB,CAAC;QAAlB,WAAM,GAAN,MAAM,CAAY;QAL9B,WAAM,GAAW,WAAW,CAAA;QAEpC,wCAAwC;QAChC,aAAQ,GAAW,OAAO,CAAA;IAEO,CAAC;IAEnC,SAAS,CAAC,MAAc,EAAE,MAAc;QAC7C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAEM,KAAK;QACV,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACnD,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;QAChB,OAAO,MAAM,CAAA;IACf,CAAC;IAEM,IAAI;QACT,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACvC,IAAI,CAAC,MAAM,EAAE,CAAA;QACb,OAAO,MAAM,CAAA;IACf,CAAC;IAEM,KAAK;QACV,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACnD,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;QAChB,OAAO,MAAM,CAAA;IACf,CAAC;IAEM,MAAM;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACpD,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;QAChB,OAAO,MAAM,CAAA;IACf,CAAC;IAEM,MAAM,CAAC,MAAc;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;QACrF,IAAI,CAAC,MAAM,IAAI,MAAM,CAAA;QACrB,OAAO,MAAM,CAAA;IACf,CAAC;IAEM,OAAO;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAA;QACzB,IAAI,GAAG,GAAG,KAAK,CAAA;QACf,oCAAoC;QACpC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,GAAE;QACnC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAA;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAA;IAC5D,CAAC;IAEM,KAAK,CAAC,MAAc;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;QACnE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAA;QACrB,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAzDD,oCAyDC"}

View File

@ -1,16 +0,0 @@
/// <reference types="node" />
export declare class Writer {
private size;
private buffer;
private offset;
private headerPosition;
constructor(size?: number);
private ensure;
addInt32(num: number): Writer;
addInt16(num: number): Writer;
addCString(string: string): Writer;
addString(string?: string): Writer;
add(otherBuffer: Buffer): Writer;
private join;
flush(code?: number): Buffer;
}

View File

@ -1,81 +0,0 @@
"use strict";
//binary data writer tuned for encoding binary specific to the postgres binary protocol
Object.defineProperty(exports, "__esModule", { value: true });
exports.Writer = void 0;
class Writer {
constructor(size = 256) {
this.size = size;
this.offset = 5;
this.headerPosition = 0;
this.buffer = Buffer.allocUnsafe(size);
}
ensure(size) {
const remaining = this.buffer.length - this.offset;
if (remaining < size) {
const oldBuffer = this.buffer;
// exponential growth factor of around ~ 1.5
// https://stackoverflow.com/questions/2269063/buffer-growth-strategy
const newSize = oldBuffer.length + (oldBuffer.length >> 1) + size;
this.buffer = Buffer.allocUnsafe(newSize);
oldBuffer.copy(this.buffer);
}
}
addInt32(num) {
this.ensure(4);
this.buffer[this.offset++] = (num >>> 24) & 0xff;
this.buffer[this.offset++] = (num >>> 16) & 0xff;
this.buffer[this.offset++] = (num >>> 8) & 0xff;
this.buffer[this.offset++] = (num >>> 0) & 0xff;
return this;
}
addInt16(num) {
this.ensure(2);
this.buffer[this.offset++] = (num >>> 8) & 0xff;
this.buffer[this.offset++] = (num >>> 0) & 0xff;
return this;
}
addCString(string) {
if (!string) {
this.ensure(1);
}
else {
const len = Buffer.byteLength(string);
this.ensure(len + 1); // +1 for null terminator
this.buffer.write(string, this.offset, 'utf-8');
this.offset += len;
}
this.buffer[this.offset++] = 0; // null terminator
return this;
}
addString(string = '') {
const len = Buffer.byteLength(string);
this.ensure(len);
this.buffer.write(string, this.offset);
this.offset += len;
return this;
}
add(otherBuffer) {
this.ensure(otherBuffer.length);
otherBuffer.copy(this.buffer, this.offset);
this.offset += otherBuffer.length;
return this;
}
join(code) {
if (code) {
this.buffer[this.headerPosition] = code;
//length is everything in this packet minus the code
const length = this.offset - (this.headerPosition + 1);
this.buffer.writeInt32BE(length, this.headerPosition + 1);
}
return this.buffer.slice(code ? 0 : 5, this.offset);
}
flush(code) {
const result = this.join(code);
this.offset = 5;
this.headerPosition = 0;
this.buffer = Buffer.allocUnsafe(this.size);
return result;
}
}
exports.Writer = Writer;
//# sourceMappingURL=buffer-writer.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"buffer-writer.js","sourceRoot":"","sources":["../src/buffer-writer.ts"],"names":[],"mappings":";AAAA,uFAAuF;;;AAEvF,MAAa,MAAM;IAIjB,YAAoB,OAAO,GAAG;QAAV,SAAI,GAAJ,IAAI,CAAM;QAFtB,WAAM,GAAW,CAAC,CAAA;QAClB,mBAAc,GAAW,CAAC,CAAA;QAEhC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IACxC,CAAC;IAEO,MAAM,CAAC,IAAY;QACzB,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QAClD,IAAI,SAAS,GAAG,IAAI,EAAE;YACpB,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAA;YAC7B,4CAA4C;YAC5C,qEAAqE;YACrE,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;YACjE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;YACzC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;SAC5B;IACH,CAAC;IAEM,QAAQ,CAAC,GAAW;QACzB,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;QAChD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;QAChD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAA;QAC/C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAA;QAC/C,OAAO,IAAI,CAAA;IACb,CAAC;IAEM,QAAQ,CAAC,GAAW;QACzB,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAA;QAC/C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAA;QAC/C,OAAO,IAAI,CAAA;IACb,CAAC;IAEM,UAAU,CAAC,MAAc;QAC9B,IAAI,CAAC,MAAM,EAAE;YACX,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;SACf;aAAM;YACL,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA,CAAC,yBAAyB;YAC9C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YAC/C,IAAI,CAAC,MAAM,IAAI,GAAG,CAAA;SACnB;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA,CAAC,kBAAkB;QACjD,OAAO,IAAI,CAAA;IACb,CAAC;IAEM,SAAS,CAAC,SAAiB,EAAE;QAClC,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QACtC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAA;QAClB,OAAO,IAAI,CAAA;IACb,CAAC;IAEM,GAAG,CAAC,WAAmB;QAC5B,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAC/B,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAC1C,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,MAAM,CAAA;QACjC,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,IAAI,CAAC,IAAa;QACxB,IAAI,IAAI,EAAE;YACR,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,IAAI,CAAA;YACvC,oDAAoD;YACpD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC,CAAA;YACtD,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC,CAAA;SAC1D;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IACrD,CAAC;IAEM,KAAK,CAAC,IAAa;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9B,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QACf,IAAI,CAAC,cAAc,GAAG,CAAC,CAAA;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3C,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAlFD,wBAkFC"}

View File

@ -1 +0,0 @@
export {};

View File

@ -1,524 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const test_buffers_1 = __importDefault(require("./testing/test-buffers"));
const buffer_list_1 = __importDefault(require("./testing/buffer-list"));
const _1 = require(".");
const assert_1 = __importDefault(require("assert"));
const stream_1 = require("stream");
const authOkBuffer = test_buffers_1.default.authenticationOk();
const paramStatusBuffer = test_buffers_1.default.parameterStatus('client_encoding', 'UTF8');
const readyForQueryBuffer = test_buffers_1.default.readyForQuery();
const backendKeyDataBuffer = test_buffers_1.default.backendKeyData(1, 2);
const commandCompleteBuffer = test_buffers_1.default.commandComplete('SELECT 3');
const parseCompleteBuffer = test_buffers_1.default.parseComplete();
const bindCompleteBuffer = test_buffers_1.default.bindComplete();
const portalSuspendedBuffer = test_buffers_1.default.portalSuspended();
const row1 = {
name: 'id',
tableID: 1,
attributeNumber: 2,
dataTypeID: 3,
dataTypeSize: 4,
typeModifier: 5,
formatCode: 0,
};
const oneRowDescBuff = test_buffers_1.default.rowDescription([row1]);
row1.name = 'bang';
const twoRowBuf = test_buffers_1.default.rowDescription([
row1,
{
name: 'whoah',
tableID: 10,
attributeNumber: 11,
dataTypeID: 12,
dataTypeSize: 13,
typeModifier: 14,
formatCode: 0,
},
]);
const rowWithBigOids = {
name: 'bigoid',
tableID: 3000000001,
attributeNumber: 2,
dataTypeID: 3000000003,
dataTypeSize: 4,
typeModifier: 5,
formatCode: 0,
};
const bigOidDescBuff = test_buffers_1.default.rowDescription([rowWithBigOids]);
const emptyRowFieldBuf = test_buffers_1.default.dataRow([]);
const oneFieldBuf = test_buffers_1.default.dataRow(['test']);
const expectedAuthenticationOkayMessage = {
name: 'authenticationOk',
length: 8,
};
const expectedParameterStatusMessage = {
name: 'parameterStatus',
parameterName: 'client_encoding',
parameterValue: 'UTF8',
length: 25,
};
const expectedBackendKeyDataMessage = {
name: 'backendKeyData',
processID: 1,
secretKey: 2,
};
const expectedReadyForQueryMessage = {
name: 'readyForQuery',
length: 5,
status: 'I',
};
const expectedCommandCompleteMessage = {
name: 'commandComplete',
length: 13,
text: 'SELECT 3',
};
const emptyRowDescriptionBuffer = new buffer_list_1.default()
.addInt16(0) // number of fields
.join(true, 'T');
const expectedEmptyRowDescriptionMessage = {
name: 'rowDescription',
length: 6,
fieldCount: 0,
fields: [],
};
const expectedOneRowMessage = {
name: 'rowDescription',
length: 27,
fieldCount: 1,
fields: [
{
name: 'id',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
],
};
const expectedTwoRowMessage = {
name: 'rowDescription',
length: 53,
fieldCount: 2,
fields: [
{
name: 'bang',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
{
name: 'whoah',
tableID: 10,
columnID: 11,
dataTypeID: 12,
dataTypeSize: 13,
dataTypeModifier: 14,
format: 'text',
},
],
};
const expectedBigOidMessage = {
name: 'rowDescription',
length: 31,
fieldCount: 1,
fields: [
{
name: 'bigoid',
tableID: 3000000001,
columnID: 2,
dataTypeID: 3000000003,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
],
};
const emptyParameterDescriptionBuffer = new buffer_list_1.default()
.addInt16(0) // number of parameters
.join(true, 't');
const oneParameterDescBuf = test_buffers_1.default.parameterDescription([1111]);
const twoParameterDescBuf = test_buffers_1.default.parameterDescription([2222, 3333]);
const expectedEmptyParameterDescriptionMessage = {
name: 'parameterDescription',
length: 6,
parameterCount: 0,
dataTypeIDs: [],
};
const expectedOneParameterMessage = {
name: 'parameterDescription',
length: 10,
parameterCount: 1,
dataTypeIDs: [1111],
};
const expectedTwoParameterMessage = {
name: 'parameterDescription',
length: 14,
parameterCount: 2,
dataTypeIDs: [2222, 3333],
};
const testForMessage = function (buffer, expectedMessage) {
it('receives and parses ' + expectedMessage.name, () => __awaiter(this, void 0, void 0, function* () {
const messages = yield parseBuffers([buffer]);
const [lastMessage] = messages;
for (const key in expectedMessage) {
assert_1.default.deepEqual(lastMessage[key], expectedMessage[key]);
}
}));
};
const plainPasswordBuffer = test_buffers_1.default.authenticationCleartextPassword();
const md5PasswordBuffer = test_buffers_1.default.authenticationMD5Password();
const SASLBuffer = test_buffers_1.default.authenticationSASL();
const SASLContinueBuffer = test_buffers_1.default.authenticationSASLContinue();
const SASLFinalBuffer = test_buffers_1.default.authenticationSASLFinal();
const expectedPlainPasswordMessage = {
name: 'authenticationCleartextPassword',
};
const expectedMD5PasswordMessage = {
name: 'authenticationMD5Password',
salt: Buffer.from([1, 2, 3, 4]),
};
const expectedSASLMessage = {
name: 'authenticationSASL',
mechanisms: ['SCRAM-SHA-256'],
};
const expectedSASLContinueMessage = {
name: 'authenticationSASLContinue',
data: 'data',
};
const expectedSASLFinalMessage = {
name: 'authenticationSASLFinal',
data: 'data',
};
const notificationResponseBuffer = test_buffers_1.default.notification(4, 'hi', 'boom');
const expectedNotificationResponseMessage = {
name: 'notification',
processId: 4,
channel: 'hi',
payload: 'boom',
};
const parseBuffers = (buffers) => __awaiter(void 0, void 0, void 0, function* () {
const stream = new stream_1.PassThrough();
for (const buffer of buffers) {
stream.write(buffer);
}
stream.end();
const msgs = [];
yield (0, _1.parse)(stream, (msg) => msgs.push(msg));
return msgs;
});
describe('PgPacketStream', function () {
testForMessage(authOkBuffer, expectedAuthenticationOkayMessage);
testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage);
testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage);
testForMessage(SASLBuffer, expectedSASLMessage);
testForMessage(SASLContinueBuffer, expectedSASLContinueMessage);
// this exercises a found bug in the parser:
// https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
// and adds a test which is deterministic, rather than relying on network packet chunking
const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])]);
testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage);
testForMessage(SASLFinalBuffer, expectedSASLFinalMessage);
// this exercises a found bug in the parser:
// https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
// and adds a test which is deterministic, rather than relying on network packet chunking
const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])]);
testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage);
testForMessage(paramStatusBuffer, expectedParameterStatusMessage);
testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage);
testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage);
testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage);
testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage);
testForMessage(test_buffers_1.default.emptyQuery(), {
name: 'emptyQuery',
length: 4,
});
testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
name: 'noData',
});
describe('rowDescription messages', function () {
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage);
testForMessage(oneRowDescBuff, expectedOneRowMessage);
testForMessage(twoRowBuf, expectedTwoRowMessage);
testForMessage(bigOidDescBuff, expectedBigOidMessage);
});
describe('parameterDescription messages', function () {
testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage);
testForMessage(oneParameterDescBuf, expectedOneParameterMessage);
testForMessage(twoParameterDescBuf, expectedTwoParameterMessage);
});
describe('parsing rows', function () {
describe('parsing empty row', function () {
testForMessage(emptyRowFieldBuf, {
name: 'dataRow',
fieldCount: 0,
});
});
describe('parsing data row with fields', function () {
testForMessage(oneFieldBuf, {
name: 'dataRow',
fieldCount: 1,
fields: ['test'],
});
});
});
describe('notice message', function () {
// this uses the same logic as error message
const buff = test_buffers_1.default.notice([{ type: 'C', value: 'code' }]);
testForMessage(buff, {
name: 'notice',
code: 'code',
});
});
testForMessage(test_buffers_1.default.error([]), {
name: 'error',
});
describe('with all the fields', function () {
const buffer = test_buffers_1.default.error([
{
type: 'S',
value: 'ERROR',
},
{
type: 'C',
value: 'code',
},
{
type: 'M',
value: 'message',
},
{
type: 'D',
value: 'details',
},
{
type: 'H',
value: 'hint',
},
{
type: 'P',
value: '100',
},
{
type: 'p',
value: '101',
},
{
type: 'q',
value: 'query',
},
{
type: 'W',
value: 'where',
},
{
type: 'F',
value: 'file',
},
{
type: 'L',
value: 'line',
},
{
type: 'R',
value: 'routine',
},
{
type: 'Z',
value: 'alsdkf',
},
]);
testForMessage(buffer, {
name: 'error',
severity: 'ERROR',
code: 'code',
message: 'message',
detail: 'details',
hint: 'hint',
position: '100',
internalPosition: '101',
internalQuery: 'query',
where: 'where',
file: 'file',
line: 'line',
routine: 'routine',
});
});
testForMessage(parseCompleteBuffer, {
name: 'parseComplete',
});
testForMessage(bindCompleteBuffer, {
name: 'bindComplete',
});
testForMessage(bindCompleteBuffer, {
name: 'bindComplete',
});
testForMessage(test_buffers_1.default.closeComplete(), {
name: 'closeComplete',
});
describe('parses portal suspended message', function () {
testForMessage(portalSuspendedBuffer, {
name: 'portalSuspended',
});
});
describe('parses replication start message', function () {
testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
name: 'replicationStart',
length: 4,
});
});
describe('copy', () => {
testForMessage(test_buffers_1.default.copyIn(0), {
name: 'copyInResponse',
length: 7,
binary: false,
columnTypes: [],
});
testForMessage(test_buffers_1.default.copyIn(2), {
name: 'copyInResponse',
length: 11,
binary: false,
columnTypes: [0, 1],
});
testForMessage(test_buffers_1.default.copyOut(0), {
name: 'copyOutResponse',
length: 7,
binary: false,
columnTypes: [],
});
testForMessage(test_buffers_1.default.copyOut(3), {
name: 'copyOutResponse',
length: 13,
binary: false,
columnTypes: [0, 1, 2],
});
testForMessage(test_buffers_1.default.copyDone(), {
name: 'copyDone',
length: 4,
});
testForMessage(test_buffers_1.default.copyData(Buffer.from([5, 6, 7])), {
name: 'copyData',
length: 7,
chunk: Buffer.from([5, 6, 7]),
});
});
// since the data message on a stream can randomly divide the incomming
// tcp packets anywhere, we need to make sure we can parse every single
// split on a tcp message
describe('split buffer, single message parsing', function () {
const fullBuffer = test_buffers_1.default.dataRow([null, 'bang', 'zug zug', null, '!']);
it('parses when full buffer comes in', function () {
return __awaiter(this, void 0, void 0, function* () {
const messages = yield parseBuffers([fullBuffer]);
const message = messages[0];
assert_1.default.equal(message.fields.length, 5);
assert_1.default.equal(message.fields[0], null);
assert_1.default.equal(message.fields[1], 'bang');
assert_1.default.equal(message.fields[2], 'zug zug');
assert_1.default.equal(message.fields[3], null);
assert_1.default.equal(message.fields[4], '!');
});
});
const testMessageReceivedAfterSplitAt = function (split) {
return __awaiter(this, void 0, void 0, function* () {
const firstBuffer = Buffer.alloc(fullBuffer.length - split);
const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length);
fullBuffer.copy(firstBuffer, 0, 0);
fullBuffer.copy(secondBuffer, 0, firstBuffer.length);
const messages = yield parseBuffers([firstBuffer, secondBuffer]);
const message = messages[0];
assert_1.default.equal(message.fields.length, 5);
assert_1.default.equal(message.fields[0], null);
assert_1.default.equal(message.fields[1], 'bang');
assert_1.default.equal(message.fields[2], 'zug zug');
assert_1.default.equal(message.fields[3], null);
assert_1.default.equal(message.fields[4], '!');
});
};
it('parses when split in the middle', function () {
return testMessageReceivedAfterSplitAt(6);
});
it('parses when split at end', function () {
return testMessageReceivedAfterSplitAt(2);
});
it('parses when split at beginning', function () {
return Promise.all([
testMessageReceivedAfterSplitAt(fullBuffer.length - 2),
testMessageReceivedAfterSplitAt(fullBuffer.length - 1),
testMessageReceivedAfterSplitAt(fullBuffer.length - 5),
]);
});
});
describe('split buffer, multiple message parsing', function () {
const dataRowBuffer = test_buffers_1.default.dataRow(['!']);
const readyForQueryBuffer = test_buffers_1.default.readyForQuery();
const fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length);
dataRowBuffer.copy(fullBuffer, 0, 0);
readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0);
const verifyMessages = function (messages) {
assert_1.default.strictEqual(messages.length, 2);
assert_1.default.deepEqual(messages[0], {
name: 'dataRow',
fieldCount: 1,
length: 11,
fields: ['!'],
});
assert_1.default.equal(messages[0].fields[0], '!');
assert_1.default.deepEqual(messages[1], {
name: 'readyForQuery',
length: 5,
status: 'I',
});
};
// sanity check
it('receives both messages when packet is not split', function () {
return __awaiter(this, void 0, void 0, function* () {
const messages = yield parseBuffers([fullBuffer]);
verifyMessages(messages);
});
});
const splitAndVerifyTwoMessages = function (split) {
return __awaiter(this, void 0, void 0, function* () {
const firstBuffer = Buffer.alloc(fullBuffer.length - split);
const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length);
fullBuffer.copy(firstBuffer, 0, 0);
fullBuffer.copy(secondBuffer, 0, firstBuffer.length);
const messages = yield parseBuffers([firstBuffer, secondBuffer]);
verifyMessages(messages);
});
};
describe('receives both messages when packet is split', function () {
it('in the middle', function () {
return splitAndVerifyTwoMessages(11);
});
it('at the front', function () {
return Promise.all([
splitAndVerifyTwoMessages(fullBuffer.length - 1),
splitAndVerifyTwoMessages(fullBuffer.length - 4),
splitAndVerifyTwoMessages(fullBuffer.length - 6),
]);
});
it('at the end', function () {
return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)]);
});
});
});
});
//# sourceMappingURL=inbound-parser.test.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,6 +0,0 @@
/// <reference types="node" />
import { DatabaseError } from './messages';
import { serialize } from './serializer';
import { MessageCallback } from './parser';
export declare function parse(stream: NodeJS.ReadableStream, callback: MessageCallback): Promise<void>;
export { serialize, DatabaseError };

View File

@ -1,15 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DatabaseError = exports.serialize = exports.parse = void 0;
const messages_1 = require("./messages");
Object.defineProperty(exports, "DatabaseError", { enumerable: true, get: function () { return messages_1.DatabaseError; } });
const serializer_1 = require("./serializer");
Object.defineProperty(exports, "serialize", { enumerable: true, get: function () { return serializer_1.serialize; } });
const parser_1 = require("./parser");
function parse(stream, callback) {
const parser = new parser_1.Parser();
stream.on('data', (buffer) => parser.parse(buffer, callback));
return new Promise((resolve) => stream.on('end', () => resolve()));
}
exports.parse = parse;
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAA0C;AAUtB,8FAVX,wBAAa,OAUW;AATjC,6CAAwC;AAS/B,0FATA,sBAAS,OASA;AARlB,qCAAkD;AAElD,SAAgB,KAAK,CAAC,MAA6B,EAAE,QAAyB;IAC5E,MAAM,MAAM,GAAG,IAAI,eAAM,EAAE,CAAA;IAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IACrE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;AACpE,CAAC;AAJD,sBAIC"}

View File

@ -1,162 +0,0 @@
/// <reference types="node" />
export declare type Mode = 'text' | 'binary';
export declare type MessageName = 'parseComplete' | 'bindComplete' | 'closeComplete' | 'noData' | 'portalSuspended' | 'replicationStart' | 'emptyQuery' | 'copyDone' | 'copyData' | 'rowDescription' | 'parameterDescription' | 'parameterStatus' | 'backendKeyData' | 'notification' | 'readyForQuery' | 'commandComplete' | 'dataRow' | 'copyInResponse' | 'copyOutResponse' | 'authenticationOk' | 'authenticationMD5Password' | 'authenticationCleartextPassword' | 'authenticationSASL' | 'authenticationSASLContinue' | 'authenticationSASLFinal' | 'error' | 'notice';
export interface BackendMessage {
name: MessageName;
length: number;
}
export declare const parseComplete: BackendMessage;
export declare const bindComplete: BackendMessage;
export declare const closeComplete: BackendMessage;
export declare const noData: BackendMessage;
export declare const portalSuspended: BackendMessage;
export declare const replicationStart: BackendMessage;
export declare const emptyQuery: BackendMessage;
export declare const copyDone: BackendMessage;
interface NoticeOrError {
message: string | undefined;
severity: string | undefined;
code: string | undefined;
detail: string | undefined;
hint: string | undefined;
position: string | undefined;
internalPosition: string | undefined;
internalQuery: string | undefined;
where: string | undefined;
schema: string | undefined;
table: string | undefined;
column: string | undefined;
dataType: string | undefined;
constraint: string | undefined;
file: string | undefined;
line: string | undefined;
routine: string | undefined;
}
export declare class DatabaseError extends Error implements NoticeOrError {
readonly length: number;
readonly name: MessageName;
severity: string | undefined;
code: string | undefined;
detail: string | undefined;
hint: string | undefined;
position: string | undefined;
internalPosition: string | undefined;
internalQuery: string | undefined;
where: string | undefined;
schema: string | undefined;
table: string | undefined;
column: string | undefined;
dataType: string | undefined;
constraint: string | undefined;
file: string | undefined;
line: string | undefined;
routine: string | undefined;
constructor(message: string, length: number, name: MessageName);
}
export declare class CopyDataMessage {
readonly length: number;
readonly chunk: Buffer;
readonly name = "copyData";
constructor(length: number, chunk: Buffer);
}
export declare class CopyResponse {
readonly length: number;
readonly name: MessageName;
readonly binary: boolean;
readonly columnTypes: number[];
constructor(length: number, name: MessageName, binary: boolean, columnCount: number);
}
export declare class Field {
readonly name: string;
readonly tableID: number;
readonly columnID: number;
readonly dataTypeID: number;
readonly dataTypeSize: number;
readonly dataTypeModifier: number;
readonly format: Mode;
constructor(name: string, tableID: number, columnID: number, dataTypeID: number, dataTypeSize: number, dataTypeModifier: number, format: Mode);
}
export declare class RowDescriptionMessage {
readonly length: number;
readonly fieldCount: number;
readonly name: MessageName;
readonly fields: Field[];
constructor(length: number, fieldCount: number);
}
export declare class ParameterDescriptionMessage {
readonly length: number;
readonly parameterCount: number;
readonly name: MessageName;
readonly dataTypeIDs: number[];
constructor(length: number, parameterCount: number);
}
export declare class ParameterStatusMessage {
readonly length: number;
readonly parameterName: string;
readonly parameterValue: string;
readonly name: MessageName;
constructor(length: number, parameterName: string, parameterValue: string);
}
export declare class AuthenticationMD5Password implements BackendMessage {
readonly length: number;
readonly salt: Buffer;
readonly name: MessageName;
constructor(length: number, salt: Buffer);
}
export declare class BackendKeyDataMessage {
readonly length: number;
readonly processID: number;
readonly secretKey: number;
readonly name: MessageName;
constructor(length: number, processID: number, secretKey: number);
}
export declare class NotificationResponseMessage {
readonly length: number;
readonly processId: number;
readonly channel: string;
readonly payload: string;
readonly name: MessageName;
constructor(length: number, processId: number, channel: string, payload: string);
}
export declare class ReadyForQueryMessage {
readonly length: number;
readonly status: string;
readonly name: MessageName;
constructor(length: number, status: string);
}
export declare class CommandCompleteMessage {
readonly length: number;
readonly text: string;
readonly name: MessageName;
constructor(length: number, text: string);
}
export declare class DataRowMessage {
length: number;
fields: any[];
readonly fieldCount: number;
readonly name: MessageName;
constructor(length: number, fields: any[]);
}
export declare class NoticeMessage implements BackendMessage, NoticeOrError {
readonly length: number;
readonly message: string | undefined;
constructor(length: number, message: string | undefined);
readonly name = "notice";
severity: string | undefined;
code: string | undefined;
detail: string | undefined;
hint: string | undefined;
position: string | undefined;
internalPosition: string | undefined;
internalQuery: string | undefined;
where: string | undefined;
schema: string | undefined;
table: string | undefined;
column: string | undefined;
dataType: string | undefined;
constraint: string | undefined;
file: string | undefined;
line: string | undefined;
routine: string | undefined;
}
export {};

View File

@ -1,160 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NoticeMessage = exports.DataRowMessage = exports.CommandCompleteMessage = exports.ReadyForQueryMessage = exports.NotificationResponseMessage = exports.BackendKeyDataMessage = exports.AuthenticationMD5Password = exports.ParameterStatusMessage = exports.ParameterDescriptionMessage = exports.RowDescriptionMessage = exports.Field = exports.CopyResponse = exports.CopyDataMessage = exports.DatabaseError = exports.copyDone = exports.emptyQuery = exports.replicationStart = exports.portalSuspended = exports.noData = exports.closeComplete = exports.bindComplete = exports.parseComplete = void 0;
exports.parseComplete = {
name: 'parseComplete',
length: 5,
};
exports.bindComplete = {
name: 'bindComplete',
length: 5,
};
exports.closeComplete = {
name: 'closeComplete',
length: 5,
};
exports.noData = {
name: 'noData',
length: 5,
};
exports.portalSuspended = {
name: 'portalSuspended',
length: 5,
};
exports.replicationStart = {
name: 'replicationStart',
length: 4,
};
exports.emptyQuery = {
name: 'emptyQuery',
length: 4,
};
exports.copyDone = {
name: 'copyDone',
length: 4,
};
class DatabaseError extends Error {
constructor(message, length, name) {
super(message);
this.length = length;
this.name = name;
}
}
exports.DatabaseError = DatabaseError;
class CopyDataMessage {
constructor(length, chunk) {
this.length = length;
this.chunk = chunk;
this.name = 'copyData';
}
}
exports.CopyDataMessage = CopyDataMessage;
class CopyResponse {
constructor(length, name, binary, columnCount) {
this.length = length;
this.name = name;
this.binary = binary;
this.columnTypes = new Array(columnCount);
}
}
exports.CopyResponse = CopyResponse;
class Field {
constructor(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, format) {
this.name = name;
this.tableID = tableID;
this.columnID = columnID;
this.dataTypeID = dataTypeID;
this.dataTypeSize = dataTypeSize;
this.dataTypeModifier = dataTypeModifier;
this.format = format;
}
}
exports.Field = Field;
class RowDescriptionMessage {
constructor(length, fieldCount) {
this.length = length;
this.fieldCount = fieldCount;
this.name = 'rowDescription';
this.fields = new Array(this.fieldCount);
}
}
exports.RowDescriptionMessage = RowDescriptionMessage;
class ParameterDescriptionMessage {
constructor(length, parameterCount) {
this.length = length;
this.parameterCount = parameterCount;
this.name = 'parameterDescription';
this.dataTypeIDs = new Array(this.parameterCount);
}
}
exports.ParameterDescriptionMessage = ParameterDescriptionMessage;
class ParameterStatusMessage {
constructor(length, parameterName, parameterValue) {
this.length = length;
this.parameterName = parameterName;
this.parameterValue = parameterValue;
this.name = 'parameterStatus';
}
}
exports.ParameterStatusMessage = ParameterStatusMessage;
class AuthenticationMD5Password {
constructor(length, salt) {
this.length = length;
this.salt = salt;
this.name = 'authenticationMD5Password';
}
}
exports.AuthenticationMD5Password = AuthenticationMD5Password;
class BackendKeyDataMessage {
constructor(length, processID, secretKey) {
this.length = length;
this.processID = processID;
this.secretKey = secretKey;
this.name = 'backendKeyData';
}
}
exports.BackendKeyDataMessage = BackendKeyDataMessage;
class NotificationResponseMessage {
constructor(length, processId, channel, payload) {
this.length = length;
this.processId = processId;
this.channel = channel;
this.payload = payload;
this.name = 'notification';
}
}
exports.NotificationResponseMessage = NotificationResponseMessage;
class ReadyForQueryMessage {
constructor(length, status) {
this.length = length;
this.status = status;
this.name = 'readyForQuery';
}
}
exports.ReadyForQueryMessage = ReadyForQueryMessage;
class CommandCompleteMessage {
constructor(length, text) {
this.length = length;
this.text = text;
this.name = 'commandComplete';
}
}
exports.CommandCompleteMessage = CommandCompleteMessage;
class DataRowMessage {
constructor(length, fields) {
this.length = length;
this.fields = fields;
this.name = 'dataRow';
this.fieldCount = fields.length;
}
}
exports.DataRowMessage = DataRowMessage;
class NoticeMessage {
constructor(length, message) {
this.length = length;
this.message = message;
this.name = 'notice';
}
}
exports.NoticeMessage = NoticeMessage;
//# sourceMappingURL=messages.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":";;;AAoCa,QAAA,aAAa,GAAmB;IAC3C,IAAI,EAAE,eAAe;IACrB,MAAM,EAAE,CAAC;CACV,CAAA;AAEY,QAAA,YAAY,GAAmB;IAC1C,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,CAAC;CACV,CAAA;AAEY,QAAA,aAAa,GAAmB;IAC3C,IAAI,EAAE,eAAe;IACrB,MAAM,EAAE,CAAC;CACV,CAAA;AAEY,QAAA,MAAM,GAAmB;IACpC,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,CAAC;CACV,CAAA;AAEY,QAAA,eAAe,GAAmB;IAC7C,IAAI,EAAE,iBAAiB;IACvB,MAAM,EAAE,CAAC;CACV,CAAA;AAEY,QAAA,gBAAgB,GAAmB;IAC9C,IAAI,EAAE,kBAAkB;IACxB,MAAM,EAAE,CAAC;CACV,CAAA;AAEY,QAAA,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,MAAM,EAAE,CAAC;CACV,CAAA;AAEY,QAAA,QAAQ,GAAmB;IACtC,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC;CACV,CAAA;AAsBD,MAAa,aAAc,SAAQ,KAAK;IAiBtC,YACE,OAAe,EACC,MAAc,EACd,IAAiB;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAA;QAHE,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAa;IAGnC,CAAC;CACF;AAxBD,sCAwBC;AAED,MAAa,eAAe;IAE1B,YACkB,MAAc,EACd,KAAa;QADb,WAAM,GAAN,MAAM,CAAQ;QACd,UAAK,GAAL,KAAK,CAAQ;QAHf,SAAI,GAAG,UAAU,CAAA;IAI9B,CAAC;CACL;AAND,0CAMC;AAED,MAAa,YAAY;IAEvB,YACkB,MAAc,EACd,IAAiB,EACjB,MAAe,EAC/B,WAAmB;QAHH,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAa;QACjB,WAAM,GAAN,MAAM,CAAS;QAG/B,IAAI,CAAC,WAAW,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,CAAA;IAC3C,CAAC;CACF;AAVD,oCAUC;AAED,MAAa,KAAK;IAChB,YACkB,IAAY,EACZ,OAAe,EACf,QAAgB,EAChB,UAAkB,EAClB,YAAoB,EACpB,gBAAwB,EACxB,MAAY;QANZ,SAAI,GAAJ,IAAI,CAAQ;QACZ,YAAO,GAAP,OAAO,CAAQ;QACf,aAAQ,GAAR,QAAQ,CAAQ;QAChB,eAAU,GAAV,UAAU,CAAQ;QAClB,iBAAY,GAAZ,YAAY,CAAQ;QACpB,qBAAgB,GAAhB,gBAAgB,CAAQ;QACxB,WAAM,GAAN,MAAM,CAAM;IAC3B,CAAC;CACL;AAVD,sBAUC;AAED,MAAa,qBAAqB;IAGhC,YACkB,MAAc,EACd,UAAkB;QADlB,WAAM,GAAN,MAAM,CAAQ;QACd,eAAU,GAAV,UAAU,CAAQ;QAJpB,SAAI,GAAgB,gBAAgB,CAAA;QAMlD,IAAI,CAAC,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC1C,CAAC;CACF;AATD,sDASC;AAED,MAAa,2BAA2B;IAGtC,YACkB,MAAc,EACd,cAAsB;QADtB,WAAM,GAAN,MAAM,CAAQ;QACd,mBAAc,GAAd,cAAc,CAAQ;QAJxB,SAAI,GAAgB,sBAAsB,CAAA;QAMxD,IAAI,CAAC,WAAW,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;IACnD,CAAC;CACF;AATD,kEASC;AAED,MAAa,sBAAsB;IAEjC,YACkB,MAAc,EACd,aAAqB,EACrB,cAAsB;QAFtB,WAAM,GAAN,MAAM,CAAQ;QACd,kBAAa,GAAb,aAAa,CAAQ;QACrB,mBAAc,GAAd,cAAc,CAAQ;QAJxB,SAAI,GAAgB,iBAAiB,CAAA;IAKlD,CAAC;CACL;AAPD,wDAOC;AAED,MAAa,yBAAyB;IAEpC,YACkB,MAAc,EACd,IAAY;QADZ,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAHd,SAAI,GAAgB,2BAA2B,CAAA;IAI5D,CAAC;CACL;AAND,8DAMC;AAED,MAAa,qBAAqB;IAEhC,YACkB,MAAc,EACd,SAAiB,EACjB,SAAiB;QAFjB,WAAM,GAAN,MAAM,CAAQ;QACd,cAAS,GAAT,SAAS,CAAQ;QACjB,cAAS,GAAT,SAAS,CAAQ;QAJnB,SAAI,GAAgB,gBAAgB,CAAA;IAKjD,CAAC;CACL;AAPD,sDAOC;AAED,MAAa,2BAA2B;IAEtC,YACkB,MAAc,EACd,SAAiB,EACjB,OAAe,EACf,OAAe;QAHf,WAAM,GAAN,MAAM,CAAQ;QACd,cAAS,GAAT,SAAS,CAAQ;QACjB,YAAO,GAAP,OAAO,CAAQ;QACf,YAAO,GAAP,OAAO,CAAQ;QALjB,SAAI,GAAgB,cAAc,CAAA;IAM/C,CAAC;CACL;AARD,kEAQC;AAED,MAAa,oBAAoB;IAE/B,YACkB,MAAc,EACd,MAAc;QADd,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAQ;QAHhB,SAAI,GAAgB,eAAe,CAAA;IAIhD,CAAC;CACL;AAND,oDAMC;AAED,MAAa,sBAAsB;IAEjC,YACkB,MAAc,EACd,IAAY;QADZ,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAHd,SAAI,GAAgB,iBAAiB,CAAA;IAIlD,CAAC;CACL;AAND,wDAMC;AAED,MAAa,cAAc;IAGzB,YACS,MAAc,EACd,MAAa;QADb,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAO;QAHN,SAAI,GAAgB,SAAS,CAAA;QAK3C,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAA;IACjC,CAAC;CACF;AATD,wCASC;AAED,MAAa,aAAa;IACxB,YACkB,MAAc,EACd,OAA2B;QAD3B,WAAM,GAAN,MAAM,CAAQ;QACd,YAAO,GAAP,OAAO,CAAoB;QAE7B,SAAI,GAAG,QAAQ,CAAA;IAD5B,CAAC;CAkBL;AAtBD,sCAsBC"}

View File

@ -1 +0,0 @@
export {};

View File

@ -1,252 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const assert_1 = __importDefault(require("assert"));
const serializer_1 = require("./serializer");
const buffer_list_1 = __importDefault(require("./testing/buffer-list"));
describe('serializer', () => {
it('builds startup message', function () {
const actual = serializer_1.serialize.startup({
user: 'brian',
database: 'bang',
});
assert_1.default.deepEqual(actual, new buffer_list_1.default()
.addInt16(3)
.addInt16(0)
.addCString('user')
.addCString('brian')
.addCString('database')
.addCString('bang')
.addCString('client_encoding')
.addCString('UTF8')
.addCString('')
.join(true));
});
it('builds password message', function () {
const actual = serializer_1.serialize.password('!');
assert_1.default.deepEqual(actual, new buffer_list_1.default().addCString('!').join(true, 'p'));
});
it('builds request ssl message', function () {
const actual = serializer_1.serialize.requestSsl();
const expected = new buffer_list_1.default().addInt32(80877103).join(true);
assert_1.default.deepEqual(actual, expected);
});
it('builds SASLInitialResponseMessage message', function () {
const actual = serializer_1.serialize.sendSASLInitialResponseMessage('mech', 'data');
assert_1.default.deepEqual(actual, new buffer_list_1.default().addCString('mech').addInt32(4).addString('data').join(true, 'p'));
});
it('builds SCRAMClientFinalMessage message', function () {
const actual = serializer_1.serialize.sendSCRAMClientFinalMessage('data');
assert_1.default.deepEqual(actual, new buffer_list_1.default().addString('data').join(true, 'p'));
});
it('builds query message', function () {
const txt = 'select * from boom';
const actual = serializer_1.serialize.query(txt);
assert_1.default.deepEqual(actual, new buffer_list_1.default().addCString(txt).join(true, 'Q'));
});
describe('parse message', () => {
it('builds parse message', function () {
const actual = serializer_1.serialize.parse({ text: '!' });
const expected = new buffer_list_1.default().addCString('').addCString('!').addInt16(0).join(true, 'P');
assert_1.default.deepEqual(actual, expected);
});
it('builds parse message with named query', function () {
const actual = serializer_1.serialize.parse({
name: 'boom',
text: 'select * from boom',
types: [],
});
const expected = new buffer_list_1.default().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P');
assert_1.default.deepEqual(actual, expected);
});
it('with multiple parameters', function () {
const actual = serializer_1.serialize.parse({
name: 'force',
text: 'select * from bang where name = $1',
types: [1, 2, 3, 4],
});
const expected = new buffer_list_1.default()
.addCString('force')
.addCString('select * from bang where name = $1')
.addInt16(4)
.addInt32(1)
.addInt32(2)
.addInt32(3)
.addInt32(4)
.join(true, 'P');
assert_1.default.deepEqual(actual, expected);
});
});
describe('bind messages', function () {
it('with no values', function () {
const actual = serializer_1.serialize.bind();
const expectedBuffer = new buffer_list_1.default()
.addCString('')
.addCString('')
.addInt16(0)
.addInt16(0)
.addInt16(1)
.addInt16(0)
.join(true, 'B');
assert_1.default.deepEqual(actual, expectedBuffer);
});
it('with named statement, portal, and values', function () {
const actual = serializer_1.serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, 'zing'],
});
const expectedBuffer = new buffer_list_1.default()
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(4)
.addInt16(0)
.addInt16(0)
.addInt16(0)
.addInt16(0)
.addInt16(4)
.addInt32(1)
.add(Buffer.from('1'))
.addInt32(2)
.add(Buffer.from('hi'))
.addInt32(-1)
.addInt32(4)
.add(Buffer.from('zing'))
.addInt16(1)
.addInt16(0)
.join(true, 'B');
assert_1.default.deepEqual(actual, expectedBuffer);
});
});
it('with custom valueMapper', function () {
const actual = serializer_1.serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, 'zing'],
valueMapper: () => null,
});
const expectedBuffer = new buffer_list_1.default()
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(4)
.addInt16(0)
.addInt16(0)
.addInt16(0)
.addInt16(0)
.addInt16(4)
.addInt32(-1)
.addInt32(-1)
.addInt32(-1)
.addInt32(-1)
.addInt16(1)
.addInt16(0)
.join(true, 'B');
assert_1.default.deepEqual(actual, expectedBuffer);
});
it('with named statement, portal, and buffer value', function () {
const actual = serializer_1.serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, Buffer.from('zing', 'utf8')],
});
const expectedBuffer = new buffer_list_1.default()
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(4) // value count
.addInt16(0) // string
.addInt16(0) // string
.addInt16(0) // string
.addInt16(1) // binary
.addInt16(4)
.addInt32(1)
.add(Buffer.from('1'))
.addInt32(2)
.add(Buffer.from('hi'))
.addInt32(-1)
.addInt32(4)
.add(Buffer.from('zing', 'utf-8'))
.addInt16(1)
.addInt16(0)
.join(true, 'B');
assert_1.default.deepEqual(actual, expectedBuffer);
});
describe('builds execute message', function () {
it('for unamed portal with no row limit', function () {
const actual = serializer_1.serialize.execute();
const expectedBuffer = new buffer_list_1.default().addCString('').addInt32(0).join(true, 'E');
assert_1.default.deepEqual(actual, expectedBuffer);
});
it('for named portal with row limit', function () {
const actual = serializer_1.serialize.execute({
portal: 'my favorite portal',
rows: 100,
});
const expectedBuffer = new buffer_list_1.default().addCString('my favorite portal').addInt32(100).join(true, 'E');
assert_1.default.deepEqual(actual, expectedBuffer);
});
});
it('builds flush command', function () {
const actual = serializer_1.serialize.flush();
const expected = new buffer_list_1.default().join(true, 'H');
assert_1.default.deepEqual(actual, expected);
});
it('builds sync command', function () {
const actual = serializer_1.serialize.sync();
const expected = new buffer_list_1.default().join(true, 'S');
assert_1.default.deepEqual(actual, expected);
});
it('builds end command', function () {
const actual = serializer_1.serialize.end();
const expected = Buffer.from([0x58, 0, 0, 0, 4]);
assert_1.default.deepEqual(actual, expected);
});
describe('builds describe command', function () {
it('describe statement', function () {
const actual = serializer_1.serialize.describe({ type: 'S', name: 'bang' });
const expected = new buffer_list_1.default().addChar('S').addCString('bang').join(true, 'D');
assert_1.default.deepEqual(actual, expected);
});
it('describe unnamed portal', function () {
const actual = serializer_1.serialize.describe({ type: 'P' });
const expected = new buffer_list_1.default().addChar('P').addCString('').join(true, 'D');
assert_1.default.deepEqual(actual, expected);
});
});
describe('builds close command', function () {
it('describe statement', function () {
const actual = serializer_1.serialize.close({ type: 'S', name: 'bang' });
const expected = new buffer_list_1.default().addChar('S').addCString('bang').join(true, 'C');
assert_1.default.deepEqual(actual, expected);
});
it('describe unnamed portal', function () {
const actual = serializer_1.serialize.close({ type: 'P' });
const expected = new buffer_list_1.default().addChar('P').addCString('').join(true, 'C');
assert_1.default.deepEqual(actual, expected);
});
});
describe('copy messages', function () {
it('builds copyFromChunk', () => {
const actual = serializer_1.serialize.copyData(Buffer.from([1, 2, 3]));
const expected = new buffer_list_1.default().add(Buffer.from([1, 2, 3])).join(true, 'd');
assert_1.default.deepEqual(actual, expected);
});
it('builds copy fail', () => {
const actual = serializer_1.serialize.copyFail('err!');
const expected = new buffer_list_1.default().addCString('err!').join(true, 'f');
assert_1.default.deepEqual(actual, expected);
});
it('builds copy done', () => {
const actual = serializer_1.serialize.copyDone();
const expected = new buffer_list_1.default().join(true, 'c');
assert_1.default.deepEqual(actual, expected);
});
});
it('builds cancel message', () => {
const actual = serializer_1.serialize.cancel(3, 4);
const expected = new buffer_list_1.default().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true);
assert_1.default.deepEqual(actual, expected);
});
});
//# sourceMappingURL=outbound-serializer.test.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,39 +0,0 @@
/// <reference types="node" />
/// <reference types="node" />
import { TransformOptions } from 'stream';
import { Mode, BackendMessage } from './messages';
export declare type Packet = {
code: number;
packet: Buffer;
};
declare type StreamOptions = TransformOptions & {
mode: Mode;
};
export declare type MessageCallback = (msg: BackendMessage) => void;
export declare class Parser {
private buffer;
private bufferLength;
private bufferOffset;
private reader;
private mode;
constructor(opts?: StreamOptions);
parse(buffer: Buffer, callback: MessageCallback): void;
private mergeBuffer;
private handlePacket;
private parseReadyForQueryMessage;
private parseCommandCompleteMessage;
private parseCopyData;
private parseCopyInMessage;
private parseCopyOutMessage;
private parseCopyMessage;
private parseNotificationMessage;
private parseRowDescriptionMessage;
private parseField;
private parseParameterDescriptionMessage;
private parseDataRowMessage;
private parseParameterStatusMessage;
private parseBackendKeyData;
parseAuthenticationResponse(offset: number, length: number, bytes: Buffer): any;
private parseErrorMessage;
}
export {};

View File

@ -1,306 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
const messages_1 = require("./messages");
const buffer_reader_1 = require("./buffer-reader");
// every message is prefixed with a single bye
const CODE_LENGTH = 1;
// every message has an int32 length which includes itself but does
// NOT include the code in the length
const LEN_LENGTH = 4;
const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH;
const emptyBuffer = Buffer.allocUnsafe(0);
class Parser {
constructor(opts) {
this.buffer = emptyBuffer;
this.bufferLength = 0;
this.bufferOffset = 0;
this.reader = new buffer_reader_1.BufferReader();
if ((opts === null || opts === void 0 ? void 0 : opts.mode) === 'binary') {
throw new Error('Binary mode not supported yet');
}
this.mode = (opts === null || opts === void 0 ? void 0 : opts.mode) || 'text';
}
parse(buffer, callback) {
this.mergeBuffer(buffer);
const bufferFullLength = this.bufferOffset + this.bufferLength;
let offset = this.bufferOffset;
while (offset + HEADER_LENGTH <= bufferFullLength) {
// code is 1 byte long - it identifies the message type
const code = this.buffer[offset];
// length is 1 Uint32BE - it is the length of the message EXCLUDING the code
const length = this.buffer.readUInt32BE(offset + CODE_LENGTH);
const fullMessageLength = CODE_LENGTH + length;
if (fullMessageLength + offset <= bufferFullLength) {
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, this.buffer);
callback(message);
offset += fullMessageLength;
}
else {
break;
}
}
if (offset === bufferFullLength) {
// No more use for the buffer
this.buffer = emptyBuffer;
this.bufferLength = 0;
this.bufferOffset = 0;
}
else {
// Adjust the cursors of remainingBuffer
this.bufferLength = bufferFullLength - offset;
this.bufferOffset = offset;
}
}
mergeBuffer(buffer) {
if (this.bufferLength > 0) {
const newLength = this.bufferLength + buffer.byteLength;
const newFullLength = newLength + this.bufferOffset;
if (newFullLength > this.buffer.byteLength) {
// We can't concat the new buffer with the remaining one
let newBuffer;
if (newLength <= this.buffer.byteLength && this.bufferOffset >= this.bufferLength) {
// We can move the relevant part to the beginning of the buffer instead of allocating a new buffer
newBuffer = this.buffer;
}
else {
// Allocate a new larger buffer
let newBufferLength = this.buffer.byteLength * 2;
while (newLength >= newBufferLength) {
newBufferLength *= 2;
}
newBuffer = Buffer.allocUnsafe(newBufferLength);
}
// Move the remaining buffer to the new one
this.buffer.copy(newBuffer, 0, this.bufferOffset, this.bufferOffset + this.bufferLength);
this.buffer = newBuffer;
this.bufferOffset = 0;
}
// Concat the new buffer with the remaining one
buffer.copy(this.buffer, this.bufferOffset + this.bufferLength);
this.bufferLength = newLength;
}
else {
this.buffer = buffer;
this.bufferOffset = 0;
this.bufferLength = buffer.byteLength;
}
}
handlePacket(offset, code, length, bytes) {
switch (code) {
case 50 /* MessageCodes.BindComplete */:
return messages_1.bindComplete;
case 49 /* MessageCodes.ParseComplete */:
return messages_1.parseComplete;
case 51 /* MessageCodes.CloseComplete */:
return messages_1.closeComplete;
case 110 /* MessageCodes.NoData */:
return messages_1.noData;
case 115 /* MessageCodes.PortalSuspended */:
return messages_1.portalSuspended;
case 99 /* MessageCodes.CopyDone */:
return messages_1.copyDone;
case 87 /* MessageCodes.ReplicationStart */:
return messages_1.replicationStart;
case 73 /* MessageCodes.EmptyQuery */:
return messages_1.emptyQuery;
case 68 /* MessageCodes.DataRow */:
return this.parseDataRowMessage(offset, length, bytes);
case 67 /* MessageCodes.CommandComplete */:
return this.parseCommandCompleteMessage(offset, length, bytes);
case 90 /* MessageCodes.ReadyForQuery */:
return this.parseReadyForQueryMessage(offset, length, bytes);
case 65 /* MessageCodes.NotificationResponse */:
return this.parseNotificationMessage(offset, length, bytes);
case 82 /* MessageCodes.AuthenticationResponse */:
return this.parseAuthenticationResponse(offset, length, bytes);
case 83 /* MessageCodes.ParameterStatus */:
return this.parseParameterStatusMessage(offset, length, bytes);
case 75 /* MessageCodes.BackendKeyData */:
return this.parseBackendKeyData(offset, length, bytes);
case 69 /* MessageCodes.ErrorMessage */:
return this.parseErrorMessage(offset, length, bytes, 'error');
case 78 /* MessageCodes.NoticeMessage */:
return this.parseErrorMessage(offset, length, bytes, 'notice');
case 84 /* MessageCodes.RowDescriptionMessage */:
return this.parseRowDescriptionMessage(offset, length, bytes);
case 116 /* MessageCodes.ParameterDescriptionMessage */:
return this.parseParameterDescriptionMessage(offset, length, bytes);
case 71 /* MessageCodes.CopyIn */:
return this.parseCopyInMessage(offset, length, bytes);
case 72 /* MessageCodes.CopyOut */:
return this.parseCopyOutMessage(offset, length, bytes);
case 100 /* MessageCodes.CopyData */:
return this.parseCopyData(offset, length, bytes);
default:
return new messages_1.DatabaseError('received invalid response: ' + code.toString(16), length, 'error');
}
}
parseReadyForQueryMessage(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const status = this.reader.string(1);
return new messages_1.ReadyForQueryMessage(length, status);
}
parseCommandCompleteMessage(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const text = this.reader.cstring();
return new messages_1.CommandCompleteMessage(length, text);
}
parseCopyData(offset, length, bytes) {
const chunk = bytes.slice(offset, offset + (length - 4));
return new messages_1.CopyDataMessage(length, chunk);
}
parseCopyInMessage(offset, length, bytes) {
return this.parseCopyMessage(offset, length, bytes, 'copyInResponse');
}
parseCopyOutMessage(offset, length, bytes) {
return this.parseCopyMessage(offset, length, bytes, 'copyOutResponse');
}
parseCopyMessage(offset, length, bytes, messageName) {
this.reader.setBuffer(offset, bytes);
const isBinary = this.reader.byte() !== 0;
const columnCount = this.reader.int16();
const message = new messages_1.CopyResponse(length, messageName, isBinary, columnCount);
for (let i = 0; i < columnCount; i++) {
message.columnTypes[i] = this.reader.int16();
}
return message;
}
parseNotificationMessage(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const processId = this.reader.int32();
const channel = this.reader.cstring();
const payload = this.reader.cstring();
return new messages_1.NotificationResponseMessage(length, processId, channel, payload);
}
parseRowDescriptionMessage(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const fieldCount = this.reader.int16();
const message = new messages_1.RowDescriptionMessage(length, fieldCount);
for (let i = 0; i < fieldCount; i++) {
message.fields[i] = this.parseField();
}
return message;
}
parseField() {
const name = this.reader.cstring();
const tableID = this.reader.uint32();
const columnID = this.reader.int16();
const dataTypeID = this.reader.uint32();
const dataTypeSize = this.reader.int16();
const dataTypeModifier = this.reader.int32();
const mode = this.reader.int16() === 0 ? 'text' : 'binary';
return new messages_1.Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode);
}
parseParameterDescriptionMessage(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const parameterCount = this.reader.int16();
const message = new messages_1.ParameterDescriptionMessage(length, parameterCount);
for (let i = 0; i < parameterCount; i++) {
message.dataTypeIDs[i] = this.reader.int32();
}
return message;
}
parseDataRowMessage(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const fieldCount = this.reader.int16();
const fields = new Array(fieldCount);
for (let i = 0; i < fieldCount; i++) {
const len = this.reader.int32();
// a -1 for length means the value of the field is null
fields[i] = len === -1 ? null : this.reader.string(len);
}
return new messages_1.DataRowMessage(length, fields);
}
parseParameterStatusMessage(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const name = this.reader.cstring();
const value = this.reader.cstring();
return new messages_1.ParameterStatusMessage(length, name, value);
}
parseBackendKeyData(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const processID = this.reader.int32();
const secretKey = this.reader.int32();
return new messages_1.BackendKeyDataMessage(length, processID, secretKey);
}
parseAuthenticationResponse(offset, length, bytes) {
this.reader.setBuffer(offset, bytes);
const code = this.reader.int32();
// TODO(bmc): maybe better types here
const message = {
name: 'authenticationOk',
length,
};
switch (code) {
case 0: // AuthenticationOk
break;
case 3: // AuthenticationCleartextPassword
if (message.length === 8) {
message.name = 'authenticationCleartextPassword';
}
break;
case 5: // AuthenticationMD5Password
if (message.length === 12) {
message.name = 'authenticationMD5Password';
const salt = this.reader.bytes(4);
return new messages_1.AuthenticationMD5Password(length, salt);
}
break;
case 10: // AuthenticationSASL
{
message.name = 'authenticationSASL';
message.mechanisms = [];
let mechanism;
do {
mechanism = this.reader.cstring();
if (mechanism) {
message.mechanisms.push(mechanism);
}
} while (mechanism);
}
break;
case 11: // AuthenticationSASLContinue
message.name = 'authenticationSASLContinue';
message.data = this.reader.string(length - 8);
break;
case 12: // AuthenticationSASLFinal
message.name = 'authenticationSASLFinal';
message.data = this.reader.string(length - 8);
break;
default:
throw new Error('Unknown authenticationOk message type ' + code);
}
return message;
}
parseErrorMessage(offset, length, bytes, name) {
this.reader.setBuffer(offset, bytes);
const fields = {};
let fieldType = this.reader.string(1);
while (fieldType !== '\0') {
fields[fieldType] = this.reader.cstring();
fieldType = this.reader.string(1);
}
const messageValue = fields.M;
const message = name === 'notice' ? new messages_1.NoticeMessage(length, messageValue) : new messages_1.DatabaseError(messageValue, length, name);
message.severity = fields.S;
message.code = fields.C;
message.detail = fields.D;
message.hint = fields.H;
message.position = fields.P;
message.internalPosition = fields.p;
message.internalQuery = fields.q;
message.where = fields.W;
message.schema = fields.s;
message.table = fields.t;
message.column = fields.c;
message.dataType = fields.d;
message.constraint = fields.n;
message.file = fields.F;
message.line = fields.L;
message.routine = fields.R;
return message;
}
}
exports.Parser = Parser;
//# sourceMappingURL=parser.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,42 +0,0 @@
declare type ParseOpts = {
name?: string;
types?: number[];
text: string;
};
declare type ValueMapper = (param: any, index: number) => any;
declare type BindOpts = {
portal?: string;
binary?: boolean;
statement?: string;
values?: any[];
valueMapper?: ValueMapper;
};
declare type ExecOpts = {
portal?: string;
rows?: number;
};
declare type PortalOpts = {
type: 'S' | 'P';
name?: string;
};
declare const serialize: {
startup: (opts: Record<string, string>) => Buffer;
password: (password: string) => Buffer;
requestSsl: () => Buffer;
sendSASLInitialResponseMessage: (mechanism: string, initialResponse: string) => Buffer;
sendSCRAMClientFinalMessage: (additionalData: string) => Buffer;
query: (text: string) => Buffer;
parse: (query: ParseOpts) => Buffer;
bind: (config?: BindOpts) => Buffer;
execute: (config?: ExecOpts) => Buffer;
describe: (msg: PortalOpts) => Buffer;
close: (msg: PortalOpts) => Buffer;
flush: () => Buffer;
sync: () => Buffer;
end: () => Buffer;
copyData: (chunk: Buffer) => Buffer;
copyDone: () => Buffer;
copyFail: (message: string) => Buffer;
cancel: (processID: number, secretKey: number) => Buffer;
};
export { serialize };

View File

@ -1,189 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.serialize = void 0;
const buffer_writer_1 = require("./buffer-writer");
const writer = new buffer_writer_1.Writer();
const startup = (opts) => {
// protocol version
writer.addInt16(3).addInt16(0);
for (const key of Object.keys(opts)) {
writer.addCString(key).addCString(opts[key]);
}
writer.addCString('client_encoding').addCString('UTF8');
const bodyBuffer = writer.addCString('').flush();
// this message is sent without a code
const length = bodyBuffer.length + 4;
return new buffer_writer_1.Writer().addInt32(length).add(bodyBuffer).flush();
};
const requestSsl = () => {
const response = Buffer.allocUnsafe(8);
response.writeInt32BE(8, 0);
response.writeInt32BE(80877103, 4);
return response;
};
const password = (password) => {
return writer.addCString(password).flush(112 /* code.startup */);
};
const sendSASLInitialResponseMessage = function (mechanism, initialResponse) {
// 0x70 = 'p'
writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse);
return writer.flush(112 /* code.startup */);
};
const sendSCRAMClientFinalMessage = function (additionalData) {
return writer.addString(additionalData).flush(112 /* code.startup */);
};
const query = (text) => {
return writer.addCString(text).flush(81 /* code.query */);
};
const emptyArray = [];
const parse = (query) => {
// expect something like this:
// { name: 'queryName',
// text: 'select * from blah',
// types: ['int8', 'bool'] }
// normalize missing query names to allow for null
const name = query.name || '';
if (name.length > 63) {
console.error('Warning! Postgres only supports 63 characters for query names.');
console.error('You supplied %s (%s)', name, name.length);
console.error('This can cause conflicts and silent errors executing queries');
}
const types = query.types || emptyArray;
const len = types.length;
const buffer = writer
.addCString(name) // name of query
.addCString(query.text) // actual query text
.addInt16(len);
for (let i = 0; i < len; i++) {
buffer.addInt32(types[i]);
}
return writer.flush(80 /* code.parse */);
};
const paramWriter = new buffer_writer_1.Writer();
const writeValues = function (values, valueMapper) {
for (let i = 0; i < values.length; i++) {
const mappedVal = valueMapper ? valueMapper(values[i], i) : values[i];
if (mappedVal == null) {
// add the param type (string) to the writer
writer.addInt16(0 /* ParamType.STRING */);
// write -1 to the param writer to indicate null
paramWriter.addInt32(-1);
}
else if (mappedVal instanceof Buffer) {
// add the param type (binary) to the writer
writer.addInt16(1 /* ParamType.BINARY */);
// add the buffer to the param writer
paramWriter.addInt32(mappedVal.length);
paramWriter.add(mappedVal);
}
else {
// add the param type (string) to the writer
writer.addInt16(0 /* ParamType.STRING */);
paramWriter.addInt32(Buffer.byteLength(mappedVal));
paramWriter.addString(mappedVal);
}
}
};
const bind = (config = {}) => {
// normalize config
const portal = config.portal || '';
const statement = config.statement || '';
const binary = config.binary || false;
const values = config.values || emptyArray;
const len = values.length;
writer.addCString(portal).addCString(statement);
writer.addInt16(len);
writeValues(values, config.valueMapper);
writer.addInt16(len);
writer.add(paramWriter.flush());
// all results use the same format code
writer.addInt16(1);
// format code
writer.addInt16(binary ? 1 /* ParamType.BINARY */ : 0 /* ParamType.STRING */);
return writer.flush(66 /* code.bind */);
};
const emptyExecute = Buffer.from([69 /* code.execute */, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00]);
const execute = (config) => {
// this is the happy path for most queries
if (!config || (!config.portal && !config.rows)) {
return emptyExecute;
}
const portal = config.portal || '';
const rows = config.rows || 0;
const portalLength = Buffer.byteLength(portal);
const len = 4 + portalLength + 1 + 4;
// one extra bit for code
const buff = Buffer.allocUnsafe(1 + len);
buff[0] = 69 /* code.execute */;
buff.writeInt32BE(len, 1);
buff.write(portal, 5, 'utf-8');
buff[portalLength + 5] = 0; // null terminate portal cString
buff.writeUInt32BE(rows, buff.length - 4);
return buff;
};
const cancel = (processID, secretKey) => {
const buffer = Buffer.allocUnsafe(16);
buffer.writeInt32BE(16, 0);
buffer.writeInt16BE(1234, 4);
buffer.writeInt16BE(5678, 6);
buffer.writeInt32BE(processID, 8);
buffer.writeInt32BE(secretKey, 12);
return buffer;
};
const cstringMessage = (code, string) => {
const stringLen = Buffer.byteLength(string);
const len = 4 + stringLen + 1;
// one extra bit for code
const buffer = Buffer.allocUnsafe(1 + len);
buffer[0] = code;
buffer.writeInt32BE(len, 1);
buffer.write(string, 5, 'utf-8');
buffer[len] = 0; // null terminate cString
return buffer;
};
const emptyDescribePortal = writer.addCString('P').flush(68 /* code.describe */);
const emptyDescribeStatement = writer.addCString('S').flush(68 /* code.describe */);
const describe = (msg) => {
return msg.name
? cstringMessage(68 /* code.describe */, `${msg.type}${msg.name || ''}`)
: msg.type === 'P'
? emptyDescribePortal
: emptyDescribeStatement;
};
const close = (msg) => {
const text = `${msg.type}${msg.name || ''}`;
return cstringMessage(67 /* code.close */, text);
};
const copyData = (chunk) => {
return writer.add(chunk).flush(100 /* code.copyFromChunk */);
};
const copyFail = (message) => {
return cstringMessage(102 /* code.copyFail */, message);
};
const codeOnlyBuffer = (code) => Buffer.from([code, 0x00, 0x00, 0x00, 0x04]);
const flushBuffer = codeOnlyBuffer(72 /* code.flush */);
const syncBuffer = codeOnlyBuffer(83 /* code.sync */);
const endBuffer = codeOnlyBuffer(88 /* code.end */);
const copyDoneBuffer = codeOnlyBuffer(99 /* code.copyDone */);
const serialize = {
startup,
password,
requestSsl,
sendSASLInitialResponseMessage,
sendSCRAMClientFinalMessage,
query,
parse,
bind,
execute,
describe,
close,
flush: () => flushBuffer,
sync: () => syncBuffer,
end: () => endBuffer,
copyData,
copyDone: () => copyDoneBuffer,
copyFail,
cancel,
};
exports.serialize = serialize;
//# sourceMappingURL=serializer.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,11 +0,0 @@
// ESM wrapper for pg-protocol
import * as protocol from '../dist/index.js'
// Re-export all the properties
export const DatabaseError = protocol.DatabaseError
export const SASL = protocol.SASL
export const serialize = protocol.serialize
export const parse = protocol.parse
// Re-export the default
export default protocol

View File

@ -1,45 +0,0 @@
{
"name": "pg-protocol",
"version": "1.10.3",
"description": "The postgres client/server binary protocol, implemented in TypeScript",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./esm/index.js",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"./dist/*": "./dist/*.js",
"./dist/*.js": "./dist/*.js"
},
"license": "MIT",
"devDependencies": {
"@types/chai": "^4.2.7",
"@types/mocha": "^10.0.7",
"@types/node": "^12.12.21",
"chai": "^4.2.0",
"chunky": "^0.0.0",
"mocha": "^10.5.2",
"ts-node": "^8.5.4",
"typescript": "^4.0.3"
},
"scripts": {
"test": "mocha dist/**/*.test.js",
"build": "tsc",
"build:watch": "tsc --watch",
"prepublish": "yarn build",
"pretest": "yarn build"
},
"repository": {
"type": "git",
"url": "git://github.com/brianc/node-postgres.git",
"directory": "packages/pg-protocol"
},
"files": [
"/dist/*{js,ts,map}",
"/src",
"/esm"
],
"gitHead": "8f8e7315e8f7c1bb01e98fdb41c8c92585510782"
}

25
node_modules/pg-protocol/src/b.ts generated vendored
View File

@ -1,25 +0,0 @@
// file for microbenchmarking
import { BufferReader } from './buffer-reader'
const LOOPS = 1000
let count = 0
const start = performance.now()
const reader = new BufferReader()
const buffer = Buffer.from([33, 33, 33, 33, 33, 33, 33, 0])
const run = () => {
if (count > LOOPS) {
console.log(performance.now() - start)
return
}
count++
for (let i = 0; i < LOOPS; i++) {
reader.setBuffer(0, buffer)
reader.cstring()
}
setImmediate(run)
}
run()

View File

@ -1,60 +0,0 @@
const emptyBuffer = Buffer.allocUnsafe(0)
export class BufferReader {
private buffer: Buffer = emptyBuffer
// TODO(bmc): support non-utf8 encoding?
private encoding: string = 'utf-8'
constructor(private offset: number = 0) {}
public setBuffer(offset: number, buffer: Buffer): void {
this.offset = offset
this.buffer = buffer
}
public int16(): number {
const result = this.buffer.readInt16BE(this.offset)
this.offset += 2
return result
}
public byte(): number {
const result = this.buffer[this.offset]
this.offset++
return result
}
public int32(): number {
const result = this.buffer.readInt32BE(this.offset)
this.offset += 4
return result
}
public uint32(): number {
const result = this.buffer.readUInt32BE(this.offset)
this.offset += 4
return result
}
public string(length: number): string {
const result = this.buffer.toString(this.encoding, this.offset, this.offset + length)
this.offset += length
return result
}
public cstring(): string {
const start = this.offset
let end = start
// eslint-disable-next-line no-empty
while (this.buffer[end++] !== 0) {}
this.offset = end
return this.buffer.toString(this.encoding, start, end - 1)
}
public bytes(length: number): Buffer {
const result = this.buffer.slice(this.offset, this.offset + length)
this.offset += length
return result
}
}

View File

@ -1,85 +0,0 @@
//binary data writer tuned for encoding binary specific to the postgres binary protocol
export class Writer {
private buffer: Buffer
private offset: number = 5
private headerPosition: number = 0
constructor(private size = 256) {
this.buffer = Buffer.allocUnsafe(size)
}
private ensure(size: number): void {
const remaining = this.buffer.length - this.offset
if (remaining < size) {
const oldBuffer = this.buffer
// exponential growth factor of around ~ 1.5
// https://stackoverflow.com/questions/2269063/buffer-growth-strategy
const newSize = oldBuffer.length + (oldBuffer.length >> 1) + size
this.buffer = Buffer.allocUnsafe(newSize)
oldBuffer.copy(this.buffer)
}
}
public addInt32(num: number): Writer {
this.ensure(4)
this.buffer[this.offset++] = (num >>> 24) & 0xff
this.buffer[this.offset++] = (num >>> 16) & 0xff
this.buffer[this.offset++] = (num >>> 8) & 0xff
this.buffer[this.offset++] = (num >>> 0) & 0xff
return this
}
public addInt16(num: number): Writer {
this.ensure(2)
this.buffer[this.offset++] = (num >>> 8) & 0xff
this.buffer[this.offset++] = (num >>> 0) & 0xff
return this
}
public addCString(string: string): Writer {
if (!string) {
this.ensure(1)
} else {
const len = Buffer.byteLength(string)
this.ensure(len + 1) // +1 for null terminator
this.buffer.write(string, this.offset, 'utf-8')
this.offset += len
}
this.buffer[this.offset++] = 0 // null terminator
return this
}
public addString(string: string = ''): Writer {
const len = Buffer.byteLength(string)
this.ensure(len)
this.buffer.write(string, this.offset)
this.offset += len
return this
}
public add(otherBuffer: Buffer): Writer {
this.ensure(otherBuffer.length)
otherBuffer.copy(this.buffer, this.offset)
this.offset += otherBuffer.length
return this
}
private join(code?: number): Buffer {
if (code) {
this.buffer[this.headerPosition] = code
//length is everything in this packet minus the code
const length = this.offset - (this.headerPosition + 1)
this.buffer.writeInt32BE(length, this.headerPosition + 1)
}
return this.buffer.slice(code ? 0 : 5, this.offset)
}
public flush(code?: number): Buffer {
const result = this.join(code)
this.offset = 5
this.headerPosition = 0
this.buffer = Buffer.allocUnsafe(this.size)
return result
}
}

View File

@ -1,568 +0,0 @@
import buffers from './testing/test-buffers'
import BufferList from './testing/buffer-list'
import { parse } from '.'
import assert from 'assert'
import { PassThrough } from 'stream'
import { BackendMessage } from './messages'
const authOkBuffer = buffers.authenticationOk()
const paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8')
const readyForQueryBuffer = buffers.readyForQuery()
const backendKeyDataBuffer = buffers.backendKeyData(1, 2)
const commandCompleteBuffer = buffers.commandComplete('SELECT 3')
const parseCompleteBuffer = buffers.parseComplete()
const bindCompleteBuffer = buffers.bindComplete()
const portalSuspendedBuffer = buffers.portalSuspended()
const row1 = {
name: 'id',
tableID: 1,
attributeNumber: 2,
dataTypeID: 3,
dataTypeSize: 4,
typeModifier: 5,
formatCode: 0,
}
const oneRowDescBuff = buffers.rowDescription([row1])
row1.name = 'bang'
const twoRowBuf = buffers.rowDescription([
row1,
{
name: 'whoah',
tableID: 10,
attributeNumber: 11,
dataTypeID: 12,
dataTypeSize: 13,
typeModifier: 14,
formatCode: 0,
},
])
const rowWithBigOids = {
name: 'bigoid',
tableID: 3000000001,
attributeNumber: 2,
dataTypeID: 3000000003,
dataTypeSize: 4,
typeModifier: 5,
formatCode: 0,
}
const bigOidDescBuff = buffers.rowDescription([rowWithBigOids])
const emptyRowFieldBuf = buffers.dataRow([])
const oneFieldBuf = buffers.dataRow(['test'])
const expectedAuthenticationOkayMessage = {
name: 'authenticationOk',
length: 8,
}
const expectedParameterStatusMessage = {
name: 'parameterStatus',
parameterName: 'client_encoding',
parameterValue: 'UTF8',
length: 25,
}
const expectedBackendKeyDataMessage = {
name: 'backendKeyData',
processID: 1,
secretKey: 2,
}
const expectedReadyForQueryMessage = {
name: 'readyForQuery',
length: 5,
status: 'I',
}
const expectedCommandCompleteMessage = {
name: 'commandComplete',
length: 13,
text: 'SELECT 3',
}
const emptyRowDescriptionBuffer = new BufferList()
.addInt16(0) // number of fields
.join(true, 'T')
const expectedEmptyRowDescriptionMessage = {
name: 'rowDescription',
length: 6,
fieldCount: 0,
fields: [],
}
const expectedOneRowMessage = {
name: 'rowDescription',
length: 27,
fieldCount: 1,
fields: [
{
name: 'id',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
],
}
const expectedTwoRowMessage = {
name: 'rowDescription',
length: 53,
fieldCount: 2,
fields: [
{
name: 'bang',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
{
name: 'whoah',
tableID: 10,
columnID: 11,
dataTypeID: 12,
dataTypeSize: 13,
dataTypeModifier: 14,
format: 'text',
},
],
}
const expectedBigOidMessage = {
name: 'rowDescription',
length: 31,
fieldCount: 1,
fields: [
{
name: 'bigoid',
tableID: 3000000001,
columnID: 2,
dataTypeID: 3000000003,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
],
}
const emptyParameterDescriptionBuffer = new BufferList()
.addInt16(0) // number of parameters
.join(true, 't')
const oneParameterDescBuf = buffers.parameterDescription([1111])
const twoParameterDescBuf = buffers.parameterDescription([2222, 3333])
const expectedEmptyParameterDescriptionMessage = {
name: 'parameterDescription',
length: 6,
parameterCount: 0,
dataTypeIDs: [],
}
const expectedOneParameterMessage = {
name: 'parameterDescription',
length: 10,
parameterCount: 1,
dataTypeIDs: [1111],
}
const expectedTwoParameterMessage = {
name: 'parameterDescription',
length: 14,
parameterCount: 2,
dataTypeIDs: [2222, 3333],
}
const testForMessage = function (buffer: Buffer, expectedMessage: any) {
it('receives and parses ' + expectedMessage.name, async () => {
const messages = await parseBuffers([buffer])
const [lastMessage] = messages
for (const key in expectedMessage) {
assert.deepEqual((lastMessage as any)[key], expectedMessage[key])
}
})
}
const plainPasswordBuffer = buffers.authenticationCleartextPassword()
const md5PasswordBuffer = buffers.authenticationMD5Password()
const SASLBuffer = buffers.authenticationSASL()
const SASLContinueBuffer = buffers.authenticationSASLContinue()
const SASLFinalBuffer = buffers.authenticationSASLFinal()
const expectedPlainPasswordMessage = {
name: 'authenticationCleartextPassword',
}
const expectedMD5PasswordMessage = {
name: 'authenticationMD5Password',
salt: Buffer.from([1, 2, 3, 4]),
}
const expectedSASLMessage = {
name: 'authenticationSASL',
mechanisms: ['SCRAM-SHA-256'],
}
const expectedSASLContinueMessage = {
name: 'authenticationSASLContinue',
data: 'data',
}
const expectedSASLFinalMessage = {
name: 'authenticationSASLFinal',
data: 'data',
}
const notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
const expectedNotificationResponseMessage = {
name: 'notification',
processId: 4,
channel: 'hi',
payload: 'boom',
}
const parseBuffers = async (buffers: Buffer[]): Promise<BackendMessage[]> => {
const stream = new PassThrough()
for (const buffer of buffers) {
stream.write(buffer)
}
stream.end()
const msgs: BackendMessage[] = []
await parse(stream, (msg) => msgs.push(msg))
return msgs
}
describe('PgPacketStream', function () {
testForMessage(authOkBuffer, expectedAuthenticationOkayMessage)
testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage)
testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage)
testForMessage(SASLBuffer, expectedSASLMessage)
testForMessage(SASLContinueBuffer, expectedSASLContinueMessage)
// this exercises a found bug in the parser:
// https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
// and adds a test which is deterministic, rather than relying on network packet chunking
const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])])
testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage)
testForMessage(SASLFinalBuffer, expectedSASLFinalMessage)
// this exercises a found bug in the parser:
// https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
// and adds a test which is deterministic, rather than relying on network packet chunking
const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])])
testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage)
testForMessage(paramStatusBuffer, expectedParameterStatusMessage)
testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage)
testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage)
testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage)
testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage)
testForMessage(buffers.emptyQuery(), {
name: 'emptyQuery',
length: 4,
})
testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
name: 'noData',
})
describe('rowDescription messages', function () {
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage)
testForMessage(oneRowDescBuff, expectedOneRowMessage)
testForMessage(twoRowBuf, expectedTwoRowMessage)
testForMessage(bigOidDescBuff, expectedBigOidMessage)
})
describe('parameterDescription messages', function () {
testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage)
testForMessage(oneParameterDescBuf, expectedOneParameterMessage)
testForMessage(twoParameterDescBuf, expectedTwoParameterMessage)
})
describe('parsing rows', function () {
describe('parsing empty row', function () {
testForMessage(emptyRowFieldBuf, {
name: 'dataRow',
fieldCount: 0,
})
})
describe('parsing data row with fields', function () {
testForMessage(oneFieldBuf, {
name: 'dataRow',
fieldCount: 1,
fields: ['test'],
})
})
})
describe('notice message', function () {
// this uses the same logic as error message
const buff = buffers.notice([{ type: 'C', value: 'code' }])
testForMessage(buff, {
name: 'notice',
code: 'code',
})
})
testForMessage(buffers.error([]), {
name: 'error',
})
describe('with all the fields', function () {
const buffer = buffers.error([
{
type: 'S',
value: 'ERROR',
},
{
type: 'C',
value: 'code',
},
{
type: 'M',
value: 'message',
},
{
type: 'D',
value: 'details',
},
{
type: 'H',
value: 'hint',
},
{
type: 'P',
value: '100',
},
{
type: 'p',
value: '101',
},
{
type: 'q',
value: 'query',
},
{
type: 'W',
value: 'where',
},
{
type: 'F',
value: 'file',
},
{
type: 'L',
value: 'line',
},
{
type: 'R',
value: 'routine',
},
{
type: 'Z', // ignored
value: 'alsdkf',
},
])
testForMessage(buffer, {
name: 'error',
severity: 'ERROR',
code: 'code',
message: 'message',
detail: 'details',
hint: 'hint',
position: '100',
internalPosition: '101',
internalQuery: 'query',
where: 'where',
file: 'file',
line: 'line',
routine: 'routine',
})
})
testForMessage(parseCompleteBuffer, {
name: 'parseComplete',
})
testForMessage(bindCompleteBuffer, {
name: 'bindComplete',
})
testForMessage(bindCompleteBuffer, {
name: 'bindComplete',
})
testForMessage(buffers.closeComplete(), {
name: 'closeComplete',
})
describe('parses portal suspended message', function () {
testForMessage(portalSuspendedBuffer, {
name: 'portalSuspended',
})
})
describe('parses replication start message', function () {
testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
name: 'replicationStart',
length: 4,
})
})
describe('copy', () => {
testForMessage(buffers.copyIn(0), {
name: 'copyInResponse',
length: 7,
binary: false,
columnTypes: [],
})
testForMessage(buffers.copyIn(2), {
name: 'copyInResponse',
length: 11,
binary: false,
columnTypes: [0, 1],
})
testForMessage(buffers.copyOut(0), {
name: 'copyOutResponse',
length: 7,
binary: false,
columnTypes: [],
})
testForMessage(buffers.copyOut(3), {
name: 'copyOutResponse',
length: 13,
binary: false,
columnTypes: [0, 1, 2],
})
testForMessage(buffers.copyDone(), {
name: 'copyDone',
length: 4,
})
testForMessage(buffers.copyData(Buffer.from([5, 6, 7])), {
name: 'copyData',
length: 7,
chunk: Buffer.from([5, 6, 7]),
})
})
// since the data message on a stream can randomly divide the incomming
// tcp packets anywhere, we need to make sure we can parse every single
// split on a tcp message
describe('split buffer, single message parsing', function () {
const fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!'])
it('parses when full buffer comes in', async function () {
const messages = await parseBuffers([fullBuffer])
const message = messages[0] as any
assert.equal(message.fields.length, 5)
assert.equal(message.fields[0], null)
assert.equal(message.fields[1], 'bang')
assert.equal(message.fields[2], 'zug zug')
assert.equal(message.fields[3], null)
assert.equal(message.fields[4], '!')
})
const testMessageReceivedAfterSplitAt = async function (split: number) {
const firstBuffer = Buffer.alloc(fullBuffer.length - split)
const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
fullBuffer.copy(firstBuffer, 0, 0)
fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
const messages = await parseBuffers([firstBuffer, secondBuffer])
const message = messages[0] as any
assert.equal(message.fields.length, 5)
assert.equal(message.fields[0], null)
assert.equal(message.fields[1], 'bang')
assert.equal(message.fields[2], 'zug zug')
assert.equal(message.fields[3], null)
assert.equal(message.fields[4], '!')
}
it('parses when split in the middle', function () {
return testMessageReceivedAfterSplitAt(6)
})
it('parses when split at end', function () {
return testMessageReceivedAfterSplitAt(2)
})
it('parses when split at beginning', function () {
return Promise.all([
testMessageReceivedAfterSplitAt(fullBuffer.length - 2),
testMessageReceivedAfterSplitAt(fullBuffer.length - 1),
testMessageReceivedAfterSplitAt(fullBuffer.length - 5),
])
})
})
describe('split buffer, multiple message parsing', function () {
const dataRowBuffer = buffers.dataRow(['!'])
const readyForQueryBuffer = buffers.readyForQuery()
const fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length)
dataRowBuffer.copy(fullBuffer, 0, 0)
readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0)
const verifyMessages = function (messages: any[]) {
assert.strictEqual(messages.length, 2)
assert.deepEqual(messages[0], {
name: 'dataRow',
fieldCount: 1,
length: 11,
fields: ['!'],
})
assert.equal(messages[0].fields[0], '!')
assert.deepEqual(messages[1], {
name: 'readyForQuery',
length: 5,
status: 'I',
})
}
// sanity check
it('receives both messages when packet is not split', async function () {
const messages = await parseBuffers([fullBuffer])
verifyMessages(messages)
})
const splitAndVerifyTwoMessages = async function (split: number) {
const firstBuffer = Buffer.alloc(fullBuffer.length - split)
const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
fullBuffer.copy(firstBuffer, 0, 0)
fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
const messages = await parseBuffers([firstBuffer, secondBuffer])
verifyMessages(messages)
}
describe('receives both messages when packet is split', function () {
it('in the middle', function () {
return splitAndVerifyTwoMessages(11)
})
it('at the front', function () {
return Promise.all([
splitAndVerifyTwoMessages(fullBuffer.length - 1),
splitAndVerifyTwoMessages(fullBuffer.length - 4),
splitAndVerifyTwoMessages(fullBuffer.length - 6),
])
})
it('at the end', function () {
return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)])
})
})
})
})

View File

@ -1,11 +0,0 @@
import { DatabaseError } from './messages'
import { serialize } from './serializer'
import { Parser, MessageCallback } from './parser'
export function parse(stream: NodeJS.ReadableStream, callback: MessageCallback): Promise<void> {
const parser = new Parser()
stream.on('data', (buffer: Buffer) => parser.parse(buffer, callback))
return new Promise((resolve) => stream.on('end', () => resolve()))
}
export { serialize, DatabaseError }

View File

@ -1,262 +0,0 @@
export type Mode = 'text' | 'binary'
export type MessageName =
| 'parseComplete'
| 'bindComplete'
| 'closeComplete'
| 'noData'
| 'portalSuspended'
| 'replicationStart'
| 'emptyQuery'
| 'copyDone'
| 'copyData'
| 'rowDescription'
| 'parameterDescription'
| 'parameterStatus'
| 'backendKeyData'
| 'notification'
| 'readyForQuery'
| 'commandComplete'
| 'dataRow'
| 'copyInResponse'
| 'copyOutResponse'
| 'authenticationOk'
| 'authenticationMD5Password'
| 'authenticationCleartextPassword'
| 'authenticationSASL'
| 'authenticationSASLContinue'
| 'authenticationSASLFinal'
| 'error'
| 'notice'
export interface BackendMessage {
name: MessageName
length: number
}
export const parseComplete: BackendMessage = {
name: 'parseComplete',
length: 5,
}
export const bindComplete: BackendMessage = {
name: 'bindComplete',
length: 5,
}
export const closeComplete: BackendMessage = {
name: 'closeComplete',
length: 5,
}
export const noData: BackendMessage = {
name: 'noData',
length: 5,
}
export const portalSuspended: BackendMessage = {
name: 'portalSuspended',
length: 5,
}
export const replicationStart: BackendMessage = {
name: 'replicationStart',
length: 4,
}
export const emptyQuery: BackendMessage = {
name: 'emptyQuery',
length: 4,
}
export const copyDone: BackendMessage = {
name: 'copyDone',
length: 4,
}
interface NoticeOrError {
message: string | undefined
severity: string | undefined
code: string | undefined
detail: string | undefined
hint: string | undefined
position: string | undefined
internalPosition: string | undefined
internalQuery: string | undefined
where: string | undefined
schema: string | undefined
table: string | undefined
column: string | undefined
dataType: string | undefined
constraint: string | undefined
file: string | undefined
line: string | undefined
routine: string | undefined
}
export class DatabaseError extends Error implements NoticeOrError {
public severity: string | undefined
public code: string | undefined
public detail: string | undefined
public hint: string | undefined
public position: string | undefined
public internalPosition: string | undefined
public internalQuery: string | undefined
public where: string | undefined
public schema: string | undefined
public table: string | undefined
public column: string | undefined
public dataType: string | undefined
public constraint: string | undefined
public file: string | undefined
public line: string | undefined
public routine: string | undefined
constructor(
message: string,
public readonly length: number,
public readonly name: MessageName
) {
super(message)
}
}
export class CopyDataMessage {
public readonly name = 'copyData'
constructor(
public readonly length: number,
public readonly chunk: Buffer
) {}
}
export class CopyResponse {
public readonly columnTypes: number[]
constructor(
public readonly length: number,
public readonly name: MessageName,
public readonly binary: boolean,
columnCount: number
) {
this.columnTypes = new Array(columnCount)
}
}
export class Field {
constructor(
public readonly name: string,
public readonly tableID: number,
public readonly columnID: number,
public readonly dataTypeID: number,
public readonly dataTypeSize: number,
public readonly dataTypeModifier: number,
public readonly format: Mode
) {}
}
export class RowDescriptionMessage {
public readonly name: MessageName = 'rowDescription'
public readonly fields: Field[]
constructor(
public readonly length: number,
public readonly fieldCount: number
) {
this.fields = new Array(this.fieldCount)
}
}
export class ParameterDescriptionMessage {
public readonly name: MessageName = 'parameterDescription'
public readonly dataTypeIDs: number[]
constructor(
public readonly length: number,
public readonly parameterCount: number
) {
this.dataTypeIDs = new Array(this.parameterCount)
}
}
export class ParameterStatusMessage {
public readonly name: MessageName = 'parameterStatus'
constructor(
public readonly length: number,
public readonly parameterName: string,
public readonly parameterValue: string
) {}
}
export class AuthenticationMD5Password implements BackendMessage {
public readonly name: MessageName = 'authenticationMD5Password'
constructor(
public readonly length: number,
public readonly salt: Buffer
) {}
}
export class BackendKeyDataMessage {
public readonly name: MessageName = 'backendKeyData'
constructor(
public readonly length: number,
public readonly processID: number,
public readonly secretKey: number
) {}
}
export class NotificationResponseMessage {
public readonly name: MessageName = 'notification'
constructor(
public readonly length: number,
public readonly processId: number,
public readonly channel: string,
public readonly payload: string
) {}
}
export class ReadyForQueryMessage {
public readonly name: MessageName = 'readyForQuery'
constructor(
public readonly length: number,
public readonly status: string
) {}
}
export class CommandCompleteMessage {
public readonly name: MessageName = 'commandComplete'
constructor(
public readonly length: number,
public readonly text: string
) {}
}
export class DataRowMessage {
public readonly fieldCount: number
public readonly name: MessageName = 'dataRow'
constructor(
public length: number,
public fields: any[]
) {
this.fieldCount = fields.length
}
}
export class NoticeMessage implements BackendMessage, NoticeOrError {
constructor(
public readonly length: number,
public readonly message: string | undefined
) {}
public readonly name = 'notice'
public severity: string | undefined
public code: string | undefined
public detail: string | undefined
public hint: string | undefined
public position: string | undefined
public internalPosition: string | undefined
public internalQuery: string | undefined
public where: string | undefined
public schema: string | undefined
public table: string | undefined
public column: string | undefined
public dataType: string | undefined
public constraint: string | undefined
public file: string | undefined
public line: string | undefined
public routine: string | undefined
}

View File

@ -1,276 +0,0 @@
import assert from 'assert'
import { serialize } from './serializer'
import BufferList from './testing/buffer-list'
describe('serializer', () => {
it('builds startup message', function () {
const actual = serialize.startup({
user: 'brian',
database: 'bang',
})
assert.deepEqual(
actual,
new BufferList()
.addInt16(3)
.addInt16(0)
.addCString('user')
.addCString('brian')
.addCString('database')
.addCString('bang')
.addCString('client_encoding')
.addCString('UTF8')
.addCString('')
.join(true)
)
})
it('builds password message', function () {
const actual = serialize.password('!')
assert.deepEqual(actual, new BufferList().addCString('!').join(true, 'p'))
})
it('builds request ssl message', function () {
const actual = serialize.requestSsl()
const expected = new BufferList().addInt32(80877103).join(true)
assert.deepEqual(actual, expected)
})
it('builds SASLInitialResponseMessage message', function () {
const actual = serialize.sendSASLInitialResponseMessage('mech', 'data')
assert.deepEqual(actual, new BufferList().addCString('mech').addInt32(4).addString('data').join(true, 'p'))
})
it('builds SCRAMClientFinalMessage message', function () {
const actual = serialize.sendSCRAMClientFinalMessage('data')
assert.deepEqual(actual, new BufferList().addString('data').join(true, 'p'))
})
it('builds query message', function () {
const txt = 'select * from boom'
const actual = serialize.query(txt)
assert.deepEqual(actual, new BufferList().addCString(txt).join(true, 'Q'))
})
describe('parse message', () => {
it('builds parse message', function () {
const actual = serialize.parse({ text: '!' })
const expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P')
assert.deepEqual(actual, expected)
})
it('builds parse message with named query', function () {
const actual = serialize.parse({
name: 'boom',
text: 'select * from boom',
types: [],
})
const expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P')
assert.deepEqual(actual, expected)
})
it('with multiple parameters', function () {
const actual = serialize.parse({
name: 'force',
text: 'select * from bang where name = $1',
types: [1, 2, 3, 4],
})
const expected = new BufferList()
.addCString('force')
.addCString('select * from bang where name = $1')
.addInt16(4)
.addInt32(1)
.addInt32(2)
.addInt32(3)
.addInt32(4)
.join(true, 'P')
assert.deepEqual(actual, expected)
})
})
describe('bind messages', function () {
it('with no values', function () {
const actual = serialize.bind()
const expectedBuffer = new BufferList()
.addCString('')
.addCString('')
.addInt16(0)
.addInt16(0)
.addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
})
it('with named statement, portal, and values', function () {
const actual = serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, 'zing'],
})
const expectedBuffer = new BufferList()
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(4)
.addInt16(0)
.addInt16(0)
.addInt16(0)
.addInt16(0)
.addInt16(4)
.addInt32(1)
.add(Buffer.from('1'))
.addInt32(2)
.add(Buffer.from('hi'))
.addInt32(-1)
.addInt32(4)
.add(Buffer.from('zing'))
.addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
})
})
it('with custom valueMapper', function () {
const actual = serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, 'zing'],
valueMapper: () => null,
})
const expectedBuffer = new BufferList()
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(4)
.addInt16(0)
.addInt16(0)
.addInt16(0)
.addInt16(0)
.addInt16(4)
.addInt32(-1)
.addInt32(-1)
.addInt32(-1)
.addInt32(-1)
.addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
})
it('with named statement, portal, and buffer value', function () {
const actual = serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, Buffer.from('zing', 'utf8')],
})
const expectedBuffer = new BufferList()
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(4) // value count
.addInt16(0) // string
.addInt16(0) // string
.addInt16(0) // string
.addInt16(1) // binary
.addInt16(4)
.addInt32(1)
.add(Buffer.from('1'))
.addInt32(2)
.add(Buffer.from('hi'))
.addInt32(-1)
.addInt32(4)
.add(Buffer.from('zing', 'utf-8'))
.addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
})
describe('builds execute message', function () {
it('for unamed portal with no row limit', function () {
const actual = serialize.execute()
const expectedBuffer = new BufferList().addCString('').addInt32(0).join(true, 'E')
assert.deepEqual(actual, expectedBuffer)
})
it('for named portal with row limit', function () {
const actual = serialize.execute({
portal: 'my favorite portal',
rows: 100,
})
const expectedBuffer = new BufferList().addCString('my favorite portal').addInt32(100).join(true, 'E')
assert.deepEqual(actual, expectedBuffer)
})
})
it('builds flush command', function () {
const actual = serialize.flush()
const expected = new BufferList().join(true, 'H')
assert.deepEqual(actual, expected)
})
it('builds sync command', function () {
const actual = serialize.sync()
const expected = new BufferList().join(true, 'S')
assert.deepEqual(actual, expected)
})
it('builds end command', function () {
const actual = serialize.end()
const expected = Buffer.from([0x58, 0, 0, 0, 4])
assert.deepEqual(actual, expected)
})
describe('builds describe command', function () {
it('describe statement', function () {
const actual = serialize.describe({ type: 'S', name: 'bang' })
const expected = new BufferList().addChar('S').addCString('bang').join(true, 'D')
assert.deepEqual(actual, expected)
})
it('describe unnamed portal', function () {
const actual = serialize.describe({ type: 'P' })
const expected = new BufferList().addChar('P').addCString('').join(true, 'D')
assert.deepEqual(actual, expected)
})
})
describe('builds close command', function () {
it('describe statement', function () {
const actual = serialize.close({ type: 'S', name: 'bang' })
const expected = new BufferList().addChar('S').addCString('bang').join(true, 'C')
assert.deepEqual(actual, expected)
})
it('describe unnamed portal', function () {
const actual = serialize.close({ type: 'P' })
const expected = new BufferList().addChar('P').addCString('').join(true, 'C')
assert.deepEqual(actual, expected)
})
})
describe('copy messages', function () {
it('builds copyFromChunk', () => {
const actual = serialize.copyData(Buffer.from([1, 2, 3]))
const expected = new BufferList().add(Buffer.from([1, 2, 3])).join(true, 'd')
assert.deepEqual(actual, expected)
})
it('builds copy fail', () => {
const actual = serialize.copyFail('err!')
const expected = new BufferList().addCString('err!').join(true, 'f')
assert.deepEqual(actual, expected)
})
it('builds copy done', () => {
const actual = serialize.copyDone()
const expected = new BufferList().join(true, 'c')
assert.deepEqual(actual, expected)
})
})
it('builds cancel message', () => {
const actual = serialize.cancel(3, 4)
const expected = new BufferList().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true)
assert.deepEqual(actual, expected)
})
})

View File

@ -1,389 +0,0 @@
import { TransformOptions } from 'stream'
import {
Mode,
bindComplete,
parseComplete,
closeComplete,
noData,
portalSuspended,
copyDone,
replicationStart,
emptyQuery,
ReadyForQueryMessage,
CommandCompleteMessage,
CopyDataMessage,
CopyResponse,
NotificationResponseMessage,
RowDescriptionMessage,
ParameterDescriptionMessage,
Field,
DataRowMessage,
ParameterStatusMessage,
BackendKeyDataMessage,
DatabaseError,
BackendMessage,
MessageName,
AuthenticationMD5Password,
NoticeMessage,
} from './messages'
import { BufferReader } from './buffer-reader'
// every message is prefixed with a single bye
const CODE_LENGTH = 1
// every message has an int32 length which includes itself but does
// NOT include the code in the length
const LEN_LENGTH = 4
const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH
export type Packet = {
code: number
packet: Buffer
}
const emptyBuffer = Buffer.allocUnsafe(0)
type StreamOptions = TransformOptions & {
mode: Mode
}
const enum MessageCodes {
DataRow = 0x44, // D
ParseComplete = 0x31, // 1
BindComplete = 0x32, // 2
CloseComplete = 0x33, // 3
CommandComplete = 0x43, // C
ReadyForQuery = 0x5a, // Z
NoData = 0x6e, // n
NotificationResponse = 0x41, // A
AuthenticationResponse = 0x52, // R
ParameterStatus = 0x53, // S
BackendKeyData = 0x4b, // K
ErrorMessage = 0x45, // E
NoticeMessage = 0x4e, // N
RowDescriptionMessage = 0x54, // T
ParameterDescriptionMessage = 0x74, // t
PortalSuspended = 0x73, // s
ReplicationStart = 0x57, // W
EmptyQuery = 0x49, // I
CopyIn = 0x47, // G
CopyOut = 0x48, // H
CopyDone = 0x63, // c
CopyData = 0x64, // d
}
export type MessageCallback = (msg: BackendMessage) => void
export class Parser {
private buffer: Buffer = emptyBuffer
private bufferLength: number = 0
private bufferOffset: number = 0
private reader = new BufferReader()
private mode: Mode
constructor(opts?: StreamOptions) {
if (opts?.mode === 'binary') {
throw new Error('Binary mode not supported yet')
}
this.mode = opts?.mode || 'text'
}
public parse(buffer: Buffer, callback: MessageCallback) {
this.mergeBuffer(buffer)
const bufferFullLength = this.bufferOffset + this.bufferLength
let offset = this.bufferOffset
while (offset + HEADER_LENGTH <= bufferFullLength) {
// code is 1 byte long - it identifies the message type
const code = this.buffer[offset]
// length is 1 Uint32BE - it is the length of the message EXCLUDING the code
const length = this.buffer.readUInt32BE(offset + CODE_LENGTH)
const fullMessageLength = CODE_LENGTH + length
if (fullMessageLength + offset <= bufferFullLength) {
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, this.buffer)
callback(message)
offset += fullMessageLength
} else {
break
}
}
if (offset === bufferFullLength) {
// No more use for the buffer
this.buffer = emptyBuffer
this.bufferLength = 0
this.bufferOffset = 0
} else {
// Adjust the cursors of remainingBuffer
this.bufferLength = bufferFullLength - offset
this.bufferOffset = offset
}
}
private mergeBuffer(buffer: Buffer): void {
if (this.bufferLength > 0) {
const newLength = this.bufferLength + buffer.byteLength
const newFullLength = newLength + this.bufferOffset
if (newFullLength > this.buffer.byteLength) {
// We can't concat the new buffer with the remaining one
let newBuffer: Buffer
if (newLength <= this.buffer.byteLength && this.bufferOffset >= this.bufferLength) {
// We can move the relevant part to the beginning of the buffer instead of allocating a new buffer
newBuffer = this.buffer
} else {
// Allocate a new larger buffer
let newBufferLength = this.buffer.byteLength * 2
while (newLength >= newBufferLength) {
newBufferLength *= 2
}
newBuffer = Buffer.allocUnsafe(newBufferLength)
}
// Move the remaining buffer to the new one
this.buffer.copy(newBuffer, 0, this.bufferOffset, this.bufferOffset + this.bufferLength)
this.buffer = newBuffer
this.bufferOffset = 0
}
// Concat the new buffer with the remaining one
buffer.copy(this.buffer, this.bufferOffset + this.bufferLength)
this.bufferLength = newLength
} else {
this.buffer = buffer
this.bufferOffset = 0
this.bufferLength = buffer.byteLength
}
}
private handlePacket(offset: number, code: number, length: number, bytes: Buffer): BackendMessage {
switch (code) {
case MessageCodes.BindComplete:
return bindComplete
case MessageCodes.ParseComplete:
return parseComplete
case MessageCodes.CloseComplete:
return closeComplete
case MessageCodes.NoData:
return noData
case MessageCodes.PortalSuspended:
return portalSuspended
case MessageCodes.CopyDone:
return copyDone
case MessageCodes.ReplicationStart:
return replicationStart
case MessageCodes.EmptyQuery:
return emptyQuery
case MessageCodes.DataRow:
return this.parseDataRowMessage(offset, length, bytes)
case MessageCodes.CommandComplete:
return this.parseCommandCompleteMessage(offset, length, bytes)
case MessageCodes.ReadyForQuery:
return this.parseReadyForQueryMessage(offset, length, bytes)
case MessageCodes.NotificationResponse:
return this.parseNotificationMessage(offset, length, bytes)
case MessageCodes.AuthenticationResponse:
return this.parseAuthenticationResponse(offset, length, bytes)
case MessageCodes.ParameterStatus:
return this.parseParameterStatusMessage(offset, length, bytes)
case MessageCodes.BackendKeyData:
return this.parseBackendKeyData(offset, length, bytes)
case MessageCodes.ErrorMessage:
return this.parseErrorMessage(offset, length, bytes, 'error')
case MessageCodes.NoticeMessage:
return this.parseErrorMessage(offset, length, bytes, 'notice')
case MessageCodes.RowDescriptionMessage:
return this.parseRowDescriptionMessage(offset, length, bytes)
case MessageCodes.ParameterDescriptionMessage:
return this.parseParameterDescriptionMessage(offset, length, bytes)
case MessageCodes.CopyIn:
return this.parseCopyInMessage(offset, length, bytes)
case MessageCodes.CopyOut:
return this.parseCopyOutMessage(offset, length, bytes)
case MessageCodes.CopyData:
return this.parseCopyData(offset, length, bytes)
default:
return new DatabaseError('received invalid response: ' + code.toString(16), length, 'error')
}
}
private parseReadyForQueryMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const status = this.reader.string(1)
return new ReadyForQueryMessage(length, status)
}
private parseCommandCompleteMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const text = this.reader.cstring()
return new CommandCompleteMessage(length, text)
}
private parseCopyData(offset: number, length: number, bytes: Buffer) {
const chunk = bytes.slice(offset, offset + (length - 4))
return new CopyDataMessage(length, chunk)
}
private parseCopyInMessage(offset: number, length: number, bytes: Buffer) {
return this.parseCopyMessage(offset, length, bytes, 'copyInResponse')
}
private parseCopyOutMessage(offset: number, length: number, bytes: Buffer) {
return this.parseCopyMessage(offset, length, bytes, 'copyOutResponse')
}
private parseCopyMessage(offset: number, length: number, bytes: Buffer, messageName: MessageName) {
this.reader.setBuffer(offset, bytes)
const isBinary = this.reader.byte() !== 0
const columnCount = this.reader.int16()
const message = new CopyResponse(length, messageName, isBinary, columnCount)
for (let i = 0; i < columnCount; i++) {
message.columnTypes[i] = this.reader.int16()
}
return message
}
private parseNotificationMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const processId = this.reader.int32()
const channel = this.reader.cstring()
const payload = this.reader.cstring()
return new NotificationResponseMessage(length, processId, channel, payload)
}
private parseRowDescriptionMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const fieldCount = this.reader.int16()
const message = new RowDescriptionMessage(length, fieldCount)
for (let i = 0; i < fieldCount; i++) {
message.fields[i] = this.parseField()
}
return message
}
private parseField(): Field {
const name = this.reader.cstring()
const tableID = this.reader.uint32()
const columnID = this.reader.int16()
const dataTypeID = this.reader.uint32()
const dataTypeSize = this.reader.int16()
const dataTypeModifier = this.reader.int32()
const mode = this.reader.int16() === 0 ? 'text' : 'binary'
return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode)
}
private parseParameterDescriptionMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const parameterCount = this.reader.int16()
const message = new ParameterDescriptionMessage(length, parameterCount)
for (let i = 0; i < parameterCount; i++) {
message.dataTypeIDs[i] = this.reader.int32()
}
return message
}
private parseDataRowMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const fieldCount = this.reader.int16()
const fields: any[] = new Array(fieldCount)
for (let i = 0; i < fieldCount; i++) {
const len = this.reader.int32()
// a -1 for length means the value of the field is null
fields[i] = len === -1 ? null : this.reader.string(len)
}
return new DataRowMessage(length, fields)
}
private parseParameterStatusMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const name = this.reader.cstring()
const value = this.reader.cstring()
return new ParameterStatusMessage(length, name, value)
}
private parseBackendKeyData(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const processID = this.reader.int32()
const secretKey = this.reader.int32()
return new BackendKeyDataMessage(length, processID, secretKey)
}
public parseAuthenticationResponse(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes)
const code = this.reader.int32()
// TODO(bmc): maybe better types here
const message: BackendMessage & any = {
name: 'authenticationOk',
length,
}
switch (code) {
case 0: // AuthenticationOk
break
case 3: // AuthenticationCleartextPassword
if (message.length === 8) {
message.name = 'authenticationCleartextPassword'
}
break
case 5: // AuthenticationMD5Password
if (message.length === 12) {
message.name = 'authenticationMD5Password'
const salt = this.reader.bytes(4)
return new AuthenticationMD5Password(length, salt)
}
break
case 10: // AuthenticationSASL
{
message.name = 'authenticationSASL'
message.mechanisms = []
let mechanism: string
do {
mechanism = this.reader.cstring()
if (mechanism) {
message.mechanisms.push(mechanism)
}
} while (mechanism)
}
break
case 11: // AuthenticationSASLContinue
message.name = 'authenticationSASLContinue'
message.data = this.reader.string(length - 8)
break
case 12: // AuthenticationSASLFinal
message.name = 'authenticationSASLFinal'
message.data = this.reader.string(length - 8)
break
default:
throw new Error('Unknown authenticationOk message type ' + code)
}
return message
}
private parseErrorMessage(offset: number, length: number, bytes: Buffer, name: MessageName) {
this.reader.setBuffer(offset, bytes)
const fields: Record<string, string> = {}
let fieldType = this.reader.string(1)
while (fieldType !== '\0') {
fields[fieldType] = this.reader.cstring()
fieldType = this.reader.string(1)
}
const messageValue = fields.M
const message =
name === 'notice' ? new NoticeMessage(length, messageValue) : new DatabaseError(messageValue, length, name)
message.severity = fields.S
message.code = fields.C
message.detail = fields.D
message.hint = fields.H
message.position = fields.P
message.internalPosition = fields.p
message.internalQuery = fields.q
message.where = fields.W
message.schema = fields.s
message.table = fields.t
message.column = fields.c
message.dataType = fields.d
message.constraint = fields.n
message.file = fields.F
message.line = fields.L
message.routine = fields.R
return message
}
}

View File

@ -1,274 +0,0 @@
import { Writer } from './buffer-writer'
const enum code {
startup = 0x70,
query = 0x51,
parse = 0x50,
bind = 0x42,
execute = 0x45,
flush = 0x48,
sync = 0x53,
end = 0x58,
close = 0x43,
describe = 0x44,
copyFromChunk = 0x64,
copyDone = 0x63,
copyFail = 0x66,
}
const writer = new Writer()
const startup = (opts: Record<string, string>): Buffer => {
// protocol version
writer.addInt16(3).addInt16(0)
for (const key of Object.keys(opts)) {
writer.addCString(key).addCString(opts[key])
}
writer.addCString('client_encoding').addCString('UTF8')
const bodyBuffer = writer.addCString('').flush()
// this message is sent without a code
const length = bodyBuffer.length + 4
return new Writer().addInt32(length).add(bodyBuffer).flush()
}
const requestSsl = (): Buffer => {
const response = Buffer.allocUnsafe(8)
response.writeInt32BE(8, 0)
response.writeInt32BE(80877103, 4)
return response
}
const password = (password: string): Buffer => {
return writer.addCString(password).flush(code.startup)
}
const sendSASLInitialResponseMessage = function (mechanism: string, initialResponse: string): Buffer {
// 0x70 = 'p'
writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse)
return writer.flush(code.startup)
}
const sendSCRAMClientFinalMessage = function (additionalData: string): Buffer {
return writer.addString(additionalData).flush(code.startup)
}
const query = (text: string): Buffer => {
return writer.addCString(text).flush(code.query)
}
type ParseOpts = {
name?: string
types?: number[]
text: string
}
const emptyArray: any[] = []
const parse = (query: ParseOpts): Buffer => {
// expect something like this:
// { name: 'queryName',
// text: 'select * from blah',
// types: ['int8', 'bool'] }
// normalize missing query names to allow for null
const name = query.name || ''
if (name.length > 63) {
console.error('Warning! Postgres only supports 63 characters for query names.')
console.error('You supplied %s (%s)', name, name.length)
console.error('This can cause conflicts and silent errors executing queries')
}
const types = query.types || emptyArray
const len = types.length
const buffer = writer
.addCString(name) // name of query
.addCString(query.text) // actual query text
.addInt16(len)
for (let i = 0; i < len; i++) {
buffer.addInt32(types[i])
}
return writer.flush(code.parse)
}
type ValueMapper = (param: any, index: number) => any
type BindOpts = {
portal?: string
binary?: boolean
statement?: string
values?: any[]
// optional map from JS value to postgres value per parameter
valueMapper?: ValueMapper
}
const paramWriter = new Writer()
// make this a const enum so typescript will inline the value
const enum ParamType {
STRING = 0,
BINARY = 1,
}
const writeValues = function (values: any[], valueMapper?: ValueMapper): void {
for (let i = 0; i < values.length; i++) {
const mappedVal = valueMapper ? valueMapper(values[i], i) : values[i]
if (mappedVal == null) {
// add the param type (string) to the writer
writer.addInt16(ParamType.STRING)
// write -1 to the param writer to indicate null
paramWriter.addInt32(-1)
} else if (mappedVal instanceof Buffer) {
// add the param type (binary) to the writer
writer.addInt16(ParamType.BINARY)
// add the buffer to the param writer
paramWriter.addInt32(mappedVal.length)
paramWriter.add(mappedVal)
} else {
// add the param type (string) to the writer
writer.addInt16(ParamType.STRING)
paramWriter.addInt32(Buffer.byteLength(mappedVal))
paramWriter.addString(mappedVal)
}
}
}
const bind = (config: BindOpts = {}): Buffer => {
// normalize config
const portal = config.portal || ''
const statement = config.statement || ''
const binary = config.binary || false
const values = config.values || emptyArray
const len = values.length
writer.addCString(portal).addCString(statement)
writer.addInt16(len)
writeValues(values, config.valueMapper)
writer.addInt16(len)
writer.add(paramWriter.flush())
// all results use the same format code
writer.addInt16(1)
// format code
writer.addInt16(binary ? ParamType.BINARY : ParamType.STRING)
return writer.flush(code.bind)
}
type ExecOpts = {
portal?: string
rows?: number
}
const emptyExecute = Buffer.from([code.execute, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00])
const execute = (config?: ExecOpts): Buffer => {
// this is the happy path for most queries
if (!config || (!config.portal && !config.rows)) {
return emptyExecute
}
const portal = config.portal || ''
const rows = config.rows || 0
const portalLength = Buffer.byteLength(portal)
const len = 4 + portalLength + 1 + 4
// one extra bit for code
const buff = Buffer.allocUnsafe(1 + len)
buff[0] = code.execute
buff.writeInt32BE(len, 1)
buff.write(portal, 5, 'utf-8')
buff[portalLength + 5] = 0 // null terminate portal cString
buff.writeUInt32BE(rows, buff.length - 4)
return buff
}
const cancel = (processID: number, secretKey: number): Buffer => {
const buffer = Buffer.allocUnsafe(16)
buffer.writeInt32BE(16, 0)
buffer.writeInt16BE(1234, 4)
buffer.writeInt16BE(5678, 6)
buffer.writeInt32BE(processID, 8)
buffer.writeInt32BE(secretKey, 12)
return buffer
}
type PortalOpts = {
type: 'S' | 'P'
name?: string
}
const cstringMessage = (code: code, string: string): Buffer => {
const stringLen = Buffer.byteLength(string)
const len = 4 + stringLen + 1
// one extra bit for code
const buffer = Buffer.allocUnsafe(1 + len)
buffer[0] = code
buffer.writeInt32BE(len, 1)
buffer.write(string, 5, 'utf-8')
buffer[len] = 0 // null terminate cString
return buffer
}
const emptyDescribePortal = writer.addCString('P').flush(code.describe)
const emptyDescribeStatement = writer.addCString('S').flush(code.describe)
const describe = (msg: PortalOpts): Buffer => {
return msg.name
? cstringMessage(code.describe, `${msg.type}${msg.name || ''}`)
: msg.type === 'P'
? emptyDescribePortal
: emptyDescribeStatement
}
const close = (msg: PortalOpts): Buffer => {
const text = `${msg.type}${msg.name || ''}`
return cstringMessage(code.close, text)
}
const copyData = (chunk: Buffer): Buffer => {
return writer.add(chunk).flush(code.copyFromChunk)
}
const copyFail = (message: string): Buffer => {
return cstringMessage(code.copyFail, message)
}
const codeOnlyBuffer = (code: code): Buffer => Buffer.from([code, 0x00, 0x00, 0x00, 0x04])
const flushBuffer = codeOnlyBuffer(code.flush)
const syncBuffer = codeOnlyBuffer(code.sync)
const endBuffer = codeOnlyBuffer(code.end)
const copyDoneBuffer = codeOnlyBuffer(code.copyDone)
const serialize = {
startup,
password,
requestSsl,
sendSASLInitialResponseMessage,
sendSCRAMClientFinalMessage,
query,
parse,
bind,
execute,
describe,
close,
flush: () => flushBuffer,
sync: () => syncBuffer,
end: () => endBuffer,
copyData,
copyDone: () => copyDoneBuffer,
copyFail,
cancel,
}
export { serialize }

View File

@ -1,67 +0,0 @@
export default class BufferList {
constructor(public buffers: Buffer[] = []) {}
public add(buffer: Buffer, front?: boolean) {
this.buffers[front ? 'unshift' : 'push'](buffer)
return this
}
public addInt16(val: number, front?: boolean) {
return this.add(Buffer.from([val >>> 8, val >>> 0]), front)
}
public getByteLength() {
return this.buffers.reduce(function (previous, current) {
return previous + current.length
}, 0)
}
public addInt32(val: number, first?: boolean) {
return this.add(
Buffer.from([(val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, (val >>> 0) & 0xff]),
first
)
}
public addCString(val: string, front?: boolean) {
const len = Buffer.byteLength(val)
const buffer = Buffer.alloc(len + 1)
buffer.write(val)
buffer[len] = 0
return this.add(buffer, front)
}
public addString(val: string, front?: boolean) {
const len = Buffer.byteLength(val)
const buffer = Buffer.alloc(len)
buffer.write(val)
return this.add(buffer, front)
}
public addChar(char: string, first?: boolean) {
return this.add(Buffer.from(char, 'utf8'), first)
}
public addByte(byte: number) {
return this.add(Buffer.from([byte]))
}
public join(appendLength?: boolean, char?: string): Buffer {
let length = this.getByteLength()
if (appendLength) {
this.addInt32(length + 4, true)
return this.join(false, char)
}
if (char) {
this.addChar(char, true)
length++
}
const result = Buffer.alloc(length)
let index = 0
this.buffers.forEach(function (buffer) {
buffer.copy(result, index, 0)
index += buffer.length
})
return result
}
}

View File

@ -1,166 +0,0 @@
// https://www.postgresql.org/docs/current/protocol-message-formats.html
import BufferList from './buffer-list'
const buffers = {
readyForQuery: function () {
return new BufferList().add(Buffer.from('I')).join(true, 'Z')
},
authenticationOk: function () {
return new BufferList().addInt32(0).join(true, 'R')
},
authenticationCleartextPassword: function () {
return new BufferList().addInt32(3).join(true, 'R')
},
authenticationMD5Password: function () {
return new BufferList()
.addInt32(5)
.add(Buffer.from([1, 2, 3, 4]))
.join(true, 'R')
},
authenticationSASL: function () {
return new BufferList().addInt32(10).addCString('SCRAM-SHA-256').addCString('').join(true, 'R')
},
authenticationSASLContinue: function () {
return new BufferList().addInt32(11).addString('data').join(true, 'R')
},
authenticationSASLFinal: function () {
return new BufferList().addInt32(12).addString('data').join(true, 'R')
},
parameterStatus: function (name: string, value: string) {
return new BufferList().addCString(name).addCString(value).join(true, 'S')
},
backendKeyData: function (processID: number, secretKey: number) {
return new BufferList().addInt32(processID).addInt32(secretKey).join(true, 'K')
},
commandComplete: function (string: string) {
return new BufferList().addCString(string).join(true, 'C')
},
rowDescription: function (fields: any[]) {
fields = fields || []
const buf = new BufferList()
buf.addInt16(fields.length)
fields.forEach(function (field) {
buf
.addCString(field.name)
.addInt32(field.tableID || 0)
.addInt16(field.attributeNumber || 0)
.addInt32(field.dataTypeID || 0)
.addInt16(field.dataTypeSize || 0)
.addInt32(field.typeModifier || 0)
.addInt16(field.formatCode || 0)
})
return buf.join(true, 'T')
},
parameterDescription: function (dataTypeIDs: number[]) {
dataTypeIDs = dataTypeIDs || []
const buf = new BufferList()
buf.addInt16(dataTypeIDs.length)
dataTypeIDs.forEach(function (dataTypeID) {
buf.addInt32(dataTypeID)
})
return buf.join(true, 't')
},
dataRow: function (columns: any[]) {
columns = columns || []
const buf = new BufferList()
buf.addInt16(columns.length)
columns.forEach(function (col) {
if (col == null) {
buf.addInt32(-1)
} else {
const strBuf = Buffer.from(col, 'utf8')
buf.addInt32(strBuf.length)
buf.add(strBuf)
}
})
return buf.join(true, 'D')
},
error: function (fields: any) {
return buffers.errorOrNotice(fields).join(true, 'E')
},
notice: function (fields: any) {
return buffers.errorOrNotice(fields).join(true, 'N')
},
errorOrNotice: function (fields: any) {
fields = fields || []
const buf = new BufferList()
fields.forEach(function (field: any) {
buf.addChar(field.type)
buf.addCString(field.value)
})
return buf.add(Buffer.from([0])) // terminator
},
parseComplete: function () {
return new BufferList().join(true, '1')
},
bindComplete: function () {
return new BufferList().join(true, '2')
},
notification: function (id: number, channel: string, payload: string) {
return new BufferList().addInt32(id).addCString(channel).addCString(payload).join(true, 'A')
},
emptyQuery: function () {
return new BufferList().join(true, 'I')
},
portalSuspended: function () {
return new BufferList().join(true, 's')
},
closeComplete: function () {
return new BufferList().join(true, '3')
},
copyIn: function (cols: number) {
const list = new BufferList()
// text mode
.addByte(0)
// column count
.addInt16(cols)
for (let i = 0; i < cols; i++) {
list.addInt16(i)
}
return list.join(true, 'G')
},
copyOut: function (cols: number) {
const list = new BufferList()
// text mode
.addByte(0)
// column count
.addInt16(cols)
for (let i = 0; i < cols; i++) {
list.addInt16(i)
}
return list.join(true, 'H')
},
copyData: function (bytes: Buffer) {
return new BufferList().add(bytes).join(true, 'd')
},
copyDone: function () {
return new BufferList().join(true, 'c')
},
}
export default buffers

View File

@ -1 +0,0 @@
declare module 'chunky'

7
node_modules/pg-types/.travis.yml generated vendored
View File

@ -1,7 +0,0 @@
language: node_js
node_js:
- '4'
- 'lts/*'
- 'node'
env:
- PGUSER=postgres

14
node_modules/pg-types/Makefile generated vendored
View File

@ -1,14 +0,0 @@
.PHONY: publish-patch test
test:
npm test
patch: test
npm version patch -m "Bump version"
git push origin master --tags
npm publish
minor: test
npm version minor -m "Bump version"
git push origin master --tags
npm publish

75
node_modules/pg-types/README.md generated vendored
View File

@ -1,75 +0,0 @@
# pg-types
This is the code that turns all the raw text from postgres into JavaScript types for [node-postgres](https://github.com/brianc/node-postgres.git)
## use
This module is consumed and exported from the root `pg` object of node-postgres. To access it, do the following:
```js
var types = require('pg').types
```
Generally what you'll want to do is override how a specific data-type is parsed and turned into a JavaScript type. By default the PostgreSQL backend server returns everything as strings. Every data type corresponds to a unique `OID` within the server, and these `OIDs` are sent back with the query response. So, you need to match a particluar `OID` to a function you'd like to use to take the raw text input and produce a valid JavaScript object as a result. `null` values are never parsed.
Let's do something I commonly like to do on projects: return 64-bit integers `(int8)` as JavaScript integers. Because JavaScript doesn't have support for 64-bit integers node-postgres cannot confidently parse `int8` data type results as numbers because if you have a _huge_ number it will overflow and the result you'd get back from node-postgres would not be the result in the datbase. That would be a __very bad thing__ so node-postgres just returns `int8` results as strings and leaves the parsing up to you. Let's say that you know you don't and wont ever have numbers greater than `int4` in your database, but you're tired of recieving results from the `COUNT(*)` function as strings (because that function returns `int8`). You would do this:
```js
var types = require('pg').types
types.setTypeParser(20, function(val) {
return parseInt(val)
})
```
__boom__: now you get numbers instead of strings.
Just as another example -- not saying this is a good idea -- let's say you want to return all dates from your database as [moment](http://momentjs.com/docs/) objects. Okay, do this:
```js
var types = require('pg').types
var moment = require('moment')
var parseFn = function(val) {
return val === null ? null : moment(val)
}
types.setTypeParser(types.builtins.TIMESTAMPTZ, parseFn)
types.setTypeParser(types.builtins.TIMESTAMP, parseFn)
```
_note: I've never done that with my dates, and I'm not 100% sure moment can parse all the date strings returned from postgres. It's just an example!_
If you're thinking "gee, this seems pretty handy, but how can I get a list of all the OIDs in the database and what they correspond to?!?!?!" worry not:
```bash
$ psql -c "select typname, oid, typarray from pg_type order by oid"
```
If you want to find out the OID of a specific type:
```bash
$ psql -c "select typname, oid, typarray from pg_type where typname = 'daterange' order by oid"
```
:smile:
## license
The MIT License (MIT)
Copyright (c) 2014 Brian M. Carlson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

137
node_modules/pg-types/index.d.ts generated vendored
View File

@ -1,137 +0,0 @@
export enum TypeId {
BOOL = 16,
BYTEA = 17,
CHAR = 18,
INT8 = 20,
INT2 = 21,
INT4 = 23,
REGPROC = 24,
TEXT = 25,
OID = 26,
TID = 27,
XID = 28,
CID = 29,
JSON = 114,
XML = 142,
PG_NODE_TREE = 194,
SMGR = 210,
PATH = 602,
POLYGON = 604,
CIDR = 650,
FLOAT4 = 700,
FLOAT8 = 701,
ABSTIME = 702,
RELTIME = 703,
TINTERVAL = 704,
CIRCLE = 718,
MACADDR8 = 774,
MONEY = 790,
MACADDR = 829,
INET = 869,
ACLITEM = 1033,
BPCHAR = 1042,
VARCHAR = 1043,
DATE = 1082,
TIME = 1083,
TIMESTAMP = 1114,
TIMESTAMPTZ = 1184,
INTERVAL = 1186,
TIMETZ = 1266,
BIT = 1560,
VARBIT = 1562,
NUMERIC = 1700,
REFCURSOR = 1790,
REGPROCEDURE = 2202,
REGOPER = 2203,
REGOPERATOR = 2204,
REGCLASS = 2205,
REGTYPE = 2206,
UUID = 2950,
TXID_SNAPSHOT = 2970,
PG_LSN = 3220,
PG_NDISTINCT = 3361,
PG_DEPENDENCIES = 3402,
TSVECTOR = 3614,
TSQUERY = 3615,
GTSVECTOR = 3642,
REGCONFIG = 3734,
REGDICTIONARY = 3769,
JSONB = 3802,
REGNAMESPACE = 4089,
REGROLE = 4096
}
export type builtinsTypes =
'BOOL' |
'BYTEA' |
'CHAR' |
'INT8' |
'INT2' |
'INT4' |
'REGPROC' |
'TEXT' |
'OID' |
'TID' |
'XID' |
'CID' |
'JSON' |
'XML' |
'PG_NODE_TREE' |
'SMGR' |
'PATH' |
'POLYGON' |
'CIDR' |
'FLOAT4' |
'FLOAT8' |
'ABSTIME' |
'RELTIME' |
'TINTERVAL' |
'CIRCLE' |
'MACADDR8' |
'MONEY' |
'MACADDR' |
'INET' |
'ACLITEM' |
'BPCHAR' |
'VARCHAR' |
'DATE' |
'TIME' |
'TIMESTAMP' |
'TIMESTAMPTZ' |
'INTERVAL' |
'TIMETZ' |
'BIT' |
'VARBIT' |
'NUMERIC' |
'REFCURSOR' |
'REGPROCEDURE' |
'REGOPER' |
'REGOPERATOR' |
'REGCLASS' |
'REGTYPE' |
'UUID' |
'TXID_SNAPSHOT' |
'PG_LSN' |
'PG_NDISTINCT' |
'PG_DEPENDENCIES' |
'TSVECTOR' |
'TSQUERY' |
'GTSVECTOR' |
'REGCONFIG' |
'REGDICTIONARY' |
'JSONB' |
'REGNAMESPACE' |
'REGROLE';
export type TypesBuiltins = {[key in builtinsTypes]: TypeId};
export type TypeFormat = 'text' | 'binary';
export const builtins: TypesBuiltins;
export function setTypeParser (id: TypeId, parseFn: ((value: string) => any)): void;
export function setTypeParser (id: TypeId, format: TypeFormat, parseFn: (value: string) => any): void;
export const getTypeParser: (id: TypeId, format?: TypeFormat) => any
export const arrayParser: (source: string, transform: (entry: any) => any) => any[];

47
node_modules/pg-types/index.js generated vendored
View File

@ -1,47 +0,0 @@
var textParsers = require('./lib/textParsers');
var binaryParsers = require('./lib/binaryParsers');
var arrayParser = require('./lib/arrayParser');
var builtinTypes = require('./lib/builtins');
exports.getTypeParser = getTypeParser;
exports.setTypeParser = setTypeParser;
exports.arrayParser = arrayParser;
exports.builtins = builtinTypes;
var typeParsers = {
text: {},
binary: {}
};
//the empty parse function
function noParse (val) {
return String(val);
};
//returns a function used to convert a specific type (specified by
//oid) into a result javascript type
//note: the oid can be obtained via the following sql query:
//SELECT oid FROM pg_type WHERE typname = 'TYPE_NAME_HERE';
function getTypeParser (oid, format) {
format = format || 'text';
if (!typeParsers[format]) {
return noParse;
}
return typeParsers[format][oid] || noParse;
};
function setTypeParser (oid, format, parseFn) {
if(typeof format == 'function') {
parseFn = format;
format = 'text';
}
typeParsers[format][oid] = parseFn;
};
textParsers.init(function(oid, converter) {
typeParsers.text[oid] = converter;
});
binaryParsers.init(function(oid, converter) {
typeParsers.binary[oid] = converter;
});

View File

@ -1,21 +0,0 @@
import * as types from '.';
import { expectType } from 'tsd';
// builtins
expectType<types.TypesBuiltins>(types.builtins);
// getTypeParser
const noParse = types.getTypeParser(types.builtins.NUMERIC, 'text');
const numericParser = types.getTypeParser(types.builtins.NUMERIC, 'binary');
expectType<string>(noParse('noParse'));
expectType<number>(numericParser([200, 1, 0, 15]));
// getArrayParser
const value = types.arrayParser('{1,2,3}', (num) => parseInt(num));
expectType<number[]>(value);
//setTypeParser
types.setTypeParser(types.builtins.INT8, parseInt);
types.setTypeParser(types.builtins.FLOAT8, parseFloat);
types.setTypeParser(types.builtins.FLOAT8, 'binary', (data) => data[0]);
types.setTypeParser(types.builtins.FLOAT8, 'text', parseFloat);

View File

@ -1,11 +0,0 @@
var array = require('postgres-array');
module.exports = {
create: function (source, transform) {
return {
parse: function() {
return array.parse(source, transform);
}
};
}
};

View File

@ -1,257 +0,0 @@
var parseInt64 = require('pg-int8');
var parseBits = function(data, bits, offset, invert, callback) {
offset = offset || 0;
invert = invert || false;
callback = callback || function(lastValue, newValue, bits) { return (lastValue * Math.pow(2, bits)) + newValue; };
var offsetBytes = offset >> 3;
var inv = function(value) {
if (invert) {
return ~value & 0xff;
}
return value;
};
// read first (maybe partial) byte
var mask = 0xff;
var firstBits = 8 - (offset % 8);
if (bits < firstBits) {
mask = (0xff << (8 - bits)) & 0xff;
firstBits = bits;
}
if (offset) {
mask = mask >> (offset % 8);
}
var result = 0;
if ((offset % 8) + bits >= 8) {
result = callback(0, inv(data[offsetBytes]) & mask, firstBits);
}
// read bytes
var bytes = (bits + offset) >> 3;
for (var i = offsetBytes + 1; i < bytes; i++) {
result = callback(result, inv(data[i]), 8);
}
// bits to read, that are not a complete byte
var lastBits = (bits + offset) % 8;
if (lastBits > 0) {
result = callback(result, inv(data[bytes]) >> (8 - lastBits), lastBits);
}
return result;
};
var parseFloatFromBits = function(data, precisionBits, exponentBits) {
var bias = Math.pow(2, exponentBits - 1) - 1;
var sign = parseBits(data, 1);
var exponent = parseBits(data, exponentBits, 1);
if (exponent === 0) {
return 0;
}
// parse mantissa
var precisionBitsCounter = 1;
var parsePrecisionBits = function(lastValue, newValue, bits) {
if (lastValue === 0) {
lastValue = 1;
}
for (var i = 1; i <= bits; i++) {
precisionBitsCounter /= 2;
if ((newValue & (0x1 << (bits - i))) > 0) {
lastValue += precisionBitsCounter;
}
}
return lastValue;
};
var mantissa = parseBits(data, precisionBits, exponentBits + 1, false, parsePrecisionBits);
// special cases
if (exponent == (Math.pow(2, exponentBits + 1) - 1)) {
if (mantissa === 0) {
return (sign === 0) ? Infinity : -Infinity;
}
return NaN;
}
// normale number
return ((sign === 0) ? 1 : -1) * Math.pow(2, exponent - bias) * mantissa;
};
var parseInt16 = function(value) {
if (parseBits(value, 1) == 1) {
return -1 * (parseBits(value, 15, 1, true) + 1);
}
return parseBits(value, 15, 1);
};
var parseInt32 = function(value) {
if (parseBits(value, 1) == 1) {
return -1 * (parseBits(value, 31, 1, true) + 1);
}
return parseBits(value, 31, 1);
};
var parseFloat32 = function(value) {
return parseFloatFromBits(value, 23, 8);
};
var parseFloat64 = function(value) {
return parseFloatFromBits(value, 52, 11);
};
var parseNumeric = function(value) {
var sign = parseBits(value, 16, 32);
if (sign == 0xc000) {
return NaN;
}
var weight = Math.pow(10000, parseBits(value, 16, 16));
var result = 0;
var digits = [];
var ndigits = parseBits(value, 16);
for (var i = 0; i < ndigits; i++) {
result += parseBits(value, 16, 64 + (16 * i)) * weight;
weight /= 10000;
}
var scale = Math.pow(10, parseBits(value, 16, 48));
return ((sign === 0) ? 1 : -1) * Math.round(result * scale) / scale;
};
var parseDate = function(isUTC, value) {
var sign = parseBits(value, 1);
var rawValue = parseBits(value, 63, 1);
// discard usecs and shift from 2000 to 1970
var result = new Date((((sign === 0) ? 1 : -1) * rawValue / 1000) + 946684800000);
if (!isUTC) {
result.setTime(result.getTime() + result.getTimezoneOffset() * 60000);
}
// add microseconds to the date
result.usec = rawValue % 1000;
result.getMicroSeconds = function() {
return this.usec;
};
result.setMicroSeconds = function(value) {
this.usec = value;
};
result.getUTCMicroSeconds = function() {
return this.usec;
};
return result;
};
var parseArray = function(value) {
var dim = parseBits(value, 32);
var flags = parseBits(value, 32, 32);
var elementType = parseBits(value, 32, 64);
var offset = 96;
var dims = [];
for (var i = 0; i < dim; i++) {
// parse dimension
dims[i] = parseBits(value, 32, offset);
offset += 32;
// ignore lower bounds
offset += 32;
}
var parseElement = function(elementType) {
// parse content length
var length = parseBits(value, 32, offset);
offset += 32;
// parse null values
if (length == 0xffffffff) {
return null;
}
var result;
if ((elementType == 0x17) || (elementType == 0x14)) {
// int/bigint
result = parseBits(value, length * 8, offset);
offset += length * 8;
return result;
}
else if (elementType == 0x19) {
// string
result = value.toString(this.encoding, offset >> 3, (offset += (length << 3)) >> 3);
return result;
}
else {
console.log("ERROR: ElementType not implemented: " + elementType);
}
};
var parse = function(dimension, elementType) {
var array = [];
var i;
if (dimension.length > 1) {
var count = dimension.shift();
for (i = 0; i < count; i++) {
array[i] = parse(dimension, elementType);
}
dimension.unshift(count);
}
else {
for (i = 0; i < dimension[0]; i++) {
array[i] = parseElement(elementType);
}
}
return array;
};
return parse(dims, elementType);
};
var parseText = function(value) {
return value.toString('utf8');
};
var parseBool = function(value) {
if(value === null) return null;
return (parseBits(value, 8) > 0);
};
var init = function(register) {
register(20, parseInt64);
register(21, parseInt16);
register(23, parseInt32);
register(26, parseInt32);
register(1700, parseNumeric);
register(700, parseFloat32);
register(701, parseFloat64);
register(16, parseBool);
register(1114, parseDate.bind(null, false));
register(1184, parseDate.bind(null, true));
register(1000, parseArray);
register(1007, parseArray);
register(1016, parseArray);
register(1008, parseArray);
register(1009, parseArray);
register(25, parseText);
};
module.exports = {
init: init
};

View File

@ -1,73 +0,0 @@
/**
* Following query was used to generate this file:
SELECT json_object_agg(UPPER(PT.typname), PT.oid::int4 ORDER BY pt.oid)
FROM pg_type PT
WHERE typnamespace = (SELECT pgn.oid FROM pg_namespace pgn WHERE nspname = 'pg_catalog') -- Take only builting Postgres types with stable OID (extension types are not guaranted to be stable)
AND typtype = 'b' -- Only basic types
AND typelem = 0 -- Ignore aliases
AND typisdefined -- Ignore undefined types
*/
module.exports = {
BOOL: 16,
BYTEA: 17,
CHAR: 18,
INT8: 20,
INT2: 21,
INT4: 23,
REGPROC: 24,
TEXT: 25,
OID: 26,
TID: 27,
XID: 28,
CID: 29,
JSON: 114,
XML: 142,
PG_NODE_TREE: 194,
SMGR: 210,
PATH: 602,
POLYGON: 604,
CIDR: 650,
FLOAT4: 700,
FLOAT8: 701,
ABSTIME: 702,
RELTIME: 703,
TINTERVAL: 704,
CIRCLE: 718,
MACADDR8: 774,
MONEY: 790,
MACADDR: 829,
INET: 869,
ACLITEM: 1033,
BPCHAR: 1042,
VARCHAR: 1043,
DATE: 1082,
TIME: 1083,
TIMESTAMP: 1114,
TIMESTAMPTZ: 1184,
INTERVAL: 1186,
TIMETZ: 1266,
BIT: 1560,
VARBIT: 1562,
NUMERIC: 1700,
REFCURSOR: 1790,
REGPROCEDURE: 2202,
REGOPER: 2203,
REGOPERATOR: 2204,
REGCLASS: 2205,
REGTYPE: 2206,
UUID: 2950,
TXID_SNAPSHOT: 2970,
PG_LSN: 3220,
PG_NDISTINCT: 3361,
PG_DEPENDENCIES: 3402,
TSVECTOR: 3614,
TSQUERY: 3615,
GTSVECTOR: 3642,
REGCONFIG: 3734,
REGDICTIONARY: 3769,
JSONB: 3802,
REGNAMESPACE: 4089,
REGROLE: 4096
};

View File

@ -1,215 +0,0 @@
var array = require('postgres-array')
var arrayParser = require('./arrayParser');
var parseDate = require('postgres-date');
var parseInterval = require('postgres-interval');
var parseByteA = require('postgres-bytea');
function allowNull (fn) {
return function nullAllowed (value) {
if (value === null) return value
return fn(value)
}
}
function parseBool (value) {
if (value === null) return value
return value === 'TRUE' ||
value === 't' ||
value === 'true' ||
value === 'y' ||
value === 'yes' ||
value === 'on' ||
value === '1';
}
function parseBoolArray (value) {
if (!value) return null
return array.parse(value, parseBool)
}
function parseBaseTenInt (string) {
return parseInt(string, 10)
}
function parseIntegerArray (value) {
if (!value) return null
return array.parse(value, allowNull(parseBaseTenInt))
}
function parseBigIntegerArray (value) {
if (!value) return null
return array.parse(value, allowNull(function (entry) {
return parseBigInteger(entry).trim()
}))
}
var parsePointArray = function(value) {
if(!value) { return null; }
var p = arrayParser.create(value, function(entry) {
if(entry !== null) {
entry = parsePoint(entry);
}
return entry;
});
return p.parse();
};
var parseFloatArray = function(value) {
if(!value) { return null; }
var p = arrayParser.create(value, function(entry) {
if(entry !== null) {
entry = parseFloat(entry);
}
return entry;
});
return p.parse();
};
var parseStringArray = function(value) {
if(!value) { return null; }
var p = arrayParser.create(value);
return p.parse();
};
var parseDateArray = function(value) {
if (!value) { return null; }
var p = arrayParser.create(value, function(entry) {
if (entry !== null) {
entry = parseDate(entry);
}
return entry;
});
return p.parse();
};
var parseIntervalArray = function(value) {
if (!value) { return null; }
var p = arrayParser.create(value, function(entry) {
if (entry !== null) {
entry = parseInterval(entry);
}
return entry;
});
return p.parse();
};
var parseByteAArray = function(value) {
if (!value) { return null; }
return array.parse(value, allowNull(parseByteA));
};
var parseInteger = function(value) {
return parseInt(value, 10);
};
var parseBigInteger = function(value) {
var valStr = String(value);
if (/^\d+$/.test(valStr)) { return valStr; }
return value;
};
var parseJsonArray = function(value) {
if (!value) { return null; }
return array.parse(value, allowNull(JSON.parse));
};
var parsePoint = function(value) {
if (value[0] !== '(') { return null; }
value = value.substring( 1, value.length - 1 ).split(',');
return {
x: parseFloat(value[0])
, y: parseFloat(value[1])
};
};
var parseCircle = function(value) {
if (value[0] !== '<' && value[1] !== '(') { return null; }
var point = '(';
var radius = '';
var pointParsed = false;
for (var i = 2; i < value.length - 1; i++){
if (!pointParsed) {
point += value[i];
}
if (value[i] === ')') {
pointParsed = true;
continue;
} else if (!pointParsed) {
continue;
}
if (value[i] === ','){
continue;
}
radius += value[i];
}
var result = parsePoint(point);
result.radius = parseFloat(radius);
return result;
};
var init = function(register) {
register(20, parseBigInteger); // int8
register(21, parseInteger); // int2
register(23, parseInteger); // int4
register(26, parseInteger); // oid
register(700, parseFloat); // float4/real
register(701, parseFloat); // float8/double
register(16, parseBool);
register(1082, parseDate); // date
register(1114, parseDate); // timestamp without timezone
register(1184, parseDate); // timestamp
register(600, parsePoint); // point
register(651, parseStringArray); // cidr[]
register(718, parseCircle); // circle
register(1000, parseBoolArray);
register(1001, parseByteAArray);
register(1005, parseIntegerArray); // _int2
register(1007, parseIntegerArray); // _int4
register(1028, parseIntegerArray); // oid[]
register(1016, parseBigIntegerArray); // _int8
register(1017, parsePointArray); // point[]
register(1021, parseFloatArray); // _float4
register(1022, parseFloatArray); // _float8
register(1231, parseFloatArray); // _numeric
register(1014, parseStringArray); //char
register(1015, parseStringArray); //varchar
register(1008, parseStringArray);
register(1009, parseStringArray);
register(1040, parseStringArray); // macaddr[]
register(1041, parseStringArray); // inet[]
register(1115, parseDateArray); // timestamp without time zone[]
register(1182, parseDateArray); // _date
register(1185, parseDateArray); // timestamp with time zone[]
register(1186, parseInterval);
register(1187, parseIntervalArray);
register(17, parseByteA);
register(114, JSON.parse.bind(JSON)); // json
register(3802, JSON.parse.bind(JSON)); // jsonb
register(199, parseJsonArray); // json[]
register(3807, parseJsonArray); // jsonb[]
register(3907, parseStringArray); // numrange[]
register(2951, parseStringArray); // uuid[]
register(791, parseStringArray); // money[]
register(1183, parseStringArray); // time[]
register(1270, parseStringArray); // timetz[]
};
module.exports = {
init: init
};

42
node_modules/pg-types/package.json generated vendored
View File

@ -1,42 +0,0 @@
{
"name": "pg-types",
"version": "2.2.0",
"description": "Query result type converters for node-postgres",
"main": "index.js",
"scripts": {
"test": "tape test/*.js | tap-spec && npm run test-ts",
"test-ts": "if-node-version '>= 8' tsd"
},
"repository": {
"type": "git",
"url": "git://github.com/brianc/node-pg-types.git"
},
"keywords": [
"postgres",
"PostgreSQL",
"pg"
],
"author": "Brian M. Carlson",
"license": "MIT",
"bugs": {
"url": "https://github.com/brianc/node-pg-types/issues"
},
"homepage": "https://github.com/brianc/node-pg-types",
"devDependencies": {
"if-node-version": "^1.1.1",
"pff": "^1.0.0",
"tap-spec": "^4.0.0",
"tape": "^4.0.0",
"tsd": "^0.7.4"
},
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
}

24
node_modules/pg-types/test/index.js generated vendored
View File

@ -1,24 +0,0 @@
var test = require('tape')
var printf = require('pff')
var getTypeParser = require('../').getTypeParser
var types = require('./types')
test('types', function (t) {
Object.keys(types).forEach(function (typeName) {
var type = types[typeName]
t.test(typeName, function (t) {
var parser = getTypeParser(type.id, type.format)
type.tests.forEach(function (tests) {
var input = tests[0]
var expected = tests[1]
var result = parser(input)
if (typeof expected === 'function') {
return expected(t, result)
}
t.equal(result, expected)
})
t.end()
})
})
})

597
node_modules/pg-types/test/types.js generated vendored
View File

@ -1,597 +0,0 @@
'use strict'
exports['string/varchar'] = {
format: 'text',
id: 1043,
tests: [
['bang', 'bang']
]
}
exports['integer/int4'] = {
format: 'text',
id: 23,
tests: [
['2147483647', 2147483647]
]
}
exports['smallint/int2'] = {
format: 'text',
id: 21,
tests: [
['32767', 32767]
]
}
exports['bigint/int8'] = {
format: 'text',
id: 20,
tests: [
['9223372036854775807', '9223372036854775807']
]
}
exports.oid = {
format: 'text',
id: 26,
tests: [
['103', 103]
]
}
var bignum = '31415926535897932384626433832795028841971693993751058.16180339887498948482045868343656381177203091798057628'
exports.numeric = {
format: 'text',
id: 1700,
tests: [
[bignum, bignum]
]
}
exports['real/float4'] = {
format: 'text',
id: 700,
tests: [
['123.456', 123.456]
]
}
exports['double precision / float 8'] = {
format: 'text',
id: 701,
tests: [
['12345678.12345678', 12345678.12345678]
]
}
exports.boolean = {
format: 'text',
id: 16,
tests: [
['TRUE', true],
['t', true],
['true', true],
['y', true],
['yes', true],
['on', true],
['1', true],
['f', false],
[null, null]
]
}
exports.timestamptz = {
format: 'text',
id: 1184,
tests: [
[
'2010-10-31 14:54:13.74-05:30',
dateEquals(2010, 9, 31, 20, 24, 13, 740)
],
[
'2011-01-23 22:05:00.68-06',
dateEquals(2011, 0, 24, 4, 5, 0, 680)
],
[
'2010-10-30 14:11:12.730838Z',
dateEquals(2010, 9, 30, 14, 11, 12, 730)
],
[
'2010-10-30 13:10:01+05',
dateEquals(2010, 9, 30, 8, 10, 1, 0)
]
]
}
exports.timestamp = {
format: 'text',
id: 1114,
tests: [
[
'2010-10-31 00:00:00',
function (t, value) {
t.equal(
value.toUTCString(),
new Date(2010, 9, 31, 0, 0, 0, 0, 0).toUTCString()
)
t.equal(
value.toString(),
new Date(2010, 9, 31, 0, 0, 0, 0, 0, 0).toString()
)
}
]
]
}
exports.date = {
format: 'text',
id: 1082,
tests: [
['2010-10-31', function (t, value) {
var now = new Date(2010, 9, 31)
dateEquals(
2010,
now.getUTCMonth(),
now.getUTCDate(),
now.getUTCHours(), 0, 0, 0)(t, value)
t.equal(value.getHours(), now.getHours())
}]
]
}
exports.inet = {
format: 'text',
id: 869,
tests: [
['8.8.8.8', '8.8.8.8'],
['2001:4860:4860::8888', '2001:4860:4860::8888'],
['127.0.0.1', '127.0.0.1'],
['fd00:1::40e', 'fd00:1::40e'],
['1.2.3.4', '1.2.3.4']
]
}
exports.cidr = {
format: 'text',
id: 650,
tests: [
['172.16.0.0/12', '172.16.0.0/12'],
['fe80::/10', 'fe80::/10'],
['fc00::/7', 'fc00::/7'],
['192.168.0.0/24', '192.168.0.0/24'],
['10.0.0.0/8', '10.0.0.0/8']
]
}
exports.macaddr = {
format: 'text',
id: 829,
tests: [
['08:00:2b:01:02:03', '08:00:2b:01:02:03'],
['16:10:9f:0d:66:00', '16:10:9f:0d:66:00']
]
}
exports.numrange = {
format: 'text',
id: 3906,
tests: [
['[,]', '[,]'],
['(,)', '(,)'],
['(,]', '(,]'],
['[1,)', '[1,)'],
['[,1]', '[,1]'],
['(1,2)', '(1,2)'],
['(1,20.5]', '(1,20.5]']
]
}
exports.interval = {
format: 'text',
id: 1186,
tests: [
['01:02:03', function (t, value) {
t.equal(value.toPostgres(), '3 seconds 2 minutes 1 hours')
t.deepEqual(value, {hours: 1, minutes: 2, seconds: 3})
}],
['01:02:03.456', function (t, value) {
t.deepEqual(value, {hours: 1, minutes:2, seconds: 3, milliseconds: 456})
}],
['1 year -32 days', function (t, value) {
t.equal(value.toPostgres(), '-32 days 1 years')
t.deepEqual(value, {years: 1, days: -32})
}],
['1 day -00:00:03', function (t, value) {
t.equal(value.toPostgres(), '-3 seconds 1 days')
t.deepEqual(value, {days: 1, seconds: -3})
}]
]
}
exports.bytea = {
format: 'text',
id: 17,
tests: [
['foo\\000\\200\\\\\\377', function (t, value) {
var buffer = new Buffer([102, 111, 111, 0, 128, 92, 255])
t.ok(buffer.equals(value))
}],
['', function (t, value) {
var buffer = new Buffer(0)
t.ok(buffer.equals(value))
}]
]
}
exports['array/boolean'] = {
format: 'text',
id: 1000,
tests: [
['{true,false}', function (t, value) {
t.deepEqual(value, [true, false])
}]
]
}
exports['array/char'] = {
format: 'text',
id: 1014,
tests: [
['{foo,bar}', function (t, value) {
t.deepEqual(value, ['foo', 'bar'])
}]
]
}
exports['array/varchar'] = {
format: 'text',
id: 1015,
tests: [
['{foo,bar}', function (t, value) {
t.deepEqual(value, ['foo', 'bar'])
}]
]
}
exports['array/text'] = {
format: 'text',
id: 1008,
tests: [
['{foo}', function (t, value) {
t.deepEqual(value, ['foo'])
}]
]
}
exports['array/bytea'] = {
format: 'text',
id: 1001,
tests: [
['{"\\\\x00000000"}', function (t, value) {
var buffer = new Buffer('00000000', 'hex')
t.ok(Array.isArray(value))
t.equal(value.length, 1)
t.ok(buffer.equals(value[0]))
}],
['{NULL,"\\\\x4e554c4c"}', function (t, value) {
var buffer = new Buffer('4e554c4c', 'hex')
t.ok(Array.isArray(value))
t.equal(value.length, 2)
t.equal(value[0], null)
t.ok(buffer.equals(value[1]))
}],
]
}
exports['array/numeric'] = {
format: 'text',
id: 1231,
tests: [
['{1.2,3.4}', function (t, value) {
t.deepEqual(value, [1.2, 3.4])
}]
]
}
exports['array/int2'] = {
format: 'text',
id: 1005,
tests: [
['{-32768, -32767, 32766, 32767}', function (t, value) {
t.deepEqual(value, [-32768, -32767, 32766, 32767])
}]
]
}
exports['array/int4'] = {
format: 'text',
id: 1005,
tests: [
['{-2147483648, -2147483647, 2147483646, 2147483647}', function (t, value) {
t.deepEqual(value, [-2147483648, -2147483647, 2147483646, 2147483647])
}]
]
}
exports['array/int8'] = {
format: 'text',
id: 1016,
tests: [
[
'{-9223372036854775808, -9223372036854775807, 9223372036854775806, 9223372036854775807}',
function (t, value) {
t.deepEqual(value, [
'-9223372036854775808',
'-9223372036854775807',
'9223372036854775806',
'9223372036854775807'
])
}
]
]
}
exports['array/json'] = {
format: 'text',
id: 199,
tests: [
[
'{{1,2},{[3],"[4,5]"},{null,NULL}}',
function (t, value) {
t.deepEqual(value, [
[1, 2],
[[3], [4, 5]],
[null, null],
])
}
]
]
}
exports['array/jsonb'] = {
format: 'text',
id: 3807,
tests: exports['array/json'].tests
}
exports['array/point'] = {
format: 'text',
id: 1017,
tests: [
['{"(25.1,50.5)","(10.1,40)"}', function (t, value) {
t.deepEqual(value, [{x: 25.1, y: 50.5}, {x: 10.1, y: 40}])
}]
]
}
exports['array/oid'] = {
format: 'text',
id: 1028,
tests: [
['{25864,25860}', function (t, value) {
t.deepEqual(value, [25864, 25860])
}]
]
}
exports['array/float4'] = {
format: 'text',
id: 1021,
tests: [
['{1.2, 3.4}', function (t, value) {
t.deepEqual(value, [1.2, 3.4])
}]
]
}
exports['array/float8'] = {
format: 'text',
id: 1022,
tests: [
['{-12345678.1234567, 12345678.12345678}', function (t, value) {
t.deepEqual(value, [-12345678.1234567, 12345678.12345678])
}]
]
}
exports['array/date'] = {
format: 'text',
id: 1182,
tests: [
['{2014-01-01,2015-12-31}', function (t, value) {
var expecteds = [new Date(2014, 0, 1), new Date(2015, 11, 31)]
t.equal(value.length, 2)
value.forEach(function (date, index) {
var expected = expecteds[index]
dateEquals(
expected.getUTCFullYear(),
expected.getUTCMonth(),
expected.getUTCDate(),
expected.getUTCHours(), 0, 0, 0)(t, date)
})
}]
]
}
exports['array/interval'] = {
format: 'text',
id: 1187,
tests: [
['{01:02:03,1 day -00:00:03}', function (t, value) {
var expecteds = [{hours: 1, minutes: 2, seconds: 3},
{days: 1, seconds: -3}]
t.equal(value.length, 2)
t.deepEqual(value, expecteds);
}]
]
}
exports['array/inet'] = {
format: 'text',
id: 1041,
tests: [
['{8.8.8.8}', function (t, value) {
t.deepEqual(value, ['8.8.8.8']);
}],
['{2001:4860:4860::8888}', function (t, value) {
t.deepEqual(value, ['2001:4860:4860::8888']);
}],
['{127.0.0.1,fd00:1::40e,1.2.3.4}', function (t, value) {
t.deepEqual(value, ['127.0.0.1', 'fd00:1::40e', '1.2.3.4']);
}]
]
}
exports['array/cidr'] = {
format: 'text',
id: 651,
tests: [
['{172.16.0.0/12}', function (t, value) {
t.deepEqual(value, ['172.16.0.0/12']);
}],
['{fe80::/10}', function (t, value) {
t.deepEqual(value, ['fe80::/10']);
}],
['{10.0.0.0/8,fc00::/7,192.168.0.0/24}', function (t, value) {
t.deepEqual(value, ['10.0.0.0/8', 'fc00::/7', '192.168.0.0/24']);
}]
]
}
exports['array/macaddr'] = {
format: 'text',
id: 1040,
tests: [
['{08:00:2b:01:02:03,16:10:9f:0d:66:00}', function (t, value) {
t.deepEqual(value, ['08:00:2b:01:02:03', '16:10:9f:0d:66:00']);
}]
]
}
exports['array/numrange'] = {
format: 'text',
id: 3907,
tests: [
['{"[1,2]","(4.5,8)","[10,40)","(-21.2,60.3]"}', function (t, value) {
t.deepEqual(value, ['[1,2]', '(4.5,8)', '[10,40)', '(-21.2,60.3]']);
}],
['{"[,20]","[3,]","[,]","(,35)","(1,)","(,)"}', function (t, value) {
t.deepEqual(value, ['[,20]', '[3,]', '[,]', '(,35)', '(1,)', '(,)']);
}],
['{"[,20)","[3,)","[,)","[,35)","[1,)","[,)"}', function (t, value) {
t.deepEqual(value, ['[,20)', '[3,)', '[,)', '[,35)', '[1,)', '[,)']);
}]
]
}
exports['binary-string/varchar'] = {
format: 'binary',
id: 1043,
tests: [
['bang', 'bang']
]
}
exports['binary-integer/int4'] = {
format: 'binary',
id: 23,
tests: [
[[0, 0, 0, 100], 100]
]
}
exports['binary-smallint/int2'] = {
format: 'binary',
id: 21,
tests: [
[[0, 101], 101]
]
}
exports['binary-bigint/int8'] = {
format: 'binary',
id: 20,
tests: [
[new Buffer([0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), '9223372036854775807']
]
}
exports['binary-oid'] = {
format: 'binary',
id: 26,
tests: [
[[0, 0, 0, 103], 103]
]
}
exports['binary-numeric'] = {
format: 'binary',
id: 1700,
tests: [
[
[0, 2, 0, 0, 0, 0, 0, hex('0x64'), 0, 12, hex('0xd'), hex('0x48'), 0, 0, 0, 0],
12.34
]
]
}
exports['binary-real/float4'] = {
format: 'binary',
id: 700,
tests: [
[['0x41', '0x48', '0x00', '0x00'].map(hex), 12.5]
]
}
exports['binary-boolean'] = {
format: 'binary',
id: 16,
tests: [
[[1], true],
[[0], false],
[null, null]
]
}
exports['binary-string'] = {
format: 'binary',
id: 25,
tests: [
[
new Buffer(['0x73', '0x6c', '0x61', '0x64', '0x64', '0x61'].map(hex)),
'sladda'
]
]
}
exports.point = {
format: 'text',
id: 600,
tests: [
['(25.1,50.5)', function (t, value) {
t.deepEqual(value, {x: 25.1, y: 50.5})
}]
]
}
exports.circle = {
format: 'text',
id: 718,
tests: [
['<(25,10),5>', function (t, value) {
t.deepEqual(value, {x: 25, y: 10, radius: 5})
}]
]
}
function hex (string) {
return parseInt(string, 16)
}
function dateEquals () {
var timestamp = Date.UTC.apply(Date, arguments)
return function (t, value) {
t.equal(value.toUTCString(), new Date(timestamp).toUTCString())
}
}

21
node_modules/pg/LICENSE generated vendored
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2010 - 2021 Brian Carlson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

95
node_modules/pg/README.md generated vendored
View File

@ -1,95 +0,0 @@
# node-postgres
[![Build Status](https://secure.travis-ci.org/brianc/node-postgres.svg?branch=master)](http://travis-ci.org/brianc/node-postgres)
<span class="badge-npmversion"><a href="https://npmjs.org/package/pg" title="View this project on NPM"><img src="https://img.shields.io/npm/v/pg.svg" alt="NPM version" /></a></span>
<span class="badge-npmdownloads"><a href="https://npmjs.org/package/pg" title="View this project on NPM"><img src="https://img.shields.io/npm/dm/pg.svg" alt="NPM downloads" /></a></span>
Non-blocking PostgreSQL client for Node.js. Pure JavaScript and optional native libpq bindings.
## Install
```sh
$ npm install pg
```
---
## :star: [Documentation](https://node-postgres.com) :star:
### Features
- Pure JavaScript client and native libpq bindings share _the same API_
- Connection pooling
- Extensible JS ↔ PostgreSQL data-type coercion
- Supported PostgreSQL features
- Parameterized queries
- Named statements with query plan caching
- Async notifications with `LISTEN/NOTIFY`
- Bulk import & export with `COPY TO/COPY FROM`
### Extras
node-postgres is by design pretty light on abstractions. These are some handy modules we've been using over the years to complete the picture.
The entire list can be found on our [wiki](https://github.com/brianc/node-postgres/wiki/Extras).
## Support
node-postgres is free software. If you encounter a bug with the library please open an issue on the [GitHub repo](https://github.com/brianc/node-postgres). If you have questions unanswered by the documentation please open an issue pointing out how the documentation was unclear & I will do my best to make it better!
When you open an issue please provide:
- version of Node
- version of Postgres
- smallest possible snippet of code to reproduce the problem
You can also follow me [@briancarlson](https://twitter.com/briancarlson) if that's your thing. I try to always announce noteworthy changes & developments with node-postgres on Twitter.
## Sponsorship :two_hearts:
node-postgres's continued development has been made possible in part by generous financial support from [the community](https://github.com/brianc/node-postgres/blob/master/SPONSORS.md).
If you or your company are benefiting from node-postgres and would like to help keep the project financially sustainable [please consider supporting](https://github.com/sponsors/brianc) its development.
### Featured sponsor
Special thanks to [medplum](https://medplum.com) for their generous and thoughtful support of node-postgres!
![medplum](https://raw.githubusercontent.com/medplum/medplum-logo/refs/heads/main/medplum-logo.png)
## Contributing
**:heart: contributions!**
I will **happily** accept your pull request if it:
- **has tests**
- looks reasonable
- does not break backwards compatibility
If your change involves breaking backwards compatibility please please point that out in the pull request & we can discuss & plan when and how to release it and what type of documentation or communicate it will require.
## Troubleshooting and FAQ
The causes and solutions to common errors can be found among the [Frequently Asked Questions (FAQ)](https://github.com/brianc/node-postgres/wiki/FAQ)
## License
Copyright (c) 2010-2020 Brian Carlson (brian.m.carlson@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More