Skip to content

Commit

Permalink
Merge branch 'main' into playground
Browse files Browse the repository at this point in the history
  • Loading branch information
brettimus committed Jan 16, 2025
2 parents 7e0475b + 7aaa7cd commit 14cf7a1
Show file tree
Hide file tree
Showing 135 changed files with 10,580 additions and 776 deletions.
1,392 changes: 818 additions & 574 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Fiberplane Studio is a local tool for building and debugging Hono APIs. It can m

## Quick Start

For FastAPI/Python developers, we've got an experimental library in the [fpxpy](./fpxpy) folder.

Create Hono project

```sh
Expand Down Expand Up @@ -51,6 +53,7 @@ Visit `http://localhost:8788` to make requests to your api, and see your logs an

Studio is designed to be used in conjunction with the [`@fiberplane/hono-otel` client library](https://www.npmjs.com/package/@fiberplane/hono-otel). You can read more about what it does in [that project's README](./packages/client-library-otel/README.md).


## Contributing

See [`DEVELOPMENT.md`](./DEVELOPMENT.md) for more details on how to run the Studio locally. Please get in touch via GitHub issues, or on the [Fiberplane Discord](https://discord.com/invite/cqdY6SpfVR), if you have any feedback or suggestions!
Expand Down
60 changes: 0 additions & 60 deletions Tiltfile

This file was deleted.

7 changes: 6 additions & 1 deletion api/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const CONFIG_FILE_NAME = "fpx.v0.config.json";
// Paths to relevant project directories and files
const WRANGLER_TOML_PATH = findInParentDirs("wrangler.toml");
const PACKAGE_JSON_PATH = findInParentDirs("package.json");
const PYPROJECT_TOML_PATH = findInParentDirs("pyproject.toml");
// NOTE - Deno projects might also not necessarily have a deno.json
const DENO_CONFIG_PATH = findInParentDirs(["deno.json", "deno.jsonc"]);
const PROJECT_ROOT_DIR = findProjectRoot() ?? process.cwd();
Expand Down Expand Up @@ -84,7 +85,10 @@ async function runWizard() {
}

const MIGHT_BE_CREATING =
IS_INITIALIZING_FPX && !WRANGLER_TOML && !PACKAGE_JSON;
IS_INITIALIZING_FPX &&
!WRANGLER_TOML &&
!PACKAGE_JSON &&
!PYPROJECT_TOML_PATH;

logger.debug("MIGHT_BE_CREATING", MIGHT_BE_CREATING);

Expand Down Expand Up @@ -389,6 +393,7 @@ function findProjectRoot() {
WRANGLER_TOML_PATH,
PACKAGE_JSON_PATH,
DENO_CONFIG_PATH,
PYPROJECT_TOML_PATH,
]);
if (!projectRoot) {
logger.debug("No project root detected");
Expand Down
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.13.0-canary.1",
"version": "0.13.0",
"name": "@fiberplane/studio",
"description": "Local development debugging interface for Hono apps",
"author": "Fiberplane<[email protected]>",
Expand Down
11 changes: 8 additions & 3 deletions api/src/lib/code-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function disableCodeAnalysis() {
}

// Getter function for RoutesResult, this is overwritten in setupCodeAnalysis
let _resultGetter = async (): Promise<RoutesResult> => {
let _resultGetter = async (): Promise<RoutesResult | undefined> => {
return Promise.reject(new Error("Routes not yet parsed"));
};

Expand All @@ -36,7 +36,7 @@ let _resultGetter = async (): Promise<RoutesResult> => {
* When we move into scenarios that require parallel analysis for routes, we will need to call `.clone` on the RouteResult
* to avoid race conditions on the returned object.
*/
export const getResult = async (): Promise<RoutesResult> => {
export const getResult = async (): Promise<RoutesResult | undefined> => {
return _resultGetter();
};

Expand Down Expand Up @@ -71,7 +71,12 @@ export function setupCodeAnalysis(monitor: RoutesMonitor) {
return monitor.lastSuccessfulResult;
}

throw new Error("Failed to get routes");
if (monitor.getFilesCount() > 0) {
logger.warn("Failed to get routes, but there are files to analyze");
throw new Error("Failed to get routes");
}

return undefined;
};

// Add a listener to start the analysis
Expand Down
19 changes: 16 additions & 3 deletions api/src/lib/proxy-request/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,29 @@ export async function executeProxyRequest(
}
}

export function removeUnsupportedHeaders(headers: Record<string, string>) {
const unsupportedHeaders = ["transfer-encoding"];
const validHeaders: Record<string, string> = {};
for (const headerName in headers) {
if (unsupportedHeaders.includes(headerName.toLowerCase())) {
logger.warn("Detected an unsupported header:", { headerName });
} else {
validHeaders[headerName] = headers[headerName];
}
}

return validHeaders;
}

function createProxyRequestFromNewAppRequest(
requestDescription: NewAppRequest,
) {
const { requestUrl, requestMethod, requestBody } = requestDescription;

let { requestHeaders } = requestDescription;

if (!requestHeaders) {
requestHeaders = {};
}
// Cleanup requestHeaders to remove unsupported headers
requestHeaders = removeUnsupportedHeaders(requestHeaders || {});

let validBody: Required<RequestInit>["body"] | null = null;
if (requestBody != null) {
Expand Down
60 changes: 33 additions & 27 deletions api/src/routes/app-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
executeProxyRequest,
handleFailedRequest,
handleSuccessfulRequest,
removeUnsupportedHeaders,
} from "../lib/proxy-request/index.js";
import type { Bindings, Variables } from "../lib/types.js";
import {
Expand Down Expand Up @@ -77,32 +78,37 @@ app.get("/v0/app-routes-file-tree", async (ctx) => {
const result = await getResult();

const routeEntries = [];
for (const currentRoute of routes) {
const url = new URL("http://localhost");
url.pathname = currentRoute.path ?? "";
const request = new Request(url, {
method: currentRoute.method ?? "",
});
result.resetHistory();
const response = await result.currentApp.fetch(request);
const responseText = await response.text();

if (responseText !== "Ok") {
logger.warn(
"Failed to fetch route for context expansion",
responseText,
);
continue;
}

const history = result.getHistory();
const routeEntryId = history[history.length - 1];
const routeEntry = result.getRouteEntryById(routeEntryId as RouteEntryId);
if (result) {
for (const currentRoute of routes) {
const url = new URL("http://localhost");
url.pathname = currentRoute.path ?? "";
const request = new Request(url, {
method: currentRoute.method ?? "",
});
result.resetHistory();
const response = await result.currentApp.fetch(request);
const responseText = await response.text();

if (responseText !== "Ok") {
logger.warn(
"Failed to fetch route for context expansion",
responseText,
);
continue;
}

routeEntries.push({
...currentRoute,
fileName: routeEntry?.fileName,
});
const history = result.getHistory();
const routeEntryId = history[history.length - 1];
const routeEntry = result.getRouteEntryById(
routeEntryId as RouteEntryId,
);

routeEntries.push({
...currentRoute,
fileName: routeEntry?.fileName,
});
}
}

const tree = buildRouteTree(
Expand Down Expand Up @@ -312,8 +318,9 @@ app.all(
const requestUrlHeader = proxyToHeader;

// NOTE - These are the headers that will be used in the request to the service
const requestHeaders: Record<string, string> =
constructProxiedRequestHeaders(ctx, headersJsonHeader ?? "", traceId);
const requestHeaders: Record<string, string> = removeUnsupportedHeaders(
constructProxiedRequestHeaders(ctx, headersJsonHeader ?? "", traceId),
);

// Construct the url we want to proxy to, using the query params from the original request
const requestQueryParams = {
Expand All @@ -326,7 +333,6 @@ app.all(
logger.debug("Proxying request to:", requestUrl);
logger.debug("Proxying request with headers:", requestHeaders);

// Create a new request object
// Clone the incoming request, so we can make a proxy Request object
const clonedReq = ctx.req.raw.clone();
const proxiedReq = new Request(requestUrl, {
Expand Down
4 changes: 4 additions & 0 deletions api/src/routes/inference/inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ app.post(
provider !== "ollama"
? await getResult()
.then(async (routesResult) => {
if (!routesResult) {
return [null, null];
}

const url = new URL("http://localhost");
url.pathname = path;
const request = new Request(url, { method });
Expand Down
5 changes: 3 additions & 2 deletions api/src/routes/traces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ app.get("/v1/traces", async (ctx) => {
});

const traceMap = new Map<string, Array<(typeof spans)[0]>>();

for (const span of spans) {
const traceId = span.inner.trace_id;
if (!traceId) {
Expand Down Expand Up @@ -137,7 +136,9 @@ app.post("/v1/traces", async (ctx) => {
);

try {
await db.insert(otelSpans).values(tracesPayload);
if (tracesPayload.length > 0) {
await db.insert(otelSpans).values(tracesPayload);
}
} catch (error) {
logger.error("Error inserting trace", error);
return ctx.text("Error inserting trace", 500);
Expand Down
15 changes: 10 additions & 5 deletions biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
}
},
{
"include": ["studio"],
"include": ["studio", "lilo/lilo-frontend"],
"linter": {
"enabled": true,
"rules": {
Expand Down Expand Up @@ -72,7 +72,8 @@
"packages/client-library-otel",
"api",
"honc-code-gen",
"fp-services"
"fp-services",
"lilo/lilo-worker"
],
"linter": {
"enabled": true,
Expand All @@ -92,19 +93,23 @@
],
"files": {
"ignore": [
// Python related
".venv",
".mypy_cache",
// API related
"meta/*.json",
// Honc code gen playground outputs
"honc-code-gen/outputs/**/*",
"honc-code-gen/storage/**/*",
// Client library and website related
"dist",
"lilo/lilo-frontend/dist",
"lilo/lilo-worker/public",
"www/",
".astro",
// ignore all tsconfig.json files
"tsconfig.json",
"tsconfig.node.json",
"tsconfig.app.json",
"tsconfig*.json",
// ignore the autogenerated routeTree.gen.ts file
"playground/src/routeTree.gen.ts",
// ignore any test data files
"test-data/*.js",
Expand Down
2 changes: 1 addition & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ allow = [
"MIT",
"MPL-2.0",
"OpenSSL",
"Unicode-DFS-2016",
"Unicode-3.0",
]
confidence-threshold = 0.8
exceptions = []
Expand Down
1 change: 1 addition & 0 deletions examples/python-fastapi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
1 change: 1 addition & 0 deletions examples/python-fastapi/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
14 changes: 14 additions & 0 deletions examples/python-fastapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# python-fastapi example

This project uses `uv`. To run:

```
$ uv sync --frozen
$ FPX_ENDPOINT=http://localhost:8788/v1/traces uv run fastapi dev main.py
```

Of course you also need to run studio next to it, for that you need [`npx`](https://docs.npmjs.com/cli/v7/commands/npx) . So you can run:

```
npx @fiberplane/studio
```
Loading

0 comments on commit 14cf7a1

Please sign in to comment.