@humbdb/server 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Humb contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @humbdb/server
2
+
3
+ The local HTTP server for Humb, built on Fastify. Exposes a small JSON API the browser UI consumes
4
+ plus a `/api/health` endpoint used for verification.
5
+
6
+ The server reaches databases only through a `DatabaseAdapter`. See
7
+ [`ARCHITECTURE.md`](../../ARCHITECTURE.md).
@@ -0,0 +1,39 @@
1
+ import { ConnectionTarget } from '@humbdb/core';
2
+ import { DatabaseAdapter } from '@humbdb/driver-contract';
3
+ import { FastifyInstance } from 'fastify';
4
+
5
+ interface CreateServerOptions {
6
+ /** The connected (or to-be-connected) database adapter. */
7
+ adapter?: DatabaseAdapter;
8
+ /** The parsed connection target, for diagnostics (credentials are redacted in responses). */
9
+ target?: ConnectionTarget;
10
+ /** Enable Fastify's logger. Defaults to false (the CLI configures logging). */
11
+ logger?: boolean;
12
+ /**
13
+ * Directory containing the built `apps/web` static assets (its `index.html` and bundle).
14
+ * When provided and it exists, the server serves the browser UI itself so `npx humb <target>`
15
+ * has something to open. When omitted, only the `/api/*` routes are registered.
16
+ */
17
+ webRoot?: string;
18
+ /**
19
+ * Absolute path to the one directory the Files tab may read `.sql` files from (the `--files-dir`
20
+ * CLI flag, resolved and validated at startup). Omitted means file browsing is disabled - see
21
+ * docs/product-specs/dashboard-ui.md's "Files tab security boundary".
22
+ */
23
+ filesRoot?: string;
24
+ }
25
+ /** Build (but do not start) the Humb HTTP server. */
26
+ declare function createServer(options?: CreateServerOptions): FastifyInstance;
27
+ interface StartServerOptions extends CreateServerOptions {
28
+ port?: number;
29
+ host?: string;
30
+ }
31
+ interface RunningServer {
32
+ app: FastifyInstance;
33
+ url: string;
34
+ close: () => Promise<void>;
35
+ }
36
+ /** Build and start the Humb HTTP server, listening on localhost. */
37
+ declare function startServer(options?: StartServerOptions): Promise<RunningServer>;
38
+
39
+ export { type CreateServerOptions, type RunningServer, type StartServerOptions, createServer, startServer };
package/dist/index.js ADDED
@@ -0,0 +1,230 @@
1
+ // src/index.ts
2
+ import { existsSync, readFileSync, statSync } from "fs";
3
+ import { join as join2 } from "path";
4
+ import fastifyStatic from "@fastify/static";
5
+ import {
6
+ DEFAULT_PORT,
7
+ fileContentQuerySchema,
8
+ redactConnectionString,
9
+ rowsQuerySchema,
10
+ runQuerySchema
11
+ } from "@humbdb/core";
12
+ import { ReadOnlyViolationError } from "@humbdb/driver-contract";
13
+ import Fastify from "fastify";
14
+
15
+ // src/event-log.ts
16
+ var MAX_EVENTS = 200;
17
+ var EventLog = class {
18
+ events = [];
19
+ nextId = 1;
20
+ log(level, message) {
21
+ this.events.push({ id: this.nextId++, timestamp: (/* @__PURE__ */ new Date()).toISOString(), level, message });
22
+ if (this.events.length > MAX_EVENTS) {
23
+ this.events.shift();
24
+ }
25
+ }
26
+ list() {
27
+ return this.events;
28
+ }
29
+ clear() {
30
+ this.events = [];
31
+ }
32
+ };
33
+
34
+ // src/files.ts
35
+ import { readdirSync } from "fs";
36
+ import { extname, join, relative, resolve, sep } from "path";
37
+ var SQL_EXTENSION = ".sql";
38
+ var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set(["node_modules"]);
39
+ function toPosixPath(path) {
40
+ return path.split(sep).join("/");
41
+ }
42
+ function walk(rootDir, dir) {
43
+ const entries = readdirSync(dir, { withFileTypes: true });
44
+ const nodes = [];
45
+ for (const entry of entries) {
46
+ if (entry.name.startsWith(".") || SKIPPED_DIR_NAMES.has(entry.name)) continue;
47
+ const absolutePath = join(dir, entry.name);
48
+ if (entry.isDirectory()) {
49
+ const children = walk(rootDir, absolutePath);
50
+ if (children.length > 0) {
51
+ nodes.push({
52
+ name: entry.name,
53
+ path: toPosixPath(relative(rootDir, absolutePath)),
54
+ type: "directory",
55
+ children
56
+ });
57
+ }
58
+ } else if (entry.isFile() && extname(entry.name).toLowerCase() === SQL_EXTENSION) {
59
+ nodes.push({
60
+ name: entry.name,
61
+ path: toPosixPath(relative(rootDir, absolutePath)),
62
+ type: "file"
63
+ });
64
+ }
65
+ }
66
+ return nodes.sort(
67
+ (a, b) => a.type !== b.type ? a.type === "directory" ? -1 : 1 : a.name.localeCompare(b.name)
68
+ );
69
+ }
70
+ function buildFileTree(rootDir) {
71
+ return walk(rootDir, rootDir);
72
+ }
73
+ var InvalidFilePathError = class extends Error {
74
+ };
75
+ function resolveSqlFilePath(rootDir, relativePath) {
76
+ if (relativePath.split(/[/\\]/).includes("..")) {
77
+ throw new InvalidFilePathError("Path must not contain '..' segments.");
78
+ }
79
+ if (extname(relativePath).toLowerCase() !== SQL_EXTENSION) {
80
+ throw new InvalidFilePathError("Only .sql files can be previewed.");
81
+ }
82
+ const absolutePath = resolve(rootDir, relativePath);
83
+ const rootWithSep = rootDir.endsWith(sep) ? rootDir : rootDir + sep;
84
+ if (!absolutePath.startsWith(rootWithSep)) {
85
+ throw new InvalidFilePathError("Path escapes the files directory.");
86
+ }
87
+ return absolutePath;
88
+ }
89
+
90
+ // src/index.ts
91
+ function requireAdapter(adapter) {
92
+ if (!adapter) {
93
+ throw Object.assign(new Error("No database connection is configured."), { statusCode: 503 });
94
+ }
95
+ return adapter;
96
+ }
97
+ function createServer(options = {}) {
98
+ const app = Fastify({ logger: options.logger ?? false });
99
+ const { adapter, target, filesRoot } = options;
100
+ const eventLog = new EventLog();
101
+ let lastKnownStatus;
102
+ app.get("/api/health", async () => {
103
+ let database = "unconfigured";
104
+ if (adapter) {
105
+ database = await adapter.ping().catch(() => false) ? "connected" : "disconnected";
106
+ }
107
+ if (lastKnownStatus !== void 0 && lastKnownStatus !== database) {
108
+ eventLog.log(
109
+ database === "connected" ? "info" : "warn",
110
+ database === "connected" ? "Database connection restored." : "Database connection lost."
111
+ );
112
+ }
113
+ lastKnownStatus = database;
114
+ const engineVersion = adapter && database === "connected" ? await adapter.getVersion().catch(() => null) : null;
115
+ return {
116
+ status: "ok",
117
+ database,
118
+ // A SQLite target's "raw" is a filesystem path, not a URL with credentials - nothing to
119
+ // redact, and redactConnectionString would otherwise mask it as "<unparseable...>".
120
+ target: target ? target.engine === "sqlite" ? target.raw : redactConnectionString(target.raw) : null,
121
+ engineVersion
122
+ };
123
+ });
124
+ app.get("/api/overview", async () => {
125
+ return requireAdapter(adapter).getOverview();
126
+ });
127
+ app.get(
128
+ "/api/tables/:schema/:table",
129
+ async (request) => {
130
+ const { schema, table } = request.params;
131
+ return requireAdapter(adapter).getTable(schema, table);
132
+ }
133
+ );
134
+ app.get(
135
+ "/api/tables/:schema/:table/rows",
136
+ async (request) => {
137
+ const { schema, table } = request.params;
138
+ const { page, pageSize } = rowsQuerySchema.parse(request.query);
139
+ return requireAdapter(adapter).getRows(schema, table, page, pageSize);
140
+ }
141
+ );
142
+ app.post("/api/query", async (request, reply) => {
143
+ const parsed = runQuerySchema.safeParse(request.body);
144
+ if (!parsed.success) {
145
+ return reply.status(400).send({ error: "Request body must be { sql: string }." });
146
+ }
147
+ const start = Date.now();
148
+ try {
149
+ const result = await requireAdapter(adapter).runReadOnlyQuery(parsed.data.sql);
150
+ eventLog.log(
151
+ "info",
152
+ `Query executed in ${Date.now() - start}ms - ${result.rows.length} rows returned`
153
+ );
154
+ return result;
155
+ } catch (error) {
156
+ if (error instanceof ReadOnlyViolationError) {
157
+ eventLog.log("warn", `Query rejected: ${error.message}`);
158
+ return reply.status(400).send({ error: error.message });
159
+ }
160
+ eventLog.log(
161
+ "error",
162
+ `Query failed: ${error instanceof Error ? error.message : String(error)}`
163
+ );
164
+ throw error;
165
+ }
166
+ });
167
+ app.get("/api/console", async () => {
168
+ return { events: eventLog.list() };
169
+ });
170
+ app.delete("/api/console", async () => {
171
+ eventLog.clear();
172
+ return { events: [] };
173
+ });
174
+ app.get("/api/files", async () => {
175
+ if (!filesRoot) return { enabled: false, tree: [] };
176
+ return { enabled: true, tree: buildFileTree(filesRoot) };
177
+ });
178
+ app.get("/api/files/content", async (request, reply) => {
179
+ if (!filesRoot) {
180
+ return reply.status(503).send({ error: "File browsing is not configured." });
181
+ }
182
+ const parsed = fileContentQuerySchema.safeParse(request.query);
183
+ if (!parsed.success) {
184
+ return reply.status(400).send({ error: "Query must include ?path=<relative path>." });
185
+ }
186
+ let absolutePath;
187
+ try {
188
+ absolutePath = resolveSqlFilePath(filesRoot, parsed.data.path);
189
+ } catch (error) {
190
+ if (error instanceof InvalidFilePathError) {
191
+ return reply.status(400).send({ error: error.message });
192
+ }
193
+ throw error;
194
+ }
195
+ if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) {
196
+ return reply.status(404).send({ error: "File not found." });
197
+ }
198
+ const content = {
199
+ path: parsed.data.path,
200
+ content: readFileSync(absolutePath, "utf-8")
201
+ };
202
+ return content;
203
+ });
204
+ if (options.webRoot && existsSync(join2(options.webRoot, "index.html"))) {
205
+ void app.register(fastifyStatic, { root: options.webRoot });
206
+ app.setNotFoundHandler((request, reply) => {
207
+ if (request.raw.url?.startsWith("/api/")) {
208
+ return reply.status(404).send({ error: "Not found" });
209
+ }
210
+ return reply.sendFile("index.html");
211
+ });
212
+ }
213
+ return app;
214
+ }
215
+ async function startServer(options = {}) {
216
+ const app = createServer(options);
217
+ const port = options.port ?? DEFAULT_PORT;
218
+ const host = options.host ?? "127.0.0.1";
219
+ await app.listen({ port, host });
220
+ return {
221
+ app,
222
+ url: `http://${host}:${port}`,
223
+ close: () => app.close()
224
+ };
225
+ }
226
+ export {
227
+ createServer,
228
+ startServer
229
+ };
230
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/event-log.ts","../src/files.ts"],"sourcesContent":["/**\n * Local HTTP server for Humb, built on Fastify.\n *\n * Exposes a small JSON API the browser UI consumes, plus a health endpoint used for verification.\n * The server never talks to a database directly; it goes through a {@link DatabaseAdapter}.\n */\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport fastifyStatic from \"@fastify/static\";\nimport {\n DEFAULT_PORT,\n fileContentQuerySchema,\n redactConnectionString,\n rowsQuerySchema,\n runQuerySchema\n} from \"@humbdb/core\";\nimport type {\n ConnectionTarget,\n ConsoleEvents,\n FileContent,\n FilesOverview,\n HealthResponse\n} from \"@humbdb/core\";\nimport { ReadOnlyViolationError } from \"@humbdb/driver-contract\";\nimport type { DatabaseAdapter } from \"@humbdb/driver-contract\";\nimport Fastify from \"fastify\";\nimport type { FastifyInstance } from \"fastify\";\nimport { EventLog } from \"./event-log.js\";\nimport { buildFileTree, InvalidFilePathError, resolveSqlFilePath } from \"./files.js\";\n\nexport interface CreateServerOptions {\n /** The connected (or to-be-connected) database adapter. */\n adapter?: DatabaseAdapter;\n /** The parsed connection target, for diagnostics (credentials are redacted in responses). */\n target?: ConnectionTarget;\n /** Enable Fastify's logger. Defaults to false (the CLI configures logging). */\n logger?: boolean;\n /**\n * Directory containing the built `apps/web` static assets (its `index.html` and bundle).\n * When provided and it exists, the server serves the browser UI itself so `npx humb <target>`\n * has something to open. When omitted, only the `/api/*` routes are registered.\n */\n webRoot?: string;\n /**\n * Absolute path to the one directory the Files tab may read `.sql` files from (the `--files-dir`\n * CLI flag, resolved and validated at startup). Omitted means file browsing is disabled - see\n * docs/product-specs/dashboard-ui.md's \"Files tab security boundary\".\n */\n filesRoot?: string;\n}\n\nfunction requireAdapter(adapter: DatabaseAdapter | undefined): DatabaseAdapter {\n if (!adapter) {\n throw Object.assign(new Error(\"No database connection is configured.\"), { statusCode: 503 });\n }\n return adapter;\n}\n\n/** Build (but do not start) the Humb HTTP server. */\nexport function createServer(options: CreateServerOptions = {}): FastifyInstance {\n const app = Fastify({ logger: options.logger ?? false });\n const { adapter, target, filesRoot } = options;\n const eventLog = new EventLog();\n let lastKnownStatus: HealthResponse[\"database\"] | undefined;\n\n app.get(\"/api/health\", async (): Promise<HealthResponse> => {\n let database: HealthResponse[\"database\"] = \"unconfigured\";\n if (adapter) {\n database = (await adapter.ping().catch(() => false)) ? \"connected\" : \"disconnected\";\n }\n\n // Log only actual transitions, not every poll - and never the very first observation (that's\n // the baseline, not a notable event).\n if (lastKnownStatus !== undefined && lastKnownStatus !== database) {\n eventLog.log(\n database === \"connected\" ? \"info\" : \"warn\",\n database === \"connected\" ? \"Database connection restored.\" : \"Database connection lost.\"\n );\n }\n lastKnownStatus = database;\n\n const engineVersion =\n adapter && database === \"connected\" ? await adapter.getVersion().catch(() => null) : null;\n\n return {\n status: \"ok\",\n database,\n // A SQLite target's \"raw\" is a filesystem path, not a URL with credentials - nothing to\n // redact, and redactConnectionString would otherwise mask it as \"<unparseable...>\".\n target: target\n ? target.engine === \"sqlite\"\n ? target.raw\n : redactConnectionString(target.raw)\n : null,\n engineVersion\n };\n });\n\n app.get(\"/api/overview\", async () => {\n return requireAdapter(adapter).getOverview();\n });\n\n app.get<{ Params: { schema: string; table: string } }>(\n \"/api/tables/:schema/:table\",\n async (request) => {\n const { schema, table } = request.params;\n return requireAdapter(adapter).getTable(schema, table);\n }\n );\n\n app.get<{ Params: { schema: string; table: string }; Querystring: Record<string, string> }>(\n \"/api/tables/:schema/:table/rows\",\n async (request) => {\n const { schema, table } = request.params;\n const { page, pageSize } = rowsQuerySchema.parse(request.query);\n return requireAdapter(adapter).getRows(schema, table, page, pageSize);\n }\n );\n\n app.post<{ Body: unknown }>(\"/api/query\", async (request, reply) => {\n const parsed = runQuerySchema.safeParse(request.body);\n if (!parsed.success) {\n return reply.status(400).send({ error: \"Request body must be { sql: string }.\" });\n }\n const start = Date.now();\n try {\n const result = await requireAdapter(adapter).runReadOnlyQuery(parsed.data.sql);\n eventLog.log(\n \"info\",\n `Query executed in ${Date.now() - start}ms - ${result.rows.length} rows returned`\n );\n return result;\n } catch (error) {\n if (error instanceof ReadOnlyViolationError) {\n eventLog.log(\"warn\", `Query rejected: ${error.message}`);\n return reply.status(400).send({ error: error.message });\n }\n eventLog.log(\n \"error\",\n `Query failed: ${error instanceof Error ? error.message : String(error)}`\n );\n throw error;\n }\n });\n\n app.get(\"/api/console\", async (): Promise<ConsoleEvents> => {\n return { events: eventLog.list() };\n });\n\n app.delete(\"/api/console\", async (): Promise<ConsoleEvents> => {\n eventLog.clear();\n return { events: [] };\n });\n\n app.get(\"/api/files\", async (): Promise<FilesOverview> => {\n if (!filesRoot) return { enabled: false, tree: [] };\n return { enabled: true, tree: buildFileTree(filesRoot) };\n });\n\n app.get<{ Querystring: Record<string, string> }>(\"/api/files/content\", async (request, reply) => {\n if (!filesRoot) {\n return reply.status(503).send({ error: \"File browsing is not configured.\" });\n }\n const parsed = fileContentQuerySchema.safeParse(request.query);\n if (!parsed.success) {\n return reply.status(400).send({ error: \"Query must include ?path=<relative path>.\" });\n }\n\n let absolutePath: string;\n try {\n absolutePath = resolveSqlFilePath(filesRoot, parsed.data.path);\n } catch (error) {\n if (error instanceof InvalidFilePathError) {\n return reply.status(400).send({ error: error.message });\n }\n throw error;\n }\n\n if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) {\n return reply.status(404).send({ error: \"File not found.\" });\n }\n\n const content: FileContent = {\n path: parsed.data.path,\n content: readFileSync(absolutePath, \"utf-8\")\n };\n return content;\n });\n\n if (options.webRoot && existsSync(join(options.webRoot, \"index.html\"))) {\n void app.register(fastifyStatic, { root: options.webRoot });\n app.setNotFoundHandler((request, reply) => {\n if (request.raw.url?.startsWith(\"/api/\")) {\n return reply.status(404).send({ error: \"Not found\" });\n }\n return reply.sendFile(\"index.html\");\n });\n }\n\n return app;\n}\n\nexport interface StartServerOptions extends CreateServerOptions {\n port?: number;\n host?: string;\n}\n\nexport interface RunningServer {\n app: FastifyInstance;\n url: string;\n close: () => Promise<void>;\n}\n\n/** Build and start the Humb HTTP server, listening on localhost. */\nexport async function startServer(options: StartServerOptions = {}): Promise<RunningServer> {\n const app = createServer(options);\n const port = options.port ?? DEFAULT_PORT;\n const host = options.host ?? \"127.0.0.1\";\n await app.listen({ port, host });\n return {\n app,\n url: `http://${host}:${port}`,\n close: () => app.close()\n };\n}\n","import type { ConsoleEvent, ConsoleEventLevel } from \"@humbdb/core\";\n\nconst MAX_EVENTS = 200;\n\n/**\n * A bounded, in-memory log of recent connection/query events for the Console tab - no persistence\n * requirement (docs/product-specs/dashboard-ui.md). A plain array with the oldest entry dropped\n * once the cap is hit, not a literal ring-buffer data structure - 200 is far too small a bound for\n * that complexity to pay for itself.\n */\nexport class EventLog {\n private events: ConsoleEvent[] = [];\n private nextId = 1;\n\n log(level: ConsoleEventLevel, message: string): void {\n this.events.push({ id: this.nextId++, timestamp: new Date().toISOString(), level, message });\n if (this.events.length > MAX_EVENTS) {\n this.events.shift();\n }\n }\n\n list(): ConsoleEvent[] {\n return this.events;\n }\n\n clear(): void {\n this.events = [];\n }\n}\n","/**\n * Read-only filesystem access for the Files tab (DF-06). The security boundary this implements is\n * documented in docs/product-specs/dashboard-ui.md's \"Files tab security boundary\" section - read\n * that before changing this file.\n */\nimport { readdirSync } from \"node:fs\";\nimport { extname, join, relative, resolve, sep } from \"node:path\";\nimport type { FileNode } from \"@humbdb/core\";\n\nconst SQL_EXTENSION = \".sql\";\nconst SKIPPED_DIR_NAMES = new Set([\"node_modules\"]);\n\nfunction toPosixPath(path: string): string {\n return path.split(sep).join(\"/\");\n}\n\nfunction walk(rootDir: string, dir: string): FileNode[] {\n const entries = readdirSync(dir, { withFileTypes: true });\n const nodes: FileNode[] = [];\n\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || SKIPPED_DIR_NAMES.has(entry.name)) continue;\n const absolutePath = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n const children = walk(rootDir, absolutePath);\n if (children.length > 0) {\n nodes.push({\n name: entry.name,\n path: toPosixPath(relative(rootDir, absolutePath)),\n type: \"directory\",\n children\n });\n }\n } else if (entry.isFile() && extname(entry.name).toLowerCase() === SQL_EXTENSION) {\n nodes.push({\n name: entry.name,\n path: toPosixPath(relative(rootDir, absolutePath)),\n type: \"file\"\n });\n }\n // Symlinks are neither isDirectory() nor isFile() (Dirent doesn't follow them) and are\n // silently excluded - see the security boundary doc for why this matters.\n }\n\n return nodes.sort((a, b) =>\n a.type !== b.type ? (a.type === \"directory\" ? -1 : 1) : a.name.localeCompare(b.name)\n );\n}\n\n/** Recursively builds a tree of `.sql` files under `rootDir`, pruning directories with none. */\nexport function buildFileTree(rootDir: string): FileNode[] {\n return walk(rootDir, rootDir);\n}\n\n/** A client-supplied file path failed the Files tab's security boundary. Maps to HTTP 400. */\nexport class InvalidFilePathError extends Error {}\n\n/**\n * Resolves a client-supplied relative path to an absolute path within `rootDir`, rejecting `..`\n * traversal, non-`.sql` extensions, and anything that resolves outside `rootDir`.\n */\nexport function resolveSqlFilePath(rootDir: string, relativePath: string): string {\n if (relativePath.split(/[/\\\\]/).includes(\"..\")) {\n throw new InvalidFilePathError(\"Path must not contain '..' segments.\");\n }\n if (extname(relativePath).toLowerCase() !== SQL_EXTENSION) {\n throw new InvalidFilePathError(\"Only .sql files can be previewed.\");\n }\n\n const absolutePath = resolve(rootDir, relativePath);\n const rootWithSep = rootDir.endsWith(sep) ? rootDir : rootDir + sep;\n if (!absolutePath.startsWith(rootWithSep)) {\n throw new InvalidFilePathError(\"Path escapes the files directory.\");\n }\n\n return absolutePath;\n}\n"],"mappings":"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","names":["join","join"]}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@humbdb/server",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "Local HTTP server for Humb (Fastify).",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "@fastify/static": "^9.1.3",
20
+ "fastify": "^5.2.0",
21
+ "@humbdb/core": "0.0.1",
22
+ "@humbdb/driver-contract": "0.0.1"
23
+ },
24
+ "devDependencies": {
25
+ "@humbdb/config": "0.0.0"
26
+ },
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "vitest run",
32
+ "clean": "rimraf dist .turbo"
33
+ }
34
+ }