funifier-mcp 0.3.19 → 0.3.20

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.
@@ -0,0 +1,150 @@
1
+ {
2
+ "funifier-create-trigger": [
3
+ "knowledge/modules/trigger.md",
4
+ "knowledge/guides/triggers-guide.md",
5
+ "knowledge/guides/java-entities.md",
6
+ "knowledge/guides/java-managers.md",
7
+ "knowledge/guides/trigger-examples.md"
8
+ ],
9
+ "funifier-create-scheduler": [
10
+ "knowledge/modules/scheduler.md",
11
+ "knowledge/guides/java-managers.md",
12
+ "knowledge/guides/java-libraries.md"
13
+ ],
14
+ "funifier-query-aggregate": [
15
+ "knowledge/guides/aggregates.md",
16
+ "knowledge/guides/database-access.md",
17
+ "knowledge/modules/database.md"
18
+ ],
19
+ "funifier-create-aggregate": [
20
+ "knowledge/guides/aggregates.md",
21
+ "knowledge/guides/database-access.md",
22
+ "knowledge/modules/database.md"
23
+ ],
24
+ "funifier-date-handling": [
25
+ "knowledge/guides/database-access.md",
26
+ "knowledge/guides/aggregates.md",
27
+ "knowledge/modules/database.md"
28
+ ],
29
+ "funifier-create-challenge": [
30
+ "knowledge/modules/challenge.md",
31
+ "knowledge/modules/action.md",
32
+ "knowledge/modules/point.md"
33
+ ],
34
+ "funifier-create-action": [
35
+ "knowledge/modules/action.md",
36
+ "knowledge/modules/action-log.md"
37
+ ],
38
+ "funifier-create-audit": [
39
+ "knowledge/modules/audit.md",
40
+ "knowledge/modules/database.md"
41
+ ],
42
+ "funifier-create-point": [
43
+ "knowledge/modules/point.md",
44
+ "knowledge/modules/achievement.md"
45
+ ],
46
+ "funifier-create-level": [
47
+ "knowledge/modules/level.md",
48
+ "knowledge/modules/point.md"
49
+ ],
50
+ "funifier-create-leaderboard": [
51
+ "knowledge/modules/leaderboard.md",
52
+ "knowledge/modules/point.md",
53
+ "knowledge/modules/action.md"
54
+ ],
55
+ "funifier-create-quiz": [
56
+ "knowledge/modules/quiz.md",
57
+ "knowledge/modules/question.md"
58
+ ],
59
+ "funifier-create-virtual-good": [
60
+ "knowledge/modules/virtual-good.md",
61
+ "knowledge/modules/point.md"
62
+ ],
63
+ "funifier-create-custom-page": [
64
+ "knowledge/modules/studio-page.md",
65
+ "knowledge/guides/aggregates.md",
66
+ "knowledge/modules/auth.md"
67
+ ],
68
+ "funifier-implement-frontend": [
69
+ "knowledge/modules/auth.md",
70
+ "knowledge/guides/aggregates.md",
71
+ "knowledge/guides/database-access.md",
72
+ "knowledge/modules/patterns.md",
73
+ "knowledge/modules/action-log.md",
74
+ "knowledge/modules/static-repo.md"
75
+ ],
76
+ "funifier-debug": [
77
+ "knowledge/guides/java-libraries.md",
78
+ "knowledge/modules/trigger.md",
79
+ "knowledge/guides/triggers-guide.md",
80
+ "knowledge/guides/trigger-examples.md",
81
+ "knowledge/modules/scheduler.md",
82
+ "knowledge/guides/aggregates.md",
83
+ "knowledge/modules/public.md"
84
+ ],
85
+ "funifier-help": [
86
+ "knowledge/index.md"
87
+ ],
88
+ "funifier-manage-player": [
89
+ "knowledge/modules/player.md"
90
+ ],
91
+ "funifier-manage-team": [
92
+ "knowledge/modules/team.md"
93
+ ],
94
+ "funifier-create-competition": [
95
+ "knowledge/modules/competition.md"
96
+ ],
97
+ "funifier-create-folder": [
98
+ "knowledge/modules/folder.md"
99
+ ],
100
+ "funifier-create-lottery": [
101
+ "knowledge/modules/lottery.md"
102
+ ],
103
+ "funifier-create-mystery": [
104
+ "knowledge/modules/mystery.md"
105
+ ],
106
+ "funifier-create-story": [
107
+ "knowledge/modules/story.md"
108
+ ],
109
+ "funifier-create-crossword": [
110
+ "knowledge/modules/crossword.md"
111
+ ],
112
+ "funifier-create-notification": [
113
+ "knowledge/modules/notification.md"
114
+ ],
115
+ "funifier-create-lastmile": [
116
+ "knowledge/modules/lastmile.md"
117
+ ],
118
+ "funifier-create-webhook": [
119
+ "knowledge/modules/webhook.md"
120
+ ],
121
+ "funifier-create-websocket": [
122
+ "knowledge/modules/websocket.md"
123
+ ],
124
+ "funifier-create-widget": [
125
+ "knowledge/modules/widget.md"
126
+ ],
127
+ "funifier-create-swap": [
128
+ "knowledge/modules/swap.md"
129
+ ],
130
+ "funifier-create-custom-object": [
131
+ "knowledge/modules/custom-object.md",
132
+ "knowledge/modules/database.md"
133
+ ],
134
+ "funifier-configure-security": [
135
+ "knowledge/modules/security.md"
136
+ ],
137
+ "funifier-import-csv": [
138
+ "knowledge/modules/csv-data.md"
139
+ ],
140
+ "funifier-upload-file": [
141
+ "knowledge/modules/upload.md"
142
+ ],
143
+ "funifier-manage-indexes": [
144
+ "knowledge/modules/database.md"
145
+ ],
146
+ "funifier-audit-permissions": [
147
+ "knowledge/guides/permission-audit.md",
148
+ "knowledge/modules/security.md"
149
+ ]
150
+ }
@@ -34,7 +34,7 @@ Este documento reúne exemplos de produção de triggers Funifier — código Gr
34
34
 
35
35
  Todas herdadas de `triggers-guide.md` §1.5 — releia antes de copiar qualquer exemplo. As mais relevantes aqui:
36
36
 
37
- > ⚠️ **`throw` não cancela persistência** rejeição é em `after_create` + `database.delete` (§8, e `triggers-guide.md` §8).
37
+ > ⚠️ **`throw` cancela em `before_*` de coleção de banco (`/v3/database/{collection}`), e apenas em instâncias atualizadas.** No restante (player, action, `after_*`, bulk) a rejeição é em `after_create` + `database.delete` (§8, e `triggers-guide.md` §8). A interrupção é **version-dependent** — confirme com a sonda de `triggers-guide.md` §8 antes de depender dela.
38
38
  > ⚠️ **Em Groovy use `.id`, não `._id`** → todos os exemplos abaixo seguem essa regra.
39
39
  > ⚠️ **Nunca `track` a mesma ação que disparou o trigger** → loop infinito; filtre por `entity.actionId` no início (§10).
40
40
 
@@ -247,13 +247,13 @@ void trigger(event, entity, player, database) {
247
247
  ## 7. `custom__c | before_create` — Normalizar Campos
248
248
 
249
249
  Quando usar: enriquecer/normalizar um documento `__c` antes de salvar.
250
- Não usar quando: precisa **rejeitar** o documento → §8 (`before_create` não rejeita).
250
+ Não usar quando: precisa **rejeitar** o documento de forma portável → §8 (`after_create` + delete funciona em qualquer instância).
251
251
  Depende de: —
252
252
  Disponível em: Trigger.
253
253
 
254
254
  ### Descrição
255
255
 
256
- `before_create` **não rejeita** (`triggers-guide.md` §8) apenas normaliza email/nome/ID antes de persistir. Métodos auxiliares extraídos para clareza.
256
+ Normaliza email/nome/ID antes de persistir. Métodos auxiliares extraídos para clareza. **Nota:** em instâncias atualizadas, um `throw` aqui também **rejeita** o documento (modo estrito — `triggers-guide.md` §8, Modo A), mas isso é version-dependent; para rejeição garantida em qualquer instância, use o padrão `after_create` do §8.
257
257
 
258
258
  ### Uso
259
259
 
@@ -288,7 +288,7 @@ String extractNumeric(Object value) {
288
288
 
289
289
  ### Armadilhas conhecidas
290
290
 
291
- - **Esperar que `before_create` rejeite valores inválidos** → não rejeita; o documento salva. Correção: validar/rejeitar em `after_create` (§8).
291
+ - **Contar com `throw` no `before_create` em qualquer instância** → rejeita no modo estrito (instâncias atualizadas; `triggers-guide.md` §8). Para portabilidade, valide/rejeite em `after_create` (§8) ou confirme o suporte com a sonda de §8 antes.
292
292
 
293
293
  ---
294
294
 
@@ -301,7 +301,7 @@ Disponível em: Trigger.
301
301
 
302
302
  ### Descrição
303
303
 
304
- O documento já foi salvo (`triggers-guide.md` §1.5). Se inválido, deleta e devolve a mensagem no `entity`. `BusinessException` centraliza o cleanup das múltiplas validações.
304
+ Padrão **portável** (funciona em toda instância, atualizada ou não): o documento já foi salvo (`triggers-guide.md` §1.5, Modo B). Se inválido, deleta e devolve a mensagem no `entity`. `BusinessException` centraliza o cleanup das múltiplas validações. Em instâncias atualizadas, a mesma validação pode rodar em `before_create` com `throw` (Modo A — aborta antes de salvar e não exige delete), mas o `after_create` abaixo é a escolha segura quando o suporte é desconhecido.
305
305
 
306
306
  ### Uso
307
307
 
@@ -346,7 +346,7 @@ public class BusinessException extends Exception {
346
346
 
347
347
  ### Armadilhas conhecidas
348
348
 
349
- - **Usar `before_create` aqui** → `throw` não impede o save (`triggers-guide.md` §8). Correção: `after_create` + delete.
349
+ - **Usar `before_create` aqui sem confirmar suporte** → `throw` impede o save no modo estrito (version-dependent; `triggers-guide.md` §8). Em instância antiga o documento salva. Correção: `after_create` + delete, ou sondar o suporte antes (§8 do guia).
350
350
  - **Esquecer `entity.clear()`** → o documento rejeitado mantém os dados. Correção: limpar e devolver só `message`.
351
351
 
352
352
  ---
@@ -13,7 +13,7 @@ Este documento descreve os conceitos de triggers Funifier — assinatura do scri
13
13
  - Consulte ao **criar uma trigger** e precisar entender `event`, `entity`, `script`.
14
14
  - Consulte ao precisar saber **quais variáveis** o script recebe (`event`, `entity`, `player`, `database`, `manager`, `context`).
15
15
  - Consulte ao escolher o **evento certo** (incl. eventos de bulk/CSV).
16
- - Consulte ao precisar **rejeitar uma criação** ou entender por que `throw` não cancela.
16
+ - Consulte ao precisar **rejeitar/abortar uma operação** incluindo o novo modo estrito (`before_*` de coleções de banco interrompem via `throw`) e por que `throw` não cancela nos demais casos.
17
17
  - Consulte ao precisar ler a **origem de uma conquista** (`context.extra`).
18
18
 
19
19
  ### 1.3 Quando NÃO consultar
@@ -33,12 +33,12 @@ Este documento descreve os conceitos de triggers Funifier — assinatura do scri
33
33
  | Escolher a entidade | Entidades disponíveis | §5 |
34
34
  | Saber o tipo do `entity` para cada combinação | Combinações Evento × Entidade | §6 |
35
35
  | Saber qual ação/conquista originou o evento | `context.extra` (cadeia de conquistas) | §7 |
36
- | Rejeitar uma criação inválida | Regra de persistência + BusinessException | §8 |
36
+ | Abortar/rejeitar uma operação | Modo estrito (`before_*` de banco via `throw`) ou `after_create` + delete | §8 |
37
37
  | Processar import/insert em lote | `entity` como List (bulk) | §9 |
38
38
 
39
39
  ### 1.5 Restrições globais críticas
40
40
 
41
- > ⚠️ **`throw` NÃO cancela a persistência.** O Funifier persiste o documento; lançar exceção não faz rollback (`trigger-examples.md`). Consequência: tentativas de "bloquear" criação com `throw` falham silenciosamente. `before_create` serve para **enriquecer** (normalizar, defaults, metadados); para **rejeitar**, use `after_create` + `database.delete(...)` (§8).
41
+ > ⚠️ **`throw` cancela a operação APENAS em `before_create`/`before_update`/`before_delete` de coleções de banco (`POST`/`PUT`/`DELETE /v3/database/{collection}`).** Nesses três eventos o `DatabaseRest` roda a trigger em **modo estrito** (`TriggerManager.executeStrict`): qualquer exceção lançada no script aborta a operação — o documento **não** é gravado/removido e a API responde **HTTP 500** com `{ "message": "<mensagem>", "statusCode": 500 }` (§8). **Em todo o resto** — player, action, swap, quiz, achievement disparados pelos managers, eventos `*_bulk`/`csv_*` (inclusive bulk insert em coleção de banco), e qualquer evento `after_*` — `throw` **NÃO** cancela: o documento persiste e a exceção só vai para o `trigger_log`. → Nesses casos, para rejeitar, use `after_create` + `database.delete(...)` (§8). ⚠️ O modo estrito é **version-dependent** — nem toda instância recebeu a atualização; **sonde antes de depender dele** (§8) ou use o `after_create` por padrão.
42
42
 
43
43
  > ⚠️ **Em Groovy, acesse o ID com `entity.id`, não `entity._id`.** Consequência: `_id` retorna `null` (`patterns.md` §10). → Use `.id` em código; `_id` só no JSON/documento. Vale para `entity`, `player`, `ActionLog`, `Achievement`.
44
44
 
@@ -168,7 +168,7 @@ Eventos definem **quando** a trigger roda. Os oficiais aparecem no dropdown do S
168
168
 
169
169
  ### Armadilhas conhecidas
170
170
 
171
- - **`before_create` esperando bloquear via `throw`** → não cancela (§1.5/§8).
171
+ - **`before_create` esperando bloquear via `throw`** → só cancela em coleções de banco (`/v3/database/{collection}`); para player/action/etc. não cancela (§1.5/§8).
172
172
  - **`POST` esperando acionar `before_update`** → `POST` dispara `before_create` (§1.5).
173
173
 
174
174
  ---
@@ -277,19 +277,39 @@ Exemplo de uso em `trigger-examples.md` (Exemplo 2 — taggear conquista com ori
277
277
 
278
278
  ---
279
279
 
280
- ## 8. Regra de Persistência e Rejeição de Criação
280
+ ## 8. Abortar uma Operação e Rejeitar Documentos
281
281
 
282
- Quando usar: ao precisar validar e **rejeitar** uma criação inválida.
283
- Não usar quando: só precisa enriquecer/normalizar → use `before_create` (mutação direta).
284
- Depende de: `database.delete(...)`, BusinessException (definida no próprio script).
282
+ Quando usar: ao precisar validar e **rejeitar/abortar** uma operação.
283
+ Não usar quando: só precisa enriquecer/normalizar → mutação direta no `before_create`.
284
+ Depende de: modo estrito (`executeStrict` — `modules/trigger.md` §2.5.1) ou `database.delete(...)` + BusinessException.
285
285
  Disponível em: Trigger.
286
286
 
287
287
  ### Descrição
288
288
 
289
- O Funifier persiste o documento; `throw` não cancela (§1.5). Portanto: `before_create` enriquece; **rejeição** se faz em `after_create`, deletando o documento já salvo e devolvendo a mensagem no `entity`.
289
+ Existem **dois modos**, e qual vale depende de **onde** a trigger roda:
290
+
291
+ **Modo A — estrito (`throw` aborta).** Vale **só** para `before_create`/`before_update`/`before_delete` de **coleções de banco** acionadas pela Database API (`POST`/`PUT`/`DELETE /v3/database/{collection}`). O `DatabaseRest` roda essas triggers via `TriggerManager.executeStrict`: lançar exceção no script **aborta a operação** — o documento não é gravado/removido e a API devolve **HTTP 500** com `{ "message": "<mensagem>", "statusCode": 500 }` (corpo `CallbackMessage`). É o caminho preferido para rejeitar documentos `__c`.
292
+
293
+ **Modo B — persistência garantida (`throw` não cancela).** Vale para **todo o resto**: player, action, swap, quiz, achievement disparados pelos managers; eventos `after_*`; e os eventos de lote (`*_bulk`/`csv_*`, inclusive bulk insert em coleção de banco). Aqui o Funifier persiste o documento antes de rodar a trigger e `throw` não faz rollback — a rejeição se faz em `after_create`, deletando o documento já salvo e devolvendo a mensagem no `entity`.
290
294
 
291
295
  ### Uso
292
296
 
297
+ **Modo A — `before_create`/`before_update`/`before_delete` de coleção `__c` (Database API):**
298
+
299
+ ```groovy
300
+ void trigger(event, entity, player, database) {
301
+ // entity é um HashMap do documento __c (em before_delete, é a lista de ids)
302
+ if (entity.get("email") == null) {
303
+ throw new RuntimeException("E-mail é obrigatório"); // aborta o POST; volta como message no 500
304
+ }
305
+ entity.put("email", entity.get("email").toString().toLowerCase()); // enriquecimento normal persiste
306
+ }
307
+ ```
308
+
309
+ > ⚠️ No modo estrito, **qualquer** exceção aborta — inclusive `TimeoutException` (estouro do `timeout`, default 5s) e erros de sandbox. Um `before_*` frágil (NPE, token bloqueado) **bloqueia todas as gravações** naquela coleção. Mantenha o script enxuto e proteja com null-checks. Apenas a **primeira** mensagem é devolvida.
310
+
311
+ **Modo B — `after_create` (rejeição com cleanup), para qualquer entidade:**
312
+
293
313
  ```groovy
294
314
  void trigger(event, entity, player, database) {
295
315
  try {
@@ -313,10 +333,53 @@ public class BusinessException extends Exception {
313
333
 
314
334
  `BusinessException` centraliza o cleanup quando há múltiplas validações. Para uma validação simples, o `database.delete` + `entity.clear()` + `entity.put("message", ...)` direto basta.
315
335
 
336
+ ### ⚠️ Disponibilidade — recurso version-dependent
337
+
338
+ O **Modo A (estrito)** depende da versão do Funifier Engine da instância: `executeStrict` foi adicionado no `DatabaseRest` em uma atualização recente e **nem toda instância já recebeu**. Em uma instância antiga, um `before_create` com `throw` numa coleção de banco **não aborta** — o documento é salvo (comporta-se como Modo B). Portanto:
339
+
340
+ - **Nunca assuma** que o Modo A está disponível. Antes de entregar uma trigger que depende dele, **sonde** a instância conectada (abaixo) ou, na dúvida, use o **Modo B** (`after_create` + delete), que funciona em qualquer versão.
341
+ - Ao gerar código para o usuário, **declare explicitamente** qual modo está usando e que o Modo A exige instância atualizada.
342
+
343
+ ### Como detectar suporte (sonda)
344
+
345
+ Cria uma trigger descartável que sempre lança exceção, tenta um insert e observa o resultado — depois limpa tudo:
346
+
347
+ ```
348
+ # 1. cria a trigger-sonda numa coleção de teste
349
+ funifier_save type=trigger payload={
350
+ "name": "__probe_strict_mode__",
351
+ "entity": "probe_strict__c",
352
+ "event": "before_create",
353
+ "active": true,
354
+ "script": "void trigger(event, entity, player, database){ throw new RuntimeException(\"__STRICT_PROBE_BLOCKED__\"); }"
355
+ }
356
+ # guarde o _id retornado da trigger
357
+
358
+ # 2. tenta inserir um documento na coleção de teste (passa por DatabaseRest.insert → executeStrict)
359
+ funifier_database action=insert collection=probe_strict__c payload={"probe": true}
360
+ ```
361
+
362
+ Interpretação:
363
+
364
+ | Resultado do insert | Conclusão |
365
+ |---|---|
366
+ | Erro **HTTP 500** com `message` contendo `__STRICT_PROBE_BLOCKED__` | **Modo A suportado** — `throw` em `before_*` de banco interrompe. |
367
+ | Sucesso (documento criado) | **Modo A NÃO suportado** (instância antiga) — use o Modo B. |
368
+
369
+ ```
370
+ # 3. limpeza — sempre execute, qualquer que seja o resultado
371
+ funifier_database action=delete collection=probe_strict__c filter={"probe": true} # remove o doc, se foi salvo
372
+ funifier_delete type=trigger id=<_id da trigger-sonda>
373
+ ```
374
+
375
+ > Se a sonda não puder ser executada (sem permissão de escrita / ambiente de produção sensível), **assuma o Modo B** e avise o usuário que o Modo A não foi confirmado.
376
+
316
377
  ### Armadilhas conhecidas
317
378
 
318
- - **Usar `before_create` + `throw` para rejeitar** → o documento persiste mesmo assim. Correção: `after_create` + delete (`trigger-examples.md` Exemplo 5b).
319
- - **Esquecer `entity.clear()`** → o documento rejeitado retém dados (sensíveis, no caso de signup). Correção: limpar e devolver `message`.
379
+ - **Esperar que `throw` aborte fora do modo estrito** (player, action, eventos `after_*`, bulk) → o documento persiste mesmo assim. Correção: `after_create` + delete (`trigger-examples.md` Exemplo 5b).
380
+ - **Assumir o Modo A sem sondar** em instância antiga o documento é salvo apesar do `throw`, e o erro passa despercebido. Correção: sondar (acima) ou usar Modo B por padrão.
381
+ - **`before_create` frágil em coleção de banco** → como ele agora roda em modo estrito, um erro acidental (NPE/timeout) passa a **derrubar todos os inserts** da coleção. Correção: null-check defensivo e lógica simples.
382
+ - **Esquecer `entity.clear()`** (modo B) → o documento rejeitado retém dados (sensíveis, no caso de signup). Correção: limpar e devolver só `message`.
320
383
 
321
384
  ---
322
385
 
@@ -25,7 +25,7 @@ Quem dispara o módulo (encontrado no código — `getTriggerManager().execute(.
25
25
  - `AchievementManager.evaluateLevelUp` — `(level, before/after_win)` no instante do level up.
26
26
  - `CharacterStarManager.evaluateLevelUp` — `(character_star_stats_level, before/after_win)`.
27
27
  - `DatabaseManager.bulkInsert` — `(<collection>, before_bulk)`, `(<collection>, before_create)`, `(<collection>, after_create)` por item, `(<collection>, after_bulk)` no final.
28
- - `DatabaseRest.insert/update/delete` — `(<collection>, before_create|after_create|before_update|after_update|before_delete|after_delete)`.
28
+ - `DatabaseRest.insert/update/delete` — `(<collection>, before_create|after_create|before_update|after_update|before_delete|after_delete)`. **Os três eventos `before_*` rodam em modo estrito** via `TriggerManager.executeStrict(...)` (não `execute`): se o script registrar qualquer exceção, a operação é abortada (§2.5.1).
29
29
  - `CatalogManager.purchase` / `PurchaseManager.buy` — `(catalog_item, before_purchase_validation)`, `(catalog_item, before_win)`, `(catalog_item, after_win)`.
30
30
  - `SwapManager.*` — `(swap, before/after_create|delete|win)`, `(swap, before_acquire_validation)`, `(swap_counter_offer, ...)`.
31
31
  - `MysteryBoxManager.execute` — `(mystery_box, before_win)`, `(mystery_box, before_lose)`, `(mystery_box, before_win_reward)`, `(mystery_box, after_win|after_lose)`.
@@ -169,6 +169,34 @@ flowchart LR
169
169
  Exception (catch-all) → exceptions.add; future.cancel(true); shutdownNow
170
170
  ```
171
171
 
172
+ ### 2.5.1 `execute` vs `executeStrict` — interrupção do fluxo (modo estrito)
173
+
174
+ O `TriggerRunner.run(...)` sempre **coleta** as exceções do script na lista `exceptions` do `Map` de retorno — ele nunca propaga. Quem decide o que fazer com essa lista é o método do `TriggerManager` que foi chamado:
175
+
176
+ | Método | Quem chama | Comportamento com `exceptions` não-vazia |
177
+ |---|---|---|
178
+ | `execute(...)` | Todos os disparos (action, achievement, swap, quiz, bulk/CSV, `after_*`, etc.) | **Ignora** — apenas grava no `trigger_log` e segue o loop. A persistência **não** é afetada. |
179
+ | `executeStrict(...)` | **Somente** `DatabaseRest.insert/update/delete` nos eventos `before_create`/`before_update`/`before_delete` | **Aborta** — relança `exceptions.get(0)` como `FunifierException` (`Response.Status.INTERNAL_SERVER_ERROR`). |
180
+
181
+ `executeStrict` (em `TriggerManager`):
182
+
183
+ ```
184
+ [1] Para cada trigger de (entity, event):
185
+ [2] result = runner.run(...) # mesma execução do execute()
186
+ [3] List exceptions = result.get("exceptions")
187
+ [4] Se exceptions != null && !exceptions.isEmpty():
188
+ [5] throw new FunifierException(exceptions.get(0), INTERNAL_SERVER_ERROR)
189
+ ```
190
+
191
+ Consequências para coleções de banco (`/v3/database/{collection}`) nos eventos `before_*`:
192
+
193
+ - **Qualquer** entrada em `exceptions` aborta a operação — não só um `throw` explícito do script, mas também `TimeoutException` (estouro do `timeout`, default 5s), `SecurityException` (token bloqueado pelo `SecureASTCustomizer`) e `CompilationFailedException`. Um `before_*` frágil (NPE, erro de sandbox) **bloqueia todas as gravações** naquela coleção.
194
+ - A operação não acontece: o documento **não** é gravado (create/update) nem removido (delete).
195
+ - A resposta HTTP é gerada pelo `FunifierExceptionMapper` → **500** com corpo `CallbackMessage`: `{ "message": "<exceptions[0]>", "statusCode": 500, "data": null }` (o mapper sempre força `INTERNAL_SERVER_ERROR`, ignorando o status passado ao `FunifierException`). Apenas a **primeira** mensagem é devolvida.
196
+ - **Não vale para bulk:** `DatabaseManager.bulkInsert` continua usando `execute(...)` (não estrito) para `before_bulk`/`before_create` por item — inserts em lote **não** são interrompidos por `throw`.
197
+
198
+ > ⚠️ **Disponibilidade version-dependent.** `executeStrict` foi adicionado ao `DatabaseRest` em uma atualização recente do Engine e **nem toda instância já recebeu**. Em uma instância antiga, `DatabaseRest` ainda chama `execute(...)` e o `before_*` não interrompe. Antes de gerar/depender de uma trigger que aborta via `throw`, confirme o suporte com a sonda de feature em `guides/triggers-guide.md` §8.
199
+
172
200
  ### 2.6 Sub-pipeline — `getScript(trigger)` — wrapper de imports
173
201
 
174
202
  `TriggerRunner.getScript` constrói um script que envelopa o `trigger.script` do usuário em uma classe `FunifierTrigger` Groovy com: