@wabot-dev/create 0.0.3 → 2.0.0-beta.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/README.md +45 -144
- package/lib/cli.mjs +1 -0
- package/lib/create-project.mjs +185 -75
- package/lib/i18n.mjs +167 -0
- package/lib/skills/framework-source.mjs +68 -0
- package/lib/skills/install.mjs +67 -3
- package/lib/skills/registry.mjs +29 -12
- package/package.json +5 -3
- package/skills/wabot-framework/SKILL.md +0 -32
- package/skills/wabot-framework/agents/openai.yaml +0 -5
- package/skills/wabot-framework/references/async-and-cron.md +0 -57
- package/skills/wabot-framework/references/controllers-and-auth.md +0 -104
- package/skills/wabot-framework/references/core-patterns.md +0 -83
- package/skills/wabot-framework/references/full-example.md +0 -278
- package/skills/wabot-framework/references/mindset-and-chat.md +0 -97
- package/skills/wabot-framework/references/ops.md +0 -58
- package/skills/wabot-framework/references/persistence.md +0 -85
- package/skills/wabot-framework/references/quickstart.md +0 -56
- package/skills/wabot-framework/references/validation-and-data.md +0 -88
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
# Full Example
|
|
2
|
-
|
|
3
|
-
Use this when you need an end-to-end pattern that combines most of the framework.
|
|
4
|
-
|
|
5
|
-
## What this example covers
|
|
6
|
-
|
|
7
|
-
- project bootstrap
|
|
8
|
-
- mindset and chat routing
|
|
9
|
-
- external mindset module
|
|
10
|
-
- validation and mapper
|
|
11
|
-
- persistence
|
|
12
|
-
- REST and socket controllers
|
|
13
|
-
- JWT auth
|
|
14
|
-
- async command and cron
|
|
15
|
-
- logger and locker
|
|
16
|
-
|
|
17
|
-
## 1. Bootstrap
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
import {
|
|
21
|
-
ChatAdapter,
|
|
22
|
-
ChatRepository,
|
|
23
|
-
container,
|
|
24
|
-
Env,
|
|
25
|
-
PgChatRepository,
|
|
26
|
-
runChatControllers,
|
|
27
|
-
runRestControllers,
|
|
28
|
-
WabotChatAdapter,
|
|
29
|
-
} from '@wabot-dev/framework'
|
|
30
|
-
import { Pool } from 'pg'
|
|
31
|
-
|
|
32
|
-
import { SalesChatController } from './controllers/SalesChatController'
|
|
33
|
-
import { SalesWebhookController } from './controllers/SalesWebhookController'
|
|
34
|
-
|
|
35
|
-
const env = container.resolve(Env)
|
|
36
|
-
container.registerInstance(Pool, new Pool({ connectionString: env.requireString('DATABASE_URL') }))
|
|
37
|
-
container.registerType(ChatAdapter, WabotChatAdapter)
|
|
38
|
-
container.registerType(ChatRepository, PgChatRepository)
|
|
39
|
-
|
|
40
|
-
runRestControllers([SalesWebhookController])
|
|
41
|
-
runChatControllers([SalesChatController])
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## 2. Mindset and module
|
|
45
|
-
|
|
46
|
-
```typescript
|
|
47
|
-
import {
|
|
48
|
-
type IMindset,
|
|
49
|
-
type IMindsetIdentity,
|
|
50
|
-
type IMindsetLlm,
|
|
51
|
-
description,
|
|
52
|
-
mindset,
|
|
53
|
-
mindsetModule,
|
|
54
|
-
} from '@wabot-dev/framework'
|
|
55
|
-
|
|
56
|
-
@mindsetModule()
|
|
57
|
-
export class LeadInsightsModule {
|
|
58
|
-
@description('Summarize the latest notes and suggest the next best action')
|
|
59
|
-
async summarizeLead(req: { note: string }) {
|
|
60
|
-
return `Next action: ${req.note}`
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
@mindset({ modules: [LeadInsightsModule] })
|
|
65
|
-
export class SalesMindset implements IMindset {
|
|
66
|
-
async context() {
|
|
67
|
-
return 'Act like a helpful sales assistant.'
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async identity(): Promise<IMindsetIdentity> {
|
|
71
|
-
return { name: 'Martin', language: 'Español' }
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async skills() {
|
|
75
|
-
return 'Qualify leads, explain offers, and guide the next step.'
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async limits() {
|
|
79
|
-
return 'Do not invent prices or promise features that are not available.'
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async workflow() {
|
|
83
|
-
return 'Ask one clear question at a time and move toward qualification.'
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async llms(): Promise<IMindsetLlm[]> {
|
|
87
|
-
return [{ provider: 'openai', model: 'gpt-4o-mini' }]
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## 3. Validation and persistence
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
import {
|
|
96
|
-
Entity,
|
|
97
|
-
Locker,
|
|
98
|
-
Logger,
|
|
99
|
-
Mapper,
|
|
100
|
-
PgCrudRepository,
|
|
101
|
-
type IEntityData,
|
|
102
|
-
injectable,
|
|
103
|
-
isNotEmpty,
|
|
104
|
-
isNumber,
|
|
105
|
-
isString,
|
|
106
|
-
min,
|
|
107
|
-
singleton,
|
|
108
|
-
} from '@wabot-dev/framework'
|
|
109
|
-
import { Pool } from 'pg'
|
|
110
|
-
|
|
111
|
-
export class CreateLeadDto {
|
|
112
|
-
@isString()
|
|
113
|
-
@isNotEmpty()
|
|
114
|
-
name!: string
|
|
115
|
-
|
|
116
|
-
@isNumber()
|
|
117
|
-
@min(1)
|
|
118
|
-
budget!: number
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface LeadData extends IEntityData {
|
|
122
|
-
name: string
|
|
123
|
-
budget: number
|
|
124
|
-
status: 'new' | 'qualified'
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export class Lead extends Entity<LeadData> {}
|
|
128
|
-
|
|
129
|
-
@singleton()
|
|
130
|
-
export class LeadRepository extends PgCrudRepository<Lead> {
|
|
131
|
-
constructor(pool: Pool) {
|
|
132
|
-
super(pool, { table: 'leads', constructor: Lead })
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
@injectable()
|
|
137
|
-
export class LeadService {
|
|
138
|
-
constructor(
|
|
139
|
-
private mapper: Mapper,
|
|
140
|
-
private leads: LeadRepository,
|
|
141
|
-
private logger: Logger,
|
|
142
|
-
private locker: Locker,
|
|
143
|
-
) {}
|
|
144
|
-
|
|
145
|
-
async create(raw: unknown) {
|
|
146
|
-
const lead = this.mapper.map(raw, CreateLeadDto)
|
|
147
|
-
await this.locker.withKey(`lead:${lead.name}`).run(async () => {
|
|
148
|
-
this.logger.info('creating lead', { name: lead.name })
|
|
149
|
-
await this.leads.create(new Lead({ name: lead.name, budget: lead.budget, status: 'new' }))
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## 4. Chat controller
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
import { chatBot, chatController, ChatBot, cmd, type IReceivedMessage } from '@wabot-dev/framework'
|
|
159
|
-
|
|
160
|
-
@chatController()
|
|
161
|
-
export class SalesChatController {
|
|
162
|
-
constructor(@chatBot(SalesMindset) private bot: ChatBot) {}
|
|
163
|
-
|
|
164
|
-
@cmd()
|
|
165
|
-
async onMessage(context: IReceivedMessage) {
|
|
166
|
-
const fallback = 'Puedo ayudarte a revisar el caso y registrar el siguiente paso.'
|
|
167
|
-
|
|
168
|
-
await this.bot.sendMessage(context.message, async (replyMessage) => {
|
|
169
|
-
await context.reply(replyMessage ?? { text: fallback })
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## 5. REST and socket auth
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
import {
|
|
179
|
-
apiKeyGuard,
|
|
180
|
-
apiKeyHandshakeGuard,
|
|
181
|
-
jwtGuard,
|
|
182
|
-
jwtHandshakeGuard,
|
|
183
|
-
onGet,
|
|
184
|
-
onPost,
|
|
185
|
-
onSocketEvent,
|
|
186
|
-
restController,
|
|
187
|
-
socketController,
|
|
188
|
-
} from '@wabot-dev/framework'
|
|
189
|
-
|
|
190
|
-
@restController('/api/leads')
|
|
191
|
-
export class SalesApiController {
|
|
192
|
-
constructor(private leadService: LeadService) {}
|
|
193
|
-
|
|
194
|
-
@apiKeyGuard()
|
|
195
|
-
@onGet()
|
|
196
|
-
async list() {
|
|
197
|
-
return []
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
@jwtGuard()
|
|
201
|
-
@onPost()
|
|
202
|
-
async create(body: CreateLeadDto) {
|
|
203
|
-
return this.leadService.create(body)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
@socketController('/sales')
|
|
208
|
-
@jwtHandshakeGuard()
|
|
209
|
-
export class SalesSocketController {
|
|
210
|
-
@onSocketEvent('ping')
|
|
211
|
-
async ping() {
|
|
212
|
-
return { ok: true }
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
@apiKeyHandshakeGuard()
|
|
217
|
-
@socketController('/sales-admin')
|
|
218
|
-
export class SalesAdminSocketController {}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## 6. Async and cron
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
import { Async, command, commandHandler, cron, isString } from '@wabot-dev/framework'
|
|
225
|
-
|
|
226
|
-
@command('sync-lead')
|
|
227
|
-
export class SyncLeadCommand {
|
|
228
|
-
@isString()
|
|
229
|
-
leadId!: string
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
@commandHandler(SyncLeadCommand)
|
|
233
|
-
export class SyncLeadHandler {
|
|
234
|
-
constructor(private leadService: LeadService) {}
|
|
235
|
-
|
|
236
|
-
async handle(input: SyncLeadCommand) {
|
|
237
|
-
await this.leadService.create({ name: input.leadId, budget: 1 })
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
@cron({ name: 'sync-leads', cron: '0 * * * *' })
|
|
242
|
-
export class SyncLeadsCron {
|
|
243
|
-
constructor(private async: Async) {}
|
|
244
|
-
|
|
245
|
-
async handle() {
|
|
246
|
-
await this.async.runCommand(SyncLeadCommand, { leadId: 'all' })
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
## 7. Operational guardrails
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
import { container, Locker, Logger, PgLocker } from '@wabot-dev/framework'
|
|
255
|
-
import { injectable } from '@wabot-dev/framework'
|
|
256
|
-
|
|
257
|
-
container.registerType(Locker, PgLocker)
|
|
258
|
-
|
|
259
|
-
@injectable()
|
|
260
|
-
export class SalesOpsService {
|
|
261
|
-
constructor(private locker: Locker) {}
|
|
262
|
-
|
|
263
|
-
async sync() {
|
|
264
|
-
const logger = new Logger('sales:app')
|
|
265
|
-
logger.info('app started')
|
|
266
|
-
|
|
267
|
-
await this.locker.withKey('sales-sync').run(async () => {
|
|
268
|
-
logger.debug('syncing leads')
|
|
269
|
-
})
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
## Read this example when
|
|
275
|
-
|
|
276
|
-
- the user wants the whole flow
|
|
277
|
-
- the task spans several framework areas
|
|
278
|
-
- the answer needs a working mental model, not just one decorator
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
# Mindset And Chat
|
|
2
|
-
|
|
3
|
-
Use this when defining bot behavior or routing chat messages.
|
|
4
|
-
|
|
5
|
-
## Mindset shape
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { type IMindset, type IMindsetIdentity, type IMindsetLlm, mindset } from '@wabot-dev/framework'
|
|
9
|
-
import { description, mindsetModule } from '@wabot-dev/framework'
|
|
10
|
-
|
|
11
|
-
class LeadNotesDto {
|
|
12
|
-
note!: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
@mindsetModule()
|
|
16
|
-
export class SalesInsightsModule {
|
|
17
|
-
@description('Summarize the lead history and suggest the next action')
|
|
18
|
-
async summarizeLead(input: LeadNotesDto) {
|
|
19
|
-
return `Lead note: ${input.note}`
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@mindset({ modules: [SalesInsightsModule] })
|
|
24
|
-
export class SalesMindset implements IMindset {
|
|
25
|
-
async context(): Promise<string> {
|
|
26
|
-
return 'You are a helpful sales assistant that keeps replies short and concrete.'
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async identity(): Promise<IMindsetIdentity> {
|
|
30
|
-
return {
|
|
31
|
-
name: 'Martin',
|
|
32
|
-
language: 'Español',
|
|
33
|
-
personality: 'Calmo, persuasivo y orientado a resultados',
|
|
34
|
-
emotions: 'Empatico, confiado y directo',
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async skills(): Promise<string> {
|
|
39
|
-
return `
|
|
40
|
-
Calificar prospectos.
|
|
41
|
-
Explicar beneficios con claridad.
|
|
42
|
-
Manejar objeciones sin presionar.
|
|
43
|
-
`
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async limits(): Promise<string> {
|
|
47
|
-
return `
|
|
48
|
-
No prometer funciones que no existen.
|
|
49
|
-
No inventar precios ni descuentos.
|
|
50
|
-
No pedir datos sensibles.
|
|
51
|
-
`
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async workflow(): Promise<string> {
|
|
55
|
-
return `
|
|
56
|
-
1. Entender la necesidad.
|
|
57
|
-
2. Hacer una pregunta clara.
|
|
58
|
-
3. Registrar datos utiles.
|
|
59
|
-
4. Proponer el siguiente paso.
|
|
60
|
-
`
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async llms(): Promise<IMindsetLlm[]> {
|
|
64
|
-
return [{ provider: 'openai', model: 'gpt-4o-mini' }]
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Mindset module
|
|
70
|
-
|
|
71
|
-
Use a module when the mindset needs an extra tool-like function.
|
|
72
|
-
|
|
73
|
-
## Chat controller
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { chatBot, chatController, ChatBot, cmd, type IReceivedMessage } from '@wabot-dev/framework'
|
|
77
|
-
|
|
78
|
-
@chatController()
|
|
79
|
-
export class SalesChatController {
|
|
80
|
-
constructor(@chatBot(SalesMindset) private bot: ChatBot) {}
|
|
81
|
-
|
|
82
|
-
@cmd()
|
|
83
|
-
async onMessage(context: IReceivedMessage) {
|
|
84
|
-
const fallback = 'Te ayudo a elegir una solucion y avanzar al siguiente paso.'
|
|
85
|
-
|
|
86
|
-
await this.bot.sendMessage(context.message, async (replyMessage) => {
|
|
87
|
-
await context.reply(replyMessage ?? { text: fallback })
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Good practice
|
|
94
|
-
|
|
95
|
-
- Put policy and personality in the mindset.
|
|
96
|
-
- Put business rules in services or modules.
|
|
97
|
-
- Keep the controller only as an entry point and reply bridge.
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# Operations
|
|
2
|
-
|
|
3
|
-
Use this when adding observability or duplicate-work protection.
|
|
4
|
-
|
|
5
|
-
## Logger
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { Logger } from '@wabot-dev/framework'
|
|
9
|
-
|
|
10
|
-
const logger = new Logger('sales:lead-service')
|
|
11
|
-
|
|
12
|
-
logger.info('service started')
|
|
13
|
-
logger.debug('loading lead', { leadId: 'abc123' })
|
|
14
|
-
logger.warn('fallback used', { reason: 'missing metadata' })
|
|
15
|
-
logger.error('failed to save lead', new Error('db timeout'))
|
|
16
|
-
logger.fatal('process cannot continue')
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Logging rule
|
|
20
|
-
|
|
21
|
-
- `trace`: every request, socket event, or message.
|
|
22
|
-
- `debug`: operational steps and query-level detail.
|
|
23
|
-
- `info`: lifecycle milestones and meaningful state changes.
|
|
24
|
-
- `warn`: recoverable issues or unusual conditions.
|
|
25
|
-
- `error`: failed operations with context and error.
|
|
26
|
-
- `fatal`: unrecoverable process-level failures.
|
|
27
|
-
|
|
28
|
-
## Locker
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
import { container, Locker, PgLocker } from '@wabot-dev/framework'
|
|
32
|
-
|
|
33
|
-
container.registerType(Locker, PgLocker)
|
|
34
|
-
|
|
35
|
-
const locker = container.resolve(Locker)
|
|
36
|
-
|
|
37
|
-
await locker.withKey('lead-sync').run(async () => {
|
|
38
|
-
await syncLeads()
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const maybeResult = await locker.withKey('lead-sync').tryRun(async () => {
|
|
42
|
-
await syncLeads()
|
|
43
|
-
})
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Combining with entities
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
await locker.withKey(lead).run(async () => {
|
|
50
|
-
await leadService.qualify(lead.id)
|
|
51
|
-
})
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Rule of thumb
|
|
55
|
-
|
|
56
|
-
- Use logger for visibility.
|
|
57
|
-
- Use locker when duplicate processing would be harmful.
|
|
58
|
-
- Prefer small lock scopes so the critical section stays short.
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Persistence
|
|
2
|
-
|
|
3
|
-
Use this when saving or loading domain information from PostgreSQL.
|
|
4
|
-
|
|
5
|
-
## Entity
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { Entity, PgCrudRepository, type IEntityData, injectable, singleton, Locker, Logger } from '@wabot-dev/framework'
|
|
9
|
-
import { Pool } from 'pg'
|
|
10
|
-
|
|
11
|
-
export interface LeadData extends IEntityData {
|
|
12
|
-
name: string
|
|
13
|
-
budget: number
|
|
14
|
-
status: 'new' | 'qualified' | 'won' | 'lost'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class Lead extends Entity<LeadData> {
|
|
18
|
-
get name() {
|
|
19
|
-
return this['data'].name
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
setQualified() {
|
|
23
|
-
this.update({ status: 'qualified' })
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Repository
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
import { singleton } from '@wabot-dev/framework'
|
|
32
|
-
import { Pool } from 'pg'
|
|
33
|
-
import { PgCrudRepository } from '@wabot-dev/framework'
|
|
34
|
-
|
|
35
|
-
@singleton()
|
|
36
|
-
export class LeadRepository extends PgCrudRepository<Lead> {
|
|
37
|
-
constructor(pool: Pool) {
|
|
38
|
-
super(pool, {
|
|
39
|
-
table: 'leads',
|
|
40
|
-
constructor: Lead,
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Service using the repository
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
import { injectable } from '@wabot-dev/framework'
|
|
50
|
-
|
|
51
|
-
@injectable()
|
|
52
|
-
export class LeadService {
|
|
53
|
-
constructor(
|
|
54
|
-
private leads: LeadRepository,
|
|
55
|
-
private locker: Locker,
|
|
56
|
-
private logger: Logger,
|
|
57
|
-
) {}
|
|
58
|
-
|
|
59
|
-
async qualify(id: string) {
|
|
60
|
-
return await this.locker.withKey(`lead:${id}`).run(async () => {
|
|
61
|
-
const lead = await this.leads.findOrThrow(id)
|
|
62
|
-
lead.setQualified()
|
|
63
|
-
this.logger.info('lead qualified', { id })
|
|
64
|
-
await this.leads.update(lead)
|
|
65
|
-
return lead
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Lock key
|
|
72
|
-
|
|
73
|
-
Entities already provide a `lockerKey()` implementation through `Entity`, so they can be used with the lock system.
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
await locker.withKey(lead).run(async () => {
|
|
77
|
-
await leadService.qualify(lead.id)
|
|
78
|
-
})
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Rule of thumb
|
|
82
|
-
|
|
83
|
-
- Keep persistence logic in repositories.
|
|
84
|
-
- Keep domain rules in entities or services.
|
|
85
|
-
- Let services orchestrate repository calls.
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# Quickstart
|
|
2
|
-
|
|
3
|
-
Use this when starting a new Wabot app or explaining how the app boots.
|
|
4
|
-
|
|
5
|
-
## Minimal bootstrap
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import {
|
|
9
|
-
ChatAdapter,
|
|
10
|
-
ChatRepository,
|
|
11
|
-
container,
|
|
12
|
-
Env,
|
|
13
|
-
PgChatRepository,
|
|
14
|
-
runChatControllers,
|
|
15
|
-
WabotChatAdapter,
|
|
16
|
-
} from '@wabot-dev/framework'
|
|
17
|
-
import { Pool } from 'pg'
|
|
18
|
-
|
|
19
|
-
import { MyChatController } from './controllers/MyChatController'
|
|
20
|
-
|
|
21
|
-
const env = container.resolve(Env)
|
|
22
|
-
|
|
23
|
-
container.registerInstance(
|
|
24
|
-
Pool,
|
|
25
|
-
new Pool({ connectionString: env.requireString('DATABASE_URL') }),
|
|
26
|
-
)
|
|
27
|
-
container.registerType(ChatAdapter, WabotChatAdapter)
|
|
28
|
-
container.registerType(ChatRepository, PgChatRepository)
|
|
29
|
-
|
|
30
|
-
runChatControllers([MyChatController])
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Starter flow
|
|
34
|
-
|
|
35
|
-
1. Create the app scaffold.
|
|
36
|
-
2. Add the database connection.
|
|
37
|
-
3. Register the chat adapter and chat repository.
|
|
38
|
-
4. Run the chat controller.
|
|
39
|
-
|
|
40
|
-
## Typical files
|
|
41
|
-
|
|
42
|
-
```text
|
|
43
|
-
src/
|
|
44
|
-
run.ts
|
|
45
|
-
controllers/MyChatController.ts
|
|
46
|
-
mindsets/MyMindset.ts
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## First commands
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npx @wabot-dev/create
|
|
53
|
-
npm install
|
|
54
|
-
npm run dev
|
|
55
|
-
```
|
|
56
|
-
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Validation And Data
|
|
2
|
-
|
|
3
|
-
Use this when shaping DTOs or mapping raw data into typed objects.
|
|
4
|
-
|
|
5
|
-
## Common validators
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import {
|
|
9
|
-
isArray,
|
|
10
|
-
isBoolean,
|
|
11
|
-
isDate,
|
|
12
|
-
isIn,
|
|
13
|
-
isModel,
|
|
14
|
-
isNotEmpty,
|
|
15
|
-
isNumber,
|
|
16
|
-
isOptional,
|
|
17
|
-
isPresent,
|
|
18
|
-
isRecord,
|
|
19
|
-
isString,
|
|
20
|
-
max,
|
|
21
|
-
min,
|
|
22
|
-
} from '@wabot-dev/framework'
|
|
23
|
-
|
|
24
|
-
export class AddressDto {
|
|
25
|
-
@isString()
|
|
26
|
-
@isNotEmpty()
|
|
27
|
-
street!: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class CreateLeadDto {
|
|
31
|
-
@isString()
|
|
32
|
-
@isPresent()
|
|
33
|
-
@isNotEmpty()
|
|
34
|
-
name!: string
|
|
35
|
-
|
|
36
|
-
@isNumber()
|
|
37
|
-
@min(1)
|
|
38
|
-
@max(1000)
|
|
39
|
-
budget!: number
|
|
40
|
-
|
|
41
|
-
@isBoolean()
|
|
42
|
-
isQualified!: boolean
|
|
43
|
-
|
|
44
|
-
@isDate()
|
|
45
|
-
followUpAt?: Date
|
|
46
|
-
|
|
47
|
-
@isString()
|
|
48
|
-
@isIn(['new', 'qualified', 'won', 'lost'])
|
|
49
|
-
status!: string
|
|
50
|
-
|
|
51
|
-
@isArray({ minLength: 1 })
|
|
52
|
-
@isString()
|
|
53
|
-
tags!: string[]
|
|
54
|
-
|
|
55
|
-
@isModel(AddressDto)
|
|
56
|
-
address?: AddressDto
|
|
57
|
-
|
|
58
|
-
@isRecord('string', 'string')
|
|
59
|
-
metadata?: Record<string, string>
|
|
60
|
-
|
|
61
|
-
@isString()
|
|
62
|
-
@isOptional()
|
|
63
|
-
source?: string
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Mapper
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
import { injectable, Mapper } from '@wabot-dev/framework'
|
|
71
|
-
|
|
72
|
-
@injectable()
|
|
73
|
-
export class LeadService {
|
|
74
|
-
constructor(private mapper: Mapper) {}
|
|
75
|
-
|
|
76
|
-
create(raw: unknown) {
|
|
77
|
-
const lead = this.mapper.map(raw, CreateLeadDto)
|
|
78
|
-
return lead
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Practical rule
|
|
84
|
-
|
|
85
|
-
- Use validation decorators on the DTO itself.
|
|
86
|
-
- Use `Mapper` when the input is raw, unknown, or nested.
|
|
87
|
-
- Keep transformation and validation together before the service work starts.
|
|
88
|
-
|