@harperfast/harper 5.2.0-beta.4 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/backup.ts +169 -0
- package/bin/cliOperations.ts +92 -73
- package/bin/harper.ts +25 -6
- package/components/Scope.ts +52 -6
- package/components/componentLoader.ts +107 -9
- package/components/mcp/toolRegistry.ts +10 -0
- package/components/mcp/tools/application.ts +12 -5
- package/components/mcp/tools/operations.ts +3 -0
- package/components/mcp/tools/schemas/operationDescriptions.ts +3 -0
- package/components/mcp/tools/schemas/operations.ts +9 -0
- package/components/operations.js +1 -0
- package/components/operationsValidation.js +32 -2
- package/components/scopeMount.ts +150 -0
- package/config-root.schema.json +4 -0
- package/dataLayer/backupManifest.ts +102 -0
- package/dataLayer/blobBackup.ts +286 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +43 -0
- package/dataLayer/restoreMarker.ts +276 -0
- package/dataLayer/rocksdbBackup.ts +1100 -0
- package/dist/bin/backup.d.ts +9 -0
- package/dist/bin/backup.js +192 -0
- package/dist/bin/backup.js.map +1 -0
- package/dist/bin/cliOperations.d.ts +13 -0
- package/dist/bin/cliOperations.js +89 -70
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/harper.d.ts +7 -0
- package/dist/bin/harper.js +27 -6
- package/dist/bin/harper.js.map +1 -1
- package/dist/components/Scope.d.ts +37 -1
- package/dist/components/Scope.js +48 -6
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -6
- package/dist/components/componentLoader.js +91 -8
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +7 -0
- package/dist/components/mcp/toolRegistry.js +10 -0
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.js +11 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +3 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -0
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +9 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/operations.js +2 -0
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +34 -2
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/scopeMount.d.ts +86 -0
- package/dist/components/scopeMount.js +131 -0
- package/dist/components/scopeMount.js.map +1 -0
- package/dist/dataLayer/backupManifest.d.ts +26 -0
- package/dist/dataLayer/backupManifest.js +97 -0
- package/dist/dataLayer/backupManifest.js.map +1 -0
- package/dist/dataLayer/blobBackup.d.ts +87 -0
- package/dist/dataLayer/blobBackup.js +282 -0
- package/dist/dataLayer/blobBackup.js.map +1 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +3 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js +33 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +122 -0
- package/dist/dataLayer/restoreMarker.js +261 -0
- package/dist/dataLayer/restoreMarker.js.map +1 -0
- package/dist/dataLayer/rocksdbBackup.d.ts +127 -0
- package/dist/dataLayer/rocksdbBackup.js +1039 -0
- package/dist/dataLayer/rocksdbBackup.js.map +1 -0
- package/dist/resources/DatabaseTransaction.js +0 -6
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +0 -3
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Table.js +22 -22
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/blob.d.ts +8 -0
- package/dist/resources/blob.js +16 -7
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/databases.d.ts +42 -1
- package/dist/resources/databases.js +276 -40
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/transaction.js +0 -3
- package/dist/resources/transaction.js.map +1 -1
- package/dist/server/REST.js +25 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +15 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +7 -1
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobProcess.js +20 -1
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobRunner.js +10 -0
- package/dist/server/jobs/jobRunner.js.map +1 -1
- package/dist/server/jobs/jobs.js +11 -0
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +10 -1
- package/dist/server/middlewareChain.js +81 -21
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +8 -4
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +11 -0
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/static.js +11 -3
- package/dist/server/static.js.map +1 -1
- package/dist/utility/OperationFunctionCaller.js +25 -6
- package/dist/utility/OperationFunctionCaller.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +11 -1
- package/dist/utility/hdbTerms.js +11 -1
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +40 -0
- package/dist/utility/logging/harper_logger.js +746 -14
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/operation_authorization.js +13 -3
- package/dist/utility/operation_authorization.js.map +1 -1
- package/npm-shrinkwrap.json +3 -2
- package/package.json +4 -1
- package/resources/DESIGN.md +2 -0
- package/resources/DatabaseTransaction.ts +0 -3
- package/resources/ResourceInterface.ts +0 -3
- package/resources/Table.ts +20 -20
- package/resources/blob.ts +16 -6
- package/resources/databases.ts +272 -42
- package/resources/transaction.ts +0 -3
- package/server/DESIGN.md +16 -0
- package/server/REST.ts +25 -9
- package/server/fastifyRoutes.ts +20 -1
- package/server/itc/serverHandlers.js +7 -1
- package/server/jobs/jobProcess.ts +18 -1
- package/server/jobs/jobRunner.ts +10 -0
- package/server/jobs/jobs.ts +11 -0
- package/server/middlewareChain.ts +79 -20
- package/server/serverHelpers/serverHandlers.js +8 -4
- package/server/serverHelpers/serverUtilities.ts +19 -0
- package/server/static.ts +12 -3
- package/static/defaultConfig.yaml +1 -0
- package/studio/web/assets/{Chat-DK3GlWEb.js → Chat-DoVWScmq.js} +2 -2
- package/studio/web/assets/{Chat-DK3GlWEb.js.map → Chat-DoVWScmq.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-fBcC1Ew_.js → FloatingChat-UZ2NsUOZ.js} +4 -4
- package/studio/web/assets/{FloatingChat-fBcC1Ew_.js.map → FloatingChat-UZ2NsUOZ.js.map} +1 -1
- package/studio/web/assets/{apiToken-DJo1nakA.js → apiToken-BUI_04o7.js} +2 -2
- package/studio/web/assets/{apiToken-DJo1nakA.js.map → apiToken-BUI_04o7.js.map} +1 -1
- package/studio/web/assets/{applications-BDfH8urd.js → applications-D03NA7wW.js} +2 -2
- package/studio/web/assets/{applications-BDfH8urd.js.map → applications-D03NA7wW.js.map} +1 -1
- package/studio/web/assets/{index-BHo3c2Gk.js → index-Bh_CNAHr.js} +6 -6
- package/studio/web/assets/index-Bh_CNAHr.js.map +1 -0
- package/studio/web/assets/{index.lazy-BTo0y6UM.js → index.lazy-Dx3MpyDC.js} +4 -4
- package/studio/web/assets/{index.lazy-BTo0y6UM.js.map → index.lazy-Dx3MpyDC.js.map} +1 -1
- package/studio/web/assets/{notifications-CMxvWNnz.js → notifications-0edoFTsb.js} +2 -2
- package/studio/web/assets/{notifications-CMxvWNnz.js.map → notifications-0edoFTsb.js.map} +1 -1
- package/studio/web/assets/{notifications-D3GoB26g.js → notifications-CwKhipK7.js} +2 -2
- package/studio/web/assets/{notifications-D3GoB26g.js.map → notifications-CwKhipK7.js.map} +1 -1
- package/studio/web/assets/{profile-Doj5FVDE.js → profile-DUfEPQtx.js} +2 -2
- package/studio/web/assets/{profile-Doj5FVDE.js.map → profile-DUfEPQtx.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-yinsqJy0.js → setComponentFile-DMPo4UjC.js} +2 -2
- package/studio/web/assets/{setComponentFile-yinsqJy0.js.map → setComponentFile-DMPo4UjC.js.map} +1 -1
- package/studio/web/assets/{setup-DJwR0BHd.js → setup-B56Oz1_u.js} +2 -2
- package/studio/web/assets/{setup-DJwR0BHd.js.map → setup-B56Oz1_u.js.map} +1 -1
- package/studio/web/assets/{status-Br_AbsJs.js → status-BAod7p3o.js} +2 -2
- package/studio/web/assets/{status-Br_AbsJs.js.map → status-BAod7p3o.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-02XH5sVf.js → swagger-ui-react-lQrBxfwM.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-02XH5sVf.js.map → swagger-ui-react-lQrBxfwM.js.map} +1 -1
- package/studio/web/assets/{tsMode-D1DMKY7h.js → tsMode-CrHCRjTK.js} +2 -2
- package/studio/web/assets/{tsMode-D1DMKY7h.js.map → tsMode-CrHCRjTK.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-DtRblRgw.js → useEntityRestURL-DoaBMEvU.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-DtRblRgw.js.map → useEntityRestURL-DoaBMEvU.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/OperationFunctionCaller.ts +24 -3
- package/utility/hdbTerms.ts +11 -1
- package/utility/logging/harper_logger.ts +729 -15
- package/utility/operation_authorization.ts +31 -3
- package/studio/web/assets/index-BHo3c2Gk.js.map +0 -1
|
@@ -151,17 +151,43 @@ export function normalizeUrlPath(urlPath: string | undefined): string | undefine
|
|
|
151
151
|
return urlPath.length <= 1 ? undefined : urlPath;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Extracts the hostname from a Host/`:authority` header value: the port is dropped and an IPv6
|
|
156
|
+
* literal is unwrapped from its brackets ('[::1]:9926' -> '::1'), so a bracket-less configured
|
|
157
|
+
* host can match. Lowercased because hostnames are case-insensitive (RFC 4343) — a configured
|
|
158
|
+
* 'API.example.com' must match the 'api.example.com' a client actually sends. A trailing dot
|
|
159
|
+
* (the absolute-FQDN form some resolvers/clients emit, e.g. 'api.example.com.') is stripped —
|
|
160
|
+
* it names the same origin per RFC 1035.
|
|
161
|
+
*/
|
|
162
|
+
export function hostnameFromHeader(hostHeader: string): string {
|
|
163
|
+
if (hostHeader.startsWith('[')) {
|
|
164
|
+
const end = hostHeader.indexOf(']');
|
|
165
|
+
if (end !== -1) return hostHeader.slice(1, end).toLowerCase();
|
|
166
|
+
}
|
|
167
|
+
const colon = hostHeader.indexOf(':');
|
|
168
|
+
const hostname = (colon === -1 ? hostHeader : hostHeader.slice(0, colon)).toLowerCase();
|
|
169
|
+
return hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Reads the request's authority for host matching: `request.host` (Harper's `Request` class)
|
|
174
|
+
* already resolves this correctly for both HTTP/1 (`Host` header) and HTTP/2 (`:authority`
|
|
175
|
+
* pseudo-header — HTTP/2 clients don't send `Host` at all). The header fallbacks below only
|
|
176
|
+
* cover request-like objects that don't carry that getter (e.g. bare fakes in tests).
|
|
177
|
+
*/
|
|
178
|
+
function requestAuthority(request: any): string {
|
|
179
|
+
return request.host ?? request.headers?.asObject?.[':authority'] ?? request.headers?.asObject?.host ?? '';
|
|
180
|
+
}
|
|
181
|
+
|
|
154
182
|
/**
|
|
155
183
|
* Returns true when `request` satisfies the route's host and urlPath constraints.
|
|
156
184
|
* urlPath matching is prefix-based and segment-boundary-aware:
|
|
157
185
|
* '/api' matches '/api' and '/api/foo' but NOT '/api2'.
|
|
158
|
-
* Trailing slashes on `route.urlPath` are ignored.
|
|
186
|
+
* Trailing slashes on `route.urlPath` are ignored. Host matching ignores the port and case.
|
|
159
187
|
*/
|
|
160
188
|
export function matchesRoute(request: any, route: { host?: string; urlPath?: string }): boolean {
|
|
161
189
|
if (route.host) {
|
|
162
|
-
|
|
163
|
-
const requestHost = hostHeader.split(':')[0];
|
|
164
|
-
if (requestHost !== route.host) return false;
|
|
190
|
+
if (hostnameFromHeader(requestAuthority(request)) !== route.host.toLowerCase()) return false;
|
|
165
191
|
}
|
|
166
192
|
const urlPath = normalizeUrlPath(route.urlPath);
|
|
167
193
|
if (urlPath) {
|
|
@@ -230,10 +256,18 @@ export function stripPrefix(request: any, prefix: string): any {
|
|
|
230
256
|
* and `describeChains` reports it, so the observed order can never drift from the served one.
|
|
231
257
|
*/
|
|
232
258
|
export function resolveRoutedChains(portEntries: HttpEntry[], onCycle?: () => void): ResolvedChain[] {
|
|
233
|
-
// Global name registry
|
|
234
|
-
|
|
259
|
+
// Global name registry, but restricted to unmounted (no host/urlPath) entries — e.g.
|
|
260
|
+
// `authentication` — so it only ever supplies dependencies that are meant to apply everywhere.
|
|
261
|
+
// A mounted route's own plugins are resolved separately per-group below; if this stayed global
|
|
262
|
+
// across ALL routes (mounted or not), two applications mounted at different hosts/paths that
|
|
263
|
+
// each happen to register a same-named plugin (e.g. both enable `rest`) would resolve `after:
|
|
264
|
+
// 'rest'` to whichever one registered first — silently splicing one application's handler into
|
|
265
|
+
// another's request chain (review finding).
|
|
266
|
+
const globalNameToEntry = new Map<string, HttpEntry>();
|
|
235
267
|
for (const entry of portEntries) {
|
|
236
|
-
if (entry.name && !
|
|
268
|
+
if (entry.name && !entry.host && !entry.urlPath && !globalNameToEntry.has(entry.name)) {
|
|
269
|
+
globalNameToEntry.set(entry.name, entry);
|
|
270
|
+
}
|
|
237
271
|
}
|
|
238
272
|
|
|
239
273
|
// Group entries by (host, normalized urlPath) so that '/api' and '/api/' coalesce.
|
|
@@ -249,11 +283,24 @@ export function resolveRoutedChains(portEntries: HttpEntry[], onCycle?: () => vo
|
|
|
249
283
|
const defaultGroup = routeGroups.find((g) => !g.host && !g.urlPath);
|
|
250
284
|
const subRouteGroups = routeGroups.filter((g) => g.host || g.urlPath);
|
|
251
285
|
|
|
252
|
-
const subRoutes: ResolvedChain[] = subRouteGroups.map((group) =>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
286
|
+
const subRoutes: ResolvedChain[] = subRouteGroups.map((group) => {
|
|
287
|
+
// This route's own plugins take priority over the global (unmounted) registry for the
|
|
288
|
+
// same name, so a same-named handler local to this mount always wins over an unrelated
|
|
289
|
+
// unmounted one — but the lookup never reaches into another mounted route's plugins.
|
|
290
|
+
const localNameToEntry = new Map(globalNameToEntry);
|
|
291
|
+
const seenInGroup = new Set<string>();
|
|
292
|
+
for (const entry of group.entries) {
|
|
293
|
+
if (entry.name && !seenInGroup.has(entry.name)) {
|
|
294
|
+
seenInGroup.add(entry.name);
|
|
295
|
+
localNameToEntry.set(entry.name, entry);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
host: group.host,
|
|
300
|
+
urlPath: group.urlPath,
|
|
301
|
+
order: topoSort(resolveDeps(group.entries, localNameToEntry), onCycle),
|
|
302
|
+
};
|
|
303
|
+
});
|
|
257
304
|
|
|
258
305
|
subRoutes.sort((a, b) => {
|
|
259
306
|
const aSpec = (a.host ? 2 : 0) + (a.urlPath ? 1 : 0);
|
|
@@ -274,23 +321,35 @@ export function buildRoutedChain(
|
|
|
274
321
|
const resolved = resolveRoutedChains(portEntries, onCycle);
|
|
275
322
|
// resolveRoutedChains returns sub-routes (dispatch order) followed by the default route last.
|
|
276
323
|
const defaultChain = buildLinearChain(resolved[resolved.length - 1].order, fallback);
|
|
324
|
+
// hostLower/urlPathWithSlash are computed once here rather than per request/per candidate:
|
|
325
|
+
// `route.urlPath` is already normalized by resolveRoutedChains, but `route.host` isn't
|
|
326
|
+
// (plugin-config hosts via routeFor aren't pre-lowercased), and matchesRoute's generic form
|
|
327
|
+
// would otherwise re-run toLowerCase()/a regex/a concat on every probe of every request —
|
|
328
|
+
// this dispatch is the mainline path once a port has any mounted route.
|
|
277
329
|
const subRouteChains = resolved.slice(0, -1).map((route) => ({
|
|
278
|
-
|
|
330
|
+
hostLower: route.host?.toLowerCase(),
|
|
279
331
|
urlPath: route.urlPath,
|
|
332
|
+
urlPathWithSlash: route.urlPath ? route.urlPath + '/' : undefined,
|
|
280
333
|
chain: buildLinearChain(route.order, fallback),
|
|
281
334
|
}));
|
|
335
|
+
const anyHostRoute = subRouteChains.some((route) => route.hostLower);
|
|
282
336
|
|
|
283
337
|
return function dispatch(...args: any[]) {
|
|
284
338
|
const request = args[requestArgIndex];
|
|
339
|
+
// The request's authority is the same for every candidate probed below, so it's
|
|
340
|
+
// extracted once per request rather than once per candidate — and skipped entirely
|
|
341
|
+
// when no mounted route in this chain matches on host at all.
|
|
342
|
+
const requestHost = anyHostRoute ? hostnameFromHeader(requestAuthority(request)) : undefined;
|
|
343
|
+
const pathname: string = request.pathname ?? '/';
|
|
285
344
|
for (const route of subRouteChains) {
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return route.chain(...args);
|
|
345
|
+
if (route.hostLower && requestHost !== route.hostLower) continue;
|
|
346
|
+
if (route.urlPath) {
|
|
347
|
+
if (pathname !== route.urlPath && !pathname.startsWith(route.urlPathWithSlash as string)) continue;
|
|
348
|
+
const newArgs = args.slice();
|
|
349
|
+
newArgs[requestArgIndex] = stripPrefix(request, route.urlPath);
|
|
350
|
+
return route.chain(...newArgs);
|
|
293
351
|
}
|
|
352
|
+
return route.chain(...args);
|
|
294
353
|
}
|
|
295
354
|
return defaultChain(...args);
|
|
296
355
|
};
|
|
@@ -174,9 +174,12 @@ async function handlePostRequest(req, res, _bypassAuth = false) {
|
|
|
174
174
|
res.header(name, value);
|
|
175
175
|
}
|
|
176
176
|
// fastify-compress has one job. I don't know why it can't do it. So we compress here to
|
|
177
|
-
// handle the case of returning a stream. Streams marked
|
|
178
|
-
//
|
|
179
|
-
|
|
177
|
+
// handle the case of returning a stream. Streams marked `noCompression` opt out (e.g.
|
|
178
|
+
// RocksDB get_backup tars: the binding gzips when requested; compressing here would
|
|
179
|
+
// mislabel a gzip:false tar or double-compress a gzip:true one). Streams marked
|
|
180
|
+
// `preCompressed` (e.g. a stored .tar.gz payload) are passed through as-is —
|
|
181
|
+
// recompressing them wastes CPU for zero gain.
|
|
182
|
+
if (req.headers['accept-encoding']?.includes('gzip') && !result.noCompression && !result.preCompressed) {
|
|
180
183
|
res.header('content-encoding', 'gzip');
|
|
181
184
|
const gzip = createGzip({ level: constants.Z_BEST_SPEED }); // go fast
|
|
182
185
|
// .pipe() does not tear down across the pipe in either direction, so wire both:
|
|
@@ -187,7 +190,8 @@ async function handlePostRequest(req, res, _bypassAuth = false) {
|
|
|
187
190
|
// - gzip close → destroy source: when the client disconnects mid-download Fastify
|
|
188
191
|
// destroys only the stream it was handed (gzip); destroy the source too so its
|
|
189
192
|
// underlying file/blob read stops and descriptors release. (No-op after a normal
|
|
190
|
-
// end. preCompressed streams skip this block; Fastify handles them
|
|
193
|
+
// end. noCompression/preCompressed streams skip this block; Fastify handles them
|
|
194
|
+
// directly.)
|
|
191
195
|
const source = result;
|
|
192
196
|
source.on('error', (error) => gzip.destroy(error));
|
|
193
197
|
gzip.on('close', () => source.destroy());
|
|
@@ -12,6 +12,7 @@ import { isDeployValidating } from './deployValidationState.ts';
|
|
|
12
12
|
import harperLogger from '../../utility/logging/harper_logger.ts';
|
|
13
13
|
import readLog from '../../utility/logging/readLog.ts';
|
|
14
14
|
import * as export_ from '../../dataLayer/export.ts';
|
|
15
|
+
import * as rocksdbBackup from '../../dataLayer/rocksdbBackup.ts';
|
|
15
16
|
import * as opAuth from '../../utility/operation_authorization.ts';
|
|
16
17
|
import * as jobs from '../jobs/jobs.ts';
|
|
17
18
|
import * as terms from '../../utility/hdbTerms.ts';
|
|
@@ -388,6 +389,9 @@ export async function executeJob(json: OperationRequestBody): Promise<JobResult>
|
|
|
388
389
|
};
|
|
389
390
|
}
|
|
390
391
|
} catch (err) {
|
|
392
|
+
// errors that already carry a statusCode (e.g. ClientError from job validation) are
|
|
393
|
+
// client-facing as-is; wrapping them here would turn a 400/404/409 into a 500
|
|
394
|
+
if (err instanceof Error && typeof (err as any).statusCode === 'number') throw err;
|
|
391
395
|
const error = err instanceof Error ? err : null;
|
|
392
396
|
const message = `There was an error executing job: ${error && 'http_resp_msg' in error ? error.http_resp_msg : err}`;
|
|
393
397
|
operationLog.error(message);
|
|
@@ -581,6 +585,21 @@ function initializeOperationFunctionMap(): Map<OperationFunctionName, OperationF
|
|
|
581
585
|
);
|
|
582
586
|
opFuncMap.set(terms.OPERATIONS_ENUM.INSTALL_NODE_MODULES, new OperationFunctionObject(npmUtilities.installModules));
|
|
583
587
|
opFuncMap.set(terms.OPERATIONS_ENUM.GET_BACKUP, new OperationFunctionObject(schema.getBackup));
|
|
588
|
+
opFuncMap.set(
|
|
589
|
+
terms.OPERATIONS_ENUM.CREATE_BACKUP,
|
|
590
|
+
new OperationFunctionObject(executeJob, rocksdbBackup.createBackup)
|
|
591
|
+
);
|
|
592
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.LIST_BACKUPS, new OperationFunctionObject(rocksdbBackup.listBackups));
|
|
593
|
+
opFuncMap.set(
|
|
594
|
+
terms.OPERATIONS_ENUM.VERIFY_BACKUP,
|
|
595
|
+
new OperationFunctionObject(executeJob, rocksdbBackup.verifyBackup)
|
|
596
|
+
);
|
|
597
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.DELETE_BACKUP, new OperationFunctionObject(rocksdbBackup.deleteBackup));
|
|
598
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.PURGE_BACKUPS, new OperationFunctionObject(rocksdbBackup.purgeBackups));
|
|
599
|
+
opFuncMap.set(
|
|
600
|
+
terms.OPERATIONS_ENUM.RESTORE_BACKUP,
|
|
601
|
+
new OperationFunctionObject(executeJob, rocksdbBackup.restoreBackup)
|
|
602
|
+
);
|
|
584
603
|
opFuncMap.set(terms.OPERATIONS_ENUM.CLEANUP_ORPHAN_BLOBS, new OperationFunctionObject(schema.cleanupOrphanBlobs));
|
|
585
604
|
|
|
586
605
|
opFuncMap.set(terms.OPERATIONS_ENUM.GET_ANALYTICS, new OperationFunctionObject(analytics.getOp));
|
package/server/static.ts
CHANGED
|
@@ -152,6 +152,10 @@ export function handleApplication(scope: Scope) {
|
|
|
152
152
|
// cannot be re-registered at runtime. Capture the matching base once so map keys always agree
|
|
153
153
|
// with the registered route (#1583).
|
|
154
154
|
const baseURLPath = resolveBaseURLPath(scope.pluginName, (scope.options.getAll() as any)?.urlPath);
|
|
155
|
+
// The same base as the client sees. `baseURLPath` is mount-relative — it must stay that way to
|
|
156
|
+
// keep agreeing with the entry URL paths the file map is keyed by — but a redirect Location has
|
|
157
|
+
// to carry the application's mount too, or it would point outside the mount (#1583).
|
|
158
|
+
const externalBaseURLPath = scope.externalBasePath(baseURLPath);
|
|
155
159
|
|
|
156
160
|
// A bare `before:` / `after:` key in YAML parses as null — treat it as unset, like before this
|
|
157
161
|
// option was validated.
|
|
@@ -283,7 +287,11 @@ export function handleApplication(scope: Scope) {
|
|
|
283
287
|
// redirect the no-slash form so relative links on the index page resolve under
|
|
284
288
|
// the mount (#1583). Query string is preserved across both redirects; compute it
|
|
285
289
|
// lazily inside each branch so the common (non-redirect) index serve stays allocation-free.
|
|
286
|
-
|
|
290
|
+
// Gated on the EXTERNAL base path, not the plugin-local one: a root-level static
|
|
291
|
+
// plugin (baseURLPath === '/') still needs this redirect when the application
|
|
292
|
+
// itself carries a host/urlPath mount, since the client-visible mount root is then
|
|
293
|
+
// externalBaseURLPath, not '/' (review finding).
|
|
294
|
+
if (staticFile && req.pathname === '/' && externalBaseURLPath !== '/') {
|
|
287
295
|
const originalPathname: string | undefined = (req as any).originalPathname;
|
|
288
296
|
if (originalPathname && !originalPathname.endsWith('/')) {
|
|
289
297
|
const queryIndex = (req.url as string).indexOf('?');
|
|
@@ -291,7 +299,7 @@ export function handleApplication(scope: Scope) {
|
|
|
291
299
|
return {
|
|
292
300
|
status: 301,
|
|
293
301
|
headers: {
|
|
294
|
-
Location:
|
|
302
|
+
Location: externalBaseURLPath + query,
|
|
295
303
|
},
|
|
296
304
|
};
|
|
297
305
|
}
|
|
@@ -300,7 +308,8 @@ export function handleApplication(scope: Scope) {
|
|
|
300
308
|
// If `null`, redirect to trailing slash. req.pathname arrives with the mount
|
|
301
309
|
// prefix stripped, so rebuild the external path for the Location header (#1583)
|
|
302
310
|
if (staticFile === null) {
|
|
303
|
-
const externalPath =
|
|
311
|
+
const externalPath =
|
|
312
|
+
externalBaseURLPath === '/' ? req.pathname : externalBaseURLPath.slice(0, -1) + req.pathname;
|
|
304
313
|
const queryIndex = (req.url as string).indexOf('?');
|
|
305
314
|
const query = queryIndex === -1 ? '' : (req.url as string).slice(queryIndex);
|
|
306
315
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e,t}from"./rolldown-runtime-CNC7AqOf.js";import{C as n,S as r,_ as i,a,b as o,c as s,d as c,h as l,i as u,l as d,m as f,p,r as m,s as h,t as g,u as _,v,w as ee,x as y,y as b}from"./vendor-core-xUefYpUO.js";import{i as x,t as S}from"./button-oQgW4t1x.js";import{C as te,I as ne,N as re,j as ie}from"./vendor-tanstack-0SjDjtT0.js";import{a as ae}from"./vendor-datadog-DyjkTkvg.js";import{r as oe}from"./vendor-react-DeL8Gtgl.js";import{Rt as C}from"./vendor-ui-Bj22lHGK.js";import{t as w}from"./createLucideIcon-BDx8noBh.js";import{l as se,t as ce}from"./react-CjDfh48f.js";import{i as le,n as ue,r as de,t as fe}from"./x-C3uc0qMD.js";import{c as pe,f as me,g as he,h as ge,i as _e,l as ve,m as ye,p as be,r as xe,t as Se,u as Ce}from"./setComponentFile-
|
|
1
|
+
import{a as e,t}from"./rolldown-runtime-CNC7AqOf.js";import{C as n,S as r,_ as i,a,b as o,c as s,d as c,h as l,i as u,l as d,m as f,p,r as m,s as h,t as g,u as _,v,w as ee,x as y,y as b}from"./vendor-core-xUefYpUO.js";import{i as x,t as S}from"./button-oQgW4t1x.js";import{C as te,I as ne,N as re,j as ie}from"./vendor-tanstack-0SjDjtT0.js";import{a as ae}from"./vendor-datadog-DyjkTkvg.js";import{r as oe}from"./vendor-react-DeL8Gtgl.js";import{Rt as C}from"./vendor-ui-Bj22lHGK.js";import{t as w}from"./createLucideIcon-BDx8noBh.js";import{l as se,t as ce}from"./react-CjDfh48f.js";import{i as le,n as ue,r as de,t as fe}from"./x-C3uc0qMD.js";import{c as pe,f as me,g as he,h as ge,i as _e,l as ve,m as ye,p as be,r as xe,t as Se,u as Ce}from"./setComponentFile-DMPo4UjC.js";import{n as we}from"./queryClient-CbA8wM7J.js";import{n as Te}from"./setLocalStorage-CD_L8p_D.js";import{t as Ee}from"./useLocalStorage-hkNSGGg8.js";import{o as De}from"./pollUnlessForbidden-HHdQZW1N.js";import{$n as Oe,At as ke,Bn as Ae,Gn as je,Gt as Me,Hn as Ne,Jn as Pe,L as Fe,Nt as Ie,Ot as Le,Qn as Re,R as ze,Un as Be,_t as Ve,ar as He,c as Ue,dn as We,ir as Ge,jt as Ke,kt as qe,lr as Je,nr as Ye,o as Xe,ot as Ze,q as Qe,s as $e,u as et,ut as tt}from"./index-Bh_CNAHr.js";import{t as nt}from"./useEntityRestURL-DoaBMEvU.js";import{n as rt}from"./getAnalytics-ChWFgfXd.js";var it=w(`between-horizontal-start`,[[`rect`,{width:`13`,height:`7`,x:`8`,y:`3`,rx:`1`,key:`pkso9a`}],[`path`,{d:`m2 9 3 3-3 3`,key:`1agib5`}],[`rect`,{width:`13`,height:`7`,x:`8`,y:`14`,rx:`1`,key:`1q5fc1`}]]),at=w(`book`,[[`path`,{d:`M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20`,key:`k3hazp`}]]),ot=w(`chart-area`,[[`path`,{d:`M3 3v16a2 2 0 0 0 2 2h16`,key:`c24i48`}],[`path`,{d:`M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z`,key:`q0gr47`}]]),st=w(`circle-x`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]]),ct=w(`file-pen`,[[`path`,{d:`M12.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v9.34`,key:`o6klzx`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`M10.378 12.622a1 1 0 0 1 3 3.003L8.36 20.637a2 2 0 0 1-.854.506l-2.867.837a.5.5 0 0 1-.62-.62l.836-2.869a2 2 0 0 1 .506-.853z`,key:`zhnas1`}]]),lt=w(`logs`,[[`path`,{d:`M3 5h1`,key:`1mv5vm`}],[`path`,{d:`M3 12h1`,key:`lp3yf2`}],[`path`,{d:`M3 19h1`,key:`w6f3n9`}],[`path`,{d:`M8 5h1`,key:`1nxr5w`}],[`path`,{d:`M8 12h1`,key:`1con00`}],[`path`,{d:`M8 19h1`,key:`k7p10e`}],[`path`,{d:`M13 5h8`,key:`a7qcls`}],[`path`,{d:`M13 12h8`,key:`h98zly`}],[`path`,{d:`M13 19h8`,key:`c3s6r1`}]]),ut=w(`message-square-heart`,[[`path`,{d:`M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z`,key:`18887p`}],[`path`,{d:`M7.5 9.5c0 .687.265 1.383.697 1.844l3.009 3.264a1.14 1.14 0 0 0 .407.314 1 1 0 0 0 .783-.004 1.14 1.14 0 0 0 .398-.31l3.008-3.264A2.77 2.77 0 0 0 16.5 9.5 2.5 2.5 0 0 0 12 8a2.5 2.5 0 0 0-4.5 1.5`,key:`1faxuh`}]]),dt=w(`send`,[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]),ft=w(`wrench`,[[`path`,{d:`M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z`,key:`1ngwbx`}]]);async function pt(){await x.delete(`/Chat/Messages/`)}var T=e(ae(),1),E=oe();function mt({setMessages:e}){let[t,n]=(0,T.useState)(!1);return(0,E.jsxs)(`button`,{type:`button`,className:`clear-chat-button gap-1`,onClick:(0,T.useCallback)(async()=>{if(!t){n(!0);try{await pt(),e([])}catch(e){console.error(`Failed to clear chat:`,e)}finally{n(!1)}}},[t,e]),disabled:t,title:`Clear chat`,children:[t?(0,E.jsx)(Re,{className:`animate-spin`,size:18}):(0,E.jsx)(Be,{size:18}),`Clear`]})}async function ht(){let{data:e}=await x.get(`/Chat/Messages/`);return e}var gt=`vercel.ai.error`,_t=Symbol.for(gt),vt,yt,D=class e extends (yt=Error,vt=_t,yt){constructor({name:e,message:t,cause:n}){super(t),this[vt]=!0,this.name=e,this.cause=n}static isInstance(t){return e.hasMarker(t,gt)}static hasMarker(e,t){let n=Symbol.for(t);return typeof e==`object`&&!!e&&n in e&&typeof e[n]==`boolean`&&e[n]===!0}};function bt(e){return e==null?`unknown error`:typeof e==`string`?e:e instanceof Error?e.toString():JSON.stringify(e)}var xt=`AI_InvalidArgumentError`,St=`vercel.ai.error.${xt}`,Ct=Symbol.for(St),wt,Tt,Et=class extends (Tt=D,wt=Ct,Tt){constructor({message:e,cause:t,argument:n}){super({name:xt,message:e,cause:t}),this[wt]=!0,this.argument=n}static isInstance(e){return D.hasMarker(e,St)}},Dt=`AI_JSONParseError`,Ot=`vercel.ai.error.${Dt}`,kt=Symbol.for(Ot),At,jt,Mt=class extends (jt=D,At=kt,jt){constructor({text:e,cause:t}){super({name:Dt,message:`JSON parsing failed: Text: ${e}.
|
|
2
2
|
Error message: ${bt(t)}`,cause:t}),this[At]=!0,this.text=e}static isInstance(e){return D.hasMarker(e,Ot)}},Nt=`AI_TypeValidationError`,Pt=`vercel.ai.error.${Nt}`,Ft=Symbol.for(Pt),It,Lt,O=class e extends (Lt=D,It=Ft,Lt){constructor({value:e,cause:t,context:n}){let r=`Type validation failed`;if(n?.field&&(r+=` for ${n.field}`),n?.entityName||n?.entityId){r+=` (`;let e=[];n.entityName&&e.push(n.entityName),n.entityId&&e.push(`id: "${n.entityId}"`),r+=e.join(`, `),r+=`)`}super({name:Nt,message:`${r}: Value: ${JSON.stringify(e)}.
|
|
3
3
|
Error message: ${bt(t)}`,cause:t}),this[It]=!0,this.value=e,this.context=n}static isInstance(e){return D.hasMarker(e,Pt)}static wrap({value:t,cause:n,context:r}){return e.isInstance(n)&&n.value===t&&n.context?.field===r?.field&&n.context?.entityName===r?.entityName&&n.context?.entityId===r?.entityId?n:new e({value:t,cause:n,context:r})}},Rt=class extends Error{constructor(e,t){super(e),this.name=`ParseError`,this.type=t.type,this.field=t.field,this.value=t.value,this.line=t.line}},zt=10,Bt=13,k=32;function Vt(e){}function Ht(e){if(typeof e==`function`)throw TypeError("`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?");let{onEvent:t=Vt,onError:n=Vt,onRetry:r=Vt,onComment:i,maxBufferSize:a}=e,o=[],s=0,c=!0,l,u=``,d=0,f,p=!1;function m(e){if(p)throw Error("Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.");if(c&&(c=!1,e.charCodeAt(0)===239&&e.charCodeAt(1)===187&&e.charCodeAt(2)===191&&(e=e.slice(3))),o.length===0){let t=g(e);t!==``&&(o.push(t),s=t.length),h();return}if(e.indexOf(`
|
|
4
4
|
`)===-1&&e.indexOf(`\r`)===-1){o.push(e),s+=e.length,h();return}o.push(e);let t=o.join(``);o.length=0,s=0;let n=g(t);n!==``&&(o.push(n),s=n.length),h()}function h(){a!==void 0&&(s+u.length<=a||(p=!0,o.length=0,s=0,l=void 0,u=``,d=0,f=void 0,n(new Rt(`Buffered data exceeded max buffer size of ${a} characters`,{type:`max-buffer-size-exceeded`}))))}function g(e){let n=0;if(e.indexOf(`\r`)===-1){let r=e.indexOf(`
|
|
@@ -1946,4 +1946,4 @@ jsResource:
|
|
|
1946
1946
|
`,"using-blob-datatype":"---\nname: using-blob-datatype\ndescription: How to use the Blob data type for efficient binary storage in Harper.\nmetadata:\n mode: generate\n sources:\n - reference/v5/database/schema.md#Blob Type\n - reference/v5/database/api.md#Streaming\n - reference/v5/database/api.md#`BlobOptions`\n - reference/v5/database/api.md#Blob Coercion\n sourceCommit: f37a8c4021e20d5c74c1d339a6b6c8c196b5603e\n inputHash: 92e03eb0b830f335\n---\n\n# Using the Blob Data Type\n\nInstructions for the agent to follow when storing and retrieving large binary content using the `Blob` data type in Harper.\n\n## When to Use\n\nApply this rule when a schema field needs to store large binary content such as images, video, audio, or large HTML — typically content larger than 20KB. Use `Blob` instead of `Bytes` when streaming support and out-of-record storage are required. See [handling-binary-data.md](handling-binary-data.md) for broader binary data guidance.\n\n## How It Works\n\n1. **Declare a `Blob` field in your schema**: Add a field typed as `Blob` to your `@table` type.\n\n ```graphql\n type MyTable @table {\n id: Any! @primaryKey\n data: Blob\n }\n ```\n\n2. **Create and store a blob with `createBlob()`**: Pass a buffer or stream to `createBlob()`, then `put` the record.\n\n ```javascript\n let blob = createBlob(largeBuffer);\n await MyTable.put({ id: 'my-record', data: blob });\n ```\n\n3. **Retrieve blob data using standard Web API methods**: The `Blob` type implements the Web API `Blob` interface. Use `.bytes()`, `.text()`, `.arrayBuffer()`, `.stream()`, or `.slice()` as needed.\n\n ```javascript\n let record = await MyTable.get('my-record');\n let buffer = await record.data.bytes(); // ArrayBuffer\n let text = await record.data.text(); // string\n let stream = record.data.stream(); // ReadableStream\n ```\n\n4. **Use `saveBeforeCommit` when full write must precede commit**: By default, `Blob` is not ACID-compliant — a record can reference a blob before it is fully written. Set `saveBeforeCommit: true` to block the transaction until the blob is fully saved.\n\n ```javascript\n let blob = createBlob(stream, { saveBeforeCommit: true });\n await MyTable.put({ id: 'my-record', data: blob });\n // put() resolves only after blob is fully written and record is committed\n ```\n\n5. **Register an error handler when returning a blob via REST**: Interrupted streams must be handled explicitly.\n\n ```javascript\n export class MyEndpoint extends MyTable {\n static async get(target) {\n const record = super.get(target);\n let blob = record.data;\n blob.on('error', () => {\n MyTable.invalidate(target);\n });\n return { status: 200, headers: {}, body: blob };\n }\n }\n ```\n\n6. **Rely on automatic coercion where applicable**: When a field is typed as `Blob` in the schema, any string or buffer assigned via `put`, `patch`, or `publish` is automatically coerced to a `Blob` — no manual `createBlob()` call is needed in those cases.\n\n### `BlobOptions` reference\n\nPass an options object as the second argument to `createBlob()`.\n\n| Option | Type | Default | Description |\n| ------------------ | --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------ |\n| `type` | `string` | `undefined` | MIME type to associate with the blob (e.g., `image/jpeg`). Readable via `blob.type` and used when serving HTTP. |\n| `size` | `number` | `undefined` | Size of the data in bytes, if known ahead of time. Otherwise inferred from a buffer or determined as a stream completes. |\n| `saveBeforeCommit` | `boolean` | `false` | Wait until the blob is fully written before the transaction commits. |\n| `compress` | `boolean` | `false` | Compress the stored data with deflate. |\n| `flush` | `boolean` | `false` | Flush the file to disk after writing, before the `createBlob` promise chain resolves. |\n\n## Examples\n\n**Store an image with a MIME type:**\n\n```javascript\nlet blob = createBlob(imageBuffer, { type: 'image/jpeg' });\nawait Photo.put({ id, data: blob });\n```\n\n**Stream a blob in as it streams out (low-latency passthrough):**\n\n```javascript\nlet blob = createBlob(incomingStream);\n// blob exists, but data is still streaming to storage\nawait MyTable.put({ id: 'my-record', data: blob });\n\nlet record = await MyTable.get('my-record');\n// blob data is accessible as it arrives\nlet outgoingStream = record.data.stream();\n```\n\n**Guarantee full write before commit using `saveBeforeCommit`:**\n\n```javascript\nlet blob = createBlob(stream, { saveBeforeCommit: true });\nawait MyTable.put({ id: 'my-record', data: blob });\n```\n\n## Notes\n\n- `Blob` stores data separately from the record. If you need the binary data to be a true, ACID-committed part of the record, use a `Bytes` field instead.\n- All standard Web API `Blob` methods — `.text()`, `.arrayBuffer()`, `.stream()`, `.slice()`, and `.bytes()` — are available on retrieved blob fields.\n- Without `saveBeforeCommit: true`, blobs are **not** ACID-compliant by default; a record can reference a blob before it is fully written to storage.\n","v5-upgrade":"---\nname: v5-upgrade\ndescription: >-\n Breaking changes and recommended updates when migrating a Harper application\n to v5.\nmetadata:\n mode: generate\n sources:\n - release-notes/v5-lincoln/v5-migration.md\n sourceCommit: de2aaf1c759a7ff5b93e862ba704153e2a392fcb\n inputHash: 80314689f3a7f42e\n---\n\n# Upgrading a Harper Application to v5\n\nInstructions for the agent to follow when migrating an existing Harper application to version 5, addressing the breaking changes and adopting the recommended patterns.\n\n## When to Use\n\nApply this rule when upgrading a Harper application from v4.x (or earlier) to v5, or when code written against older APIs behaves differently after a v5 upgrade — for example a record that can no longer be mutated, a query that returns stale data, or a `spawn` call that now throws. Harper v5 introduces breaking changes; applications built on documented APIs need the updates below, while code relying on undocumented behavior or timing may need broader review.\n\n## How It Works\n\n1. **Adopt the `harper` package name**: HarperDB is now Harper, and the package was renamed. Install the open source edition with `npm i -g harper` and the pro edition with `npm i -g @harperfast/harper-pro`. Update application imports from `harperdb` to `harper`:\n\n ```javascript\n import { tables } from 'harper';\n ```\n\n2. **Opt in to install scripts when required**: Harper now installs packages with `--ignore-scripts` to guard against accidental script execution, a common security risk. If an application genuinely needs install scripts to run (for example to build native binaries), pass the `allowInstallScripts` option when deploying.\n\n3. **Update `Table.get` return-value handling**: `Table.get` now returns a plain, frozen record object rather than an instance of the table class, so instance methods are no longer available on the result. The commonly used `wasLoadedFromSource()` method is gone; cache-vs-origin information now lives on the request `target` as `target.loadedFromSource`. Pass a `RequestTarget` to `Table.get` and read the flag from it. Because the record is frozen, create a copy instead of mutating it in place.\n\n4. **Account for automatic transaction context**: With RocksDB, transactions are fully supported by the storage engine, and Harper v5 uses asynchronous context tracking to carry the current transaction across calls automatically. A nested `Table.get` now reads within the current transaction's snapshot, so it will not observe newly written data until you commit. Access the context through `getContext()` and either commit the current transaction (`getContext().transaction.commit()`) or run the read inside a fresh `transaction(...)` to see the latest data. This matters most for code executing outside the context of a Harper request.\n\n5. **Register spawnable commands**: Spawning processes is now tightly controlled. `spawn`, `exec`, and `execFile` may only run executables listed in the `applications.allowedSpawnCommands` configuration, and each call must pass a `name` in its `options` so that only a single named process is started across Harper's multiple threads. Use a distinct `name` when you deliberately need a separate process.\n\n6. **Return Response-like objects to set headers**: If a REST method returns an object with a `headers` property, Harper uses it as the response headers.\n\n7. **Replace `blob.save()`**: The `blob.save()` method has been removed. Pass the `saveBeforeCommit` flag in the options to the `Blob` constructor instead.\n\n8. **Configure the VM module loader**: v5 loads application modules through Node.js's VM module API, giving each application its own module cache and an application-scoped `harper` module. All of this is controlled by the `applications` section of `harperdb-config.yaml`:\n - `moduleLoader` — `vm-current-context` (default), `vm`, `native`, or `compartment`. The default shares intrinsics with Harper for best compatibility; choose `vm` only if you need per-application intrinsics, and `native` to disable the VM loader entirely (restores pre-v5 loading, but per-app `logger`/`config` context is unavailable).\n - `lockdown` — `freeze-after-load` (default), `freeze`, `ses`, or `none`. Freezing intrinsics prevents prototype-pollution attacks; set `lockdown: none` only as a temporary workaround if a dependency mutates built-in prototypes.\n - `allowedDirectory` — `app` (default) restricts module loading to the application's own directory tree; set `any` if the app must load files from outside it in production.\n - `allowedBuiltinModules` — an optional allowlist restricting which Node.js built-ins the application may import (all are allowed if omitted).\n - `dependencyLoader` — `auto` (default), `app`, or `native`, controlling how npm dependencies are loaded through or around the VM loader.\n\n## Examples\n\n**Updating `Table.get` and its cache check:**\n\n```javascript\n// Old (v4.x)\nconst record = await Table.get(id);\nif (record.wasLoadedFromSource()) {\n // loaded from origin, not cache\n}\n\n// New (v5)\nconst target = new RequestTarget(); // passed in when overriding `get`\ntarget.id = id;\nconst record = await Table.get(target);\nif (target.loadedFromSource) {\n // loaded from origin, not cache\n}\n```\n\n**Committing the transaction to read fresh data:**\n\n```javascript\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { getContext, transaction } from 'harper';\n\nclass MyResource {\n static async get(target) {\n // The current transaction is a consistent snapshot; commit it to see updates.\n await getContext().transaction.commit();\n while ((await transaction(() => Table.get(target))).status !== 'ready') {\n delay(100);\n }\n return Table.get(target);\n }\n}\n```\n\n**Module loader and security configuration in `harperdb-config.yaml`:**\n\n```yaml\napplications:\n lockdown: freeze-after-load # default\n moduleLoader: vm-current-context # vm-current-context (default) | vm | native | compartment\n dependencyLoader: auto # auto (default) | app | native\n allowedDirectory: app # app (default) | any\n allowedSpawnCommands:\n - npm\n - node\n # allowedBuiltinModules: [] # if omitted, all Node.js built-ins are allowed\n```\n\n## Recommended Changes\n\nBeyond the required fixes above, Harper v5 encourages these patterns for new and migrated code:\n\n- Implement endpoints with `static` methods on Resources/Tables, reading request information from the request `target` argument (or from `getContext()`).\n- Rely on automatic context propagation rather than threading context through every call manually; access it via `getContext()` exported from `harper`.\n- Access Harper functions and APIs through the `harper` package rather than through global variables.\n","vector-indexing":'---\nname: vector-indexing\ndescription: How to enable and query vector indexes for similarity search in Harper.\nmetadata:\n mode: generate\n sources:\n - reference/v5/database/schema.md#Vector Indexing\n sourceCommit: 4fe4c9c95e0974eaa77032f6f10e36fbd8ec64ac\n inputHash: d90b1b74597d08a6\n---\n\n# Vector Indexing\n\nInstructions for the agent to enable HNSW vector indexes on table fields and query them for similarity search in Harper.\n\n## When to Use\n\nApply this rule when adding a vector similarity search capability to a Harper table — for example, storing text embeddings and querying for nearest neighbors, filtering by distance threshold, or tuning index construction and search parameters. Use it alongside [adding-tables-with-schemas.md](adding-tables-with-schemas.md) when defining the schema that hosts the vector field.\n\n## How It Works\n\n1. **Declare the vector index on a field**: Add `@indexed(type: "HNSW")` to a `[Float]` field inside a `@table` type. This creates an HNSW (Hierarchical Navigable Small World) index for approximate nearest-neighbor search.\n\n ```graphql\n type Document @table {\n id: Long @primaryKey\n textEmbeddings: [Float] @indexed(type: "HNSW")\n }\n ```\n\n2. **Query by nearest neighbors using `sort`**: Call `.search()` with a `sort` descriptor that specifies the indexed `attribute` and a `target` vector. Use `limit` to cap results.\n\n ```javascript\n let results = Document.search({\n sort: { attribute: \'textEmbeddings\', target: searchVector },\n limit: 5,\n });\n ```\n\n3. **Combine with filter conditions**: Add a `conditions` array alongside `sort` to pre-filter records before ranking by similarity.\n\n ```javascript\n let results = Document.search({\n conditions: [{ attribute: \'price\', comparator: \'lt\', value: 50 }],\n sort: { attribute: \'textEmbeddings\', target: searchVector },\n limit: 5,\n });\n ```\n\n4. **Filter by distance threshold**: To return only records within a similarity cutoff (without ranking), place `target` directly on the condition alongside `comparator` and `value`. This bounds result quality rather than ranking by similarity.\n\n ```javascript\n let results = Document.search({\n conditions: {\n attribute: \'textEmbeddings\',\n comparator: \'lt\',\n value: 0.1,\n target: searchVector,\n },\n });\n ```\n\n5. **Include computed distance in results**: Use the special `$distance` field in `select` to return the distance from the target vector. Available in both `sort`-based and threshold-based queries.\n\n ```javascript\n let results = Document.search({\n select: [\'name\', \'$distance\'],\n sort: { attribute: \'textEmbeddings\', target: searchVector },\n limit: 5,\n });\n ```\n\n6. **Tune per-query search options**: Pass `distance` and `ef` directly on the `sort` descriptor to override index defaults for a single query.\n\n ```javascript\n let results = Document.search({\n sort: { attribute: \'textEmbeddings\', target: searchVector, distance: \'dotProduct\', ef: 200 },\n limit: 5,\n });\n ```\n\n - `distance` — overrides the distance function for this query: `"cosine"`, `"euclidean"`, or `"dotProduct"`.\n - `ef` — overrides the search exploration budget. Higher values improve recall at the cost of latency.\n\n7. **Configure HNSW index parameters**: Pass parameters directly in the `@indexed` directive. Structural parameters (`distance`, `M`, `efConstruction`, `quantization`) trigger an index rebuild when changed; `efConstructionSearch` does not.\n\n ```graphql\n type Document @table {\n id: Long @primaryKey\n textEmbeddings: [Float]\n @indexed(type: "HNSW", distance: "euclidean", optimizeRouting: 0, efConstructionSearch: 100)\n }\n ```\n\n8. **Enable vector quantization**: Use `quantization: "int8"` to store vectors as 8-bit integers, reducing index size and memory usage. Harper re-ranks nearest-neighbor `sort` results against full-precision vectors automatically.\n\n ```graphql\n type Document @table {\n id: Long @primaryKey\n textEmbeddings: [Float] @indexed(type: "HNSW", quantization: "int8")\n }\n ```\n\n## Examples\n\nFull schema with custom HNSW parameters and a nearest-neighbor query with distance output:\n\n```graphql\ntype Document @table {\n id: Long @primaryKey\n textEmbeddings: [Float]\n @indexed(type: "HNSW", distance: "euclidean", optimizeRouting: 0, efConstructionSearch: 100)\n}\n```\n\n```javascript\n// Nearest-neighbor search with distance scores\nlet results = Document.search({\n select: [\'name\', \'$distance\'],\n sort: { attribute: \'textEmbeddings\', target: searchVector },\n limit: 5,\n});\n\n// Distance-threshold query (no ranking)\nlet closeMatches = Document.search({\n conditions: {\n attribute: \'textEmbeddings\',\n comparator: \'lt\',\n value: 0.1,\n target: searchVector,\n },\n});\n```\n\n## Notes\n\n### HNSW Parameters\n\n| Parameter | Default | Description |\n| ---------------------- | ----------------- | ------------------------------------------------------------------------------------------------------ |\n| `distance` | `"cosine"` | Distance function: `"cosine"`, `"euclidean"`, or `"dotProduct"` |\n| `efConstruction` | `100` | Max nodes explored during index construction. Higher = better recall, lower = better performance |\n| `M` | `16` | Preferred connections per graph layer. Higher = more space, better recall for high-dimensional data |\n| `optimizeRouting` | `0.5` | Heuristic aggressiveness for omitting redundant connections (0 = off, 1 = most aggressive) |\n| `mL` | computed from `M` | Normalization factor for level generation |\n| `efConstructionSearch` | auto-scaled | Max nodes explored during search. When unset, auto-scales with index size; setting it fixes the budget |\n| `quantization` | — | `"int8"` stores vectors quantized to int8 |\n\n- The `distance` option on a per-query `sort` descriptor accepts `"cosine"`, `"euclidean"`, or `"dotProduct"`.\n- When no `ef` is passed and `efConstructionSearch` (or `efConstruction`) is not explicitly set on the index, the search budget auto-scales with index size.\n- `efConstruction` seeds the initial value of `efConstructionSearch`; setting either one fixes the search budget.\n- The correct parameter name is `efConstructionSearch` (not `efSearchConstruction`).\n- `$distance` is available in both `sort`-based ranking and `conditions`-based threshold queries.\n- For `quantization: "int8"`, distance-threshold (`lt`/`le`) queries filter on approximate distance; `sort` queries re-rank against full-precision vectors.\n'},sa={name:`readHarperSkill`,description:`Returns documentation for a Harper skill or best practice. Skills provide guidance on developing Harper applications.`,inputSchema:v({skill:m(aa)})};async function ca({input:{skill:e}}){return{success:!!oa[e],message:oa[e]||`No skill found with the name ${e}`}}var la={...sa,icon:at,execute:ca},ua={name:`readLogs`,description:`Returns the matching logs from the server.`,inputSchema:v({log_name:m([`hdb.log`,`system.log`]).default(`hdb.log`),limit:o().or(a()).optional(),level:m([`notify`,`error`,`warn`,`info`,`debug`,`trace`,`undefined`]).or(a()).optional(),from:o().or(a()).optional(),until:o().or(a()).optional()})};async function da({input:e,instanceClientParams:t}){try{return{success:!0,data:await Xe({...t,logFilters:e,replicated:t.entityType===`cluster`})}}catch(e){return{success:!1,message:`Error: ${e}`}}}var fa={...ua,icon:lt,execute:da},pa={name:`readTableRecords`,description:`Retrieves some or all table records from a database on the server.`,inputSchema:v({database:o().trim(),table:o().trim(),pageIndex:i().default(0),pageSize:i().default(10),primaryKey:o(),conditions:s(v({search_attribute:o(),search_type:m([`between`,`eq`,`equals`,`greater_than`,`greater_than_equal`,`less_than`,`less_than_equal`,`ne`,`not_equal`,`starts_with`]),search_value:h()})),sort:v({attribute:o(),descending:d()})})};async function ma({input:{database:e,table:t,conditions:n,primaryKey:r,...i},instanceClientParams:a}){try{if(!n.length){let{data:n}=await qe({...a,databaseName:e,tableName:t,onlyIfCached:!0,searchAttribute:r,...i});return{success:!0,data:n}}let{data:o}=await ze({...a,databaseName:e,tableName:t,onlyIfCached:!0,conditions:n,...i});return{success:!0,data:o}}catch(e){return{success:!1,message:`Error: ${e}`}}}var ha={...pa,icon:Pe,execute:ma},ga={name:`restartHTTPService`,description:`Restarts the HTTP service on the server to allow schema and resource changes to be applied.`,inputSchema:v({})};async function _a({instanceClientParams:e,baseURL:t}){let n=C.loading(`Restarting HTTP service...`,{description:`This may take a bit.`,duration:3e5});try{await Ve({...e,operation:`restart_service`,replicated:e.entityType===`cluster`})}catch(e){return{success:!1,message:`Error: ${e}`}}return C.success(`Done!`,{description:`HTTP Service restarted!`,id:n,duration:5e3}),{success:!0,message:`HTTP Service restarted!`,webURL:t}}var va={...ga,icon:Re,execute:_a,requiresApproval:!0},ya={name:`setComponentFile`,description:`Returns the contents of a component file by its full path (which was returned by getComponents)`,inputSchema:v({path:o().trim(),payload:o(),encoding:m([`utf8`,`ASCII`,`binary`,`hex`,`base64`,`utf16le`,`latin1`,`ucs2`])})};async function ba({input:{path:e,encoding:t,payload:n},instanceClientParams:r}){try{let i=e.split(`/`),a=i.shift(),o=i.join(`/`),s=await Se({...r,file:o,project:a,payload:n,encoding:t});return await we.invalidateQueries({queryKey:[r.entityId,`get_component_file`,a,o]}),Ie(`ReloadApplicationRootEntries`,!0),{success:!0,data:s}}catch(e){return{success:!1,message:`Error: ${e}`}}}var xa={...ya,icon:ct,execute:ba,requiresApproval:!0},Sa={name:`updateTableRecords`,description:`Updates records in a particular table in a particular database on the server.`,inputSchema:v({database:o().trim(),table:o().trim(),records:s(h())})};async function Ca({input:{database:e,table:t,records:n},instanceClientParams:r,params:i}){try{let a=await $e({...r,databaseName:e,tableName:t,records:n}),{databaseName:o,tableName:s}=i;return await we.invalidateQueries({queryKey:[r.entityId,o,s]}),{success:!0,data:a}}catch(e){return{success:!1,message:`Error: ${e}`}}}var wa={readHarperSkill:la,createApp:Di,readLogs:fa,getAnalytics:Ii,listAnalyticsMetrics:ia,restartHTTPService:va,collectFeedback:wi,getUserContext:Zi,getComponentFile:zi,getComponents:Hi,setComponentFile:xa,dropComponentFile:Ni,getDescribeAll:Gi,getDescribeTable:Ji,insertTableRecords:ea,readTableRecords:ha,updateTableRecords:{...Sa,icon:it,execute:Ca,requiresApproval:!0},deleteTableRecords:Ai};function Ta(e){return wa[e]}function Ea(e){return e.state===`input-available`&&!!Ta(Qr(e))?.requiresApproval}function Da(e){let t=[];for(let[n,r]of(e??[]).entries()){if(G(r)){if(Ea(r)){t.push({kind:`part`,part:r,index:n});continue}let e=t.at(-1);e?.kind===`tool-group`?e.parts.push(r):t.push({kind:`tool-group`,parts:[r],index:n});continue}Jr(r)&&r.text.length>0&&t.push({kind:`part`,part:r,index:n})}return t}function Oa({part:e,onApprove:t,onDeny:n,onAlwaysApprove:r,isApproving:i}){let[a,o]=(0,T.useState)(!1),[s,c]=(0,T.useState)(!1),l=Qr(e),u=Ta(l),d=u?.icon||Ge,f=u?.requiresApproval,p=(0,T.useMemo)(()=>!e.input||typeof e.input==`object`&&Object.keys(e.input).length===0,[e.input]),m=(0,T.useMemo)(()=>{let t=JSON.stringify(e.input,null,` `);return{json:t,lines:t?t.split(`
|
|
1947
1947
|
`).length:0}},[e.input]),h=(0,T.useMemo)(()=>{let t=JSON.stringify(e.output,null,` `);return{json:t,lines:t?t.split(`
|
|
1948
1948
|
`).length:0}},[e.output]);return(0,E.jsxs)(`div`,{className:`tool-invocation ${e.state}`,children:[(0,E.jsxs)(`div`,{className:`tool-info`,children:[(0,E.jsxs)(`div`,{className:`tool-name`,children:[(0,E.jsx)(d,{size:14}),(0,E.jsx)(`span`,{children:l})]}),(0,E.jsxs)(`div`,{className:`tool-status`,children:[e.state===`input-streaming`&&(0,E.jsx)(`span`,{children:`Thinking...`}),e.state===`input-available`&&(0,E.jsx)(`span`,{children:i?`Executing...`:f?`Awaiting Approval...`:`Executing...`}),e.state===`output-available`&&(e.output?.error?(0,E.jsx)(st,{size:14,className:`text-destructive`}):(0,E.jsx)(le,{size:14}))]})]}),e.state!==`input-streaming`&&(0,E.jsxs)(`div`,{className:`tool-io`,children:[!p&&(0,E.jsxs)(`div`,{className:`tool-args`,children:[(0,E.jsxs)(`div`,{className:`flex items-center justify-between gap-2 mb-1`,children:[(0,E.jsx)(`strong`,{children:`Input:`}),m.lines>3&&(0,E.jsx)(S,{type:`button`,variant:`ghost`,size:`sm`,className:`h-6 px-2 text-[10px] uppercase tracking-wider text-muted-foreground hover:text-foreground`,onClick:()=>o(!a),children:a?(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(ue,{size:12}),`Hide`]}):(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(de,{size:12}),`Show`]})})]}),(0,E.jsx)(`div`,{className:a?`whitespace-pre-wrap`:`line-clamp-3 overflow-hidden whitespace-pre-wrap`,children:m.json})]}),e.state===`input-available`&&f&&(0,E.jsxs)(`div`,{className:`flex gap-2 mt-3 pt-3 border-t`,children:[(0,E.jsxs)(S,{size:`sm`,className:`h-8 text-xs bg-green-600 hover:bg-green-700 text-white`,onClick:()=>t?.(e.toolCallId),disabled:i,children:[i?(0,E.jsx)(Ye,{className:`mr-2 h-3 w-3 animate-spin`}):null,`Approve`]}),(0,E.jsx)(S,{type:`button`,size:`sm`,variant:`outline`,className:`h-8 text-xs approval-outline`,onClick:()=>r?.(e.toolCallId),disabled:i,children:`Always Approve`}),(0,E.jsx)(S,{type:`button`,size:`sm`,variant:`outline`,className:`h-8 text-xs approval-outline`,onClick:()=>n?.(e.toolCallId),disabled:i,children:`Deny`})]}),e.state===`output-available`&&(0,E.jsx)(E.Fragment,{children:u?.render?u.render(e):(0,E.jsxs)(`div`,{className:`tool-result`,children:[(0,E.jsxs)(`div`,{className:`flex items-center justify-between gap-2 mb-1`,children:[(0,E.jsx)(`strong`,{children:`Result:`}),h.lines>3&&(0,E.jsx)(S,{type:`button`,variant:`ghost`,size:`sm`,className:`h-6 px-2 text-[10px] uppercase tracking-wider text-muted-foreground hover:text-foreground`,onClick:()=>c(!s),children:s?(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(ue,{size:12}),`Hide`]}):(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(de,{size:12}),`Show`]})})]}),(0,E.jsx)(`div`,{className:s?`whitespace-pre-wrap`:`line-clamp-3 overflow-hidden whitespace-pre-wrap`,children:h.json})]})})]})]})}function ka({parts:e,onApprove:t,onDeny:n,onAlwaysApprove:r,approvingToolCallIds:i}){let[a,o]=(0,T.useState)(!1),s=e.some(e=>e.state!==`output-available`&&e.state!==`output-error`),c=e.some(e=>e.state===`output-error`||e.state===`output-available`&&e.output?.error),l=e.length===1?Qr(e[0]):void 0,u=l&&Ta(l)?.icon||ft,d=l??`${e.length} tools`;return(0,E.jsxs)(`div`,{className:`tool-group`,children:[(0,E.jsxs)(`button`,{type:`button`,className:`tool-group-summary`,"aria-expanded":a,onClick:()=>o(!a),children:[a?(0,E.jsx)(de,{size:14}):(0,E.jsx)(Je,{size:14}),(0,E.jsx)(u,{size:14}),(0,E.jsx)(`span`,{children:s?`Using ${d}...`:`Used ${d}`}),(0,E.jsx)(`span`,{className:`tool-group-status`,children:s?(0,E.jsx)(Ye,{size:14,className:`animate-spin`}):c?(0,E.jsx)(st,{size:14,className:`text-destructive`}):(0,E.jsx)(le,{size:14})})]}),a&&e.map(e=>(0,E.jsx)(Oa,{part:e,onApprove:t,onDeny:n,onAlwaysApprove:r,isApproving:i?.has(e.toolCallId)},e.toolCallId))]})}function Aa({message:e,onApprove:t,onDeny:n,onAlwaysApprove:r,approvingToolCallIds:i}){return e.parts?.some(e=>Jr(e)&&e.text.length>0||G(e))?(0,E.jsxs)(ce.div,{initial:{opacity:0,y:10},animate:{opacity:1,y:0},className:`message-bubble ${e.role===`user`?`user`:`assistant`}`,children:[(0,E.jsx)(`div`,{className:`avatar`,children:e.role===`user`?(0,E.jsx)(Ae,{size:18}):(0,E.jsx)(se,{size:18})}),(0,E.jsx)(`div`,{className:`content`,children:Da(e.parts).map(e=>{if(e.kind===`tool-group`)return(0,E.jsx)(ka,{parts:e.parts,onApprove:t,onDeny:n,onAlwaysApprove:r,approvingToolCallIds:i},e.parts[0].toolCallId);let{part:a,index:o}=e;return Jr(a)?(0,E.jsx)(`div`,{className:`text-block`,children:a.text},o):G(a)?(0,E.jsx)(Oa,{part:a,onApprove:t,onDeny:n,onAlwaysApprove:r,isApproving:i?.has(a.toolCallId)},o):null})})]},e.id):null}function ja(e,t){if(e!==`submitted`&&e!==`streaming`)return!1;if(t?.role!==`assistant`)return!0;let n=t.parts?.at(-1);return n?Jr(n)?n.state!==`streaming`||n.text.length===0:!G(n)||n.state===`output-available`||n.state===`output-error`:!0}function Ma(){return(0,E.jsxs)(ce.div,{initial:{opacity:0,y:10},animate:{opacity:1,y:0},transition:{delay:.2},className:`message-bubble assistant`,children:[(0,E.jsx)(`div`,{className:`avatar`,children:(0,E.jsx)(se,{size:18})}),(0,E.jsxs)(`div`,{className:`content thinking-indicator`,role:`status`,"aria-label":`Harper Agent is thinking`,children:[(0,E.jsx)(`span`,{className:`thinking-dot`}),(0,E.jsx)(`span`,{className:`thinking-dot`}),(0,E.jsx)(`span`,{className:`thinking-dot`})]})]})}function Na(e){return ie({queryKey:[`getMyUsage`,e],queryFn:async()=>{let{data:t}=await x.get(`/Chat/Usage/${e}`);return t}})}function Pa(){let{organizationId:e}=te({strict:!1});return re(Na(e))}function Fa(){let{data:e,isLoading:t,error:n}=Pa();if(t||n||!e)return null;let{usageUSD:r,monthlyLimitUSD:i,usageBarPercent:a}=e,o=e=>new Intl.NumberFormat(`en-US`,{style:`currency`,currency:`USD`}).format(e);return(0,E.jsxs)(`div`,{className:`usage-container`,children:[(0,E.jsxs)(`div`,{className:`usage-info`,children:[(0,E.jsx)(`span`,{children:`Monthly Org Usage`}),(0,E.jsxs)(`span`,{children:[o(r),` / `,o(i)]}),(0,E.jsxs)(`span`,{children:[Math.round(a),`%`]})]}),(0,E.jsx)(`div`,{className:`usage-bar-bg`,children:(0,E.jsx)(`div`,{className:`usage-bar-fill`,style:{width:`${a}%`}})})]})}function Ia({autoFocus:e,closeChat:t}){let n=te({strict:!1}),{organizationId:r}=n,[i,a]=ke(`ApplicationChat`,``),[o,s]=(0,T.useState)(!0),[c,l]=(0,T.useState)({}),[u,d]=(0,T.useState)(new Set),[f,p]=Ee(Te.ChatAlwaysApprovedTools,[]),m=new Set(f),h=nt(),g=De(),_=ne(),{messages:v,sendMessage:ee,status:y,addToolOutput:b,setMessages:x}=yi({transport:ci(r),generateId:A(),sendAutomaticallyWhen:si,onFinish(){_.invalidateQueries({queryKey:[`getMyUsage`]})},async onToolCall({toolCall:e}){if(e.dynamic)return;let t=Ta(e.toolName);if(t){if(t.requiresApproval&&!m.has(e.toolName)){let t={type:`tool-call`,toolCallId:e.toolCallId,toolName:e.toolName,input:e.input};l(n=>({...n,[e.toolCallId]:t}));return}let r=await t.execute({input:e.input,instanceClientParams:g,baseURL:h,params:n});b({tool:e.toolName,toolCallId:e.toolCallId,output:r})}}}),S=(0,T.useCallback)(async e=>{let t=c[e];if(t){d(t=>{let n=new Set(t);return n.add(e),n});try{let r=Ta(t.toolName);if(r){let i=await r.execute({input:t.input,instanceClientParams:g,baseURL:h,params:n});b({tool:t.toolName,toolCallId:t.toolCallId,output:i}),l(t=>{let n={...t};return delete n[e],n})}}finally{d(t=>{let n=new Set(t);return n.delete(e),n})}}},[c,g,h,b,n]),re=(0,T.useCallback)(e=>{let t=c[e];t&&(b({tool:t.toolName,toolCallId:t.toolCallId,output:{error:`User denied the tool execution.`}}),l(t=>{let n={...t};return delete n[e],n}))},[c,b]),ie=(0,T.useCallback)(async e=>{let t=c[e];t&&(p(e=>Le([...e,t.toolName])),await S(e))},[c,p,S]);(0,T.useEffect)(()=>{(async()=>{try{let e=await ht();Array.isArray(e)&&x(e)}catch(e){console.error(`Failed to fetch initial messages:`,e)}finally{s(!1)}})()},[x]);let ae=y===`streaming`||y===`submitted`,oe=(0,T.useRef)(null);return(0,T.useEffect)(()=>{oe.current?.scrollIntoView({behavior:`smooth`})},[v]),(0,E.jsxs)(`div`,{className:`flex flex-col h-full`,children:[(0,E.jsxs)(`div`,{className:`flex items-start justify-between gap-6 px-4 py-2.5 border-b border-border bg-card`,children:[(0,E.jsxs)(`div`,{className:`flex flex-col gap-1 min-w-0 flex-1`,children:[(0,E.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,E.jsx)(se,{className:`text-primary`,size:20}),(0,E.jsx)(`span`,{className:`font-semibold text-foreground`,children:`Harper Agent`})]}),(0,E.jsx)(Fa,{})]}),(0,E.jsxs)(`div`,{className:`flex items-center gap-2 shrink-0`,children:[(0,E.jsx)(mt,{setMessages:x}),(0,E.jsx)(`button`,{onClick:t,className:`p-1 hover:bg-accent rounded-md transition-colors text-muted-foreground hover:text-foreground`,title:`Close chat`,children:(0,E.jsx)(fe,{size:20})})]})]}),(0,E.jsx)(`div`,{className:`flex-1 overflow-hidden`,children:(0,E.jsxs)(`div`,{className:`chat-interface h-full w-full`,children:[(0,E.jsxs)(`div`,{className:`messages-area`,children:[o&&(0,E.jsx)(xi,{}),!o&&v.length===0&&(0,E.jsxs)(`div`,{className:`empty-state`,children:[(0,E.jsx)(se,{size:48}),(0,E.jsx)(`p`,{children:`Ask me to create a Harper app!`})]}),v.map(e=>(0,E.jsx)(Aa,{message:e,onApprove:S,onDeny:re,onAlwaysApprove:ie,approvingToolCallIds:u},e.id)),ja(y,v.at(-1))&&(0,E.jsx)(Ma,{}),(0,E.jsx)(`div`,{ref:oe})]}),(0,E.jsx)(bi,{input:i,setInput:a,onSubmit:e=>{e.preventDefault(),i.trim()&&!ae&&!o&&(ee({text:i}),a(``))},disabled:o,autoFocus:e})]})})]})}export{Ia as Chat};
|
|
1949
|
-
//# sourceMappingURL=Chat-
|
|
1949
|
+
//# sourceMappingURL=Chat-DoVWScmq.js.map
|