Comparing version 0.1.3 to 0.1.4
{ | ||
"name": "funclify", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "src/index.ts", |
@@ -5,2 +5,4 @@ # Funclify 🤖 | ||
Currently, this is a **TypeScript-only** framework. This reflects the current focus of the project, being a type-safe and DX focused package, and in the future it may be updated to compile to support ESM. It's early days, so please forgive the narrow-focus. | ||
## ⚠️ Just one thing! | ||
@@ -10,2 +12,12 @@ | ||
## Install | ||
```shell | ||
# pnpm | ||
pnpm add funclify | ||
# npm | ||
npm install funclify | ||
``` | ||
## Basic Use | ||
@@ -27,1 +39,61 @@ | ||
``` | ||
### Typed Route Parameters | ||
Funclify is focused on being a strongly-typed framework. This extends to route parameters. Making heavy use of `infer`, we can create a strongly-typed `params` property that lives on the `req` argument passed to your route handler. | ||
```ts | ||
api.get("/users/:user_id/orders/:order_id", async({ params }, res) => { | ||
// params: { user_id: string, order_id: string } | ||
const { user_id, order_id } = params; | ||
const order = await fetchOrder(user_id, order_id); | ||
return res.withJSON(order); | ||
}) | ||
``` | ||
## Testing | ||
Funclify comes bundled with a test harness to make it simple to run integration tests against your API. | ||
Although you could adopt a more "unit" approach, the framework is built to encourage testing to the boundary of your application for each and every API route. | ||
An example below utilising Vitest | ||
```ts | ||
import { describe, it, beforeEach, expect } from "vitest"; | ||
import { ApiTestHarness } from "funclify"; | ||
import { api } from "../functions/api"; | ||
describe("API", () => { | ||
let test: ApiTestHarness<typeof api>; | ||
beforeEach(() => { | ||
// This could be set once rather than in before-each, as | ||
// in theory an API should be idempotent. However, for flexibility | ||
// atomicity can be guaranteed by initialising in the beforeEach | ||
test = new ApiTestHarness(api); | ||
}); | ||
it("should return a user object", async () => { | ||
// Perform the request. Under the hood, this | ||
// emulates the `event` and `context` fed in | ||
// from a Netlify Function | ||
const response = await test.get("/user/123"); | ||
expect(response.statusCode).toBe(200); | ||
// Regardless of your application output, the response | ||
// from a Netlify Function will be a string, so we need | ||
// to parse into JSON to assert on the returned objects | ||
expect(response.body).toBeTypeOf("string"); | ||
const body = JSON.parse(response.body!); | ||
expect(body).toContain({ | ||
id: "123", | ||
name: "Ed", | ||
}); | ||
}); | ||
}); | ||
``` |
@@ -84,5 +84,9 @@ import { HandlerContext } from '@netlify/functions'; | ||
}): InternalRouteHandler { | ||
let routeUrl = url; | ||
if (url.endsWith('/')) { | ||
routeUrl = url.slice(0, -1) as TPath; | ||
} | ||
const route: InternalRouteHandler = { | ||
method, | ||
urlPattern: new UrlPattern(url), | ||
urlPattern: new UrlPattern(routeUrl), | ||
handlers, | ||
@@ -98,3 +102,8 @@ }; | ||
const components = url.split('/'); | ||
if (routeUrl === '/') { | ||
this.routeTree.routeHandler = route; | ||
return route; | ||
} | ||
const components = routeUrl.split('/'); | ||
let current = this.routeTree; | ||
@@ -140,4 +149,22 @@ | ||
const components = url.split('/').slice(1); | ||
console.log({ components }); | ||
const [current, ...rest] = components; | ||
console.log({ tree }); | ||
if (url === '/') { | ||
if (tree.routeHandler) { | ||
if ( | ||
tree.routeHandler.method === method && | ||
tree.routeHandler.urlPattern.match(fullUrl) | ||
) { | ||
return { | ||
matchedRoute: tree.routeHandler, | ||
params: tree.routeHandler.urlPattern.parse(fullUrl), | ||
handlers: tree.routeHandler.handlers, | ||
}; | ||
} | ||
} | ||
} | ||
// Try a direct match | ||
@@ -255,2 +282,22 @@ const node = tree.children?.[current]; | ||
} | ||
public GetAllRoutes(): InternalRouteHandler[] { | ||
const routes: InternalRouteHandler[] = []; | ||
const recurse = (tree: RouteTree) => { | ||
if (tree.routeHandler) { | ||
routes.push(tree.routeHandler); | ||
} | ||
if (tree.children) { | ||
for (const child of Object.values(tree.children)) { | ||
recurse(child); | ||
} | ||
} | ||
}; | ||
recurse(this.routeTree); | ||
return routes; | ||
} | ||
} |
@@ -63,3 +63,6 @@ import { ZodSchema, z } from 'zod'; | ||
export type RouteMiddleware = RouteHandler<any, any>; | ||
export type RouteMiddleware<TPath extends string = any> = RouteHandler< | ||
TPath, | ||
any | ||
>; | ||
@@ -66,0 +69,0 @@ export interface InternalRouteHandler { |
43288
1048
97