@kb-labs/rest-api-core 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/README.md +72 -0
- package/dist/index.d.ts +251 -0
- package/dist/index.js +253 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @kb-labs/rest-api-core
|
|
2
|
+
|
|
3
|
+
Configuration loader and shared types for the KB Labs REST API runtime.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Provides the Zod schema, loader, and environment variable mapping for `@kb-labs/rest-api-app`.
|
|
8
|
+
No business logic — pure configuration infrastructure.
|
|
9
|
+
|
|
10
|
+
## API Reference
|
|
11
|
+
|
|
12
|
+
- `loadRestApiConfig(options)` — load and validate REST API config from `kb-labs.config.json`
|
|
13
|
+
- `restApiConfigSchema` — Zod schema for REST API configuration
|
|
14
|
+
- `RestApiConfig` — TypeScript type for validated config
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
Loaded from `kb-labs.config.json` under the `restApi` key:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"restApi": {
|
|
23
|
+
"port": 5050,
|
|
24
|
+
"basePath": "/api/v1",
|
|
25
|
+
"apiVersion": "1.0.0",
|
|
26
|
+
"cors": {
|
|
27
|
+
"origins": ["http://localhost:3000"],
|
|
28
|
+
"profile": "dev"
|
|
29
|
+
},
|
|
30
|
+
"http2": {
|
|
31
|
+
"enabled": false,
|
|
32
|
+
"allowHTTP1": true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Environment variables (`KB_REST_*`) override file config — see root [README.md](../../README.md) for full list.
|
|
39
|
+
|
|
40
|
+
### HTTP/2
|
|
41
|
+
|
|
42
|
+
Enable HTTP/2 to remove the 6-connection browser limit (important for SSE):
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"restApi": {
|
|
47
|
+
"http2": { "enabled": true, "allowHTTP1": true },
|
|
48
|
+
"ssl": { "keyPath": "ssl/server.key", "certPath": "ssl/server.cert" }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Generate a self-signed cert for local development:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
mkdir -p ssl
|
|
57
|
+
openssl req -x509 -newkey rsa:2048 -nodes \
|
|
58
|
+
-keyout ssl/server.key -out ssl/server.cert \
|
|
59
|
+
-days 365 -subj "/CN=localhost"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { loadRestApiConfig } from '@kb-labs/rest-api-core';
|
|
66
|
+
|
|
67
|
+
const config = await loadRestApiConfig({ cwd: process.cwd() });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
KB Public License v1.1 © KB Labs
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ManifestV3 } from '@kb-labs/plugin-contracts';
|
|
3
|
+
import { StudioPluginEntry, StudioWidgetDecl, StudioMenuDecl, StudioLayoutDecl, StudioRegistry } from '@kb-labs/rest-api-contracts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @module @kb-labs/rest-api-core/config/schema
|
|
7
|
+
* Zod schema for REST API configuration
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
declare const restApiConfigSchema: z.ZodObject<{
|
|
11
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
basePath: z.ZodDefault<z.ZodString>;
|
|
13
|
+
apiVersion: z.ZodDefault<z.ZodString>;
|
|
14
|
+
cors: z.ZodDefault<z.ZodObject<{
|
|
15
|
+
origins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
16
|
+
allowCredentials: z.ZodDefault<z.ZodBoolean>;
|
|
17
|
+
profile: z.ZodDefault<z.ZodEnum<["dev", "preview", "prod"]>>;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
origins: string[];
|
|
20
|
+
allowCredentials: boolean;
|
|
21
|
+
profile: "dev" | "preview" | "prod";
|
|
22
|
+
}, {
|
|
23
|
+
origins?: string[] | undefined;
|
|
24
|
+
allowCredentials?: boolean | undefined;
|
|
25
|
+
profile?: "dev" | "preview" | "prod" | undefined;
|
|
26
|
+
}>>;
|
|
27
|
+
timeouts: z.ZodOptional<z.ZodObject<{
|
|
28
|
+
requestTimeout: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
bodyLimit: z.ZodDefault<z.ZodNumber>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
requestTimeout: number;
|
|
32
|
+
bodyLimit: number;
|
|
33
|
+
}, {
|
|
34
|
+
requestTimeout?: number | undefined;
|
|
35
|
+
bodyLimit?: number | undefined;
|
|
36
|
+
}>>;
|
|
37
|
+
rateLimit: z.ZodOptional<z.ZodObject<{
|
|
38
|
+
max: z.ZodDefault<z.ZodNumber>;
|
|
39
|
+
timeWindow: z.ZodDefault<z.ZodString>;
|
|
40
|
+
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
max: number;
|
|
42
|
+
timeWindow: string;
|
|
43
|
+
}, {
|
|
44
|
+
max?: number | undefined;
|
|
45
|
+
timeWindow?: string | undefined;
|
|
46
|
+
}>>;
|
|
47
|
+
startup: z.ZodOptional<z.ZodObject<{
|
|
48
|
+
maxConcurrent: z.ZodDefault<z.ZodNumber>;
|
|
49
|
+
queueLimit: z.ZodDefault<z.ZodNumber>;
|
|
50
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
51
|
+
retryAfterSeconds: z.ZodDefault<z.ZodNumber>;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
maxConcurrent: number;
|
|
54
|
+
queueLimit: number;
|
|
55
|
+
timeoutMs: number;
|
|
56
|
+
retryAfterSeconds: number;
|
|
57
|
+
}, {
|
|
58
|
+
maxConcurrent?: number | undefined;
|
|
59
|
+
queueLimit?: number | undefined;
|
|
60
|
+
timeoutMs?: number | undefined;
|
|
61
|
+
retryAfterSeconds?: number | undefined;
|
|
62
|
+
}>>;
|
|
63
|
+
plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
64
|
+
mockMode: z.ZodDefault<z.ZodBoolean>;
|
|
65
|
+
redis: z.ZodOptional<z.ZodObject<{
|
|
66
|
+
url: z.ZodString;
|
|
67
|
+
namespace: z.ZodDefault<z.ZodString>;
|
|
68
|
+
}, "strip", z.ZodTypeAny, {
|
|
69
|
+
url: string;
|
|
70
|
+
namespace: string;
|
|
71
|
+
}, {
|
|
72
|
+
url: string;
|
|
73
|
+
namespace?: string | undefined;
|
|
74
|
+
}>>;
|
|
75
|
+
events: z.ZodOptional<z.ZodObject<{
|
|
76
|
+
registry: z.ZodOptional<z.ZodObject<{
|
|
77
|
+
token: z.ZodString;
|
|
78
|
+
headerName: z.ZodDefault<z.ZodString>;
|
|
79
|
+
queryParam: z.ZodDefault<z.ZodString>;
|
|
80
|
+
}, "strip", z.ZodTypeAny, {
|
|
81
|
+
token: string;
|
|
82
|
+
headerName: string;
|
|
83
|
+
queryParam: string;
|
|
84
|
+
}, {
|
|
85
|
+
token: string;
|
|
86
|
+
headerName?: string | undefined;
|
|
87
|
+
queryParam?: string | undefined;
|
|
88
|
+
}>>;
|
|
89
|
+
}, "strip", z.ZodTypeAny, {
|
|
90
|
+
registry?: {
|
|
91
|
+
token: string;
|
|
92
|
+
headerName: string;
|
|
93
|
+
queryParam: string;
|
|
94
|
+
} | undefined;
|
|
95
|
+
}, {
|
|
96
|
+
registry?: {
|
|
97
|
+
token: string;
|
|
98
|
+
headerName?: string | undefined;
|
|
99
|
+
queryParam?: string | undefined;
|
|
100
|
+
} | undefined;
|
|
101
|
+
}>>;
|
|
102
|
+
http2: z.ZodOptional<z.ZodObject<{
|
|
103
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
104
|
+
allowHTTP1: z.ZodDefault<z.ZodBoolean>;
|
|
105
|
+
}, "strip", z.ZodTypeAny, {
|
|
106
|
+
enabled: boolean;
|
|
107
|
+
allowHTTP1: boolean;
|
|
108
|
+
}, {
|
|
109
|
+
enabled?: boolean | undefined;
|
|
110
|
+
allowHTTP1?: boolean | undefined;
|
|
111
|
+
}>>;
|
|
112
|
+
ssl: z.ZodOptional<z.ZodObject<{
|
|
113
|
+
keyPath: z.ZodString;
|
|
114
|
+
certPath: z.ZodString;
|
|
115
|
+
}, "strip", z.ZodTypeAny, {
|
|
116
|
+
keyPath: string;
|
|
117
|
+
certPath: string;
|
|
118
|
+
}, {
|
|
119
|
+
keyPath: string;
|
|
120
|
+
certPath: string;
|
|
121
|
+
}>>;
|
|
122
|
+
}, "strip", z.ZodTypeAny, {
|
|
123
|
+
port: number;
|
|
124
|
+
basePath: string;
|
|
125
|
+
apiVersion: string;
|
|
126
|
+
cors: {
|
|
127
|
+
origins: string[];
|
|
128
|
+
allowCredentials: boolean;
|
|
129
|
+
profile: "dev" | "preview" | "prod";
|
|
130
|
+
};
|
|
131
|
+
plugins: string[];
|
|
132
|
+
mockMode: boolean;
|
|
133
|
+
timeouts?: {
|
|
134
|
+
requestTimeout: number;
|
|
135
|
+
bodyLimit: number;
|
|
136
|
+
} | undefined;
|
|
137
|
+
rateLimit?: {
|
|
138
|
+
max: number;
|
|
139
|
+
timeWindow: string;
|
|
140
|
+
} | undefined;
|
|
141
|
+
startup?: {
|
|
142
|
+
maxConcurrent: number;
|
|
143
|
+
queueLimit: number;
|
|
144
|
+
timeoutMs: number;
|
|
145
|
+
retryAfterSeconds: number;
|
|
146
|
+
} | undefined;
|
|
147
|
+
redis?: {
|
|
148
|
+
url: string;
|
|
149
|
+
namespace: string;
|
|
150
|
+
} | undefined;
|
|
151
|
+
events?: {
|
|
152
|
+
registry?: {
|
|
153
|
+
token: string;
|
|
154
|
+
headerName: string;
|
|
155
|
+
queryParam: string;
|
|
156
|
+
} | undefined;
|
|
157
|
+
} | undefined;
|
|
158
|
+
http2?: {
|
|
159
|
+
enabled: boolean;
|
|
160
|
+
allowHTTP1: boolean;
|
|
161
|
+
} | undefined;
|
|
162
|
+
ssl?: {
|
|
163
|
+
keyPath: string;
|
|
164
|
+
certPath: string;
|
|
165
|
+
} | undefined;
|
|
166
|
+
}, {
|
|
167
|
+
port?: number | undefined;
|
|
168
|
+
basePath?: string | undefined;
|
|
169
|
+
apiVersion?: string | undefined;
|
|
170
|
+
cors?: {
|
|
171
|
+
origins?: string[] | undefined;
|
|
172
|
+
allowCredentials?: boolean | undefined;
|
|
173
|
+
profile?: "dev" | "preview" | "prod" | undefined;
|
|
174
|
+
} | undefined;
|
|
175
|
+
timeouts?: {
|
|
176
|
+
requestTimeout?: number | undefined;
|
|
177
|
+
bodyLimit?: number | undefined;
|
|
178
|
+
} | undefined;
|
|
179
|
+
rateLimit?: {
|
|
180
|
+
max?: number | undefined;
|
|
181
|
+
timeWindow?: string | undefined;
|
|
182
|
+
} | undefined;
|
|
183
|
+
startup?: {
|
|
184
|
+
maxConcurrent?: number | undefined;
|
|
185
|
+
queueLimit?: number | undefined;
|
|
186
|
+
timeoutMs?: number | undefined;
|
|
187
|
+
retryAfterSeconds?: number | undefined;
|
|
188
|
+
} | undefined;
|
|
189
|
+
plugins?: string[] | undefined;
|
|
190
|
+
mockMode?: boolean | undefined;
|
|
191
|
+
redis?: {
|
|
192
|
+
url: string;
|
|
193
|
+
namespace?: string | undefined;
|
|
194
|
+
} | undefined;
|
|
195
|
+
events?: {
|
|
196
|
+
registry?: {
|
|
197
|
+
token: string;
|
|
198
|
+
headerName?: string | undefined;
|
|
199
|
+
queryParam?: string | undefined;
|
|
200
|
+
} | undefined;
|
|
201
|
+
} | undefined;
|
|
202
|
+
http2?: {
|
|
203
|
+
enabled?: boolean | undefined;
|
|
204
|
+
allowHTTP1?: boolean | undefined;
|
|
205
|
+
} | undefined;
|
|
206
|
+
ssl?: {
|
|
207
|
+
keyPath: string;
|
|
208
|
+
certPath: string;
|
|
209
|
+
} | undefined;
|
|
210
|
+
}>;
|
|
211
|
+
type RestApiConfig = z.infer<typeof restApiConfigSchema>;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @module @kb-labs/rest-api-core/config/loader
|
|
215
|
+
* Configuration loader using loadBundle from @kb-labs/core-bundle
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Load REST API configuration
|
|
220
|
+
*/
|
|
221
|
+
declare function loadRestApiConfig(cwd?: string, cliOverrides?: Partial<RestApiConfig>): Promise<{
|
|
222
|
+
config: RestApiConfig;
|
|
223
|
+
diagnostics: Array<{
|
|
224
|
+
level: string;
|
|
225
|
+
code: string;
|
|
226
|
+
message: string;
|
|
227
|
+
}>;
|
|
228
|
+
}>;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @module @kb-labs/rest-api-core/studio-registry
|
|
232
|
+
*
|
|
233
|
+
* Transforms ManifestV3 studio sections into StudioRegistry format.
|
|
234
|
+
* REST API uses this to provide pre-computed registry data to Studio frontend.
|
|
235
|
+
*/
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Convert a single ManifestV3 to StudioRegistry entries.
|
|
239
|
+
*/
|
|
240
|
+
declare function manifestToRegistry(manifest: ManifestV3): {
|
|
241
|
+
plugin: StudioPluginEntry;
|
|
242
|
+
widgets: StudioWidgetDecl[];
|
|
243
|
+
menus: StudioMenuDecl[];
|
|
244
|
+
layouts: StudioLayoutDecl[];
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Combine multiple manifests into a single StudioRegistry.
|
|
248
|
+
*/
|
|
249
|
+
declare function combineManifestsToRegistry(manifests: ManifestV3[], _registryVersion?: string): StudioRegistry;
|
|
250
|
+
|
|
251
|
+
export { type RestApiConfig, combineManifestsToRegistry, loadRestApiConfig, manifestToRegistry, restApiConfigSchema };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { loadBundle } from '@kb-labs/core-bundle';
|
|
2
|
+
import { findNearestConfig, readJsonWithDiagnostics, resolveConfig } from '@kb-labs/core-config';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
// src/config/loader.ts
|
|
6
|
+
var restApiConfigSchema = z.object({
|
|
7
|
+
port: z.number().int().positive().default(5050),
|
|
8
|
+
basePath: z.string().default("/api/v1"),
|
|
9
|
+
apiVersion: z.string().default("1.0.0"),
|
|
10
|
+
cors: z.object({
|
|
11
|
+
origins: z.array(z.string()).default(["http://localhost:3000", "http://localhost:5173"]),
|
|
12
|
+
allowCredentials: z.boolean().default(true),
|
|
13
|
+
profile: z.enum(["dev", "preview", "prod"]).default("dev")
|
|
14
|
+
}).default({}),
|
|
15
|
+
timeouts: z.object({
|
|
16
|
+
requestTimeout: z.number().int().positive().default(3e4),
|
|
17
|
+
bodyLimit: z.number().int().positive().default(10485760)
|
|
18
|
+
}).optional(),
|
|
19
|
+
rateLimit: z.object({
|
|
20
|
+
max: z.number().int().positive().default(60),
|
|
21
|
+
timeWindow: z.string().default("1 minute")
|
|
22
|
+
}).optional(),
|
|
23
|
+
startup: z.object({
|
|
24
|
+
maxConcurrent: z.number().int().positive().default(32),
|
|
25
|
+
queueLimit: z.number().int().nonnegative().default(128),
|
|
26
|
+
timeoutMs: z.number().int().positive().default(5e3),
|
|
27
|
+
retryAfterSeconds: z.number().int().positive().default(2)
|
|
28
|
+
}).optional(),
|
|
29
|
+
plugins: z.array(z.string()).default([]),
|
|
30
|
+
mockMode: z.boolean().default(false),
|
|
31
|
+
redis: z.object({
|
|
32
|
+
url: z.string().min(1),
|
|
33
|
+
namespace: z.string().default("kb")
|
|
34
|
+
}).optional(),
|
|
35
|
+
events: z.object({
|
|
36
|
+
registry: z.object({
|
|
37
|
+
token: z.string().min(1),
|
|
38
|
+
headerName: z.string().default("authorization"),
|
|
39
|
+
queryParam: z.string().default("access_token")
|
|
40
|
+
}).optional()
|
|
41
|
+
}).optional(),
|
|
42
|
+
http2: z.object({
|
|
43
|
+
enabled: z.boolean().default(false),
|
|
44
|
+
allowHTTP1: z.boolean().default(true)
|
|
45
|
+
// Fallback for old clients
|
|
46
|
+
}).optional(),
|
|
47
|
+
ssl: z.object({
|
|
48
|
+
keyPath: z.string().min(1),
|
|
49
|
+
certPath: z.string().min(1)
|
|
50
|
+
}).optional()
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// src/config/loader.ts
|
|
54
|
+
async function loadRestApiConfig(cwd = process.cwd(), cliOverrides) {
|
|
55
|
+
let bundleConfig = {};
|
|
56
|
+
try {
|
|
57
|
+
const bundle = await loadBundle({
|
|
58
|
+
cwd,
|
|
59
|
+
product: "restApi",
|
|
60
|
+
// TODO: Add 'restApi' to ProductId type
|
|
61
|
+
profileId: "default"
|
|
62
|
+
});
|
|
63
|
+
bundleConfig = bundle.config;
|
|
64
|
+
} catch {
|
|
65
|
+
const { path: configPath } = await findNearestConfig({
|
|
66
|
+
startDir: cwd,
|
|
67
|
+
filenames: [".kb/kb.config.json", ".kb/kb.config.yaml"]
|
|
68
|
+
});
|
|
69
|
+
if (configPath) {
|
|
70
|
+
const readResult = await readJsonWithDiagnostics(configPath);
|
|
71
|
+
if (readResult.ok && readResult.data?.rest) {
|
|
72
|
+
bundleConfig = readResult.data.rest;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const defaults = {
|
|
77
|
+
port: 5050,
|
|
78
|
+
basePath: "/api/v1",
|
|
79
|
+
apiVersion: "1.0.0",
|
|
80
|
+
cors: {
|
|
81
|
+
origins: ["http://localhost:3000", "http://localhost:5173"],
|
|
82
|
+
allowCredentials: true,
|
|
83
|
+
profile: "dev"
|
|
84
|
+
},
|
|
85
|
+
timeouts: {
|
|
86
|
+
requestTimeout: 3e4,
|
|
87
|
+
bodyLimit: 10485760
|
|
88
|
+
},
|
|
89
|
+
rateLimit: {
|
|
90
|
+
// 600/min for dev (dashboard needs ~10 requests per page load)
|
|
91
|
+
// Override with KB_REST_RATE_LIMIT_MAX=60 for production public API
|
|
92
|
+
max: 600,
|
|
93
|
+
timeWindow: "1 minute"
|
|
94
|
+
},
|
|
95
|
+
plugins: [],
|
|
96
|
+
mockMode: false
|
|
97
|
+
};
|
|
98
|
+
const envMapper = (env) => {
|
|
99
|
+
const overrides = {};
|
|
100
|
+
if (env.KB_REST_PORT) {
|
|
101
|
+
const parsedPort = Number.parseInt(env.KB_REST_PORT, 10);
|
|
102
|
+
if (!Number.isNaN(parsedPort)) {
|
|
103
|
+
overrides.port = parsedPort;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (env.KB_REST_BASE_PATH) {
|
|
107
|
+
overrides.basePath = env.KB_REST_BASE_PATH;
|
|
108
|
+
}
|
|
109
|
+
if (env.KB_REST_API_VERSION) {
|
|
110
|
+
overrides.apiVersion = env.KB_REST_API_VERSION;
|
|
111
|
+
}
|
|
112
|
+
if (env.KB_REST_CORS_ORIGINS) {
|
|
113
|
+
const origins = env.KB_REST_CORS_ORIGINS.split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
114
|
+
overrides.cors = {
|
|
115
|
+
...overrides.cors ?? defaults.cors,
|
|
116
|
+
origins
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (env.KB_REST_CORS_PROFILE) {
|
|
120
|
+
overrides.cors = {
|
|
121
|
+
...overrides.cors ?? defaults.cors,
|
|
122
|
+
profile: env.KB_REST_CORS_PROFILE
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (env.KB_REST_MOCK_MODE === "true" || env.KB_REST_MOCK_MODE === "1") {
|
|
126
|
+
overrides.mockMode = true;
|
|
127
|
+
}
|
|
128
|
+
if (env.KB_REST_REDIS_URL) {
|
|
129
|
+
overrides.redis = {
|
|
130
|
+
...overrides.redis ?? {},
|
|
131
|
+
url: env.KB_REST_REDIS_URL,
|
|
132
|
+
namespace: overrides.redis?.namespace ?? defaults.redis?.namespace ?? "kb"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (env.KB_REST_REDIS_NAMESPACE) {
|
|
136
|
+
overrides.redis = {
|
|
137
|
+
...overrides.redis ?? (env.KB_REST_REDIS_URL ? { url: env.KB_REST_REDIS_URL } : defaults.redis ?? {}),
|
|
138
|
+
namespace: env.KB_REST_REDIS_NAMESPACE
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (env.KB_REST_RATE_LIMIT_MAX) {
|
|
142
|
+
const parsedMax = Number.parseInt(env.KB_REST_RATE_LIMIT_MAX, 10);
|
|
143
|
+
if (!Number.isNaN(parsedMax)) {
|
|
144
|
+
overrides.rateLimit = {
|
|
145
|
+
...overrides.rateLimit ?? defaults.rateLimit ?? { max: 600, timeWindow: "1 minute" },
|
|
146
|
+
max: parsedMax
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (env.KB_REST_RATE_LIMIT_WINDOW) {
|
|
151
|
+
overrides.rateLimit = {
|
|
152
|
+
...overrides.rateLimit ?? defaults.rateLimit ?? { max: 600, timeWindow: "1 minute" },
|
|
153
|
+
timeWindow: env.KB_REST_RATE_LIMIT_WINDOW
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (env.KB_REST_REQUEST_TIMEOUT) {
|
|
157
|
+
const parsedTimeout = Number.parseInt(env.KB_REST_REQUEST_TIMEOUT, 10);
|
|
158
|
+
if (!Number.isNaN(parsedTimeout)) {
|
|
159
|
+
const baseTimeout = overrides.timeouts ?? defaults.timeouts;
|
|
160
|
+
overrides.timeouts = {
|
|
161
|
+
requestTimeout: parsedTimeout,
|
|
162
|
+
bodyLimit: baseTimeout?.bodyLimit ?? defaults.timeouts?.bodyLimit ?? 10485760
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (env.KB_REST_BODY_LIMIT) {
|
|
167
|
+
const parsedBodyLimit = Number.parseInt(env.KB_REST_BODY_LIMIT, 10);
|
|
168
|
+
if (!Number.isNaN(parsedBodyLimit)) {
|
|
169
|
+
const baseTimeout = overrides.timeouts ?? defaults.timeouts;
|
|
170
|
+
overrides.timeouts = {
|
|
171
|
+
requestTimeout: baseTimeout?.requestTimeout ?? defaults.timeouts?.requestTimeout ?? 3e4,
|
|
172
|
+
bodyLimit: parsedBodyLimit
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return overrides;
|
|
177
|
+
};
|
|
178
|
+
const resolved = resolveConfig({
|
|
179
|
+
defaults,
|
|
180
|
+
fileConfig: bundleConfig,
|
|
181
|
+
envMapper,
|
|
182
|
+
cliOverrides,
|
|
183
|
+
validate: (cfg) => {
|
|
184
|
+
const result = restApiConfigSchema.safeParse(cfg);
|
|
185
|
+
if (result.success) {
|
|
186
|
+
return { ok: true };
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
diagnostics: result.error.errors.map((err) => ({
|
|
191
|
+
level: "error",
|
|
192
|
+
code: "CONFIG_VALIDATION_ERROR",
|
|
193
|
+
message: `${err.path.join(".")}: ${err.message}`
|
|
194
|
+
}))
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
const validated = restApiConfigSchema.parse(resolved.value);
|
|
199
|
+
return {
|
|
200
|
+
config: validated,
|
|
201
|
+
diagnostics: resolved.diagnostics
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/studio-registry.ts
|
|
206
|
+
function manifestToRegistry(manifest) {
|
|
207
|
+
const studio = manifest.studio;
|
|
208
|
+
if (!studio) {
|
|
209
|
+
return {
|
|
210
|
+
plugin: {
|
|
211
|
+
pluginId: manifest.id,
|
|
212
|
+
displayName: manifest.display?.name,
|
|
213
|
+
widgets: [],
|
|
214
|
+
menus: [],
|
|
215
|
+
layouts: []
|
|
216
|
+
},
|
|
217
|
+
widgets: [],
|
|
218
|
+
menus: [],
|
|
219
|
+
layouts: []
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const widgets = studio.widgets ?? [];
|
|
223
|
+
const menus = studio.menus ?? [];
|
|
224
|
+
const layouts = studio.layouts ?? [];
|
|
225
|
+
const plugin = {
|
|
226
|
+
pluginId: manifest.id,
|
|
227
|
+
displayName: manifest.display?.name,
|
|
228
|
+
widgets,
|
|
229
|
+
menus,
|
|
230
|
+
layouts
|
|
231
|
+
};
|
|
232
|
+
return { plugin, widgets, menus, layouts };
|
|
233
|
+
}
|
|
234
|
+
function combineManifestsToRegistry(manifests, _registryVersion) {
|
|
235
|
+
const plugins = [];
|
|
236
|
+
for (const manifest of manifests) {
|
|
237
|
+
if (!manifest.studio) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const { plugin } = manifestToRegistry(manifest);
|
|
241
|
+
plugins.push(plugin);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
schema: "kb.studio/1",
|
|
245
|
+
schemaVersion: 1,
|
|
246
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
247
|
+
plugins
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { combineManifestsToRegistry, loadRestApiConfig, manifestToRegistry, restApiConfigSchema };
|
|
252
|
+
//# sourceMappingURL=index.js.map
|
|
253
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/schema.ts","../src/config/loader.ts","../src/studio-registry.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/rest-api-core/config/schema\n * Zod schema for REST API configuration\n */\n\nimport { z } from 'zod';\n\nexport const restApiConfigSchema = z.object({\n port: z.number().int().positive().default(5050),\n basePath: z.string().default('/api/v1'),\n apiVersion: z.string().default('1.0.0'),\n cors: z.object({\n origins: z.array(z.string()).default(['http://localhost:3000', 'http://localhost:5173']),\n allowCredentials: z.boolean().default(true),\n profile: z.enum(['dev', 'preview', 'prod']).default('dev'),\n }).default({}),\n timeouts: z.object({\n requestTimeout: z.number().int().positive().default(30000),\n bodyLimit: z.number().int().positive().default(10_485_760),\n }).optional(),\n rateLimit: z.object({\n max: z.number().int().positive().default(60),\n timeWindow: z.string().default('1 minute'),\n }).optional(),\n startup: z.object({\n maxConcurrent: z.number().int().positive().default(32),\n queueLimit: z.number().int().nonnegative().default(128),\n timeoutMs: z.number().int().positive().default(5000),\n retryAfterSeconds: z.number().int().positive().default(2),\n }).optional(),\n plugins: z.array(z.string()).default([]),\n mockMode: z.boolean().default(false),\n redis: z\n .object({\n url: z.string().min(1),\n namespace: z.string().default('kb'),\n })\n .optional(),\n events: z\n .object({\n registry: z\n .object({\n token: z.string().min(1),\n headerName: z.string().default('authorization'),\n queryParam: z.string().default('access_token'),\n })\n .optional(),\n })\n .optional(),\n http2: z\n .object({\n enabled: z.boolean().default(false),\n allowHTTP1: z.boolean().default(true), // Fallback for old clients\n })\n .optional(),\n ssl: z\n .object({\n keyPath: z.string().min(1),\n certPath: z.string().min(1),\n })\n .optional(),\n});\n\nexport type RestApiConfig = z.infer<typeof restApiConfigSchema>;\n\n","/**\n * @module @kb-labs/rest-api-core/config/loader\n * Configuration loader using loadBundle from @kb-labs/core-bundle\n */\n\nimport { loadBundle } from '@kb-labs/core-bundle';\nimport { resolveConfig, findNearestConfig, readJsonWithDiagnostics } from '@kb-labs/core-config';\nimport { restApiConfigSchema, type RestApiConfig } from './schema';\n\n/**\n * Load REST API configuration\n */\nexport async function loadRestApiConfig(\n cwd: string = process.cwd(),\n cliOverrides?: Partial<RestApiConfig>\n): Promise<{ config: RestApiConfig; diagnostics: Array<{ level: string; code: string; message: string }> }> {\n // Try to load bundle with 'restApi' product\n // Note: if 'restApi' is not registered as ProductId, we'll fall back to direct config loading\n let bundleConfig: any = {};\n \n try {\n const bundle = await loadBundle({\n cwd,\n product: 'restApi' as any, // TODO: Add 'restApi' to ProductId type\n profileId: 'default',\n });\n bundleConfig = bundle.config;\n } catch {\n // If bundle loading fails (product not registered), try direct config loading\n const { path: configPath } = await findNearestConfig({\n startDir: cwd,\n filenames: ['.kb/kb.config.json', '.kb/kb.config.yaml'],\n });\n \n if (configPath) {\n const readResult = await readJsonWithDiagnostics<any>(configPath);\n if (readResult.ok && readResult.data?.rest) {\n bundleConfig = readResult.data.rest;\n }\n }\n }\n \n const defaults: RestApiConfig = {\n port: 5050,\n basePath: '/api/v1',\n apiVersion: '1.0.0',\n cors: {\n origins: ['http://localhost:3000', 'http://localhost:5173'],\n allowCredentials: true,\n profile: 'dev',\n },\n timeouts: {\n requestTimeout: 30000,\n bodyLimit: 10_485_760,\n },\n rateLimit: {\n // 600/min for dev (dashboard needs ~10 requests per page load)\n // Override with KB_REST_RATE_LIMIT_MAX=60 for production public API\n max: 600,\n timeWindow: '1 minute',\n },\n plugins: [],\n mockMode: false,\n };\n\n const envMapper = (env: NodeJS.ProcessEnv): Partial<RestApiConfig> => {\n const overrides: Partial<RestApiConfig> = {};\n\n if (env.KB_REST_PORT) {\n const parsedPort = Number.parseInt(env.KB_REST_PORT, 10);\n if (!Number.isNaN(parsedPort)) {\n overrides.port = parsedPort;\n }\n }\n\n if (env.KB_REST_BASE_PATH) {\n overrides.basePath = env.KB_REST_BASE_PATH;\n }\n\n if (env.KB_REST_API_VERSION) {\n overrides.apiVersion = env.KB_REST_API_VERSION;\n }\n\n if (env.KB_REST_CORS_ORIGINS) {\n const origins = env.KB_REST_CORS_ORIGINS.split(',').map(origin => origin.trim()).filter(Boolean);\n overrides.cors = {\n ...(overrides.cors ?? defaults.cors),\n origins,\n };\n }\n\n if (env.KB_REST_CORS_PROFILE) {\n overrides.cors = {\n ...(overrides.cors ?? defaults.cors),\n profile: env.KB_REST_CORS_PROFILE as RestApiConfig['cors']['profile'],\n };\n }\n\n if (env.KB_REST_MOCK_MODE === 'true' || env.KB_REST_MOCK_MODE === '1') {\n overrides.mockMode = true;\n }\n\n if (env.KB_REST_REDIS_URL) {\n overrides.redis = {\n ...(overrides.redis ?? {}),\n url: env.KB_REST_REDIS_URL,\n namespace: overrides.redis?.namespace ?? defaults.redis?.namespace ?? 'kb',\n } as RestApiConfig['redis'];\n }\n\n if (env.KB_REST_REDIS_NAMESPACE) {\n overrides.redis = {\n ...(overrides.redis ?? (env.KB_REST_REDIS_URL ? { url: env.KB_REST_REDIS_URL } : defaults.redis ?? {})),\n namespace: env.KB_REST_REDIS_NAMESPACE,\n } as RestApiConfig['redis'];\n }\n\n if (env.KB_REST_RATE_LIMIT_MAX) {\n const parsedMax = Number.parseInt(env.KB_REST_RATE_LIMIT_MAX, 10);\n if (!Number.isNaN(parsedMax)) {\n overrides.rateLimit = {\n ...(overrides.rateLimit ?? defaults.rateLimit ?? { max: 600, timeWindow: '1 minute' }),\n max: parsedMax,\n };\n }\n }\n\n if (env.KB_REST_RATE_LIMIT_WINDOW) {\n overrides.rateLimit = {\n ...(overrides.rateLimit ?? defaults.rateLimit ?? { max: 600, timeWindow: '1 minute' }),\n timeWindow: env.KB_REST_RATE_LIMIT_WINDOW,\n };\n }\n\n if (env.KB_REST_REQUEST_TIMEOUT) {\n const parsedTimeout = Number.parseInt(env.KB_REST_REQUEST_TIMEOUT, 10);\n if (!Number.isNaN(parsedTimeout)) {\n const baseTimeout = overrides.timeouts ?? defaults.timeouts;\n overrides.timeouts = {\n requestTimeout: parsedTimeout,\n bodyLimit: baseTimeout?.bodyLimit ?? defaults.timeouts?.bodyLimit ?? 10_485_760,\n };\n }\n }\n\n if (env.KB_REST_BODY_LIMIT) {\n const parsedBodyLimit = Number.parseInt(env.KB_REST_BODY_LIMIT, 10);\n if (!Number.isNaN(parsedBodyLimit)) {\n const baseTimeout = overrides.timeouts ?? defaults.timeouts;\n overrides.timeouts = {\n requestTimeout: baseTimeout?.requestTimeout ?? defaults.timeouts?.requestTimeout ?? 30000,\n bodyLimit: parsedBodyLimit,\n };\n }\n }\n\n return overrides;\n };\n \n // Resolve configuration: defaults → bundleConfig → env → cliOverrides\n const resolved = resolveConfig<RestApiConfig>({\n defaults,\n fileConfig: bundleConfig,\n envMapper,\n cliOverrides,\n validate: (cfg) => {\n const result = restApiConfigSchema.safeParse(cfg);\n if (result.success) {\n return { ok: true };\n }\n return {\n ok: false,\n diagnostics: result.error.errors.map((err) => ({\n level: 'error' as const,\n code: 'CONFIG_VALIDATION_ERROR',\n message: `${err.path.join('.')}: ${err.message}`,\n })),\n };\n },\n });\n \n // Parse with Zod to get properly typed config\n const validated = restApiConfigSchema.parse(resolved.value);\n \n return {\n config: validated,\n diagnostics: resolved.diagnostics,\n };\n}\n\n","/**\n * @module @kb-labs/rest-api-core/studio-registry\n *\n * Transforms ManifestV3 studio sections into StudioRegistry format.\n * REST API uses this to provide pre-computed registry data to Studio frontend.\n */\n\nimport type { ManifestV3 } from '@kb-labs/plugin-contracts';\nimport type {\n StudioRegistry,\n StudioPluginEntry,\n StudioWidgetDecl,\n StudioMenuDecl,\n StudioLayoutDecl,\n} from '@kb-labs/rest-api-contracts';\n\n/**\n * Convert a single ManifestV3 to StudioRegistry entries.\n */\nexport function manifestToRegistry(manifest: ManifestV3): {\n plugin: StudioPluginEntry;\n widgets: StudioWidgetDecl[];\n menus: StudioMenuDecl[];\n layouts: StudioLayoutDecl[];\n} {\n const studio = manifest.studio;\n if (!studio) {\n return {\n plugin: {\n pluginId: manifest.id,\n displayName: manifest.display?.name,\n widgets: [],\n menus: [],\n layouts: [],\n },\n widgets: [],\n menus: [],\n layouts: [],\n };\n }\n\n // Widgets, menus, layouts are already in correct format from manifest.studio\n const widgets = studio.widgets ?? [];\n const menus = studio.menus ?? [];\n const layouts = studio.layouts ?? [];\n\n const plugin: StudioPluginEntry = {\n pluginId: manifest.id,\n displayName: manifest.display?.name,\n widgets,\n menus,\n layouts,\n };\n\n return { plugin, widgets, menus, layouts };\n}\n\n/**\n * Combine multiple manifests into a single StudioRegistry.\n */\nexport function combineManifestsToRegistry(\n manifests: ManifestV3[],\n _registryVersion?: string\n): StudioRegistry {\n const plugins: StudioPluginEntry[] = [];\n\n for (const manifest of manifests) {\n if (!manifest.studio) {continue;}\n\n const { plugin } = manifestToRegistry(manifest);\n plugins.push(plugin);\n }\n\n return {\n schema: 'kb.studio/1',\n schemaVersion: 1,\n generatedAt: new Date().toISOString(),\n plugins,\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/rest-api-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core REST API functionality for KB Labs, including request handling and routing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"zod": "^3.23.8",
|
|
22
|
+
"@kb-labs/core-bundle": "link:../../../kb-labs-core/packages/core-bundle",
|
|
23
|
+
"@kb-labs/core-config": "link:../../../kb-labs-core/packages/core-config",
|
|
24
|
+
"@kb-labs/plugin-contracts": "link:../../../kb-labs-plugin/packages/plugin-contracts",
|
|
25
|
+
"@kb-labs/rest-api-contracts": "link:../rest-api-contracts"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"vitest": "^3.2.4",
|
|
29
|
+
"@kb-labs/devkit": "link:../../../kb-labs-devkit",
|
|
30
|
+
"tsup": "^8.5.0",
|
|
31
|
+
"@types/node": "^24.3.3",
|
|
32
|
+
"rimraf": "^6.0.1",
|
|
33
|
+
"typescript": "^5.6.3"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.0.0",
|
|
37
|
+
"pnpm": ">=9.0.0"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"clean": "rimraf dist",
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"type-check": "tsc --noEmit",
|
|
47
|
+
"lint": "eslint .",
|
|
48
|
+
"lint:fix": "eslint . --fix",
|
|
49
|
+
"test": "vitest run --passWithNoTests -c ../../vitest.config.ts",
|
|
50
|
+
"test:watch": "vitest --passWithNoTests -c ../../vitest.config.ts"
|
|
51
|
+
}
|
|
52
|
+
}
|