@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.
@@ -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
-