@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 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
@@ -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
+ }