datera 0.1.0
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/LICENSE +21 -0
- package/README.md +132 -0
- package/dist/api/clean.d.ts +138 -0
- package/dist/api/clean.js +138 -0
- package/dist/api/formats.d.ts +19 -0
- package/dist/api/formats.js +106 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.js +27 -0
- package/dist/api/inspect.d.ts +13 -0
- package/dist/api/inspect.js +44 -0
- package/dist/api/parse.d.ts +2 -0
- package/dist/api/parse.js +19 -0
- package/dist/api/rows.d.ts +23 -0
- package/dist/api/rows.js +63 -0
- package/dist/cli.d.ts +62 -0
- package/dist/cli.js +225 -0
- package/dist/commands.d.ts +3 -0
- package/dist/commands.js +169 -0
- package/dist/config/check.d.ts +11 -0
- package/dist/config/check.js +99 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +16 -0
- package/dist/config/ioSchema.d.ts +97 -0
- package/dist/config/ioSchema.js +111 -0
- package/dist/config/load.d.ts +5 -0
- package/dist/config/load.js +68 -0
- package/dist/config/mergeSchema.d.ts +36 -0
- package/dist/config/mergeSchema.js +46 -0
- package/dist/config/messages.d.ts +7 -0
- package/dist/config/messages.js +33 -0
- package/dist/config/prepareSchema.d.ts +12 -0
- package/dist/config/prepareSchema.js +15 -0
- package/dist/config/schema.d.ts +195 -0
- package/dist/config/schema.js +44 -0
- package/dist/config/validationSchema.d.ts +27 -0
- package/dist/config/validationSchema.js +50 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.js +15 -0
- package/dist/filters/combine.d.ts +10 -0
- package/dist/filters/combine.js +60 -0
- package/dist/filters/combineTypes.d.ts +6 -0
- package/dist/filters/combineTypes.js +2 -0
- package/dist/filters/dedupe.d.ts +12 -0
- package/dist/filters/dedupe.js +45 -0
- package/dist/filters/fillEmpty.d.ts +9 -0
- package/dist/filters/fillEmpty.js +32 -0
- package/dist/filters/fillEmptyTypes.d.ts +5 -0
- package/dist/filters/fillEmptyTypes.js +2 -0
- package/dist/filters/merge.d.ts +15 -0
- package/dist/filters/merge.js +113 -0
- package/dist/filters/mergeStrategies.d.ts +10 -0
- package/dist/filters/mergeStrategies.js +69 -0
- package/dist/filters/mergeTypes.d.ts +32 -0
- package/dist/filters/mergeTypes.js +2 -0
- package/dist/filters/mergeUnkeyed.d.ts +3 -0
- package/dist/filters/mergeUnkeyed.js +21 -0
- package/dist/filters/types.d.ts +3 -0
- package/dist/filters/types.js +2 -0
- package/dist/filters/validate.d.ts +16 -0
- package/dist/filters/validate.js +79 -0
- package/dist/filters/validateTypes.d.ts +28 -0
- package/dist/filters/validateTypes.js +2 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +40 -0
- package/dist/io/csv/csvFormat.d.ts +4 -0
- package/dist/io/csv/csvFormat.js +94 -0
- package/dist/io/csv/csvSink.d.ts +14 -0
- package/dist/io/csv/csvSink.js +32 -0
- package/dist/io/csv/csvSource.d.ts +13 -0
- package/dist/io/csv/csvSource.js +29 -0
- package/dist/io/custom/loader.d.ts +1 -0
- package/dist/io/custom/loader.js +36 -0
- package/dist/io/custom/registry.d.ts +13 -0
- package/dist/io/custom/registry.js +50 -0
- package/dist/io/excel/excelCell.d.ts +4 -0
- package/dist/io/excel/excelCell.js +29 -0
- package/dist/io/excel/excelSink.d.ts +14 -0
- package/dist/io/excel/excelSink.js +57 -0
- package/dist/io/excel/excelSource.d.ts +11 -0
- package/dist/io/excel/excelSource.js +47 -0
- package/dist/io/excel/workbook.d.ts +4 -0
- package/dist/io/excel/workbook.js +20 -0
- package/dist/io/factory.d.ts +5 -0
- package/dist/io/factory.js +106 -0
- package/dist/io/files.d.ts +7 -0
- package/dist/io/files.js +48 -0
- package/dist/io/header.d.ts +2 -0
- package/dist/io/header.js +42 -0
- package/dist/io/json/jsonSink.d.ts +12 -0
- package/dist/io/json/jsonSink.js +19 -0
- package/dist/io/json/jsonSource.d.ts +11 -0
- package/dist/io/json/jsonSource.js +66 -0
- package/dist/io/mysql/client.d.ts +13 -0
- package/dist/io/mysql/client.js +73 -0
- package/dist/io/mysql/mysqlSource.d.ts +13 -0
- package/dist/io/mysql/mysqlSource.js +25 -0
- package/dist/io/optional.d.ts +2 -0
- package/dist/io/optional.js +36 -0
- package/dist/io/parquet/parquetLibrary.d.ts +11 -0
- package/dist/io/parquet/parquetLibrary.js +20 -0
- package/dist/io/parquet/parquetSink.d.ts +15 -0
- package/dist/io/parquet/parquetSink.js +58 -0
- package/dist/io/parquet/parquetSource.d.ts +13 -0
- package/dist/io/parquet/parquetSource.js +68 -0
- package/dist/io/postgres/postgresSource.d.ts +26 -0
- package/dist/io/postgres/postgresSource.js +42 -0
- package/dist/io/safety.d.ts +4 -0
- package/dist/io/safety.js +83 -0
- package/dist/io/sheets/client.d.ts +10 -0
- package/dist/io/sheets/client.js +141 -0
- package/dist/io/sheets/sheetsSink.d.ts +12 -0
- package/dist/io/sheets/sheetsSink.js +24 -0
- package/dist/io/sheets/sheetsSource.d.ts +10 -0
- package/dist/io/sheets/sheetsSource.js +47 -0
- package/dist/io/sql/cell.d.ts +5 -0
- package/dist/io/sql/cell.js +38 -0
- package/dist/io/sql/driver.d.ts +1 -0
- package/dist/io/sql/driver.js +15 -0
- package/dist/io/sql/names.d.ts +1 -0
- package/dist/io/sql/names.js +12 -0
- package/dist/io/sqlite/sqliteSource.d.ts +11 -0
- package/dist/io/sqlite/sqliteSource.js +52 -0
- package/dist/io/sqlserver/sqlServerSource.d.ts +24 -0
- package/dist/io/sqlserver/sqlServerSource.js +49 -0
- package/dist/io/types.d.ts +11 -0
- package/dist/io/types.js +2 -0
- package/dist/io/xml/xmlParse.d.ts +9 -0
- package/dist/io/xml/xmlParse.js +178 -0
- package/dist/io/xml/xmlSink.d.ts +18 -0
- package/dist/io/xml/xmlSink.js +79 -0
- package/dist/io/xml/xmlSource.d.ts +11 -0
- package/dist/io/xml/xmlSource.js +93 -0
- package/dist/logger.d.ts +11 -0
- package/dist/logger.js +29 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +18 -0
- package/dist/normalizers/builtin.d.ts +5 -0
- package/dist/normalizers/builtin.js +23 -0
- package/dist/normalizers/index.d.ts +2 -0
- package/dist/normalizers/index.js +14 -0
- package/dist/normalizers/loader.d.ts +1 -0
- package/dist/normalizers/loader.js +30 -0
- package/dist/normalizers/registry.d.ts +9 -0
- package/dist/normalizers/registry.js +29 -0
- package/dist/pipeline/index.d.ts +7 -0
- package/dist/pipeline/index.js +17 -0
- package/dist/pipeline/modes.d.ts +6 -0
- package/dist/pipeline/modes.js +48 -0
- package/dist/pipeline/report.d.ts +3 -0
- package/dist/pipeline/report.js +32 -0
- package/dist/pipeline/runEtl.d.ts +24 -0
- package/dist/pipeline/runEtl.js +98 -0
- package/dist/pipeline/steps.d.ts +4 -0
- package/dist/pipeline/steps.js +36 -0
- package/dist/pipeline/types.d.ts +34 -0
- package/dist/pipeline/types.js +2 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +2 -0
- package/package.json +128 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ParquetSource = void 0;
|
|
7
|
+
exports.parquetRow = parquetRow;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const cell_1 = require("../sql/cell");
|
|
10
|
+
const parquetLibrary_1 = require("./parquetLibrary");
|
|
11
|
+
function join(prefix, name) {
|
|
12
|
+
return prefix ? `${prefix}.${name}` : name;
|
|
13
|
+
}
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return (typeof value === "object" &&
|
|
16
|
+
value !== null &&
|
|
17
|
+
!Array.isArray(value) &&
|
|
18
|
+
!(value instanceof Date) &&
|
|
19
|
+
!(value instanceof Uint8Array));
|
|
20
|
+
}
|
|
21
|
+
function isSimple(value) {
|
|
22
|
+
return value === null || (typeof value !== "object" && value !== undefined);
|
|
23
|
+
}
|
|
24
|
+
function flatten(value, prefix, row) {
|
|
25
|
+
if (isPlainObject(value)) {
|
|
26
|
+
for (const [name, inner] of Object.entries(value)) {
|
|
27
|
+
flatten(inner, join(prefix, name), row);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (Array.isArray(value) && value.every(isSimple)) {
|
|
31
|
+
row[prefix] = value
|
|
32
|
+
.map((item) => String((0, cell_1.toCell)(item) ?? ""))
|
|
33
|
+
.join(", ");
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
row[prefix] = (0, cell_1.toCell)(value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function parquetRow(record) {
|
|
40
|
+
const row = {};
|
|
41
|
+
for (const [name, value] of Object.entries(record)) {
|
|
42
|
+
flatten(value, name, row);
|
|
43
|
+
}
|
|
44
|
+
return row;
|
|
45
|
+
}
|
|
46
|
+
class ParquetSource {
|
|
47
|
+
options;
|
|
48
|
+
library;
|
|
49
|
+
constructor(options, library = parquetLibrary_1.parquetLibrary) {
|
|
50
|
+
this.options = options;
|
|
51
|
+
this.library = library;
|
|
52
|
+
}
|
|
53
|
+
async read() {
|
|
54
|
+
if (!fs_1.default.existsSync(this.options.path)) {
|
|
55
|
+
throw new Error(`Arquivo não encontrado: ${this.options.path}`);
|
|
56
|
+
}
|
|
57
|
+
let records;
|
|
58
|
+
try {
|
|
59
|
+
records = await this.library.read(this.options.path);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
63
|
+
throw new Error(`Não consegui ler o Parquet em ${this.options.path}: ${reason}`);
|
|
64
|
+
}
|
|
65
|
+
return records.map(parquetRow);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.ParquetSource = ParquetSource;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TableRow } from "../../types";
|
|
2
|
+
import { Source } from "../types";
|
|
3
|
+
export interface PostgresConnection {
|
|
4
|
+
host: string;
|
|
5
|
+
port: number;
|
|
6
|
+
user: string;
|
|
7
|
+
password?: string | undefined;
|
|
8
|
+
database: string;
|
|
9
|
+
ssl?: boolean | undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface PostgresClient {
|
|
12
|
+
connect(): Promise<unknown>;
|
|
13
|
+
query(text: string): Promise<{
|
|
14
|
+
rows: Record<string, unknown>[];
|
|
15
|
+
}>;
|
|
16
|
+
end(): Promise<unknown>;
|
|
17
|
+
}
|
|
18
|
+
export type PostgresClientFactory = (connection: PostgresConnection) => PostgresClient;
|
|
19
|
+
export declare const createPostgresClient: PostgresClientFactory;
|
|
20
|
+
export declare class PostgresSource implements Source {
|
|
21
|
+
private readonly connection;
|
|
22
|
+
private readonly table;
|
|
23
|
+
private readonly createClient;
|
|
24
|
+
constructor(connection: PostgresConnection, table: string, createClient?: PostgresClientFactory);
|
|
25
|
+
read(): Promise<TableRow[]>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresSource = exports.createPostgresClient = void 0;
|
|
4
|
+
const optional_1 = require("../optional");
|
|
5
|
+
const names_1 = require("../sql/names");
|
|
6
|
+
const cell_1 = require("../sql/cell");
|
|
7
|
+
const createPostgresClient = (connection) => {
|
|
8
|
+
const pg = (0, optional_1.requireOptional)(() => require("pg"), ["pg"], "PostgreSQL");
|
|
9
|
+
return new pg.Client({
|
|
10
|
+
host: connection.host,
|
|
11
|
+
port: connection.port,
|
|
12
|
+
user: connection.user,
|
|
13
|
+
...(connection.password !== undefined
|
|
14
|
+
? { password: connection.password }
|
|
15
|
+
: {}),
|
|
16
|
+
database: connection.database,
|
|
17
|
+
...(connection.ssl ? { ssl: true } : {}),
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
exports.createPostgresClient = createPostgresClient;
|
|
21
|
+
class PostgresSource {
|
|
22
|
+
connection;
|
|
23
|
+
table;
|
|
24
|
+
createClient;
|
|
25
|
+
constructor(connection, table, createClient = exports.createPostgresClient) {
|
|
26
|
+
this.connection = connection;
|
|
27
|
+
this.table = table;
|
|
28
|
+
this.createClient = createClient;
|
|
29
|
+
}
|
|
30
|
+
async read() {
|
|
31
|
+
const client = this.createClient(this.connection);
|
|
32
|
+
await client.connect();
|
|
33
|
+
try {
|
|
34
|
+
const result = await client.query(`SELECT * FROM ${(0, names_1.quoteTable)(this.table, '"')}`);
|
|
35
|
+
return result.rows.map(cell_1.toRow);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await client.end();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.PostgresSource = PostgresSource;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isSameFile = isSameFile;
|
|
7
|
+
exports.assertDifferentFiles = assertDifferentFiles;
|
|
8
|
+
exports.assertSourceIsSafe = assertSourceIsSafe;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const validate_1 = require("../filters/validate");
|
|
12
|
+
function sameName(a, b) {
|
|
13
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
function filePath(side) {
|
|
16
|
+
return "path" in side && typeof side.path === "string"
|
|
17
|
+
? side.path
|
|
18
|
+
: undefined;
|
|
19
|
+
}
|
|
20
|
+
function assertSheetsAreSafe(config) {
|
|
21
|
+
const source = config.source;
|
|
22
|
+
const destination = config.destination ?? { type: "sheets" };
|
|
23
|
+
if (source?.type !== "sheets" || destination.type !== "sheets")
|
|
24
|
+
return;
|
|
25
|
+
const destinationId = destination.spreadsheetId ?? config.spreadsheetId;
|
|
26
|
+
if (source.spreadsheetId !== destinationId)
|
|
27
|
+
return;
|
|
28
|
+
if (source.sheet === undefined) {
|
|
29
|
+
throw new Error('A fonte e o destino são a mesma planilha. Coloque "sheet" na fonte com o nome da aba dos dados originais, senão ele pode ler e apagar a mesma aba.');
|
|
30
|
+
}
|
|
31
|
+
if (destination.sheet === undefined) {
|
|
32
|
+
throw new Error('A fonte e o destino são a mesma planilha. Coloque "sheet" no destino com o nome da aba do resultado, senão ele grava na primeira aba, que pode ser a dos dados originais.');
|
|
33
|
+
}
|
|
34
|
+
const pendingSheet = config.validation
|
|
35
|
+
? (config.validation.pendingSheet ?? validate_1.DEFAULT_PENDING_SHEET)
|
|
36
|
+
: undefined;
|
|
37
|
+
for (const target of [destination.sheet, pendingSheet]) {
|
|
38
|
+
if (target !== undefined && sameName(target, source.sheet)) {
|
|
39
|
+
throw new Error(`A aba "${source.sheet}" é de onde o Datera lê, então ele não pode gravar nela. Escolha outro nome pra aba do resultado ou das pendências.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function comparablePath(file) {
|
|
44
|
+
const resolved = path_1.default.resolve(file);
|
|
45
|
+
let real = resolved;
|
|
46
|
+
try {
|
|
47
|
+
real = fs_1.default.realpathSync.native(resolved);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
try {
|
|
51
|
+
real = path_1.default.join(fs_1.default.realpathSync.native(path_1.default.dirname(resolved)), path_1.default.basename(resolved));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
real = resolved;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return process.platform === "win32" || process.platform === "darwin"
|
|
58
|
+
? real.toLowerCase()
|
|
59
|
+
: real;
|
|
60
|
+
}
|
|
61
|
+
function isSameFile(a, b) {
|
|
62
|
+
return comparablePath(a) === comparablePath(b);
|
|
63
|
+
}
|
|
64
|
+
function assertDifferentFiles(input, output) {
|
|
65
|
+
if (isSameFile(input, output)) {
|
|
66
|
+
throw new Error(`A entrada e a saída são o mesmo arquivo (${input}). A saída é apagada antes de gravar, então escolha outro arquivo pra ela.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function assertFilesAreSafe(config) {
|
|
70
|
+
const sourcePath = config.source ? filePath(config.source) : undefined;
|
|
71
|
+
const destinationPath = config.destination
|
|
72
|
+
? filePath(config.destination)
|
|
73
|
+
: undefined;
|
|
74
|
+
if (sourcePath === undefined || destinationPath === undefined)
|
|
75
|
+
return;
|
|
76
|
+
if (isSameFile(sourcePath, destinationPath)) {
|
|
77
|
+
throw new Error(`A fonte e o destino são o mesmo arquivo (${sourcePath}). O destino é apagado antes de gravar, então escolha outro arquivo pra saída.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function assertSourceIsSafe(config) {
|
|
81
|
+
assertSheetsAreSafe(config);
|
|
82
|
+
assertFilesAreSafe(config);
|
|
83
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { sheets_v4 } from "@googleapis/sheets";
|
|
2
|
+
import { TableRow } from "../../types";
|
|
3
|
+
import { Logger } from "../../logger";
|
|
4
|
+
export declare function createSheetsClient(credentialsPath: string): sheets_v4.Sheets;
|
|
5
|
+
export interface WriteOptions {
|
|
6
|
+
sheetName?: string | undefined;
|
|
7
|
+
logger?: Logger | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare function readSheet(sheets: sheets_v4.Sheets, spreadsheetId: string, sheetName?: string): Promise<unknown[][]>;
|
|
10
|
+
export declare function writeData(sheets: sheets_v4.Sheets, spreadsheetId: string, data: TableRow[], options?: WriteOptions): Promise<void>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSheetsClient = createSheetsClient;
|
|
4
|
+
exports.readSheet = readSheet;
|
|
5
|
+
exports.writeData = writeData;
|
|
6
|
+
const optional_1 = require("../optional");
|
|
7
|
+
const header_1 = require("../header");
|
|
8
|
+
const logger_1 = require("../../logger");
|
|
9
|
+
function createSheetsClient(credentialsPath) {
|
|
10
|
+
const { auth, sheets: sheetsApi } = (0, optional_1.requireOptional)(() => require("@googleapis/sheets"), ["@googleapis/sheets"], "Google Sheets");
|
|
11
|
+
const credentials = new auth.GoogleAuth({
|
|
12
|
+
keyFile: credentialsPath,
|
|
13
|
+
scopes: ["https://www.googleapis.com/auth/spreadsheets"],
|
|
14
|
+
});
|
|
15
|
+
return sheetsApi({ version: "v4", auth: credentials });
|
|
16
|
+
}
|
|
17
|
+
function quoteSheetTitle(title) {
|
|
18
|
+
return `'${title.replace(/'/g, "''")}'`;
|
|
19
|
+
}
|
|
20
|
+
async function listSheets(sheets, spreadsheetId) {
|
|
21
|
+
const response = await sheets.spreadsheets.get({
|
|
22
|
+
spreadsheetId,
|
|
23
|
+
fields: "sheets.properties(sheetId,title)",
|
|
24
|
+
});
|
|
25
|
+
const found = [];
|
|
26
|
+
for (const sheet of response.data.sheets ?? []) {
|
|
27
|
+
const { title, sheetId } = sheet.properties ?? {};
|
|
28
|
+
if (title && sheetId !== null && sheetId !== undefined) {
|
|
29
|
+
found.push({ title, sheetId });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
async function resolveSheet(sheets, spreadsheetId, sheetName, logger = logger_1.consoleLogger) {
|
|
35
|
+
const existing = await listSheets(sheets, spreadsheetId);
|
|
36
|
+
const found = sheetName === undefined
|
|
37
|
+
? existing[0]
|
|
38
|
+
: existing.find((sheet) => sheet.title === sheetName);
|
|
39
|
+
if (found)
|
|
40
|
+
return found;
|
|
41
|
+
if (sheetName === undefined) {
|
|
42
|
+
throw new Error("A planilha não tem nenhuma aba pra escrever.");
|
|
43
|
+
}
|
|
44
|
+
const created = await sheets.spreadsheets.batchUpdate({
|
|
45
|
+
spreadsheetId,
|
|
46
|
+
requestBody: {
|
|
47
|
+
requests: [{ addSheet: { properties: { title: sheetName } } }],
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const sheetId = created.data.replies?.[0]?.addSheet?.properties?.sheetId;
|
|
51
|
+
if (sheetId === null || sheetId === undefined) {
|
|
52
|
+
throw new Error(`Não consegui criar a aba "${sheetName}".`);
|
|
53
|
+
}
|
|
54
|
+
logger.info(`Aba "${sheetName}" criada.`);
|
|
55
|
+
return { title: sheetName, sheetId };
|
|
56
|
+
}
|
|
57
|
+
async function readSheet(sheets, spreadsheetId, sheetName) {
|
|
58
|
+
const existing = await listSheets(sheets, spreadsheetId);
|
|
59
|
+
const target = sheetName === undefined
|
|
60
|
+
? existing[0]
|
|
61
|
+
: existing.find((sheet) => sheet.title === sheetName);
|
|
62
|
+
if (!target) {
|
|
63
|
+
const names = existing.map((sheet) => sheet.title).join(", ");
|
|
64
|
+
throw new Error(sheetName === undefined
|
|
65
|
+
? "A planilha não tem nenhuma aba pra ler."
|
|
66
|
+
: `Aba "${sheetName}" não encontrada na planilha. Abas: ${names}`);
|
|
67
|
+
}
|
|
68
|
+
const response = await sheets.spreadsheets.values.get({
|
|
69
|
+
spreadsheetId,
|
|
70
|
+
range: quoteSheetTitle(target.title),
|
|
71
|
+
valueRenderOption: "UNFORMATTED_VALUE",
|
|
72
|
+
dateTimeRenderOption: "FORMATTED_STRING",
|
|
73
|
+
});
|
|
74
|
+
return response.data.values ?? [];
|
|
75
|
+
}
|
|
76
|
+
async function formatSheet(sheets, spreadsheetId, sheetId, columnCount) {
|
|
77
|
+
await sheets.spreadsheets.batchUpdate({
|
|
78
|
+
spreadsheetId,
|
|
79
|
+
requestBody: {
|
|
80
|
+
requests: [
|
|
81
|
+
{
|
|
82
|
+
updateSheetProperties: {
|
|
83
|
+
properties: {
|
|
84
|
+
sheetId,
|
|
85
|
+
gridProperties: { frozenRowCount: 1 },
|
|
86
|
+
},
|
|
87
|
+
fields: "gridProperties.frozenRowCount",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
repeatCell: {
|
|
92
|
+
range: { sheetId, startRowIndex: 0, endRowIndex: 1 },
|
|
93
|
+
cell: {
|
|
94
|
+
userEnteredFormat: {
|
|
95
|
+
textFormat: { bold: true },
|
|
96
|
+
backgroundColor: {
|
|
97
|
+
red: 0.9,
|
|
98
|
+
green: 0.9,
|
|
99
|
+
blue: 0.9,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
fields: "userEnteredFormat(textFormat,backgroundColor)",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
autoResizeDimensions: {
|
|
108
|
+
dimensions: {
|
|
109
|
+
sheetId,
|
|
110
|
+
dimension: "COLUMNS",
|
|
111
|
+
startIndex: 0,
|
|
112
|
+
endIndex: columnCount,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function writeData(sheets, spreadsheetId, data, options = {}) {
|
|
121
|
+
const logger = options.logger ?? logger_1.consoleLogger;
|
|
122
|
+
const target = await resolveSheet(sheets, spreadsheetId, options.sheetName, logger);
|
|
123
|
+
const range = quoteSheetTitle(target.title);
|
|
124
|
+
await sheets.spreadsheets.values.clear({ spreadsheetId, range });
|
|
125
|
+
if (data.length === 0) {
|
|
126
|
+
logger.info(`Nenhum dado pra escrever na aba "${target.title}".`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const header = (0, header_1.buildHeader)(data);
|
|
130
|
+
const rows = data.map((row) => header.map((column) => row[column] ?? ""));
|
|
131
|
+
await sheets.spreadsheets.values.update({
|
|
132
|
+
spreadsheetId,
|
|
133
|
+
range: `${range}!A1`,
|
|
134
|
+
valueInputOption: "RAW",
|
|
135
|
+
requestBody: {
|
|
136
|
+
values: [header, ...rows],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
await formatSheet(sheets, spreadsheetId, target.sheetId, header.length);
|
|
140
|
+
logger.info(`${data.length} linhas escritas na aba "${target.title}".`);
|
|
141
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { sheets_v4 } from "@googleapis/sheets";
|
|
2
|
+
import { TableRow } from "../../types";
|
|
3
|
+
import { Sink, SinkWriteOptions } from "../types";
|
|
4
|
+
import { Logger } from "../../logger";
|
|
5
|
+
export declare class SheetsSink implements Sink {
|
|
6
|
+
private readonly sheets;
|
|
7
|
+
private readonly spreadsheetId;
|
|
8
|
+
private readonly sheet?;
|
|
9
|
+
private readonly logger;
|
|
10
|
+
constructor(sheets: sheets_v4.Sheets, spreadsheetId: string, sheet?: string | undefined, logger?: Logger);
|
|
11
|
+
write(rows: TableRow[], options?: SinkWriteOptions): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SheetsSink = void 0;
|
|
4
|
+
const client_1 = require("./client");
|
|
5
|
+
const logger_1 = require("../../logger");
|
|
6
|
+
class SheetsSink {
|
|
7
|
+
sheets;
|
|
8
|
+
spreadsheetId;
|
|
9
|
+
sheet;
|
|
10
|
+
logger;
|
|
11
|
+
constructor(sheets, spreadsheetId, sheet, logger = logger_1.consoleLogger) {
|
|
12
|
+
this.sheets = sheets;
|
|
13
|
+
this.spreadsheetId = spreadsheetId;
|
|
14
|
+
this.sheet = sheet;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
}
|
|
17
|
+
async write(rows, options = {}) {
|
|
18
|
+
await (0, client_1.writeData)(this.sheets, this.spreadsheetId, rows, {
|
|
19
|
+
sheetName: options.name ?? this.sheet,
|
|
20
|
+
logger: this.logger,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.SheetsSink = SheetsSink;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { sheets_v4 } from "@googleapis/sheets";
|
|
2
|
+
import { TableRow } from "../../types";
|
|
3
|
+
import { Source } from "../types";
|
|
4
|
+
export declare class SheetsSource implements Source {
|
|
5
|
+
private readonly sheets;
|
|
6
|
+
private readonly spreadsheetId;
|
|
7
|
+
private readonly sheet?;
|
|
8
|
+
constructor(sheets: sheets_v4.Sheets, spreadsheetId: string, sheet?: string | undefined);
|
|
9
|
+
read(): Promise<TableRow[]>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SheetsSource = void 0;
|
|
4
|
+
const client_1 = require("./client");
|
|
5
|
+
function fromSheetCell(value) {
|
|
6
|
+
if (value === null || value === undefined || value === "")
|
|
7
|
+
return null;
|
|
8
|
+
if (typeof value === "number" || typeof value === "string")
|
|
9
|
+
return value;
|
|
10
|
+
return String(value);
|
|
11
|
+
}
|
|
12
|
+
class SheetsSource {
|
|
13
|
+
sheets;
|
|
14
|
+
spreadsheetId;
|
|
15
|
+
sheet;
|
|
16
|
+
constructor(sheets, spreadsheetId, sheet) {
|
|
17
|
+
this.sheets = sheets;
|
|
18
|
+
this.spreadsheetId = spreadsheetId;
|
|
19
|
+
this.sheet = sheet;
|
|
20
|
+
}
|
|
21
|
+
async read() {
|
|
22
|
+
const [header, ...lines] = await (0, client_1.readSheet)(this.sheets, this.spreadsheetId, this.sheet);
|
|
23
|
+
if (!header)
|
|
24
|
+
return [];
|
|
25
|
+
const columns = header.map((cell) => {
|
|
26
|
+
const name = fromSheetCell(cell);
|
|
27
|
+
return name === null ? null : String(name).trim();
|
|
28
|
+
});
|
|
29
|
+
const rows = [];
|
|
30
|
+
for (const values of lines) {
|
|
31
|
+
const row = {};
|
|
32
|
+
let hasValue = false;
|
|
33
|
+
columns.forEach((column, index) => {
|
|
34
|
+
if (column === null)
|
|
35
|
+
return;
|
|
36
|
+
const value = fromSheetCell(values[index]);
|
|
37
|
+
row[column] = value;
|
|
38
|
+
if (value !== null)
|
|
39
|
+
hasValue = true;
|
|
40
|
+
});
|
|
41
|
+
if (hasValue)
|
|
42
|
+
rows.push(row);
|
|
43
|
+
}
|
|
44
|
+
return rows;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.SheetsSource = SheetsSource;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toCell = toCell;
|
|
4
|
+
exports.toRow = toRow;
|
|
5
|
+
function isoDate(date) {
|
|
6
|
+
if (Number.isNaN(date.getTime()))
|
|
7
|
+
return "";
|
|
8
|
+
const iso = date.toISOString();
|
|
9
|
+
return iso.endsWith("T00:00:00.000Z") ? iso.slice(0, 10) : iso;
|
|
10
|
+
}
|
|
11
|
+
function toCell(value) {
|
|
12
|
+
if (value === null || value === undefined)
|
|
13
|
+
return null;
|
|
14
|
+
if (typeof value === "number")
|
|
15
|
+
return Number.isFinite(value) ? value : null;
|
|
16
|
+
if (typeof value === "string")
|
|
17
|
+
return value === "" ? null : value;
|
|
18
|
+
if (typeof value === "bigint") {
|
|
19
|
+
return Number.isSafeInteger(Number(value))
|
|
20
|
+
? Number(value)
|
|
21
|
+
: value.toString();
|
|
22
|
+
}
|
|
23
|
+
if (typeof value === "boolean")
|
|
24
|
+
return String(value);
|
|
25
|
+
if (value instanceof Date)
|
|
26
|
+
return isoDate(value) || null;
|
|
27
|
+
if (value instanceof Uint8Array) {
|
|
28
|
+
return Buffer.from(value).toString("base64");
|
|
29
|
+
}
|
|
30
|
+
return JSON.stringify(value, (_key, inner) => typeof inner === "bigint" ? inner.toString() : inner);
|
|
31
|
+
}
|
|
32
|
+
function toRow(record) {
|
|
33
|
+
const row = {};
|
|
34
|
+
for (const [column, value] of Object.entries(record)) {
|
|
35
|
+
row[column] = toCell(value);
|
|
36
|
+
}
|
|
37
|
+
return row;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loadDriver<T>(load: () => unknown, packageName: string, label: string): T;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadDriver = loadDriver;
|
|
4
|
+
function loadDriver(load, packageName, label) {
|
|
5
|
+
try {
|
|
6
|
+
return load();
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
const code = error?.code;
|
|
10
|
+
if (code === "MODULE_NOT_FOUND") {
|
|
11
|
+
throw new Error(`Pra ler de ${label}, o pacote "${packageName}" precisa estar instalado. Rode: npm install ${packageName}`);
|
|
12
|
+
}
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function quoteTable(table: string, open: string, close?: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.quoteTable = quoteTable;
|
|
4
|
+
function quoteTable(table, open, close = open) {
|
|
5
|
+
const parts = table.split(".").map((part) => part.trim());
|
|
6
|
+
if (parts.some((part) => part === "")) {
|
|
7
|
+
throw new Error(`Nome de tabela inválido: "${table}".`);
|
|
8
|
+
}
|
|
9
|
+
return parts
|
|
10
|
+
.map((part) => `${open}${part.split(close).join(close + close)}${close}`)
|
|
11
|
+
.join(".");
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TableRow } from "../../types";
|
|
2
|
+
import { Source } from "../types";
|
|
3
|
+
export interface SqliteSourceOptions {
|
|
4
|
+
path: string;
|
|
5
|
+
table: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class SqliteSource implements Source {
|
|
8
|
+
private readonly options;
|
|
9
|
+
constructor(options: SqliteSourceOptions);
|
|
10
|
+
read(): Promise<TableRow[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SqliteSource = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const names_1 = require("../sql/names");
|
|
9
|
+
const cell_1 = require("../sql/cell");
|
|
10
|
+
function loadSqlite() {
|
|
11
|
+
try {
|
|
12
|
+
return require("node:sqlite");
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error("Essa versão do Node não sabe ler SQLite. Atualize pro Node 22.13 ou mais novo.");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
class SqliteSource {
|
|
19
|
+
options;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
async read() {
|
|
24
|
+
if (!fs_1.default.existsSync(this.options.path)) {
|
|
25
|
+
throw new Error(`Arquivo não encontrado: ${this.options.path}`);
|
|
26
|
+
}
|
|
27
|
+
const { DatabaseSync } = loadSqlite();
|
|
28
|
+
const database = new DatabaseSync(this.options.path, {
|
|
29
|
+
readOnly: true,
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
const tables = database
|
|
33
|
+
.prepare("SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name")
|
|
34
|
+
.all()
|
|
35
|
+
.map((row) => String(row.name));
|
|
36
|
+
if (!tables.includes(this.options.table)) {
|
|
37
|
+
throw new Error(`A tabela "${this.options.table}" não existe em ${this.options.path}.` +
|
|
38
|
+
(tables.length > 0
|
|
39
|
+
? ` As que existem são: ${tables.join(", ")}.`
|
|
40
|
+
: " O arquivo não tem nenhuma tabela."));
|
|
41
|
+
}
|
|
42
|
+
return database
|
|
43
|
+
.prepare(`SELECT * FROM ${(0, names_1.quoteTable)(this.options.table, '"')}`)
|
|
44
|
+
.all()
|
|
45
|
+
.map((record) => (0, cell_1.toRow)(record));
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
database.close();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.SqliteSource = SqliteSource;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TableRow } from "../../types";
|
|
2
|
+
import { Source } from "../types";
|
|
3
|
+
export interface SqlServerConnection {
|
|
4
|
+
host: string;
|
|
5
|
+
port: number;
|
|
6
|
+
user: string;
|
|
7
|
+
password?: string | undefined;
|
|
8
|
+
database: string;
|
|
9
|
+
encrypt?: boolean | undefined;
|
|
10
|
+
trustServerCertificate?: boolean | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface SqlServerClient {
|
|
13
|
+
query(text: string): Promise<Record<string, unknown>[]>;
|
|
14
|
+
close(): Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
export type SqlServerClientFactory = (connection: SqlServerConnection) => Promise<SqlServerClient>;
|
|
17
|
+
export declare const createSqlServerClient: SqlServerClientFactory;
|
|
18
|
+
export declare class SqlServerSource implements Source {
|
|
19
|
+
private readonly connection;
|
|
20
|
+
private readonly table;
|
|
21
|
+
private readonly createClient;
|
|
22
|
+
constructor(connection: SqlServerConnection, table: string, createClient?: SqlServerClientFactory);
|
|
23
|
+
read(): Promise<TableRow[]>;
|
|
24
|
+
}
|