brasil-ceps-offline 2.1.3 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -148,6 +148,54 @@ import type {
148
148
 
149
149
  ---
150
150
 
151
+ ## ☁️ Deploy na Vercel / Next.js
152
+
153
+ A Vercel não executa `postinstall` durante o build de produção por padrão. Siga os passos abaixo para garantir que o banco de dados seja baixado antes do deploy.
154
+
155
+ ### 1. Adicione o sync ao script de build (`package.json`)
156
+
157
+ ```json
158
+ {
159
+ "scripts": {
160
+ "build": "npx brasil-ceps-offline sync ./public/.db && next build"
161
+ }
162
+ }
163
+ ```
164
+
165
+ ### 2. Inclua o banco no rastreamento de arquivos (`next.config.js`)
166
+
167
+ O Next.js usa **Output File Tracing** para empacotar apenas os arquivos necessários. O SQLite precisa ser incluído explicitamente:
168
+
169
+ ```js
170
+ // next.config.js
171
+ /** @type {import('next').NextConfig} */
172
+ const nextConfig = {
173
+ experimental: {
174
+ outputFileTracingIncludes: {
175
+ // Inclui o banco para todas as rotas da API
176
+ '/api/**': [
177
+ './public/.db/**',
178
+ './node_modules/brasil-ceps-offline/.db/**',
179
+ ],
180
+ },
181
+ },
182
+ };
183
+
184
+ module.exports = nextConfig;
185
+ ```
186
+
187
+ ### 3. Variável de ambiente (opcional)
188
+
189
+ Se preferir armazenar o banco fora de `node_modules` (ex: em `/tmp` na Vercel):
190
+
191
+ ```bash
192
+ BRASIL_CEPS_DB_PATH=/tmp/ceps.sqlite
193
+ ```
194
+
195
+ > **Atenção:** O diretório `/tmp` na Vercel é efêmero — o banco precisará ser baixado a cada cold start. Para produção de alta disponibilidade, use o caminho padrão via `node_modules`.
196
+
197
+ ---
198
+
151
199
  ## ⚖️ Licença e Fonte dos Dados
152
200
 
153
201
  **Código fonte:** MIT License
package/dist/bin/cli.js CHANGED
@@ -15,7 +15,7 @@ function printHelp() {
15
15
  brasil-ceps-offline — CLI de gerenciamento do banco de dados
16
16
 
17
17
  Uso:
18
- npx brasil-ceps-offline <comando>
18
+ npx brasil-ceps-offline <comando> [destino]
19
19
 
20
20
  Comandos:
21
21
  sync Baixa ou atualiza o banco de dados para a versão mais recente
@@ -24,6 +24,7 @@ Comandos:
24
24
 
25
25
  Exemplos:
26
26
  npx brasil-ceps-offline sync
27
+ npx brasil-ceps-offline sync ./public/.db
27
28
  npx brasil-ceps-offline status
28
29
  `.trim());
29
30
  }
@@ -58,7 +59,7 @@ async function main() {
58
59
  const command = (process.argv[2] ?? 'help');
59
60
  switch (command) {
60
61
  case 'sync':
61
- await (0, download_db_1.downloadDatabase)();
62
+ await (0, download_db_1.downloadDatabase)(process.argv[3]);
62
63
  break;
63
64
  case 'status':
64
65
  printStatus();
package/dist/config.d.ts CHANGED
@@ -1,21 +1,2 @@
1
- /**
2
- * Caminho para o banco de dados SQLite
3
- */
1
+ export declare const DB_DIR: string;
4
2
  export declare const DB_PATH: string;
5
- /**
6
- * URL padrão para download do banco de dados (GitHub Releases)
7
- * NOTA: Você precisa substituir isso pela URL real do seu repositório
8
- */
9
- export declare const DEFAULT_DOWNLOAD_URL: string;
10
- /**
11
- * Timeout para downloads (30 segundos)
12
- */
13
- export declare const DOWNLOAD_TIMEOUT: number;
14
- /**
15
- * Tamanho máximo de chunk para downloads (1MB)
16
- */
17
- export declare const CHUNK_SIZE: number;
18
- /**
19
- * Número de tentativas de retry para download
20
- */
21
- export declare const MAX_RETRIES = 3;
package/dist/config.js CHANGED
@@ -3,27 +3,42 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.MAX_RETRIES = exports.CHUNK_SIZE = exports.DOWNLOAD_TIMEOUT = exports.DEFAULT_DOWNLOAD_URL = exports.DB_PATH = void 0;
6
+ exports.DB_PATH = exports.DB_DIR = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
8
9
  /**
9
- * Caminho para o banco de dados SQLite
10
+ * Resolve o diretório `.db` tentando múltiplos candidatos em ordem de prioridade.
11
+ *
12
+ * Prioridade:
13
+ * 1. Vercel / Next.js build — `<cwd>/node_modules/brasil-ceps-offline/.db`
14
+ * 2. Instalação npm padrão — dois níveis acima de `dist/scripts/` → `<pkg>/.db`
15
+ * 3. Desenvolvimento local — um nível acima de `dist/` → `<pkg>/.db`
16
+ * 4. Variável de ambiente — `BRASIL_CEPS_DB_PATH` (caminho completo para o .sqlite)
17
+ *
18
+ * Retorna o primeiro diretório que já contenha `ceps.sqlite`, ou o caminho
19
+ * do candidato nº 1 como destino de instalação quando nenhum arquivo existe ainda.
10
20
  */
11
- exports.DB_PATH = path_1.default.join(__dirname, '..', '.db', 'ceps.sqlite');
12
- /**
13
- * URL padrão para download do banco de dados (GitHub Releases)
14
- * NOTA: Você precisa substituir isso pela URL real do seu repositório
15
- */
16
- exports.DEFAULT_DOWNLOAD_URL = process.env.BRASIL_CEPS_URL ||
17
- 'https://github.com/seu-usuario/brasil-ceps-offline-db/releases/download/latest/ceps.sqlite.zip';
18
- /**
19
- * Timeout para downloads (30 segundos)
20
- */
21
- exports.DOWNLOAD_TIMEOUT = 30 * 1000;
22
- /**
23
- * Tamanho máximo de chunk para downloads (1MB)
24
- */
25
- exports.CHUNK_SIZE = 1024 * 1024;
26
- /**
27
- * Número de tentativas de retry para download
28
- */
29
- exports.MAX_RETRIES = 3;
21
+ function resolveDbDir() {
22
+ // Permite override total via env (útil em deploys customizados)
23
+ if (process.env.BRASIL_CEPS_DB_PATH) {
24
+ return path_1.default.dirname(process.env.BRASIL_CEPS_DB_PATH);
25
+ }
26
+ const candidates = [
27
+ // 1. Vercel/Next (cwd = raiz do projeto do usuário)
28
+ path_1.default.join(process.cwd(), 'node_modules', 'brasil-ceps-offline', '.db'),
29
+ // 2. npm install padrão: dist/scripts/ → ../../.db
30
+ path_1.default.join(__dirname, '..', '..', '.db'),
31
+ // 3. Fallback: dist/ ../.db
32
+ path_1.default.join(__dirname, '..', '.db'),
33
+ ];
34
+ // Retorna o primeiro candidato que já tem o banco instalado
35
+ for (const dir of candidates) {
36
+ if (fs_1.default.existsSync(path_1.default.join(dir, 'ceps.sqlite'))) {
37
+ return dir;
38
+ }
39
+ }
40
+ // Nenhum banco encontrado → usa o candidato nº 1 como destino de instalação
41
+ return candidates[0];
42
+ }
43
+ exports.DB_DIR = resolveDbDir();
44
+ exports.DB_PATH = process.env.BRASIL_CEPS_DB_PATH ?? path_1.default.join(exports.DB_DIR, 'ceps.sqlite');
package/dist/index.d.ts CHANGED
@@ -129,4 +129,5 @@ export declare function getCepsInRadius(centerCep: string, radiusKm: number, lim
129
129
  export declare function getTimezone(cep: string): TimezoneResult | null;
130
130
  /** Valida se o código IBGE informado corresponde ao CEP. */
131
131
  export declare function validateIbge(cep: string, ibgeProvided: number): IbgeValidationResult | null;
132
+ export { downloadDatabase } from './scripts/download-db';
132
133
  export type { Address, DistanceResult, AddressWithDistance, TimezoneResult, IbgeValidationResult, Coordinates, FallbackOptions, BrasilCepsConfig, } from './types';
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.brasilCeps = exports.BrasilCepsOffline = void 0;
6
+ exports.downloadDatabase = exports.brasilCeps = exports.BrasilCepsOffline = void 0;
7
7
  exports.init = init;
8
8
  exports.findByCep = findByCep;
9
9
  exports.findByAddress = findByAddress;
@@ -13,12 +13,48 @@ exports.getCepsInRadius = getCepsInRadius;
13
13
  exports.getTimezone = getTimezone;
14
14
  exports.validateIbge = validateIbge;
15
15
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
16
- const config_1 = require("./config");
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
17
18
  const database_1 = require("./database");
18
19
  const SELECT_ALL = `
19
20
  cep, state, city, neighborhood, street,
20
21
  ibge, ddd, timezone, latitude, longitude
21
22
  `;
23
+ function log(msg) { console.log(`[brasil-ceps-offline] ${msg}`); }
24
+ function resolveDatabasePath() {
25
+ const envPath = process.env.BRASIL_CEPS_DB_PATH;
26
+ if (envPath) {
27
+ if (!fs_1.default.existsSync(envPath)) {
28
+ throw new Error(`BRASIL_CEPS_DB_PATH definido, mas arquivo não existe: ${envPath}`);
29
+ }
30
+ return { dbPath: envPath, mode: 'env' };
31
+ }
32
+ const candidates = [
33
+ {
34
+ mode: 'serverless',
35
+ dbPath: path_1.default.join(process.cwd(), 'public', '.db', 'ceps.sqlite'),
36
+ },
37
+ {
38
+ mode: 'node',
39
+ dbPath: path_1.default.join(process.cwd(), 'node_modules', 'brasil-ceps-offline', '.db', 'ceps.sqlite'),
40
+ },
41
+ {
42
+ mode: 'relative',
43
+ dbPath: path_1.default.join(__dirname, '..', '..', '.db', 'ceps.sqlite'),
44
+ },
45
+ {
46
+ mode: 'cwd',
47
+ dbPath: path_1.default.join(process.cwd(), '.db', 'ceps.sqlite'),
48
+ },
49
+ ];
50
+ for (const candidate of candidates) {
51
+ if (fs_1.default.existsSync(candidate.dbPath))
52
+ return candidate;
53
+ }
54
+ const tried = candidates.map((c) => `- ${c.dbPath}`).join('\n');
55
+ throw new Error(`Banco de dados não encontrado. Caminhos testados:\n${tried}\n` +
56
+ `Execute "npx brasil-ceps-offline sync" (Node) ou "npx brasil-ceps-offline sync ./public/.db" (Vercel/Next).`);
57
+ }
22
58
  /**
23
59
  * Motor de Inteligência Geográfica Offline para CEPs brasileiros.
24
60
  * Dados do Censo IBGE 2022 — sem rede, sem API keys, sem limites.
@@ -42,13 +78,21 @@ class BrasilCepsOffline {
42
78
  initialize(_config) {
43
79
  if (this.isInitialized)
44
80
  return;
81
+ const { dbPath, mode } = resolveDatabasePath();
82
+ if (mode === 'serverless') {
83
+ log(`Modo Serverless detectado. Usando banco em ${path_1.default.join(process.cwd(), 'public', '.db')}`);
84
+ }
45
85
  try {
46
- this.db = new better_sqlite3_1.default(config_1.DB_PATH, { readonly: true, fileMustExist: true });
86
+ // Valida explicitamente antes de instanciar o better-sqlite3 (evita erros opacos em serverless)
87
+ if (!fs_1.default.existsSync(dbPath)) {
88
+ throw new Error(`Arquivo não existe: ${dbPath}`);
89
+ }
90
+ this.db = new better_sqlite3_1.default(dbPath, { readonly: true, fileMustExist: true });
47
91
  this.db.pragma('cache_size = -32000'); // 32 MB cache
48
92
  this.isInitialized = true;
49
93
  }
50
94
  catch (error) {
51
- throw new Error(`Falha ao abrir o banco de dados em [${config_1.DB_PATH}]: ${error}`);
95
+ throw new Error(`Falha ao abrir o banco de dados em [${dbPath}]: ${error}`);
52
96
  }
53
97
  }
54
98
  /**
@@ -400,3 +444,5 @@ function validateIbge(cep, ibgeProvided) {
400
444
  exports.brasilCeps.initialize();
401
445
  return exports.brasilCeps.validateIbge(cep, ibgeProvided);
402
446
  }
447
+ var download_db_1 = require("./scripts/download-db");
448
+ Object.defineProperty(exports, "downloadDatabase", { enumerable: true, get: function () { return download_db_1.downloadDatabase; } });
@@ -1,2 +1,2 @@
1
- declare function downloadDatabase(): Promise<void>;
1
+ declare function downloadDatabase(customPath?: string): Promise<void>;
2
2
  export { downloadDatabase };
@@ -9,6 +9,7 @@ const path_1 = __importDefault(require("path"));
9
9
  const promises_1 = require("stream/promises");
10
10
  const stream_1 = require("stream");
11
11
  const zlib_1 = __importDefault(require("zlib"));
12
+ const config_1 = require("../config");
12
13
  // ─── Constantes ──────────────────────────────────────────────────────────────
13
14
  const REPO = 'kaique-oliveira/brasil-ceps-offline';
14
15
  const ASSET_NAME = 'brasil-ceps.sqlite.gz';
@@ -16,32 +17,30 @@ const GITHUB_API = `https://api.github.com/repos/${REPO}/releases/latest`;
16
17
  /** URL estática de fallback — usada se a API do GitHub falhar ou atingir rate limit. */
17
18
  const FALLBACK_URL = `https://github.com/${REPO}/releases/download/v2.0.5/${ASSET_NAME}`;
18
19
  const USER_AGENT = 'brasil-ceps-offline-installer';
19
- const DB_DIR = path_1.default.join(__dirname, '..', '..', '.db');
20
- const DB_PATH = path_1.default.join(DB_DIR, 'ceps.sqlite');
21
- const VERSION_FILE = path_1.default.join(DB_DIR, 'version.json');
22
20
  // ─── Helpers ─────────────────────────────────────────────────────────────────
23
21
  function log(msg) { console.log(`[brasil-ceps-offline] ${msg}`); }
24
22
  function warn(msg) { console.warn(`[brasil-ceps-offline] ⚠️ ${msg}`); }
25
- function readVersionCache() {
23
+ function readVersionCache(versionFile) {
26
24
  try {
27
- if (!fs_1.default.existsSync(VERSION_FILE))
25
+ if (!fs_1.default.existsSync(versionFile))
28
26
  return null;
29
- return JSON.parse(fs_1.default.readFileSync(VERSION_FILE, 'utf-8'));
27
+ return JSON.parse(fs_1.default.readFileSync(versionFile, 'utf-8'));
30
28
  }
31
29
  catch {
32
30
  return null;
33
31
  }
34
32
  }
35
- function writeVersionCache(tag) {
33
+ function writeVersionCache(versionFile, tag) {
36
34
  const data = { tag, downloadedAt: new Date().toISOString() };
37
- fs_1.default.writeFileSync(VERSION_FILE, JSON.stringify(data, null, 2));
35
+ fs_1.default.writeFileSync(versionFile, JSON.stringify(data, null, 2));
38
36
  }
39
- function hasValidSchema() {
37
+ function hasValidSchema(dbPath) {
40
38
  try {
41
- if (!fs_1.default.existsSync(DB_PATH))
39
+ if (!fs_1.default.existsSync(dbPath))
42
40
  return false;
41
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
43
42
  const Database = require('better-sqlite3');
44
- const db = new Database(DB_PATH, { readonly: true, fileMustExist: true });
43
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
45
44
  const cols = db.pragma('table_info(addresses)');
46
45
  db.close();
47
46
  return cols.some((c) => c.name === 'timezone');
@@ -51,10 +50,6 @@ function hasValidSchema() {
51
50
  }
52
51
  }
53
52
  // ─── Descoberta dinâmica do release ──────────────────────────────────────────
54
- /**
55
- * Tarefa 1: Consulta a API do GitHub para descobrir a tag e URL do asset mais recente.
56
- * Tarefa 3: Retorna null em caso de falha (rate limit, rede, etc.) — o chamador usa o fallback.
57
- */
58
53
  async function fetchLatestRelease() {
59
54
  try {
60
55
  log('Consultando GitHub API para release mais recente...');
@@ -88,7 +83,7 @@ async function fetchLatestRelease() {
88
83
  }
89
84
  }
90
85
  // ─── Download e descompactação ────────────────────────────────────────────────
91
- async function downloadAndExtract(url) {
86
+ async function downloadAndExtract(url, dbPath) {
92
87
  log(`Baixando banco de dados de: ${url}`);
93
88
  const res = await fetch(url, {
94
89
  headers: { 'User-Agent': USER_AGENT },
@@ -98,64 +93,64 @@ async function downloadAndExtract(url) {
98
93
  if (!res.body)
99
94
  throw new Error('Corpo da resposta vazio.');
100
95
  const nodeStream = stream_1.Readable.fromWeb(res.body);
101
- const fileStream = fs_1.default.createWriteStream(DB_PATH);
96
+ const fileStream = fs_1.default.createWriteStream(dbPath);
102
97
  await (0, promises_1.pipeline)(nodeStream, zlib_1.default.createGunzip(), fileStream);
103
98
  log('Banco descompactado com sucesso.');
104
99
  }
105
100
  // ─── Ponto de entrada ─────────────────────────────────────────────────────────
106
- async function downloadDatabase() {
101
+ async function downloadDatabase(customPath) {
107
102
  if (process.env.SKIP_POSTINSTALL_DB === 'true') {
108
103
  log('CI/CD detectado (SKIP_POSTINSTALL_DB=true). Pulando download.');
109
104
  return;
110
105
  }
111
- if (!fs_1.default.existsSync(DB_DIR))
112
- fs_1.default.mkdirSync(DB_DIR, { recursive: true });
113
- // Tarefa 1: Descobre release mais recente via API; cai no fallback se necessário
106
+ const dbDir = customPath ? path_1.default.resolve(customPath) : config_1.DB_DIR;
107
+ const dbPath = customPath ? path_1.default.join(dbDir, 'ceps.sqlite') : config_1.DB_PATH;
108
+ const versionFile = path_1.default.join(dbDir, 'version.json');
109
+ // Garante que o diretório existe antes de qualquer operação
110
+ if (!fs_1.default.existsSync(dbDir))
111
+ fs_1.default.mkdirSync(dbDir, { recursive: true });
112
+ log(`Banco será instalado em: ${dbPath}`);
114
113
  const release = await fetchLatestRelease();
115
114
  const tag = release?.tag ?? 'fallback';
116
115
  const downloadUrl = release?.downloadUrl ?? FALLBACK_URL;
117
- // Tarefa 2: Compara tag local com a remota
118
- const cache = readVersionCache();
119
- if (cache && cache.tag === tag && tag !== 'fallback' && hasValidSchema()) {
116
+ const cache = readVersionCache(versionFile);
117
+ if (cache && cache.tag === tag && tag !== 'fallback' && hasValidSchema(dbPath)) {
120
118
  log(`Banco já está na versão mais recente (${tag}). Download ignorado.`);
121
119
  return;
122
120
  }
123
121
  if (cache && cache.tag !== tag && tag !== 'fallback') {
124
122
  log(`Nova versão disponível: ${cache.tag} → ${tag}. Atualizando banco...`);
125
- if (fs_1.default.existsSync(DB_PATH))
126
- fs_1.default.unlinkSync(DB_PATH);
123
+ if (fs_1.default.existsSync(dbPath))
124
+ fs_1.default.unlinkSync(dbPath);
127
125
  }
128
- else if (!hasValidSchema()) {
126
+ else if (!hasValidSchema(dbPath)) {
129
127
  log('Banco ausente ou com schema desatualizado. Iniciando download...');
130
- if (fs_1.default.existsSync(DB_PATH))
131
- fs_1.default.unlinkSync(DB_PATH);
128
+ if (fs_1.default.existsSync(dbPath))
129
+ fs_1.default.unlinkSync(dbPath);
132
130
  }
133
131
  try {
134
- await downloadAndExtract(downloadUrl);
135
- // Grava cache de versão apenas quando obteve tag real da API
132
+ await downloadAndExtract(downloadUrl, dbPath);
136
133
  if (tag !== 'fallback') {
137
- writeVersionCache(tag);
138
- log(`Versão ${tag} salva em ${VERSION_FILE}.`);
134
+ writeVersionCache(versionFile, tag);
135
+ log(`Versão ${tag} salva em ${versionFile}.`);
139
136
  }
140
137
  log('🚀 Banco de dados pronto. Zero-latência ativada.');
141
138
  }
142
139
  catch (err) {
143
- // Se a URL principal falhou e já era o fallback, propaga o erro
144
140
  if (downloadUrl === FALLBACK_URL) {
145
- if (fs_1.default.existsSync(DB_PATH))
146
- fs_1.default.unlinkSync(DB_PATH);
141
+ if (fs_1.default.existsSync(dbPath))
142
+ fs_1.default.unlinkSync(dbPath);
147
143
  throw new Error(`Falha no download (incluindo fallback): ${err.message}`);
148
144
  }
149
- // URL dinâmica falhou → tenta o fallback estático
150
145
  warn(`Download falhou: ${err.message}. Tentando URL de fallback...`);
151
- if (fs_1.default.existsSync(DB_PATH))
152
- fs_1.default.unlinkSync(DB_PATH);
153
- await downloadAndExtract(FALLBACK_URL);
146
+ if (fs_1.default.existsSync(dbPath))
147
+ fs_1.default.unlinkSync(dbPath);
148
+ await downloadAndExtract(FALLBACK_URL, dbPath);
154
149
  log('🚀 Banco de dados instalado via fallback.');
155
150
  }
156
151
  }
157
- // Executa automaticamente apenas quando invocado diretamente (postinstall).
158
- // Quando importado pelo CLI ou pelos testes, não executa.
152
+ // Executa automaticamente quando invocado diretamente (postinstall).
153
+ // Quando importado como módulo (CLI, testes), não executa.
159
154
  if (require.main === module) {
160
155
  downloadDatabase().catch((err) => {
161
156
  console.error(`[brasil-ceps-offline] ❌ Erro crítico no instalador: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brasil-ceps-offline",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "description": "Motor de Inteligência Geográfica Offline para CEPs brasileiros — dados do Censo IBGE 2022 com IBGE, DDD, fuso horário e coordenadas",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",