@wabot-dev/framework 0.9.80 → 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.
Files changed (77) hide show
  1. package/bin/skills.mjs +151 -0
  2. package/bin/wabot-skills.mjs +120 -0
  3. package/dist/build/build.js +1031 -8
  4. package/dist/src/addon/chat-bot/in-memory/InMemoryChatMemory.js +1 -3
  5. package/dist/src/addon/chat-bot/xai/XAIChatAdapter.js +180 -0
  6. package/dist/src/addon/chat-controller/cmd/cmdChannelSocketPath.js +1 -5
  7. package/dist/src/addon/chat-controller/hubspot/@hubspot.js +28 -0
  8. package/dist/src/addon/chat-controller/hubspot/HubSpotChannel.js +81 -0
  9. package/dist/src/addon/chat-controller/hubspot/HubSpotChannelConfig.js +20 -0
  10. package/dist/src/addon/chat-controller/hubspot/HubSpotReceiver.js +42 -0
  11. package/dist/src/addon/chat-controller/hubspot/HubSpotSender.js +118 -0
  12. package/dist/src/addon/chat-controller/hubspot/HubSpotWebhookController.js +122 -0
  13. package/dist/src/addon/chat-controller/hubspot/downloadHubSpotAttachments.js +45 -0
  14. package/dist/src/addon/chat-controller/hubspot/hubspotChannelName.js +3 -0
  15. package/dist/src/addon/chat-controller/hubspot/verifyHubSpotSignatureV3.js +28 -0
  16. package/dist/src/addon/chat-controller/{telegram/markdownToTelegramHtml.js → markdown/markdownToChatHtml.js} +5 -8
  17. package/dist/src/addon/chat-controller/slack/@slack.js +22 -0
  18. package/dist/src/addon/chat-controller/slack/SlackChannel.js +187 -0
  19. package/dist/src/addon/chat-controller/slack/SlackChannelConfig.js +12 -0
  20. package/dist/src/addon/chat-controller/slack/markdownToSlackMrkdwn.js +38 -0
  21. package/dist/src/addon/chat-controller/slack/slackChannelName.js +3 -0
  22. package/dist/src/addon/chat-controller/telegram/TelegramChannel.js +2 -2
  23. package/dist/src/addon/ui/preact/PreactRenderer.js +86 -0
  24. package/dist/src/addon/ui/preact/outlet.js +22 -0
  25. package/dist/src/addon/ui/preact/preactClientRuntime.js +67 -0
  26. package/dist/src/core/repository/CrudRepository.js +7 -7
  27. package/dist/src/feature/async/computeDedupKey.js +1 -1
  28. package/dist/src/feature/pg/@pgExtension.js +2 -4
  29. package/dist/src/feature/project-runner/ProjectRunner.js +62 -10
  30. package/dist/src/feature/project-runner/scanner.js +1 -1
  31. package/dist/src/feature/repository/@memExtension.js +1 -2
  32. package/dist/src/feature/ui-controller/actions.js +35 -0
  33. package/dist/src/feature/ui-controller/bundler/UiBundler.js +191 -0
  34. package/dist/src/feature/ui-controller/bundler/devMiddleware.js +41 -0
  35. package/dist/src/feature/ui-controller/bundler/index.js +4 -0
  36. package/dist/src/feature/ui-controller/bundler/manifest.js +34 -0
  37. package/dist/src/feature/ui-controller/bundler/navRuntime.js +236 -0
  38. package/dist/src/feature/ui-controller/bundler/pageAssets.js +30 -0
  39. package/dist/src/feature/ui-controller/document/escape.js +17 -0
  40. package/dist/src/feature/ui-controller/document/helpers.js +13 -0
  41. package/dist/src/feature/ui-controller/document/renderDocument.js +43 -0
  42. package/dist/src/feature/ui-controller/island/IslandRegistry.js +68 -0
  43. package/dist/src/feature/ui-controller/island/island.js +40 -0
  44. package/dist/src/feature/ui-controller/island/serialize.js +35 -0
  45. package/dist/src/feature/ui-controller/metadata/@action.js +18 -0
  46. package/dist/src/feature/ui-controller/metadata/@uiController.js +19 -0
  47. package/dist/src/feature/ui-controller/metadata/@uiMiddleware.js +20 -0
  48. package/dist/src/feature/ui-controller/metadata/@view.js +18 -0
  49. package/dist/src/feature/ui-controller/metadata/UiControllerMetadataStore.js +107 -0
  50. package/dist/src/feature/ui-controller/renderer/UiRendererRegistry.js +42 -0
  51. package/dist/src/feature/ui-controller/runUiControllers.js +285 -0
  52. package/dist/src/index.d.ts +632 -3
  53. package/dist/src/index.js +30 -1
  54. package/dist/src/testing/index.d.ts +43 -1
  55. package/dist/src/testing/index.js +1 -0
  56. package/dist/src/testing/uiHarness.js +102 -0
  57. package/dist/src/ui/client.js +6 -0
  58. package/dist/src/ui/index.d.ts +427 -0
  59. package/dist/src/ui/index.js +29 -0
  60. package/dist/src/ui/jsx-dev-runtime.d.ts +1 -0
  61. package/dist/src/ui/jsx-dev-runtime.js +1 -0
  62. package/dist/src/ui/jsx-runtime.d.ts +1 -0
  63. package/dist/src/ui/jsx-runtime.js +1 -0
  64. package/package.json +33 -13
  65. package/skills/wabot-async/SKILL.md +143 -0
  66. package/skills/wabot-auth/SKILL.md +153 -0
  67. package/skills/wabot-chat/SKILL.md +140 -0
  68. package/skills/wabot-di-config/SKILL.md +117 -0
  69. package/skills/wabot-framework/SKILL.md +81 -0
  70. package/skills/wabot-framework/references/quickstart.md +85 -0
  71. package/skills/wabot-mindset/SKILL.md +159 -0
  72. package/skills/wabot-ops/SKILL.md +151 -0
  73. package/skills/wabot-persistence/SKILL.md +159 -0
  74. package/skills/wabot-rest-socket/SKILL.md +167 -0
  75. package/skills/wabot-testing/SKILL.md +214 -0
  76. package/skills/wabot-ui/SKILL.md +201 -0
  77. package/skills/wabot-validation/SKILL.md +108 -0
@@ -0,0 +1,201 @@
1
+ ---
2
+ name: wabot-ui
3
+ description: Use when building server-rendered web pages, forms, or interactive UI from a Wabot app. Covers @uiController, @view, @action, @uiMiddleware, the one validated-DTO handler arg, redirect(), islands (island() in *.island.tsx, callAction/actionUrl), signals vs hooks, boosted navigation (app: true) with layout/<Outlet/> and swr, head resource hints, CSS modules, the tsconfig JSX setup, and custom UiRenderer. Argument validation is shared with wabot-validation.
4
+ ---
5
+
6
+ # UI controllers
7
+
8
+ Wabot's UI layer server-renders Preact and ships JavaScript only for the interactive parts ("islands"). You write `@uiController` classes with `@view` (GET → HTML) and `@action` (POST → JSON) methods; the runner discovers them from `src/`, SSRs each view inside a default HTML document, and — in dev/prod — bundles islands for client hydration. Everything works without JS; islands are a progressive enhancement.
9
+
10
+ Everything ships from `@wabot-dev/framework/ui`. Never import from internal paths.
11
+
12
+ ## Setup
13
+
14
+ Importing `@wabot-dev/framework/ui` registers the Preact renderer as a side effect. Configure JSX in `tsconfig.json`:
15
+
16
+ ```jsonc
17
+ {
18
+ "compilerOptions": {
19
+ "jsx": "react-jsx",
20
+ "jsxImportSource": "@wabot-dev/framework/ui"
21
+ }
22
+ }
23
+ ```
24
+
25
+ No manual registration: `run(config)` discovers `@uiController` classes, loads the Preact renderer, and sets up the island bundler automatically. `config.ui.bundlerAlias` is only needed when islands import the framework UI through a non-package specifier (path alias in a monorepo/in-repo dev) — regular consumers never set it.
26
+
27
+ ## Controller
28
+
29
+ ```tsx
30
+ import { isNotEmpty, isString } from '@wabot-dev/framework'
31
+ import { uiController, view, action, redirect } from '@wabot-dev/framework/ui'
32
+ import { HomePage } from './ui/pages/HomePage'
33
+ import { addMessage, messages } from './ui/store'
34
+
35
+ class AddMessageDto {
36
+ @isString()
37
+ @isNotEmpty()
38
+ text?: string
39
+ }
40
+
41
+ @uiController('/board')
42
+ export class BoardController {
43
+ // GET /board — empty/omitted path is the controller index.
44
+ @view({ title: 'Message board' })
45
+ index() {
46
+ return <HomePage messages={messages} />
47
+ }
48
+
49
+ // GET /board/archive
50
+ @view({ path: 'archive', title: 'Archive' })
51
+ archive() {
52
+ return <HomePage messages={messages} archived />
53
+ }
54
+
55
+ // POST /board/_action/add — path defaults to the method name.
56
+ @action()
57
+ add(input: AddMessageDto) {
58
+ addMessage(input.text!)
59
+ return redirect('/board') // post/redirect/get for no-JS forms
60
+ }
61
+
62
+ // POST /board/_action/postMessage — returns JSON for callAction() from an island.
63
+ @action()
64
+ postMessage(input: AddMessageDto) {
65
+ addMessage(input.text!)
66
+ return { messages }
67
+ }
68
+ }
69
+ ```
70
+
71
+ - **`@uiController(path | { path, middlewares?, app?, layout?, head? })`** — `path` is the base every view/action mounts under.
72
+ - **`@view(subpath | { path?, title?, meta?, swr? })`** → `GET <controllerPath>/<path>`. Returns a JSX node (SSR'd) or `redirect(...)`. `title`/`meta` fill the document `<head>`.
73
+ - **`@action(subpath | { path? })`** → `POST <controllerPath>/_action/<path>` (path defaults to the method name). Returns a JSON-serializable value (circular refs stripped) or `redirect(...)`.
74
+ - **Handler args:** at most **one** parameter — a class the framework validates exactly like a rest-controller request DTO (see `wabot-validation`). The request object is `body`, `query`, and route `params` merged. Omit the parameter for handlers that need no input.
75
+ - **`redirect(location, status = 302)`** — return it from a view or action to send an HTTP redirect instead of rendering.
76
+
77
+ ## Middlewares / guards
78
+
79
+ Same `IMiddleware` contract as REST (see `wabot-rest-socket` / `wabot-auth`).
80
+
81
+ ```tsx
82
+ import { injectable, type IMiddleware } from '@wabot-dev/framework'
83
+ import { uiController, uiMiddleware } from '@wabot-dev/framework/ui'
84
+
85
+ @injectable()
86
+ class RequireAuth implements IMiddleware {
87
+ async handle(req: unknown, res: unknown) {
88
+ /* throw or write a response to block */
89
+ }
90
+ }
91
+
92
+ @uiController({ path: '/admin', middlewares: [RequireAuth] }) // whole controller
93
+ export class AdminController {
94
+ @uiMiddleware(RequireAuth) // …or a single view/action
95
+ @view()
96
+ index() {
97
+ return <Dashboard />
98
+ }
99
+ }
100
+ ```
101
+
102
+ Middlewares run before the handler; if one sends a response, the handler is skipped.
103
+
104
+ ## Islands (client interactivity)
105
+
106
+ Only islands ship JS and hydrate; the rest of the page is static SSR HTML. An island **must** be the default export of a `*.island.tsx` file so the bundler can give it a stable id.
107
+
108
+ ```tsx
109
+ // Counter.island.tsx
110
+ import { island, useSignal } from '@wabot-dev/framework/ui'
111
+
112
+ function Counter({ start = 0 }: { start?: number }) {
113
+ const count = useSignal(start)
114
+ return <button onClick={() => count.value++}>{count}</button>
115
+ }
116
+
117
+ export default island(Counter)
118
+ ```
119
+
120
+ Render an island like any component from a view or another component: `<Counter start={3} />`. Props must be JSON-serializable (they are sent to the client for hydration). Islands may use **signals** (`signal`, `useSignal`, `useComputed`, `useSignalEffect`) or **hooks** (`useState`, `useEffect`, `useRef`, …) — both re-exported from `@wabot-dev/framework/ui`.
121
+
122
+ ### Calling actions from an island
123
+
124
+ ```tsx
125
+ // MessageBoard.island.tsx
126
+ import { island, useSignal, callAction, actionUrl } from '@wabot-dev/framework/ui'
127
+
128
+ const POST_URL = actionUrl('/board', 'postMessage') // -> /board/_action/postMessage
129
+
130
+ function MessageBoard({ initial = [] }) {
131
+ const messages = useSignal(initial)
132
+ async function send(text: string) {
133
+ const res = await callAction<{ messages: any[] }>(POST_URL, { text })
134
+ messages.value = res.messages
135
+ }
136
+ // …form that calls send()…
137
+ }
138
+
139
+ export default island(MessageBoard)
140
+ ```
141
+
142
+ - **`actionUrl(controllerPath, actionName)`** builds the action URL.
143
+ - **`callAction<T>(url, data?, { headers?, signal? })`** POSTs JSON and returns the parsed result; it throws on non-2xx with the server-provided error message.
144
+ - Both are client-safe (no Node imports) so they bundle into islands. A plain `<form method="post" action="/board/_action/add">` also works for no-JS progressive enhancement.
145
+
146
+ ## App shell & boosted navigation
147
+
148
+ `app: true` opts a controller into client-side ("boosted") navigation between its own views: in-scope links are swapped in without a full reload, backed by a stale-while-revalidate cache. Views still SSR and work without JS.
149
+
150
+ ```tsx
151
+ import { uiController, view, Outlet } from '@wabot-dev/framework/ui'
152
+
153
+ function AppLayout() {
154
+ return (
155
+ <div class="app">
156
+ <nav>{/* … */}</nav>
157
+ <main><Outlet /></main>{/* current view renders here */}
158
+ </div>
159
+ )
160
+ }
161
+
162
+ @uiController({ path: '/panel', app: true, layout: AppLayout })
163
+ export class PanelController {
164
+ @view({ path: '', title: 'Home', swr: { maxAge: 30 } })
165
+ home() { return <HomeView /> }
166
+
167
+ @view({ path: ':id', title: 'Detail', swr: { version: ({ id }) => revisionOf(id) } })
168
+ detail(input: DetailParams) { return <DetailView id={input.id} /> }
169
+ }
170
+ ```
171
+
172
+ - **`layout: Component`** — a persistent shell rendered once around every view; it renders `<Outlet/>` where the current view goes. During boosted nav only the outlet swaps, so the shell and its islands keep their state. Full loads render inside the layout; boosted-nav fragments render just the view.
173
+ - **`swr: { maxAge?, version? }`** on `@view` tunes the boosted-nav cache. `maxAge` (seconds) serves a revisit from cache without revalidating. `version(request)` returns a short deterministic string; boosted-nav revalidation answers `304` from it *without running the handler or SSR*. Parameterized `app` views should declare `swr.version` keyed off their route params.
174
+ - **`head: { preconnect?, preload? }`** on `@uiController` emits `<link rel="preconnect|preload">` hints on full loads (the natural home for app-wide fonts — the head persists across boosted nav).
175
+
176
+ ## Styling
177
+
178
+ CSS modules are supported in islands and components:
179
+
180
+ ```tsx
181
+ import styles from './styles.module.css'
182
+ export default island(() => <div class={styles.box}>styled</div>)
183
+ ```
184
+
185
+ Inline `<style dangerouslySetInnerHTML={{ __html: css }} />` works too (as in a layout that injects a design-system stylesheet once).
186
+
187
+ ## Advanced
188
+
189
+ - **`renderDocument(options)`** produces the default HTML shell (`bodyHtml`, `title`, `meta`, `styles`, `links`, `scripts`, `headHtml`, `bodyEndHtml`, `lang`). The runner calls it for you; use it directly only for a custom document.
190
+ - **Custom renderer:** implement the `UiRenderer` interface and register it with `UiRendererRegistry.setDefault(myRenderer)` instead of importing the Preact entry. The default id is `"preact"`.
191
+
192
+ ## Hard rules
193
+
194
+ - Import everything from `@wabot-dev/framework/ui` (server decorators + client helpers + Preact/signals re-exports). Never import from `preact` / `@preact/signals` directly, and never from framework internal paths.
195
+ - Islands live in `*.island.tsx` files and are the file's default export via `island()`. A component used interactively but not wrapped this way will render but never hydrate.
196
+ - View/action handlers take at most one parameter, and it is a validated class — not raw `req`/`res`.
197
+ - `@preact/signals`' `action` is re-exported as **`signalAction`** to avoid clashing with the `@action` controller decorator.
198
+
199
+ ## Testing
200
+
201
+ `createUiHarness({ controllers, register? })` from `@wabot-dev/framework/testing` mounts `@uiController` classes on a private ephemeral-port server and exercises the real pipeline (middlewares/guards, validation, SSR, actions). `harness.get(path)` returns the rendered HTML document; `harness.action(path, body?)` POSTs to an action route and `res.json()` parses the reply. Islands render as static SSR HTML — no client bundling needed. See the `wabot-testing` skill.
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: wabot-validation
3
+ description: Use when defining request DTOs, mindset module argument classes, or any class that must be validated and transformed by Wabot — including REST/socket inputs and tool function parameters. Covers the validator decorators (@isString, @isNumber, @isBoolean, @isDate, @isIn, @isNotEmpty, @isPresent, @isRecord, @isOptional, @isModel, @isArray, @min, @max), @description, validateAndTransform, and Mapper.
4
+ ---
5
+
6
+ # Validation, description, mapping
7
+
8
+ Wabot validates request shapes through decorator-driven metadata. The same metadata feeds:
9
+
10
+ - REST endpoint argument binding (`@onPost`, etc.).
11
+ - Socket event payloads (`@onSocketEvent`).
12
+ - Mindset tool function parameters (the LLM sees the validator type and the `@description`).
13
+ - `Async.runCommand` / `Async.scheduleCommand` payloads (validated against the `@command` class).
14
+
15
+ ## Field validators
16
+
17
+ All decorators are exported from `@wabot-dev/framework`. Apply them to public class fields.
18
+
19
+ | Decorator | Effect |
20
+ | --- | --- |
21
+ | `@isString()` | Value must be `string` |
22
+ | `@isNumber()` | Value must be `number` |
23
+ | `@isBoolean()` | Value must be `boolean` |
24
+ | `@isDate()` | Value must parse as `Date` (ISO 8601 input) |
25
+ | `@isIn([...values])` | Value must be one of the listed literals |
26
+ | `@isNotEmpty()` | String must be non-empty after trim |
27
+ | `@isPresent()` | Field must be present (not `undefined`) |
28
+ | `@isRecord(keyType, valueType)` | Plain object with typed keys/values; `keyType: 'number' \| 'string'`, `valueType: 'number' \| 'string' \| 'boolean'` |
29
+ | `@isOptional()` | Skip remaining validators when the value is null/undefined |
30
+ | `@isModel(OtherClass)` | Nested model validated against `OtherClass` |
31
+ | `@isArray({ minLength?, maxLength? })` | Field must be an array |
32
+ | `@min(n)` | Numeric / length minimum |
33
+ | `@max(n)` | Numeric / length maximum |
34
+
35
+ Validators chain top-to-bottom. Put `@isOptional()` **above** the type validator if the field is optional.
36
+
37
+ ```typescript
38
+ import { description, isIn, isNotEmpty, isNumber, isOptional, isString, max, min } from '@wabot-dev/framework'
39
+
40
+ export class ListGamesRequest {
41
+ @isString()
42
+ @isIn(['backlog', 'playing', 'finished', 'abandoned'])
43
+ @description('Filter by status')
44
+ status: 'backlog' | 'playing' | 'finished' | 'abandoned' = 'backlog'
45
+
46
+ @isNumber()
47
+ @min(1)
48
+ @max(20)
49
+ @description('Maximum number of games to return')
50
+ limit: number = 5
51
+
52
+ @isOptional()
53
+ @isString()
54
+ cursor?: string
55
+ }
56
+ ```
57
+
58
+ The `default` value on the field becomes the value when the input is missing and the field is optional.
59
+
60
+ ## `@description`
61
+
62
+ `@description('...')` annotates a field or method. Two roles:
63
+
64
+ 1. On a request-class field: it becomes the LLM-facing parameter description for mindset tool functions.
65
+ 2. On a `@mindsetModule()` method: it becomes the tool's description (without it the function is not exposed as a tool).
66
+
67
+ Mindset module functions **must** have a single request-object parameter (or no parameter), and every property of that request class must have both a type validator (e.g. `@isString()`) and a `@description(...)`. The framework throws at boot otherwise.
68
+
69
+ ## `validateAndTransform`
70
+
71
+ ```typescript
72
+ import { validateAndTransform } from '@wabot-dev/framework'
73
+
74
+ const { value, error } = validateAndTransform(rawInput, ListGamesRequest)
75
+ if (error) {
76
+ // error.description + error.properties (per-field errors)
77
+ }
78
+ ```
79
+
80
+ Use this when you receive a payload outside a framework-managed boundary (e.g. a manually parsed file or queue message). Inside a REST/socket controller or a command handler the framework already validates the parameter for you.
81
+
82
+ ## `Mapper`
83
+
84
+ `Mapper` is an injectable utility that combines deep-copy + `validateAndTransform`. It is useful when you need to convert a database row, a remote response, or a Storable into a typed model and you want a hard failure on shape mismatch (it throws `CustomError` with `httpCode: 500`).
85
+
86
+ ```typescript
87
+ import { container, Mapper } from '@wabot-dev/framework'
88
+
89
+ const mapper = container.resolve(Mapper)
90
+ const game: GameDto = mapper.map(rawRow, GameDto)
91
+ ```
92
+
93
+ `Mapper.map(data, ctor)` will:
94
+
95
+ - Deep-copy `data` (Storables → plain objects, Dates → epoch ms).
96
+ - Run all validators on the target class.
97
+ - Return a typed instance, or throw.
98
+
99
+ ## Rules
100
+
101
+ - Every public DTO/Request field needs at least one type validator. Without it, the framework can't introspect the type at runtime (TypeScript types are erased).
102
+ - Always pair `@description` with a type validator on mindset module request fields.
103
+ - Prefer field defaults over `@isOptional()` when you have a sensible default; reserve `@isOptional()` for fields that may genuinely be absent.
104
+ - Don't subclass `RestRequest` unless you want the raw Express `Request` injected into the endpoint — see `wabot-rest-socket`.
105
+
106
+ ## Testing
107
+
108
+ `assertValid(Model, data)`, `assertInvalid(Model, data, { path? })` and `validateFixture(Model, data)` from `@wabot-dev/framework/testing` test decorated models directly, with issues flattened to `{ path: 'items[0].name', message }`. See the `wabot-testing` skill.