-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Welcome to the PatchBay wiki!
Documentation will be provided here until I get a dedicated docs site up and running.
- Middleware/plugins
- This feature is coming soon, it will be called "modifiers". A
Modifier
is run for everyRequest
orResponse
to/from a router (including the main router). - It will have a similar API to
Patch
, but returning the same type it takes in – instead of(Request) => Response
, it would be(Request) => Request
for an entry modifier or(Response) => Response
for an exit modifier. However, you will be able to throw aResponse
from an entry modifier.
- This feature is coming soon, it will be called "modifiers". A
- Integration with an ORM
- Objection.js seems like a good choice
- Faster route searching
- PatchBay uses JavaScript RegExp to find routes, which can be slow compared to other regex engines – at least in V8/Node. If there’s a need for better regex performance, I’d like to try using Bun:ffi to “drop in” a potentially faster regex engine like Rust's.
I recommend reading this whole section before reading the in-depth docs.
There are four major concepts you'll need to know to begin:
-
PBRequest
is a class that extendsRequest
(so you can use it just like any regularRequest
) and adds in an important property calledPBurl
for PatchBay's internal use. This property is writable – though it is not a good idea to write to it. Note: In 0.1.5, PBRequest will be removed from the public API, it will be replaced with regularRequest
s. -
Patch
is an abstract class that you will extend to build your logical components. -
Router
is an abstract class that is essentially a "folder" ofPatchable
s. You will likely not be extending it, but rather using the provided utility classes that extend it. -
Patchable
is an interface defining any route that can be put into a router. APatchable
just stores its own route and a single function that takes aPBRequest
and returns aResponse
.Patch
andRouter
both implementPatchable
... meaning Routers can have recursive Routers inside them (and they commonly will). You probably won't be using this interface to build your own components, but you can totally prove me wrong on that!
The starting point for your app is the bay.ts file. This module has a default export which we'll call the main bay. In a new project, your main bay looks like this:
const mainBay: MainBay = {
baseURL: "http://localhost:3000",
port: 3000,
patches: [
new StaticAssetRouter("/", "./dist"),
new UserPage("/{username}{queryString}")
]
}
export default mainBay;
Let's break that down! First we've got the baseURL
property, a string which lets the main router know where it
lives. Then we have the port
property, a number which tells Bun what port to serve on.
Then, things get interesting with patches
. This is an array of any objects implementing Patchable
, which the main
router will use to route top-level requests. Our only patchable in there right now is a StaticAssetRouter
, a utility
class that serves the assets inside a given directory (in this case "./dist"
). Notice how we construct a new
StaticAssetRouter
anonymously, and it just lives in the array. This is how nearly all patchable components will be
created. The first parameter we pass to the StaticAssetRouter
constructor is its route, "/"
. This means what you'd
expect it to mean – the router will serve these assets at the top level (e.g. http://localhost:3000/index.html
).
The next Patchable is UserPage
, the class extending Patch
that you can see above the main bay. Notice how the route
contains curly brackets. These are capture groups ("wildcards") that allow you to use any content inside them inside a Patch.
Why would you want to do this instead of using a router? Well, a router is essentially a "dumb" component; it just passes requests
onward based on the route. But sometimes the URL contains information you want to use, such as a username. A router can't
do anything with that information...
Instead, that data needs to land in a Patch that can do something useful like request the user's data from a database and
display it in a template. These route parameters can be accessed in a Patch via the instance property routeParameters
,
but only after being parsed with this.parseRouteParams(req)
– this is to remove overhead where you don't need it (a
running theme you'll notice when programming with PatchBay).
There is also a special route parameter you can specify at the end of a route, {queryString}
. If you include this, parseRouteParams
will also initialize the instance property queryStringParameters
to access the query string (if it exists) as key-value pairs just like the route parameters.
You can look at the UserPage
class to see these properties in action.
An important question remains: if both these patches operate on the top-level, how can the main router know whether index.html
is a file or a username? This is where an important aspect of PatchBay comes in: fall-through behavior. Routers search for routes
in a linear fashion, picking the first one that matches the URL. If nothing matches, by default it will hand back control to its
parent router to continue searching.
Say that the URL being requested is http://localhost:3000/johnsmith
. Although both StaticAssetRouter
and UserPage
have top-
level routes (since route parameters don't count as concrete values in the route), the StaticAssetRouter
comes first in the main
router's Patchable array, so it gets to check this request first. When it looks through all of its Patchables and doesn't see one
matching "/johnsmith", it knows this request is not for itself, and hands control back to the main router. The main router continues
on to the UserPage Patch – which has a route starting with a wildcard – so it will hand this request off to the Patch, which will be
its last stop. The UserPage gladly accepts this request, processes it, and returns a template response for the user's homepage.
The launch.ts
file is the entrypoint for a PatchBay app, as well as its configuration file. Its only required functionality is to instantiate the PBApp
and tell it to start serving. That's all it needs to do, and out of the box that's all it will do. We've kept it simple on purpose, so you have a mostly-blank slate to easily do things like declare a global database instance, set options for the Bun server, and so on.