Add `jetzig test` command which runs build step `jetzig:test`.
Add `jetzig.testing` namespace which provides test helpers and a test
app.
Add tests to view generator (i.e. include tests for generated routes).
Allow middleware to resolve a request by calling `request.redirect` or
`request.render` - after this point, the request stops processing and
renders immediately.
Permit setting template during view render with `request.setTemplate()`
Permit middleware to define custom routes to static content with
`pub const Routes` (implemented for something no longer needed but seems
useful anyway).
Implement globbing on custom routes, `/foo/:bar*` will glob all path
segments including and after `/foo/...`, e.g. `/foo/bar/baz/qux` will
pass invoke the custom view function with an array of `bar`, `baz`,
`qux` as first argument (instead of typical resource ID).
In `main()`:
```zig
app.route(.GET, "/custom/:id/foo/bar", @import("app/views/custom/foo.zig"), .bar);
```
Routes `GET` request with path (e.g.) `/custom/1234/foo/bar` to `bar()`
defined in `src/app/views/custom/foo.zig`.
Routes with an `:id` segment expect a function with three parameters,
routes without an `:id` segment expect a function with two parameters
(i.e. the same as `get` vs `index`).
Use Karl Seguin's http.zig as HTTP server backend:
https://github.com/karlseguin/http.zig
Update loggers to use new `jetzig.loggers.LogQueue` to offload logging
to a background thread.
Numerous other optimizations to remove unneeded allocs.
Performance jump on a simple request from approx. 2k requests/second to
approx. 40k requests/second (Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz).
Color support for Windows
Zmpl partial arg type coercion
Remove need to add all mail template params to view response data. This
was originally intended functionality but the wrong value was passed to
the `deliver` function, and no `data` argument was given, making it
cumbersome to create new values.
Render `src/app/views/errors.zig` view (`index` action) when an
unexpected error occurs. If the view does not exist, try rendering
`public/404.html`, `public/500.html` (etc.), finally falling back to a
standard basic HTML/JSON response.
Create mailers with `jetzig generate mailer <name>`. Mailers define
default values for email fields (e.g. subject, from address, etc.).
Mailers use Zmpl for rendering text/HTML parts.
Send an email from a request with `request.mail()`. Call
`deliver(.background, .{})` on the return value to use the built-in
mail job to send the email asynchronously.
Improve query/HTTP request body param parsing - unescape `+` and `%XX`
characters.
Use in-memory KV store (JetKV) for simple job queue.
Build script generates an array of Zig modules in `src/app/jobs/` and
stores their name + run function (`run(allocator, params, logger)`).
View functions schedule jobs with arbitrary params.
Thread pool spawns a (configurable) number of workers and pops jobs from
the queue, then invokes the appropriate run function.
When query param is empty, use `jetzig.data.Data.NullType` - this way
the value is still non-null (i.e. can be captured) and is still a
`jetzig.data.Value` so is consistent with previous implementation.
Add very weird bugfix for 404 responses.
Fix `Bad Request` response code.
Generate an array of `jetzig.views.Route` in `GenerateRoutes.zig` exe
instead of a meta-route that we later translate into an actual route.
This makes things much simpler for static routes at build time and
dynamic routes at run time as we no longer need to use comptime - we
just have an array of routes ready-made.
When `HX-Target` header is present, bypass any configured layout for the
request. This allows a full page reload to render with a layout, i.e.
render the entire page, while a request from htmx will load just the
content directly generated by the view.
Latest Zmpl provides `template.renderWithLayout(other_template, data)`,
allowing a template to be renedered within another template.
Create layouts in `src/app/views/layouts/` or use
`jetzig generate layout [name]` and set `pub const layout = "name";` in
each view file.
Views are copied to zig-cache, meaning that any files in `src/` are not
available for `@import` by default. Copy all .zig files from `src/` into
zig-cache so that relative imports work as expected. This also removes
the need to specifically copy views into zig-cache as the full tree is
now present.
Also fix a bug in static route fallback - remove superfluous underscore
so static routes with non-matching params render default HTML/JSON
content.
Offload as much work as possible into new `jetzigInit` function provided
by Jetzig's `build.zig`. This means that a new Jetzig project's
`build.zig` is now almost completely vanilla with the exception of two
extra lines:
```zig
const jetzig = @import("jetzig");
```
```zig
try jetzig.jetzigInit(b, exe, .{});
```
Allow custom functions with arbitrary signatures and avoid any issues of
trying to parse functions that are not Jeztig route action functions
(i.e. index, get, post, put, patch, delete).
Remove old bash script for setting up a new project, do everything in
Zig to make it platform agnostic and give us an easy place to add
scaffolding commands in future.
Andrew overhauled std.http to avoid allocations and put control of
connection + stream into hands of users. For now, do a barebones
implementation to restore compatibility, later we can do keepalive and
pipelining. For now, still getting decent enough performance the "slow"
way.
Relevant commit: 6395ba852a
Commit message copied here for posterity:
> Author: Andrew Kelley <andrew@ziglang.org>
> Date: Tue Feb 20 03:30:51 2024 -0700
> std.http.Server: rework the API entirely
> Mainly, this removes the poorly named `wait`, `send`, `finish`
> functions, which all operated on the same "Response" object, which was
> actually being used as the request.
>
> Now, it looks like this:
> 1. std.net.Server.accept() gives you a std.net.Server.Connection
> 2. std.http.Server.init() with the connection
> 3. Server.receiveHead() gives you a Request
> 4. Request.reader() gives you a body reader
> 5. Request.respond() is a one-shot, or Request.respondStreaming() creates
> a Response
> 6. Response.writer() gives you a body writer
> 7. Response.end() finishes the response; Response.endChunked() allows
> passing response trailers.
>
> In other words, the type system now guides the API user down the correct
> path.
>
> receiveHead allows extra bytes to be read into the read buffer, and then
> will reuse those bytes for the body or the next request upon connection
> reuse.
>
> respond(), the one-shot function, will send the entire response in one
> syscall.
>
> Streaming response bodies no longer wastefully wraps every call to write
> with a chunk header and trailer; instead it only sends the HTTP chunk
> wrapper when flushing. This means the user can still control when it
> happens but it also does not add unnecessary chunks.
>
> Empirically, in my example project that uses this API, the usage code is
> significantly less noisy, it has less error handling while handling
> errors more correctly, it's more obvious what is happening, and it is
> syscall-optimal.
>
> Additionally:
> * Uncouple std.http.HeadParser from protocol.zig
> * Delete std.Server.Connection; use std.net.Server.Connection instead.
> - The API user supplies the read buffer when initializing the
> http.Server, and it is used for the HTTP head as well as a buffer
> for reading the body into.
> * Replace and document the State enum. No longer is there both "start"
> and "first".
Create `jetzig.http.Response` when `std.http.Server.Response` is created
and delegate all management of `std.http.Server.Response` to
`jetzig.http.Response`. Using this internally means we use the same
internal interface for the `Response` as the user, which gives us things
like response headers management in views for free (user simply calls
`request.response.headers.append()` etc.).
Implement `jetzig.http.Request.params()` which parses either a JSON
request body or a query param string into a `jetzig.data.Value`.
Allow configuring params for static site generation - configure an array
of params for each endpoint which is then parsed at build time and a
separate JSON and HTML output is generated for each by invoking the
relevant view function and passing in resource ID/params. Params are
stored in generated `routes.zig` for route matching at run time.
Remove `anyerror` from example definitions - we only need this in the
struct definition, no need to have explicit error set defined in
user-defined view functions.
Generate views defined with `request: *jetzig.http.StaticRequest` as
static content into `static/` directory so that exported JSON and HTML
can be rendered direct from disk, skipping runtime rendering.