@forgeportal/plugin-sdk 1.0.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/.turbo/turbo-build.log +4 -0
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/__tests__/registry.test.ts +195 -0
- package/__tests__/types.test-d.ts +60 -0
- package/dist/backend.d.ts +56 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +37 -0
- package/dist/backend.js.map +1 -0
- package/dist/context.d.ts +24 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +25 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +32 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +65 -0
- package/dist/react.js.map +1 -0
- package/dist/registry.d.ts +31 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +80 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +212 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/backend.ts +84 -0
- package/src/context.tsx +63 -0
- package/src/index.ts +34 -0
- package/src/react.ts +80 -0
- package/src/registry.ts +103 -0
- package/src/types.ts +221 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +9 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bendaamerahmed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# @forgeportal/plugin-sdk
|
|
2
|
+
|
|
3
|
+
The official SDK for building ForgePortal plugins.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @forgeportal/plugin-sdk
|
|
9
|
+
# For UI plugins also install peer deps:
|
|
10
|
+
pnpm add react @tanstack/react-query
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Plugin Types
|
|
14
|
+
|
|
15
|
+
| Type | What it provides |
|
|
16
|
+
|-------------|-----------------------------------------------------|
|
|
17
|
+
| `ui` | Entity tabs, entity cards, top-level routes |
|
|
18
|
+
| `backend` | Fastify routes, action providers, catalog providers |
|
|
19
|
+
| `fullstack` | Both UI and backend capabilities |
|
|
20
|
+
|
|
21
|
+
## Quick Start — UI Plugin
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// src/index.ts
|
|
25
|
+
import type { ForgePluginSDK } from '@forgeportal/plugin-sdk';
|
|
26
|
+
import { MyEntityTab } from './MyEntityTab.js';
|
|
27
|
+
|
|
28
|
+
export function registerPlugin(sdk: ForgePluginSDK) {
|
|
29
|
+
sdk.registerEntityTab({
|
|
30
|
+
id: 'my-plugin-tab',
|
|
31
|
+
title: 'My Plugin',
|
|
32
|
+
component: MyEntityTab,
|
|
33
|
+
appliesTo: { kinds: ['service'] },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// src/MyEntityTab.tsx
|
|
40
|
+
import { useEntity } from '@forgeportal/plugin-sdk/react';
|
|
41
|
+
|
|
42
|
+
export function MyEntityTab() {
|
|
43
|
+
const { entity } = useEntity();
|
|
44
|
+
return <div>Entity: {entity.name}</div>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start — Backend Plugin (Action Provider)
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// src/actions/myAction.ts
|
|
52
|
+
import type { ActionProvider } from '@forgeportal/plugin-sdk';
|
|
53
|
+
|
|
54
|
+
export const myAction: ActionProvider = {
|
|
55
|
+
id: 'myplugin.doSomething',
|
|
56
|
+
version: 'v1',
|
|
57
|
+
schema: {
|
|
58
|
+
input: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: { message: { type: 'string', title: 'Message' } },
|
|
61
|
+
required: ['message'],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
async handler(ctx, input) {
|
|
65
|
+
ctx.logger.info('Running action', { input });
|
|
66
|
+
await ctx.log('info', `Processing: ${input['message']}`);
|
|
67
|
+
return { status: 'success', outputs: { done: true } };
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Quick Start — Fullstack Plugin (Catalog Provider)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// src/index.ts
|
|
76
|
+
import type { ForgePluginSDK } from '@forgeportal/plugin-sdk';
|
|
77
|
+
import { myAction } from './actions/myAction.js';
|
|
78
|
+
import { MyCatalogTab } from './MyCatalogTab.js';
|
|
79
|
+
import { myCatalogProvider } from './catalog.js';
|
|
80
|
+
|
|
81
|
+
export function registerPlugin(sdk: ForgePluginSDK) {
|
|
82
|
+
// Backend: action provider
|
|
83
|
+
sdk.registerActionProvider(myAction);
|
|
84
|
+
// Backend: catalog ingest provider
|
|
85
|
+
sdk.registerCatalogProvider(myCatalogProvider);
|
|
86
|
+
// UI: custom entity tab
|
|
87
|
+
sdk.registerEntityTab({
|
|
88
|
+
id: 'my-catalog-tab',
|
|
89
|
+
title: 'My Catalog',
|
|
90
|
+
component: MyCatalogTab,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## React Hooks
|
|
96
|
+
|
|
97
|
+
| Hook | Description |
|
|
98
|
+
|-------------------|------------------------------------------------------------------------|
|
|
99
|
+
| `useEntity()` | Returns `{ entity }` — the current catalog entity. Use inside EntityTab/EntityCard. |
|
|
100
|
+
| `useConfig<T>(key)` | Returns plugin config value from `forgeportal.yaml`. |
|
|
101
|
+
| `useApi<T>(path)` | TanStack Query wrapper for ForgePortal API calls. |
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { useEntity, useConfig, useApi } from '@forgeportal/plugin-sdk/react';
|
|
105
|
+
|
|
106
|
+
function MyTab() {
|
|
107
|
+
const { entity } = useEntity();
|
|
108
|
+
const apiUrl = useConfig<string>('apiEndpoint');
|
|
109
|
+
const { data } = useApi<{ incidents: unknown[] }>(`/api/v1/my-plugin/${entity.id}/incidents`);
|
|
110
|
+
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Plugin Manifest (`forgeportal-plugin.json`)
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"name": "@myorg/forge-plugin-my-plugin",
|
|
119
|
+
"version": "1.0.0",
|
|
120
|
+
"forgeportal": {
|
|
121
|
+
"engineVersion": "^1.0.0",
|
|
122
|
+
"type": "ui",
|
|
123
|
+
"capabilities": {
|
|
124
|
+
"ui": {
|
|
125
|
+
"entityTabs": ["my-plugin-tab"]
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"config": {
|
|
129
|
+
"apiEndpoint": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "External service URL",
|
|
132
|
+
"required": true
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## ActionContext Services
|
|
140
|
+
|
|
141
|
+
| Property | Type | Description |
|
|
142
|
+
|--------------------|-------------------------|-----------------------------------------------------|
|
|
143
|
+
| `config` | `ActionConfigAccessor` | Plugin config from `forgeportal.yaml` |
|
|
144
|
+
| `logger` | `ActionLogger` | Structured logger (info/warn/error) |
|
|
145
|
+
| `scm` | `ActionScmAccessor` | SCM file reads (getFile, listFiles) |
|
|
146
|
+
| `db` | `ActionDbAccessor` | Read-only SQL query access |
|
|
147
|
+
| `acquireRepoLock` | `(repoUrl) => Promise` | Advisory lock to prevent concurrent SCM writes |
|
|
148
|
+
| `log` | `(level, msg) => Promise` | Persisted action run log line |
|
|
149
|
+
|
|
150
|
+
## Versioning
|
|
151
|
+
|
|
152
|
+
The SDK follows semantic versioning:
|
|
153
|
+
|
|
154
|
+
- **Patch** — bug fixes, no interface changes
|
|
155
|
+
- **Minor** — new capabilities (backward-compatible)
|
|
156
|
+
- **Major** — breaking contract changes
|
|
157
|
+
|
|
158
|
+
Plugins declare `engineVersion: "^1.0.0"` to stay compatible with any 1.x SDK release.
|
|
159
|
+
|
|
160
|
+
The current SDK version is exported as `SDK_VERSION`:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { SDK_VERSION } from '@forgeportal/plugin-sdk';
|
|
164
|
+
console.log(SDK_VERSION); // "1.0.0"
|
|
165
|
+
```
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { PluginRegistry } from '../src/registry.js';
|
|
3
|
+
import type { EntityTab, EntityCard, Route, ActionProvider, CatalogProvider } from '../src/types.js';
|
|
4
|
+
|
|
5
|
+
// ─── Test fixtures ─────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
function makeTab(id: string, kinds?: string[]): EntityTab {
|
|
8
|
+
return {
|
|
9
|
+
id,
|
|
10
|
+
title: `Tab ${id}`,
|
|
11
|
+
component: () => null,
|
|
12
|
+
appliesTo: kinds ? { kinds } : undefined,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeCard(id: string, kinds?: string[]): EntityCard {
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
title: `Card ${id}`,
|
|
20
|
+
component: () => null,
|
|
21
|
+
appliesTo: kinds ? { kinds } : undefined,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeRoute(path: string, navLabel?: string): Route {
|
|
26
|
+
return { path, component: () => null, navLabel };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeActionProvider(id: string, version = 'v1'): ActionProvider {
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
version,
|
|
33
|
+
schema: { input: { type: 'object', properties: {} } },
|
|
34
|
+
handler: vi.fn().mockResolvedValue({ status: 'success', outputs: {} }),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeCatalogProvider(id: string): CatalogProvider {
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
async *ingest() { /* yields nothing */ },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── EntityTab tests ────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
describe('PluginRegistry — EntityTab', () => {
|
|
48
|
+
it('registers a tab and retrieves it', () => {
|
|
49
|
+
const reg = new PluginRegistry();
|
|
50
|
+
reg.registerEntityTab(makeTab('tab-a'));
|
|
51
|
+
expect(reg.getEntityTabs()).toHaveLength(1);
|
|
52
|
+
expect(reg.getEntityTabs()[0]?.id).toBe('tab-a');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('skips duplicate registration (warns)', () => {
|
|
56
|
+
const reg = new PluginRegistry();
|
|
57
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
58
|
+
reg.registerEntityTab(makeTab('tab-dup'));
|
|
59
|
+
reg.registerEntityTab(makeTab('tab-dup'));
|
|
60
|
+
expect(reg.getEntityTabs()).toHaveLength(1);
|
|
61
|
+
expect(warnSpy).toHaveBeenCalledOnce();
|
|
62
|
+
warnSpy.mockRestore();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('filters tabs by entity kind — matching kind', () => {
|
|
66
|
+
const reg = new PluginRegistry();
|
|
67
|
+
reg.registerEntityTab(makeTab('tab-svc', ['service']));
|
|
68
|
+
reg.registerEntityTab(makeTab('tab-all')); // no kinds = all
|
|
69
|
+
const tabs = reg.getEntityTabs('service');
|
|
70
|
+
expect(tabs.map(t => t.id)).toEqual(['tab-svc', 'tab-all']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('filters tabs by entity kind — non-matching kind', () => {
|
|
74
|
+
const reg = new PluginRegistry();
|
|
75
|
+
reg.registerEntityTab(makeTab('tab-svc', ['service']));
|
|
76
|
+
reg.registerEntityTab(makeTab('tab-all'));
|
|
77
|
+
const tabs = reg.getEntityTabs('library');
|
|
78
|
+
expect(tabs.map(t => t.id)).toEqual(['tab-all']);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns all tabs when no kind filter provided', () => {
|
|
82
|
+
const reg = new PluginRegistry();
|
|
83
|
+
reg.registerEntityTab(makeTab('a', ['service']));
|
|
84
|
+
reg.registerEntityTab(makeTab('b', ['library']));
|
|
85
|
+
expect(reg.getEntityTabs()).toHaveLength(2);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ─── EntityCard tests ───────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
describe('PluginRegistry — EntityCard', () => {
|
|
92
|
+
it('registers and retrieves a card', () => {
|
|
93
|
+
const reg = new PluginRegistry();
|
|
94
|
+
reg.registerEntityCard(makeCard('card-a'));
|
|
95
|
+
expect(reg.getEntityCards()).toHaveLength(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('skips duplicate cards', () => {
|
|
99
|
+
const reg = new PluginRegistry();
|
|
100
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
101
|
+
reg.registerEntityCard(makeCard('card-dup'));
|
|
102
|
+
reg.registerEntityCard(makeCard('card-dup'));
|
|
103
|
+
expect(reg.getEntityCards()).toHaveLength(1);
|
|
104
|
+
warnSpy.mockRestore();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('filters cards by entity kind', () => {
|
|
108
|
+
const reg = new PluginRegistry();
|
|
109
|
+
reg.registerEntityCard(makeCard('c-svc', ['service']));
|
|
110
|
+
reg.registerEntityCard(makeCard('c-all'));
|
|
111
|
+
expect(reg.getEntityCards('library').map(c => c.id)).toEqual(['c-all']);
|
|
112
|
+
expect(reg.getEntityCards('service').map(c => c.id)).toEqual(['c-svc', 'c-all']);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ─── Route tests ────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
describe('PluginRegistry — Route', () => {
|
|
119
|
+
it('registers routes and retrieves them', () => {
|
|
120
|
+
const reg = new PluginRegistry();
|
|
121
|
+
reg.registerRoute(makeRoute('/pd', 'PagerDuty'));
|
|
122
|
+
reg.registerRoute(makeRoute('/slack'));
|
|
123
|
+
const routes = reg.getRoutes();
|
|
124
|
+
expect(routes).toHaveLength(2);
|
|
125
|
+
expect(routes[0]?.navLabel).toBe('PagerDuty');
|
|
126
|
+
expect(routes[1]?.navLabel).toBeUndefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('skips duplicate routes by path', () => {
|
|
130
|
+
const reg = new PluginRegistry();
|
|
131
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
132
|
+
reg.registerRoute(makeRoute('/pd'));
|
|
133
|
+
reg.registerRoute(makeRoute('/pd'));
|
|
134
|
+
expect(reg.getRoutes()).toHaveLength(1);
|
|
135
|
+
warnSpy.mockRestore();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ─── ActionProvider tests ────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
describe('PluginRegistry — ActionProvider', () => {
|
|
142
|
+
it('registers action providers by id@version key', () => {
|
|
143
|
+
const reg = new PluginRegistry();
|
|
144
|
+
reg.registerActionProvider(makeActionProvider('slack.send', 'v1'));
|
|
145
|
+
reg.registerActionProvider(makeActionProvider('slack.send', 'v2'));
|
|
146
|
+
expect(reg.getActionProviders()).toHaveLength(2);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('retrieves a specific action provider by id and version', () => {
|
|
150
|
+
const reg = new PluginRegistry();
|
|
151
|
+
const provider = makeActionProvider('pd.create', 'v1');
|
|
152
|
+
reg.registerActionProvider(provider);
|
|
153
|
+
expect(reg.getActionProvider('pd.create', 'v1')).toBe(provider);
|
|
154
|
+
expect(reg.getActionProvider('pd.create', 'v2')).toBeUndefined();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('skips duplicate id@version', () => {
|
|
158
|
+
const reg = new PluginRegistry();
|
|
159
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
160
|
+
reg.registerActionProvider(makeActionProvider('x', 'v1'));
|
|
161
|
+
reg.registerActionProvider(makeActionProvider('x', 'v1'));
|
|
162
|
+
expect(reg.getActionProviders()).toHaveLength(1);
|
|
163
|
+
warnSpy.mockRestore();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ─── CatalogProvider tests ────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
describe('PluginRegistry — CatalogProvider', () => {
|
|
170
|
+
it('registers catalog providers', () => {
|
|
171
|
+
const reg = new PluginRegistry();
|
|
172
|
+
reg.registerCatalogProvider(makeCatalogProvider('pd-catalog'));
|
|
173
|
+
expect(reg.getCatalogProviders()).toHaveLength(1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('skips duplicate catalog provider IDs', () => {
|
|
177
|
+
const reg = new PluginRegistry();
|
|
178
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
179
|
+
reg.registerCatalogProvider(makeCatalogProvider('dup'));
|
|
180
|
+
reg.registerCatalogProvider(makeCatalogProvider('dup'));
|
|
181
|
+
expect(reg.getCatalogProviders()).toHaveLength(1);
|
|
182
|
+
warnSpy.mockRestore();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ─── Registry isolation ───────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
describe('PluginRegistry — isolation', () => {
|
|
189
|
+
it('two registry instances do not share state', () => {
|
|
190
|
+
const a = new PluginRegistry();
|
|
191
|
+
const b = new PluginRegistry();
|
|
192
|
+
a.registerEntityTab(makeTab('shared-tab'));
|
|
193
|
+
expect(b.getEntityTabs()).toHaveLength(0);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expectTypeOf } from 'vitest';
|
|
2
|
+
import type {
|
|
3
|
+
ForgePluginSDK,
|
|
4
|
+
ActionProvider,
|
|
5
|
+
ActionContext,
|
|
6
|
+
ActionResult,
|
|
7
|
+
PluginManifest,
|
|
8
|
+
} from '../src/types.js';
|
|
9
|
+
|
|
10
|
+
describe('ForgePluginSDK interface', () => {
|
|
11
|
+
it('has all registration methods', () => {
|
|
12
|
+
type Methods = keyof ForgePluginSDK;
|
|
13
|
+
expectTypeOf<'registerEntityTab'>().toMatchTypeOf<Methods>();
|
|
14
|
+
expectTypeOf<'registerEntityCard'>().toMatchTypeOf<Methods>();
|
|
15
|
+
expectTypeOf<'registerRoute'>().toMatchTypeOf<Methods>();
|
|
16
|
+
expectTypeOf<'registerActionProvider'>().toMatchTypeOf<Methods>();
|
|
17
|
+
expectTypeOf<'registerCatalogProvider'>().toMatchTypeOf<Methods>();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('ActionResult interface', () => {
|
|
22
|
+
it('requires status and outputs', () => {
|
|
23
|
+
type Keys = keyof ActionResult;
|
|
24
|
+
expectTypeOf<'status'>().toMatchTypeOf<Keys>();
|
|
25
|
+
expectTypeOf<'outputs'>().toMatchTypeOf<Keys>();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('status is a union of success | failed', () => {
|
|
29
|
+
expectTypeOf<ActionResult['status']>().toEqualTypeOf<'success' | 'failed'>();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('ActionContext interface', () => {
|
|
34
|
+
it('exposes all required service accessors', () => {
|
|
35
|
+
type Keys = keyof ActionContext;
|
|
36
|
+
expectTypeOf<'config'>().toMatchTypeOf<Keys>();
|
|
37
|
+
expectTypeOf<'logger'>().toMatchTypeOf<Keys>();
|
|
38
|
+
expectTypeOf<'scm'>().toMatchTypeOf<Keys>();
|
|
39
|
+
expectTypeOf<'db'>().toMatchTypeOf<Keys>();
|
|
40
|
+
expectTypeOf<'acquireRepoLock'>().toMatchTypeOf<Keys>();
|
|
41
|
+
expectTypeOf<'log'>().toMatchTypeOf<Keys>();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('acquireRepoLock takes a string and returns Promise<void>', () => {
|
|
45
|
+
expectTypeOf<ActionContext['acquireRepoLock']>().toEqualTypeOf<(repoUrl: string) => Promise<void>>();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('PluginManifest interface', () => {
|
|
50
|
+
it('forgeportal.type is a union of plugin types', () => {
|
|
51
|
+
expectTypeOf<PluginManifest['forgeportal']['type']>().toEqualTypeOf<'ui' | 'backend' | 'fullstack'>();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('ActionProvider interface', () => {
|
|
56
|
+
it('handler returns Promise<ActionResult>', () => {
|
|
57
|
+
type HandlerReturn = ReturnType<ActionProvider['handler']>;
|
|
58
|
+
expectTypeOf<HandlerReturn>().toEqualTypeOf<Promise<ActionResult>>();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { ActionProvider, CatalogProvider, ActionConfigAccessor, ActionLogger } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A backend route registration — Fastify plugin function scoped under
|
|
5
|
+
* /api/v1/plugins/{pluginId}/{path}/
|
|
6
|
+
*/
|
|
7
|
+
export interface BackendRoute {
|
|
8
|
+
/** Relative path prefix, e.g. '/alerts'. No leading slash required. */
|
|
9
|
+
path: string;
|
|
10
|
+
/**
|
|
11
|
+
* Fastify async plugin that registers route handlers.
|
|
12
|
+
* Receives a Fastify instance pre-scoped to the plugin's route prefix.
|
|
13
|
+
* Do NOT call fastify.listen() — only register routes.
|
|
14
|
+
*/
|
|
15
|
+
handler: (fastify: FastifyInstance) => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The SDK object passed to backend and fullstack plugin entry points.
|
|
19
|
+
* Distinct from ForgePluginSDK (which uses React components).
|
|
20
|
+
*
|
|
21
|
+
* Backend plugin entry: export function registerBackendPlugin(sdk: ForgeBackendPluginSDK): void
|
|
22
|
+
*/
|
|
23
|
+
export interface ForgeBackendPluginSDK {
|
|
24
|
+
/** Plugin-scoped config accessor (from forgeportal.yaml plugins.<id>.config). */
|
|
25
|
+
readonly config: ActionConfigAccessor;
|
|
26
|
+
/** Structured logger scoped to this plugin. */
|
|
27
|
+
readonly logger: ActionLogger;
|
|
28
|
+
/** Register an action provider (available in action runner + templates). */
|
|
29
|
+
registerActionProvider(provider: ActionProvider): void;
|
|
30
|
+
/** Register a catalog provider (periodic ingestion of external entities). */
|
|
31
|
+
registerCatalogProvider(provider: CatalogProvider): void;
|
|
32
|
+
/**
|
|
33
|
+
* Register backend routes under /api/v1/plugins/{pluginId}/{route.path}/
|
|
34
|
+
* The Fastify instance passed to handler is already authenticated (authGuard runs).
|
|
35
|
+
*/
|
|
36
|
+
registerBackendRoute(route: BackendRoute): void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Backend plugin registry — in-memory store for backend capabilities.
|
|
40
|
+
* Instantiated by the plugin loader for each plugin.
|
|
41
|
+
*/
|
|
42
|
+
export declare class BackendPluginRegistry implements ForgeBackendPluginSDK {
|
|
43
|
+
readonly config: ActionConfigAccessor;
|
|
44
|
+
readonly logger: ActionLogger;
|
|
45
|
+
private readonly _actionProviders;
|
|
46
|
+
private readonly _catalogProviders;
|
|
47
|
+
private readonly _routes;
|
|
48
|
+
constructor(config: ActionConfigAccessor, logger: ActionLogger);
|
|
49
|
+
registerActionProvider(provider: ActionProvider): void;
|
|
50
|
+
registerCatalogProvider(provider: CatalogProvider): void;
|
|
51
|
+
registerBackendRoute(route: BackendRoute): void;
|
|
52
|
+
getActionProviders(): ActionProvider[];
|
|
53
|
+
getCatalogProviders(): CatalogProvider[];
|
|
54
|
+
getBackendRoutes(): BackendRoute[];
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,IAAI,EAAK,MAAM,CAAC;IAChB;;;;OAIG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,4EAA4E;IAC5E,sBAAsB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACvD,6EAA6E;IAC7E,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACzD;;;OAGG;IACH,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CACjD;AAED;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,qBAAqB;IAM/D,QAAQ,CAAC,MAAM,EAAE,oBAAoB;IACrC,QAAQ,CAAC,MAAM,EAAE,YAAY;IAN/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsC;IACvE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;IACxE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAGnC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,YAAY;IAG/B,sBAAsB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAStD,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAQxD,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAI/C,kBAAkB,IAAK,cAAc,EAAE;IACvC,mBAAmB,IAAI,eAAe,EAAE;IACxC,gBAAgB,IAAO,YAAY,EAAE;CACtC"}
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend plugin registry — in-memory store for backend capabilities.
|
|
3
|
+
* Instantiated by the plugin loader for each plugin.
|
|
4
|
+
*/
|
|
5
|
+
export class BackendPluginRegistry {
|
|
6
|
+
config;
|
|
7
|
+
logger;
|
|
8
|
+
_actionProviders = new Map();
|
|
9
|
+
_catalogProviders = new Map();
|
|
10
|
+
_routes = [];
|
|
11
|
+
constructor(config, logger) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
}
|
|
15
|
+
registerActionProvider(provider) {
|
|
16
|
+
const key = `${provider.id}@${provider.version}`;
|
|
17
|
+
if (this._actionProviders.has(key)) {
|
|
18
|
+
console.warn(`[ForgePortal SDK] ActionProvider "${key}" already registered — skipping.`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this._actionProviders.set(key, provider);
|
|
22
|
+
}
|
|
23
|
+
registerCatalogProvider(provider) {
|
|
24
|
+
if (this._catalogProviders.has(provider.id)) {
|
|
25
|
+
console.warn(`[ForgePortal SDK] CatalogProvider "${provider.id}" already registered — skipping.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this._catalogProviders.set(provider.id, provider);
|
|
29
|
+
}
|
|
30
|
+
registerBackendRoute(route) {
|
|
31
|
+
this._routes.push(route);
|
|
32
|
+
}
|
|
33
|
+
getActionProviders() { return [...this._actionProviders.values()]; }
|
|
34
|
+
getCatalogProviders() { return [...this._catalogProviders.values()]; }
|
|
35
|
+
getBackendRoutes() { return [...this._routes]; }
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AA6CA;;;GAGG;AACH,MAAM,OAAO,qBAAqB;IAMrB;IACA;IANM,gBAAgB,GAAI,IAAI,GAAG,EAA0B,CAAC;IACtD,iBAAiB,GAAG,IAAI,GAAG,EAA2B,CAAC;IACvD,OAAO,GAAmB,EAAE,CAAC;IAE9C,YACW,MAA4B,EAC5B,MAAoB;QADpB,WAAM,GAAN,MAAM,CAAsB;QAC5B,WAAM,GAAN,MAAM,CAAc;IAC5B,CAAC;IAEJ,sBAAsB,CAAC,QAAwB;QAC7C,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QACjD,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,IAAI,CAAC,qCAAqC,GAAG,kCAAkC,CAAC,CAAC;YACzF,OAAO;QACT,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED,uBAAuB,CAAC,QAAyB;QAC/C,IAAI,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,sCAAsC,QAAQ,CAAC,EAAE,kCAAkC,CAAC,CAAC;YAClG,OAAO;QACT,CAAC;QACD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED,oBAAoB,CAAC,KAAmB;QACtC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,kBAAkB,KAAyB,OAAO,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACxF,mBAAmB,KAAwB,OAAO,CAAC,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACzF,gBAAgB,KAA2B,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CACvE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Entity } from './types.js';
|
|
3
|
+
interface EntityContextValue {
|
|
4
|
+
entity: Entity;
|
|
5
|
+
}
|
|
6
|
+
export declare const EntityContext: React.Context<EntityContextValue | null>;
|
|
7
|
+
export declare function EntityProvider({ entity, children, }: {
|
|
8
|
+
entity: Entity;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
interface PluginConfigContextValue {
|
|
12
|
+
get<T = unknown>(key: string): T | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare const PluginConfigContext: React.Context<PluginConfigContextValue>;
|
|
15
|
+
export declare function PluginConfigProvider({ config, children, }: {
|
|
16
|
+
config: Record<string, unknown>;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
/** @internal Used by useEntity hook */
|
|
20
|
+
export declare function useEntityContext(): EntityContextValue | null;
|
|
21
|
+
/** @internal Used by useConfig hook */
|
|
22
|
+
export declare function usePluginConfigContext(): PluginConfigContextValue;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAIzC,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,aAAa,0CAAiD,CAAC;AAE5E,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAI,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,2CAMA;AAID,UAAU,wBAAwB;IAChC,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC9C;AAMD,eAAO,MAAM,mBAAmB,yCAAyD,CAAC;AAE1F,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,2CASA;AAED,uCAAuC;AACvC,wBAAgB,gBAAgB,IAAI,kBAAkB,GAAG,IAAI,CAE5D;AAED,uCAAuC;AACvC,wBAAgB,sBAAsB,IAAI,wBAAwB,CAEjE"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
export const EntityContext = createContext(null);
|
|
4
|
+
export function EntityProvider({ entity, children, }) {
|
|
5
|
+
return (_jsx(EntityContext.Provider, { value: { entity }, children: children }));
|
|
6
|
+
}
|
|
7
|
+
const defaultConfig = {
|
|
8
|
+
get: () => undefined,
|
|
9
|
+
};
|
|
10
|
+
export const PluginConfigContext = createContext(defaultConfig);
|
|
11
|
+
export function PluginConfigProvider({ config, children, }) {
|
|
12
|
+
const accessor = {
|
|
13
|
+
get: (key) => config[key],
|
|
14
|
+
};
|
|
15
|
+
return (_jsx(PluginConfigContext.Provider, { value: accessor, children: children }));
|
|
16
|
+
}
|
|
17
|
+
/** @internal Used by useEntity hook */
|
|
18
|
+
export function useEntityContext() {
|
|
19
|
+
return useContext(EntityContext);
|
|
20
|
+
}
|
|
21
|
+
/** @internal Used by useConfig hook */
|
|
22
|
+
export function usePluginConfigContext() {
|
|
23
|
+
return useContext(PluginConfigContext);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AASzD,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAA4B,IAAI,CAAC,CAAC;AAE5E,MAAM,UAAU,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,GAIT;IACC,OAAO,CACL,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,MAAM,EAAE,YACtC,QAAQ,GACc,CAC1B,CAAC;AACJ,CAAC;AAQD,MAAM,aAAa,GAA6B;IAC9C,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;CACrB,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAA2B,aAAa,CAAC,CAAC;AAE1F,MAAM,UAAU,oBAAoB,CAAC,EACnC,MAAM,EACN,QAAQ,GAIT;IACC,MAAM,QAAQ,GAA6B;QACzC,GAAG,EAAE,CAAc,GAAW,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAkB;KAChE,CAAC;IACF,OAAO,CACL,KAAC,mBAAmB,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,YAC1C,QAAQ,GACoB,CAChC,CAAC;AACJ,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,gBAAgB;IAC9B,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;AACnC,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,sBAAsB;IACpC,OAAO,UAAU,CAAC,mBAAmB,CAAC,CAAC;AACzC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { Entity, EntityDraft, EntityTab, EntityTabAppliesTo, EntityCard, Route, JsonSchema, JsonSchemaType, ActionResult, ActionLogger, ActionScmAccessor, ActionDbAccessor, ActionConfigAccessor, ActionContext, ActionProvider, CatalogProviderContext, CatalogProvider, ForgePluginSDK, PluginConfigFieldSchema, PluginCapabilities, PluginManifest, } from './types.js';
|
|
2
|
+
export { PluginRegistry, globalRegistry } from './registry.js';
|
|
3
|
+
export type { BackendRoute, ForgeBackendPluginSDK } from './backend.js';
|
|
4
|
+
export { BackendPluginRegistry } from './backend.js';
|
|
5
|
+
export declare const SDK_VERSION = "1.0.0";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,MAAM,EACN,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,UAAU,EACV,KAAK,EACL,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,GACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/D,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAGrD,eAAO,MAAM,WAAW,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
2
|
+
export { PluginRegistry, globalRegistry } from './registry.js';
|
|
3
|
+
export { BackendPluginRegistry } from './backend.js';
|
|
4
|
+
// ─── SDK version (used by plugin loader for engineVersion compatibility check) ─
|
|
5
|
+
export const SDK_VERSION = '1.0.0';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAyBA,iFAAiF;AACjF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAI/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,kFAAkF;AAClF,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC"}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type UseQueryOptions, type UseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
import type { Entity } from './types.js';
|
|
3
|
+
export { EntityProvider, EntityContext, PluginConfigProvider, PluginConfigContext, } from './context.js';
|
|
4
|
+
/**
|
|
5
|
+
* Returns the current entity from the entity detail page context.
|
|
6
|
+
* Must be used inside a component rendered as an EntityTab or EntityCard.
|
|
7
|
+
*
|
|
8
|
+
* @throws if called outside of an EntityProvider.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useEntity(): {
|
|
11
|
+
entity: Entity;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Returns the plugin-scoped config value for the given key.
|
|
15
|
+
* Config is sourced from `forgeportal.yaml` -> `plugins.<pluginId>.config`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const apiUrl = useConfig<string>('apiEndpoint');
|
|
19
|
+
*/
|
|
20
|
+
export declare function useConfig<T = unknown>(key: string): T | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Typed API fetcher backed by TanStack Query.
|
|
23
|
+
* Automatically includes credentials for session-based auth.
|
|
24
|
+
*
|
|
25
|
+
* @param path - Absolute API path, e.g. '/api/v1/entities'
|
|
26
|
+
* @param options - Optional TanStack Query options to override defaults
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const { data, isPending } = useApi<MyResponse[]>('/api/v1/my-plugin/data');
|
|
30
|
+
*/
|
|
31
|
+
export declare function useApi<TData = unknown>(path: string, options?: Omit<UseQueryOptions<TData>, 'queryKey' | 'queryFn'>): UseQueryResult<TData, Error>;
|
|
32
|
+
//# sourceMappingURL=react.d.ts.map
|