cosmemilton-acbr-node 0.1.0 → 0.1.1
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/dist/cli.js +330 -60
- package/dist/cli.js.map +3 -3
- package/dist/index.cjs +320 -46
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +317 -46
- package/dist/index.js.map +4 -4
- package/dist/runtime.cjs +254 -35
- package/dist/runtime.cjs.map +3 -3
- package/dist/runtime.js +251 -34
- package/dist/runtime.js.map +3 -3
- package/dist/types/generated/acbr/index.d.ts +15 -1
- package/dist/types/generated/acbr/models.d.ts +32 -0
- package/dist/types/src/diagnostics.d.ts +8 -0
- package/dist/types/src/generator.d.ts +1 -0
- package/dist/types/src/index.d.ts +13 -0
- package/dist/types/src/runtime.d.ts +1 -0
- package/dist/types/src/types.d.ts +34 -1
- package/docs/API.md +90 -32
- package/docs/CI.md +6 -0
- package/docs/VALIDACAO.md +3 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ __export(generator_exports, {
|
|
|
16
16
|
compareContracts: () => compareContracts,
|
|
17
17
|
generateModel: () => generateModel,
|
|
18
18
|
renderClientIndex: () => renderClientIndex,
|
|
19
|
+
renderModelValidation: () => renderModelValidation,
|
|
19
20
|
renderPascal: () => renderPascal,
|
|
20
21
|
renderTypeScript: () => renderTypeScript
|
|
21
22
|
});
|
|
@@ -124,6 +125,74 @@ function compareContracts(previous, current) {
|
|
|
124
125
|
for (const name of old.keys()) if (!now.has(name)) removals.push(name);
|
|
125
126
|
return { additions: additions.sort(), removals: removals.sort(), changes: changes.sort(), breaking: removals.length > 0 || changes.length > 0 };
|
|
126
127
|
}
|
|
128
|
+
function renderModelValidation() {
|
|
129
|
+
return String.raw`
|
|
130
|
+
export type CodigoValidacaoModelo = 'MODELO_DESCONHECIDO'|'ENUM_INVALIDO'|'ARRAY_ESPERADO'|'OBJETO_ESPERADO'|'CAMPO_DESCONHECIDO'|'CAMPO_SOMENTE_LEITURA'|'INTEIRO_INVALIDO'|'BOOLEANO_ESPERADO'|'DECIMAL_INVALIDO'|'DATA_HORA_INVALIDA'|'DATA_INVALIDA'|'TEXTO_ESPERADO';
|
|
131
|
+
function campoConhecido(at: unknown): string | undefined {
|
|
132
|
+
if(typeof at!=='string'||at.length>512||! /^[A-Za-z_]\w*(?:(?:\.[A-Za-z_]\w*)|(?:\[\d{1,7}\]))*$/.test(at))return undefined;
|
|
133
|
+
const parts=at.replace(/\[(\d+)\]/g,'.$1').split('.');let schema=Object.hasOwn(MODEL_SCHEMA,parts[0])?MODEL_SCHEMA[parts[0]]:undefined;
|
|
134
|
+
if(!schema)return undefined;
|
|
135
|
+
for(const part of parts.slice(1)){
|
|
136
|
+
if(schema.kind==='list'){if(! /^\d+$/.test(part))return undefined;schema=MODEL_SCHEMA[schema.item];}
|
|
137
|
+
else if(schema.kind==='object'){const p=schema.properties.find((p:any)=>p.name===part);if(!p)return undefined;schema=p.ref?MODEL_SCHEMA[p.ref]:{kind:p.kind};}
|
|
138
|
+
else return undefined;
|
|
139
|
+
}
|
|
140
|
+
return at;
|
|
141
|
+
}
|
|
142
|
+
/** TypeError compatível, com contexto seguro e sem o valor informado ou chaves desconhecidas. */
|
|
143
|
+
export class ErroValidacaoModelo extends TypeError {
|
|
144
|
+
readonly codigo: CodigoValidacaoModelo;
|
|
145
|
+
readonly diagnostico: Readonly<{codigo:CodigoValidacaoModelo;etapa:'documento';campo?:string;mensagem:string;acao:string;statusEnvio:'nao_enviado';causa:Readonly<{codigo:CodigoValidacaoModelo;etapa:'documento';campo?:string}>}>;
|
|
146
|
+
constructor(codigo: CodigoValidacaoModelo, at?: string) {
|
|
147
|
+
const catalogo:Record<CodigoValidacaoModelo,[string,string]>={
|
|
148
|
+
MODELO_DESCONHECIDO:['Modelo desconhecido.','Use um modelo exportado pelo cliente gerado.'],
|
|
149
|
+
ENUM_INVALIDO:['enum inválido','Use um dos valores textuais do enum exportado pelo cliente.'],
|
|
150
|
+
ARRAY_ESPERADO:['esperado array','Informe uma lista JSON entre colchetes.'],
|
|
151
|
+
OBJETO_ESPERADO:['esperado objeto','Informe um objeto JSON entre chaves.'],
|
|
152
|
+
CAMPO_DESCONHECIDO:['propriedade desconhecida','Confira os nomes permitidos neste grupo; a chave desconhecida não é registrada.'],
|
|
153
|
+
CAMPO_SOMENTE_LEITURA:['propriedade somente leitura','Remova este campo da entrada; o motor preenche seu resultado.'],
|
|
154
|
+
INTEIRO_INVALIDO:['inteiro fora do intervalo','Informe um número inteiro dentro do intervalo do campo.'],
|
|
155
|
+
BOOLEANO_ESPERADO:['esperado boolean','Informe true ou false como valor JSON, sem aspas.'],
|
|
156
|
+
DECIMAL_INVALIDO:['esperado decimal em string','Informe o decimal entre aspas, com ponto e a escala permitida pelo campo.'],
|
|
157
|
+
DATA_HORA_INVALIDA:['esperado data/hora civil ISO (sem timezone)','Informe a data/hora civil da UF como AAAA-MM-DDTHH:mm:ss, sem Z ou offset. Confira a data antes do envio.'],
|
|
158
|
+
DATA_INVALIDA:['data inválida','Confira dia, mês, ano e horário usando uma data civil válida.'],
|
|
159
|
+
TEXTO_ESPERADO:['esperado string','Informe texto entre aspas no JSON.']
|
|
160
|
+
};
|
|
161
|
+
const safeCode=Object.hasOwn(catalogo,codigo)?codigo:'MODELO_DESCONHECIDO';
|
|
162
|
+
const campo=campoConhecido(at),[texto,acao]=catalogo[safeCode],mensagem=(campo?campo+': ':'')+texto;
|
|
163
|
+
super(mensagem);this.codigo=safeCode;
|
|
164
|
+
const causa=Object.freeze({codigo:safeCode,etapa:'documento' as const,...(campo?{campo}:{})});
|
|
165
|
+
this.diagnostico=Object.freeze({codigo:safeCode,etapa:'documento' as const,...(campo?{campo}:{}),mensagem,acao,statusEnvio:'nao_enviado' as const,causa});
|
|
166
|
+
Object.defineProperty(this,'cause',{value:causa,enumerable:false});
|
|
167
|
+
}
|
|
168
|
+
toJSON(){return this.diagnostico;}
|
|
169
|
+
}
|
|
170
|
+
export function validateModel(model: string, value: unknown, at=model): void {
|
|
171
|
+
const schema=Object.hasOwn(MODEL_SCHEMA,model)?MODEL_SCHEMA[model]:undefined;
|
|
172
|
+
if(!schema)throw new ErroValidacaoModelo('MODELO_DESCONHECIDO');
|
|
173
|
+
at=campoConhecido(at)??model;
|
|
174
|
+
if(schema.kind==='enum'){if(typeof value!=='string'||!schema.values.includes(value))throw new ErroValidacaoModelo('ENUM_INVALIDO',at);return;}
|
|
175
|
+
if(schema.kind==='list'){if(!Array.isArray(value))throw new ErroValidacaoModelo('ARRAY_ESPERADO',at);value.forEach((item,index)=>validateModel(schema.item,item,at+'['+index+']'));return;}
|
|
176
|
+
if(value===null||typeof value!=='object'||Array.isArray(value))throw new ErroValidacaoModelo('OBJETO_ESPERADO',at);
|
|
177
|
+
const fields=new Map<string,any>(schema.properties.filter((p:any)=>!p.readonly).map((p:any)=>[p.name,p]));
|
|
178
|
+
for(const [name,v] of Object.entries(value)){
|
|
179
|
+
if(v===undefined)continue;
|
|
180
|
+
const p=fields.get(name);
|
|
181
|
+
if(!p){const readonly=schema.properties.some((p:any)=>p.name===name&&p.readonly);throw new ErroValidacaoModelo(readonly?'CAMPO_SOMENTE_LEITURA':'CAMPO_DESCONHECIDO',readonly?at+'.'+name:at);}
|
|
182
|
+
const field=at+'.'+name;
|
|
183
|
+
if(p.ref){validateModel(p.ref,v,field);continue;}
|
|
184
|
+
if(p.kind==='integer'){if(typeof v!=='number'||!Number.isSafeInteger(v)||v<p.min||v>p.max)throw new ErroValidacaoModelo('INTEIRO_INVALIDO',field);}
|
|
185
|
+
else if(p.kind==='boolean'){if(typeof v!=='boolean')throw new ErroValidacaoModelo('BOOLEANO_ESPERADO',field);}
|
|
186
|
+
else if(p.kind==='decimal'){if(typeof v!=='string'||v.length>64||! /^-?\d+(?:\.\d+)?$/.test(v)||p.scale!==undefined&&(v.split('.')[1]?.length??0)>p.scale)throw new ErroValidacaoModelo('DECIMAL_INVALIDO',field);}
|
|
187
|
+
else if(p.kind==='datetime'){
|
|
188
|
+
if(typeof v!=='string'||! /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)?$/.test(v)||Number.isNaN(Date.parse(v+(v.length===10?'T00:00:00Z':'Z'))))throw new ErroValidacaoModelo('DATA_HORA_INVALIDA',field);
|
|
189
|
+
const date=new Date(v+(v.length===10?'T00:00:00Z':'Z'));if(date.toISOString().slice(0,10)!==v.slice(0,10))throw new ErroValidacaoModelo('DATA_INVALIDA',field);
|
|
190
|
+
}else if(typeof v!=='string')throw new ErroValidacaoModelo('TEXTO_ESPERADO',field);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export function validateTNFe(value: unknown): asserts value is TNFeInput { validateModel('TNFe',value); }
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
127
196
|
function renderTypeScript(ir, revision2, contractHash) {
|
|
128
197
|
const out = ["// Generated from ACBr AST. LGPL-2.1-or-later. Do not edit.", "export const ACBR_CONTRACT = " + stable({ sourceRevision: revision2, contractHash }) + " as const;", "export const SOURCE_REVISION=ACBR_CONTRACT.sourceRevision;", "export const CONTRACT_HASH=ACBR_CONTRACT.contractHash;"];
|
|
129
198
|
for (const t of ir.types) {
|
|
@@ -153,30 +222,7 @@ function renderTypeScript(ir, revision2, contractHash) {
|
|
|
153
222
|
}
|
|
154
223
|
}
|
|
155
224
|
out.push("const MODEL_SCHEMA: Record<string, any> = " + stable(Object.fromEntries(ir.types.map((t) => [t.name, t]))) + ";");
|
|
156
|
-
out.push(
|
|
157
|
-
export function validateModel(model: string, value: unknown, at=model): void {
|
|
158
|
-
const schema=MODEL_SCHEMA[model]; if(!schema)throw new TypeError('Modelo desconhecido: '+model);
|
|
159
|
-
if(schema.kind==='enum'){ if(typeof value!=='string'||!schema.values.includes(value))throw new TypeError(at+': enum inválido'); return; }
|
|
160
|
-
if(schema.kind==='list'){ if(!Array.isArray(value))throw new TypeError(at+': esperado array'); value.forEach((item,index)=>validateModel(schema.item,item,at+'['+index+']'));return; }
|
|
161
|
-
if(value===null||typeof value!=='object'||Array.isArray(value))throw new TypeError(at+': esperado objeto');
|
|
162
|
-
const fields=new Map<string,any>(schema.properties.filter((p:any)=>!p.readonly).map((p:any)=>[p.name,p]));
|
|
163
|
-
for(const [name,v] of Object.entries(value)){
|
|
164
|
-
if(v===undefined)continue;
|
|
165
|
-
const p=fields.get(name);if(!p)throw new TypeError(at+'.'+name+': propriedade desconhecida ou somente leitura');
|
|
166
|
-
if(v===undefined)continue;
|
|
167
|
-
const field=at+'.'+name;
|
|
168
|
-
if(p.ref){validateModel(p.ref,v,field);continue;}
|
|
169
|
-
if(p.kind==='integer'){if(typeof v!=='number'||!Number.isSafeInteger(v)||v<p.min||v>p.max)throw new TypeError(field+': inteiro fora do intervalo');}
|
|
170
|
-
else if(p.kind==='boolean'){if(typeof v!=='boolean')throw new TypeError(field+': esperado boolean');}
|
|
171
|
-
else if(p.kind==='decimal'){if(typeof v!=='string'||v.length>64||! /^-?\d+(?:\.\d+)?$/.test(v)||p.scale!==undefined&&(v.split('.')[1]?.length??0)>p.scale)throw new TypeError(field+': esperado decimal em string'+(p.scale!==undefined?' de até '+p.scale+' casas':''));}
|
|
172
|
-
else if(p.kind==='datetime'){
|
|
173
|
-
if(typeof v!=='string'||! /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)?$/.test(v)||Number.isNaN(Date.parse(v+(v.length===10?'T00:00:00Z':'Z'))))throw new TypeError(field+': esperado data/hora civil ISO (sem timezone)');
|
|
174
|
-
const date=new Date(v+(v.length===10?'T00:00:00Z':'Z'));if(date.toISOString().slice(0,10)!==v.slice(0,10))throw new TypeError(field+': data inválida');
|
|
175
|
-
}else if(typeof v!=='string')throw new TypeError(field+': esperado string');
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
export function validateTNFe(value: unknown): asserts value is TNFeInput { validateModel('TNFe',value); }
|
|
179
|
-
`);
|
|
225
|
+
out.push(renderModelValidation());
|
|
180
226
|
return out.join("\n") + "\n";
|
|
181
227
|
}
|
|
182
228
|
function renderPascal(ir, revision2, contractHash) {
|
|
@@ -257,9 +303,11 @@ end;
|
|
|
257
303
|
function renderClientIndex() {
|
|
258
304
|
return [
|
|
259
305
|
"// Generated client bound to its own ACBr contract. Do not edit.",
|
|
260
|
-
"import { criarEmissor as criarRuntime, type OpcoesEmissor, type OpcoesOperacao } from " + JSON.stringify("cosmemilton-acbr-node/runtime") + ";",
|
|
261
|
-
"import { ACBR_CONTRACT, validateTNFe, type TNFeInput } from " + JSON.stringify("./models.js") + ";",
|
|
306
|
+
"import { criarEmissor as criarRuntime, diagnosticarErro as diagnosticarErroRuntime, diagnosticarResultado, type OpcoesEmissor, type OpcoesOperacao } from " + JSON.stringify("cosmemilton-acbr-node/runtime") + ";",
|
|
307
|
+
"import { ACBR_CONTRACT, validateTNFe, ErroValidacaoModelo, type TNFeInput } from " + JSON.stringify("./models.js") + ";",
|
|
262
308
|
"export * from " + JSON.stringify("./models.js") + ";",
|
|
309
|
+
"export { diagnosticarResultado };",
|
|
310
|
+
"export function diagnosticarErro(erro:unknown) { return erro instanceof ErroValidacaoModelo ? erro.diagnostico : diagnosticarErroRuntime(erro); }",
|
|
263
311
|
"export function criarEmissor(options: Omit<OpcoesEmissor, " + JSON.stringify("contrato") + " | " + JSON.stringify("validaDocumento") + ">) {",
|
|
264
312
|
" const emissor=criarRuntime({...options, contrato:ACBR_CONTRACT, validaDocumento:validateTNFe});",
|
|
265
313
|
" return {...emissor, gerarXml(documento:TNFeInput, opcoes?:OpcoesOperacao) { return emissor.gerarXml(documento as unknown as Record<string,unknown>, opcoes); }};",
|
|
@@ -355,6 +403,7 @@ var init_generator = __esm({
|
|
|
355
403
|
});
|
|
356
404
|
|
|
357
405
|
// src/cli.ts
|
|
406
|
+
import { realpathSync } from "node:fs";
|
|
358
407
|
import { access as access4 } from "node:fs/promises";
|
|
359
408
|
import { resolve as resolve4 } from "node:path";
|
|
360
409
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -1086,24 +1135,155 @@ import { createRequire } from "node:module";
|
|
|
1086
1135
|
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
1087
1136
|
import { TextDecoder } from "node:util";
|
|
1088
1137
|
|
|
1138
|
+
// src/diagnostics.ts
|
|
1139
|
+
var ETAPAS = /* @__PURE__ */ new Set(["configuracao", "documento", "runtime", "geracao", "validacao", "assinatura", "transmissao", "consulta", "cancelamento", "carta_correcao", "inutilizacao", "operacao", "limpeza"]);
|
|
1140
|
+
var CAMPOS = /* @__PURE__ */ new Set(["cnpj", "uf", "modelo", "ambiente", "contrato", "certificado", "certificado.senha", "certificado.pfx", "certificado.arquivo", "csc", "csc.id", "csc.valor", "timeoutMs", "limiteFila", "limiteBytes", "runtimeDirectory", "documento", "xml", "consulta", "chave", "recibo", "cancelamento", "protocolo", "justificativa", "cartaCorrecao", "correcao", "sequenciaEvento", "inutilizacao", "ano", "serie", "numeroInicial", "numeroFinal", "operacao", "signal"]);
|
|
1141
|
+
var CATALOGO = {
|
|
1142
|
+
CONFIGURACAO_INVALIDA: ["configuracao", "A configura\xE7\xE3o cont\xE9m um campo inv\xE1lido.", "Confira o campo indicado e seu tipo antes de continuar."],
|
|
1143
|
+
CONTRATO_INVALIDO: ["configuracao", "O contrato do cliente \xE9 inv\xE1lido.", "Use o contrato gerado junto com o cliente."],
|
|
1144
|
+
CERTIFICADO_INVALIDO: ["configuracao", "O certificado A1 ou sua configura\xE7\xE3o \xE9 inv\xE1lido.", "Confira formato e validade do A1, pfx ou arquivo e a senha digitada, sem registrar esses dados."],
|
|
1145
|
+
CERTIFICADO_DIVERGENTE: ["assinatura", "O certificado n\xE3o corresponde \xE0 raiz CNPJ do emitente.", "Confira o CNPJ configurado e selecione o A1 correspondente ao emitente."],
|
|
1146
|
+
DEPENDENCIA_AUSENTE: ["runtime", "Uma depend\xEAncia do motor fiscal n\xE3o est\xE1 dispon\xEDvel.", "Reinstale o runtime completo da plataforma; confira a disponibilidade do OpenSSL distribu\xEDdo."],
|
|
1147
|
+
SCHEMAS_AUSENTES: ["runtime", "Os schemas fiscais n\xE3o est\xE3o dispon\xEDveis.", "Reinstale o runtime completo da plataforma."],
|
|
1148
|
+
SERVICOS_AUSENTES: ["runtime", "A configura\xE7\xE3o dos servi\xE7os fiscais n\xE3o est\xE1 dispon\xEDvel.", "Reinstale o runtime completo da plataforma."],
|
|
1149
|
+
CSC_AUSENTE: ["configuracao", "A opera\xE7\xE3o de NFC-e exige CSC.", "Informe id e valor do CSC na configura\xE7\xE3o sem registrar seu conte\xFAdo."],
|
|
1150
|
+
IDENTIDADE_DIVERGENTE: ["documento", "A identidade fiscal n\xE3o corresponde \xE0 configura\xE7\xE3o ou ao retorno.", "Confira emitente, UF, modelo, ambiente e chave do documento antes de continuar."],
|
|
1151
|
+
XML_SCHEMA_INVALIDO: ["validacao", "O XML n\xE3o atende aos schemas fiscais.", "Confira os campos obrigat\xF3rios e os tributos; a valida\xE7\xE3o de XML assinado deve ocorrer ap\xF3s a assinatura."],
|
|
1152
|
+
ASSINATURA_INVALIDA: ["assinatura", "A assinatura do XML n\xE3o foi validada.", "Confira o A1 correspondente e use o XML assinado sem altera\xE7\xF5es."],
|
|
1153
|
+
XML_NAO_ASSINADO: ["assinatura", "A opera\xE7\xE3o exige XML previamente assinado.", "Conclua a assinatura e a valida\xE7\xE3o antes de transmitir."],
|
|
1154
|
+
XML_ALTERADO: ["assinatura", "O XML assinado foi alterado.", "Use exatamente o XML assinado e persistido; n\xE3o edite seu conte\xFAdo."],
|
|
1155
|
+
RETORNO_INVALIDO: ["transmissao", "A identidade ou o protocolo do retorno fiscal \xE9 inv\xE1lido.", "Consulte a situa\xE7\xE3o fiscal antes de decidir repetir a opera\xE7\xE3o."],
|
|
1156
|
+
OPERACAO_FISCAL_FALHOU: ["operacao", "O motor n\xE3o conseguiu concluir a etapa fiscal.", "Confira a configura\xE7\xE3o da etapa, os dados do XML e as depend\xEAncias do runtime."],
|
|
1157
|
+
TLS_SERVIDOR_INVALIDO: ["transmissao", "A verifica\xE7\xE3o TLS do servi\xE7o fiscal falhou.", "Confira o servi\xE7o e a cadeia de confian\xE7a; mantenha a verifica\xE7\xE3o TLS habilitada."],
|
|
1158
|
+
CSC_INVALIDO: ["configuracao", "A configura\xE7\xE3o do CSC \xE9 inv\xE1lida.", "Confira id e valor do CSC sem registrar seu conte\xFAdo."],
|
|
1159
|
+
PLATAFORMA_NAO_SUPORTADA: ["runtime", "A plataforma n\xE3o \xE9 suportada.", "Execute em Windows ou Linux x64 com Node.js 22.12 ou superior."],
|
|
1160
|
+
RUNTIME_AUSENTE: ["runtime", "O motor fiscal n\xE3o foi encontrado.", "Instale o runtime da plataforma incluindo depend\xEAncias opcionais."],
|
|
1161
|
+
RUNTIME_INVALIDO: ["runtime", "O runtime fiscal \xE9 inv\xE1lido.", "Use um runtime distribu\xEDdo e compat\xEDvel com a plataforma."],
|
|
1162
|
+
RUNTIME_INCOMPLETO: ["runtime", "Falta um arquivo do runtime fiscal.", "Reinstale o runtime da plataforma."],
|
|
1163
|
+
RUNTIME_ALTERADO: ["runtime", "A integridade do runtime fiscal n\xE3o foi confirmada.", "Reinstale o runtime; n\xE3o desative a verifica\xE7\xE3o dos hashes."],
|
|
1164
|
+
CONTRATO_DIVERGENTE: ["runtime", "Cliente e motor usam contratos diferentes.", "Use vers\xF5es compat\xEDveis ou regenere e recompile ambos."],
|
|
1165
|
+
DOCUMENTO_INVALIDO: ["documento", "O documento cont\xE9m dados incompat\xEDveis com o modelo.", "Valide os campos e tipos do documento antes de gerar o XML."],
|
|
1166
|
+
OPERACAO_INVALIDA: ["operacao", "Os par\xE2metros da opera\xE7\xE3o s\xE3o inv\xE1lidos.", "Informe um objeto com os campos exigidos pela opera\xE7\xE3o."],
|
|
1167
|
+
OPERACAO_CANCELADA: ["operacao", "A opera\xE7\xE3o foi cancelada.", "Confira o estado do envio antes de decidir continuar."],
|
|
1168
|
+
TIMEOUT_MOTOR: ["runtime", "O motor excedeu o tempo de espera.", "Confira o estado do envio antes de repetir a opera\xE7\xE3o."],
|
|
1169
|
+
EMISSOR_FECHADO: ["operacao", "O emissor foi encerrado.", "Crie um novo emissor para continuar."],
|
|
1170
|
+
FALHA_INICIAR_MOTOR: ["runtime", "N\xE3o foi poss\xEDvel iniciar o motor fiscal.", "Confira a instala\xE7\xE3o e as permiss\xF5es de execu\xE7\xE3o do runtime."],
|
|
1171
|
+
FALHA_PIPE: ["runtime", "A comunica\xE7\xE3o com o motor foi interrompida.", "Confira a instala\xE7\xE3o e o estado do envio antes de continuar."],
|
|
1172
|
+
RESPOSTA_EXCESSIVA: ["runtime", "A resposta do motor excedeu o limite.", "Confira o tamanho da opera\xE7\xE3o e a vers\xE3o do runtime."],
|
|
1173
|
+
RESPOSTA_INVALIDA: ["runtime", "O motor devolveu uma resposta inv\xE1lida.", "Confira a compatibilidade do runtime e o estado do envio."],
|
|
1174
|
+
FALHA_MOTOR: ["runtime", "O motor encerrou sem uma resposta fiscal v\xE1lida.", "Confira a instala\xE7\xE3o e o estado do envio antes de continuar."],
|
|
1175
|
+
CERTIFICADO_AUSENTE: ["configuracao", "O certificado A1 n\xE3o est\xE1 dispon\xEDvel para esta opera\xE7\xE3o.", "Configure um A1 acess\xEDvel e sua senha sem registrar esses dados."],
|
|
1176
|
+
CERTIFICADO_EXCESSIVO: ["configuracao", "O certificado excede o limite de tamanho.", "Use um arquivo A1 de at\xE9 2 MiB."],
|
|
1177
|
+
REQUISICAO_INVALIDA: ["operacao", "Os dados n\xE3o puderam ser serializados ou excedem o limite.", "Use dados JSON e reduza o tamanho da opera\xE7\xE3o."],
|
|
1178
|
+
FILA_CHEIA: ["operacao", "A fila fiscal est\xE1 cheia.", "Aguarde a opera\xE7\xE3o atual terminar antes de iniciar outra."],
|
|
1179
|
+
XML_INVALIDO: ["documento", "O XML est\xE1 vazio, tem tipo inv\xE1lido ou excede o limite.", "Informe o XML como texto UTF-8 de at\xE9 2 MiB."],
|
|
1180
|
+
CHAVE_INVALIDA: ["documento", "A chave fiscal tem formato inv\xE1lido.", "Informe a chave como texto com 44 d\xEDgitos."],
|
|
1181
|
+
RECIBO_INVALIDO: ["documento", "O recibo tem formato inv\xE1lido.", "Informe o recibo como texto com 15 d\xEDgitos."],
|
|
1182
|
+
JUSTIFICATIVA_INVALIDA: ["documento", "A justificativa tem tamanho inv\xE1lido.", "Informe uma justificativa com 15 a 255 caracteres."],
|
|
1183
|
+
PROTOCOLO_INVALIDO: ["documento", "O protocolo tem formato inv\xE1lido.", "Informe o protocolo como texto com 15 d\xEDgitos."],
|
|
1184
|
+
OPERACAO_NAO_SUPORTADA: ["operacao", "A opera\xE7\xE3o n\xE3o \xE9 suportada para este modelo.", "Carta de corre\xE7\xE3o exige NF-e modelo 55."],
|
|
1185
|
+
CORRECAO_INVALIDA: ["documento", "Os dados da carta de corre\xE7\xE3o s\xE3o inv\xE1lidos.", "Confira a corre\xE7\xE3o de 15 a 1000 caracteres e a sequ\xEAncia de 1 a 20."],
|
|
1186
|
+
INUTILIZACAO_INVALIDA: ["documento", "A faixa de inutiliza\xE7\xE3o \xE9 inv\xE1lida.", "Confira ano, s\xE9rie e n\xFAmeros inicial e final."],
|
|
1187
|
+
FALHA_LOCAL: ["operacao", "N\xE3o foi poss\xEDvel concluir a opera\xE7\xE3o local.", "Confira a etapa e os tipos dos dados; n\xE3o registre o erro bruto."]
|
|
1188
|
+
};
|
|
1189
|
+
function etapaDaOperacao(comando) {
|
|
1190
|
+
return { gerarXml: "geracao", validar: "validacao", assinar: "assinatura", transmitir: "transmissao", consultar: "consulta", cancelar: "cancelamento", cartaCorrecao: "carta_correcao", inutilizar: "inutilizacao" }[comando] ?? "operacao";
|
|
1191
|
+
}
|
|
1192
|
+
function criarDiagnosticoErro(codigo, desconhecido = false, detalhes = {}) {
|
|
1193
|
+
const safeCode = Object.hasOwn(CATALOGO, codigo) ? codigo : "FALHA_LOCAL";
|
|
1194
|
+
const [defaultStage, mensagem, defaultAction] = CATALOGO[safeCode];
|
|
1195
|
+
const etapa = detalhes.etapa && ETAPAS.has(detalhes.etapa) ? detalhes.etapa : defaultStage;
|
|
1196
|
+
const campo = detalhes.campo && CAMPOS.has(detalhes.campo) ? detalhes.campo : void 0;
|
|
1197
|
+
const acoesConfig = { cnpj: "Informe o CNPJ como texto com 14 d\xEDgitos.", uf: "Informe a sigla da UF em letras mai\xFAsculas, por exemplo PE.", modelo: "Informe o n\xFAmero 55 para NF-e ou 65 para NFC-e, sem aspas.", ambiente: "Informe o n\xFAmero 2 para homologa\xE7\xE3o ou 1 para produ\xE7\xE3o, sem aspas.", timeoutMs: "Informe um n\xFAmero inteiro de 1 a 120000 milissegundos.", limiteFila: "Informe um n\xFAmero inteiro de 0 a 1000.", limiteBytes: "Informe um n\xFAmero inteiro de 256 a 8388608 bytes.", runtimeDirectory: "Informe o diret\xF3rio do runtime como texto n\xE3o vazio." };
|
|
1198
|
+
const acao = safeCode === "CONFIGURACAO_INVALIDA" && campo && Object.hasOwn(acoesConfig, campo) ? acoesConfig[campo] : defaultAction;
|
|
1199
|
+
const causa = Object.freeze({ codigo: safeCode, etapa, ...campo ? { campo } : {} });
|
|
1200
|
+
return Object.freeze({
|
|
1201
|
+
codigo: safeCode,
|
|
1202
|
+
etapa,
|
|
1203
|
+
...campo ? { campo } : {},
|
|
1204
|
+
mensagem,
|
|
1205
|
+
acao: desconhecido ? "Consulte a situa\xE7\xE3o fiscal antes de repetir. O envio pode ter ocorrido; n\xE3o retransmita automaticamente." : acao,
|
|
1206
|
+
statusEnvio: desconhecido ? "resultado_incerto" : "nao_enviado",
|
|
1207
|
+
causa
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
function adicionarDiagnosticoLimpeza(diagnostico) {
|
|
1211
|
+
const acao = diagnostico.statusEnvio === "resultado_incerto" ? "Encerre esta aplica\xE7\xE3o antes de iniciar outro motor e consulte a situa\xE7\xE3o fiscal antes de repetir qualquer envio." : "Encerre esta aplica\xE7\xE3o antes de iniciar outro motor. As pr\xF3ximas opera\xE7\xF5es deste emissor foram bloqueadas.";
|
|
1212
|
+
const limpeza = Object.freeze({ codigo: "ENCERRAMENTO_MOTOR_NAO_CONFIRMADO", etapa: "limpeza", mensagem: "N\xE3o foi poss\xEDvel confirmar o encerramento do motor fiscal.", acao });
|
|
1213
|
+
return Object.freeze({ ...diagnostico, acao, limpeza });
|
|
1214
|
+
}
|
|
1215
|
+
function diagnosticarResultado(resultado, comando) {
|
|
1216
|
+
const etapa = etapaDaOperacao(comando);
|
|
1217
|
+
if (!resultado || typeof resultado !== "object") return criarDiagnosticoErro("FALHA_LOCAL", true, { etapa });
|
|
1218
|
+
if (resultado.envioIniciado !== void 0 && typeof resultado.envioIniciado !== "boolean") return criarDiagnosticoErro("RESPOSTA_INVALIDA", true, { etapa });
|
|
1219
|
+
let statusEnvio = "resultado_incerto", codigo = "RESULTADO_INCERTO";
|
|
1220
|
+
let mensagem = "A situa\xE7\xE3o fiscal ainda n\xE3o foi confirmada.";
|
|
1221
|
+
let acao = "Consulte a situa\xE7\xE3o fiscal antes de repetir. N\xE3o retransmita automaticamente.";
|
|
1222
|
+
const local = comando === "gerarXml" && resultado.estado === "XML_GERADO" || comando === "validar" && resultado.estado === "VALIDADO" || comando === "assinar" && resultado.estado === "ASSINADO";
|
|
1223
|
+
const semEvento = resultado.envioIniciado === false || resultado.envioIniciado === void 0;
|
|
1224
|
+
const protocolo = typeof resultado.protocolo === "string" && /^\d{15}$/.test(resultado.protocolo);
|
|
1225
|
+
const chave = typeof resultado.chave === "string" && /^\d{44}$/.test(resultado.chave);
|
|
1226
|
+
const autorizacao = (comando === "transmitir" || comando === "consultar") && resultado.estado === "AUTORIZADO" && [100, 150].includes(resultado.cStat) && protocolo && chave && (comando === "consultar" || resultado.envioIniciado !== false);
|
|
1227
|
+
const evento = comando === "cancelar" && resultado.estado === "CANCELADO" && [135, 136].includes(resultado.cStat) && chave || comando === "cartaCorrecao" && resultado.estado === "EVENTO_REGISTRADO" && resultado.cStat === 135 && chave || comando === "inutilizar" && resultado.estado === "INUTILIZADO" && resultado.cStat === 102;
|
|
1228
|
+
if (local && resultado.sucesso === true && semEvento) {
|
|
1229
|
+
statusEnvio = "nao_enviado";
|
|
1230
|
+
codigo = "ETAPA_LOCAL_CONCLUIDA";
|
|
1231
|
+
mensagem = "A etapa local foi conclu\xEDda; esta opera\xE7\xE3o n\xE3o transmitiu a nota.";
|
|
1232
|
+
acao = "Confira o resultado antes de seguir para a pr\xF3xima etapa.";
|
|
1233
|
+
} else if (resultado.sucesso === true && (autorizacao || evento && protocolo && resultado.envioIniciado !== false)) {
|
|
1234
|
+
statusEnvio = "autorizado";
|
|
1235
|
+
codigo = "OPERACAO_CONFIRMADA";
|
|
1236
|
+
mensagem = { AUTORIZADO: "A nota foi autorizada.", CANCELADO: "O cancelamento foi confirmado.", EVENTO_REGISTRADO: "O evento foi registrado.", INUTILIZADO: "A faixa foi inutilizada." }[resultado.estado];
|
|
1237
|
+
acao = "Guarde o resultado fiscal. N\xE3o repita esta opera\xE7\xE3o.";
|
|
1238
|
+
} else if (resultado.sucesso === false && (comando === "consultar" || resultado.envioIniciado !== false) && ["transmitir", "consultar", "cancelar", "cartaCorrecao", "inutilizar"].includes(comando) && Number.isInteger(resultado.cStat) && Number(resultado.cStat) > 0 && Number(resultado.cStat) <= 9999 && (resultado.estado === "DENEGADO" && [110, 301, 302].includes(resultado.cStat) || resultado.estado === "REJEITADO" && ![100, 150, 110, 301, 302, 101, 151, 155, 103, 104, 105, 108, 109, 204, 539, 573, 135, 136, 102, 217].includes(resultado.cStat))) {
|
|
1239
|
+
statusEnvio = "rejeitado";
|
|
1240
|
+
codigo = resultado.estado === "DENEGADO" ? "NOTA_DENEGADA" : "OPERACAO_REJEITADA";
|
|
1241
|
+
mensagem = resultado.estado === "DENEGADO" ? "A nota foi denegada." : "A opera\xE7\xE3o foi rejeitada.";
|
|
1242
|
+
acao = resultado.estado === "DENEGADO" ? "Consulte a situa\xE7\xE3o fiscal e a orienta\xE7\xE3o respons\xE1vel antes de usar a numera\xE7\xE3o." : "Confira o cStat e revise os dados antes de decidir uma nova tentativa.";
|
|
1243
|
+
} else if (resultado.estado === "ERRO" && resultado.envioIniciado === false) {
|
|
1244
|
+
if (typeof resultado.codigo === "string" && Object.hasOwn(CATALOGO, resultado.codigo)) return criarDiagnosticoErro(resultado.codigo, false, { etapa });
|
|
1245
|
+
statusEnvio = "nao_enviado";
|
|
1246
|
+
codigo = "FALHA_ANTES_ENVIO";
|
|
1247
|
+
mensagem = "O motor informou falha antes de iniciar o envio desta opera\xE7\xE3o.";
|
|
1248
|
+
acao = "Revise a configura\xE7\xE3o e os dados antes de continuar.";
|
|
1249
|
+
}
|
|
1250
|
+
const cStat = Number.isInteger(resultado.cStat) && Number(resultado.cStat) > 0 && Number(resultado.cStat) <= 9999 ? resultado.cStat : void 0;
|
|
1251
|
+
return Object.freeze({ codigo, etapa, mensagem, acao, statusEnvio, ...cStat !== void 0 ? { cStat } : {} });
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1089
1254
|
// src/types.ts
|
|
1090
1255
|
var ErroAcbrNode = class extends Error {
|
|
1091
|
-
constructor(codigo, mensagem, resultadoDesconhecido = false, xmlBase64) {
|
|
1256
|
+
constructor(codigo, mensagem, resultadoDesconhecido = false, xmlBase64, detalhes) {
|
|
1092
1257
|
super(mensagem);
|
|
1093
1258
|
this.codigo = codigo;
|
|
1094
1259
|
this.resultadoDesconhecido = resultadoDesconhecido;
|
|
1095
1260
|
this.xmlBase64 = xmlBase64;
|
|
1261
|
+
this.#diagnostico = criarDiagnosticoErro(codigo, resultadoDesconhecido, detalhes);
|
|
1262
|
+
Object.defineProperty(this, "xmlBase64", { value: xmlBase64, enumerable: false });
|
|
1263
|
+
Object.defineProperty(this, "cause", { value: this.diagnostico.causa, enumerable: false });
|
|
1096
1264
|
}
|
|
1097
1265
|
name = "ErroAcbrNode";
|
|
1266
|
+
#diagnostico;
|
|
1267
|
+
get diagnostico() {
|
|
1268
|
+
return this.#diagnostico;
|
|
1269
|
+
}
|
|
1270
|
+
/** Acrescenta apenas o aviso estático de limpeza, preservando o erro da operação. */
|
|
1271
|
+
anotarFalhaLimpeza() {
|
|
1272
|
+
this.#diagnostico = adicionarDiagnosticoLimpeza(this.#diagnostico);
|
|
1273
|
+
}
|
|
1274
|
+
toJSON() {
|
|
1275
|
+
return this.diagnostico;
|
|
1276
|
+
}
|
|
1098
1277
|
};
|
|
1099
1278
|
|
|
1100
1279
|
// src/runtime.ts
|
|
1101
1280
|
var STATES = /* @__PURE__ */ new Set(["DENEGADO", "XML_GERADO", "VALIDADO", "ASSINADO", "AUTORIZADO", "REJEITADO", "PENDENTE", "INDETERMINADO", "CONSULTADO", "CANCELADO", "EVENTO_REGISTRADO", "INUTILIZADO", "ERRO"]);
|
|
1102
1281
|
var UFS = new Set("AC AL AP AM BA CE DF ES GO MA MT MS MG PA PB PR PE PI RJ RN RS RO RR SC SP SE TO".split(" "));
|
|
1103
1282
|
var MAX_BYTES = 8 * 1024 * 1024;
|
|
1283
|
+
var MAX_TEARDOWN_MS = 1e3;
|
|
1104
1284
|
var plain = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1105
1285
|
var validContract = (v) => plain(v) && Number.isInteger(v.sourceRevision) && Number(v.sourceRevision) > 0 && typeof v.contractHash === "string" && /^[a-f0-9]{64}$/.test(v.contractHash);
|
|
1106
|
-
var fail = (code, message, req, unknown = false) => new ErroAcbrNode(code, message, unknown, req?.comando === "transmitir" ? req.xmlBase64 : void 0);
|
|
1286
|
+
var fail = (code, message, req, unknown = false, detalhes) => new ErroAcbrNode(code, message, unknown, req?.comando === "transmitir" ? req.xmlBase64 : void 0, { ...req ? { etapa: etapaDaOperacao(req.comando) } : {}, ...detalhes });
|
|
1107
1287
|
function confined(root, relative3) {
|
|
1108
1288
|
const file = path.resolve(root, relative3), part = path.relative(root, file);
|
|
1109
1289
|
if (!relative3 || path.isAbsolute(relative3) || part === ".." || part.startsWith(".." + path.sep)) throw fail("RUNTIME_INVALIDO", "Caminho inv\xE1lido no runtime.");
|
|
@@ -1146,26 +1326,48 @@ async function resolveRuntime(directory, contract) {
|
|
|
1146
1326
|
return { directory: root, executable: confined(root, m.executable) };
|
|
1147
1327
|
}
|
|
1148
1328
|
function criarEmissor(options) {
|
|
1149
|
-
|
|
1329
|
+
const configFail = (campo) => fail("CONFIGURACAO_INVALIDA", "Informe campos e tipos v\xE1lidos na configura\xE7\xE3o.", void 0, false, { etapa: "configuracao", campo });
|
|
1330
|
+
if (!plain(options)) throw configFail();
|
|
1331
|
+
if (typeof options.cnpj !== "string" || !/^\d{14}$/.test(options.cnpj)) throw configFail("cnpj");
|
|
1332
|
+
if (typeof options.uf !== "string" || !UFS.has(options.uf)) throw configFail("uf");
|
|
1333
|
+
if (![55, 65].includes(options.modelo)) throw configFail("modelo");
|
|
1334
|
+
if (![1, 2].includes(options.ambiente)) throw configFail("ambiente");
|
|
1150
1335
|
if (!validContract(options.contrato)) throw fail("CONTRATO_INVALIDO", "Contrato gerado inv\xE1lido.");
|
|
1151
1336
|
const cert = options.certificado;
|
|
1152
|
-
if (cert && (typeof cert.senha !== "string" || !!cert.pfx === !!cert.arquivo || cert.pfx && !(cert.pfx instanceof Uint8Array))) throw fail("CERTIFICADO_INVALIDO", "Informe A1 em pfx ou arquivo e sua senha.");
|
|
1153
|
-
if (options.csc && (!/^\d{1,6}$/.test(options.csc.id) || typeof options.csc.valor !== "string" || !options.csc.valor || options.csc.valor.length > 256)) throw fail("CSC_INVALIDO", "CSC inv\xE1lido.");
|
|
1337
|
+
if (cert !== void 0 && (!plain(cert) || typeof cert.senha !== "string" || !!cert.pfx === !!cert.arquivo || cert.pfx !== void 0 && !(cert.pfx instanceof Uint8Array) || cert.arquivo !== void 0 && (typeof cert.arquivo !== "string" || !cert.arquivo))) throw fail("CERTIFICADO_INVALIDO", "Informe A1 em pfx ou arquivo e sua senha.", void 0, false, { campo: "certificado" });
|
|
1338
|
+
if (options.csc !== void 0 && (!plain(options.csc) || typeof options.csc.id !== "string" || !/^\d{1,6}$/.test(options.csc.id) || typeof options.csc.valor !== "string" || !options.csc.valor || options.csc.valor.length > 256)) throw fail("CSC_INVALIDO", "CSC inv\xE1lido.", void 0, false, { campo: "csc" });
|
|
1339
|
+
if (options.runtimeDirectory !== void 0 && (typeof options.runtimeDirectory !== "string" || !options.runtimeDirectory)) throw configFail("runtimeDirectory");
|
|
1340
|
+
if (options.validaDocumento !== void 0 && typeof options.validaDocumento !== "function" || options.criaProcesso !== void 0 && typeof options.criaProcesso !== "function") throw configFail();
|
|
1154
1341
|
const cfg = { cnpj: options.cnpj, uf: options.uf, modelo: options.modelo, ambiente: options.ambiente, csc: options.csc ? { ...options.csc } : void 0 };
|
|
1155
1342
|
const certificate = cert ? { ...cert, pfx: cert.pfx ? Buffer.from(cert.pfx) : void 0 } : void 0;
|
|
1156
1343
|
const contract = { ...options.contrato };
|
|
1157
1344
|
const timeout = options.timeoutMs ?? 12e4, capacity = options.limiteFila ?? 32, bytesLimit = options.limiteBytes ?? MAX_BYTES;
|
|
1158
|
-
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 12e4
|
|
1345
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 12e4) throw configFail("timeoutMs");
|
|
1346
|
+
if (!Number.isInteger(capacity) || capacity < 0 || capacity > 1e3) throw configFail("limiteFila");
|
|
1347
|
+
if (!Number.isInteger(bytesLimit) || bytesLimit < 256 || bytesLimit > MAX_BYTES) throw configFail("limiteBytes");
|
|
1159
1348
|
const launch = options.criaProcesso ?? ((exe, args, config) => spawn2(exe, args, { ...config, stdio: ["pipe", "pipe", "pipe"] }));
|
|
1160
|
-
let runtimePromise, closed = false, current;
|
|
1349
|
+
let runtimePromise, closed = false, teardownFailed = false, current;
|
|
1161
1350
|
const queue = [];
|
|
1351
|
+
const closedError = () => {
|
|
1352
|
+
const error = fail("EMISSOR_FECHADO", "Emissor encerrado.");
|
|
1353
|
+
if (teardownFailed) error.anotarFalhaLimpeza();
|
|
1354
|
+
return error;
|
|
1355
|
+
};
|
|
1356
|
+
const rejectQueue = () => {
|
|
1357
|
+
for (const item of queue.splice(0)) {
|
|
1358
|
+
item.signal?.removeEventListener("abort", item.abort);
|
|
1359
|
+
item.bytes.fill(0);
|
|
1360
|
+
item.reject(closedError());
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1162
1363
|
async function execute2(item) {
|
|
1163
1364
|
if (closed || item.signal?.aborted) throw fail("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada antes da execu\xE7\xE3o.");
|
|
1164
1365
|
const runtime = await (runtimePromise ??= resolveRuntime(options.runtimeDirectory, contract));
|
|
1165
1366
|
if (closed || item.signal?.aborted) throw fail("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada antes da execu\xE7\xE3o.");
|
|
1166
|
-
await new Promise((done) => {
|
|
1367
|
+
return await new Promise((done, rejectExecution) => {
|
|
1167
1368
|
let stopError;
|
|
1168
1369
|
let child, finished = false, started = false, sending = false, total = 0, rest = Buffer.alloc(0), result;
|
|
1370
|
+
let teardownTimer;
|
|
1169
1371
|
let finishCurrent;
|
|
1170
1372
|
const doneCurrent = new Promise((r) => {
|
|
1171
1373
|
finishCurrent = r;
|
|
@@ -1175,18 +1377,54 @@ function criarEmissor(options) {
|
|
|
1175
1377
|
if (finished) return;
|
|
1176
1378
|
finished = true;
|
|
1177
1379
|
clearTimeout(timer);
|
|
1380
|
+
clearTimeout(teardownTimer);
|
|
1178
1381
|
item.signal?.removeEventListener("abort", abort);
|
|
1179
1382
|
item.bytes.fill(0);
|
|
1180
1383
|
if (current?.done === doneCurrent) current = void 0;
|
|
1181
1384
|
finishCurrent();
|
|
1182
|
-
error ?
|
|
1183
|
-
|
|
1385
|
+
error ? rejectExecution(error) : done(result);
|
|
1386
|
+
};
|
|
1387
|
+
const detachOwnedChild = () => {
|
|
1388
|
+
for (const pipe of [child?.stdin, child?.stdout, child?.stderr]) {
|
|
1389
|
+
try {
|
|
1390
|
+
pipe?.destroy();
|
|
1391
|
+
} catch {
|
|
1392
|
+
}
|
|
1393
|
+
try {
|
|
1394
|
+
pipe?.unref?.();
|
|
1395
|
+
} catch {
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
try {
|
|
1399
|
+
child?.unref();
|
|
1400
|
+
} catch {
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
const teardownFallback = () => {
|
|
1404
|
+
if (finished) return;
|
|
1405
|
+
stopError ??= fail("FALHA_MOTOR", "O motor n\xE3o foi encerrado.", item.req, uncertain());
|
|
1406
|
+
stopError.anotarFalhaLimpeza();
|
|
1407
|
+
teardownFailed = true;
|
|
1408
|
+
closed = true;
|
|
1409
|
+
rejectQueue();
|
|
1410
|
+
certificate?.pfx?.fill(0);
|
|
1411
|
+
finish(stopError);
|
|
1412
|
+
detachOwnedChild();
|
|
1184
1413
|
};
|
|
1185
1414
|
const stop = (code, message) => {
|
|
1186
1415
|
if (finished || stopError) return;
|
|
1187
1416
|
stopError = fail(code, message, item.req, uncertain());
|
|
1188
|
-
if (child?.pid)
|
|
1189
|
-
|
|
1417
|
+
if (!child?.pid) {
|
|
1418
|
+
finish(stopError);
|
|
1419
|
+
detachOwnedChild();
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
teardownTimer = setTimeout(teardownFallback, MAX_TEARDOWN_MS);
|
|
1423
|
+
try {
|
|
1424
|
+
if (child.kill("SIGKILL") !== true) teardownFallback();
|
|
1425
|
+
} catch {
|
|
1426
|
+
teardownFallback();
|
|
1427
|
+
}
|
|
1190
1428
|
};
|
|
1191
1429
|
const abort = () => stop("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada; confira a situa\xE7\xE3o fiscal quando o envio foi iniciado.");
|
|
1192
1430
|
const timer = setTimeout(() => stop("TIMEOUT_MOTOR", "O motor excedeu o prazo; consulte a situa\xE7\xE3o antes de transmitir novamente."), timeout);
|
|
@@ -1213,8 +1451,17 @@ function criarEmissor(options) {
|
|
|
1213
1451
|
child.on("spawn", () => {
|
|
1214
1452
|
started = true;
|
|
1215
1453
|
});
|
|
1216
|
-
child.on("error", () =>
|
|
1454
|
+
child.on("error", () => {
|
|
1455
|
+
if (finished) return;
|
|
1456
|
+
if (stopError) {
|
|
1457
|
+
teardownFallback();
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
stop("FALHA_INICIAR_MOTOR", "N\xE3o foi poss\xEDvel iniciar o motor.");
|
|
1461
|
+
});
|
|
1217
1462
|
child.stdin.on("error", () => stop("FALHA_PIPE", "N\xE3o foi poss\xEDvel comunicar com o motor."));
|
|
1463
|
+
child.stdout.on("error", () => stop("FALHA_PIPE", "N\xE3o foi poss\xEDvel comunicar com o motor."));
|
|
1464
|
+
child.stderr.on("error", () => stop("FALHA_PIPE", "N\xE3o foi poss\xEDvel comunicar com o motor."));
|
|
1218
1465
|
child.stderr.on("data", () => {
|
|
1219
1466
|
});
|
|
1220
1467
|
child.stdout.on("data", (chunk) => {
|
|
@@ -1240,7 +1487,9 @@ function criarEmissor(options) {
|
|
|
1240
1487
|
if (result || frame.versao !== 1 || frame.sourceRevision !== contract.sourceRevision || frame.contractHash !== contract.contractHash || typeof frame.sucesso !== "boolean" || !STATES.has(String(frame.estado))) throw Error();
|
|
1241
1488
|
if (frame.xmlBase64 !== void 0 && (typeof frame.xmlBase64 !== "string" || !frame.xmlBase64.length || Buffer.from(frame.xmlBase64, "base64").toString("base64") !== frame.xmlBase64)) throw Error();
|
|
1242
1489
|
if (frame.cStat !== void 0 && (!Number.isInteger(frame.cStat) || Number(frame.cStat) < 1 || Number(frame.cStat) > 9999)) throw Error();
|
|
1243
|
-
|
|
1490
|
+
if (frame.envioIniciado !== void 0 && typeof frame.envioIniciado !== "boolean" || sending && frame.envioIniciado === false) throw Error();
|
|
1491
|
+
const fiscal = frame;
|
|
1492
|
+
result = { ...fiscal, diagnostico: diagnosticarResultado(sending ? { ...fiscal, envioIniciado: true } : fiscal, item.req.comando) };
|
|
1244
1493
|
} catch {
|
|
1245
1494
|
stop("RESPOSTA_INVALIDA", "O motor retornou uma resposta inv\xE1lida.");
|
|
1246
1495
|
return;
|
|
@@ -1271,7 +1520,8 @@ function criarEmissor(options) {
|
|
|
1271
1520
|
busy = true;
|
|
1272
1521
|
item.signal?.removeEventListener("abort", item.abort);
|
|
1273
1522
|
try {
|
|
1274
|
-
await execute2(item);
|
|
1523
|
+
const result = await execute2(item);
|
|
1524
|
+
item.resolve(result);
|
|
1275
1525
|
} catch (e) {
|
|
1276
1526
|
item.bytes.fill(0);
|
|
1277
1527
|
item.reject(e);
|
|
@@ -1281,7 +1531,11 @@ function criarEmissor(options) {
|
|
|
1281
1531
|
}
|
|
1282
1532
|
}
|
|
1283
1533
|
async function request(command, data, operation = {}) {
|
|
1284
|
-
|
|
1534
|
+
const rawOperation = operation;
|
|
1535
|
+
if (!plain(rawOperation)) throw fail("OPERACAO_INVALIDA", "Informe op\xE7\xF5es v\xE1lidas para a opera\xE7\xE3o.", void 0, false, { etapa: etapaDaOperacao(command), campo: "operacao" });
|
|
1536
|
+
const signal = operation.signal;
|
|
1537
|
+
if (signal !== void 0 && (!plain(signal) || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function" || typeof signal.removeEventListener !== "function")) throw fail("OPERACAO_INVALIDA", "Informe op\xE7\xF5es v\xE1lidas para a opera\xE7\xE3o.", void 0, false, { etapa: etapaDaOperacao(command), campo: "signal" });
|
|
1538
|
+
if (closed) throw closedError();
|
|
1285
1539
|
if (operation.signal?.aborted) throw fail("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada antes da execu\xE7\xE3o.");
|
|
1286
1540
|
if (["assinar", "transmitir", "consultar", "cancelar", "cartaCorrecao", "inutilizar"].includes(command) && !certificate) throw fail("CERTIFICADO_AUSENTE", "A opera\xE7\xE3o exige um certificado A1.");
|
|
1287
1541
|
if (command === "gerarXml") options.validaDocumento?.(data.documento);
|
|
@@ -1307,7 +1561,7 @@ function criarEmissor(options) {
|
|
|
1307
1561
|
return new Promise((resolve5, reject) => {
|
|
1308
1562
|
if (closed) {
|
|
1309
1563
|
bytes.fill(0);
|
|
1310
|
-
reject(
|
|
1564
|
+
reject(closedError());
|
|
1311
1565
|
return;
|
|
1312
1566
|
}
|
|
1313
1567
|
if (busy && queue.length >= capacity) {
|
|
@@ -1332,14 +1586,17 @@ function criarEmissor(options) {
|
|
|
1332
1586
|
});
|
|
1333
1587
|
}
|
|
1334
1588
|
const xmlData = (xml) => {
|
|
1335
|
-
if (typeof xml !== "string" || !xml.trim() || Buffer.byteLength(xml, "utf8") > 2 * 1024 * 1024) throw fail("XML_INVALIDO", "XML vazio ou acima do limite.");
|
|
1589
|
+
if (typeof xml !== "string" || !xml.trim() || Buffer.byteLength(xml, "utf8") > 2 * 1024 * 1024) throw fail("XML_INVALIDO", "XML vazio ou acima do limite.", void 0, false, { campo: "xml" });
|
|
1336
1590
|
return { xmlBase64: Buffer.from(xml, "utf8").toString("base64") };
|
|
1337
1591
|
};
|
|
1338
1592
|
const checkKey = (key) => {
|
|
1339
|
-
if (!/^\d{44}$/.test(key)) throw fail("CHAVE_INVALIDA", "Chave fiscal inv\xE1lida.");
|
|
1593
|
+
if (typeof key !== "string" || !/^\d{44}$/.test(key)) throw fail("CHAVE_INVALIDA", "Chave fiscal inv\xE1lida.", void 0, false, { campo: "chave" });
|
|
1340
1594
|
};
|
|
1341
1595
|
const reason = (value) => {
|
|
1342
|
-
if (typeof value !== "string" || value.length < 15 || value.length > 255) throw fail("JUSTIFICATIVA_INVALIDA", "Justificativa deve ter de 15 a 255 caracteres.");
|
|
1596
|
+
if (typeof value !== "string" || value.length < 15 || value.length > 255) throw fail("JUSTIFICATIVA_INVALIDA", "Justificativa deve ter de 15 a 255 caracteres.", void 0, false, { campo: "justificativa" });
|
|
1597
|
+
};
|
|
1598
|
+
const checkObject = (data, campo) => {
|
|
1599
|
+
if (!plain(data)) throw fail("OPERACAO_INVALIDA", "Informe os par\xE2metros da opera\xE7\xE3o como objeto.", void 0, false, { campo });
|
|
1343
1600
|
};
|
|
1344
1601
|
return {
|
|
1345
1602
|
gerarXml: (documento, op) => request("gerarXml", { documento }, op),
|
|
@@ -1347,39 +1604,43 @@ function criarEmissor(options) {
|
|
|
1347
1604
|
assinar: (xml, op) => request("assinar", xmlData(xml), op),
|
|
1348
1605
|
transmitir: (xml, op) => request("transmitir", xmlData(xml), op),
|
|
1349
1606
|
consultar: (consulta, op) => {
|
|
1607
|
+
checkObject(consulta, "consulta");
|
|
1350
1608
|
checkKey(consulta.chave);
|
|
1351
|
-
if (consulta.recibo && !/^\d{15}$/.test(consulta.recibo)) throw fail("RECIBO_INVALIDO", "Recibo inv\xE1lido.");
|
|
1609
|
+
if (consulta.recibo !== void 0 && (typeof consulta.recibo !== "string" || !/^\d{15}$/.test(consulta.recibo))) throw fail("RECIBO_INVALIDO", "Recibo inv\xE1lido.", void 0, false, { campo: "recibo" });
|
|
1352
1610
|
return request("consultar", consulta, op);
|
|
1353
1611
|
},
|
|
1354
1612
|
cancelar: (data, op) => {
|
|
1613
|
+
checkObject(data, "cancelamento");
|
|
1355
1614
|
checkKey(data.chave);
|
|
1356
1615
|
reason(data.justificativa);
|
|
1357
|
-
if (!/^\d{15}$/.test(data.protocolo)) throw fail("PROTOCOLO_INVALIDO", "Protocolo inv\xE1lido.");
|
|
1616
|
+
if (typeof data.protocolo !== "string" || !/^\d{15}$/.test(data.protocolo)) throw fail("PROTOCOLO_INVALIDO", "Protocolo inv\xE1lido.", void 0, false, { campo: "protocolo" });
|
|
1358
1617
|
return request("cancelar", data, op);
|
|
1359
1618
|
},
|
|
1360
1619
|
cartaCorrecao: (data, op) => {
|
|
1620
|
+
checkObject(data, "cartaCorrecao");
|
|
1361
1621
|
if (cfg.modelo !== 55) throw fail("OPERACAO_NAO_SUPORTADA", "Carta de corre\xE7\xE3o exige NF-e modelo 55.");
|
|
1362
1622
|
checkKey(data.chave);
|
|
1363
|
-
if (typeof data.correcao !== "string" || data.correcao.length < 15 || data.correcao.length > 1e3 || !Number.isInteger(data.sequenciaEvento ?? 1) || (data.sequenciaEvento ?? 1) < 1 || (data.sequenciaEvento ?? 1) > 20) throw fail("CORRECAO_INVALIDA", "Dados da carta de corre\xE7\xE3o inv\xE1lidos.");
|
|
1623
|
+
if (typeof data.correcao !== "string" || data.correcao.length < 15 || data.correcao.length > 1e3 || !Number.isInteger(data.sequenciaEvento ?? 1) || (data.sequenciaEvento ?? 1) < 1 || (data.sequenciaEvento ?? 1) > 20) throw fail("CORRECAO_INVALIDA", "Dados da carta de corre\xE7\xE3o inv\xE1lidos.", void 0, false, { campo: "cartaCorrecao" });
|
|
1364
1624
|
return request("cartaCorrecao", { ...data, sequenciaEvento: data.sequenciaEvento ?? 1 }, op);
|
|
1365
1625
|
},
|
|
1366
1626
|
inutilizar: (data, op) => {
|
|
1627
|
+
checkObject(data, "inutilizacao");
|
|
1367
1628
|
reason(data.justificativa);
|
|
1368
|
-
if (!Number.isInteger(data.ano) || data.ano < 0 || data.ano > 99 || !Number.isInteger(data.serie) || data.serie < 0 || data.serie > 999 || !Number.isInteger(data.numeroInicial) || data.numeroInicial < 1 || !Number.isInteger(data.numeroFinal) || data.numeroFinal < data.numeroInicial || data.numeroFinal > 999999999) throw fail("INUTILIZACAO_INVALIDA", "Faixa de inutiliza\xE7\xE3o inv\xE1lida.");
|
|
1629
|
+
if (!Number.isInteger(data.ano) || data.ano < 0 || data.ano > 99 || !Number.isInteger(data.serie) || data.serie < 0 || data.serie > 999 || !Number.isInteger(data.numeroInicial) || data.numeroInicial < 1 || !Number.isInteger(data.numeroFinal) || data.numeroFinal < data.numeroInicial || data.numeroFinal > 999999999) throw fail("INUTILIZACAO_INVALIDA", "Faixa de inutiliza\xE7\xE3o inv\xE1lida.", void 0, false, { campo: "inutilizacao" });
|
|
1369
1630
|
return request("inutilizar", { inutilizacao: { ano: data.ano, serie: data.serie, numeroInicial: data.numeroInicial, numeroFinal: data.numeroFinal }, justificativa: data.justificativa }, op);
|
|
1370
1631
|
},
|
|
1371
1632
|
async fechar() {
|
|
1372
|
-
if (closed)
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
item.signal?.removeEventListener("abort", item.abort);
|
|
1376
|
-
item.bytes.fill(0);
|
|
1377
|
-
item.reject(fail("EMISSOR_FECHADO", "Emissor encerrado."));
|
|
1633
|
+
if (!closed) {
|
|
1634
|
+
closed = true;
|
|
1635
|
+
rejectQueue();
|
|
1378
1636
|
}
|
|
1379
1637
|
const running = current;
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1638
|
+
try {
|
|
1639
|
+
running?.stop();
|
|
1640
|
+
if (running) await running.done;
|
|
1641
|
+
} finally {
|
|
1642
|
+
certificate?.pfx?.fill(0);
|
|
1643
|
+
}
|
|
1383
1644
|
}
|
|
1384
1645
|
};
|
|
1385
1646
|
}
|
|
@@ -1639,7 +1900,16 @@ ${offlineChecks.length} verifica\xE7\xF5es offline do motor aprovadas; nenhuma t
|
|
|
1639
1900
|
return 1;
|
|
1640
1901
|
}
|
|
1641
1902
|
}
|
|
1642
|
-
|
|
1903
|
+
function isDirectInvocation() {
|
|
1904
|
+
const entry = process.argv[1];
|
|
1905
|
+
if (!entry) return false;
|
|
1906
|
+
try {
|
|
1907
|
+
return realpathSync(resolve4(entry)) === realpathSync(fileURLToPath3(import.meta.url));
|
|
1908
|
+
} catch {
|
|
1909
|
+
return false;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
if (isDirectInvocation()) {
|
|
1643
1913
|
main().then((code) => {
|
|
1644
1914
|
process.exitCode = code;
|
|
1645
1915
|
});
|