funifier-mcp 0.2.26 → 0.2.27

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.
Files changed (170) hide show
  1. package/.cursor/rules/funifier.mdc +38 -41
  2. package/.github/copilot-instructions.md +38 -41
  3. package/AGENTS.md +56 -49
  4. package/README.md +40 -22
  5. package/datasource-funifier-docs/.coverage.json +326 -0
  6. package/datasource-funifier-docs/.validation.json +593 -0
  7. package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
  8. package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
  9. package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
  10. package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
  11. package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
  12. package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
  13. package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
  14. package/datasource-funifier-docs/knowledge/index.md +4 -1
  15. package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
  16. package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
  17. package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
  18. package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
  19. package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
  20. package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
  21. package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
  22. package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
  23. package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
  24. package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
  25. package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
  26. package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
  27. package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
  28. package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
  29. package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
  30. package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
  31. package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
  32. package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
  33. package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
  34. package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
  35. package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
  36. package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
  37. package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
  38. package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
  39. package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
  40. package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
  41. package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
  42. package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
  43. package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
  44. package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
  45. package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
  46. package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
  47. package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
  48. package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
  49. package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
  50. package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
  51. package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
  52. package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
  53. package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
  54. package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
  55. package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
  56. package/dist/cli/init.d.ts.map +1 -1
  57. package/dist/cli/init.js +42 -1
  58. package/dist/cli/init.js.map +1 -1
  59. package/dist/cli/init.test.js +74 -3
  60. package/dist/cli/init.test.js.map +1 -1
  61. package/dist/cli/persona.d.ts +3 -0
  62. package/dist/cli/persona.d.ts.map +1 -0
  63. package/dist/cli/persona.js +25 -0
  64. package/dist/cli/persona.js.map +1 -0
  65. package/dist/mcp/bundle.js +119 -93
  66. package/dist/mcp/index.js +2 -2
  67. package/dist/mcp/index.js.map +1 -1
  68. package/dist/mcp/resources/documentation.d.ts +1 -1
  69. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  70. package/dist/mcp/resources/documentation.js +39 -3
  71. package/dist/mcp/resources/documentation.js.map +1 -1
  72. package/dist/mcp/tools/connect.d.ts.map +1 -1
  73. package/dist/mcp/tools/connect.js +18 -8
  74. package/dist/mcp/tools/connect.js.map +1 -1
  75. package/dist/mcp/tools/database.d.ts.map +1 -1
  76. package/dist/mcp/tools/database.js +59 -47
  77. package/dist/mcp/tools/database.js.map +1 -1
  78. package/dist/mcp/tools/database.test.js +2 -2
  79. package/dist/mcp/tools/database.test.js.map +1 -1
  80. package/dist/mcp/tools/delete.d.ts.map +1 -1
  81. package/dist/mcp/tools/delete.js +13 -3
  82. package/dist/mcp/tools/delete.js.map +1 -1
  83. package/dist/mcp/tools/execute.d.ts.map +1 -1
  84. package/dist/mcp/tools/execute.js +20 -9
  85. package/dist/mcp/tools/execute.js.map +1 -1
  86. package/dist/mcp/tools/folder.d.ts.map +1 -1
  87. package/dist/mcp/tools/folder.js +22 -12
  88. package/dist/mcp/tools/folder.js.map +1 -1
  89. package/dist/mcp/tools/get.d.ts.map +1 -1
  90. package/dist/mcp/tools/get.js +16 -6
  91. package/dist/mcp/tools/get.js.map +1 -1
  92. package/dist/mcp/tools/index.d.ts +1 -1
  93. package/dist/mcp/tools/index.d.ts.map +1 -1
  94. package/dist/mcp/tools/index.js +3 -1
  95. package/dist/mcp/tools/index.js.map +1 -1
  96. package/dist/mcp/tools/list.d.ts.map +1 -1
  97. package/dist/mcp/tools/list.js +38 -14
  98. package/dist/mcp/tools/list.js.map +1 -1
  99. package/dist/mcp/tools/logs.d.ts.map +1 -1
  100. package/dist/mcp/tools/logs.js +15 -5
  101. package/dist/mcp/tools/logs.js.map +1 -1
  102. package/dist/mcp/tools/save.d.ts.map +1 -1
  103. package/dist/mcp/tools/save.js +14 -4
  104. package/dist/mcp/tools/save.js.map +1 -1
  105. package/dist/mcp/tools/save.test.js +3 -3
  106. package/dist/mcp/tools/save.test.js.map +1 -1
  107. package/dist/mcp/tools/search-docs.d.ts +3 -0
  108. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  109. package/dist/mcp/tools/search-docs.js +102 -0
  110. package/dist/mcp/tools/search-docs.js.map +1 -0
  111. package/package.json +6 -2
  112. package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
  113. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  114. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  115. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  116. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  117. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  118. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  119. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
  120. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  121. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  122. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  123. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  124. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  125. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  126. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  127. package/skills/funifier/SKILL.md +88 -0
  128. package/skills/funifier/references/configure-security.md +96 -0
  129. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  130. package/skills/funifier/references/create-aggregate.md +144 -0
  131. package/skills/funifier/references/create-challenge.md +116 -0
  132. package/skills/funifier/references/create-competition.md +98 -0
  133. package/skills/funifier/references/create-crossword.md +574 -0
  134. package/skills/funifier/references/create-custom-object.md +91 -0
  135. package/skills/funifier/references/create-custom-page.md +135 -0
  136. package/skills/funifier/references/create-folder.md +104 -0
  137. package/skills/funifier/references/create-lastmile.md +643 -0
  138. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  139. package/skills/funifier/references/create-level.md +94 -0
  140. package/skills/funifier/references/create-lottery.md +913 -0
  141. package/skills/funifier/references/create-mystery.md +769 -0
  142. package/skills/funifier/references/create-notification.md +75 -0
  143. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  144. package/skills/funifier/references/create-quiz.md +98 -0
  145. package/skills/funifier/references/create-scheduler.md +141 -0
  146. package/skills/funifier/references/create-story.md +636 -0
  147. package/skills/funifier/references/create-swap.md +95 -0
  148. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  149. package/skills/funifier/references/create-virtual-good.md +96 -0
  150. package/skills/funifier/references/create-webhook.md +72 -0
  151. package/skills/funifier/references/create-websocket.md +71 -0
  152. package/skills/funifier/references/create-widget.md +76 -0
  153. package/skills/funifier/references/debug.md +87 -0
  154. package/skills/funifier/references/help.md +81 -0
  155. package/skills/funifier/references/implement-frontend.md +106 -0
  156. package/skills/funifier/references/import-csv.md +75 -0
  157. package/skills/funifier/references/manage-player.md +82 -0
  158. package/skills/funifier/references/manage-team.md +76 -0
  159. package/skills/funifier/references/upload-file.md +91 -0
  160. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  161. package/skills/funifier-create-challenge/SKILL.md +0 -88
  162. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  163. package/skills/funifier-create-level/SKILL.md +0 -87
  164. package/skills/funifier-create-quiz/SKILL.md +0 -87
  165. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  166. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  167. package/skills/funifier-debug/SKILL.md +0 -92
  168. package/skills/funifier-help/SKILL.md +0 -86
  169. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  170. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,178 +1,96 @@
1
1
  # Trigger Examples — Padrões de Produção
2
2
 
3
- Referência prática de eventos, entidades, constantes e exemplos reais para criação de triggers no Funifier.
3
+ ## 1. Visão Geral
4
4
 
5
- ---
6
-
7
- ## Eventos Oficiais (dropdown do Studio)
8
-
9
- | Evento | Quando é acionado |
10
- |--------|-------------------|
11
- | `before_win` | Antes de registrar uma conquista (ponto, desafio, item) |
12
- | `after_win` | Depois de registrar uma conquista |
13
- | `before_lose` | Antes de remover uma conquista |
14
- | `after_lose` | Depois de remover uma conquista |
15
- | `before_create` | Antes de persistir um novo documento |
16
- | `after_create` | Depois de persistir um novo documento |
17
- | `before_update` | Antes de atualizar um documento existente |
18
- | `after_update` | Depois de atualizar um documento |
19
- | `before_delete` | Antes de deletar um documento |
20
- | `after_delete` | Depois de deletar um documento |
5
+ ### 1.1 O que é este documento
21
6
 
22
- ---
7
+ Este documento reúne exemplos de produção de triggers Funifier — código Groovy pronto, anotado, para padrões reais (atualizar coleção relacionada, enriquecer conquista, criar achievement, inicializar jogador, normalizar/rejeitar `__c`, processar lote, disparar ação). Os **conceitos** (eventos, entidades, variáveis, regra de persistência) não são repetidos aqui — vivem no guia de triggers.
23
8
 
24
- ## Eventos Estendidos (free-text only — não aparecem no dropdown)
9
+ ### 1.2 Quando consultar
25
10
 
26
- | Evento | Quando é acionado |
27
- |--------|-------------------|
28
- | `before_bulk` | Antes de um insert/update em lote via API |
29
- | `after_bulk` | Depois de um insert/update em lote via API |
30
- | `csv_before_bulk` | Antes de processar um import CSV |
31
- | `csv_after_bulk` | Depois de processar um import CSV |
11
+ - Consulte ao precisar de um **exemplo concreto** para um par evento+entidade.
12
+ - Consulte ao implementar **rejeição de criação** (`after_create` + delete) ou **processamento em lote**.
13
+ - Consulte ao precisar **registrar uma ação** a partir de um trigger (`track`/`trackSynchonous`).
32
14
 
33
- > ⚠ Nesses eventos a variável `entity` recebe uma **List** (não um único documento). Itere com `for (Object item : entity)`.
15
+ ### 1.3 Quando NÃO consultar
34
16
 
35
- ---
17
+ - **NÃO** consulte para entender **eventos, entidades, `context.extra` ou a regra de persistência** — use `datasource-funifier-docs/knowledge/guides/triggers-guide.md`.
18
+ - **NÃO** consulte para **métodos de manager / campos de entidade / bibliotecas** — use `java-managers.md`, `java-entities.md`, `java-libraries.md`.
36
19
 
37
- ## Entidades Disponíveis
20
+ ### 1.4 Índice de decisão
38
21
 
39
- **Dropdown hardcoded do Studio:**
40
- `action`, `catalog`, `catalog_item`, `crown`, `leaderboard`, `level`, `player`, `team`, `challenge`, `point_category`, `character_star_stats_level`, `upload`, `mystery_box`, `lottery`
22
+ | Quero… | Exemplo | Seção |
23
+ |---|---|---|
24
+ | Atualizar um documento `__c` quando uma ação ocorre | `action \| before_win` | §3 |
25
+ | Marcar a origem (ação/desafio) de uma conquista | `achievement \| before_create` | §4 |
26
+ | Creditar pontos a outro jogador como efeito de uma ação | `action \| after_win` | §5 |
27
+ | Preencher dados do jogador no cadastro | `player \| after_create` | §6 |
28
+ | Normalizar campos antes de salvar um `__c` | `custom__c \| before_create` | §7 |
29
+ | Validar e rejeitar a criação de um `__c` | `custom__c \| after_create` | §8 |
30
+ | Processar um import/insert em lote | `custom__c \| after_bulk` | §9 |
31
+ | Disparar uma ação de dentro de um trigger | `track` / `trackSynchonous` | §10 |
41
32
 
42
- **Entidades dinâmicas** (carregadas via `/v3/database/collections`):
43
- `achievement` e todas as coleções customizadas (`__c`) do projeto aparecem aqui.
44
- Excluídas: `action_log`, `challenge_progress`, `player_status`, `security`, `technique_field`, `join_log`.
33
+ ### 1.5 Restrições globais críticas
45
34
 
46
- **Free-text**: o formulário do Studio permite digitar qualquer nome de entidade/evento não listado.
35
+ Todas herdadas de `triggers-guide.md` §1.5 releia antes de copiar qualquer exemplo. As mais relevantes aqui:
47
36
 
48
- ---
37
+ > ⚠️ **`throw` não cancela persistência** → rejeição é em `after_create` + `database.delete` (§8, e `triggers-guide.md` §8).
38
+ > ⚠️ **Em Groovy use `.id`, não `._id`** → todos os exemplos abaixo seguem essa regra.
39
+ > ⚠️ **Nunca `track` a mesma ação que disparou o trigger** → loop infinito; filtre por `entity.actionId` no início (§10).
49
40
 
50
- ## Constantes de Achievement
41
+ ### 1.6 Documentos relacionados
51
42
 
52
- | Constante Java | Valor int | Descrição |
53
- |----------------|-----------|-----------|
54
- | `Achievement.TYPE_POINT` | `0` | Ponto acumulado |
55
- | `Achievement.TYPE_CHALLENGE` | `1` | Desafio completado |
56
- | `Achievement.TYPE_VIRTUAL_GOOD` | `2` | Item de catálogo comprado |
57
- | `Achievement.TYPE_LEVEL` | `3` | Nível alcançado |
58
- | `Achievement.TYPE_LOTTERY` | `5` | Loteria |
59
- | `Achievement.TYPE_COMPETITION` | `9` | Competição |
43
+ > 📄 `datasource-funifier-docs/knowledge/guides/triggers-guide.md` conceitos (eventos, entidades, `context.extra`, persistência)
44
+ > 📄 `datasource-funifier-docs/knowledge/guides/java-managers.md` — `track`, `addAchievement`, `getJongoConnection`
45
+ > 📄 `datasource-funifier-docs/knowledge/guides/database-access.md` `findOne`/`save`/`count`/`delete`
60
46
 
61
47
  ---
62
48
 
63
- ## Campos das Entidades Java nos Triggers
64
-
65
- ### ActionLog (`entity` em triggers de `action`)
66
- ```java
67
- entity.actionId // String — ID da ação (ex: "complete_task")
68
- entity.userId // String — ID do jogador que executou
69
- entity.attributes // Map<String, Object> — atributos da ação
70
- entity.time // Date — momento da execução
71
- entity.id // String — alias para entity._id
72
- entity._id // String — ID do registro de log
73
- ```
74
-
75
- ### Player (`entity` em triggers de `player`)
76
- ```java
77
- entity._id / entity.id // String — ID do jogador
78
- entity.name // String
79
- entity.email // String
80
- entity.extra // Map<String, Object> — campos customizados
81
- entity.teams // List<String> — IDs das equipes
82
- entity.image // ImageSet — getOriginal().getUrl(), getMedium(), getSmall()
83
- ```
49
+ ## 2. Formato do Documento de Trigger
84
50
 
85
- ### Achievement (`entity` em triggers de `achievement` / `point_category`)
86
- ```java
87
- entity._id / entity.id // String
88
- entity.player // String — ID do jogador
89
- entity.total // double — quantidade
90
- entity.type // int — ver tabela de constantes acima
91
- entity.item // String — ID do ponto/desafio/item
92
- entity.time // Date
93
- entity.extra // Map<String, Object>
94
- ```
95
-
96
- ---
51
+ Quando usar: ao deployar qualquer exemplo abaixo via API.
52
+ Não usar quando: criando a trigger pelo Studio (cole só o script no campo).
53
+ Depende de: `Trigger` (`java-entities.md` §10).
54
+ Disponível em: Trigger.
97
55
 
98
- ## context.extra — Campos Disponíveis
56
+ ### Descrição
99
57
 
100
- Disponível nos eventos `before_win`, `before_create`, `after_create` quando a trigger é acionada por uma cadeia de conquistas:
58
+ Os exemplos abaixo mostram o **script Groovy** legível. Para deployar via `POST /v3/trigger`, envolva o script (como string JSON-escapada) neste envelope:
101
59
 
102
- ```java
103
- context.extra.parentAchievement // Achievement que gerou este evento
104
- context.extra.parentAchievement.id // String
105
- context.extra.parentAchievement.type // int (usar constantes acima)
106
- context.extra.parentAchievement.item // String
107
- context.extra.parentAchievement.player // String
108
- context.extra.parentAchievement.total // double
60
+ ### Uso
109
61
 
110
- context.extra.parentActionLog // ActionLog que originou a cadeia
111
- context.extra.parentActionLog.id // String
112
- context.extra.parentActionLog.actionId // String
113
- context.extra.parentActionLog.userId // String
114
- context.extra.parentActionLog.attributes // Map
115
-
116
- context.extra.originActionLog // ActionLog raiz (ponta da cadeia)
62
+ ```json
63
+ {
64
+ "name": "Nome descritivo",
65
+ "entity": "<entidade>",
66
+ "event": "<evento>",
67
+ "active": true,
68
+ "script": "void trigger(event, entity, player, database) { /* … código … */ }"
69
+ }
117
70
  ```
118
71
 
119
- ---
72
+ ### Armadilhas conhecidas
120
73
 
121
- ## Regra Crítica: Triggers NÃO impedem persistência via throw
122
-
123
- O Funifier **sempre salva o documento** antes de executar o trigger. Não é possível usar `throw` para cancelar a persistência.
124
-
125
- **Padrão correto para rejeitar uma criação:**
126
- 1. Use `after_create` (não `before_create`)
127
- 2. Valide no trigger; se inválido:
128
- ```java
129
- database.delete(entity._id, "collection__c");
130
- entity.clear();
131
- entity.put("message", "Motivo da rejeição");
132
- return;
133
- ```
134
- 3. Para lógica complexa, use uma `BusinessException` interna para centralizar o cleanup:
135
- ```java
136
- // A exceção é capturada dentro do próprio trigger — não propaga para a plataforma
137
- try {
138
- if (condicaoInvalida) throw new BusinessException("Motivo");
139
- // ... mais validações ...
140
- } catch (BusinessException e) {
141
- database.delete(entity._id, "collection__c");
142
- entity.clear();
143
- entity.put("message", e.getMessage());
144
- }
145
-
146
- public class BusinessException extends Exception {
147
- private String message;
148
- public BusinessException(String message) { super(message); this.message = message; }
149
- public String getMessage() { return this.message; }
150
- }
151
- ```
152
-
153
- **`before_create` é para enriquecimento apenas:** normalizar strings, preencher defaults, adicionar metadados.
74
+ - **Atualizar a trigger com `POST` parcial** o `script` é apagado (full replace; `patterns.md` §1.5). Correção: reenviar o documento completo.
154
75
 
155
76
  ---
156
77
 
157
- ## Exemplos Anotados
78
+ ## 3. `action | before_win` — Atualizar Coleção Relacionada
158
79
 
159
- ### Exemplo 1 `action | before_win`: Atualizar coleção relacionada
80
+ Quando usar: ler atributos de uma ActionLog e atualizar um documento em coleção `__c`.
81
+ Não usar quando: o efeito é conceder pontos/conquista → §5.
82
+ Depende de: `database`/`getJongoConnection` (`database-access.md`), `JsonUtil` (`java-libraries.md`).
83
+ Disponível em: Trigger.
160
84
 
161
- Padrão: ler atributos da ActionLog, atualizar um documento em coleção customizada.
85
+ ### Descrição
162
86
 
163
- ```json
164
- {
165
- "name": "Update Task Status on Action Win",
166
- "entity": "action",
167
- "event": "before_win",
168
- "active": true,
169
- "script": "void trigger(event, entity, player, database) {\n if (!entity.actionId.equals(\"complete_task\")) return;\n\n String taskId = String.valueOf(entity.attributes.get(\"taskId\"));\n if (taskId == null || taskId.equals(\"null\")) return;\n\n HashMap<String, Object> task = manager.getJongoConnection()\n .getCollection(\"task__c\")\n .findOne(\"{_id:#}\", taskId).as(HashMap.class);\n\n if (task != null) {\n task.put(\"status\", \"done\");\n task.put(\"completedAt\", new Date());\n manager.getJongoConnection().getCollection(\"task__c\").save(task);\n entity.attributes.put(\"status\", \"processed\");\n println(\"task updated: \" + JsonUtil.toJson(task));\n }\n}\n"
170
- }
171
- ```
87
+ Ação `complete_task` marca a task correspondente como concluída em `task__c`.
88
+
89
+ ### Uso
172
90
 
173
91
  ```groovy
174
92
  void trigger(event, entity, player, database) {
175
- if (!entity.actionId.equals("complete_task")) return;
93
+ if (!entity.actionId.equals("complete_task")) return; // filtra a ação alvo
176
94
 
177
95
  String taskId = String.valueOf(entity.attributes.get("taskId"));
178
96
  if (taskId == null || taskId.equals("null")) return;
@@ -191,28 +109,32 @@ void trigger(event, entity, player, database) {
191
109
  }
192
110
  ```
193
111
 
112
+ ### Armadilhas conhecidas
113
+
114
+ - **Não filtrar `entity.actionId`** → roda para toda ação. Correção: `return` cedo se não for a ação alvo.
115
+ - **`get("taskId")` ausente** → `"null"` (String). Correção: comparar com `"null"` após `String.valueOf`.
116
+
194
117
  ---
195
118
 
196
- ### Exemplo 2 — `achievement | before_create`: Enriquecer com origem
119
+ ## 4. `achievement | before_create` Enriquecer com Origem
197
120
 
198
- Padrão: ler `context.extra` para saber qual action log ou achievement gerou este registro.
121
+ Quando usar: marcar de qual ação/desafio uma conquista de ponto se originou.
122
+ Não usar quando: não precisa da origem → não leia `context.extra`.
123
+ Depende de: `context.extra` (`triggers-guide.md` §7), constantes `TYPE_*` (`java-entities.md` §3).
124
+ Disponível em: Trigger.
199
125
 
200
- ```json
201
- {
202
- "name": "Tag Achievement with Origin",
203
- "entity": "achievement",
204
- "event": "before_create",
205
- "active": true,
206
- "script": "void trigger(event, entity, player, database) {\n if (entity.type != Achievement.TYPE_POINT) return;\n\n if (entity.extra == null) entity.extra = new HashMap<>();\n if (context.extra == null) return;\n\n if (context.extra.parentAchievement != null &&\n context.extra.parentAchievement.type == Achievement.TYPE_CHALLENGE) {\n entity.extra.put(\"originChallenge\", context.extra.parentAchievement.id);\n }\n\n if (context.extra.parentActionLog != null) {\n entity.extra.put(\"originAction\", context.extra.parentActionLog.id);\n entity.extra.put(\"originActionId\", context.extra.parentActionLog.actionId);\n }\n\n println(\"entity: \" + JsonUtil.toJson(entity));\n}\n"
207
- }
208
- ```
126
+ ### Descrição
127
+
128
+ Ao criar um Achievement de ponto, grava no `extra` o desafio/ação que o gerou, lendo `context.extra`.
129
+
130
+ ### Uso
209
131
 
210
132
  ```groovy
211
133
  void trigger(event, entity, player, database) {
212
134
  if (entity.type != Achievement.TYPE_POINT) return;
213
135
 
214
136
  if (entity.extra == null) entity.extra = new HashMap<>();
215
- if (context.extra == null) return;
137
+ if (context.extra == null) return; // sem cadeia → nada a marcar
216
138
 
217
139
  if (context.extra.parentAchievement != null &&
218
140
  context.extra.parentAchievement.type == Achievement.TYPE_CHALLENGE) {
@@ -228,21 +150,24 @@ void trigger(event, entity, player, database) {
228
150
  }
229
151
  ```
230
152
 
153
+ ### Armadilhas conhecidas
154
+
155
+ - **Acessar `context.extra.*` sem checar null** → NPE. Correção: `if (context.extra == null) return;` e checar cada campo.
156
+
231
157
  ---
232
158
 
233
- ### Exemplo 3 — `action | after_win`: Criar Achievement programaticamente
159
+ ## 5. `action | after_win` Criar Achievement Programaticamente
234
160
 
235
- Padrão: creditar pontos para outro jogador como efeito colateral de uma ação.
161
+ Quando usar: creditar pontos a outro jogador como efeito colateral de uma ação.
162
+ Não usar quando: o crédito é para o próprio jogador da ação → as regras da ação já o fazem.
163
+ Depende de: `AchievementManager.addAchievement` (`java-managers.md` §4), `Guid` (`java-libraries.md` §3).
164
+ Disponível em: Trigger.
236
165
 
237
- ```json
238
- {
239
- "name": "Credit Referrer Points on Invite Accept",
240
- "entity": "action",
241
- "event": "after_win",
242
- "active": true,
243
- "script": "void trigger(event, entity, player, database) {\n if (!entity.actionId.equals(\"accept_invite\")) return;\n\n String referrerId = String.valueOf(entity.attributes.get(\"referrer\"));\n if (referrerId == null || referrerId.equals(\"null\")) return;\n\n Achievement credit = new Achievement();\n credit.id = Guid.newShortGuid();\n credit.type = Achievement.TYPE_POINT;\n credit.player = referrerId;\n credit.item = \"xp\";\n credit.total = 50;\n credit.time = new Date();\n credit.extra = new HashMap();\n credit.extra.put(\"origin\", entity.id);\n credit.extra.put(\"originAction\", entity.actionId);\n\n manager.getAchievementManager().addAchievement(credit);\n println(\"credited: \" + JsonUtil.toJson(credit));\n}\n"
244
- }
245
- ```
166
+ ### Descrição
167
+
168
+ Ao aceitar um convite (`accept_invite`), credita 50 XP ao indicador (`referrer`).
169
+
170
+ ### Uso
246
171
 
247
172
  ```groovy
248
173
  void trigger(event, entity, player, database) {
@@ -252,7 +177,7 @@ void trigger(event, entity, player, database) {
252
177
  if (referrerId == null || referrerId.equals("null")) return;
253
178
 
254
179
  Achievement credit = new Achievement();
255
- credit.id = Guid.newShortGuid();
180
+ credit.id = Guid.newShortGuid(); // .id, não ._id
256
181
  credit.type = Achievement.TYPE_POINT;
257
182
  credit.player = referrerId;
258
183
  credit.item = "xp";
@@ -267,21 +192,25 @@ void trigger(event, entity, player, database) {
267
192
  }
268
193
  ```
269
194
 
195
+ ### Armadilhas conhecidas
196
+
197
+ - **`credit._id`** → não atribui em Groovy. Correção: `credit.id` (`triggers-guide.md` §1.5).
198
+ - **Creditar sem filtrar a ação** → crédito indevido em qualquer ação. Correção: `return` cedo.
199
+
270
200
  ---
271
201
 
272
- ### Exemplo 4 — `player | after_create`: Inicializar dados do jogador
202
+ ## 6. `player | after_create` Inicializar Dados do Jogador
273
203
 
274
- Padrão: ao criar um jogador, buscar dados relacionados em coleção customizada e preencher `extra`.
204
+ Quando usar: ao criar um jogador, buscar dados relacionados em `__c` e preencher `extra`.
205
+ Não usar quando: os dados já vêm no payload de cadastro.
206
+ Depende de: `PlayerManager` (`java-managers.md` §2), `getJongoConnection` (`database-access.md`).
207
+ Disponível em: Trigger.
275
208
 
276
- ```json
277
- {
278
- "name": "Initialize Player from Employee Data",
279
- "entity": "player",
280
- "event": "after_create",
281
- "active": true,
282
- "script": "void trigger(event, entity, player, database) {\n println(\"new player: \" + JsonUtil.toJson(entity));\n\n HashMap<String, Object> employee = manager.getJongoConnection()\n .getCollection(\"employee__c\")\n .findOne(\"{email:#}\", entity.email).as(HashMap.class);\n\n if (employee == null) return;\n\n Player p = manager.getPlayerManager().findById(entity._id);\n if (p == null) return;\n\n if (p.extra == null) p.extra = new HashMap();\n p.extra.put(\"department\", employee.get(\"department\"));\n p.extra.put(\"role\", employee.get(\"role\"));\n p.extra.put(\"employeeId\", employee.get(\"_id\"));\n\n manager.getPlayerManager().insert(p);\n println(\"player initialized: \" + p._id);\n}\n"
283
- }
284
- ```
209
+ ### Descrição
210
+
211
+ Ao cadastrar o jogador, busca o funcionário correspondente em `employee__c` (por email) e copia `department`/`role` para `player.extra`.
212
+
213
+ ### Uso
285
214
 
286
215
  ```groovy
287
216
  void trigger(event, entity, player, database) {
@@ -293,34 +222,40 @@ void trigger(event, entity, player, database) {
293
222
 
294
223
  if (employee == null) return;
295
224
 
296
- Player p = manager.getPlayerManager().findById(entity._id);
225
+ Player p = manager.getPlayerManager().findById(entity.id); // .id, não ._id
297
226
  if (p == null) return;
298
227
 
299
228
  if (p.extra == null) p.extra = new HashMap();
300
229
  p.extra.put("department", employee.get("department"));
301
230
  p.extra.put("role", employee.get("role"));
302
- p.extra.put("employeeId", employee.get("_id"));
231
+ p.extra.put("employeeId", employee.get("_id")); // _id é chave do HashMap (__c), não propriedade
303
232
 
304
- manager.getPlayerManager().insert(p);
305
- println("player initialized: " + p._id);
233
+ manager.getPlayerManager().insert(p); // insert = upsert (java-managers.md §2)
234
+ println("player initialized: " + p.id);
306
235
  }
307
236
  ```
308
237
 
238
+ > Nota: em `__c` (HashMap) a chave é literalmente `"_id"` (`employee.get("_id")`); a regra `.id` vale só para POJOs em Groovy (`triggers-guide.md` §1.5).
239
+
240
+ ### Armadilhas conhecidas
241
+
242
+ - **`pm.save(p)`** → inexistente. Correção: `insert` (`java-managers.md` §2).
243
+ - **`entity._id`** → `null`. Correção: `entity.id`.
244
+
309
245
  ---
310
246
 
311
- ### Exemplo 5a — `custom__c | before_create`: Normalizar campos antes de persistir
247
+ ## 7. `custom__c | before_create` Normalizar Campos
312
248
 
313
- Padrão: `before_create` **não rejeita** — apenas enriquece/normaliza o `entity` antes de salvar.
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).
251
+ Depende de: —
252
+ Disponível em: Trigger.
314
253
 
315
- ```json
316
- {
317
- "name": "Normalize Employee Fields",
318
- "entity": "employee__c",
319
- "event": "before_create",
320
- "active": true,
321
- "script": "void trigger(event, entity, player, database) {\n entity.put(\"email\", normalizeEmail(entity.get(\"email\")));\n entity.put(\"name\", normalizeName(entity.get(\"name\")));\n entity.put(\"employeeId\", extractNumeric(entity.get(\"employeeId\")));\n}\n\nString normalizeEmail(Object value) {\n if (value == null) return \"\";\n String s = String.valueOf(value).replaceAll(\"\\\\s\", \"\");\n return s.equals(\"null\") ? \"\" : s.toLowerCase();\n}\n\nString normalizeName(Object value) {\n if (value == null) return \"\";\n String s = String.valueOf(value).trim();\n return s.equals(\"null\") ? \"\" : s;\n}\n\nString extractNumeric(Object value) {\n if (value == null) return \"\";\n try {\n return String.valueOf(Long.parseLong(String.valueOf(value).replaceAll(\"\\\\s\", \"\")));\n } catch (NumberFormatException e) {\n return \"\";\n }\n}\n"
322
- }
323
- ```
254
+ ### Descrição
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.
257
+
258
+ ### Uso
324
259
 
325
260
  ```groovy
326
261
  void trigger(event, entity, player, database) {
@@ -351,21 +286,24 @@ String extractNumeric(Object value) {
351
286
  }
352
287
  ```
353
288
 
289
+ ### Armadilhas conhecidas
290
+
291
+ - **Esperar que `before_create` rejeite valores inválidos** → não rejeita; o documento salva. Correção: validar/rejeitar em `after_create` (§8).
292
+
354
293
  ---
355
294
 
356
- ### Exemplo 5b — `custom__c | after_create`: Validar e rejeitar criação
295
+ ## 8. `custom__c | after_create` Validar e Rejeitar
357
296
 
358
- Padrão: document foi salvo se inválido, deletar e retornar erro no `entity`.
297
+ Quando usar: validar um documento `__c` recém-criado e rejeitá-lo se inválido.
298
+ Não usar quando: só precisa normalizar → §7.
299
+ Depende de: regra de persistência + `BusinessException` (`triggers-guide.md` §8), `getJongoConnection` (`database-access.md`).
300
+ Disponível em: Trigger.
359
301
 
360
- ```json
361
- {
362
- "name": "Validate Order on Create",
363
- "entity": "order__c",
364
- "event": "after_create",
365
- "active": true,
366
- "script": "void trigger(event, entity, player, database) {\n try {\n if (player == null || player.isEmpty()) {\n throw new BusinessException(\"Player obrigatório.\");\n }\n\n HashMap<String, Object> schedule = manager.getJongoConnection()\n .getCollection(\"schedule__c\")\n .findOne(\"{status:#}\", \"open\").as(HashMap.class);\n\n if (schedule == null) {\n throw new BusinessException(\"Nenhuma janela de cadastro aberta.\");\n }\n\n long existing = manager.getJongoConnection().getCollection(\"order__c\")\n .count('{\"_id\":{\"$ne\":#},\"owner\":#,\"status\":{\"$ne\":\"cancelled\"}}',\n entity._id, player);\n\n if (existing >= 3) {\n throw new BusinessException(\"Limite de cadastros atingido nesta janela.\");\n }\n\n } catch (BusinessException e) {\n database.delete(entity._id, \"order__c\");\n entity.clear();\n entity.put(\"message\", e.getMessage());\n println(\"rejected: \" + e.getMessage());\n return;\n }\n}\n\npublic class BusinessException extends Exception {\n private String message;\n public BusinessException(String msg) { super(msg); this.message = msg; }\n public String getMessage() { return this.message; }\n}\n"
367
- }
368
- ```
302
+ ### Descrição
303
+
304
+ O documento 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.
305
+
306
+ ### Uso
369
307
 
370
308
  ```groovy
371
309
  void trigger(event, entity, player, database) {
@@ -384,14 +322,14 @@ void trigger(event, entity, player, database) {
384
322
 
385
323
  long existing = manager.getJongoConnection().getCollection("order__c")
386
324
  .count('{"_id":{"$ne":#},"owner":#,"status":{"$ne":"cancelled"}}',
387
- entity._id, player);
325
+ entity.id, player);
388
326
 
389
327
  if (existing >= 3) {
390
328
  throw new BusinessException("Limite de cadastros atingido nesta janela.");
391
329
  }
392
330
 
393
331
  } catch (BusinessException e) {
394
- database.delete(entity._id, "order__c");
332
+ database.delete(entity.id, "order__c"); // documento já salvo → deletar
395
333
  entity.clear();
396
334
  entity.put("message", e.getMessage());
397
335
  println("rejected: " + e.getMessage());
@@ -406,26 +344,29 @@ public class BusinessException extends Exception {
406
344
  }
407
345
  ```
408
346
 
347
+ ### Armadilhas conhecidas
348
+
349
+ - **Usar `before_create` aqui** → `throw` não impede o save (`triggers-guide.md` §8). Correção: `after_create` + delete.
350
+ - **Esquecer `entity.clear()`** → o documento rejeitado mantém os dados. Correção: limpar e devolver só `message`.
351
+
409
352
  ---
410
353
 
411
- ### Exemplo 6 — `custom__c | after_bulk`: Processar import em lote
354
+ ## 9. `custom__c | after_bulk` Processar Lote
412
355
 
413
- Padrão: `entity` é uma **List** itere e processe cada item.
356
+ Quando usar: processar cada item de um import/insert em lote.
357
+ Não usar quando: o evento é de documento único → `entity` não é List (`triggers-guide.md` §9).
358
+ Depende de: evento estendido `after_bulk` (`triggers-guide.md` §4, §9).
359
+ Disponível em: Trigger.
414
360
 
415
- ```json
416
- {
417
- "name": "Process Imported Records",
418
- "entity": "import__c",
419
- "event": "after_bulk",
420
- "active": true,
421
- "script": "void trigger(event, entity, player, database) {\n if (entity == null || entity.isEmpty()) return;\n\n println(\"bulk size: \" + entity.size());\n\n for (Object raw : entity) {\n HashMap<String, Object> item = (HashMap<String, Object>) raw;\n if (item == null) continue;\n\n String employeeId = String.valueOf(item.get(\"employeeId\"));\n if (employeeId == null || employeeId.equals(\"null\")) continue;\n\n HashMap<String, Object> employee = manager.getJongoConnection()\n .getCollection(\"employee__c\")\n .findOne(\"{_id:#}\", employeeId).as(HashMap.class);\n\n if (employee != null) {\n item.put(\"department\", employee.get(\"department\"));\n item.put(\"processed\", true);\n manager.getJongoConnection().getCollection(\"import__c\").save(item);\n }\n }\n\n println(\"bulk processed: \" + entity.size() + \" records\");\n}\n"
422
- }
423
- ```
361
+ ### Descrição
362
+
363
+ Em `after_bulk`, `entity` é uma **List**. Para cada item, busca o funcionário em `employee__c` e enriquece.
364
+
365
+ ### Uso
424
366
 
425
367
  ```groovy
426
368
  void trigger(event, entity, player, database) {
427
369
  if (entity == null || entity.isEmpty()) return;
428
-
429
370
  println("bulk size: " + entity.size());
430
371
 
431
372
  for (Object raw : entity) {
@@ -445,36 +386,38 @@ void trigger(event, entity, player, database) {
445
386
  manager.getJongoConnection().getCollection("import__c").save(item);
446
387
  }
447
388
  }
448
-
449
389
  println("bulk processed: " + entity.size() + " records");
450
390
  }
451
391
  ```
452
392
 
393
+ ### Armadilhas conhecidas
394
+
395
+ - **Tratar `entity` como documento único** → `ClassCastException`. Correção: iterar a List (`triggers-guide.md` §9).
396
+
453
397
  ---
454
398
 
455
- ### Exemplo 7 — Registrar uma ação programaticamente (`ActionManager.track`)
399
+ ## 10. Registrar Ação Programaticamente (`track` / `trackSynchonous`)
456
400
 
457
- Padrão: criar e disparar um ActionLog de dentro de um trigger, acionando as regras de gamificação normalmente.
401
+ Quando usar: disparar uma ActionLog de dentro de um trigger, acionando as regras normalmente.
402
+ Não usar quando: a ação a registrar é a **mesma** que disparou o trigger → loop infinito.
403
+ Depende de: `ActionManager.track`/`trackSynchonous` (`java-managers.md` §3).
404
+ Disponível em: Trigger.
458
405
 
459
- ```json
460
- {
461
- "name": "Track Secondary Action on Win",
462
- "entity": "action",
463
- "event": "after_win",
464
- "active": true,
465
- "script": "void trigger(event, entity, player, database) {\n if (!entity.actionId.equals(\"complete_task\")) return;\n\n ActionLog log = new ActionLog();\n log.id = Guid.newShortGuid();\n log.userId = entity.userId;\n log.actionId = \"task_completed_by_team\";\n log.time = new Date();\n log.attributes = new HashMap();\n log.attributes.put(\"taskId\", entity.attributes.get(\"taskId\"));\n log.attributes.put(\"origin\", entity.id);\n manager.getActionManager().track(log);\n println(\"tracked async: \" + JsonUtil.toJson(log));\n}\n"
466
- }
467
- ```
406
+ ### Descrição
407
+
408
+ `track` é assíncrono (não bloqueia); `trackSynchonous` bloqueia e retorna as conquistas geradas. Escolha pela necessidade de saber o resultado.
409
+
410
+ ### Uso
468
411
 
412
+ **Assíncrono** (dispara e segue):
469
413
  ```groovy
470
414
  void trigger(event, entity, player, database) {
471
- if (!entity.actionId.equals("complete_task")) return;
415
+ if (!entity.actionId.equals("complete_task")) return; // evita loop (§1.5)
472
416
 
473
- // Async: dispara e não bloqueia — conquistas processadas em background
474
417
  ActionLog log = new ActionLog();
475
- log.id = Guid.newShortGuid(); // .id, não ._id
418
+ log.id = Guid.newShortGuid(); // .id, não ._id
476
419
  log.userId = entity.userId;
477
- log.actionId = "task_completed_by_team";
420
+ log.actionId = "task_completed_by_team"; // ação DIFERENTE da que disparou
478
421
  log.time = new Date();
479
422
  log.attributes = new HashMap();
480
423
  log.attributes.put("taskId", entity.attributes.get("taskId"));
@@ -484,11 +427,10 @@ void trigger(event, entity, player, database) {
484
427
  }
485
428
  ```
486
429
 
487
- Variante síncrona — use quando precisar saber quais conquistas foram geradas:
488
-
430
+ **Síncrono** (precisa saber quais conquistas foram geradas):
489
431
  ```groovy
490
432
  void trigger(event, entity, player, database) {
491
- // Add action filter: if (!entity.actionId.equals("your_action")) return;
433
+ // if (!entity.actionId.equals("your_action")) return; // filtre para evitar loop
492
434
 
493
435
  ActionLog log = new ActionLog();
494
436
  log.id = Guid.newShortGuid();
@@ -497,7 +439,6 @@ void trigger(event, entity, player, database) {
497
439
  log.time = new Date();
498
440
  log.attributes = new HashMap();
499
441
 
500
- // trackSynchonous bloqueia e retorna as conquistas geradas
501
442
  List<Achievement> earned = manager.getActionManager().trackSynchonous(log);
502
443
  if (earned != null) {
503
444
  println("earned: " + earned.size() + " achievements");
@@ -508,4 +449,7 @@ void trigger(event, entity, player, database) {
508
449
  }
509
450
  ```
510
451
 
511
- > Nunca chame `track` ou `trackSynchonous` dentro de um trigger `before_win` / `after_win` para a **mesma entidade/ação** que disparou o trigger — causa loop infinito. Use uma action diferente ou verifique com `entity.actionId` antes.
452
+ ### Armadilhas conhecidas
453
+
454
+ - **`track`/`trackSynchonous` da mesma ação que disparou** → loop infinito (§1.5). Correção: ação diferente ou filtro por `entity.actionId`.
455
+ - **Esperar conquistas do `track`** → retorna `void`. Correção: `trackSynchonous` (`java-managers.md` §3).