@gmickel/gno 0.6.1 → 0.7.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/package.json +4 -4
- package/src/cli/commands/collection/add.ts +27 -66
- package/src/cli/commands/collection/remove.ts +20 -39
- package/src/cli/commands/embed.ts +46 -18
- package/src/collection/add.ts +113 -0
- package/src/collection/index.ts +17 -0
- package/src/collection/remove.ts +65 -0
- package/src/collection/types.ts +70 -0
- package/src/serve/config-sync.ts +139 -0
- package/src/serve/jobs.ts +172 -0
- package/src/serve/routes/api.ts +432 -0
- package/src/serve/security.ts +84 -0
- package/src/serve/server.ts +126 -15
package/src/serve/server.ts
CHANGED
|
@@ -12,13 +12,19 @@ import { SqliteAdapter } from '../store/sqlite/adapter';
|
|
|
12
12
|
import { createServerContext, disposeServerContext } from './context';
|
|
13
13
|
// HTML import - Bun handles bundling TSX/CSS automatically via routes
|
|
14
14
|
import homepage from './public/index.html';
|
|
15
|
+
import type { ContextHolder } from './routes/api';
|
|
15
16
|
import {
|
|
16
17
|
handleAsk,
|
|
17
18
|
handleCapabilities,
|
|
18
19
|
handleCollections,
|
|
20
|
+
handleCreateCollection,
|
|
21
|
+
handleCreateDoc,
|
|
22
|
+
handleDeactivateDoc,
|
|
23
|
+
handleDeleteCollection,
|
|
19
24
|
handleDoc,
|
|
20
25
|
handleDocs,
|
|
21
26
|
handleHealth,
|
|
27
|
+
handleJob,
|
|
22
28
|
handleModelPull,
|
|
23
29
|
handleModelStatus,
|
|
24
30
|
handlePresets,
|
|
@@ -26,7 +32,9 @@ import {
|
|
|
26
32
|
handleSearch,
|
|
27
33
|
handleSetPreset,
|
|
28
34
|
handleStatus,
|
|
35
|
+
handleSync,
|
|
29
36
|
} from './routes/api';
|
|
37
|
+
import { forbiddenResponse, isRequestAllowed } from './security';
|
|
30
38
|
|
|
31
39
|
export interface ServeOptions {
|
|
32
40
|
/** Port to listen on (default: 3000) */
|
|
@@ -127,9 +135,21 @@ export async function startServer(
|
|
|
127
135
|
return { success: false, error: openResult.error.message };
|
|
128
136
|
}
|
|
129
137
|
|
|
138
|
+
// Sync collections and contexts from config to DB (same as CLI initStore)
|
|
139
|
+
const syncCollResult = await store.syncCollections(config.collections);
|
|
140
|
+
if (!syncCollResult.ok) {
|
|
141
|
+
await store.close();
|
|
142
|
+
return { success: false, error: syncCollResult.error.message };
|
|
143
|
+
}
|
|
144
|
+
const syncCtxResult = await store.syncContexts(config.contexts ?? []);
|
|
145
|
+
if (!syncCtxResult.ok) {
|
|
146
|
+
await store.close();
|
|
147
|
+
return { success: false, error: syncCtxResult.error.message };
|
|
148
|
+
}
|
|
149
|
+
|
|
130
150
|
// Create server context with LLM ports for hybrid search and AI answers
|
|
131
151
|
// Use holder pattern to allow hot-reloading presets
|
|
132
|
-
const ctxHolder = {
|
|
152
|
+
const ctxHolder: ContextHolder = {
|
|
133
153
|
current: await createServerContext(store, config),
|
|
134
154
|
config, // Keep original config for reloading
|
|
135
155
|
};
|
|
@@ -158,7 +178,7 @@ export async function startServer(
|
|
|
158
178
|
// Enable development mode for HMR and console logging
|
|
159
179
|
development: isDev,
|
|
160
180
|
|
|
161
|
-
//
|
|
181
|
+
// Static routes - Bun handles HTML bundling and /_bun/* assets automatically
|
|
162
182
|
routes: {
|
|
163
183
|
// SPA routes - all serve the same React app
|
|
164
184
|
'/': homepage,
|
|
@@ -167,7 +187,7 @@ export async function startServer(
|
|
|
167
187
|
'/doc': homepage,
|
|
168
188
|
'/ask': homepage,
|
|
169
189
|
|
|
170
|
-
// API routes
|
|
190
|
+
// API routes with CSRF protection wrapper
|
|
171
191
|
'/api/health': {
|
|
172
192
|
GET: () => withSecurityHeaders(handleHealth(), isDev),
|
|
173
193
|
},
|
|
@@ -178,12 +198,56 @@ export async function startServer(
|
|
|
178
198
|
'/api/collections': {
|
|
179
199
|
GET: async () =>
|
|
180
200
|
withSecurityHeaders(await handleCollections(store), isDev),
|
|
201
|
+
POST: async (req: Request) => {
|
|
202
|
+
if (!isRequestAllowed(req, port)) {
|
|
203
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
204
|
+
}
|
|
205
|
+
return withSecurityHeaders(
|
|
206
|
+
await handleCreateCollection(ctxHolder, store, req),
|
|
207
|
+
isDev
|
|
208
|
+
);
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
'/api/sync': {
|
|
212
|
+
POST: async (req: Request) => {
|
|
213
|
+
if (!isRequestAllowed(req, port)) {
|
|
214
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
215
|
+
}
|
|
216
|
+
return withSecurityHeaders(
|
|
217
|
+
await handleSync(ctxHolder, store, req),
|
|
218
|
+
isDev
|
|
219
|
+
);
|
|
220
|
+
},
|
|
181
221
|
},
|
|
182
222
|
'/api/docs': {
|
|
183
223
|
GET: async (req: Request) => {
|
|
184
224
|
const url = new URL(req.url);
|
|
185
225
|
return withSecurityHeaders(await handleDocs(store, url), isDev);
|
|
186
226
|
},
|
|
227
|
+
POST: async (req: Request) => {
|
|
228
|
+
if (!isRequestAllowed(req, port)) {
|
|
229
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
230
|
+
}
|
|
231
|
+
return withSecurityHeaders(
|
|
232
|
+
await handleCreateDoc(ctxHolder, store, req),
|
|
233
|
+
isDev
|
|
234
|
+
);
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
'/api/docs/:id/deactivate': {
|
|
238
|
+
POST: async (req: Request) => {
|
|
239
|
+
if (!isRequestAllowed(req, port)) {
|
|
240
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
241
|
+
}
|
|
242
|
+
const url = new URL(req.url);
|
|
243
|
+
// Extract id from /api/docs/:id/deactivate
|
|
244
|
+
const parts = url.pathname.split('/');
|
|
245
|
+
const id = decodeURIComponent(parts[3] || '');
|
|
246
|
+
return withSecurityHeaders(
|
|
247
|
+
await handleDeactivateDoc(store, id),
|
|
248
|
+
isDev
|
|
249
|
+
);
|
|
250
|
+
},
|
|
187
251
|
},
|
|
188
252
|
'/api/doc': {
|
|
189
253
|
GET: async (req: Request) => {
|
|
@@ -192,19 +256,34 @@ export async function startServer(
|
|
|
192
256
|
},
|
|
193
257
|
},
|
|
194
258
|
'/api/search': {
|
|
195
|
-
POST: async (req: Request) =>
|
|
196
|
-
|
|
259
|
+
POST: async (req: Request) => {
|
|
260
|
+
if (!isRequestAllowed(req, port)) {
|
|
261
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
262
|
+
}
|
|
263
|
+
return withSecurityHeaders(await handleSearch(store, req), isDev);
|
|
264
|
+
},
|
|
197
265
|
},
|
|
198
266
|
'/api/query': {
|
|
199
|
-
POST: async (req: Request) =>
|
|
200
|
-
|
|
267
|
+
POST: async (req: Request) => {
|
|
268
|
+
if (!isRequestAllowed(req, port)) {
|
|
269
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
270
|
+
}
|
|
271
|
+
return withSecurityHeaders(
|
|
201
272
|
await handleQuery(ctxHolder.current, req),
|
|
202
273
|
isDev
|
|
203
|
-
)
|
|
274
|
+
);
|
|
275
|
+
},
|
|
204
276
|
},
|
|
205
277
|
'/api/ask': {
|
|
206
|
-
POST: async (req: Request) =>
|
|
207
|
-
|
|
278
|
+
POST: async (req: Request) => {
|
|
279
|
+
if (!isRequestAllowed(req, port)) {
|
|
280
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
281
|
+
}
|
|
282
|
+
return withSecurityHeaders(
|
|
283
|
+
await handleAsk(ctxHolder.current, req),
|
|
284
|
+
isDev
|
|
285
|
+
);
|
|
286
|
+
},
|
|
208
287
|
},
|
|
209
288
|
'/api/capabilities': {
|
|
210
289
|
GET: () =>
|
|
@@ -213,18 +292,50 @@ export async function startServer(
|
|
|
213
292
|
'/api/presets': {
|
|
214
293
|
GET: () =>
|
|
215
294
|
withSecurityHeaders(handlePresets(ctxHolder.current), isDev),
|
|
216
|
-
POST: async (req: Request) =>
|
|
217
|
-
|
|
295
|
+
POST: async (req: Request) => {
|
|
296
|
+
if (!isRequestAllowed(req, port)) {
|
|
297
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
298
|
+
}
|
|
299
|
+
return withSecurityHeaders(
|
|
300
|
+
await handleSetPreset(ctxHolder, req),
|
|
301
|
+
isDev
|
|
302
|
+
);
|
|
303
|
+
},
|
|
218
304
|
},
|
|
219
305
|
'/api/models/status': {
|
|
220
306
|
GET: () => withSecurityHeaders(handleModelStatus(), isDev),
|
|
221
307
|
},
|
|
222
308
|
'/api/models/pull': {
|
|
223
|
-
POST: () =>
|
|
309
|
+
POST: (req: Request) => {
|
|
310
|
+
if (!isRequestAllowed(req, port)) {
|
|
311
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
312
|
+
}
|
|
313
|
+
return withSecurityHeaders(handleModelPull(ctxHolder), isDev);
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
'/api/jobs/:id': {
|
|
317
|
+
GET: (req: Request) => {
|
|
318
|
+
const url = new URL(req.url);
|
|
319
|
+
const id = decodeURIComponent(url.pathname.split('/').pop() || '');
|
|
320
|
+
return withSecurityHeaders(handleJob(id), isDev);
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
'/api/collections/:name': {
|
|
324
|
+
DELETE: async (req: Request) => {
|
|
325
|
+
if (!isRequestAllowed(req, port)) {
|
|
326
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
327
|
+
}
|
|
328
|
+
const url = new URL(req.url);
|
|
329
|
+
const name = decodeURIComponent(
|
|
330
|
+
url.pathname.split('/').pop() || ''
|
|
331
|
+
);
|
|
332
|
+
return withSecurityHeaders(
|
|
333
|
+
await handleDeleteCollection(ctxHolder, store, name),
|
|
334
|
+
isDev
|
|
335
|
+
);
|
|
336
|
+
},
|
|
224
337
|
},
|
|
225
338
|
},
|
|
226
|
-
|
|
227
|
-
// No fetch fallback - let Bun handle /_bun/* assets and return 404 for others
|
|
228
339
|
});
|
|
229
340
|
} catch (e) {
|
|
230
341
|
await store.close();
|