# tsreport-sdk [English](./README.md) | [日本語](./README.ja.md) | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md) | [한국어](./README.ko.md) | [Tiếng Việt](./README.vi.md) | [ไทย](./README.th.md) | [Bahasa Indonesia](./README.id.md) | [Deutsch](./README.de.md) | [Français](./README.fr.md) | [Español](./README.es.md) | Português | [العربية](./README.ar.md) | [עברית](./README.he.md) **Um cliente para solicitar a impressão de PDFs a um servidor de relatórios e receber os resultados. Zero dependências de pacotes em tempo de execução. Destinado ao uso no lado do servidor (Node.js).** O tsreport-sdk assume toda a comunicação com o servidor de API de impressão externo fornecido pelo tsreport-editor. A autenticação via OAuth 2.0, o envio de trabalhos de impressão, a espera até a conclusão, o download do PDF e até a obtenção de materiais para pré-visualização no navegador podem ser tratados como uma única classe tipada. A obtenção do token, o gerenciamento de sua expiração e a reobtenção em caso de invalidação são todos concluídos internamente pela biblioteca, então tudo o que o lado utilizador precisa escrever é "em qual template enviar quais dados". ## Arquitetura: existem dois limites de autenticação O sistema que incorpora este SDK é composto por três camadas. **Primeiro, entenda a visão geral.** A maioria dos mal-entendidos sobre este SDK surge da confusão entre os dois limites de autenticação existentes. ```mermaid flowchart LR browser["Navegador\ncreateEndpointConnector\n(não possui credenciais)"] app["Seu servidor de aplicação\ncreatePreviewEndpoint + TsreportClient\n(clientSecret existe apenas aqui)"] editor["Servidor de API de impressão\n(tsreport-editor)"] browser -->|"Limite de autenticação A: autenticação de sessão\n(implementada por você mesmo)"| app app -->|"Limite de autenticação B: OAuth 2.0\n(processado automaticamente pelo SDK)"| editor ``` | Camada | Componente do SDK utilizado | Papel na autenticação | | --- | --- | --- | | Navegador | `createEndpointConnector()` | **Lado que recebe** a autenticação do limite A. Apenas envia cookies de sessão etc., sem possuir nenhuma credencial | | Seu servidor de aplicação | `createPreviewEndpoint()`+`TsreportClient` | **Lado que verifica** o limite A (essa verificação é implementação própria da aplicação). O limite B é delegado ao SDK. `clientSecret` existe apenas nesta camada | | Servidor de API de impressão | (fornecido pelo tsreport-editor) | Verifica o limite B | **O SDK cuida apenas do limite B.** A obtenção do token, o gerenciamento de expiração e o reenvio via `clientId`/`clientSecret` são todos processados internamente pelo `TsreportClient`. Por outro lado, **o mecanismo para julgar o limite A — "quem está operando agora, e essa pessoa tem permissão para ver este relatório" — não existe em nenhum lugar deste SDK.** O `createPreviewEndpoint()` é um retransmissor que não realiza nenhuma autorização. Login, gerenciamento de sessão e verificação de permissões são mecanismos que a aplicação já possui, e você **deve sempre colocá-los na frente do endpoint por conta própria**. Se isso for omitido, qualquer pessoa que conseguir acessar a URL poderá ler o relatório e os materiais. A "verificação de sessão" e o `requireAppUser` que aparecem nos exemplos a seguir não são decorativos. **É código próprio do limite A que deve obrigatoriamente ser implementado pela aplicação.** ## O que este pacote faz e o que não faz Este pacote é responsável **apenas pela comunicação**. - **O que faz** — autenticação (OAuth 2.0 client credentials), chamadas à API de impressão, polling do estado do trabalho, obtenção do PDF, obtenção de materiais de pré-visualização, fornecimento de um endpoint de retransmissão a ser colocado no servidor de aplicação - **Onde funciona** — `TsreportClient` e `tsreport-sdk/server` são exclusivos para o lado do servidor. O único que pode ser usado no navegador é o `createEndpointConnector()`, que não possui credenciais - **O que não faz** — layout de relatórios ou geração de PDF (responsabilidade do `tsreport-core`), renderização de tela da pré-visualização (responsabilidade do `tsreport-react`) e **autenticação/autorização do usuário** (limite A. Responsabilidade da aplicação do lado utilizador) Há também duas promessas de design. **Zero dependências de pacotes em tempo de execução** (não há `dependencies` em `package.json`) e **nenhuma leitura de variáveis de ambiente**. Tanto o destino da conexão quanto as credenciais são todos recebidos como argumentos explícitos, então o comportamento não muda independentemente do ambiente onde for colocado. ## Instalação ```sh npm install tsreport-sdk ``` Funciona com Node.js 18 ou superior. Internamente usa apenas `fetch` e Web Streams, sem depender de APIs específicas do Node.js. **Use esta biblioteca no lado do servidor.** O `TsreportClient`, que é o núcleo, requer `clientSecret`, então executá-lo no navegador resultaria na distribuição da chave secreta para todos os usuários. Se você quiser lidar com relatórios a partir do navegador, adote a estrutura de três camadas descrita em "Pré-visualização a partir do navegador" mais adiante, usando apenas o `createEndpointConnector()`, que não possui credenciais, no lado do navegador. ## O que é necessário previamente Esta biblioteca não funciona sozinha. **Pressupõe-se que o servidor de API de impressão externo do tsreport-editor esteja em funcionamento.** Obtenha os quatro itens a seguir desse servidor. | Item necessário | Descrição | | --- | --- | | URL base | URL do servidor de API (exemplo: `https://reports.example.com`). Pode ou não ter barra final | | ID do cliente | client_id do OAuth 2.0 | | Segredo do cliente | client_secret do OAuth 2.0. **Mantenha-o apenas no lado do servidor e nunca o passe para o navegador** | | Chave do workspace | Identificador do workspace onde os relatórios estão localizados (formato UUID) | Ao cliente são atribuídos escopos de acordo com o uso: `report:print` (envio de impressão), `report:status` (verificação de estado), `report:download` (obtenção do PDF) e `report:preview` (obtenção de materiais de pré-visualização), quatro tipos ao todo. ## Primeiro passo: receber um relatório como PDF Este é o código mais curto para passar o template e os dados e obter a sequência de bytes do PDF. ```ts import { writeFileSync } from 'node:fs' import { TsreportClient } from 'tsreport-sdk' const client = new TsreportClient({ baseUrl: 'https://reports.example.com', clientId: 'my-client-id', clientSecret: 'my-client-secret', }) const pdf = await client.printAndDownload( '00000000-0000-0000-0000-000000000002', // Chave do workspace 'invoice.report', // Caminho do template dentro do workspace 'v1', // Tag do template (versão) { rows: [{ item: 'Peça A', amount: 12000 }] }, // Dados a injetar no relatório ) writeFileSync('./invoice.pdf', pdf) ``` `printAndDownload()` executa em conjunto as três etapas "envio → espera pela conclusão → download" explicadas a seguir. ## Fluxo do trabalho de impressão A impressão é **assíncrona**. No momento da solicitação, o PDF ainda não existe; o processamento em lote do lado do servidor o gera em ordem. Por isso, a solicitação e o recebimento estão divididos em dois procedimentos. ``` print() downloadPdf() │ │ ▼ ▼ ┌────────┐ ┌────────────┐ ┌───────────┐ ┌─────────┐ │ queued │ → │ processing │ → │ completed │ → │ PDF │ └────────┘ └────────────┘ └───────────┘ └─────────┘ │ ▼ ┌───────┐ │ error │ → PrintJobError é lançado └───────┘ ``` - `print()` retorna a **chave do trabalho** (string). Neste momento o PDF ainda não foi gerado - O estado do trabalho transita de `queued` (na fila) → `processing` (em geração) → `completed` (concluído). Em caso de falha, torna-se `error` - `waitForCompletion()` verifica repetidamente o estado até que se torne `completed`, e lança uma exceção se tornar-se `error` - Somente após tornar-se `completed` é possível obter o PDF com `downloadPdf()` ## Uso conforme o objetivo ### Quero receber o PDF em uma única chamada — `printAndDownload()` Realiza envio, espera e download em conjunto. **Normalmente, use este.** ```ts const pdf = await client.printAndDownload(workspaceKey, 'invoice.report', 'v1', data) ``` Se quiser alterar o intervalo ou o limite de espera, especifique-os no quinto argumento. ```ts const pdf = await client.printAndDownload(workspaceKey, 'invoice.report', 'v1', data, { intervalMs: 2000, // Intervalo de verificação do estado timeoutMs: 90000, // PollTimeoutError se este valor for excedido }) ``` ### Quero separar o envio do recebimento — `print()` e `waitForCompletion()` Use em uma estrutura na qual a chave do trabalho é salva em um banco de dados e o resultado é buscado posteriormente. ```ts // Apenas faz a solicitação e guarda a chave const key = await client.print(workspaceKey, 'invoice.report', 'v1', data) await db.jobs.insert({ key, requestedAt: new Date() }) // ── Em outra requisição ou outro processo ── await client.waitForCompletion(key) const pdf = await client.downloadPdf(key) ``` ### Quero gerenciar o progresso por conta própria — `getStatus()` Obtém apenas o estado no momento, sem esperar. Use quando quiser exibir o progresso na tela, por exemplo. ```ts const status = await client.getStatus(key) if (status.status === 'completed') { const pdf = await client.downloadPdf(key) } else if (status.status === 'error') { console.error('Falha na impressão:', status.errorReason) } else { console.log('Em processamento:', status.status) // 'queued' ou 'processing' } ``` `getStatus()` apenas retorna o estado e não lança exceção mesmo em `error` (quem lança exceção é `waitForCompletion()`). ### Quero lidar com PDFs grandes sem carregá-los na memória — `getPdfStream()` `downloadPdf()` carrega o PDF inteiro na memória. Para relatórios de centenas de páginas, é mais seguro recebê-lo como stream e enviá-lo diretamente para um arquivo ou resposta HTTP. ```ts import { Writable } from 'node:stream' import { createWriteStream } from 'node:fs' const stream = await client.getPdfStream(key) await stream.pipeTo(Writable.toWeb(createWriteStream('./invoice.pdf'))) ``` ### Quero interromper a espera no meio do caminho — `signal` Interrompe o polling, por exemplo, quando o usuário sai da tela. ```ts const controller = new AbortController() // Quando o usuário pressionar "Cancelar", chame controller.abort(new Error('Cancelado')) const pdf = await client.printAndDownload(workspaceKey, 'invoice.report', 'v1', data, { signal: controller.signal, }) ``` Ao interromper, a Promise é rejeitada com o valor passado em `signal.reason`. ## Pré-visualização a partir do navegador Para exibir a pré-visualização de relatórios no navegador, é necessário entregar ao navegador materiais como templates, fontes e imagens. Como **o segredo do cliente não pode ser colocado no navegador** nesse momento, adota-se uma estrutura de três camadas, intercalando o seu próprio servidor de aplicação no meio. ``` ┌──────────────┐ ① solicita recursos ┌────────────────┐ ② autentica e envia ┌──────────────┐ │ Navegador │ ───────────────→ │ Servidor da │ ───────────────→ │ Servidor da API │ │ │ │ sua aplicação │ │ de impressão │ │ createEndpoint│ ←─────────────── │ createPreview │ ←─────────────── │ │ │ Connector │ ④ recursos chegam │ Endpoint │ ③ recursos voltam │ │ └──────────────┘ └────────────────┘ └──────────────┘ não possui credenciais o clientSecret fica só aqui ``` - O lado do navegador, com `createEndpointConnector()`, só precisa conhecer **a URL da sua própria aplicação** - O lado do servidor de aplicação apenas coloca `createPreviewEndpoint()`, que anexa a autenticação e retransmite para a API de impressão - Este endpoint de retransmissão satisfaz diretamente o contrato `PreviewConnector` do `tsreport-react`, então pode ser passado diretamente ao componente de pré-visualização Na figura acima, o trecho "Navegador→Aplicação" corresponde ao **Limite A** inicial (autenticação de sessão da aplicação, implementação própria), e o trecho "Aplicação→API de impressão" corresponde ao **Limite B** (OAuth, processado pelo SDK). ### Quero colocar um endpoint de retransmissão no servidor de aplicação — `createPreviewEndpoint()` Importe de `tsreport-sdk/server` (um subcaminho **exclusivo para servidor**). ```ts import { TsreportClient } from 'tsreport-sdk' import { createPreviewEndpoint } from 'tsreport-sdk/server' const client = new TsreportClient({ baseUrl: 'https://reports.example.com', clientId: 'my-client-id', clientSecret: 'my-client-secret', }) const handler = createPreviewEndpoint({ client, target: { workspace: '00000000-0000-0000-0000-000000000002', path: 'reports/invoice.report', tag: 'v1', }, }) ``` `handler` é uma função padrão do tipo `(request: Request) => Promise`. Ao especificar `target`, **independentemente do que for solicitado pelo navegador, este template sempre será retornado**. Como isso evita o problema de o lado do navegador poder especificar qualquer template arbitrário, recomenda-se especificar `target` em telas públicas (se omitido, segue a especificação vinda do navegador). > **Importante**: `createPreviewEndpoint()` não realiza nenhuma autorização. A decisão de "se este usuário pode ver este relatório" é responsabilidade do lado utilizador. Certifique-se de montá-lo sempre **dentro** da autenticação/autorização da aplicação. ### Quero integrar ao Next.js App Router Envolva o handler de retransmissão **com sua própria autenticação** antes de exportá-lo como um route handler. ```ts // app/api/report-preview/route.ts import { TsreportClient } from 'tsreport-sdk' import { createPreviewEndpoint } from 'tsreport-sdk/server' import { getSessionUser } from '@/lib/auth' // ← implementar na aplicação (NextAuth, iron-session, sessão própria etc.) const client = new TsreportClient({ baseUrl: process.env.REPORT_API_URL!, clientId: process.env.REPORT_CLIENT_ID!, clientSecret: process.env.REPORT_CLIENT_SECRET!, }) const previewHandler = createPreviewEndpoint({ client, target: { workspace: 'chave do workspace', path: 'reports/invoice.report', tag: 'v1' }, }) export async function GET(request: Request): Promise { // Aqui fica a autenticação de sessão própria da aplicação (fronteira A). Como o SDK não verifica nada, // omitir esta verificação permitiria que qualquer pessoa com acesso à URL visualizasse os relatórios const user = await getSessionUser(request) if (user === null) { return new Response( JSON.stringify({ message: 'unauthorized', statusCode: 401 }), { status: 401, headers: { 'content-type': 'application/json' } }, ) } return previewHandler(request) } ``` Embora seja tecnicamente possível exportar diretamente com `export const GET = createPreviewEndpoint(...)`, **isso não é recomendado, pois não há lugar para inserir autenticação**. Sempre retransmita após passar pela sua própria autenticação, como na forma acima. A leitura de variáveis de ambiente é **código do lado utilizador**. A própria biblioteca não lê variáveis de ambiente. ### Quero integrar ao Express — `toExpressHandler()` ```ts import express from 'express' import { TsreportClient } from 'tsreport-sdk' import { createPreviewEndpoint, toExpressHandler } from 'tsreport-sdk/server' const app = express() const client = new TsreportClient({ baseUrl, clientId, clientSecret }) const handler = createPreviewEndpoint({ client, target }) // requireAppUser é o middleware de autenticação de sessão próprio da aplicação (fronteira A, implementação própria). // Montá-lo antes desta posição é o que define a fronteira de autorização app.get('/api/report-preview', requireAppUser, toExpressHandler(handler)) ``` `toExpressHandler()` encaminha para `next(error)` caso a própria retransmissão falhe (por exemplo, se não conseguir alcançar o servidor de API), permitindo que seja tratado pelo manipulador de erros da aplicação. Observe que **express não é uma dependência deste pacote**; ele apenas recebe algo cujo formato de tipo seja compatível. ### Quero integrar ao `node:http` — `toNodeHandler()` ```ts import { createServer } from 'node:http' import { createPreviewEndpoint, toNodeHandler } from 'tsreport-sdk/server' const previewHandler = toNodeHandler(createPreviewEndpoint({ client, target })) createServer(async (req, res) => { if (req.url?.startsWith('/api/report-preview')) { // Autenticação de sessão própria da aplicação (fronteira A, implementação própria). Não deve ser omitida if (!isAuthorizedAppUser(req)) { // ← implementar na aplicação res.statusCode = 401 res.setHeader('content-type', 'application/json') res.end(JSON.stringify({ message: 'unauthorized', statusCode: 401 })) return } try { await previewHandler(req, res) } catch (error) { res.statusCode = 500 res.setHeader('content-type', 'application/json') res.end(JSON.stringify({ message: 'preview failed', statusCode: 500 })) } return } res.statusCode = 404 res.end() }).listen(3000) ``` A Promise retornada por `toNodeHandler()` rejeita quando a própria retransmissão falha. Trate-a como no exemplo acima e retorne uma resposta de acordo com a política da aplicação. ### Quero obter materiais a partir do lado do navegador — `createEndpointConnector()` Basta passar a URL do endpoint de retransmissão colocado no servidor de aplicação. ```ts import { createEndpointConnector } from 'tsreport-sdk' const connector = createEndpointConnector({ endpoint: '/api/report-preview', fetchInit: { credentials: 'include' }, // Envia o cookie de sessão da aplicação (para passar pela autenticação da fronteira A) }) const payload = await connector.fetchTemplate({ workspace: 'chave do workspace', path: 'reports/invoice.report', tag: 'v1', }) // payload.template … definição do template // payload.fontIds … array de IDs de fontes que este template requer for (const fontId of payload.fontIds) { const bytes = await connector.fetchFont(fontId) // null se não for encontrada } ``` Se quiser tipar o template em TypeScript, passe o argumento de tipo. ```ts import type { ReportTemplate } from 'tsreport-core' const connector = createEndpointConnector({ endpoint: '/api/report-preview' }) ``` Há **um comportamento que deve ser lembrado** sobre o connector. Quando `fetchTemplate()` tem sucesso, ele memoriza o workspace e o diretório desse template, e passa a anexá-los automaticamente às chamadas seguintes de `resolveImage()`. Por isso, chame sempre `fetchTemplate()` antes de resolver imagens (se for um endpoint com `target` fixo, isso é desnecessário, pois o lado do servidor complementa essa informação). ## Quero lidar diretamente com materiais de pré-visualização no lado do servidor Também é possível obter materiais a partir do Node.js sem passar pelo navegador. ### Quero obter o template — `getPreviewTemplate()` ```ts const { template, fontIds } = await client.getPreviewTemplate(workspaceKey, 'invoice.report', 'v1') ``` `fontIds` é a lista de fontes que este template requer. Como inclui até mesmo as fontes padrão e as fontes para fórmulas, e é **calculado pelo servidor com a mesma lógica do pipeline de impressão**, carregar exatamente conforme esta lista faz a pré-visualização coincidir com o resultado da impressão. ### Quero obter o template de um subrelatório — `getPreviewSubreport()` ```ts const { template, fontIds } = await client.getPreviewSubreport(workspaceKey, 'reports/sub.report') ``` Difere de `getPreviewTemplate()` por não receber uma tag. ### Quero obter arquivos como imagens — `getPreviewFile()` ```ts const bytes = await client.getPreviewFile(workspaceKey, 'assets/logo.png') ``` ### Quero listar as fontes disponíveis — `listPreviewFonts()` ```ts const fonts = await client.listPreviewFonts() // [{ id: 'NotoSansJP', fileName: 'NotoSansJP-VariableFont_wght.ttf' }, ...] ``` ### Quero obter o arquivo real da fonte — `getPreviewFont()` ```ts const fontBytes = await client.getPreviewFont('NotoSansJP') ``` ### Quero escrever uma retransmissão personalizada — `fetchPreviewResource()` Quando os métodos acima não forem suficientes, realiza um GET autenticado para qualquer caminho de pré-visualização. ```ts const response = await client.fetchPreviewResource('/api/report/preview/fonts') ``` **Apenas este método, excepcionalmente, retorna o `Response` diretamente sem lançar exceção mesmo em caso de erro.** É um ponto de entrada de baixo nível para casos em que se deseja retransmitir diretamente o código de status e os cabeçalhos (é exatamente isso que `createPreviewEndpoint()` usa internamente). ## O mecanismo de autenticação Não é necessário que o lado utilizador se preocupe com isso, mas internamente funciona da seguinte forma. 1. Quando um método que requer autenticação é chamado, verifica-se se há um token válido 2. Caso não haja, solicita-se a `POST /api/oauth/token` com `grant_type=client_credentials` 3. O token obtido é mantido **apenas na memória da instância**, e reutilizado até 10 segundos antes de seu vencimento 4. Se a requisição for rejeitada com 401 ou 403, o token é descartado e reobtido, e **a mesma requisição é reenviada apenas uma vez** O token nunca é salvo em disco ou em um armazenamento externo. Use `getAccessToken()` apenas quando o token for necessário explicitamente. ```ts const token = await client.getAccessToken() ``` ## Tratamento de erros Todos os erros herdam de `TsreportClientError`, então é possível capturá-los em conjunto ou bifurcar o tratamento por tipo. ```ts import { ApiError, PollTimeoutError, PrintJobError, TokenError, TsreportClientError, } from 'tsreport-sdk' try { const pdf = await client.printAndDownload(workspaceKey, 'invoice.report', 'v1', data) } catch (error) { if (error instanceof TokenError) { // Credenciais incorretas, cliente desativado etc. console.error('Falha na autenticação:', error.status, error.errorCode) } else if (error instanceof PrintJobError) { // A geração no servidor falhou por causa do template ou dos dados console.error('O job de impressão falhou:', error.key, error.errorReason) } else if (error instanceof PollTimeoutError) { // Não foi concluído dentro do prazo (o job em si pode ainda estar ativo) console.error('A espera expirou:', error.key, error.timeoutMs) } else if (error instanceof ApiError) { // A API retornou 4xx/5xx (template inexistente, permissão insuficiente etc.) console.error('Erro de API:', error.status, error.errorMessage) } else if (error instanceof TsreportClientError) { // Casos diferentes dos anteriores console.error(error.message) } } ``` | Classe de erro | Situação em que ocorre | Informação mantida | | --- | --- | --- | | `TokenError` | Falha na obtenção do token (credenciais incorretas, cliente desativado, etc.) | `status`(status HTTP)、`errorCode`、`errorDescription` | | `ApiError` | A API retornou 4xx/5xx (template inexistente, permissão insuficiente, etc.) | `status`、`errorMessage`(mensagem retornada pelo servidor) | | `PrintJobError` | O estado do trabalho tornou-se `error` | `key`、`errorReason`(motivo da falha retornado pelo servidor) | | `PollTimeoutError` | `waitForCompletion()` não conseguiu confirmar a conclusão dentro do tempo limite | `key`、`timeoutMs` | | `TsreportClientError` | Outros casos além dos acima. Classe base de todos os erros | — | `PollTimeoutError` significa apenas que "o cliente parou de esperar" e **não significa que o trabalho falhou no lado do servidor**. Como a chave permanece válida, é possível verificar posteriormente com `getStatus()`. Além disso, apenas o connector retornado por `createEndpointConnector()` retorna, excepcionalmente, `null` em vez de uma exceção quando um material não é encontrado (404). Isso porque a pré-visualização deve conseguir continuar a renderização mesmo que parte dos materiais esteja faltando. No entanto, `fetchTemplate()` está fora dessa exceção: se o próprio template não puder ser obtido, um `ApiError` é lançado (pois não há nada a ser renderizado). ## Referência da API ### `new TsreportClient(options)` | Propriedade | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `baseUrl` | string | ✓ | URL base do servidor de API. A barra final é removida automaticamente | | `clientId` | string | ✓ | client_id do OAuth 2.0 | | `clientSecret` | string | ✓ | client_secret do OAuth 2.0 | | `scope` | string | | Restringe especificando os escopos solicitados separados por espaço. Se omitido, todos os escopos registrados no cliente são concedidos | ### Métodos | Método | Valor de retorno | Descrição | | --- | --- | --- | | `print(workspace, templatePath, tag, data)` | `Promise` | Solicita a impressão e retorna a chave do trabalho | | `getStatus(key)` | `Promise` | Retorna o estado atual do trabalho (não lança exceção) | | `waitForCompletion(key, options?)` | `Promise` | Espera até a conclusão. Em caso de falha, `PrintJobError`; em caso de tempo esgotado, `PollTimeoutError` | | `downloadPdf(key)` | `Promise` | Obtém todos os bytes do PDF | | `getPdfStream(key)` | `Promise>` | Obtém o PDF como stream | | `printAndDownload(workspace, templatePath, tag, data, options?)` | `Promise` | Realiza envio, espera e download em conjunto | | `getAccessToken()` | `Promise` | Retorna um token de acesso válido (obtendo-o se necessário) | | `getPreviewTemplate(workspace, templatePath, tag)` | `Promise` | Obtém a definição do template e os IDs de fontes necessários | | `getPreviewSubreport(workspace, templatePath)` | `Promise` | Obtém o template de um subrelatório | | `getPreviewFile(workspace, filePath)` | `Promise` | Obtém um arquivo (imagem, etc.) dentro do workspace | | `listPreviewFonts()` | `Promise` | Obtém a lista de fontes disponíveis | | `getPreviewFont(id)` | `Promise` | Obtém o arquivo real de uma fonte | | `fetchPreviewResource(resourcePath)` | `Promise` | Realiza um GET autenticado para qualquer caminho de pré-visualização e retorna o `Response` bruto (não lança exceção) | ### `WaitForCompletionOptions` | Propriedade | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `intervalMs` | number | | Intervalo para verificar o estado (milissegundos). Padrão: 1000 | | `timeoutMs` | number | | Limite máximo de espera (milissegundos). Se ultrapassado, `PollTimeoutError`. Padrão: 120000 | | `signal` | AbortSignal | | Sinal para interromper a espera. Ao interromper, rejeita com `signal.reason` | ### Tipos de valor de retorno | Tipo | Estrutura | | --- | --- | | `PrintStatusResult` | `{ key: string, status: PrintJobState, errorReason?: string }` | | `PrintJobState` | `'queued'`=na fila / `'processing'`=em geração / `'completed'`=concluído / `'error'`=falha | | `PreviewTemplateResult` | `{ template: unknown, fontIds: string[] }` | | `PreviewFontInfo` | `{ id: string, fileName: string }` | ### `createEndpointConnector(options)` | Propriedade | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `endpoint` | string | ✓ | URL onde o endpoint de retransmissão está montado (exemplo: `/api/report-preview`) | | `fetchInit` | RequestInit | | Configuração anexada a todas as requisições. Para enviar o cookie de sessão, use `{ credentials: 'include' }` | Os métodos do connector retornado são os quatro a seguir. Todos retornam `null` quando o material não é encontrado (exceto `fetchTemplate()`). | Método | Valor de retorno | Descrição | | --- | --- | --- | | `fetchTemplate(source)` | `Promise` | Obtém o template. `source` é `{ workspace, path, tag }` | | `fetchFont(fontId)` | `Promise` | Obtém o arquivo real de uma fonte | | `resolveImage(ref)` | `Promise` | Resolve uma imagem referenciada pelo template | | `fetchSubreportTemplate(ref, context)` | `Promise` | Obtém o template de um subrelatório. `context` é `{ workingDirectory }` | ### `tsreport-sdk/server` | Função | Valor de retorno | Descrição | | --- | --- | --- | | `createPreviewEndpoint(options)` | `(request: Request) => Promise` | Cria o handler de retransmissão de materiais de pré-visualização | | `toNodeHandler(handler)` | `(req, res) => Promise` | Adaptador para `node:http`. Rejeita quando a própria retransmissão falha | | `toExpressHandler(handler)` | `(req, res, next) => Promise` | Adaptador para Express. Encaminha a falha da própria retransmissão para `next(error)` | Opções de `createPreviewEndpoint`: | Propriedade | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `client` | TsreportClient | ✓ | Cliente autenticado. O destino da conexão e as credenciais são passados somente a partir daqui | | `target` | `{ workspace, path, tag }` | | Template a ser fixado. Se especificado, ignora a especificação do lado da requisição e sempre retorna este template. O diretório base para imagens e subrelatórios também é complementado | ## Exemplos de código O diretório `examples/` contém exemplos de implementação que funcionam diretamente (também incluídos no pacote npm). | Arquivo | Conteúdo | | --- | --- | | `examples/nextjs-route.ts` | Route handler do Next.js App Router (com gate de autenticação de sessão) | | `examples/express-server.ts` | Integração ao Express e posicionamento do middleware de autorização | | `examples/node-server.ts` | Integração ao `node:http` | | `examples/browser-connector.ts` | Obtenção de materiais de pré-visualização a partir do navegador | Como estes são **efetivamente verificados iniciando o servidor** dentro de `npm test`, não há risco de código não funcional ser incluído. ## Testes | Comando | Conteúdo | | --- | --- | | `npm test` | Testes unitários e de integração. Inclui a verificação de inicialização real de `examples/` | | `npm run test:live` | Teste de integração com servidor real, **pressupondo um tsreport-editor em funcionamento e dados de seed** | ## Ambiente de execução - Node.js 18 ou superior (ambiente de execução de `TsreportClient` e `tsreport-sdk/server`) - Navegadores modernos (apenas `createEndpointConnector()`. Como não possui credenciais, pode ser posicionado com segurança) - Nenhuma dependência de pacotes em tempo de execução - Compatível com ESM / CommonJS ## Projetos relacionados - [tsreport-core](https://github.com/pontasan/tsreport-core) - [tsreport-editor](https://github.com/pontasan/tsreport-editor) - [tsreport-sdk](https://github.com/pontasan/tsreport-sdk) - [tsreport-react](https://github.com/pontasan/tsreport-react) ## License O tsreport-sdk pode ser usado, à escolha do usuário, sob a [MIT License](./LICENSE-MIT) ou a [Apache License 2.0](./LICENSE-APACHE) (SPDX: `MIT OR Apache-2.0`).