teraprox-core-sdk 0.3.10 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,515 @@
1
+ # teraprox-core-sdk
2
+
3
+ > Contrato tipado Core ↔ Federados — interfaces, context, hooks e componentes compartilhados.
4
+
5
+ ---
6
+
7
+ ## Índice
8
+
9
+ 1. [Visão geral](#visão-geral)
10
+ 2. [Instalação](#instalação)
11
+ 3. [Modo Standalone (`StandaloneProvider`)](#modo-standalone-standaloneprovider)
12
+ - [Setup mínimo](#setup-mínimo)
13
+ - [Toast: `toast` vs `addToast`](#toast-toast-vs-addtoast)
14
+ - [RTDB em tempo real](#rtdb-em-tempo-real)
15
+ - [Resolução automática do tenant](#resolução-automática-do-tenant)
16
+ - [Emulador vs Firebase Cloud](#emulador-vs-firebase-cloud)
17
+ - [Fallback controllers (dev)](#fallback-controllers-dev)
18
+ 4. [Modo Hospedado (`FederatedBridge`)](#modo-hospedado-federatedbridge)
19
+ 5. [CoreServiceBuilder (programático)](#coreservicebuilder-programático)
20
+ 6. [Hooks](#hooks)
21
+ - [`useCoreService`](#usecoreservice)
22
+ - [`useMatchingObject`](#usematchingobject)
23
+ - [`useHttpController` / `useFetchData` / `usePostData`](#usehttpcontroller--usefetchdata--usepostdata)
24
+ - [`useAnexoManager`](#useanexomanager)
25
+ - [`useToast`](#usetoast)
26
+ 7. [DevAutoLogin](#devautologin)
27
+ 8. [Fluxo RTDB ponta a ponta](#fluxo-rtdb-ponta-a-ponta)
28
+ 9. [Variáveis de ambiente](#variáveis-de-ambiente)
29
+ 10. [Tipos exportados](#tipos-exportados)
30
+ 11. [CHANGELOG](#changelog)
31
+
32
+ ---
33
+
34
+ ## Visão geral
35
+
36
+ O `teraprox-core-sdk` é o contrato compartilhado entre o **shell Core** e os **microfrontends federados** (SGM-SS, SGM-OM, SGP etc.).
37
+
38
+ Ele provê:
39
+
40
+ | Camada | O que entrega |
41
+ |--------|--------------|
42
+ | **Contexto** | `CoreServiceContext` — acesso a `createController`, `toast`, `subscribe`/`unsubscribe` |
43
+ | **Standalone** | `StandaloneProvider` — modo dev/autônomo com Firebase RTDB integrado |
44
+ | **Hospedado** | `FederatedBridge` — ponte que injeta o `CoreService` provido pelo shell |
45
+ | **Hooks** | `useMatchingObject`, `useAnexoManager`, `useFetchData`, `usePostData`, `useToast`… |
46
+ | **Builder** | `CoreServiceBuilder` — construção programática do `CoreService` |
47
+ | **Tipos** | Interfaces para `CoreService`, `HttpController`, `ToastService`, `IAnexoPort`… |
48
+
49
+ ---
50
+
51
+ ## Instalação
52
+
53
+ ```bash
54
+ npm install teraprox-core-sdk
55
+ # ou, a partir do monorepo local:
56
+ npm install file:../../packages/core-sdk
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Modo Standalone (`StandaloneProvider`)
62
+
63
+ Use quando o microfrontend roda de forma **autônoma** (`npm start`) fora do shell Core.
64
+
65
+ ### Setup mínimo
66
+
67
+ ```tsx
68
+ // App.tsx
69
+ import { StandaloneProvider } from 'teraprox-core-sdk/federation'
70
+ import { FetchHttpAdapter } from 'teraprox-core-sdk'
71
+ import { useSelector } from 'react-redux'
72
+
73
+ const API = process.env.REACT_APP_API_URL || 'http://localhost:4021'
74
+
75
+ function makeController(context: string, baseEndPoint?: string) {
76
+ const ep = baseEndPoint ?? (context ? `${API}/${context}` : API)
77
+ return new FetchHttpAdapter(ep, { 'x-teraprox-host': 'manutencao' })
78
+ }
79
+
80
+ // Wrapper dentro do Redux Provider
81
+ function StandaloneWrapper({ children }: { children: React.ReactNode }) {
82
+ const tenant = useSelector((s: any) => s.global?.companyId)
83
+
84
+ return (
85
+ <StandaloneProvider
86
+ createController={makeController}
87
+ toast={myToastService}
88
+ tenant={tenant}
89
+ >
90
+ {children}
91
+ </StandaloneProvider>
92
+ )
93
+ }
94
+ ```
95
+
96
+ > **Importante:** `StandaloneWrapper` deve ser renderizado **dentro** do `Redux Provider`
97
+ > para que `useSelector` funcione. Coloque-o acima do `RouterProvider`.
98
+
99
+ ---
100
+
101
+ ### Toast: `toast` vs `addToast`
102
+
103
+ O provider aceita dois formatos — **prefira `toast`**:
104
+
105
+ ```tsx
106
+ // ✅ Preferido — passa ToastService diretamente (sem bridge)
107
+ <StandaloneProvider toast={minhaImplementacaoDeToast} ...>
108
+
109
+ // ✅ Compatibilidade — react-toast-notifications
110
+ <StandaloneProvider addToast={addToast} ...>
111
+
112
+ // Se nenhum for passado, chamadas de toast vão para console.warn em dev.
113
+ ```
114
+
115
+ **Implementando `ToastService`:**
116
+
117
+ ```ts
118
+ import type { ToastService } from 'teraprox-core-sdk'
119
+
120
+ class DomToastAdapter implements ToastService {
121
+ success(msg: string) { /* renderiza toast verde */ }
122
+ warning(msg: string) { /* renderiza toast amarelo */ }
123
+ error(msg: string) { /* renderiza toast vermelho */ }
124
+ info(msg: string) { /* renderiza toast azul */ }
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ### RTDB em tempo real
131
+
132
+ Quando `tenant` está resolvido, o `StandaloneProvider` conecta ao Firebase RTDB e
133
+ despacha eventos para os listeners registrados via `useMatchingObject`.
134
+
135
+ ```tsx
136
+ // Escuta mudanças em tempo real de uma entidade específica
137
+ import { useMatchingObject } from 'teraprox-core-sdk'
138
+
139
+ useMatchingObject('solicitacaoDeServico', '*', (payload) => {
140
+ // payload = id da entidade que mudou
141
+ fetchById(payload).then(dispatch)
142
+ })
143
+ ```
144
+
145
+ O listener recebe apenas o `id` afetado e faz um `GET` pontual —
146
+ **atualização granular**, sem refetch da lista inteira.
147
+
148
+ ---
149
+
150
+ ### Resolução automática do tenant
151
+
152
+ O tenant identifica o caminho no RTDB: `{tenant}/matchingObjects`.
153
+
154
+ **Ordem de prioridade (primeira que tiver valor):**
155
+
156
+ | # | Fonte | Quando usar |
157
+ |---|-------|-------------|
158
+ | 1 | `tenant` prop | Dinâmico — vem do Redux após login (`state.global.companyId`) |
159
+ | 2 | `REACT_APP_RTDB_TENANT` no `.env` | Estático — garante conexão imediata no boot, antes do Redux |
160
+ | 3 | `'dev-local'` (automático) | Último recurso em `NODE_ENV=development`, zero configuração |
161
+
162
+ **Configuração recomendada para dev:**
163
+
164
+ ```dotenv
165
+ # .env do microfrontend
166
+ REACT_APP_RTDB_TENANT=1 # deve bater com o companyId do DevAutoLogin / API local
167
+ ```
168
+
169
+ > Em produção o tenant **sempre** vem do Redux (login real). Os fallbacks 2 e 3
170
+ > são ignorados quando `NODE_ENV=production`.
171
+
172
+ Se o tenant não resolver em 5 s, um `console.warn` explica o problema e como corrigir.
173
+
174
+ ---
175
+
176
+ ### Emulador vs Firebase Cloud
177
+
178
+ **Resolução do emulador (mesma lógica de prioridade):**
179
+
180
+ | # | Fonte | Exemplo |
181
+ |---|-------|---------|
182
+ | 1 | `emulator` prop | `{ host: 'localhost', port: 9000, namespace: 'teraprox-default-rtdb' }` |
183
+ | 2 | `REACT_APP_RTDB_EMULATOR_HOST` env var | `localhost:9000` |
184
+ | 3 | Auto-probe `localhost:9000` | Detecta se o emulador está rodando |
185
+ | 4 | `firebaseConfig` prop | Objeto de config do Firebase Cloud |
186
+ | — | Nenhum encontrado | Popup de aviso em dev |
187
+
188
+ **Emulador Docker (recomendado para dev):**
189
+
190
+ ```tsx
191
+ // App.tsx — configuração explícita via prop (mais confiável que env var em node_modules)
192
+ <StandaloneProvider
193
+ createController={makeController}
194
+ toast={domToast}
195
+ tenant={tenant}
196
+ emulator={{ host: 'localhost', port: 9000, namespace: 'teraprox-default-rtdb' }}
197
+ >
198
+ ```
199
+
200
+ ```dotenv
201
+ # .env — alternativa via variável (lida pelo bundler na app, não no node_modules)
202
+ REACT_APP_RTDB_EMULATOR_HOST=localhost:9000
203
+ REACT_APP_RTDB_EMULATOR_NS=teraprox-default-rtdb
204
+ ```
205
+
206
+ > **Nota sobre `process.env.*` em `node_modules`:** webpack's `DefinePlugin` pode não
207
+ > substituir variáveis dentro de dependências. Prefira sempre o **prop explícito** para
208
+ > garantir funcionamento (tanto para `emulator` quanto para `tenant`).
209
+
210
+ ---
211
+
212
+ ### Fallback controllers (dev)
213
+
214
+ Use para simular endpoints que não existem localmente:
215
+
216
+ ```tsx
217
+ import { NullHttpController } from 'teraprox-core-sdk'
218
+
219
+ class ArvoreEstruturalFallback extends NullHttpController {
220
+ get(path?: string) {
221
+ if (path?.includes('branchByBranchLevel')) {
222
+ return Promise.resolve([
223
+ { id: 1, branchLevel: { level: 1, nome: 'Empresa' }, branchNodes: [] }
224
+ ])
225
+ }
226
+ return super.get(path)
227
+ }
228
+ }
229
+
230
+ function makeController(context: string, baseEndPoint?: string) {
231
+ // Intercepta contextos específicos antes de chegar ao HTTP
232
+ if (process.env.NODE_ENV !== 'production') {
233
+ if (context === '') return new ArvoreEstruturalFallback()
234
+ if (context === 'branchLevel') return new BranchLevelFallback()
235
+ }
236
+ const ep = baseEndPoint ?? `${API}/${context}`
237
+ return new FetchHttpAdapter(ep, { 'x-teraprox-host': 'manutencao' })
238
+ }
239
+ ```
240
+
241
+ ---
242
+
243
+ ## Modo Hospedado (`FederatedBridge`)
244
+
245
+ Quando o microfrontend é carregado pelo **shell Core**, ele recebe o `CoreService` via
246
+ Module Federation. Use o `FederatedBridge` para injetar esse serviço no contexto:
247
+
248
+ ```tsx
249
+ // O shell Core passa o CoreService por prop ou window
250
+ import { FederatedBridge } from 'teraprox-core-sdk/federation'
251
+
252
+ function RemoteRoot({ coreService }: { coreService: CoreService }) {
253
+ return (
254
+ <FederatedBridge coreService={coreService}>
255
+ <App />
256
+ </FederatedBridge>
257
+ )
258
+ }
259
+ ```
260
+
261
+ > `FederatedBridge` é o oposto do `StandaloneProvider` — não conecta ao RTDB
262
+ > (o shell já faz isso), apenas injeta o `CoreService` no contexto.
263
+
264
+ ---
265
+
266
+ ## CoreServiceBuilder (programático)
267
+
268
+ Útil para **testes** ou quando um componente React não é viável:
269
+
270
+ ```ts
271
+ import { CoreServiceBuilder, FetchHttpAdapter } from 'teraprox-core-sdk'
272
+
273
+ const service = new CoreServiceBuilder()
274
+ .withHttpEndpoint('http://localhost:4021')
275
+ .withGatewayHost('manutencao')
276
+ .withToast(myToast)
277
+ .withTracing(true) // adiciona header traceparent W3C
278
+ .withFallbackController('', new ArvoreFallback())
279
+ .build()
280
+ ```
281
+
282
+ O `.build()` retorna um `CoreService` com `subscribe`/`unsubscribe` **funcionais**
283
+ (armazenam as subscriptions numa lista interna), prontos para serem consumidos por
284
+ `useMatchingObject`. **Não conecta ao RTDB por si só** — para RTDB em standalone,
285
+ use `StandaloneProvider`.
286
+
287
+ ---
288
+
289
+ ## Hooks
290
+
291
+ ### `useCoreService`
292
+
293
+ Acessa o `CoreService` do contexto. Deve estar dentro de `StandaloneProvider` ou `FederatedBridge`.
294
+
295
+ ```ts
296
+ const { createController, toast, subscribe, unsubscribe } = useCoreService()
297
+ ```
298
+
299
+ ---
300
+
301
+ ### `useMatchingObject`
302
+
303
+ Registra um listener de tempo real com cleanup automático.
304
+
305
+ ```ts
306
+ import { useMatchingObject } from 'teraprox-core-sdk'
307
+
308
+ // Dentro de um componente:
309
+ useMatchingObject(
310
+ 'solicitacaoDeServico', // context — deve bater com o model do MatchingObject no backend
311
+ '*', // location — '*' captura qualquer target
312
+ (payload) => {
313
+ // payload = data do MatchingObject (geralmente o id da entidade)
314
+ fetchSolicitacao(payload).then(dispatch)
315
+ },
316
+ [dispatch] // deps extras para o useEffect interno
317
+ )
318
+ ```
319
+
320
+ **Por que `useMatchingObject` e não `subscribe` manual?**
321
+
322
+ - Registra e **remove** automaticamente no unmount (evita leak de memória)
323
+ - É seguro com React StrictMode (dupla montagem não duplica listeners)
324
+ - Expressivo: deixa explícito o contexto que o componente observa
325
+
326
+ ---
327
+
328
+ ### `useHttpController` / `useFetchData` / `usePostData`
329
+
330
+ ```ts
331
+ // Acesso direto ao controller
332
+ const controller = useHttpController('solicitacaoDeServico')
333
+
334
+ // GET com loading state
335
+ const { data, loading, error, refetch } = useFetchData('solicitacaoDeServico')
336
+
337
+ // POST com loading state
338
+ const { post, loading } = usePostData('solicitacaoDeServico')
339
+ ```
340
+
341
+ ---
342
+
343
+ ### `useAnexoManager`
344
+
345
+ Gerencia o ciclo completo de anexos com upload direto ao GCS via signed URLs.
346
+
347
+ ```ts
348
+ const anexo = useAnexoManager({
349
+ entityId: form?.id, // id da entidade dona dos anexos
350
+ context: 'solicitacaoDeServico', // context para o path no GCS
351
+ port: 'anexo', // controller que expõe /intent, /confirm, etc.
352
+ })
353
+
354
+ // Uso:
355
+ anexo.addLocal(files) // adiciona arquivos locais (sem upload ainda)
356
+ await anexo.uploadAll(entityId) // faz intent → upload GCS → confirm para cada arquivo
357
+ anexo.locais // AnexoLocal[] — arquivos ainda não enviados
358
+ anexo.persistidos // AnexoPersistido[] — já salvos na API
359
+ ```
360
+
361
+ **Fluxo de upload:**
362
+
363
+ ```
364
+ addLocal(File[])
365
+
366
+ uploadAll(entityId)
367
+ ├─ POST /anexo/intent → { uploadUrl, key, fileName, contentType }
368
+ ├─ PUT uploadUrl (GCS) → upload direto (sem passar pela API)
369
+ └─ POST /anexo/confirm → registra o anexo no banco
370
+ ```
371
+
372
+ ---
373
+
374
+ ### `useToast`
375
+
376
+ ```ts
377
+ const toast = useToast()
378
+
379
+ toast.success('Salvo com sucesso!')
380
+ toast.warning('Verifique os campos obrigatórios.')
381
+ toast.error('Erro ao salvar.')
382
+ toast.info('Processando...')
383
+ ```
384
+
385
+ ---
386
+
387
+ ## DevAutoLogin
388
+
389
+ Popula automaticamente o Redux com um usuário de dev em `NODE_ENV=development`.
390
+ Não faz nada em produção nem quando o microfrontend está hospedado pelo shell Core.
391
+
392
+ ```tsx
393
+ import { DevAutoLogin } from 'teraprox-core-sdk/federation'
394
+ import { logIn, setCompany } from './Reducers/globalConfigReducer'
395
+
396
+ <DevAutoLogin actions={{ logIn, setCompany }}>
397
+ <App />
398
+ </DevAutoLogin>
399
+ ```
400
+
401
+ **Usuário padrão injetado:**
402
+
403
+ ```ts
404
+ {
405
+ id: '1', companyId: '1', role: 'admin',
406
+ firstName: 'Dev', lastName: 'User',
407
+ setor: 'Desenvolvimento', ...
408
+ }
409
+ ```
410
+
411
+ Para sobrescrever campos específicos:
412
+
413
+ ```tsx
414
+ <DevAutoLogin
415
+ actions={{ logIn, setCompany }}
416
+ devUser={{ companyId: 'minha-empresa', role: 'operator' }}
417
+ >
418
+ ```
419
+
420
+ ---
421
+
422
+ ## Fluxo RTDB ponta a ponta
423
+
424
+ ```
425
+ Backend (API)
426
+ sentinel.appendMo(new MatchingObject('solicitacaoDeServico', '*', id))
427
+ ↓ (buffer durante a request)
428
+ afterCommit → publishMatchingObjects(tenant, mos)
429
+
430
+ Firebase RTDB push em: {tenant}/matchingObjects
431
+
432
+ Frontend (StandaloneProvider)
433
+ onChildAdded('{tenant}/matchingObjects')
434
+
435
+ Encontra subscribers com context='solicitacaoDeServico' e location='*'
436
+
437
+ refresher(payload=id)
438
+
439
+ GET /solicitacaoDeServico/:id → dispatch(updateSingleSsRow(ss))
440
+
441
+ Apenas a linha afetada é atualizada na UI — sem refetch da lista completa
442
+ ```
443
+
444
+ ---
445
+
446
+ ## Variáveis de ambiente
447
+
448
+ Coloque no `.env` de cada microfrontend:
449
+
450
+ ```dotenv
451
+ # Endpoint da API principal
452
+ REACT_APP_API_URL=http://localhost:4021
453
+
454
+ # Gateway host (header x-teraprox-host)
455
+ REACT_APP_TERAPROX_GATEWAY_HOST=manutencao
456
+
457
+ # ── RTDB ─────────────────────────────────────────────────────────────────────
458
+
459
+ # Tenant padrão para dev — deve bater com o companyId da API local.
460
+ # Garante que o RTDB conecta no boot, antes do Redux ser populado.
461
+ # Em produção é ignorado (tenant vem do Redux após login real).
462
+ REACT_APP_RTDB_TENANT=1
463
+
464
+ # Emulador Firebase RTDB (Docker / firebase-tools)
465
+ REACT_APP_RTDB_EMULATOR_HOST=localhost:9000
466
+ REACT_APP_RTDB_EMULATOR_NS=teraprox-default-rtdb
467
+ ```
468
+
469
+ > **Nota:** variáveis `REACT_APP_RTDB_*` são substituídas pelo webpack `DefinePlugin`
470
+ > **no código da app**, mas podem não ser resolvidas dentro de `node_modules`.
471
+ > Para garantir, passe os valores como **props explícitas** ao `StandaloneProvider`:
472
+ >
473
+ > ```tsx
474
+ > <StandaloneProvider
475
+ > emulator={{ host: 'localhost', port: 9000, namespace: 'teraprox-default-rtdb' }}
476
+ > tenant={tenant ?? process.env.REACT_APP_RTDB_TENANT}
477
+ > ...
478
+ > >
479
+ > ```
480
+
481
+ ---
482
+
483
+ ## Tipos exportados
484
+
485
+ ```ts
486
+ import type {
487
+ CoreService, // interface principal do serviço
488
+ HttpController, // interface de controllers HTTP
489
+ ToastService, // interface de toast
490
+ MatchingObjectSubscription, // { context, location, refresher }
491
+ IAnexoPort, // interface para gerenciamento de anexos
492
+ AnexoPersistido, // anexo já salvo na API
493
+ AnexoLocal, // arquivo local ainda não enviado
494
+ UploadIntent, // resposta do /anexo/intent
495
+ IObservabilityPort, // interface de observabilidade
496
+ } from 'teraprox-core-sdk'
497
+ ```
498
+
499
+ ---
500
+
501
+ ## CHANGELOG
502
+
503
+ ### 0.3.12
504
+ - **`StandaloneProvider`**: aceita `toast?: ToastService` diretamente (sem necessidade de bridge `addToast`)
505
+ - **`StandaloneProvider`**: resolução automática do tenant em 3 camadas (`tenant` prop → `REACT_APP_RTDB_TENANT` → `'dev-local'`)
506
+ - **`StandaloneProvider`**: aviso de `console.warn` após 5 s quando tenant não resolve
507
+ - **`CoreServiceBuilder`**: `subscribe`/`unsubscribe` armazenam subscriptions corretamente (não são mais stubs)
508
+
509
+ ### 0.3.x
510
+ - `useAnexoManager`: suporte a `overrideEntityId` em `uploadAll(entityId?)` para entidades recém-criadas
511
+ - `FetchHttpAdapter`: tratamento correto de `FormData` vs JSON no body
512
+ - `TracingHttpAdapter`: injeção automática do header `traceparent` W3C
513
+ - `StandaloneProvider`: auto-detecção do emulador RTDB com probe em `localhost:9000`
514
+ - `DevAutoLogin`: suporte a `devUser` parcial para sobrescrever campos do usuário dev
515
+ - Hooks: `useFetchData`, `usePostData`, `useFormStorage`, `useSmartSearch`, `useValidation`