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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/api/clean.d.ts +138 -0
  4. package/dist/api/clean.js +138 -0
  5. package/dist/api/formats.d.ts +19 -0
  6. package/dist/api/formats.js +106 -0
  7. package/dist/api/index.d.ts +8 -0
  8. package/dist/api/index.js +27 -0
  9. package/dist/api/inspect.d.ts +13 -0
  10. package/dist/api/inspect.js +44 -0
  11. package/dist/api/parse.d.ts +2 -0
  12. package/dist/api/parse.js +19 -0
  13. package/dist/api/rows.d.ts +23 -0
  14. package/dist/api/rows.js +63 -0
  15. package/dist/cli.d.ts +62 -0
  16. package/dist/cli.js +225 -0
  17. package/dist/commands.d.ts +3 -0
  18. package/dist/commands.js +169 -0
  19. package/dist/config/check.d.ts +11 -0
  20. package/dist/config/check.js +99 -0
  21. package/dist/config/index.d.ts +7 -0
  22. package/dist/config/index.js +16 -0
  23. package/dist/config/ioSchema.d.ts +97 -0
  24. package/dist/config/ioSchema.js +111 -0
  25. package/dist/config/load.d.ts +5 -0
  26. package/dist/config/load.js +68 -0
  27. package/dist/config/mergeSchema.d.ts +36 -0
  28. package/dist/config/mergeSchema.js +46 -0
  29. package/dist/config/messages.d.ts +7 -0
  30. package/dist/config/messages.js +33 -0
  31. package/dist/config/prepareSchema.d.ts +12 -0
  32. package/dist/config/prepareSchema.js +15 -0
  33. package/dist/config/schema.d.ts +195 -0
  34. package/dist/config/schema.js +44 -0
  35. package/dist/config/validationSchema.d.ts +27 -0
  36. package/dist/config/validationSchema.js +50 -0
  37. package/dist/env.d.ts +2 -0
  38. package/dist/env.js +15 -0
  39. package/dist/filters/combine.d.ts +10 -0
  40. package/dist/filters/combine.js +60 -0
  41. package/dist/filters/combineTypes.d.ts +6 -0
  42. package/dist/filters/combineTypes.js +2 -0
  43. package/dist/filters/dedupe.d.ts +12 -0
  44. package/dist/filters/dedupe.js +45 -0
  45. package/dist/filters/fillEmpty.d.ts +9 -0
  46. package/dist/filters/fillEmpty.js +32 -0
  47. package/dist/filters/fillEmptyTypes.d.ts +5 -0
  48. package/dist/filters/fillEmptyTypes.js +2 -0
  49. package/dist/filters/merge.d.ts +15 -0
  50. package/dist/filters/merge.js +113 -0
  51. package/dist/filters/mergeStrategies.d.ts +10 -0
  52. package/dist/filters/mergeStrategies.js +69 -0
  53. package/dist/filters/mergeTypes.d.ts +32 -0
  54. package/dist/filters/mergeTypes.js +2 -0
  55. package/dist/filters/mergeUnkeyed.d.ts +3 -0
  56. package/dist/filters/mergeUnkeyed.js +21 -0
  57. package/dist/filters/types.d.ts +3 -0
  58. package/dist/filters/types.js +2 -0
  59. package/dist/filters/validate.d.ts +16 -0
  60. package/dist/filters/validate.js +79 -0
  61. package/dist/filters/validateTypes.d.ts +28 -0
  62. package/dist/filters/validateTypes.js +2 -0
  63. package/dist/index.d.ts +13 -0
  64. package/dist/index.js +40 -0
  65. package/dist/io/csv/csvFormat.d.ts +4 -0
  66. package/dist/io/csv/csvFormat.js +94 -0
  67. package/dist/io/csv/csvSink.d.ts +14 -0
  68. package/dist/io/csv/csvSink.js +32 -0
  69. package/dist/io/csv/csvSource.d.ts +13 -0
  70. package/dist/io/csv/csvSource.js +29 -0
  71. package/dist/io/custom/loader.d.ts +1 -0
  72. package/dist/io/custom/loader.js +36 -0
  73. package/dist/io/custom/registry.d.ts +13 -0
  74. package/dist/io/custom/registry.js +50 -0
  75. package/dist/io/excel/excelCell.d.ts +4 -0
  76. package/dist/io/excel/excelCell.js +29 -0
  77. package/dist/io/excel/excelSink.d.ts +14 -0
  78. package/dist/io/excel/excelSink.js +57 -0
  79. package/dist/io/excel/excelSource.d.ts +11 -0
  80. package/dist/io/excel/excelSource.js +47 -0
  81. package/dist/io/excel/workbook.d.ts +4 -0
  82. package/dist/io/excel/workbook.js +20 -0
  83. package/dist/io/factory.d.ts +5 -0
  84. package/dist/io/factory.js +106 -0
  85. package/dist/io/files.d.ts +7 -0
  86. package/dist/io/files.js +48 -0
  87. package/dist/io/header.d.ts +2 -0
  88. package/dist/io/header.js +42 -0
  89. package/dist/io/json/jsonSink.d.ts +12 -0
  90. package/dist/io/json/jsonSink.js +19 -0
  91. package/dist/io/json/jsonSource.d.ts +11 -0
  92. package/dist/io/json/jsonSource.js +66 -0
  93. package/dist/io/mysql/client.d.ts +13 -0
  94. package/dist/io/mysql/client.js +73 -0
  95. package/dist/io/mysql/mysqlSource.d.ts +13 -0
  96. package/dist/io/mysql/mysqlSource.js +25 -0
  97. package/dist/io/optional.d.ts +2 -0
  98. package/dist/io/optional.js +36 -0
  99. package/dist/io/parquet/parquetLibrary.d.ts +11 -0
  100. package/dist/io/parquet/parquetLibrary.js +20 -0
  101. package/dist/io/parquet/parquetSink.d.ts +15 -0
  102. package/dist/io/parquet/parquetSink.js +58 -0
  103. package/dist/io/parquet/parquetSource.d.ts +13 -0
  104. package/dist/io/parquet/parquetSource.js +68 -0
  105. package/dist/io/postgres/postgresSource.d.ts +26 -0
  106. package/dist/io/postgres/postgresSource.js +42 -0
  107. package/dist/io/safety.d.ts +4 -0
  108. package/dist/io/safety.js +83 -0
  109. package/dist/io/sheets/client.d.ts +10 -0
  110. package/dist/io/sheets/client.js +141 -0
  111. package/dist/io/sheets/sheetsSink.d.ts +12 -0
  112. package/dist/io/sheets/sheetsSink.js +24 -0
  113. package/dist/io/sheets/sheetsSource.d.ts +10 -0
  114. package/dist/io/sheets/sheetsSource.js +47 -0
  115. package/dist/io/sql/cell.d.ts +5 -0
  116. package/dist/io/sql/cell.js +38 -0
  117. package/dist/io/sql/driver.d.ts +1 -0
  118. package/dist/io/sql/driver.js +15 -0
  119. package/dist/io/sql/names.d.ts +1 -0
  120. package/dist/io/sql/names.js +12 -0
  121. package/dist/io/sqlite/sqliteSource.d.ts +11 -0
  122. package/dist/io/sqlite/sqliteSource.js +52 -0
  123. package/dist/io/sqlserver/sqlServerSource.d.ts +24 -0
  124. package/dist/io/sqlserver/sqlServerSource.js +49 -0
  125. package/dist/io/types.d.ts +11 -0
  126. package/dist/io/types.js +2 -0
  127. package/dist/io/xml/xmlParse.d.ts +9 -0
  128. package/dist/io/xml/xmlParse.js +178 -0
  129. package/dist/io/xml/xmlSink.d.ts +18 -0
  130. package/dist/io/xml/xmlSink.js +79 -0
  131. package/dist/io/xml/xmlSource.d.ts +11 -0
  132. package/dist/io/xml/xmlSource.js +93 -0
  133. package/dist/logger.d.ts +11 -0
  134. package/dist/logger.js +29 -0
  135. package/dist/main.d.ts +2 -0
  136. package/dist/main.js +18 -0
  137. package/dist/normalizers/builtin.d.ts +5 -0
  138. package/dist/normalizers/builtin.js +23 -0
  139. package/dist/normalizers/index.d.ts +2 -0
  140. package/dist/normalizers/index.js +14 -0
  141. package/dist/normalizers/loader.d.ts +1 -0
  142. package/dist/normalizers/loader.js +30 -0
  143. package/dist/normalizers/registry.d.ts +9 -0
  144. package/dist/normalizers/registry.js +29 -0
  145. package/dist/pipeline/index.d.ts +7 -0
  146. package/dist/pipeline/index.js +17 -0
  147. package/dist/pipeline/modes.d.ts +6 -0
  148. package/dist/pipeline/modes.js +48 -0
  149. package/dist/pipeline/report.d.ts +3 -0
  150. package/dist/pipeline/report.js +32 -0
  151. package/dist/pipeline/runEtl.d.ts +24 -0
  152. package/dist/pipeline/runEtl.js +98 -0
  153. package/dist/pipeline/steps.d.ts +4 -0
  154. package/dist/pipeline/steps.js +36 -0
  155. package/dist/pipeline/types.d.ts +34 -0
  156. package/dist/pipeline/types.js +2 -0
  157. package/dist/types.d.ts +3 -0
  158. package/dist/types.js +2 -0
  159. package/package.json +128 -0
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SqlServerSource = exports.createSqlServerClient = void 0;
4
+ const optional_1 = require("../optional");
5
+ const names_1 = require("../sql/names");
6
+ const cell_1 = require("../sql/cell");
7
+ const createSqlServerClient = async (connection) => {
8
+ const mssql = (0, optional_1.requireOptional)(() => require("mssql"), ["mssql"], "SQL Server");
9
+ const pool = new mssql.ConnectionPool({
10
+ server: connection.host,
11
+ port: connection.port,
12
+ user: connection.user,
13
+ ...(connection.password !== undefined
14
+ ? { password: connection.password }
15
+ : {}),
16
+ database: connection.database,
17
+ options: {
18
+ encrypt: connection.encrypt ?? true,
19
+ trustServerCertificate: connection.trustServerCertificate ?? false,
20
+ },
21
+ });
22
+ await pool.connect();
23
+ return {
24
+ query: async (text) => (await pool.request().query(text)).recordset,
25
+ close: () => pool.close(),
26
+ };
27
+ };
28
+ exports.createSqlServerClient = createSqlServerClient;
29
+ class SqlServerSource {
30
+ connection;
31
+ table;
32
+ createClient;
33
+ constructor(connection, table, createClient = exports.createSqlServerClient) {
34
+ this.connection = connection;
35
+ this.table = table;
36
+ this.createClient = createClient;
37
+ }
38
+ async read() {
39
+ const client = await this.createClient(this.connection);
40
+ try {
41
+ const records = await client.query(`SELECT * FROM ${(0, names_1.quoteTable)(this.table, "[", "]")}`);
42
+ return records.map(cell_1.toRow);
43
+ }
44
+ finally {
45
+ await client.close();
46
+ }
47
+ }
48
+ }
49
+ exports.SqlServerSource = SqlServerSource;
@@ -0,0 +1,11 @@
1
+ import { TableRow } from "../types";
2
+ export interface Source {
3
+ read(): Promise<TableRow[]>;
4
+ close?(): Promise<void>;
5
+ }
6
+ export interface SinkWriteOptions {
7
+ name?: string | undefined;
8
+ }
9
+ export interface Sink {
10
+ write(rows: TableRow[], options?: SinkWriteOptions): Promise<void>;
11
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ export interface XmlElement {
2
+ name: string;
3
+ attributes: Record<string, string>;
4
+ children: XmlElement[];
5
+ text: string;
6
+ }
7
+ export declare class XmlError extends Error {
8
+ }
9
+ export declare function parseXml(text: string): XmlElement;
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.XmlError = void 0;
4
+ exports.parseXml = parseXml;
5
+ const NAMED_ENTITIES = {
6
+ lt: "<",
7
+ gt: ">",
8
+ amp: "&",
9
+ quot: '"',
10
+ apos: "'",
11
+ };
12
+ const NAME = /^[^\s/>=<"'!?]+/;
13
+ class XmlError extends Error {
14
+ }
15
+ exports.XmlError = XmlError;
16
+ class Reader {
17
+ text;
18
+ position = 0;
19
+ constructor(text) {
20
+ this.text = text;
21
+ }
22
+ get done() {
23
+ return this.position >= this.text.length;
24
+ }
25
+ fail(message, at = this.position) {
26
+ const line = this.text.slice(0, at).split("\n").length;
27
+ throw new XmlError(`${message} (linha ${line})`);
28
+ }
29
+ startsWith(value) {
30
+ return this.text.startsWith(value, this.position);
31
+ }
32
+ skip(value) {
33
+ if (!this.startsWith(value))
34
+ this.fail(`Esperava "${value}"`);
35
+ this.position += value.length;
36
+ }
37
+ skipSpaces() {
38
+ while (!this.done && /\s/.test(this.text[this.position] ?? "")) {
39
+ this.position++;
40
+ }
41
+ }
42
+ until(end, what) {
43
+ const found = this.text.indexOf(end, this.position);
44
+ if (found === -1)
45
+ this.fail(`${what} sem fechar`);
46
+ const value = this.text.slice(this.position, found);
47
+ this.position = found + end.length;
48
+ return value;
49
+ }
50
+ textUntilTag() {
51
+ const found = this.text.indexOf("<", this.position);
52
+ const end = found === -1 ? this.text.length : found;
53
+ const start = this.position;
54
+ this.position = end;
55
+ return decode(this.text.slice(start, end), this, start);
56
+ }
57
+ name() {
58
+ const match = this.text.slice(this.position).match(NAME);
59
+ if (!match)
60
+ this.fail("Esperava o nome de um elemento ou atributo");
61
+ this.position += match[0].length;
62
+ return match[0];
63
+ }
64
+ quoted() {
65
+ const quote = this.text[this.position];
66
+ if (quote !== '"' && quote !== "'") {
67
+ this.fail("O valor do atributo precisa estar entre aspas");
68
+ }
69
+ this.position++;
70
+ const start = this.position;
71
+ const value = this.until(quote, "Valor de atributo");
72
+ if (value.includes("<"))
73
+ this.fail("Atributo com < dentro", start);
74
+ return decode(value, this, start);
75
+ }
76
+ }
77
+ function decode(value, reader, at) {
78
+ return value.replace(/&([^;&\s]*);?/g, (whole, entity) => {
79
+ if (!whole.endsWith(";"))
80
+ reader.fail("Tem um & solto no texto", at);
81
+ const named = NAMED_ENTITIES[entity];
82
+ if (named !== undefined)
83
+ return named;
84
+ const code = entity.startsWith("#x")
85
+ ? parseInt(entity.slice(2), 16)
86
+ : entity.startsWith("#")
87
+ ? parseInt(entity.slice(1), 10)
88
+ : NaN;
89
+ if (Number.isNaN(code) || code > 0x10ffff) {
90
+ reader.fail(`A entidade &${entity}; não existe`, at);
91
+ }
92
+ return String.fromCodePoint(code);
93
+ });
94
+ }
95
+ function skipMarkup(reader) {
96
+ if (reader.startsWith("<?")) {
97
+ reader.until("?>", "Instrução <?");
98
+ return true;
99
+ }
100
+ if (reader.startsWith("<!--")) {
101
+ reader.until("-->", "Comentário");
102
+ return true;
103
+ }
104
+ if (reader.startsWith("<!DOCTYPE") || reader.startsWith("<!ENTITY")) {
105
+ reader.fail("O XML tem um <!DOCTYPE>, e o Datera não lê isso por segurança. Tire essa parte do arquivo");
106
+ }
107
+ return false;
108
+ }
109
+ function readElement(reader) {
110
+ reader.skip("<");
111
+ const element = {
112
+ name: reader.name(),
113
+ attributes: {},
114
+ children: [],
115
+ text: "",
116
+ };
117
+ for (;;) {
118
+ reader.skipSpaces();
119
+ if (reader.startsWith("/>")) {
120
+ reader.skip("/>");
121
+ return element;
122
+ }
123
+ if (reader.startsWith(">")) {
124
+ reader.skip(">");
125
+ break;
126
+ }
127
+ const attribute = reader.name();
128
+ reader.skipSpaces();
129
+ reader.skip("=");
130
+ reader.skipSpaces();
131
+ element.attributes[attribute] = reader.quoted();
132
+ }
133
+ const texts = [];
134
+ for (;;) {
135
+ if (reader.done)
136
+ reader.fail(`O elemento <${element.name}> não fecha`);
137
+ if (reader.startsWith("</")) {
138
+ reader.skip("</");
139
+ const closing = reader.name();
140
+ if (closing !== element.name) {
141
+ reader.fail(`<${element.name}> foi fechado com </${closing}>`);
142
+ }
143
+ reader.skipSpaces();
144
+ reader.skip(">");
145
+ element.text = texts.join("").trim();
146
+ return element;
147
+ }
148
+ if (reader.startsWith("<![CDATA[")) {
149
+ reader.skip("<![CDATA[");
150
+ texts.push(reader.until("]]>", "Bloco CDATA"));
151
+ }
152
+ else if (!skipMarkup(reader)) {
153
+ if (reader.startsWith("<"))
154
+ element.children.push(readElement(reader));
155
+ else
156
+ texts.push(reader.textUntilTag());
157
+ }
158
+ }
159
+ }
160
+ function parseXml(text) {
161
+ const reader = new Reader(text.replace(/^/, ""));
162
+ let root;
163
+ for (;;) {
164
+ reader.skipSpaces();
165
+ if (reader.done)
166
+ break;
167
+ if (skipMarkup(reader))
168
+ continue;
169
+ if (!reader.startsWith("<"))
170
+ reader.fail("Tem texto fora do elemento principal");
171
+ if (root)
172
+ reader.fail("O XML tem mais de um elemento principal");
173
+ root = readElement(reader);
174
+ }
175
+ if (!root)
176
+ throw new XmlError("O arquivo não tem nenhum elemento XML.");
177
+ return root;
178
+ }
@@ -0,0 +1,18 @@
1
+ import { TableRow } from "../../types";
2
+ import { Sink, SinkWriteOptions } from "../types";
3
+ import { Logger } from "../../logger";
4
+ export interface XmlSinkOptions {
5
+ path: string;
6
+ root?: string | undefined;
7
+ record?: string | undefined;
8
+ }
9
+ export declare const DEFAULT_XML_ROOT = "registros";
10
+ export declare const DEFAULT_XML_RECORD = "registro";
11
+ export declare function xmlNameFor(column: string): string;
12
+ export declare function toXml(rows: TableRow[], root: string, record: string, names?: Map<string, string>): string;
13
+ export declare class XmlSink implements Sink {
14
+ private readonly options;
15
+ private readonly logger;
16
+ constructor(options: XmlSinkOptions, logger?: Logger);
17
+ write(rows: TableRow[], options?: SinkWriteOptions): Promise<void>;
18
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.XmlSink = exports.DEFAULT_XML_RECORD = exports.DEFAULT_XML_ROOT = void 0;
4
+ exports.xmlNameFor = xmlNameFor;
5
+ exports.toXml = toXml;
6
+ const files_1 = require("../files");
7
+ const logger_1 = require("../../logger");
8
+ exports.DEFAULT_XML_ROOT = "registros";
9
+ exports.DEFAULT_XML_RECORD = "registro";
10
+ const INDENT = " ";
11
+ function escapeText(value) {
12
+ return value
13
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, "")
14
+ .replace(/&/g, "&amp;")
15
+ .replace(/</g, "&lt;")
16
+ .replace(/>/g, "&gt;");
17
+ }
18
+ function xmlNameFor(column) {
19
+ const cleaned = column.trim().replace(/[^\p{L}\p{N}_.-]+/gu, "_");
20
+ if (cleaned === "")
21
+ return "coluna";
22
+ return /^[\p{L}_]/u.test(cleaned) ? cleaned : `_${cleaned}`;
23
+ }
24
+ function elementNames(columns) {
25
+ const names = new Map();
26
+ const used = new Set();
27
+ for (const column of columns) {
28
+ const base = xmlNameFor(column);
29
+ let name = base;
30
+ for (let count = 2; used.has(name); count++)
31
+ name = `${base}_${count}`;
32
+ used.add(name);
33
+ names.set(column, name);
34
+ }
35
+ return names;
36
+ }
37
+ function columnsOf(rows) {
38
+ const columns = new Set();
39
+ for (const row of rows) {
40
+ for (const column of Object.keys(row))
41
+ columns.add(column);
42
+ }
43
+ return [...columns];
44
+ }
45
+ function toXml(rows, root, record, names = elementNames(columnsOf(rows))) {
46
+ const lines = ['<?xml version="1.0" encoding="UTF-8"?>', `<${root}>`];
47
+ for (const row of rows) {
48
+ lines.push(`${INDENT}<${record}>`);
49
+ for (const [column, value] of Object.entries(row)) {
50
+ const name = names.get(column) ?? xmlNameFor(column);
51
+ lines.push(value === null || value === ""
52
+ ? `${INDENT}${INDENT}<${name}/>`
53
+ : `${INDENT}${INDENT}<${name}>${escapeText(String(value))}</${name}>`);
54
+ }
55
+ lines.push(`${INDENT}</${record}>`);
56
+ }
57
+ lines.push(`</${root}>`);
58
+ return lines.join("\n") + "\n";
59
+ }
60
+ class XmlSink {
61
+ options;
62
+ logger;
63
+ constructor(options, logger = logger_1.consoleLogger) {
64
+ this.options = options;
65
+ this.logger = logger;
66
+ }
67
+ async write(rows, options = {}) {
68
+ const filePath = (0, files_1.extraOutputPath)(this.options.path, options.name);
69
+ const names = elementNames(columnsOf(rows));
70
+ for (const [column, name] of names) {
71
+ if (column !== name) {
72
+ this.logger.warn(`A coluna "${column}" virou <${name}> no XML, porque o nome original não vale como elemento.`);
73
+ }
74
+ }
75
+ (0, files_1.writeTextFile)(filePath, toXml(rows, this.options.root ?? exports.DEFAULT_XML_ROOT, this.options.record ?? exports.DEFAULT_XML_RECORD, names));
76
+ this.logger.info(`${rows.length} linhas escritas em ${filePath}.`);
77
+ }
78
+ }
79
+ exports.XmlSink = XmlSink;
@@ -0,0 +1,11 @@
1
+ import { TableRow } from "../../types";
2
+ import { Source } from "../types";
3
+ export interface XmlSourceOptions {
4
+ path: string;
5
+ recordsPath?: string | undefined;
6
+ }
7
+ export declare class XmlSource implements Source {
8
+ private readonly options;
9
+ constructor(options: XmlSourceOptions);
10
+ read(): Promise<TableRow[]>;
11
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.XmlSource = void 0;
4
+ const files_1 = require("../files");
5
+ const xmlParse_1 = require("./xmlParse");
6
+ function join(prefix, name) {
7
+ return prefix ? `${prefix}.${name}` : name;
8
+ }
9
+ function isSimple(element) {
10
+ return (element.children.length === 0 &&
11
+ Object.keys(element.attributes).length === 0);
12
+ }
13
+ function groupByName(elements) {
14
+ const groups = new Map();
15
+ for (const element of elements) {
16
+ const group = groups.get(element.name) ?? [];
17
+ group.push(element);
18
+ groups.set(element.name, group);
19
+ }
20
+ return groups;
21
+ }
22
+ function flatten(element, prefix, row) {
23
+ for (const [name, value] of Object.entries(element.attributes)) {
24
+ row[join(prefix, name)] = value === "" ? null : value;
25
+ }
26
+ if (prefix && element.children.length === 0) {
27
+ if (element.text !== "")
28
+ row[prefix] = element.text;
29
+ else if (isSimple(element))
30
+ row[prefix] = null;
31
+ }
32
+ for (const [name, group] of groupByName(element.children)) {
33
+ const key = join(prefix, name);
34
+ const [first] = group;
35
+ if (group.length === 1 && first) {
36
+ flatten(first, key, row);
37
+ }
38
+ else if (group.every(isSimple)) {
39
+ row[key] = group.map((item) => item.text).join(", ");
40
+ }
41
+ else {
42
+ row[key] = JSON.stringify(group.map((item) => {
43
+ const inner = {};
44
+ flatten(item, "", inner);
45
+ return inner;
46
+ }));
47
+ }
48
+ }
49
+ }
50
+ function findRecords(root, recordsPath) {
51
+ const [first, ...rest] = recordsPath.split(".");
52
+ if (first !== root.name) {
53
+ throw new Error(`O XML começa em <${root.name}>, então o recordsPath precisa começar com "${root.name}".`);
54
+ }
55
+ let current = [root];
56
+ for (const name of rest) {
57
+ current = current.flatMap((element) => element.children.filter((child) => child.name === name));
58
+ }
59
+ return current;
60
+ }
61
+ class XmlSource {
62
+ options;
63
+ constructor(options) {
64
+ this.options = options;
65
+ }
66
+ async read() {
67
+ let root;
68
+ try {
69
+ root = (0, xmlParse_1.parseXml)((0, files_1.readTextFile)(this.options.path));
70
+ }
71
+ catch (error) {
72
+ if (error instanceof xmlParse_1.XmlError) {
73
+ throw new Error(`XML inválido em ${this.options.path}: ${error.message}`);
74
+ }
75
+ throw error;
76
+ }
77
+ const { recordsPath } = this.options;
78
+ const records = recordsPath
79
+ ? findRecords(root, recordsPath)
80
+ : root.children;
81
+ if (records.length === 0) {
82
+ throw new Error(recordsPath
83
+ ? `Não achei nenhum registro em "${recordsPath}" no XML ${this.options.path}.`
84
+ : `O elemento <${root.name}> em ${this.options.path} não tem nenhum registro dentro. Se os registros estão mais para dentro, use recordsPath.`);
85
+ }
86
+ return records.map((record) => {
87
+ const row = {};
88
+ flatten(record, "", row);
89
+ return row;
90
+ });
91
+ }
92
+ }
93
+ exports.XmlSource = XmlSource;
@@ -0,0 +1,11 @@
1
+ export interface Logger {
2
+ info(message: string): void;
3
+ warn(message: string): void;
4
+ error(message: string, error?: unknown): void;
5
+ }
6
+ export declare const consoleLogger: Logger;
7
+ export declare const silentLogger: Logger;
8
+ export interface MemoryLogger extends Logger {
9
+ messages: string[];
10
+ }
11
+ export declare function createMemoryLogger(): MemoryLogger;
package/dist/logger.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.silentLogger = exports.consoleLogger = void 0;
4
+ exports.createMemoryLogger = createMemoryLogger;
5
+ function withReason(message, error) {
6
+ if (error === undefined)
7
+ return message;
8
+ const reason = error instanceof Error ? error.message : String(error);
9
+ return `${message} ${reason}`;
10
+ }
11
+ exports.consoleLogger = {
12
+ info: (message) => console.log(message),
13
+ warn: (message) => console.warn(message),
14
+ error: (message, error) => console.error(withReason(message, error)),
15
+ };
16
+ exports.silentLogger = {
17
+ info: () => { },
18
+ warn: () => { },
19
+ error: () => { },
20
+ };
21
+ function createMemoryLogger() {
22
+ const messages = [];
23
+ return {
24
+ messages,
25
+ info: (message) => messages.push(message),
26
+ warn: (message) => messages.push(message),
27
+ error: (message, error) => messages.push(withReason(message, error)),
28
+ };
29
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
package/dist/main.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ require("dotenv/config");
5
+ const cli_1 = require("./cli");
6
+ const commands_1 = require("./commands");
7
+ const logger_1 = require("./logger");
8
+ async function main() {
9
+ const cli = (0, cli_1.parseCli)();
10
+ try {
11
+ process.exitCode = await (0, commands_1.runCommand)(cli, logger_1.consoleLogger);
12
+ }
13
+ catch (error) {
14
+ logger_1.consoleLogger.error(cli.command === "run" ? "Deu erro no ETL:" : "Deu erro:", error);
15
+ process.exitCode = 1;
16
+ }
17
+ }
18
+ main();
@@ -0,0 +1,5 @@
1
+ import { KeyNormalizer } from "./registry";
2
+ export declare const trimNormalizer: KeyNormalizer;
3
+ export declare const lowercaseNormalizer: KeyNormalizer;
4
+ export declare const digitsOnlyNormalizer: KeyNormalizer;
5
+ export declare const alphanumericNormalizer: KeyNormalizer;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.alphanumericNormalizer = exports.digitsOnlyNormalizer = exports.lowercaseNormalizer = exports.trimNormalizer = void 0;
4
+ const trimNormalizer = (raw) => ({
5
+ key: typeof raw === "string" ? raw.trim() : raw,
6
+ });
7
+ exports.trimNormalizer = trimNormalizer;
8
+ const lowercaseNormalizer = (raw) => ({
9
+ key: String(raw).trim().toLowerCase(),
10
+ });
11
+ exports.lowercaseNormalizer = lowercaseNormalizer;
12
+ const digitsOnlyNormalizer = (raw) => {
13
+ const digits = String(raw).replace(/\D/g, "");
14
+ return digits === "" ? null : { key: digits };
15
+ };
16
+ exports.digitsOnlyNormalizer = digitsOnlyNormalizer;
17
+ const alphanumericNormalizer = (raw) => {
18
+ const clean = String(raw)
19
+ .replace(/[^\p{L}\p{N}]/gu, "")
20
+ .toLowerCase();
21
+ return clean === "" ? null : { key: clean };
22
+ };
23
+ exports.alphanumericNormalizer = alphanumericNormalizer;
@@ -0,0 +1,2 @@
1
+ export declare function registerBuiltinKeyNormalizers(): void;
2
+ export { loadNormalizerModules } from "./loader";
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadNormalizerModules = void 0;
4
+ exports.registerBuiltinKeyNormalizers = registerBuiltinKeyNormalizers;
5
+ const registry_1 = require("./registry");
6
+ const builtin_1 = require("./builtin");
7
+ function registerBuiltinKeyNormalizers() {
8
+ (0, registry_1.registerKeyNormalizer)("trim", builtin_1.trimNormalizer);
9
+ (0, registry_1.registerKeyNormalizer)("lowercase", builtin_1.lowercaseNormalizer);
10
+ (0, registry_1.registerKeyNormalizer)("digitsOnly", builtin_1.digitsOnlyNormalizer);
11
+ (0, registry_1.registerKeyNormalizer)("alphanumeric", builtin_1.alphanumericNormalizer);
12
+ }
13
+ var loader_1 = require("./loader");
14
+ Object.defineProperty(exports, "loadNormalizerModules", { enumerable: true, get: function () { return loader_1.loadNormalizerModules; } });
@@ -0,0 +1 @@
1
+ export declare function loadNormalizerModules(modulePaths: string[]): void;
@@ -0,0 +1,30 @@
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.loadNormalizerModules = loadNormalizerModules;
7
+ const path_1 = __importDefault(require("path"));
8
+ const registry_1 = require("./registry");
9
+ function loadNormalizerModules(modulePaths) {
10
+ for (const modulePath of modulePaths) {
11
+ let loaded;
12
+ try {
13
+ loaded = require(path_1.default.resolve(modulePath));
14
+ }
15
+ catch (error) {
16
+ const reason = error instanceof Error ? error.message : String(error);
17
+ throw new Error(`Não consegui carregar o módulo de normalizadores "${modulePath}": ${reason}`);
18
+ }
19
+ const normalizers = loaded?.normalizers;
20
+ if (!normalizers || typeof normalizers !== "object") {
21
+ throw new Error(`O módulo "${modulePath}" precisa exportar um objeto chamado "normalizers". Exemplo: export const normalizers = { meuNormalizador };`);
22
+ }
23
+ for (const [name, normalizer] of Object.entries(normalizers)) {
24
+ if (typeof normalizer !== "function") {
25
+ throw new Error(`Em "${modulePath}", "${name}" não é uma função.`);
26
+ }
27
+ (0, registry_1.registerKeyNormalizer)(name, normalizer);
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,9 @@
1
+ export interface NormalizedKey {
2
+ key: string | number;
3
+ group?: string;
4
+ }
5
+ export type KeyNormalizer = (raw: string | number) => NormalizedKey | null;
6
+ export declare function registerKeyNormalizer(name: string, normalizer: KeyNormalizer): void;
7
+ export declare function getKeyNormalizer(name: string): KeyNormalizer;
8
+ export declare function listKeyNormalizers(): string[];
9
+ export declare function clearKeyNormalizers(): void;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerKeyNormalizer = registerKeyNormalizer;
4
+ exports.getKeyNormalizer = getKeyNormalizer;
5
+ exports.listKeyNormalizers = listKeyNormalizers;
6
+ exports.clearKeyNormalizers = clearKeyNormalizers;
7
+ const registry = new Map();
8
+ function registerKeyNormalizer(name, normalizer) {
9
+ const existing = registry.get(name);
10
+ if (existing && existing !== normalizer) {
11
+ throw new Error(`Já existe um normalizador de chave registrado com o nome "${name}".`);
12
+ }
13
+ registry.set(name, normalizer);
14
+ }
15
+ function getKeyNormalizer(name) {
16
+ const normalizer = registry.get(name);
17
+ if (!normalizer) {
18
+ const available = listKeyNormalizers();
19
+ const hint = available.length > 0 ? available.join(", ") : "nenhum registrado";
20
+ throw new Error(`Normalizador de chave "${name}" não encontrado. Disponíveis: ${hint}`);
21
+ }
22
+ return normalizer;
23
+ }
24
+ function listKeyNormalizers() {
25
+ return Array.from(registry.keys());
26
+ }
27
+ function clearKeyNormalizers() {
28
+ registry.clear();
29
+ }
@@ -0,0 +1,7 @@
1
+ export { runEtl, applySteps, countPendingReasons, DEFAULT_PREVIEW_SIZE, } from "./runEtl";
2
+ export type { RunEtlOptions, AppliedSteps } from "./runEtl";
3
+ export { buildSteps } from "./steps";
4
+ export { MODES, parseMode, buildModeStep } from "./modes";
5
+ export type { Mode } from "./modes";
6
+ export { formatReport, formatDuration } from "./report";
7
+ export type { EtlReport, PendingReason, Step, StepOutput, StepReport, } from "./types";