Technical Manual
Return to main Reference page.
š§ In development. š§
This is a complete walkthrough and reference of how Kitten works from a technical perspective. Think of its as the equivalent of those old-school factory service manuals you would get for cars and even computers, back in the day.
One of the goals for the Technical Manual is to help anyone who wants to help improve Kitten to quickly get a conceptual model of how it works.
This manual is not necessary if all you want to do is to use Kitten to build Small Web apps and sites. For that, read the general reference guide and follow the tutorials. However, if you do decide to work your way through this manual, you will get a deeper understanding of how Kitten works under the hood.
š” The Technical Manual is not meant to be a line-by-line explanation of the code but more of a summary, although it will go into detail on the important bits.
While important third-party modules will be mentioned, for a full list of modules used and credits, please see the credits page.
The credits page is a combination of a manually-authored special thanks section and a package contributors section that is automatically generated by the contributors script on every build.
Installer
![Screenshot of the Kitten installation script running in a terminal window. Heading: š± Installing Kitten⦠⢠Web install detected. ⢠Installing latest Kitten distribution. ⢠Runtime v22.11.0 already exists, not updating. Installed. Following this, there are additional macOS instructions shown for updating your systemās path to add the location of the kitten binary under zsh and Fish shell and adding localhost aliases to your hosts file. Output ends with kitten [path to serve] and the terminal prompt following the scriptās exit.](images/installer.light.webp)
Kittenās installer is a single Bash script (install) that has customised behaviour depending on whether it is:
- invoked by being piped into Bash via a web install, or,
- run locally in a terminal.
š” Kitten (and its installer) is currently supported on Linux, macOS, and Windows. However, support for Windows might be removed in the future given the direction that OS has gone which is incompatible with the ethical principles that underlie Kitten. For greater context, Kitten is also not supported Chromebooks.
š” The installer script requires Bash version 5 or later to run. macOS, specifically, ships an ancient version of Bash that must be updated before the installer can be run. The installer will detect if your version of Bash is too old, provide instructions on how to upgrade your version of Bash, and refuse to run if so.
How it works
Download and install the Kitten runtime, if necessary.
The Kitten runtime is currently the latest available Node.js LTS release. By managing its own runtime, Kitten does not require Node.js to be installed on the machine and sidesteps versioning issues.
The installer uses either
curl
orwget
to download the runtime binary. If neither are available, it refuses to continue.š” The web-based single-line installation command defaults to
curl
for macOS andwget
for Linux. The former is guaranteed to ship with macOS. On Linux, there is no such guarantee so itās a best effort.Decide how to install Kitten.
a. If the script is being run interactively (i.e., during development), the
build
script is run to build Kitten from source. Also, if the--npm
flag is provided, the installer runsnpm install
to install dependencies.š” If you pass a positional argument to the installer script, it will be interpreted as the local path of a Kitten package file that you want to install. A Kitten package file is what is served from kittens.small-web.org for use when Kitten is being installed via the single-line installation command from the Web.
This feature lets you test these package files locally.
š” Note that the
npm
being run is Kittenās own npm from the runtime it downloaded in Step 1. A system-wide Node.js installation is not required to build Kitten from source or even to develop with Kitten.You can run Kittenās version of Node using the
kitten-node
command. Similarly, you can run Kittenās version of npm usingkitten-npm
. This will ensure youāre running the exact same versions that Kitten does when you invoke thekitten
command.b. If the script is being run from the web-based single-installation command (by being piped to Bash), then the build step is skipped. Instead, the requested Kitten package file for the requested Kitten version is downloaded and installed from the Kitten deployment site (source)
On Linux, check if privileged ports are enabled and disable them if so (so Kitten can run on ports 443 and 80).
š This requires
sudo
access and will prompt you for your password.š” Privileged ports is a concept that dates back to mainframe computers. It made sense back then, it doesnāt today. Linux is the only platform that holds onto this archaic concept.
One of the design principles behind Kitten is that Kitten should run the same regardless of whether youāre in a development or deployment environment without requiring the use of heavyweight technologies like Docker. So Kitten running with TLS locally and defaulting to ports 443 and 80 (for redirections) and behaving exactly is it does in deployment is a feature, not a bug.
Furthermore, Kitten is designed to run one site/app on one machine (VPS, single-board computer, etc.) Running multiple sites/apps on one Kitten install or running Kitten under Docket, etc., in deployment is not a planned or supported feature. Again, this is to keep things as simple as possible and make the deployment process (e.g., using Domain) as quick and seamless as possible to enable everyday people who use technology as an everyday thing to get started on the Small Web as easily and effortlessly as possible.
Provide platform-specific post-installation instructions, if necessary:
i. Add
~/.local/bin
to the system path (if it isnāt already there)to enable use of thekitten
command.ii. On macOS, to add localhost aliases for place1.localhost, place2.localhost, etc., to the hosts file. (These aliases are used when testing peer-to-peer Small Web apps locally during development.)
Show the syntax for running the
kitten
command and exit.
Kitten ābinaryā

The kitten
ābinaryā is a bash script installed at ~/.local/bin/kitten
that runs the main process using the Kitten runtime with loader customisations.
Specifically, it:
Changes the working directory from the folder the
kitten
command was run in to the Kitten distribution folderDecides whether to run the Kitten Process Manager or the Kitten bundle directly based on whether it is being run in development mode (default) or production mode (with the environment variable
PRODUCTION=true
).š” If the
FLAME_GRAPH=true
environmental variable is set, the Kitten bundle is run directly as if in production mode due to the 0x flame graph module not being compatible with Nodeās cluster module as used by the Kitten Process Manager.Launches Kittenās runtime (the latest Node.js LTS binary as downloaded by the Kitten installer) to run the main process using the EcmaScript Module loading customisations found in the loader script, setting:
QUIET=true
to disable console output from some of the modules used by Kitten (like Auto Encrypt).NODE_OPTIONS='--require=./suppress-experimental.cjs'
to load in code to turn off warnings for the experimental Node features Kitten uses so as not to pollute the toolās output.0x -0 --
if theFLAME_GRAPH=true
environment variable was set.--inspect
if theINSPECT=true
environment variable is set.Kittenās
--working-directory
to the directory that thekitten
command was run from.
Main process
The main process is different based on whether Kitten is running in development or production:
In development: The main process is the Kitten Process Manager. It manages the creation of Kitten processes and responds to restart requests from Kitten.
In production: The main process is the Kitten bundle. Kitten is run as a systemd daemon and it handles restart requests, etc.
Kitten Process Manager
The Kitten Process Manager runs the Kitten bundle in a separate process using the Node cluster module to automatically restart it on restart requests (e.g., when the source code of the app/site being served changes).
Specifically, it:
- Creates a new Kitten process for the Kitten bundle.
- Listens for the
exit
event on the process. - If the exit code is
99
, which is Kittenās code for a restart request, goes back to 1. - For any other exit code, quits.
š” Basically, Kitten Process Manager does what we would use a module like Nodemon for but in a very focussed manner in ~20 lines of code.
Loader script
The loader script uses the experimental ES Module Loaders feature (soon, module customisation hooks).
Specifically, it:
Resolves files with custom Kitten extensions (
.page.js
,.get.js
,.post.js
,.socket.js
,.component.js
,.lyaout.js
,.fragment.js
,.script.js
,.styles.js
, etc.) to JavaScript modules.Wraps non-Javascript fragments in
.fragment.md
andfragment.css
files as well as non-JavaScript pages in.page.md
files with JavaScript wrappers to render them as Kitten HTML.
š”Using compound extensions enables Kitten to apply specialised logic to different types of files while enabling authoring tools (editors, etc.) to work with them without requiring any additional tooling (e.g., a custom language server).
š” The loader loads before the main process so that the loader hooks are available and used for all imports in Kitten itself as well as the app/site Kitten is serving. The loader runs in a separate realm to the Kitten bundle. Currently, there is no inter-process communication between the two realms and, given the design of Kitten, it is not likely that there will need to be in the future either.
Kitten bundle
Kitten is always run from the Kitten bundle created using the build
script and installed using the installer. This ensures that what weāre testing in development is exactly the same as what weāll be running in production.
The entrypoint to the Kitten bundle is the main script.
Main script
The main script is the entrypoint to the Kitten bundle and handles basic bootstrapping tasks before launching the Command-Line Interface (CLI).
Specifically, it:
Creates the global
kitten
object and populates it with theversion
property. (Other properties are added later during Kittenās start-up process.)Monkeypatches
console.debug
so that output sent to it is only shown on the console if theVERBOSE=true
environment variable is set e.g., if Kitten is started withVERBOSE=true kitten
.Runs any migrations for Kittenās internal databases, if necessary.
Initialises Kittenās global internal database.
Instantiates the CLI.
Command-Line Interface (CLI)
The Kitten Command-Line Interface (CLI) parses the arguments passed to Kitten command, if any, and configures and runs different commands based on them.
It uses the lightweight sade module to carry out the parsing.
When Kitten is invoked without any arguments, the default behaviour is to run the default serve
command on the current working directory.
š” To see the full list of available commands, use the
kitten help
command. You can also find the usage information in the Command-line Interface (CLI) section of the usage guide, and each command is also covered in the Kitten Commands section of the Technical Manual, below.
All commands display a common Kitten header.
Kitten Header
The Kitten header is displayed at the top of every CLI command.
In the development environment, and if the terminal supports it, a sixel image of the Kitten logo is displayed at the start of the header. Otherwise, a text fallback with the Kitten emoji is displayed (āš± Kittenā).
Following this is the author credit, detailed version information, a [link to the funding page] for Small Technology Foundation, and a help section with links to the Kitten tutorials, reference, issues.
Coloured output is provided via the chalk module and terminal links are created using the terminal-link module.
Kitten Commands
This is a full list of the Kitten Commands, detailing what they do and how they do it.
Serve
The serve
command is the default command. So kitten serve
and kitten
are equivalent.
It attempts to create and initialise a Kitten Server, applying any passed Kitten server options, like the domain and port to serve on, any domain aliases, etc.
Prior to launching the Kitten Server, the serve
command calculates the base path for the app/site being served as the relative path between the working directory and the path to serve, if any, that was passed as a Kitten Server option and changes the current working directory to that base path.
- [] TODO: Document the other commands
Kitten Server
The Kitten Server contains the primary functionality of Kitten and is a singleton that is asynchronously constructed via its getInstanceAsync()
method.
Alongside the html parser and the page route class, it is among the densest of the files in Kitten. (And it might make sense to refactor it further to break it up more in the future to improve maintainability.)
The Kitten server uses Polka.
Source code:
Construction/configuration flow
As the Kitten Server is a singleton, you get a reference to it via its static factory method. Furthermore, since the initialisation of the Kitten Server instance is asynchronous, so is this factory method.
const server = await Server.getInstanceAsync(options)
The factory method returns a promise that either resolves immediately with a reference to the existing Kitten Server instance or does so after constructing and asynchronously initialising one.
Construction (synchronous)
- [] TODO: Add flowchart
The Kitten Server constructor handles any synchronous tasks it can and returns a partially configured Kitten Server instance. To fully configure the Kitten Server instance, asynchronous tasks are run in the asynchronous initialisation method.
šļø Attempting to call the constructor directly will result in an error. Await the
getInstanceAsync(options)
method instead to get a reference to the Kitten Server singleton.
Source code:
The constructor handles a number of tasks, key among them, it:
Determines which main domain and port to run the server under and which domain aliases, if any, to also respond to.
Populates the global
kitten
object with the maindomain
andport
and, by initialising the globals helper, many others.š” The most common Kitten global is the
kitten.html
tagged template string that renders your Kitten markup. Other globals that are useful for authoring include thekitten.markdown
tagged template string for rendering Markdown and thekitten.md()
function for direct access to Kittenās MarkdownIt instance for more advanced, dynamic use cases, thekitten.events
EventEmitter, for listening for and dispatching global events, and thekitten.safelyAddHtml()
function for allowing a subset of safe tags in untrusted HTML content.Another important Kitten global is the
kitten.db
reference to your appās database and thekitten._db
reference to Kittenās internal database (which persists data related to things Kitten handles for you, like Sessions). These references, however, are added later in the asynchronous initialisation stage.The easiest way to see whatās available on the global
kitten
object is to use the.ls
command while running Kittenās interactive shell (REPL).Starts populating the serverās
options
property with thedomains
,settingsPath
(the path to Kittenās TLS directory where certificates are automatically generated and stored), and, if provided, thedomainToken
(from the--domain-token
command-line argument) andsmallWebHostDomain
(from the--small-web-host-domain
command-line argument).š” The last two properties are set if the Kitten server is being set up by a Small Web host running Domain. They facilitate communication between the Small Web host and the Small Web site/app running Kitten.
Creates the Polka app.
Configures the Polka app, setting up various middleware.
š Middleware
Extends the Polka app using express-busboy to handle multi-part form parsing and uploads.
Domains and domain aliases
The Kitten Server determines the main domain and port, as well as any domain aliases, if any, to respond to based on convention and the values present in the --domain
, --port
, and --aliases
command-line options.
Source code:
Convention:
If a domain is not explicitly specified using the
--domain
command-line option, the default domain oflocalhost
is used.If the domain is
localhost
, we also respond to the following aliases:place1.localhost
,place2.localhost
,place3.localhost
, andplace4.localhost
.These localhost aliases are used when testing peer-to-peer Small Web apps locally. Note that when you are running servers at these aliases, each one must have its own unique port.
If a port is not explicitly specified using the
--port
command-line option, the default port of 443 is used.If the
--port
option is passed without a value, or, if its value is set to0
(zero), a random, available port is used.If the domain is not
localhost
and a list of one or more comma-separated domains (without spaces between them) is provided as the value of the--aliases
option, the server also responds to these domains.If the shorthand domain alias
www
is passed in the--aliases
list, it is rewritten to its fullwww.<main domain>
value.
Middleware
The Kitten Server makes use of a middleware that act on incoming requests before any routes do.
š” Middleware often modifies the
request
andresponse
objects that routes receive to inject functionality that is useful during authoring.
š” The list, below, is presented in order of initialisation. Order matters in middleware as those initialised earlier will intercept the request earlier.
Third-party middleware:
First-party middleware (Kitten-specific middleware):
stats: Logs referrers and hits to the
stats
table in Kittenās internal database (kitten._db
). Note that these are entirely anonymous aggregate statistics; no personal information ā e.g., IP addresses, etc. ā is recorded.request-and-response-helpers: adds request and response helpers to aid authoring to the
request
andresponse
objects received by routes.trailing-slashes: Adds trailing slash to all URL paths that need one. So,
/hello
will become/hello/
, for example. This aids authoring by avoiding issues with relative URLs breaking when the path is missing a trailing slash (e.g.,<script src='./index.js'></script>
).sessions: Mixes in a
session
object to the incomingrequest
from Kittenās automatic session handling.authentication: Implements Kittenās automatic authentication handling whereby Kitten handles authentication for any route that begins with a lock emoji (š).
websocket: Finally, if running in a development environment, a WebSocket endpoint for handling development-time live reloads is created at the
/š±/development-socket
route using the websocket middleware.
Initialisation (asynchronous)
- [] TODO: Add flowchart
šļø Attempting to call the
initialise()
method directorly will result in an error. Await thegetInstanceAsync(options)
method instead to get a reference to the Kitten Server singleton.
Following the initial synchronous construction of the server instance, the singleton factory method awaits the serverās [async initialise()] method.
This method finishes configuring the Kitten Server by handling any asynchronous tasks that need to be done.
Specifically, it:
Initialises Kittenās internal database (
kitten._db
)In production, initialises the app repository. This is a working copy of the git repository that the Kitten app was deployed from. It is used by Kitten to handle versioning and updates for deployed Kitten apps/sites.
In production, sets up automatic updates for Kitten.
Checks to see if a cryptographic identity ā their ed25519 keys, and, specifically, their secret key encoded in KittenMoji format ā has been generated for the person who owns this Kitten instance. If one does not exist and this Kitten instance was deployed by Domain and a domain token was provided by the Domain host, it is stored. Otherwise, a new domain token is generated.
š” The domain token is a cryptographically-secure random string that forms part of the path for generating a personās cryptographic identity for a particular Kitten instance.
(A Kitten instance is defined by the unique combination of the appās location on the local disk as well as the
<domain>:<port>
that it is being run at.)Checks to see if a Small Web Host domain has been specified and persists it, if so. (This is used for communication with the Domain instance that set up a Kitten instance, if thatās how the Kitten instance was set up.)
Initialises the
KittenPackage
singleton that enables a Kitten instance to provide the manual Kitten updates feature from its Settings section at /š±/settings/.š” The /š±/settings/ section is, itself, part of an internal Kitten app that all Kitten instances serve alongside the primary app/site they are serving.
The /š±/ namespace sepearates internal Kitten routes from those of the Small Web app that Kitten is serving.
Similarly, the /š/ namespace is used to implement Small Web Protocol routes (š).
(This is similar to how .well-known routes operate elsewhere but this is the Small Web and Kitten weāre talking about here so instead of a cryptic prefix, we use emoji. Because, why not?)
Source code: https://codeberg.org/kitten/app/src/branch/main/web
Installs Node modules, if necessary, for the Kitten app/site being served as well as for any app modules it might contain. Node modules are installed using the
npm ci
command from Kittenās own version of npm from its runtime.Initialises the App Database. This is the database for the Kitten app/site that Kitten is serving, accessible from the global
kitten.db
reference.If there is a main.script.js file in the project, imports and awaits it (as it could be asynchronous).
š” This optional script is used in Kitten projects to perform global setup tasks.
Initialises the routes for the app/site being served.
š Routes
Initialises the routes for Kittenās own internal web app.
Adds first the routes from the project being served and then the routes from Kittenās own internal web app to the Polka server.
š” The order is important: by adding Kittenās own routes after the those of the app/site being served, Kitten ensures that authors can overwrite its own routes should they want to.
This is not recommended, but it is possible, should you need to for some reason.
Sets the minimum TLS version of the server to TLS 1.3.
Creates an https server using the Polka handler along with either auto-encrypt or auto encrypt localhost depending on wether the server is running on localhost or a non-local domain.
Creates a WebSocket server (using ws) and starts handling upgrade events.
The implementation mixes in a
ws
closure to incoming request objects, which, when invoked, returns a promise that resolves to an upgraded WebSocket connection using the Kitten Serverās web socket server instance.š” The WebSocket implementation in Kitten is based on express-websocket and tinyws.
Itās designed to keep some of the modern interface of tinyws (async/await syntax) while avoiding the client connection timeout issue in tinyws.
Starts listening at the specified port.
Populates the global
kitten.app
object with references to the Polka app (kitten.app.router
) and the server instance itself (kitten.app.server
).Displays information about the domain the server is running at (and any aliases), a link to the path where the source code of the app/site being served is on disk, as well as a link to the data folder for the app/site being served (where the databases as well as any uploads are kept).
If a cryptographic identity hasnāt been created for the person who owns this Kitten instance yet, displays instructions for how the person can create one by following the provided link.
If Kitten is running interactively in a terminal, displays instructions for how to launch the interactive shell (REPL).
Informs the person that the server is running.
If an app repository exists for the app/site being run, displays its version.
If Kitten was launched with the
--open
flag, opens the system browser at the address where the app/site is being served.Instantiates the Global Kitten Database.
Checks the Global Kitten Database to see if Kitten is currently being deployed by Domain. If it is, it informs the Small Web Host running Domain that the server has successfully launched. (It also unsets that global flag if it was set so as not to impact future launches of the server.)
Finally, it starts the Kitten REPL.
Routes
The Routes class manages the asychronous initialisation of the static and dynamic routes at a given base path.
The default base path is that of the primary web app/site that Kitten is serving and is kept in the basePath
environment variable.
Routes.getRoutes(basePath = process.env.basePath)
The bulk of the work in instantiating server routes is split between the Routes
class and the Files
class it uses.
š Files class
The Routes
initialiser enumerates the collection of files by extension type returned by the Files
class and does two things:
If run in a development environment, listens for the
file
event that fires when a file within the collection has changed.Creates one of the following two route types based on the type of file:
Static Routes
All static routes are of type StaticRoute and expose a very simple GET
handler
that uses the connect-static-file module to serve the file.
š”
.gz
files are supported and handled properly.
Dynamic Routes
Dynamic routes fall into two three broad categories, all of with are lazily-loaded routes (their handlers are lazily loaded on first hit):
Page Routes
Page routes are identified by their .page.js
and page.md
extensions (the latter for Markdown pages) and result in the creation of PageRoute instances.
š” Alongside Kitten Server and the html parser, page routes are the most complex parts of Kitten as they abstract away a lot of functionality to make authoring easier.
Page routes work in tandem with Page Socket Routes and the Page
class to implement Kittenās Streaming HTML workflow.
š Page Socket Routes
There are two ways to author Kitten pages:
- Using functions
- Using classes
Each method has its strengths and weaknesses and is useful for different use cases.
Youāll notice that many of the introductory examples and tutorials use functions. This is because function-based authoring is both more familiar from other JavaScript frameworks and is easier to get started with. Itās perfect for quickly prototyping things and for quick experiments.
š§ Class-based authoring, on the other hand, involves a bit more overhead but affords better maintainability. In conjunction with the KittenComponent
class, it enables you to work in a fully object-oriented manner in composing your pages from a DOM-like hierarchy of components, complete with automatic event bubbling so you can encapsulate event handling and streaming HTML responses within components themselves.
Since you can move from function-based page routes to class-based ones easily, itās always possible to start with the former and refactor to use the latter only if you find that you are going to be maintaining the app/site youāre building long term or if you start hitting the point where refactoring becomes a chore.
š” A good smell for when to refactor to use class-based pages is if you find yourself handling lots of client events in a central
onConnect()
handler and the handler starts to become unmanageable or unsightly.
Page types
There are three types of pages in Kitten:
- Dynamic pages
- Chached pages
- Live pages
Dynamic pages
The dynamic page is the default page type in Kitten.
All you have to do to create a dynamic page is to expose a default route that returns kitten.html
.
For example, the following displays the current date and time every time the page is reloaded:
export default () => kitten.html`
<h1>${new Date()}</h1>
`
Dynamic routes are re-rendered (have their default function run and the results sent to the browser in response to GET
requests) whenever the route is hit.
Thatās all there is to dynamic pages.
š” All HTTP routes ā e.g.,
GET
,POST
, etc. ā are also rendered on every hit. If you want to cache their results, you have to implement your own caching mechanism. (Which you could do using Kittenās built-in database support.)
Cached pages
š§ [ ] Implement.
Cached pages are dynamic pages that export a special flag that tells Kitten to only render the page the first time it is hit:
// Cache response until server restarts.
export const cache = true
This will make Kitten cache the page until the server restarts (which will happen, for example, if Kitten or the app is updated).
Optionally, you can also set the cache
value to the time, in minutes, that you want content cached for:
// Cache response for a day.
export const cache = 24 /* hours */ * 60 /* minutes */
Live pages
Live pages are dynamic pages that automatically a create a socket connection to client and use this connection to enable a Streaming HTML workflow where you can respond to events raised on the client with interface updates generated on the server that you stream to the client.
To create a live page, you must export at least one event handler from your page file. Event handlers are written in camelCase and start with on
, followed by the name of the event youāre handling.
By convention, the name of the event is set using the name
attribute of the client side DOM element that the event originates from.
If the element that triggers an event does not have a name
attribute, Kitten will fall back to using its id
instead.
If neither the name
nor id
attributes exist on a triggering element, Kitten will display an error message and fail to route the message.
š” If both
name
andid
attributes are present,name
takes precedence. Thename
attribute is chosen as the recommended attribute for event mapping as more than one element can have the same name, which means that you can have events on multiple elements all result in the same event handler getting called. This is useful, for example, when there are multiple buttons that submit the same form but are meant to result in a different action being performed. Another use case is radio button groups.
Kitten handles the transport of the event from the client, via the automatically-created default web socket, to the Kitten page (and any connected Kitten components) on the server.
Kitten pages can have components connected to them. Connected components automatically receive events that are sent from the page. Connected components can, in turn, have other components connect to them. Events bubble up the component hierarchy automatically. You do not have to add event listeners to be notified of events. All you have to do is to implement an event handler, using the naming convention outlined above.
š” Live pages are more resource intensive than regular pages not only because each live page also creates a socket route but because the live page and any components on it are kept in memory for as long as the page is alive on the client. So, if you have 1,000 concurrent people on a live page, youāre going to have 1,000 live page instances. If that page has a component hierarchy where there are 10 components on a page, youāre going to have 10,000 live components in memory.
While this may seem excessive, for the type of functionality Kitten provides with live pages, you would end up having to keep as much in memory even if you implemented it yourself, although you might get away with optimisations using more efficient data structures. However, given Small Web use cases, this should not present a scalability problem for personal web sites and apps.
(If it does, you can always not use live pages and implement a more efficient system using your own custom data types and persistence mechanisms.)
š” You can create live pages using either function-based or class-based routes and using function-based or class-based component but only the class-based ones will take advantage of the automatic event bubbling, etc.
Page flow.
A request comes in to the
lazilyLoadedHandler()
override.Just like other lazily loaded handlers, if the internal handler method (
_handler
) doesnāt exist yet, it is created.Properties shared by all the routes are saved to the route instance itself. (e.g.,
request.url
)If the route is for Kittenās own internal sign-in route (
/š/sign-in/
), populates theredirectToAfterSignIn
property on the session so that Kitten knows where it needs to redirect the person after they sign in.š§ If any event handlers are exported from the page route, that means that this is a live page, so create a
Page
instance in memory to track its lifecycle and enable the handling of events from the client as well the streaming of responses back to the client.Call custom event handler exported by the developer, passing it a reference to the
Page
instance as well as the HTTPSrequest
andresponse
objects.If the page has ended the
response
itself (this is not encouraged, but is possible), do nothing else.Do the final render of the page, wrapping it up in common Kitten functionality and a valid HTML shell.
Update the hit counter for the page in (
kitten._db.stats
).
Page Socket Routes
Page Socket Routes are special in that, unlike all other types of routes, they do not have their own file on the file system.
š§ Instead, they are automatically-created from Page Routes that either export page event handlers (onā¦()
) or export the page as an instance of the kitten.Page
class.
If a page is called, say, /my.page.js
in the site/app being served, the path of its route will be /my/
. If it exports event handlers or an instance of the kitten.Page
class, a Page Socket Route will be automatically created for it at the path /my.default.socket
.
Furthermore, the page that is rendered by the Page Routeās handler will automatically contain code to make a WebSocket connection to the Page Socket Route.
When the /my/
route is hit, it will;
- Lazily load the default export in
/my.page.js
. - Create a
Page
instance for this request (this is known as a live page) and - Among other things, inject code to connect to
/my.default.socket
and pass it thePage
instanceāsid
on load.
The Page
instance, while being instantiated:
Generates a cryptographically secure universally unique identifier (UUID) to act as the page
id
.Adds itās
id
to the global registry of live pages atkitten.pages
.Emits a
kittenPageCreate
event.
Then, when the page loads in the browser and makes a connection to the automatically-generated default WebSocket, the Page Socket Route:
Wires up the WebSocket to handle keep alive ping/pongs.
Adds the connection to the list of connections for this socket.
Checks that the
Page
instanceāsid
is provided in the query (request.query.id
), handles the error if it isnāt by telling the page to reload itself, and otherwise sets the socketās id (ws.id
) to the pageās id.Callās the
Page
instanceāsconnect
method, passing it references to the WebSocket, its list of connections, and its event handlers that were imported from the page route.On socket close, sets up an event handler to perform clean up by removing the associated
Page
instance from the global registry of live pages.
The Page
instance, when its connect
method is called:
Saves the socket, connections, and event handlers it is passed by the Page Socket Route.
Creates two
MessageSender
properties,everyone
andeveryoneElse
, that are used by author of the Page Route to broadcast messages to all connections or to all connections apart from the current connection, respectively.Broadcasts a generic
kittenPageConnect
event as well as a specifickittenPageConnect-<page id>
event.
In addition to message sending functionality, the Page
instance contains framework code that handles the parsing of incoming messages and the mapping of parsed messages to event handlers (onEventName()
).
Post routes
Post routes respond to POST
requests and are identified by their post.js
extension. They result in the creation of PostRoute instances.
They have a bit of additional logic to other HTTP routes for handling file uploads and they inject an uploads
property in the request
objects received by the route handler to aid in authoring.
Socket routes
Socket routes simplify the use of WebSockets in Kitten apps/site. They are identified by their .socket.js
extensions and result in the creation of WebSocketRoute instances.
Socket routes make use of the ws
reference injected by the Kitten Serverās WebSocket middleware and implement keep-alive and connection handling as well as providing a high-level interface for authors to send and respond to WebSocket messages.
When using Socket routes authors receive a parameter object that contains a request
object as well as a socket
object.
The socket
object is implemented internally as a Proxy that mixes in the following two methods to the underlying ws
object:
- `broadcast()`
- `all()`
The broadcast()
method send the passed message
object (which can be an arbitrary object or a string) to everyone but the socket that is broadcasting.
To send to everyone, including the socket that originated the message, use the all()
method, instead.
The extent of error handling is limited to logging an error message to the console. To customise error handling, you can add your own .on('error', ā¦)
handler to the returned socket proxy.
Other HTTP Routes
All other route types (e.g, .get.js
, head.js
, etc.) are handled by the generic/simple HTTPRoute class that simply passes the request
and response
objects to the routeās handler and expects the person authoring the route to handle the responses themselves without injecting any higher-level functionality as with the other route types.
Files
The Files class finds files to serve by recursively searching its base path and watches for changes on them, dispatching file
events when changes occur.
It is used by Routes when creating the file system-based routes for Kitten Server.
The Files class is an EventTarget
.
The Files class is instantiated using an asynchronous factory method (new
).
const files = Files.new(basePath)
Files are categorised by extension category type:
console.log(files.byExtensionCategoryType)
Files uses the Watcher module for file system watching and the tiny-readdir module to get the list of files from the file system. (The latter module is also used by Watcher itself.)
While compiling the list of paths for routing, the following paths are ignored:
Any path starting with a dot (hidden paths) apart from those starting with
.local/share/
(a subdirectory of which is where Kittenās own web app is stored), and.well-known
(so that people can use Well-known URIs in their apps/sites, should they so wish.)Node modules (
node_modules
) folders.App modules (
app_modules
) folders.
š” The same ignore list is used when deciding which files to watch, with the exception of
app_modules
folders, which are watched for changes, resulting in live reloads during development if you change a file in the app module youāre working on (just like with any other file in your Kitten app/site).
āCurrently, the file map is created twice.
First, we use tiny-readdir (which is also used by Watcher), to create our initial list of files. Then we instantiate Watcher. We should be able to pass the returned
readdirMap
to Watcher so it doesnāt have to replicate the directory traversal but there is a bug in Watcher thatās currently preventing this.Keep an eye on the following two Watcher issues:
Files dispatches a file
event with a detail
property of type FileEvenDetails
for the following events:
- File added
- File changed
- File unlinked (deleted)
- Directory added
- Directory unlinked (deleted)
On Watcher error, it closes the watcher.
Extension category types
Files are sorted into the following category types, based on their extensions:
- Back-end routes (
files.byExtensionCategoryType.backendRoutes
) - Front-end routes (
files.byExtensionCategoryType.frontendRoutes
) - Dependencies (
files.byExtensionCategoryType.dependencies
) - Dynamic routes (
files.byExtensionCategoryType.dynamicRoutes
) - Static routes (
files.byExtensionCategoryType.staticRoutes
) - All routes (
files.byExtensionCategoryType.allRoutes
)
š” The
allRoutes
list contains all the routes from the other categories, combined, and is included for ease of authoring.
Questions?
Contact Aral on the fediverse.