+
+```
+
+Serve it from a route.
+
+```js caption=routes/marko.js
+import { view } from "primate";
+
+const posts = [{
+ id: 1,
+ title: "First post",
+}];
+
+export default {
+ get() {
+ return view("post-index.marko", { posts });
+ },
+};
+```
+
+The rendered component will be accessible at http://localhost:6161/marko.
+
+## MySQL database support
+
+This release introduces support for MySQL using the `mysql2` driver. The MySQL
+driver supports all of Primate's ORM operations as well as transactions and
+connection pools. In addition to installing the `mysql2` package with `npm
+install mysql2@3`, this driver requires running a MySQL server either locally
+or remotely. Visit the MySQL website or consult your operating system's manuals
+on how to install and run a server.
+
+The MySQL driver uses the `host` (default `"localhost"`), `port` (default
+`3306`) `database`, `user`, and `password` configuration properties.
+
+### Configure
+
+```js caption=primate.config.js
+import { default as store, mysql } from "@primate/store";
+
+export default {
+ modules: [
+ store({
+ // use the MySQL server at localhost:3306 and the "app" database
+ driver: mongodb({
+ // if "localhost", can be omitted
+ host: "localhost",
+ // if 3306, can be omitted
+ port: 3306,
+ database: "app",
+ user: "username",
+ // can be omitted
+ password: "password",
+ }),
+ }),
+ ],
+};
+```
+
+Once configured, this will be the default driver for all stores.
+
+## New handlers for WebSockets and Server-sent events
+
+With WebSocket support moving into [rcompat][rcompat], we deprecated the
+`@primate/ws` package in favor of inclusion into core as the `ws` handler. In
+addition, we added a new handler for Server-sent events, `sse`.
+
+### WebSocket handler
+
+You can now upgrade any `GET` route to a WebSocket route with the `ws` handler.
+
+```js caption=routes/ws.js
+import { ws } from "primate";
+
+export default {
+ get(request) {
+ const limit = request.query.get("limit") ?? 20;
+ let n = 1;
+ return ws({
+ open(socket) {
+ // connection opens
+ },
+ message(socket, message) {
+ if (n > 0 && n < limit) {
+ n++;
+ socket.send(`You wrote ${payload}`);
+ }
+ },
+ close(socket) {
+ // connection closes
+ },
+ });
+ },
+};
+```
+
+In this example, we have a small chat which reflects back anything to the user
+up to a given number of messages, the default being 20.
+
+```html caption=components/chat.html
+
+
+
+
+```
+
+### Server-sent events handler
+
+Similarly to `ws`, you can use the `sse` handler to upgrade a `GET` request to
+stream out server-sent events to the client.
+
+```js caption=routes/sse.js
+import { sse } from "primate";
+
+const passed = start_time => Math.floor((Date.now() - start_time) / 1000);
+
+export default {
+ get() {
+ let interval;
+ let start_time = Date.now();
+
+ return sse({
+ open(source) {
+ // connection opens
+ interval = globalThis.setInterval(() => {
+ source.send("passed", passed(start_time));
+ }, 5000);
+ },
+ close() {
+ // connection closes
+ globalThis.clearInterval(interval);
+ },
+ });
+ },
+};
+```
+
+In this example, we send a `passed` event to the client every 5 seconds,
+indicating how many seconds have passed since the connection was established.
+The client subscribes to this event and prints it to the console.
+
+```html components/sse-client.html
+
+```
+
+This client is then served using another route.
+
+```js routes/sse-client.js
+import { view } from "primate";
+
+export default {
+ get() {
+ return view("sse-client.html");
+ },
+};
+```
+
+## Migrating from 0.28
+
+### remove @primate/ws
+
+You no longer need to import and use `@primate/ws` in order to use WebSockets.
+Use the built-in `ws` handler instead.
+
+### remove @primate/liveview
+
+You no longer need to import and use `@primate/liveview` for your application
+to use SPA browsing. If you wish to deactive SPA browsing, pass `{ spa: false }`
+to the frontend handler in your configuration file.
+
+## Other changes
+
+Consult the [full changelog][changelog] for a list of all relevant changes.
+
+## Next on the road
+
+Some of the things we plan to tackle in the upcoming weeks are,
+
+* Add projections and relations to stores
+* Multidriver transactions
+* Add a `command` hook that would allow modules to register command line
+ namespaces, to be able to run `npx primate [namespace] [command] [flags]`
+* Use this new hook to create database migrations for SQL-flavored databases
+* Add hydration and liveview support for `@primate/vue`
+* Flesh out stores with default values, additional predicates and relations
+ between tables/collections
+* Add more type variants
+
+This list isn't exhaustive or binding. None, some or all of these features may
+be included in 0.30, and other features may be prioritized according to
+feedback.
+
+## Fin
+
+If you like Primate, consider [joining our channel #primate][irc] on
+irc.libera.chat.
+
+Otherwise, have a blast with the new version!
+
+[Getting started]: /guide/getting-started
+[irc]: https://web.libera.chat#primate
+[changelog]: https://github.com/primatejs/primate/releases/tag/0.29.0
+[rcompat]: https://github.com/rcompat/rcompat
diff --git a/docs/examples/frontend.md b/docs/examples/frontend.md
index 2b24d765..76be08ae 100644
--- a/docs/examples/frontend.md
+++ b/docs/examples/frontend.md
@@ -1,10 +1,10 @@
-%%% React, Svelte, Vue, Solid
+%%% React, Svelte, Vue, Solid, Angular
```jsx caption=components/Index.jsx
export default ({ posts }) => {
return (<>
+ `,
+ standalone: true,
+})
+export default class Index {
+ @Input() posts = [];
+}
+```
+
%%%
diff --git a/docs/guide/hooks.md b/docs/guide/hooks.md
index fa319ebb..deb1d957 100644
--- a/docs/guide/hooks.md
+++ b/docs/guide/hooks.md
@@ -32,9 +32,6 @@ subscribers accept different types of parameters, depending on the hook.
├─ `bundle` # if `npx primate serve` is run, otherwise skipped
│ └─ # modules transform `build` directory
│
-├─ `serve`
-│ └─ # modules modify the underlying server to access low-level operations
-│
├─ # *end* start-up phase
│
├─ # *begin* client request phase, hooks here are called per request
@@ -207,21 +204,6 @@ particularly useful for bundler modules such as [esbuild](/modules/esbuild).
This hook accepts the app as its first and the next subscriber as its second
parameter.
-## serve
-
-**Executed** once
-
-**Precondition** The HTTP server has been created.
-
-This hook allows modules to access the low-level HTTP server implementation in
-order to deal with additions to HTTP such as the WebSocket protocol. Such
-modules can add support for additional verbs aside from the official HTTP
-verbs. For example, the [WebSocket](/modules/ws) module adds support for
-a `ws` verb.
-
-This hook accepts the app, augmented with a `server` property, as its first
-and the next subscriber as its second parameter.
-
## handle
**Executed** for every request
diff --git a/docs/guide/responses.md b/docs/guide/responses.md
index 3e90a01a..93dbd97a 100644
--- a/docs/guide/responses.md
+++ b/docs/guide/responses.md
@@ -185,6 +185,128 @@ export default {
A request to `/server-error` will result in a `500` response with the HTML body
`Internal Server Error`.
+### WebSocket
+
+You can upgrade any `GET` route to a WebSocket route with the `ws` handler.
+
+```js caption=routes/ws.js
+import { ws } from "primate";
+
+export default {
+ get(request) {
+ const { limit } = request.query.get() ?? 20;
+ let n = 1;
+ return ws({
+ open(socket) {
+ // connection opens
+ },
+ message(socket, message) {
+ if (n > 0 && n < limit) {
+ n++;
+ socket.send(`You wrote ${payload}`);
+ }
+ },
+ close(socket) {
+ // connection closes
+ },
+ });
+ },
+};
+```
+
+In this example, we have a small chat which reflects back anything to the user
+up to a given number of messages, the default being 20.
+
+```html caption=components/chat.html
+
+
+
+
+```
+
+### Server-sent events
+
+Similarly to `ws`, you can use the `sse` handler to upgrade a `GET` request to
+stream out server-sent events to the client.
+
+```js caption=routes/sse.js
+import { sse } from "primate";
+
+const passed = start_time => Math.floor((Date.now() - start_time) / 1000);
+
+export default {
+ get() {
+ let interval;
+ let start_time = Date.now();
+
+ return sse({
+ open(source) {
+ // connection opens
+ interval = globalThis.setInterval(() => {
+ source.send("passed", passed(start_time));
+ }, 5000);
+ },
+ close() {
+ // connection closes
+ globalThis.clearInterval(interval);
+ },
+ });
+ },
+};
+```
+
+In this example, we send a `passed` event to the client every 5 seconds,
+indicating how many seconds have passed since the connection was established.
+The client subscribes to this event and prints it to the console.
+
+```html components/sse-client.html
+
+```
+
+This client is then served using another route.
+
+```js routes/sse-client.js
+import { view } from "primate";
+
+export default {
+ get() {
+ return view("sse-client.html");
+ },
+};
+```
+
### Custom response
Lastly, for a custom response status, you can return a `Response` object from a
diff --git a/docs/modules/angular.md b/docs/modules/angular.md
new file mode 100644
index 00000000..ed3fde5d
--- /dev/null
+++ b/docs/modules/angular.md
@@ -0,0 +1,90 @@
+# Angular
+
+This handler module supports SSR and serves Angular components with the
+`.component.ts` extension.
+
+## Install
+
+`npm install @primate/frontend @angular/{compiler,core,platform-browser,platform-server,ssr}@17`
+
+## Configure
+
+Import and initialize the module in your configuration.
+
+```js caption=primate.config.js
+import { angular } from "@primate/frontend";
+
+export default {
+ modules: [
+ angular(),
+ ],
+};
+```
+
+## Use
+
+Create an Angular component in `components`.
+
+```ts caption=components/post-index.component.ts
+import { Component, Input } from "@angular/core";
+import { CommonModule } from "@angular/common";
+
+@Component({
+ selector: "post-index",
+ imports: [ CommonModule ],
+ template: `
+
+ `,
+ standalone: true,
+})
+export default class PostIndex {
+ @Input() posts = [];
+}
+```
+
+Serve it from a route.
+
+```js caption=routes/angular.js
+import { view } from "primate";
+
+const posts = [{
+ id: 1,
+ title: "First post",
+}];
+
+export default {
+ get() {
+ return view("post-index.component.ts", { posts });
+ },
+};
+```
+
+The rendered component will be accessible at http://localhost:6161/angular.
+
+## Configuration options
+
+### extension
+
+Default `".component.ts"`
+
+The file extension associated with Angular components.
+
+### mode
+
+Default `"production"`
+
+Angular's mode of operation. Any value other than `"production"` will test
+Angular to development mode.
+
+## Resources
+
+* [Repository][repo]
+
+[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend
diff --git a/docs/modules/drivers.md b/docs/modules/drivers.md
index 094cb816..4904fab4 100644
--- a/docs/modules/drivers.md
+++ b/docs/modules/drivers.md
@@ -25,8 +25,8 @@ export default {
```
This driver requires no DBMS to be installed and is by its nature volatile --
-its contents only exist in memory during the lifetime of the app run. It's a
-good fit if you want to quickly prototype your data stores, but you will most
+its contents only exist in memory during the lifetime of the app run. It's a
+good fit if you want to quickly prototype your data stores, but you will most
likely want to switch to persistent storage later on.
## JSON file
@@ -53,9 +53,10 @@ export default {
## SQLite
-The SQLite driver uses the `better-sqlite3` package for its underlying driver
-calls. Install this package with `npm install better-sqlite3` before you
-proceed to use this driver.
+`npm install better-sqlite3@9`
+
+The SQLite driver uses the `better-sqlite3` package for its underlying driver.
+Install this package before you proceed.
Similarly to the JSON file driver, the SQLite driver uses the `filename`
property to indicate which file to manage the data in. If unset, it will
@@ -78,11 +79,12 @@ export default {
## MongoDB
-The MongoDB driver uses the `mongodb` package for its underlying driver.
-Install this package with `npm install mongodb` before you proceed to use this
-driver. In addition, it requires running MongoDB server, either locally or
-remotely. Visit the MongoDB website or consult your operating system's manuals
-on how to install and run a server.
+`npm install mongodb@6`
+
+The SQLite driver uses the `mongodb` package for its underlying driver.
+Install this package before you proceed. In addition, it requires running
+MongoDB server either locally or remotely. Visit the MongoDB website or consult
+your operating system's manuals on how to install and run a server.
This driver uses the `host` (default `"localhost"`), `port` (default `27017`)
and `db` configuration properties.
@@ -108,11 +110,12 @@ export default {
## PostgreSQL
-The PostgreSQL driver uses the `postgres` package for its underlying driver.
-Install this package with `npm install postgres` before you proceed to use this
-driver. In addition, it requires running a PostgreSQL server, either locally or
-remotely. Visit the PostgreSQL website or consult your operating system's
-manuals on how to install and run a server.
+`npm install postgres@3`
+
+The SQLite driver uses the `postgres` package for its underlying driver.
+Install this package before you proceed. In addition, it requires running
+PostgerSQL server either locally or remotely. Visit the PostGreSQL website or
+consult your operating system's manuals on how to install and run a server.
This driver uses the `host` (default `"localhost"`), `port` (default `5432`)
`db`, `user`, and `pass` configuration properties.
@@ -138,21 +141,58 @@ export default {
};
```
+## MySQL
+
+`npm install mysql2@3`
+
+The MySQL driver uses the `mysql2` package for its underlying driver. Install
+this package before you proceed. In addition, it requires running a MySQL
+server either locally or remotely. Visit the MySQL website or consult your
+operating system's manuals on how to install and run a server.
+
+This driver uses the `host` (default `"localhost"`), `port` (default `3306`)
+`database`, `user`, and `password` configuration properties.
+
+### Configure
+
+```js caption=primate.config.js
+import { default as store, mysql } from "@primate/store";
+
+export default {
+ modules: [
+ store({
+ // use the MySQL server at localhost:3306 and the "app" database
+ driver: mongodb({
+ // if "localhost", can be omitted
+ host: "localhost",
+ // if 3306, can be omitted
+ port: 3306,
+ database: "app",
+ user: "username",
+ // can be omitted
+ password: "password",
+ }),
+ }),
+ ],
+};
+```
+
## SurrealDB
!!!
This driver does not yet support automatic transaction rollback.
!!!
-The SurrealDB driver uses the `surrealdb.js` package for its underlying driver.
-Install this package with `npm install surrealdb.js` before you proceed to use
-this driver. In addition, it requires running a SurrealDB server, either locally
-or remotely. Visit the SurrealDB website or consult your operating system's
-manuals on how to install and run a server.
+`npm install surrealdb.js@0.11`
+
+The MySQL driver uses the `surrealdb.js` package for its underlying driver.
+Install this package before you proceed. In addition, it requires running a
+SurrealDB server either locally or remotely. Visit the SurrealDB website or
+consult your operating system's manuals on how to install and run a server.
This driver uses the `host` (default `"http://localhost"`), `port` (default
-`8000`), `path` (default: "`rpc`"), `ns` (default: `"default"`), `db`
-`user`, and `pass` configuration properties.
+`8000`), `path` (default: "`rpc`"), `namespace`, `database`, `username`, and
+`password` configuration properties.
```js caption=primate.config.js
import { default as store, surrealdb } from "@primate/store";
diff --git a/docs/modules/frontend.md b/docs/modules/frontend.md
index 74214a30..9cf27eb0 100644
--- a/docs/modules/frontend.md
+++ b/docs/modules/frontend.md
@@ -7,11 +7,9 @@ cover many frontend frameworks. When loaded, they extend the `view` handler
to support more file extensions.
Those frameworks come with different capabilities, like server-side rendering
-(SSR), hydration and support for the [liveview](liveview) module, turning your
-application into a single-page application (SPA) after it has been initially
-loaded. In some cases, some capabilities have simply not been implemented yet
-in the module. In other cases, the frontend framework itself doesn't support
-those.
+(SSR), hydration and SPA support. In some cases, some capabilities have simply
+not been implemented yet in the module. In other cases, the frontend framework
+itself doesn't support those.
## Layouts
@@ -40,19 +38,12 @@ Vue hydration is planned for future versions.
HTMX, having no SSR support, also has no support for hydration. The HTMX client
is *always* sent along the page and activates on page load.
-## Liveview
+## SPA
-The [liveview](/modules/liveview) module bridges the gap between SSR/hydration
-and single page applications (SPA). It injects a small JavaScript client into
-the build which uses `fetch` to manage clicking on links and submitting forms
-instead of reloading the entire page, and also manages browsing the history.
-
-Currently the Svelte, React and Solid modules support liveview. The Vue module
-is expected to receive liveview support when hydration has been implemented for
-it.
-
-HTMX itself stands somewhat in competition to liveview, as it can register
-handles to load links or send forms via `fetch`.
+For modules that support it (currently Svelte, React and Solid), SPA browsing
+s active by default. It injects a small JavaScript client into the build which=
+uses `fetch` to manage clicking on links and submitting forms instead of
+reloading the entire page, and also manages browsing the history.
## Head component
@@ -67,15 +58,17 @@ Every frontend framework registers its own file extension with the
[`view`][view] handler and needs to be loaded in `primate.config.js`. You can
use different frontend frameworks alongside each other, in different routes.
-| |HTML |[Svelte] |[React] |[Solid] |[Vue] |[HTMX] |[Handlebars]|
-|----------|-------|---------------|--------|--------|------|-------|------------|
-|Extension |`.html`|`.svelte` |`.jsx` |`.jsx` |`.vue`|`.htmx`|`.hbs` |
-|[Layouts] |✗ |✓ |✓ |✓ |✗ |✗ |✗ |
-|SSR |✗ |✓ |✓ |✓ |✓ |✗ |✓ |
-|Hydration |✗ |✓ |✓ |✓ |✗ |✗ |✗ |
-|[Liveview]|✗ |✓ |✓ |✓ |✗ |✗ |✗ |
-|Head |✗ |``|``|``|✗ |✗ |✗ |
-|[I18N] |✗ |✓ |✓ |✓ |✗ |✗ |✗ |
+|Framework |Extension |Layouts|SSR|Hydration|SPA|Head|I18N|
+|------------|---------------|-------|---|---------|---|----|----|
+|HTML |`.html` |✗ |✗ |✗ |✗ |✗ |✗ |
+|[Svelte] |`.svelte` |✓ |✓ |✓ |✓ |✓ |✓ |
+|[React] |`.jsx` |✓ |✓ |✓ |✓ |✓ |✓ |
+|[Solid] |`.jsx` |✓ |✓ |✓ |✓ |✓ |✓ |
+|[Vue] |`.vue` |✗ |✓ |✗ |✗ |✗ |✗ |
+|[Angular] |`.component.ts`|✗ |✓ |✗ |✗ |✗ |✗ |
+|[HTMX] |`.htmx` |✗ |✗ |✗ |✗ |✗ |✗ |
+|[Handlebars]|`.hbs` |✗ |✓ |✓ |✗ |✗ |✗ |
+|[Marko] |`.marko` |✗ |✓ |✗ |✗ |✗ |✗ |
## Error list
@@ -97,10 +90,11 @@ dependency is missing and what command you need to issue to install it.
[React]: /modules/react
[Solid]: /modules/solid
[Vue]: /modules/vue
+[Angular]: /modules/angular
[HTMX]: /modules/htmx
[Handlebars]: /modules/handlebars
+[Marko]: /modules/marko
[Layouts]: /guide/layouts
-[Liveview]: /modules/liveview
[I18N]: /modules/i18n
[bailout]: /guide/logging#bailout
[error]: /guide/logging#error
diff --git a/docs/modules/handlebars.md b/docs/modules/handlebars.md
index 4d1a7d4a..dea6a771 100644
--- a/docs/modules/handlebars.md
+++ b/docs/modules/handlebars.md
@@ -33,7 +33,7 @@ Create a Handlebars component in `components`.
```
-Create a route and serve the Handlebars `PostIndex` component.
+Serve it from a route.
```js caption=routes/hbs.js
import { view } from "primate";
@@ -50,17 +50,10 @@ export default {
};
```
-Your rendered Handlebars component will be accessible at
-http://localhost:6161/hbs.
+The rendered component will be accessible at http://localhost:6161/hbs.
## Configuration options
-### directory
-
-Default `config.location.components`
-
-Directory where the Handlebars components reside.
-
### extension
Default `".hbs"`
diff --git a/docs/modules/htmx.md b/docs/modules/htmx.md
index 72b8a2fc..52ca0bcc 100644
--- a/docs/modules/htmx.md
+++ b/docs/modules/htmx.md
@@ -39,8 +39,7 @@ Create an HTMX component in `components`.
```
-Create a route and serve the HTMX `post-add` component, adding a POST route for
-handling its form.
+Serve it from a route.
```js caption=routes/htmx.js
import { view, html } from "primate";
@@ -50,21 +49,11 @@ export default {
get() {
return view("post-add.htmx");
},
- post({ body }) {
- return html(
- `
Adding a post with:
-
Title ${body.title}
-
Text ${body.text}
`,
- { partial: true });
- },
+ // create a POST route to replace contents upon form submission
};
```
-Your rendered HTMX component will be accessible at http://localhost:6161/htmx.
-
-Here, we used the `html` handler to return HTML directly from the POST route,
-indicating it should not include the `app.html` layout by setting `partial`
-to `true`.
+The rendered component will be accessible at http://localhost:6161/htmx.
## Configuration options
diff --git a/docs/modules/liveview.md b/docs/modules/liveview.md
deleted file mode 100644
index 9a5ab55f..00000000
--- a/docs/modules/liveview.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# Liveview
-
-This module adds liveview client loading to your application, turning it into a
-SPA.
-
-## Install
-
-`npm install @primate/liveview`
-
-## Configure
-
-Import and initialize the module in your configuration.
-
-```js caption=primate.config.js
-import liveview from "@primate/liveview";
-
-export default {
- modules: [
- liveview(),
- ],
-};
-```
-
-## Use
-
-Once loaded, this module works automatically.
-
-The liveview module uses a special `X-Primate-Liveview` header to indicate to
-the handler that instead of rendering the entire HTML page, only the reference
-to the next component and its data are required and should be returned as JSON.
-Accordingly, every frontend handler must implement support for this header, and
-currently Svelte, React and Solid handlers do.
-
-## Configuration options
-
-This module has no configuration options.
-
-## Resources
-
-* [Repository][repo]
-
-[repo]: https://github.com/primatejs/primate/tree/master/packages/liveview
diff --git a/docs/modules/markdown.md b/docs/modules/markdown.md
index 2f2092bc..f0e9de38 100644
--- a/docs/modules/markdown.md
+++ b/docs/modules/markdown.md
@@ -31,7 +31,7 @@ Create a Markdown file in `components`.
This is the **first** post
```
-Create a route and serve the Markdown `PostIndex` component.
+Serve it from a route.
```js caption=routes/markdown.js
import { view } from "primate";
@@ -43,8 +43,7 @@ export default {
};
```
-Your rendered Markdown component will be accessible at
-http://localhost:6161/markdown.
+The rendered component will be accessible at http://localhost:6161/markdown.
## Configuration options
diff --git a/docs/modules/marko.md b/docs/modules/marko.md
new file mode 100644
index 00000000..f0acf0eb
--- /dev/null
+++ b/docs/modules/marko.md
@@ -0,0 +1,70 @@
+# Marko
+
+This handler module supports SSR and serves Marko components with the `.marko`
+extension.
+
+## Install
+
+`npm install @primate/frontend @marko/{compiler,translator-default}@5`
+
+## Configure
+
+Import and initialize the module in your configuration.
+
+```js caption=primate.config.js
+import { marko } from "@primate/frontend";
+
+export default {
+ modules: [
+ marko(),
+ ],
+};
+```
+
+## Use
+
+Create a Marko component in `components`.
+
+```html caption=components/post-index.marko
+