arckode-framework 1.2.2 → 1.2.3
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/cli/stubs/module-stub.ts +11 -1
- package/package.json +1 -1
- package/skills/connectors/SKILL.md +60 -4
package/cli/stubs/module-stub.ts
CHANGED
|
@@ -200,8 +200,18 @@ export class ${p.name}Service {
|
|
|
200
200
|
private readonly cache: CacheAdapter,
|
|
201
201
|
) {}
|
|
202
202
|
|
|
203
|
+
// ACUMULA handlers — nunca pisa el anterior.
|
|
204
|
+
// Si dos conectores registran el mismo evento, ambos corren en cadena (secuencial).
|
|
205
|
+
// Para ejecución paralela independiente → usar EventBus en composition-root.ts.
|
|
203
206
|
setSockets(s: Partial<${p.name}Sockets>): void {
|
|
204
|
-
|
|
207
|
+
const next = s as Record<string, any>
|
|
208
|
+
const cur = this.sockets as Record<string, any>
|
|
209
|
+
for (const key of Object.keys(next)) {
|
|
210
|
+
const h = next[key]
|
|
211
|
+
if (!h) continue
|
|
212
|
+
const prev = cur[key]
|
|
213
|
+
cur[key] = prev ? async (...a: any[]) => { await prev(...a); await h(...a) } : h
|
|
214
|
+
}
|
|
205
215
|
}
|
|
206
216
|
|
|
207
217
|
async list(query?: ${p.name}Query): Promise<${p.name}Paginated> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arckode-framework",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "AI-first TypeScript/Bun framework. Modular, SOLID, zero magic. The AI reads the composition root and knows everything.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./kernel/framework.ts",
|
|
@@ -66,9 +66,17 @@ import type { PedidosSockets } from './sockets'
|
|
|
66
66
|
export class PedidosService {
|
|
67
67
|
private sockets: PedidosSockets = {}
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// ACUMULA — si dos conectores registran el mismo evento, ambos corren en cadena.
|
|
70
|
+
// NUNCA usar { ...this.sockets, ...s } — pisa silenciosamente el handler anterior.
|
|
70
71
|
setSockets(s: Partial<PedidosSockets>): void {
|
|
71
|
-
|
|
72
|
+
const next = s as Record<string, any>
|
|
73
|
+
const cur = this.sockets as Record<string, any>
|
|
74
|
+
for (const key of Object.keys(next)) {
|
|
75
|
+
const h = next[key]
|
|
76
|
+
if (!h) continue
|
|
77
|
+
const prev = cur[key]
|
|
78
|
+
cur[key] = prev ? async (...a: any[]) => { await prev(...a); await h(...a) } : h
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
async confirmar(id: string): Promise<PedidoDTO> {
|
|
@@ -149,7 +157,54 @@ export function conectarPedidoCompleto(ctx: ConnectorContext): void {
|
|
|
149
157
|
|
|
150
158
|
---
|
|
151
159
|
|
|
152
|
-
## 7.
|
|
160
|
+
## 7. SOCKETS DENTRO VS FUERA DE TRANSACCIÓN
|
|
161
|
+
|
|
162
|
+
Este es el error más silencioso del patrón sockets. **Hay dos tipos de socket calls:**
|
|
163
|
+
|
|
164
|
+
### Dentro de `transactor.run()` — SOLO lógica DB crítica
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// citas/service.ts
|
|
168
|
+
await this.transactor.run(async () => {
|
|
169
|
+
cita = await this.repo.create({ ... })
|
|
170
|
+
await this.sockets.onCitaCreada?.(cita) // ← SOLO ops DB que deben revertirse si fallan
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Reglas:
|
|
175
|
+
- El handler DEBE poder fallar y revertir la transacción completa
|
|
176
|
+
- NO hacer llamadas HTTP (WhatsApp, email) — si el HTTP falla, la transacción falla pero el booking ya ocurrió en la DB
|
|
177
|
+
- Solo poner lógica que tenga sentido dentro de una transacción (ej: reservar un slot)
|
|
178
|
+
|
|
179
|
+
### Post-commit — notificaciones y side effects
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// Después del transactor — la cita ya existe, no se puede revertir
|
|
183
|
+
await this.sockets.onCitaCreadaCommitted?.(cita) // WhatsApp, email, CRM, etc.
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Reglas:
|
|
187
|
+
- Si el handler falla, NO revierte la transacción (ya committeó)
|
|
188
|
+
- Ideal para notificaciones, actualizaciones de otros sistemas
|
|
189
|
+
- Puede hacer HTTP, enviar mensajes, actualizar servicios externos
|
|
190
|
+
|
|
191
|
+
### Convención de nombres
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// sockets.ts — separar explícitamente por contexto de ejecución
|
|
195
|
+
export interface MiModuloSockets {
|
|
196
|
+
// Corre DENTRO de transactor.run() — para lógica DB crítica
|
|
197
|
+
onItemCreado?: (item: ItemDTO) => Promise<void>
|
|
198
|
+
// Corre DESPUÉS del commit — para notificaciones y side effects
|
|
199
|
+
onItemCreadoCommitted?: (item: ItemDTO) => Promise<void>
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Regla crítica:** si un socket dispara una llamada HTTP (WhatsApp, email, push notification), DEBE ser un evento `*Committed` post-transacción.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 8. EVENTBUS (alternativa para eventos broadcast)
|
|
153
208
|
|
|
154
209
|
Cuando muchos módulos necesitan reaccionar al mismo evento sin conocerse entre sí:
|
|
155
210
|
|
|
@@ -253,7 +308,8 @@ test('confirmar pedido funciona sin sockets registrados', async () => {
|
|
|
253
308
|
|
|
254
309
|
- [ ] Solo importa tipos (no implementaciones) desde `index.ts` de módulos
|
|
255
310
|
- [ ] Sin `if`, `for`, ni lógica de negocio — solo llamadas de delegación
|
|
256
|
-
- [ ]
|
|
311
|
+
- [ ] `setSockets()` usa el patrón ACUMULATIVO (for loop + chaining) — NUNCA `{ ...this.sockets, ...s }`
|
|
312
|
+
- [ ] Sockets con HTTP/WhatsApp/email usan eventos `*Committed` — nunca dentro de `transactor.run()`
|
|
257
313
|
- [ ] `setSockets()` usa `?.` al llamar los hooks (son opcionales)
|
|
258
314
|
- [ ] Registrado en composition-root DESPUÉS de los módulos
|
|
259
315
|
- [ ] El módulo funciona correctamente sin el conector (sockets son opcionales)
|