@webiny/mcp 6.1.0 → 6.2.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/index.d.ts +2 -0
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/skills/admin/admin-permissions/SKILL.md +227 -73
- package/skills/admin/ui-extensions/SKILL.md +11 -42
- package/skills/api/api-architect/SKILL.md +2 -1
- package/skills/api/http-route/SKILL.md +176 -0
- package/skills/api/permissions/SKILL.md +141 -59
- package/skills/dependency-injection/SKILL.md +200 -2
- package/skills/generated/admin/SKILL.md +6 -16
- package/skills/generated/admin/cms/SKILL.md +39 -1
- package/skills/generated/admin/languages/SKILL.md +29 -0
- package/skills/generated/admin/security/SKILL.md +34 -1
- package/skills/generated/admin/ui/SKILL.md +21 -1
- package/skills/generated/admin/website-builder/SKILL.md +16 -1
- package/skills/generated/api/SKILL.md +58 -1
- package/skills/generated/api/cms/SKILL.md +111 -1
- package/skills/generated/api/db/SKILL.md +34 -0
- package/skills/generated/api/languages/SKILL.md +31 -0
- package/skills/generated/api/security/SKILL.md +26 -1
- package/skills/local-development/SKILL.md +1 -1
- package/skills/webiny-sdk/SKILL.md +174 -60
package/index.d.ts
CHANGED
|
@@ -4,3 +4,5 @@ export { startMcpServer } from "./cli/McpServer.js";
|
|
|
4
4
|
export type { IMcpServerParams } from "./cli/McpServer.js";
|
|
5
5
|
export { configureMcp } from "./cli/ConfigureMcp.js";
|
|
6
6
|
export type { IConfigureMcpParams } from "./cli/ConfigureMcp.js";
|
|
7
|
+
export { discoverAgents, discoverPresets } from "./agents/discover.js";
|
|
8
|
+
export type { AgentPreset, AgentModule } from "./agents/types.js";
|
package/index.js
CHANGED
|
@@ -5,5 +5,7 @@ export { ConsoleUi } from "./ui.js";
|
|
|
5
5
|
// Core functions
|
|
6
6
|
export { startMcpServer } from "./cli/McpServer.js";
|
|
7
7
|
export { configureMcp } from "./cli/ConfigureMcp.js";
|
|
8
|
+
// Agent discovery
|
|
9
|
+
export { discoverAgents, discoverPresets } from "./agents/discover.js";
|
|
8
10
|
|
|
9
11
|
//# sourceMappingURL=index.js.map
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["ConsoleUi","startMcpServer","configureMcp"],"sources":["index.ts"],"sourcesContent":["// Ui interface and console implementation\nexport type { IUi } from \"./ui.js\";\nexport { ConsoleUi } from \"./ui.js\";\n\n// Core functions\nexport { startMcpServer } from \"./cli/McpServer.js\";\nexport type { IMcpServerParams } from \"./cli/McpServer.js\";\nexport { configureMcp } from \"./cli/ConfigureMcp.js\";\nexport type { IConfigureMcpParams } from \"./cli/ConfigureMcp.js\";\n"],"mappings":"AAAA;;AAEA,SAASA,SAAS;;AAElB;AACA,SAASC,cAAc;AAEvB,SAASC,YAAY","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["ConsoleUi","startMcpServer","configureMcp","discoverAgents","discoverPresets"],"sources":["index.ts"],"sourcesContent":["// Ui interface and console implementation\nexport type { IUi } from \"./ui.js\";\nexport { ConsoleUi } from \"./ui.js\";\n\n// Core functions\nexport { startMcpServer } from \"./cli/McpServer.js\";\nexport type { IMcpServerParams } from \"./cli/McpServer.js\";\nexport { configureMcp } from \"./cli/ConfigureMcp.js\";\nexport type { IConfigureMcpParams } from \"./cli/ConfigureMcp.js\";\n\n// Agent discovery\nexport { discoverAgents, discoverPresets } from \"./agents/discover.js\";\nexport type { AgentPreset, AgentModule } from \"./agents/types.js\";\n"],"mappings":"AAAA;;AAEA,SAASA,SAAS;;AAElB;AACA,SAASC,cAAc;AAEvB,SAASC,YAAY;AAGrB;AACA,SAASC,cAAc,EAAEC,eAAe","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/mcp",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"directory": "dist"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@modelcontextprotocol/sdk": "1.
|
|
21
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
22
22
|
"front-matter": "4.0.2",
|
|
23
23
|
"zod": "4.3.6"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/lodash": "4.17.24",
|
|
27
27
|
"@types/ncp": "2.0.8",
|
|
28
|
-
"@webiny/build-tools": "6.
|
|
28
|
+
"@webiny/build-tools": "6.2.0",
|
|
29
29
|
"execa": "5.1.1",
|
|
30
30
|
"tsx": "4.21.0",
|
|
31
31
|
"typescript": "5.9.3"
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"prepublishOnly": "bash ./prepublishOnly.sh"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "3d3148358b6febbc857371930871743bec3b3939"
|
|
37
37
|
}
|
|
@@ -2,82 +2,68 @@
|
|
|
2
2
|
name: webiny-admin-permissions
|
|
3
3
|
context: webiny-extensions
|
|
4
4
|
description: >
|
|
5
|
-
Admin-side permission UI registration
|
|
6
|
-
adding permission controls to the admin UI — schema-based
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Admin-side permission UI registration and DI-backed permission checking.
|
|
6
|
+
Use this skill when adding permission controls to the admin UI — schema-based
|
|
7
|
+
auto-generated forms, injectable permissions via createPermissionsAbstraction/
|
|
8
|
+
createPermissionsFeature, typed hooks (createUsePermissions), the HasPermission
|
|
9
|
+
component (createHasPermission), and the Security.Permissions component props.
|
|
10
|
+
Covers both simple apps and complex multi-entity permission schemas.
|
|
10
11
|
---
|
|
11
12
|
|
|
12
|
-
# Admin Permissions
|
|
13
|
+
# Admin Permissions
|
|
13
14
|
|
|
14
15
|
## Overview
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Permissions follow three layers: **domain** (schema), **features** (DI artifacts + registration), and **presentation** (hooks, components, UI config). The framework auto-generates the permission UI from a schema and provides injectable permission checking via the DI container.
|
|
17
18
|
|
|
18
|
-
##
|
|
19
|
+
## Layer 1: Domain — Permission Schema
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
import React from "react";
|
|
22
|
-
import { AdminConfig } from "@webiny/app-admin";
|
|
23
|
-
import { ReactComponent as Icon } from "@webiny/icons/shield.svg";
|
|
21
|
+
Define the schema in `src/domain/permissionsSchema.ts`:
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
```ts
|
|
24
|
+
import { createPermissionSchema } from "webiny/admin/security";
|
|
26
25
|
|
|
27
|
-
export const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
id: "settings",
|
|
60
|
-
title: "Settings",
|
|
61
|
-
permission: "sm.settings",
|
|
62
|
-
scopes: ["full"]
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
}}
|
|
66
|
-
/>
|
|
67
|
-
</AdminConfig>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
26
|
+
export const SM_PERMISSIONS_SCHEMA = createPermissionSchema({
|
|
27
|
+
prefix: "sm",
|
|
28
|
+
fullAccess: true,
|
|
29
|
+
entities: [
|
|
30
|
+
{
|
|
31
|
+
id: "product",
|
|
32
|
+
title: "Products",
|
|
33
|
+
permission: "sm.product",
|
|
34
|
+
scopes: ["full", "own"],
|
|
35
|
+
actions: [
|
|
36
|
+
{ name: "rwd" },
|
|
37
|
+
{ name: "pw" },
|
|
38
|
+
{ name: "import", label: "Import products" },
|
|
39
|
+
{ name: "export", label: "Export products" }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "category",
|
|
44
|
+
title: "Categories",
|
|
45
|
+
permission: "sm.category",
|
|
46
|
+
scopes: ["full"],
|
|
47
|
+
actions: [{ name: "rwd" }]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "settings",
|
|
51
|
+
title: "Settings",
|
|
52
|
+
permission: "sm.settings",
|
|
53
|
+
scopes: ["full"]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
});
|
|
70
57
|
```
|
|
71
58
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## Schema Reference
|
|
59
|
+
### Schema Reference
|
|
75
60
|
|
|
76
|
-
| Field
|
|
77
|
-
|
|
|
78
|
-
| `prefix`
|
|
79
|
-
| `fullAccess`
|
|
80
|
-
| `
|
|
61
|
+
| Field | Type | Required | Description |
|
|
62
|
+
| ---------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
63
|
+
| `prefix` | `string` | Yes | Permission prefix (e.g., `"sm"`) |
|
|
64
|
+
| `fullAccess` | `true \| object` | Yes | `true` for standard full access. Pass an object with custom boolean flags for full-access extras (e.g., `{ canForceUnlock: true }`). |
|
|
65
|
+
| `readOnlyAccess` | `boolean` | No | Whether to show a "Read-only access" option |
|
|
66
|
+
| `entities` | `EntityDefinition[]` | No | Entity definitions. Omit for binary full/no access. |
|
|
81
67
|
|
|
82
68
|
### Entity Definition
|
|
83
69
|
|
|
@@ -110,19 +96,170 @@ Child entities can depend on a parent. If the parent lacks the required action,
|
|
|
110
96
|
}
|
|
111
97
|
```
|
|
112
98
|
|
|
99
|
+
### Simple Apps (No Entities)
|
|
100
|
+
|
|
101
|
+
Omit `entities` for binary full/no access:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
export const MA_PERMISSIONS_SCHEMA = createPermissionSchema({
|
|
105
|
+
prefix: "ma",
|
|
106
|
+
fullAccess: true
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
113
110
|
---
|
|
114
111
|
|
|
115
|
-
##
|
|
112
|
+
## Layer 2: Features — DI Artifacts + Registration
|
|
116
113
|
|
|
117
|
-
|
|
114
|
+
### Abstraction (`src/features/permissions/abstractions.ts`)
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { createPermissionsAbstraction } from "webiny/admin/security";
|
|
118
|
+
import type { Permissions } from "webiny/admin/security";
|
|
119
|
+
import { SM_PERMISSIONS_SCHEMA } from "~/domain/permissionsSchema.js";
|
|
120
|
+
|
|
121
|
+
export const SmPermissions = createPermissionsAbstraction(SM_PERMISSIONS_SCHEMA);
|
|
122
|
+
|
|
123
|
+
export namespace SmPermissions {
|
|
124
|
+
export type Interface = Permissions<typeof SM_PERMISSIONS_SCHEMA>;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Feature (`src/features/permissions/feature.ts`)
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { createPermissionsFeature } from "webiny/admin/security";
|
|
132
|
+
import { SM_PERMISSIONS_SCHEMA } from "~/domain/permissionsSchema.js";
|
|
133
|
+
import { SmPermissions } from "./abstractions.js";
|
|
134
|
+
|
|
135
|
+
export const SmPermissionsFeature = createPermissionsFeature(SM_PERMISSIONS_SCHEMA, SmPermissions);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Extension Registration
|
|
139
|
+
|
|
140
|
+
Register the feature and the permission UI in your extension component:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { AdminConfig, RegisterFeature } from "webiny/admin/security";
|
|
144
|
+
import { ReactComponent as Icon } from "@webiny/icons/shield.svg";
|
|
145
|
+
import { SM_PERMISSIONS_SCHEMA } from "~/domain/permissionsSchema.js";
|
|
146
|
+
import { SmPermissionsFeature } from "~/features/permissions/feature.js";
|
|
147
|
+
|
|
148
|
+
const { Security } = AdminConfig;
|
|
149
|
+
|
|
150
|
+
export const Extension = () => {
|
|
151
|
+
return (
|
|
152
|
+
<>
|
|
153
|
+
<RegisterFeature feature={SmPermissionsFeature} />
|
|
154
|
+
<AdminConfig>
|
|
155
|
+
<Security.Permissions
|
|
156
|
+
name="store-manager"
|
|
157
|
+
title="Store Manager"
|
|
158
|
+
description="Manage Store Manager permissions."
|
|
159
|
+
icon={<Icon />}
|
|
160
|
+
schema={SM_PERMISSIONS_SCHEMA}
|
|
161
|
+
/>
|
|
162
|
+
{/* Routes, menus, etc. */}
|
|
163
|
+
</AdminConfig>
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Layer 3: Presentation — Hooks & Components
|
|
172
|
+
|
|
173
|
+
### `usePermissions` Hook
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// src/presentation/security/usePermissions.ts
|
|
177
|
+
import { createUsePermissions } from "webiny/admin/security";
|
|
178
|
+
import { SmPermissions } from "~/features/permissions/abstractions.js";
|
|
179
|
+
|
|
180
|
+
export const usePermissions = createUsePermissions(SmPermissions);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Usage:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const permissions = usePermissions();
|
|
187
|
+
|
|
188
|
+
permissions.canAccess("product"); // has any access
|
|
189
|
+
permissions.canRead("product"); // rwd includes "r"
|
|
190
|
+
permissions.canCreate("product"); // rwd includes "w"
|
|
191
|
+
permissions.canEdit("product", item); // rwd includes "w", respects own scope
|
|
192
|
+
permissions.canDelete("product"); // rwd includes "d"
|
|
193
|
+
permissions.canPublish("product"); // pw includes "p"
|
|
194
|
+
permissions.canUnpublish("product"); // pw includes "u"
|
|
195
|
+
permissions.canAction("import", "product"); // custom boolean flag
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Entity IDs are fully typed — `canRead("bogus")` produces a type error.
|
|
199
|
+
|
|
200
|
+
### `HasPermission` Component
|
|
118
201
|
|
|
119
202
|
```tsx
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
203
|
+
// src/presentation/security/HasPermission.tsx
|
|
204
|
+
import { createHasPermission } from "webiny/admin/security";
|
|
205
|
+
import { SmPermissions } from "~/features/permissions/abstractions.js";
|
|
206
|
+
import { SM_PERMISSIONS_SCHEMA } from "~/domain/permissionsSchema.js";
|
|
207
|
+
|
|
208
|
+
export const HasPermission = createHasPermission(SmPermissions, SM_PERMISSIONS_SCHEMA);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Usage in JSX:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
<HasPermission entity="product">
|
|
215
|
+
{/* Rendered if user can access "product" */}
|
|
216
|
+
</HasPermission>
|
|
217
|
+
|
|
218
|
+
<HasPermission any={["product", "category"]}>
|
|
219
|
+
{/* Rendered if user can access ANY of these entities */}
|
|
220
|
+
</HasPermission>
|
|
221
|
+
|
|
222
|
+
<HasPermission all={["product", "category"]}>
|
|
223
|
+
{/* Rendered if user can access ALL of these entities */}
|
|
224
|
+
</HasPermission>
|
|
225
|
+
|
|
226
|
+
<HasPermission entity="product" action="read">
|
|
227
|
+
{/* Rendered if user canRead("product") */}
|
|
228
|
+
</HasPermission>
|
|
229
|
+
|
|
230
|
+
<HasPermission entity="product" allActions={["read", "publish"]}>
|
|
231
|
+
{/* Rendered if user canRead AND canPublish "product" */}
|
|
232
|
+
</HasPermission>
|
|
233
|
+
|
|
234
|
+
<HasPermission entity="product" someActions={["import", "export"]}>
|
|
235
|
+
{/* Rendered if user can do ANY of these custom actions */}
|
|
236
|
+
</HasPermission>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## DI Injection in Features
|
|
242
|
+
|
|
243
|
+
Inject permissions into feature implementations:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import type { SmPermissions } from "~/features/permissions/abstractions.js";
|
|
247
|
+
import { SmPermissions as SmPermissionsAbstraction } from "~/features/permissions/abstractions.js";
|
|
248
|
+
|
|
249
|
+
class SomeFeatureImpl {
|
|
250
|
+
constructor(private permissions: SmPermissions.Interface) {}
|
|
251
|
+
|
|
252
|
+
doSomething() {
|
|
253
|
+
if (this.permissions.canEdit("product")) {
|
|
254
|
+
// ...
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const SomeFeature = SomeAbstraction.createImplementation({
|
|
260
|
+
implementation: SomeFeatureImpl,
|
|
261
|
+
dependencies: [SmPermissionsAbstraction]
|
|
262
|
+
});
|
|
126
263
|
```
|
|
127
264
|
|
|
128
265
|
---
|
|
@@ -141,12 +278,29 @@ Omit `entities` for binary full/no access:
|
|
|
141
278
|
|
|
142
279
|
---
|
|
143
280
|
|
|
281
|
+
## File Structure
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
src/
|
|
285
|
+
├── domain/
|
|
286
|
+
│ └── permissionsSchema.ts # createPermissionSchema()
|
|
287
|
+
├── features/
|
|
288
|
+
│ └── permissions/
|
|
289
|
+
│ ├── abstractions.ts # createPermissionsAbstraction() + namespace type
|
|
290
|
+
│ └── feature.ts # createPermissionsFeature()
|
|
291
|
+
├── presentation/
|
|
292
|
+
│ └── security/
|
|
293
|
+
│ ├── usePermissions.ts # createUsePermissions()
|
|
294
|
+
│ └── HasPermission.tsx # createHasPermission()
|
|
295
|
+
└── Extension.tsx # RegisterFeature + Security.Permissions
|
|
296
|
+
```
|
|
297
|
+
|
|
144
298
|
## Matching API-Side Permissions
|
|
145
299
|
|
|
146
300
|
The admin schema and the API-side `createPermissions` schema should use the **same prefix, entity IDs, and action names**. This ensures the permissions emitted by the admin UI are correctly evaluated by the API.
|
|
147
301
|
|
|
148
302
|
```
|
|
149
|
-
Admin:
|
|
303
|
+
Admin: createPermissionSchema({ prefix: "sm", entities: [{ id: "product", permission: "sm.product", ... }] })
|
|
150
304
|
API: createPermissions({ prefix: "sm", entities: [{ id: "product", permission: "sm.product", ... }] })
|
|
151
305
|
```
|
|
152
306
|
|
|
@@ -176,56 +176,25 @@ Create custom forms for Website Builder page types using Webiny's form component
|
|
|
176
176
|
```tsx
|
|
177
177
|
// extensions/customPageTypes/RetailPageForm.tsx
|
|
178
178
|
import React from "react";
|
|
179
|
-
import {
|
|
180
|
-
import {
|
|
181
|
-
import type { FormApi } from "webiny/admin/form";
|
|
182
|
-
import { Bind, UnsetOnUnmount, useForm, validation } from "webiny/admin/form";
|
|
179
|
+
import { PageListConfig } from "webiny/admin/website-builder/page/list";
|
|
180
|
+
import { Bind, UnsetOnUnmount, validation } from "webiny/admin/form";
|
|
183
181
|
|
|
184
|
-
const
|
|
185
|
-
const title = form.getValue("properties.title");
|
|
186
|
-
const language = form.getValue("extensions.language");
|
|
187
|
-
|
|
188
|
-
const titlePath = pagePathFromTitle(title ?? "");
|
|
189
|
-
const parts = [language, titlePath].filter(Boolean);
|
|
190
|
-
|
|
191
|
-
form.setValue("properties.path", `/${parts.join("/")}`);
|
|
192
|
-
};
|
|
182
|
+
const { PageType } = PageListConfig;
|
|
193
183
|
|
|
194
184
|
export const RetailPageForm = () => {
|
|
195
185
|
const form = useForm();
|
|
196
186
|
|
|
197
187
|
return (
|
|
198
188
|
<>
|
|
189
|
+
{/* Mount the default page form fields. */}
|
|
190
|
+
<PageType.Language />
|
|
191
|
+
<PageType.Title />
|
|
192
|
+
<PageType.Path />
|
|
193
|
+
{/* Add custom fields.*/}
|
|
199
194
|
<Grid.Column span={12}>
|
|
200
|
-
<UnsetOnUnmount name={"
|
|
201
|
-
<Bind name={"
|
|
202
|
-
<Input label={"
|
|
203
|
-
</Bind>
|
|
204
|
-
</UnsetOnUnmount>
|
|
205
|
-
</Grid.Column>
|
|
206
|
-
<Grid.Column span={12}>
|
|
207
|
-
<UnsetOnUnmount name={"extensions.language"}>
|
|
208
|
-
<Bind
|
|
209
|
-
name={"extensions.language"}
|
|
210
|
-
validators={[validation.create("required")]}
|
|
211
|
-
afterChange={generatePath(form)}
|
|
212
|
-
>
|
|
213
|
-
<Select
|
|
214
|
-
placeholder={"Select a language"}
|
|
215
|
-
label={"Language"}
|
|
216
|
-
options={[
|
|
217
|
-
{ label: "English", value: "en" },
|
|
218
|
-
{ label: "German", value: "de" },
|
|
219
|
-
{ label: "French", value: "fr" }
|
|
220
|
-
]}
|
|
221
|
-
/>
|
|
222
|
-
</Bind>
|
|
223
|
-
</UnsetOnUnmount>
|
|
224
|
-
</Grid.Column>
|
|
225
|
-
<Grid.Column span={12}>
|
|
226
|
-
<UnsetOnUnmount name={"properties.path"}>
|
|
227
|
-
<Bind name={"properties.path"} validators={[validation.create("required")]}>
|
|
228
|
-
<Input label={"Path"} />
|
|
195
|
+
<UnsetOnUnmount name={"extensions.customField"}>
|
|
196
|
+
<Bind name={"extensions.customField"} validators={[validation.create("required")]}>
|
|
197
|
+
<Input label={"Custom Field"} />
|
|
229
198
|
</Bind>
|
|
230
199
|
</UnsetOnUnmount>
|
|
231
200
|
</Grid.Column>
|
|
@@ -426,7 +426,7 @@ export const CreateEntityFeature = createFeature({
|
|
|
426
426
|
A deployed API must **NEVER** use `process.env` to read configuration. All configuration flows through `BuildParams` via DI:
|
|
427
427
|
|
|
428
428
|
```ts
|
|
429
|
-
import { BuildParams } from "webiny/api
|
|
429
|
+
import { BuildParams } from "webiny/api";
|
|
430
430
|
|
|
431
431
|
class MyServiceImpl implements MyService.Interface {
|
|
432
432
|
constructor(private buildParams: BuildParams.Interface) {}
|
|
@@ -672,6 +672,7 @@ Creates a feature definition that the framework loads as an extension.
|
|
|
672
672
|
- **webiny-api-permissions** — Schema-based permissions, CRUD authorization patterns, own-record scoping, testing
|
|
673
673
|
- **webiny-event-handler-pattern** — EventHandler lifecycle, domain event definition and publishing, handler abstractions
|
|
674
674
|
- **webiny-custom-graphql-api** — GraphQL schema creation, dynamic inputs, namespaced mutations
|
|
675
|
+
- **webiny-http-route** — Custom HTTP endpoints via `Api.Route` and `Route.Interface`
|
|
675
676
|
- **webiny-v5-to-v6-migration** — Side-by-side migration patterns for AI agents
|
|
676
677
|
- **webiny-full-stack-architect** — Top-level component, shared domain layer, BuildParam declarations
|
|
677
678
|
- **webiny-dependency-injection** — The `createImplementation` DI pattern and injectable services
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webiny-http-route
|
|
3
|
+
context: webiny-extensions
|
|
4
|
+
description: >
|
|
5
|
+
Adding custom HTTP routes to the API using Api.Route and Route.Interface.
|
|
6
|
+
Use this skill when the developer wants to expose a custom HTTP endpoint (GET, POST, PUT, etc.)
|
|
7
|
+
on the API Gateway alongside the GraphQL handler, implement Route.Interface with full DI support,
|
|
8
|
+
or register a custom HTTP handler in webiny.config.tsx.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Custom HTTP Routes
|
|
12
|
+
|
|
13
|
+
## TL;DR
|
|
14
|
+
|
|
15
|
+
Add a custom HTTP route by implementing `Route.Interface` and registering it with `<Api.Route>` in `webiny.config.tsx`. The `path` and `method` props configure API Gateway and Fastify route registration. Your handler receives a framework-agnostic `Route.Request` and `Route.Reply` and supports full DI (Logger, BuildParams, UseCases, etc.).
|
|
16
|
+
|
|
17
|
+
**YOU MUST include the full file path with the `.ts` extension in the `src` prop.** For example, use `src={"/extensions/MyRoute.ts"}`, NOT `src={"/extensions/MyRoute"}`. Omitting the file extension will cause a build failure.
|
|
18
|
+
|
|
19
|
+
**YOU MUST use `export default` for the `createImplementation()` call** when the file is targeted directly by a `src` prop. Named exports cause build failures here.
|
|
20
|
+
|
|
21
|
+
## The Route Pattern
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// extensions/MyRoute.ts
|
|
25
|
+
import { Route } from "webiny/api/route";
|
|
26
|
+
import { Logger } from "webiny/api/logger";
|
|
27
|
+
import { BuildParams } from "webiny/api/build-params";
|
|
28
|
+
|
|
29
|
+
class MyRouteImpl implements Route.Interface {
|
|
30
|
+
constructor(
|
|
31
|
+
private logger: Logger.Interface,
|
|
32
|
+
private buildParams: BuildParams.Interface
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
async execute(request: Route.Request, reply: Route.Reply): Promise<void> {
|
|
36
|
+
this.logger.info("Handling request", { url: request.url });
|
|
37
|
+
|
|
38
|
+
const config = this.buildParams.get<string>("MY_CONFIG");
|
|
39
|
+
|
|
40
|
+
reply.code(200).send({ status: "ok", config });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default Route.createImplementation({
|
|
45
|
+
implementation: MyRouteImpl,
|
|
46
|
+
dependencies: [Logger, BuildParams]
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Register in `webiny.config.tsx`:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Api.Route method={"POST"} path={"/my-route"} src={"/extensions/MyRoute.ts"} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Props Reference
|
|
57
|
+
|
|
58
|
+
| Prop | Type | Required | Description |
|
|
59
|
+
| ----------- | -------- | -------- | --------------------------------------------------------------------------- |
|
|
60
|
+
| `path` | `string` | Yes | Route path — must start with `/` |
|
|
61
|
+
| `method` | `string` | Yes | HTTP method (see list below) |
|
|
62
|
+
| `src` | `string` | Yes | Path to the handler file (must include `.ts` extension) |
|
|
63
|
+
| `routeName` | `string` | No | Pulumi resource name (kebab-case). Auto-derived from path+method if omitted |
|
|
64
|
+
|
|
65
|
+
## Supported HTTP Methods
|
|
66
|
+
|
|
67
|
+
`DELETE`, `GET`, `HEAD`, `PATCH`, `POST`, `PUT`, `OPTIONS`, `ANY`
|
|
68
|
+
|
|
69
|
+
Use `ANY` to match all HTTP methods on a given path.
|
|
70
|
+
|
|
71
|
+
## Route.Request / Route.Reply Interfaces
|
|
72
|
+
|
|
73
|
+
These are framework-agnostic — no Fastify types leak into user code.
|
|
74
|
+
|
|
75
|
+
### `Route.Request`
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
interface Route.Request {
|
|
79
|
+
body: unknown;
|
|
80
|
+
headers: Record<string, string | string[] | undefined>;
|
|
81
|
+
method: string;
|
|
82
|
+
url: string;
|
|
83
|
+
params: unknown; // path parameters, e.g. { id: "abc" }
|
|
84
|
+
query: unknown; // query string parameters
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `Route.Reply`
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
interface Route.Reply {
|
|
92
|
+
code(statusCode: number): this; // set HTTP status code
|
|
93
|
+
send(data?: unknown): void; // send response body
|
|
94
|
+
header(key: string, value: unknown): this;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Chaining example:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
reply.code(201).header("X-Custom", "value").send({ created: true });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## How It Works
|
|
105
|
+
|
|
106
|
+
The `<Api.Route>` extension does two things at build/deploy time:
|
|
107
|
+
|
|
108
|
+
1. **Build time** — injects two entries into `apps/api/graphql/src/extensions.ts`:
|
|
109
|
+
|
|
110
|
+
- A `createContextPlugin` that registers your handler in the DI container
|
|
111
|
+
- A `createRoute` that registers the Fastify route with the hardcoded `path` and `method`
|
|
112
|
+
|
|
113
|
+
2. **Deploy time (Pulumi)** — calls `graphql.addRoute({ name, path, method })` on the `ApiGraphql` module to create the API Gateway route
|
|
114
|
+
|
|
115
|
+
At request time, the handler is resolved from the DI container — so all `dependencies` (Logger, BuildParams, UseCases, etc.) are fully injected.
|
|
116
|
+
|
|
117
|
+
## Example with Path Parameters
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// extensions/GetOrderRoute.ts
|
|
121
|
+
import { Route } from "webiny/api/route";
|
|
122
|
+
import { Logger } from "webiny/api/logger";
|
|
123
|
+
|
|
124
|
+
interface OrderParams {
|
|
125
|
+
orderId: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class GetOrderRouteImpl implements Route.Interface {
|
|
129
|
+
constructor(private logger: Logger.Interface) {}
|
|
130
|
+
|
|
131
|
+
async execute(request: Route.Request, reply: Route.Reply): Promise<void> {
|
|
132
|
+
const { orderId } = request.params as OrderParams;
|
|
133
|
+
this.logger.info("Fetching order", { orderId });
|
|
134
|
+
|
|
135
|
+
// ... fetch and return order
|
|
136
|
+
reply.code(200).send({ orderId, status: "fulfilled" });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export default Route.createImplementation({
|
|
141
|
+
implementation: GetOrderRouteImpl,
|
|
142
|
+
dependencies: [Logger]
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<Api.Route method={"GET"} path={"/orders/{orderId}"} src={"/extensions/GetOrderRoute.ts"} />
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Key Rules
|
|
151
|
+
|
|
152
|
+
- **`path` and `method` are hardcoded at build time** — the Fastify route is registered before any request arrives. Your handler does not need to specify them.
|
|
153
|
+
- **DI is fully available in `execute()`** — the handler instance is resolved from the container per request, so all dependencies are injected normally.
|
|
154
|
+
- **One handler per file** — each `src` file exports one `Route.createImplementation` result.
|
|
155
|
+
- **Constructor param order must match the `dependencies` array** exactly.
|
|
156
|
+
- **Use `.js` extensions** in all relative imports inside the handler file (ESM).
|
|
157
|
+
- **Do not read `process.env`** at runtime — use `BuildParams` for configuration instead.
|
|
158
|
+
|
|
159
|
+
## Quick Reference
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Import (handler): import { Route } from "webiny/api/route";
|
|
163
|
+
Interface: Route.Interface
|
|
164
|
+
Request type: Route.Request
|
|
165
|
+
Reply type: Route.Reply
|
|
166
|
+
Export: Route.createImplementation({ implementation, dependencies })
|
|
167
|
+
Register: <Api.Route method={"POST"} path={"/my-route"} src={"/extensions/MyRoute.ts"} />
|
|
168
|
+
Deploy: yarn webiny deploy api --env=dev
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Related Skills
|
|
172
|
+
|
|
173
|
+
- **webiny-api-architect** — DI patterns, Services, UseCases, feature organization
|
|
174
|
+
- **webiny-custom-graphql-api** — Custom GraphQL endpoints (alternative to HTTP)
|
|
175
|
+
- **webiny-dependency-injection** — Injectable services catalog (Logger, BuildParams, etc.)
|
|
176
|
+
- **webiny-infrastructure-extensions** — Pulumi-level infrastructure customization
|