@seedgrid/ai-toolkit 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/adapters/claude.js +114 -0
- package/dist/adapters/codex.js +108 -0
- package/dist/adapters/index.js +17 -0
- package/dist/adapters/shared.js +142 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli.js +203 -0
- package/dist/commands/common.js +98 -0
- package/dist/commands/doctor.js +290 -0
- package/dist/commands/init.js +9 -0
- package/dist/commands/install.js +107 -0
- package/dist/commands/remove.js +54 -0
- package/dist/commands/review.js +127 -0
- package/dist/commands/status.js +73 -0
- package/dist/commands/update.js +8 -0
- package/dist/commands/validate.js +27 -0
- package/dist/core/apply.js +181 -0
- package/dist/core/blocks.js +64 -0
- package/dist/core/content.js +528 -0
- package/dist/core/env.js +40 -0
- package/dist/core/frontmatter.js +29 -0
- package/dist/core/hash.js +14 -0
- package/dist/core/inspect.js +51 -0
- package/dist/core/lock.js +124 -0
- package/dist/core/paths.js +65 -0
- package/dist/core/plan.js +242 -0
- package/dist/core/tools.js +62 -0
- package/dist/core/types.js +11 -0
- package/dist/core/updates.js +121 -0
- package/docs/arquitetura.md +80 -0
- package/docs/compatibilidade.md +50 -0
- package/docs/formato.md +155 -0
- package/docs/instalacao.md +105 -0
- package/docs/manutencao.md +115 -0
- package/package.json +43 -4
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-enums.md +161 -0
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-propriedades.md +971 -0
- package/seedgridKB/SeedGrid/Extensions/Indice.md +63 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Configuracao.md +124 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Enums.md +40 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Exemplos.md +97 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao-Rust.md +69 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao.md +88 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Enums.md +33 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Exemplos.md +121 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Rust.md +126 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Visao-geral.md +111 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao-Rust.md +93 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao.md +127 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Enums.md +66 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Exemplos.md +151 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Rust.md +120 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Configuracao-Rust.md +136 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Configuracao.md +319 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Enums.md +131 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Exemplos.md +210 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Rust.md +135 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Visao-geral.md +150 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Configuracao-Rust.md +86 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Configuracao.md +243 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Enums.md +165 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Exemplos.md +205 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Rust.md +109 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Visao-geral.md +181 -0
- package/seedgridKB/SeedGrid/Extensions/broker/Configuracao-Rust.md +40 -0
- package/seedgridKB/SeedGrid/Extensions/broker/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Configuracao-Rust.md +73 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Configuracao.md +112 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Enums.md +36 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Exemplos.md +149 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Rust.md +81 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Visao-geral.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Configuracao.md +107 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Enums.md +105 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Exemplos.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Visao-geral.md +99 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Configuracao.md +122 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Enums.md +29 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Exemplos.md +82 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Visao-geral.md +118 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao-Rust.md +110 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao.md +157 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Exemplos.md +157 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Rust.md +70 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Visao-geral.md +88 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao-Rust.md +124 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao.md +195 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Enums.md +71 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Exemplos.md +149 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Rust.md +139 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Visao-geral.md +130 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Configuracao.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Enums.md +52 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Exemplos.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Visao-geral.md +113 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao-Rust.md +67 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao.md +94 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Enums.md +79 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Exemplos.md +172 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Rust.md +107 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Visao-geral.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao-Rust.md +72 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao.md +104 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Exemplos.md +104 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Rust.md +69 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Visao-geral.md +105 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao-Rust.md +68 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao.md +186 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Enums.md +96 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Exemplos.md +229 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Rust.md +80 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Visao-geral.md +112 -0
- package/seedgridKB/SeedGrid/Extensions/runtime/Configuracao-Rust.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/runtime/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/security/Configuracao-Rust.md +327 -0
- package/seedgridKB/SeedGrid/Extensions/security/Configuracao.md +510 -0
- package/seedgridKB/SeedGrid/Extensions/security/Enums.md +95 -0
- package/seedgridKB/SeedGrid/Extensions/security/Exemplos.md +218 -0
- package/seedgridKB/SeedGrid/Extensions/security/Rust.md +203 -0
- package/seedgridKB/SeedGrid/Extensions/security/Visao-geral.md +131 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust.md +188 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao.md +345 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Enums.md +117 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Exemplos.md +205 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Rust.md +161 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Visao-geral.md +142 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao-Rust.md +75 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao.md +152 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Enums.md +198 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Exemplos.md +173 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Rust.md +83 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Visao-geral.md +99 -0
- package/seedgridKB/SeedGrid/Mapa-dos-repositorios.md +112 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/Indice.md +323 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/API.md +257 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Exemplos.md +307 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Visao-geral.md +51 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/API.md +174 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Exemplos.md +236 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Visao-geral.md +76 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/API.md +339 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/Exemplos.md +311 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/Visao-geral.md +57 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/API.md +125 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Exemplos.md +116 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Visao-geral.md +86 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/API.md +490 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Exemplos.md +544 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Visao-geral.md +69 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/API.md +113 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Exemplos.md +159 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Visao-geral.md +92 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/API.md +516 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Exemplos.md +629 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Visao-geral.md +80 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/API.md +615 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Exemplos.md +384 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Visao-geral.md +111 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/API.md +661 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Exemplos.md +312 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Visao-geral.md +123 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/API.md +1090 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/Exemplos.md +1172 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/Visao-geral.md +98 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/Visao-geral.md +98 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-apikeys.md +118 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-audit.md +88 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-backup.md +131 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-platform.md +132 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-user.md +120 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-login-tenancy.md +119 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-security.md +427 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-self-enter.md +104 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-subdomain-tenancy.md +110 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/API.md +490 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Exemplos.md +561 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Visao-geral.md +82 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/Visao-geral.md +68 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-commons.md +61 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-core.md +140 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-playground.md +112 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-theme.md +97 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/API.md +177 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/Exemplos.md +332 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/Visao-geral.md +106 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/showcase.md +70 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/arquiteto.md +53 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/backend-java.md +45 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/backend-rust.md +43 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/frontend-react.md +41 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/qa.md +44 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/revisor.md +40 -0
- package/seedgridKB/SeedGrid/Toolkit/references/fluxo-de-trabalho.md +78 -0
- package/seedgridKB/SeedGrid/Toolkit/references/glossario.md +15 -0
- package/seedgridKB/SeedGrid/Toolkit/references/modos-shared-native.md +27 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/backend-java-seedgrid.md +54 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/coordenacao.md +39 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/formato-entrega.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/frontend-seedgrid.md +47 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/personalizacao-local.md +22 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/planejar-mudanca/SKILL.md +38 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/SKILL.md +33 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/references/checklist.md +25 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-backend-crud/SKILL.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-bump/SKILL.md +26 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/SKILL.md +221 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/agents/openai.yaml +4 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/checklist.md +31 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/patterns.md +185 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/routing.md +66 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/SKILL.md +56 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/agents/openai.yaml +4 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/checklist.md +32 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/steps.md +114 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-review/SKILL.md +61 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/validar-mudanca/SKILL.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/decisao-arquitetura.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/especificacao-tarefa.md +32 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/relatorio-validacao.md +30 -0
- package/seedgridKB/SeedGrid/Versoes.md +64 -0
- package/seedgridKB/SeedGrid/Visao-geral.md +35 -0
- package/seedgridKB/manifest.json +142 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, realtime]
|
|
3
|
+
fonte: java/seedgrid-quarkus-ext-realtime
|
|
4
|
+
branch: main
|
|
5
|
+
commit: 8f13639b3c
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Realtime (Quarkus) — visão geral
|
|
10
|
+
|
|
11
|
+
Avisos em tempo real para o **usuário logado** através do **SeedGrid Pulse** (serviço externo de publish/subscribe). A extension entrega ao front um token curto de conexão ao Pulse e repassa ao Pulse todo `SgUserEvent` da plataforma, sem código de negócio no host.
|
|
12
|
+
|
|
13
|
+
- Configuração: [[SeedGrid/Extensions/realtime/Configuracao|Configuração do realtime]]
|
|
14
|
+
- Exemplos: [[SeedGrid/Extensions/realtime/Exemplos|Exemplos do realtime]]
|
|
15
|
+
- Enums: a extension não define enums (nem Java nem de contrato com o Pulse).
|
|
16
|
+
- Porte Rust (crate `seedgrid-rust-crate-realtime`): [[SeedGrid/Extensions/realtime/Rust|Porte Rust]]
|
|
17
|
+
|
|
18
|
+
## Funcionalidades (Confirmado)
|
|
19
|
+
|
|
20
|
+
| Funcionalidade | Onde |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `POST /realtime/connect` (autenticado): emite o token de conexão do usuário **da sessão** (nunca do corpo do request) | `RealtimeController#connect()` |
|
|
23
|
+
| Bean injetável `RealtimePublisher` (`publishToUser`, `issueConnectToken`, `isEnabled`) | `RealtimePublisher` |
|
|
24
|
+
| Observer de `SgUserEvent` (`seedgrid-commons`) em `TransactionPhase.AFTER_SUCCESS` → publica para o usuário | `SgUserEventObserver#onUserEvent` |
|
|
25
|
+
| Rate limit de emissão de token por usuário (janela fixa de 1 min, em memória, por instância) | `ConnectRateLimiter#tryAcquire` |
|
|
26
|
+
| "Nunca derruba quem chama": desligado = no-op; Pulse fora do ar = log WARN e segue | `RealtimePublisher#publishToUser` |
|
|
27
|
+
|
|
28
|
+
## Módulos Maven e coordenadas (Confirmado — `pom.xml`)
|
|
29
|
+
|
|
30
|
+
| Módulo | groupId | artifactId | Versão |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| parent | `br.com.seedgrid` | `seedgrid-realtime-parent` | `2026.10.08` |
|
|
33
|
+
| runtime | `br.com.seedgrid` | `seedgrid-realtime` | `2026.10.08` |
|
|
34
|
+
| deployment | `br.com.seedgrid` | `seedgrid-realtime-deployment` | `2026.10.08` |
|
|
35
|
+
|
|
36
|
+
Parent: `br.com.seedgrid:seedgrid-parent:2026.07.02`. `quarkus-extension.properties` exige Quarkus `[3.30,)`. Status no `quarkus-extension.yaml`: `preview`.
|
|
37
|
+
|
|
38
|
+
## Dependências
|
|
39
|
+
|
|
40
|
+
| Dependência | Versão | Observação |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `br.com.seedgrid:seedgrid-commons` | `2026.10.08` | Só `SgUserEvent` e `CurrentUserProvider`; exclui `quarkus-hibernate-orm` e `quarkus-hibernate-orm-panache` (a extension não tem banco). |
|
|
43
|
+
| `br.com.seedgrid:seedgrid-pulse-publisher` | `2026.10.0` | SDK do Pulse (`PulsePublisher`, `ConnectToken`, `IssueConnectTokenInput`, `PublishMessageInput`). |
|
|
44
|
+
| `quarkus-arc`, `quarkus-rest-jackson`, `quarkus-security` | BOM | — |
|
|
45
|
+
|
|
46
|
+
Requisito implícito (Inferência, pelo uso de `CurrentUserProvider` e `@Authenticated`): o host precisa de um mecanismo de autenticação e de um `CurrentUserProvider` que devolva o `publicId` do usuário (no ecossistema SeedGrid, o `JwtCurrentUserProvider` do `seedgrid-security`, segundo o javadoc de `TestCurrentUserProvider`).
|
|
47
|
+
|
|
48
|
+
## Arquitetura e fluxo
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
flowchart LR
|
|
52
|
+
P[Processo do host / outra extension] -- "Event<SgUserEvent>.fire()" --> O[SgUserEventObserver<br/>AFTER_SUCCESS]
|
|
53
|
+
O --> RP[RealtimePublisher.publishToUser]
|
|
54
|
+
RP -- "publishAsync (x-api-key = project key)" --> PULSE[(SeedGrid Pulse)]
|
|
55
|
+
F[Front logado] -- "POST /realtime/connect" --> C[RealtimeController]
|
|
56
|
+
C --> RL[ConnectRateLimiter]
|
|
57
|
+
C --> RP2[RealtimePublisher.issueConnectToken]
|
|
58
|
+
RP2 --> PULSE
|
|
59
|
+
C -- "token pct_..., expiresAt, connectUrl, channels" --> F
|
|
60
|
+
F -- "WebSocket com o token" --> PULSE
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- O `RealtimePublisher` monta o `PulsePublisher` uma vez, no construtor: só se `seedgrid.realtime.enabled=true` **e** houver `pulse.base-url` e `pulse.project-key` não vazios. Caso contrário fica desligado (com log ERROR se estava ligado mas incompleto). (Confirmado — `RealtimePublisher#connect`)
|
|
64
|
+
- `SgUserEventObserver` só publica depois do commit; se a transação desfizer, não publica. (Confirmado)
|
|
65
|
+
- O Pulse fingido nos testes expõe `POST /api/v1/connect-tokens` e `POST /api/v1/messages`, e o header usado é `x-api-key`. (Confirmado em `RealtimeEnabledTest`; são detalhes do SDK do Pulse, não desta extension.)
|
|
66
|
+
|
|
67
|
+
## API pública e pontos de integração
|
|
68
|
+
|
|
69
|
+
### Endpoint REST
|
|
70
|
+
|
|
71
|
+
| Método | Path | Segurança | Respostas |
|
|
72
|
+
|---|---|---|---|
|
|
73
|
+
| `POST` | `/realtime/connect` (sob o root path REST do host, se houver) | `@Authenticated` | `200 {enabled:false}` (desligado); `200 {enabled:true, token, expiresAt, connectUrl, channels}`; `401` sem usuário (inclusive o UUID zerado `00000000-0000-0000-0000-000000000000` do provider padrão); `429` com `Retry-After` (segundos); `503` se o Pulse falhar |
|
|
74
|
+
|
|
75
|
+
`RealtimeConnectResponse` usa `@JsonInclude(NON_NULL)`; `expiresAt` é epoch em **segundos**; `channels` ausente = todos os da conta. (Confirmado)
|
|
76
|
+
|
|
77
|
+
### Beans / eventos
|
|
78
|
+
|
|
79
|
+
| Símbolo | Tipo | Uso |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `br.com.seedgrid.realtime.services.RealtimePublisher` | `@ApplicationScoped`, unremovable | `publishToUser(String channel, String event, String userPublicId, Map<String,Object> data)`; `issueConnectToken(String userPublicId)` (lança `IllegalStateException` se desligado); `isEnabled()` |
|
|
82
|
+
| `br.com.seedgrid.commons.events.SgUserEvent` | evento CDI (do commons) | `new SgUserEvent(userPublicId, channel, event, data)` — observado pela extension |
|
|
83
|
+
| `br.com.seedgrid.realtime.configs.RealtimeConfig` | `@ConfigMapping` | ver [[SeedGrid/Extensions/realtime/Configuracao|Configuração]] |
|
|
84
|
+
|
|
85
|
+
O deployment (`SeedgridRealtimeProcessor`) registra a feature `seedgrid-realtime`, o `ConfigMappingBuildItem` e torna `RealtimePublisher`, `ConnectRateLimiter` e `SgUserEventObserver` unremovable.
|
|
86
|
+
|
|
87
|
+
## Migrations
|
|
88
|
+
|
|
89
|
+
Nenhuma (a extension não tem banco). (Confirmado)
|
|
90
|
+
|
|
91
|
+
## Testes existentes (não executados)
|
|
92
|
+
|
|
93
|
+
| Teste | O que cobre |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `runtime/src/test/.../RealtimeEnabledTest` | 401 sem sessão; token do usuário da sessão com a project key; `subject` do request ignorado; 503 com Pulse fora; `SgUserEvent` vira publicação; erro do Pulse não chega a quem disparou |
|
|
96
|
+
| `runtime/src/test/.../RealtimeDisabledTest` | desligado: `{enabled:false}` e nenhuma chamada ao Pulse |
|
|
97
|
+
| `runtime/src/test/.../services/ConnectRateLimiterTest` | limite por usuário, reabertura da janela, `0` desliga |
|
|
98
|
+
| `FakePulse` (WireMock) / `TestCurrentUserProvider` | infraestrutura de teste |
|
|
99
|
+
|
|
100
|
+
## Limitações
|
|
101
|
+
|
|
102
|
+
- O rate limit é **em memória e por instância**: com N réplicas o limite efetivo por usuário é até N × `connect-rate-per-minute` (Inferência a partir de `ConnectRateLimiter`, que usa `ConcurrentHashMap` local). As janelas antigas não são removidas do mapa (Inferência: não há limpeza no código).
|
|
103
|
+
- A configuração é lida uma vez na construção do `RealtimePublisher` (bean `@ApplicationScoped`); mudar config exige reinício (Inferência).
|
|
104
|
+
- `data` deve carregar apenas identificadores, nunca conteúdo (recomendação do README; não há validação no código).
|
|
105
|
+
- O `NO_USER` (UUID zerado) é uma convenção do `DefaultCurrentUserProvider` do commons; outros providers que devolvam outro sentinela não são tratados (Inferência).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, resilience, rust, configuracao]
|
|
3
|
+
fonte: rust/seedgrid-rust-crate-resilience
|
|
4
|
+
branch: main
|
|
5
|
+
commit: be738a2d1e
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# resilience — Configuração Rust
|
|
10
|
+
|
|
11
|
+
Visão do crate: [[SeedGrid/Extensions/resilience/Rust|Porte Rust do resilience]] · Java: [[SeedGrid/Extensions/resilience/Configuracao|Configuração do resilience (Java)]].
|
|
12
|
+
|
|
13
|
+
## Cabeçalho
|
|
14
|
+
| Item | Valor |
|
|
15
|
+
|---|---|
|
|
16
|
+
| Mecanismo | (1) structs-"anotação" `SeedGridCircuitBreaker` e `SeedGridRateLimit` com `Default` (= defaults das anotações Java); (2) overrides por nome lidos de uma `ConfigSource` injetada (`src/config.rs`) |
|
|
17
|
+
| Variáveis de ambiente | **nenhuma** — `grep` por `env::var` em `src/` não encontra nada (**Confirmado**) |
|
|
18
|
+
| `ConfigSource` disponíveis | `EmptyConfigSource` (nenhuma chave) e `MapConfigSource` (mapa tipado). **Não existe** implementação baseada em ambiente no workspace |
|
|
19
|
+
| Uso real no workspace | security (`src/ratelimit.rs`) e `seedgrid-runtime` (exemplo/teste) usam `EmptyConfigSource` → os overrides abaixo **não têm efeito** a menos que o host implemente `ConfigSource` |
|
|
20
|
+
| Leitura | a cada `try_acquire` / `resolve` (sem cache) |
|
|
21
|
+
| Equivalente Java | `Config#getOptionalValue` em `RateLimitService.java` e `CircuitBreakerConfigResolver.java`; mesmas chaves |
|
|
22
|
+
|
|
23
|
+
Os overrides só são consultados quando a "anotação" tem `config_key` (rate limit) ou `name` (circuit breaker) não vazio.
|
|
24
|
+
|
|
25
|
+
## Overrides de rate limit — `seedgrid.ratelimit.{configKey}.*`
|
|
26
|
+
| Propriedade | Tipo (`ConfigValue`) | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Leitura | Origem |
|
|
27
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
28
|
+
| `seedgrid.ratelimit.{configKey}.enabled` | `Bool` | `true`/`false` | `true` | Não | `false` libera sem tocar o cache (kill switch) | `RateLimitService::try_acquire` | — | por chamada | `src/ratelimiter/service.rs` → `is_enabled` |
|
|
29
|
+
| `seedgrid.ratelimit.{configKey}.permits` | `Long` | > 0 | `SeedGridRateLimit.permits` (5) | Não | permits por janela; ≤ 0 → panic | `resolve_permits` | `burst-capacity` | por chamada | idem |
|
|
30
|
+
| `seedgrid.ratelimit.{configKey}.window` | `Duration` | > 0 | `window` × `window_unit` (30 s) | Não | tamanho da janela | `resolve_window_ms` | — | por chamada | idem |
|
|
31
|
+
| `seedgrid.ratelimit.{configKey}.min-spacing` | `Duration` | ≥ 0 | `min_spacing` × `min_spacing_unit` (0) | Não | espaçamento mínimo entre chamadas | `resolve_min_spacing_ms` | — | por chamada | idem |
|
|
32
|
+
| `seedgrid.ratelimit.{configKey}.burst-capacity` | `Long` | ≥ 0 (0 = usa `permits`) | `burst_capacity` (0) | Não | capacidade do token bucket | `resolve_burst_capacity` | `algorithm` | por chamada | idem |
|
|
33
|
+
| `seedgrid.ratelimit.{configKey}.algorithm` | `Str` | `FIXED_WINDOW`, `SLIDING_WINDOW`, `TOKEN_BUCKET` (`trim`, maiúsculas, `-`→`_`); outro → **panic** | `algorithm` (`FIXED_WINDOW`) | Não | escolhe a estratégia | `resolve_algorithm` / `parse_algorithm` | — | por chamada | idem |
|
|
34
|
+
|
|
35
|
+
`configKey`s usados no workspace (fixos em código): `export-create` (backup, 10/hora), `billing-checkout` (30/min), `billing-portal` (30/min), `billing-webhook` (300/min); a security define os seus em `rust/seedgrid-rust-crate-security/src/ratelimit.rs` (fora deste escopo).
|
|
36
|
+
|
|
37
|
+
## Overrides de circuit breaker — `seedgrid.circuitbreaker.{name}.*`
|
|
38
|
+
| Propriedade | Tipo | Valores aceitos | Padrão (`SeedGridCircuitBreaker::default`) | Obrigatória | Comportamento | Uso | Relações | Leitura | Origem |
|
|
39
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
40
|
+
| `seedgrid.circuitbreaker.{name}.enabled` | `Bool` | bool | `true` | Não | `false` desliga o circuito | `CircuitBreakerConfigResolver::is_enabled` | — | por chamada | `src/circuitbreaker/config_resolver.rs` |
|
|
41
|
+
| `.sliding-window-size` | `Int` | inteiro | `100` | Não | slots da janela (em `TIME_BASED`, segundos) | `resolve` | `window-type` | idem | idem |
|
|
42
|
+
| `.window-type` | `Str` | `COUNT_BASED`, `TIME_BASED` (normalizado); outro → **panic** | `COUNT_BASED` | Não | tipo de janela | `resolve` / `parse_window_type` | — | idem | idem |
|
|
43
|
+
| `.failure-rate-threshold` | `Double` | % | `50.0` | Não | abre ao atingir a taxa de falha | `resolve` | `minimum-number-of-calls` | idem | idem |
|
|
44
|
+
| `.slow-call-duration` | `Duration` | duração (→ ms) | `0` (desligado) | Não | limiar de chamada lenta | `resolve` | `slow-call-rate-threshold` | idem | idem |
|
|
45
|
+
| `.slow-call-rate-threshold` | `Double` | % | `100.0` | Não | abre pela taxa de lentidão | `resolve` | — | idem | idem |
|
|
46
|
+
| `.minimum-number-of-calls` | `Int` | inteiro | `20` | Não | mínimo antes de avaliar | `resolve` | — | idem | idem |
|
|
47
|
+
| `.open-state-duration` | `Duration` | duração (→ ms) | `30000` ms | Não | tempo em OPEN antes de HALF_OPEN | `resolve` | — | idem | idem |
|
|
48
|
+
| `.permitted-calls-in-half-open` | `Int` | inteiro | `10` | Não | sondagens em HALF_OPEN | `resolve` | — | idem | idem |
|
|
49
|
+
|
|
50
|
+
`record_exceptions` (padrão `["Exception"]`), `ignore_exceptions` e `fallback_method` existem só na struct (referências de código).
|
|
51
|
+
|
|
52
|
+
## Defaults de `SeedGridRateLimit`
|
|
53
|
+
`permits=5`, `window=30`, `window_unit=Seconds`, `algorithm=FixedWindow`, `burst_capacity=0`, `min_spacing=0`, `min_spacing_unit=Millis`, `identity=IpIdentityResolver`, `group=""`, `scope=Method`, `emit_headers=true`, `config_key=""` (`src/ratelimiter/config.rs`).
|
|
54
|
+
|
|
55
|
+
## Features Cargo
|
|
56
|
+
Nenhuma.
|
|
57
|
+
|
|
58
|
+
## Configuração mínima (programática)
|
|
59
|
+
```rust
|
|
60
|
+
let source = MapConfigSource::new()
|
|
61
|
+
.with("seedgrid.ratelimit.billing-checkout.permits", ConfigValue::Long(60))
|
|
62
|
+
.with("seedgrid.ratelimit.billing-checkout.window", ConfigValue::Duration(Duration::from_secs(60)));
|
|
63
|
+
let service = RateLimitService::new(cache, Arc::new(source));
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Receitas
|
|
67
|
+
- **Desligar um rate limit em produção**: `seedgrid.ratelimit.<configKey>.enabled = Bool(false)` numa `ConfigSource` do host.
|
|
68
|
+
- **Ler overrides do ambiente**: implementar `ConfigSource` no host (ex.: mapear `SEEDGRID_RATELIMIT_<KEY>_PERMITS`) — não fornecido pela crate.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, resilience, configuracao]
|
|
3
|
+
fonte: java/seedgrid-quarkus-ext-resilience
|
|
4
|
+
branch: main
|
|
5
|
+
commit: eff41304f8
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Configuração — SeedGrid Resilience (Quarkus)
|
|
10
|
+
|
|
11
|
+
Voltar: [[SeedGrid/Extensions/resilience/Visao-geral|Visão geral do resilience]] · [[SeedGrid/Extensions/resilience/Exemplos|Exemplos]] · [[SeedGrid/Extensions/resilience/Rust|Porte Rust]]
|
|
12
|
+
|
|
13
|
+
## 1. Cabeçalho
|
|
14
|
+
|
|
15
|
+
| Item | Valor |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Prefixos | `seedgrid.ratelimit.{configKey}.*` e `seedgrid.circuitbreaker.{name}.*` (segmento dinâmico vindo da anotação) |
|
|
18
|
+
| Classes que leem config | `runtime/.../ratelimiter/services/RateLimitService.java` (`CONFIG_PREFIX = "seedgrid.ratelimit."`) e `runtime/.../circuitbreaker/configs/CircuitBreakerConfigResolver.java` (`CONFIG_PREFIX = "seedgrid.circuitbreaker."`) |
|
|
19
|
+
| Mecanismo | `org.eclipse.microprofile.config.Config` injetado por construtor + `config.getOptionalValue(prefixo + chave, Tipo.class)` com chave montada por concatenação. **Não há** `@ConfigMapping`, `@ConfigRoot`, `@ConfigProperty`, `ConfigProvider.getConfig()`, `META-INF/microprofile-config.properties` nem `application.properties` em `src/main` do runtime. O deployment (`SeedgridResilienceCoreProcessor`) só registra a feature; não há `*BuildTimeConfig`/`*RuntimeConfig` nem `@IfBuildProperty`/`@LookupIfProperty`. **Confirmado** (grep + leitura de todos os `.java`). |
|
|
20
|
+
| Fase | **Runtime**. Lidas **a cada invocação** do método anotado (sem cache local). **Inferência**: alterações em uma fonte de config dinâmica seriam percebidas sem reinício; com `application.properties` estático, valem do boot em diante. |
|
|
21
|
+
| Precedência | Atributo da anotação = padrão; property (quando a anotação tem `configKey`/`name` não vazio) = override. Sem `configKey`/`name`, **nenhuma** property é lida. |
|
|
22
|
+
| MicroProfile Fault Tolerance | Não usado; chaves `<classe>/<método>/<Anotação>/<param>` não têm efeito nesta extension. |
|
|
23
|
+
|
|
24
|
+
**Variáveis de ambiente (Inferência)**: as leituras são por nome exato via `Config#getOptionalValue`; a fonte de env do SmallRye procura o nome exato, depois com não alfanuméricos trocados por `_`, depois em maiúsculas. Logo `seedgrid.ratelimit.login.permits` ↔ `SEEDGRID_RATELIMIT_LOGIN_PERMITS` e `seedgrid.ratelimit.forgot-password.enabled` ↔ `SEEDGRID_RATELIMIT_FORGOT_PASSWORD_ENABLED`. Como a busca parte do nome (não da env), não há ambiguidade na leitura; mas `configKey` `forgot-password` e `forgot.password` colidiriam na mesma env. Não testado no repositório.
|
|
25
|
+
|
|
26
|
+
Legenda da coluna *Padrão*: "atributo X da anotação" significa que, sem a property, vale o valor informado na anotação; entre parênteses o default declarado na própria anotação.
|
|
27
|
+
|
|
28
|
+
## 2. Propriedades da extension
|
|
29
|
+
|
|
30
|
+
### 2.1 Rate limit — `seedgrid.ratelimit.{configKey}.*`
|
|
31
|
+
|
|
32
|
+
`{configKey}` = `SeedGridRateLimit#configKey()` (texto livre; cada valor é um limiter independente no que diz respeito à config — o contador no cache é definido pela chave `rl:...`, ver [[SeedGrid/Extensions/resilience/Visao-geral|Visão geral]]).
|
|
33
|
+
|
|
34
|
+
| Propriedade | Tipo | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Fase | Origem |
|
|
35
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
36
|
+
| `seedgrid.ratelimit.{configKey}.enabled` | `Boolean` | `true` \| `false` | `true` (fixo no código) | Não | `false` desliga o limiter: `tryAcquire` retorna `allow` sem tocar no cache (headers ainda emitidos com `permits`/janela da anotação se `emitHeaders=true`) | Kill switch por limiter | Só lida se `configKey` não vazio | Runtime (por chamada) | Declaração/consumo: `RateLimitService#isEnabled(String)` chamado em `#tryAcquire` |
|
|
37
|
+
| `seedgrid.ratelimit.{configKey}.permits` | `Long` | inteiro `> 0` (senão `IllegalArgumentException("permits must be > 0")` na chamada) | atributo `permits` da anotação (`5`) | Não | Requisições permitidas por janela; no token bucket é a taxa de reposição por janela e a capacidade quando `burst-capacity` = 0 | Ajustar limite sem recompilar | `window`; `burst-capacity` | Runtime | `RateLimitService#resolvePermits` / `#validateConfig` |
|
|
38
|
+
| `seedgrid.ratelimit.{configKey}.window` | `java.time.Duration` | ISO-8601 (`PT30S`, `PT1M`, `PT1H`) — confirmado pelo README e teste; outros formatos (ex.: `30s`) **não confirmado**. Deve resultar em `> 0` ms | atributo `window` × `windowUnit` (`30` × `SECONDS`) | Não | Tamanho da janela (substitui `window`+`windowUnit`); também TTL das chaves no cache | Ajustar janela | `permits` | Runtime | `RateLimitService#resolveWindowMs` |
|
|
39
|
+
| `seedgrid.ratelimit.{configKey}.algorithm` | `String` → `RateLimitAlgorithm` | `fixed-window`, `sliding-window`, `token-bucket` ou os nomes do enum (`FIXED_WINDOW`, `SLIDING_WINDOW`, `TOKEN_BUCKET`) — [[SeedGrid/Extensions/resilience/Enums#RateLimitAlgorithm|RateLimitAlgorithm]]; case-insensitive, `-`→`_`. Valor inválido → `IllegalArgumentException` na chamada | atributo `algorithm` (`FIXED_WINDOW`) | Não | Seleciona estratégia `FixedWindowAlgorithm` / `SlidingWindowAlgorithm` / `TokenBucketAlgorithm` | Trocar algoritmo em produção | `burst-capacity` só afeta token bucket; `min-spacing` não afeta fixed window | Runtime | `RateLimitService#resolveAlgorithm` / `#resolveStrategy` |
|
|
40
|
+
| `seedgrid.ratelimit.{configKey}.burst-capacity` | `Long` | `>= 0` (negativo → `IllegalArgumentException`) | atributo `burstCapacity` (`0`) | Não | Capacidade do bucket; `0` = usa `permits` | Permitir rajadas | Efetivo só com `token-bucket` | Runtime | `RateLimitService#resolveBurstCapacity`; consumo em `TokenBucketAlgorithm#doAcquire` |
|
|
41
|
+
| `seedgrid.ratelimit.{configKey}.min-spacing` | `java.time.Duration` | ISO-8601 (`PT1S`, `PT0.2S`); `>= 0` | atributo `minSpacing` × `minSpacingUnit` (`0` × `MILLIS`; se `minSpacing>0` mas < 1 ms, vira 1 ms) | Não | Intervalo mínimo entre chamadas permitidas da mesma chave | Anti-martelada | **Ignorado** por `FIXED_WINDOW`; aplicado por `SLIDING_WINDOW` e `TOKEN_BUCKET` | Runtime | `RateLimitService#resolveMinSpacingMs`; consumo em `SlidingWindowAlgorithm`/`TokenBucketAlgorithm` |
|
|
42
|
+
|
|
43
|
+
Atributos **somente na anotação** (sem property): `identity`, `group`, `scope`, `emitHeaders`, `configKey`, `windowUnit`, `minSpacingUnit`. **Confirmado** (`RateLimitService` não lê outras chaves).
|
|
44
|
+
|
|
45
|
+
### 2.2 Circuit breaker — `seedgrid.circuitbreaker.{name}.*`
|
|
46
|
+
|
|
47
|
+
`{name}` = `SeedGridCircuitBreaker#name()`. Sem `name`, o circuito usa a assinatura do método como nome e **nenhuma** property é lida.
|
|
48
|
+
|
|
49
|
+
| Propriedade | Tipo | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Fase | Origem |
|
|
50
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
51
|
+
| `seedgrid.circuitbreaker.{name}.enabled` | `Boolean` | `true` \| `false` | `true` (fixo no código) | Não | `false`: interceptor chama o método direto, sem estado nem fallback | Kill switch por circuito | Só lida se `name` não vazio | Runtime (por chamada) | `CircuitBreakerConfigResolver#isEnabled`; consumo `CircuitBreakerInterceptor#around` |
|
|
52
|
+
| `seedgrid.circuitbreaker.{name}.sliding-window-size` | `Integer` | inteiro `> 0` (**Inferência**: 0/negativo quebraria o ring buffer; sem validação explícita) | atributo `slidingWindowSize` (`100`) | Não | Tamanho do ring buffer; em `TIME_BASED` também é a duração da janela **em segundos**. Mudar o valor descarta o estado salvo | Ajustar janela de métricas | `window-type`, `minimum-number-of-calls` | Runtime | `CircuitBreakerConfigResolver#resolve`; consumo `CacheBackedCircuitBreakerStore#addToRing/#evaluateThresholds/#deserialize` |
|
|
53
|
+
| `seedgrid.circuitbreaker.{name}.window-type` | `String` → `SlidingWindowType` | `count-based`, `time-based` (ou `COUNT_BASED`/`TIME_BASED`) — [[SeedGrid/Extensions/resilience/Enums#SlidingWindowType|SlidingWindowType]]; case-insensitive, `-`→`_` | atributo `windowType` (`COUNT_BASED`) | Não | Janela por contagem ou por tempo | Escolher semântica da janela | `sliding-window-size` | Runtime | `CircuitBreakerConfigResolver#resolve`; consumo `CacheBackedCircuitBreakerStore#evaluateThresholds` |
|
|
54
|
+
| `seedgrid.circuitbreaker.{name}.failure-rate-threshold` | `Double` | percentual (0–100; sem validação) | atributo `failureRateThreshold` (`50.0`) | Não | Abre quando `falhas*100/total >= valor` | Sensibilidade a falhas | `minimum-number-of-calls` | Runtime | `CircuitBreakerConfigResolver#resolve`; `CacheBackedCircuitBreakerStore#evaluateThresholds` |
|
|
55
|
+
| `seedgrid.circuitbreaker.{name}.slow-call-duration` | `java.time.Duration` | ISO-8601 (`PT2S`) | atributo `slowCallDurationThresholdMs` (`0` = desligado) | Não | Chamada com duração `>=` valor conta como lenta; `0` desativa a regra de lentidão | Detectar degradação | Necessária (>0) para `slow-call-rate-threshold` ter efeito | Runtime | `CircuitBreakerConfigResolver#resolve` (→ `slowCallDurationThresholdMs`); `CacheBackedCircuitBreakerStore#doRecord/#evaluateThresholds` |
|
|
56
|
+
| `seedgrid.circuitbreaker.{name}.slow-call-rate-threshold` | `Double` | percentual (0–100) | atributo `slowCallRateThreshold` (`100.0`) | Condicional (só tem efeito se `slow-call-duration` > 0) | Abre quando `lentas*100/total >= valor` | Abrir por lentidão | `slow-call-duration` | Runtime | `CircuitBreakerConfigResolver#resolve`; `CacheBackedCircuitBreakerStore#evaluateThresholds` |
|
|
57
|
+
| `seedgrid.circuitbreaker.{name}.minimum-number-of-calls` | `Integer` | inteiro `>= 0` | atributo `minimumNumberOfCalls` (`20`) | Não | Mínimo de chamadas na janela antes de avaliar thresholds | Evitar abrir com amostra pequena | Se > `sliding-window-size`, o circuito nunca abre (**Inferência**: `totalFilled` ≤ tamanho do buffer) | Runtime | `CircuitBreakerConfigResolver#resolve`; `CacheBackedCircuitBreakerStore#evaluateThresholds` |
|
|
58
|
+
| `seedgrid.circuitbreaker.{name}.open-state-duration` | `java.time.Duration` | ISO-8601 (`PT30S`) | atributo `openStateDurationMs` (`30000` ms) | Não | Tempo em `OPEN` antes de ir a `HALF_OPEN`; base do `retryAfterMs`; TTL do estado = `max(2×valor, 1h)` | Tempo de recuperação | `permitted-calls-in-half-open` | Runtime | `CircuitBreakerConfigResolver#resolve`; `CacheBackedCircuitBreakerStore#doAllowCall/#save` |
|
|
59
|
+
| `seedgrid.circuitbreaker.{name}.permitted-calls-in-half-open` | `Integer` | inteiro `> 0` | atributo `permittedCallsInHalfOpen` (`10`) | Não | Nº de sondas em `HALF_OPEN`; todas precisam ter sucesso para fechar | Recuperação gradual | `open-state-duration` | Runtime | `CircuitBreakerConfigResolver#resolve`; `CacheBackedCircuitBreakerStore#doAllowCall/#doRecord` |
|
|
60
|
+
|
|
61
|
+
Atributos **somente na anotação**: `recordExceptions` (default `{Exception.class}`), `ignoreExceptions` (default `{}`), `fallbackMethod` (default `""`), `name`. **Confirmado** (Javadoc de `CircuitBreakerConfigResolver` e código).
|
|
62
|
+
|
|
63
|
+
Total de propriedades públicas da extension: **15** (6 rate limit + 9 circuit breaker), todas com segmento dinâmico.
|
|
64
|
+
|
|
65
|
+
## 3. Propriedades de frameworks/bibliotecas
|
|
66
|
+
|
|
67
|
+
| Propriedade | Situação | Observação | Origem |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `quarkus.redis.hosts` | Recomendada (quando usar `seedgrid-cache-redis`) | Conexão do Redis usado pelo provider de cache | `README.adoc` § "Configuração do Redis" |
|
|
70
|
+
| `quarkus.redis.password` 🔒 | Recomendada (se o Redis exigir) | Use `<SEGREDO:redis-password>` | `README.adoc` |
|
|
71
|
+
| `quarkus.redis.timeout` | Opcional | Exemplo no README: `PT5S` | `README.adoc` |
|
|
72
|
+
| `quarkus.redis.max-pool-size` | Opcional | Exemplo no README: `10` | `README.adoc` |
|
|
73
|
+
|
|
74
|
+
A extension não define nem exige nenhuma propriedade `quarkus.*`/`mp.*`/`smallrye.*` em código. A seleção/configuração do provider de cache (Redis/Valkey/Dragonfly) pertence à lib cache — ver [[SeedGrid/Extensions/cache/Configuracao|Configuração do cache]].
|
|
75
|
+
|
|
76
|
+
## 4. Propriedades usadas só em testes
|
|
77
|
+
|
|
78
|
+
Nenhum `application.properties` com conteúdo (o de `integration-tests/src/main/resources` está vazio). Os testes unitários mockam `Config` com estas chaves (instâncias das propriedades da seção 2):
|
|
79
|
+
|
|
80
|
+
| Chave | Teste |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `seedgrid.ratelimit.checkout.permits`, `.enabled`, `.window`, `.algorithm` (`token-bucket`), `.burst-capacity` | `runtime/src/test/.../RateLimitServiceConfigOverrideTest` |
|
|
83
|
+
| `seedgrid.circuitbreaker.payments.enabled`, `.sliding-window-size`, `.window-type` (`time-based`), `.failure-rate-threshold`, `.slow-call-duration`, `.slow-call-rate-threshold`, `.minimum-number-of-calls`, `.open-state-duration`, `.permitted-calls-in-half-open` | `runtime/src/test/.../CircuitBreakerConfigResolverTest` |
|
|
84
|
+
|
|
85
|
+
Os ITs (`RedisTestBase`) montam o Redis via Testcontainers programaticamente (sem properties).
|
|
86
|
+
|
|
87
|
+
## 5. Aliases, obsoletas, renomeações
|
|
88
|
+
|
|
89
|
+
- `algorithm` e `window-type` aceitam tanto kebab-case quanto o nome do enum (normalização `trim().toUpperCase().replace('-', '_')`). **Confirmado**.
|
|
90
|
+
- `window`/`min-spacing`/`slow-call-duration`/`open-state-duration` substituem pares da anotação (`window`+`windowUnit`, `minSpacing`+`minSpacingUnit`, `slowCallDurationThresholdMs`, `openStateDurationMs`) — nomes diferentes entre anotação e property.
|
|
91
|
+
- Nenhuma propriedade obsoleta encontrada.
|
|
92
|
+
|
|
93
|
+
## 6. Configuração mínima
|
|
94
|
+
|
|
95
|
+
Nenhuma property é obrigatória: as anotações já trazem os valores. Para estado compartilhado entre instâncias, adicione um provider de cache distribuído e sua conexão:
|
|
96
|
+
|
|
97
|
+
```properties
|
|
98
|
+
# Provider seedgrid-cache-redis no classpath
|
|
99
|
+
quarkus.redis.hosts=redis://<host>:6379
|
|
100
|
+
quarkus.redis.password=<SEGREDO:redis-password>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 7. Configuração completa comentada
|
|
104
|
+
|
|
105
|
+
```properties
|
|
106
|
+
# ===== Rate limit: um bloco por configKey usado em @SeedGridRateLimit(configKey = "...") =====
|
|
107
|
+
# liga/desliga o limiter (padrão true)
|
|
108
|
+
seedgrid.ratelimit.<configKey>.enabled=true
|
|
109
|
+
# requisições por janela (> 0)
|
|
110
|
+
seedgrid.ratelimit.<configKey>.permits=<n>
|
|
111
|
+
# janela ISO-8601 (> 0), ex.: PT1M
|
|
112
|
+
seedgrid.ratelimit.<configKey>.window=<duration>
|
|
113
|
+
# fixed-window | sliding-window | token-bucket
|
|
114
|
+
seedgrid.ratelimit.<configKey>.algorithm=<algoritmo>
|
|
115
|
+
# capacidade do bucket (>= 0; 0 = usa permits) — só token-bucket
|
|
116
|
+
seedgrid.ratelimit.<configKey>.burst-capacity=<n>
|
|
117
|
+
# intervalo mínimo entre chamadas (ISO-8601) — ignorado em fixed-window
|
|
118
|
+
seedgrid.ratelimit.<configKey>.min-spacing=<duration>
|
|
119
|
+
|
|
120
|
+
# ===== Circuit breaker: um bloco por name usado em @SeedGridCircuitBreaker(name = "...") =====
|
|
121
|
+
# liga/desliga o circuito (padrão true)
|
|
122
|
+
seedgrid.circuitbreaker.<name>.enabled=true
|
|
123
|
+
# tamanho da janela (nº de chamadas; em time-based também = segundos)
|
|
124
|
+
seedgrid.circuitbreaker.<name>.sliding-window-size=<n>
|
|
125
|
+
# count-based | time-based
|
|
126
|
+
seedgrid.circuitbreaker.<name>.window-type=<tipo>
|
|
127
|
+
# % de falhas para abrir
|
|
128
|
+
seedgrid.circuitbreaker.<name>.failure-rate-threshold=<percentual>
|
|
129
|
+
# duração a partir da qual a chamada é lenta (ISO-8601); omitir/0 desliga
|
|
130
|
+
seedgrid.circuitbreaker.<name>.slow-call-duration=<duration>
|
|
131
|
+
# % de chamadas lentas para abrir (exige slow-call-duration > 0)
|
|
132
|
+
seedgrid.circuitbreaker.<name>.slow-call-rate-threshold=<percentual>
|
|
133
|
+
# mínimo de chamadas antes de avaliar
|
|
134
|
+
seedgrid.circuitbreaker.<name>.minimum-number-of-calls=<n>
|
|
135
|
+
# tempo aberto antes do half-open (ISO-8601)
|
|
136
|
+
seedgrid.circuitbreaker.<name>.open-state-duration=<duration>
|
|
137
|
+
# sondas no half-open
|
|
138
|
+
seedgrid.circuitbreaker.<name>.permitted-calls-in-half-open=<n>
|
|
139
|
+
|
|
140
|
+
# ===== Cache distribuído (lib seedgrid-cache-redis) =====
|
|
141
|
+
quarkus.redis.hosts=redis://<host>:6379
|
|
142
|
+
quarkus.redis.password=<SEGREDO:redis-password>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 8. Receitas
|
|
146
|
+
|
|
147
|
+
**Desligar um limiter em produção** (método com `configKey = "forgot-password"`):
|
|
148
|
+
```properties
|
|
149
|
+
seedgrid.ratelimit.forgot-password.enabled=false
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Afrouxar o login temporariamente** (`configKey = "login"`):
|
|
153
|
+
```properties
|
|
154
|
+
seedgrid.ratelimit.login.permits=200
|
|
155
|
+
seedgrid.ratelimit.login.window=PT30S
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Trocar para token bucket com rajada e espaçamento** (`configKey = "export-create"`):
|
|
159
|
+
```properties
|
|
160
|
+
seedgrid.ratelimit.export-create.algorithm=token-bucket
|
|
161
|
+
seedgrid.ratelimit.export-create.burst-capacity=3
|
|
162
|
+
seedgrid.ratelimit.export-create.min-spacing=PT1S
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Circuito mais sensível, com detecção de lentidão** (`name = "payments"`):
|
|
166
|
+
```properties
|
|
167
|
+
seedgrid.circuitbreaker.payments.failure-rate-threshold=25
|
|
168
|
+
seedgrid.circuitbreaker.payments.minimum-number-of-calls=5
|
|
169
|
+
seedgrid.circuitbreaker.payments.slow-call-duration=PT2S
|
|
170
|
+
seedgrid.circuitbreaker.payments.slow-call-rate-threshold=80
|
|
171
|
+
seedgrid.circuitbreaker.payments.open-state-duration=PT10S
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Janela por tempo de 20 s**:
|
|
175
|
+
```properties
|
|
176
|
+
seedgrid.circuitbreaker.payments.window-type=time-based
|
|
177
|
+
seedgrid.circuitbreaker.payments.sliding-window-size=20
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 9. Dúvidas e lacunas
|
|
181
|
+
|
|
182
|
+
- Formatos de `Duration` além de ISO-8601 (ex.: `30s`): dependem do conversor registrado no Quarkus; **não confirmado** nesta extension.
|
|
183
|
+
- Variáveis de ambiente: mapeamento descrito é **Inferência** (regra SmallRye), sem teste no repositório.
|
|
184
|
+
- Sem validação no boot: chaves com erro de digitação são silenciosamente ignoradas; valores inválidos (`algorithm`, `window-type`, `permits<=0`) só falham na chamada.
|
|
185
|
+
- `sliding-window-size` e `permitted-calls-in-half-open` não têm validação de positivo.
|
|
186
|
+
- Não há propriedade global (ex.: desligar todos os limiters ou escolher store) — apenas por `configKey`/`name`.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, resilience, enum]
|
|
3
|
+
fonte: java/seedgrid-quarkus-ext-resilience
|
|
4
|
+
branch: main
|
|
5
|
+
commit: eff41304f8
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# SeedGrid Resilience — Enums
|
|
10
|
+
|
|
11
|
+
Todos os enums ficam em `runtime/src/main/java/br/com/seedgrid/resilience/core/runtime/`. Enums de teste não foram considerados. Visão geral: [[SeedGrid/Extensions/resilience/Visao-geral|Visão geral]]; propriedades: [[SeedGrid/Extensions/resilience/Configuracao|Configuração]]; equivalentes Rust: [[SeedGrid/Extensions/resilience/Rust|Rust]].
|
|
12
|
+
|
|
13
|
+
## Índice
|
|
14
|
+
|
|
15
|
+
| Enum | Pacote/arquivo | Usado em | Público? |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| [[#RateLimitAlgorithm]] | `ratelimiter.enums` | `@SeedGridRateLimit#algorithm`, propriedade `seedgrid.ratelimit.{configKey}.algorithm` | Sim |
|
|
18
|
+
| [[#KeyScope]] | `circuitbreaker.enums` (pacote do CB, mas só o rate limit usa) | `@SeedGridRateLimit#scope` | Sim |
|
|
19
|
+
| [[#SlidingWindowType]] | `circuitbreaker.enums` | `@SeedGridCircuitBreaker#windowType`, `CircuitBreakerConfig`, propriedade `seedgrid.circuitbreaker.{name}.window-type` | Sim |
|
|
20
|
+
| [[#CircuitState]] | `circuitbreaker.enums` | `CircuitDecision#state()` (retorno do SPI `CircuitBreakerStore#allowCall`), estado persistido no cache | Sim |
|
|
21
|
+
|
|
22
|
+
## RateLimitAlgorithm
|
|
23
|
+
|
|
24
|
+
Arquivo: `ratelimiter/enums/RateLimitAlgorithm.java` — FQN `br.com.seedgrid.resilience.core.runtime.ratelimiter.enums.RateLimitAlgorithm`.
|
|
25
|
+
|
|
26
|
+
| Valor | Significado | Efeito / onde é tratado | Observações |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `FIXED_WINDOW` | Janela fixa (default da anotação) | `RateLimitService#resolveStrategy` → `FixedWindowAlgorithm` | **Confirmado** |
|
|
29
|
+
| `TOKEN_BUCKET` | Balde de tokens | → `TokenBucketAlgorithm`; `burstCapacity` só tem efeito aqui | **Confirmado** |
|
|
30
|
+
| `SLIDING_WINDOW` | Janela deslizante | → `SlidingWindowAlgorithm` | **Confirmado** |
|
|
31
|
+
|
|
32
|
+
- Campos/métodos: nenhum (enum puro). `switch` exaustivo sem `default` em `resolveStrategy`.
|
|
33
|
+
- Serialização: não é persistido nem exposto em JSON. Na config é lido como `String` e convertido por `RateLimitAlgorithm.valueOf(v.trim().toUpperCase().replace('-', '_'))` — aceita `token-bucket`, `Token_Bucket` etc.; valor inválido lança `IllegalArgumentException` **na chamada** (não no boot). Só é lido quando a anotação tem `configKey`.
|
|
34
|
+
- Usado em: [[SeedGrid/Extensions/resilience/Configuracao|`seedgrid.ratelimit.{configKey}.algorithm`]]; anotação `@SeedGridRateLimit`.
|
|
35
|
+
- Todas as constantes são tratadas.
|
|
36
|
+
|
|
37
|
+
## KeyScope
|
|
38
|
+
|
|
39
|
+
Arquivo: `circuitbreaker/enums/KeyScope.java` — FQN `br.com.seedgrid.resilience.core.runtime.circuitbreaker.enums.KeyScope`.
|
|
40
|
+
|
|
41
|
+
| Valor | Significado | Efeito / onde é tratado | Observações |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| `METHOD` | Limite por método (default) | `RateLimitService#buildKey`: `scopePart` = `methodSignature` (`Classe#metodo(tipos)`) | **Confirmado** |
|
|
44
|
+
| `CLASS` | Limite compartilhado por classe | `scopePart` = `invocation.getTarget().getClass().getName()` | **Confirmado**; é a classe do alvo (pode ser subclasse/proxy CDI — **Inferência**) |
|
|
45
|
+
| `GROUP` | Limite compartilhado por grupo lógico | `scopePart` = `ann.group()`; se vazio/blank cai em `methodSignature` | **Confirmado** |
|
|
46
|
+
|
|
47
|
+
- Campos/métodos: nenhum.
|
|
48
|
+
- Serialização: não é lido da config (só atributo da anotação); entra apenas na chave de cache `rl:{tenant}[:{operation}]:{scopePart}:{identity}`.
|
|
49
|
+
- Observação: o enum vive no pacote `circuitbreaker.enums`, mas o circuit breaker não o usa.
|
|
50
|
+
|
|
51
|
+
## SlidingWindowType
|
|
52
|
+
|
|
53
|
+
Arquivo: `circuitbreaker/enums/SlidingWindowType.java` — FQN `br.com.seedgrid.resilience.core.runtime.circuitbreaker.enums.SlidingWindowType`.
|
|
54
|
+
|
|
55
|
+
| Valor | Significado | Efeito / onde é tratado | Observações |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `COUNT_BASED` | Janela por contagem (default) | `CacheBackedCircuitBreakerStore#evaluateThresholds`: sem corte temporal (`windowStart = Long.MIN_VALUE`) | **Confirmado** |
|
|
58
|
+
| `TIME_BASED` | Janela por tempo | `windowStart = now - slidingWindowSize*1000`; `slidingWindowSize` vira **segundos**; entradas com timestamp `<= windowStart` são ignoradas | **Confirmado** |
|
|
59
|
+
|
|
60
|
+
- Campos/métodos: nenhum. O código só compara com `TIME_BASED`; qualquer outro valor se comportaria como `COUNT_BASED`.
|
|
61
|
+
- Serialização: não é persistido no estado do cache. Na config: `String` → `valueOf(v.trim().toUpperCase().replace('-', '_'))`; inválido → `IllegalArgumentException` na chamada. Só é lido quando a anotação tem `name` preenchido (`CircuitBreakerConfigResolver#resolve`).
|
|
62
|
+
- Usado em: [[SeedGrid/Extensions/resilience/Configuracao|`seedgrid.circuitbreaker.{name}.window-type`]]; `@SeedGridCircuitBreaker#windowType`; `CircuitBreakerConfig`.
|
|
63
|
+
|
|
64
|
+
## CircuitState
|
|
65
|
+
|
|
66
|
+
Arquivo: `circuitbreaker/enums/CircuitState.java` — FQN `br.com.seedgrid.resilience.core.runtime.circuitbreaker.enums.CircuitState`.
|
|
67
|
+
|
|
68
|
+
| Valor | Significado | Efeito / onde é tratado | Observações |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `CLOSED` (ordinal 0) | Chamadas passam; métricas gravadas | `doAllowCall` permite; `doRecord` grava no ring e avalia limiares | Estado inicial e fallback de ordinal inválido. **Confirmado** |
|
|
71
|
+
| `OPEN` (ordinal 1) | Chamadas negadas | `doAllowCall`: se `elapsed >= openStateDurationMs` → `HALF_OPEN`, senão `deny(restante)`; `doRecord` ignora resultados | **Confirmado** |
|
|
72
|
+
| `HALF_OPEN` (ordinal 2) | Sondagem com até `permittedCallsInHalfOpen` chamadas | permite até o limite, depois `deny(0)`; falha → `OPEN`; `permittedCallsInHalfOpen` sucessos → `CLOSED` (zera o ring) | **Confirmado** |
|
|
73
|
+
|
|
74
|
+
- Campos/métodos: nenhum.
|
|
75
|
+
- Persistência: **por ordinal** — `circuitState.ordinal()` (int de 4 bytes) no blob binário do cache (`serialize`/`deserialize`); ordinal fora de 0..2 → `CLOSED`. **Reordenar ou inserir constantes quebra estados já gravados e a compatibilidade com o Rust** (ver [[SeedGrid/Extensions/resilience/Rust|Rust]]). Sem JSON, sem tabela SQL/CHECK.
|
|
76
|
+
- Exposição: `CircuitDecision#state()` retorna `CLOSED`/`HALF_OPEN` em `allow` e sempre `OPEN` em `deny`; nenhum código do módulo lê `state()` (o interceptor usa só `allowed()` e `retryAfterMs()`).
|
|
77
|
+
|
|
78
|
+
```mermaid
|
|
79
|
+
stateDiagram-v2
|
|
80
|
+
[*] --> CLOSED
|
|
81
|
+
CLOSED --> OPEN: limiar de falha/lentidão
|
|
82
|
+
OPEN --> HALF_OPEN: openStateDuration expirou (próxima chamada)
|
|
83
|
+
HALF_OPEN --> OPEN: qualquer falha
|
|
84
|
+
HALF_OPEN --> CLOSED: permittedCallsInHalfOpen sucessos
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
A transição `CLOSED → OPEN` está em `CacheBackedCircuitBreakerStore` (`s.circuitState = CircuitState.OPEN` após `evaluateThresholds`).
|
|
88
|
+
|
|
89
|
+
## Internos
|
|
90
|
+
|
|
91
|
+
Nenhum enum interno em `src/main`; os 4 acima são públicos. Enum do JDK no contrato: `java.time.temporal.ChronoUnit` em `@SeedGridRateLimit#windowUnit` (default `SECONDS`) e `#minSpacingUnit` (default `MILLIS`).
|
|
92
|
+
|
|
93
|
+
## Notas Rust
|
|
94
|
+
|
|
95
|
+
- Grafia em PascalCase (`Closed`, `HalfOpen`, `CountBased`…); valor de string inválido causa `panic!`.
|
|
96
|
+
- `TimeUnit` aceita só `Millis`, `Seconds`, `Minutes`, `Hours`.
|