diff --git a/README.md b/README.md index eb9a110f..14862c72 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ constraints of being tied to specific options like Nuxt, Next, or others. - Svelte - React - Vue +- Angular - Solid - Web Components -- Angular (coming soon) ### Databases We Support @@ -57,7 +57,6 @@ constraints of being tied to specific options like Nuxt, Next, or others. |[@primate/types](packages/types) | Runtime types | |[@primate/build](packages/build) | Bundling JS/CSS | |[@primate/session](packages/session) | User sessions | -|[@primate/ws](packages/ws) | Web sockets | |[@primate/liveview](packages/liveview) | Liveview client (SPA) | |[@primate/i18n](packages/i18n) | Internationalization | |[@primate/binding](packages/binding) | Other backend languages | @@ -65,18 +64,19 @@ constraints of being tied to specific options like Nuxt, Next, or others. ## Comparison with other frameworks -| Feature | Next | Nuxt | SvelteKit | Primate | -|-------------------|--------|--------|------------|-------------------------------------------------| -| Backend | JS, TS | JS, TS | JS, TS | JS, TS, Go, Python, Ruby | -| Frontend | React | Vue | Svelte | React, Vue, Svelte, Solid, HTMX, Handlebars, WC | -| Native runtime | Node | Node | Node | Node, Deno, Bun | -| I18N | ✓ | ✓ | ✗ | @primate/i18n | -| Head Component | ✓ | ✓ | ✗ | React, Svelte, Solid | -| Route guards | ✗ | ✗ | ✗ | ✓ | -| Recursive layouts | ✓ | ✓ | ✓ | ✓ | -| Data stores/ORM | ✗ | ✗ | ✗ | SQLite, PostgreSQL, MongoDB, SurrealDb | -| WebSockets | ✗ | ✗ | ✗ | @primate/ws | -| User sessions | ✗ | ✓ | ✗ | @primate/session | +|Feature |Next |Nuxt |SvelteKit|Primate | +|------------------|------|------|---------|--------------------------------------------------------| +|Backend |JS, TS|JS, TS|JS, TS |JS, TS, Go, Python, Ruby | +|Frontend |React |Vue |Svelte |React, Vue, Svelte, Solid, Angular, HTMX, Handlebars, WC| +|Native runtime |Node |Node |Node |Node, Deno, Bun | +|I18N |✓ |✓ |✗ |@primate/i18n | +|Head Component |✓ |✓ |✗ |React, Svelte, Solid | +|Route guards |✗ |✗ |✗ |✓ | +|Recursive layouts |✓ |✓ |✓ |✓ | +|Data stores/ORM |✗ |✗ |✗ |SQLite, PostgreSQL, MongoDB, SurrealDb | +|WebSockets |✗ |✗ |✗ |✓ | +|Server-sent events|✗ |✗ |✗ |✓ | +|User sessions |✗ |✓ |✗ |@primate/session | ## Resources diff --git a/docs/blog/release-029.json b/docs/blog/release-029.json new file mode 100644 index 00000000..5bbdbc9f --- /dev/null +++ b/docs/blog/release-029.json @@ -0,0 +1,5 @@ +{ + "title": "Release 0.29: Angular and Marko, MySQL, WebSockets and Server-sent events", + "epoch": 1707519387000, + "author": "terrablue" +} diff --git a/docs/blog/release-029.md b/docs/blog/release-029.md new file mode 100644 index 00000000..d362cf6d --- /dev/null +++ b/docs/blog/release-029.md @@ -0,0 +1,355 @@ +Today we're announcing the availability of the Primate 0.29 preview release. +This release introduces support for Angular and Marko on the frontend and MySQL +on the backend, as well as two new core handlers for WebSockets and Server-sent +events across all runtimes. + +!!! +If you're new to Primate, we recommend reading the [Getting started] page to +get an idea of it. +!!! + +## Angular + +This release adds Angular to the list of frameworks Primate supports. With +Angular, Primate now supports all major frontend frameworks using a unified +view rendering API on the server. This handler supports SSR and serves Angular +components with the `.component.ts` extension. + +### Install + +To add support for Angular, install the `@primate/frontend` module and Angular +dependencies. + +`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 ], + standalone: true, + template: ` +

All posts

+
+

+ + {{post.title}} + +

+
+ `, +}) +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. + +## Marko + +This release adds Marko to the list of frameworks Primate supports. 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 +

All posts

+ +

+ + ${post.title} + +

+ +``` + +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 (<>

All posts

- {posts.map((post) => ( + {posts.map(post => (

{post.title} @@ -20,10 +20,10 @@ export default ({ posts }) => { export let posts;

All posts

-{#each posts as { id, title }} +{#each posts as post}

- - {title} + + {post.title}

{/each} @@ -59,4 +59,28 @@ export default ({ posts }) => { } ``` +```ts caption=components/index.component.ts +import { Component, Input } from "@angular/core"; +import { CommonModule } from "@angular/common"; + +@Component({ + selector: "index", + imports: [ CommonModule ], + template: ` +

All posts

+
+

+ + {{post.title}} + +

+
+ `, + 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: ` +

All posts

+
+

+ + {{post.title}} + +

+
+ `, + 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 +

All posts

+ +

+ + ${post.title} + +

+ +``` + +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. + +## Configuration options + +### extension + +Default `".marko"` + +The file extension associated with Marko components. + +## Resources + +* [Repository][repo] + +[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend diff --git a/docs/modules/react.md b/docs/modules/react.md index 71111f5c..8517da63 100644 --- a/docs/modules/react.md +++ b/docs/modules/react.md @@ -36,7 +36,7 @@ export default function PostIndex({ posts }) { } ``` -Create a route and serve the React `PostIndex` component. +Serve it from a route. ```js caption=routes/react.js import { view } from "primate"; @@ -53,23 +53,22 @@ export default { }; ``` -Your rendered React component will be accessible at -http://localhost:6161/react. +The rendered component will be accessible at http://localhost:6161/react. ## Configuration options -### directory - -Default `config.location.components` - -Directory where the React JSX components reside. - ### extension Default `".jsx"` The file extension associated with React JSX components. +### spa + +Default `true` + +Whether SPA browsing using `fetch` should be active. + ## Resources * [Repository][repo] diff --git a/docs/modules/solid.md b/docs/modules/solid.md index 505daf97..13a35448 100644 --- a/docs/modules/solid.md +++ b/docs/modules/solid.md @@ -54,7 +54,7 @@ export default function PostIndex(props) { } ``` -Create a route and serve the Solid `PostIndex` component. +Serve it from a route. ```js caption=routes/solid.js import { view } from "primate"; @@ -71,23 +71,22 @@ export default { }; ``` -Your rendered Solid component will be accessible at -http://localhost:6161/solid. +The rendered component will be accessible at http://localhost:6161/solid. ## Configuration options -### directory - -Default `config.location.components` - -Directory where the Solid JSX components reside. - ### extension Default `".jsx"` The file extension associated with Solid JSX components. +### spa + +Default `true` + +Whether SPA browsing using `fetch` should be active. + ## Resources * [Repository][repo] diff --git a/docs/modules/store.md b/docs/modules/store.md index 7e8a8367..862a3e28 100644 --- a/docs/modules/store.md +++ b/docs/modules/store.md @@ -437,8 +437,8 @@ Default [`memory`][memory] (volatile in-memory driver) The database driver used to persist data. This module also exports `json` as a non-volatile alternative driver which stores its data in a JSON file. Other -supported DMBSs are [MongoDB](/modules/mongodb), -[PostgreSQL](/modules/postgresql) and [SQLite](/modules/sqlite). +supported DMBSs are [MongoDB][mongodb],[PostgreSQL][postgresql], [MySQL][mysql] +and [SQLite][sqlite]. ### strict @@ -535,6 +535,10 @@ Transaction rolled back due to previous error. [memory]: https://github.com/primatejs/primate/blob/master/packages/store/src/drivers/memory.js [in-memory]: /modules/drivers#in-memory [json-file]: /modules/drivers#json-file +[MongoDB]: /modules/drivers#mongodb +[PostgreSQL]: /modules/drivers#postgresql +[MySQL]: /modules/drivers#mysql +[SQLite]: /modules/drivers#sqlite [error]: /guide/logging#error [bailout]: /guide/logging#bailout [warn]: /guide/logging#warn diff --git a/docs/modules/svelte.md b/docs/modules/svelte.md index fd5c36b1..411c57cb 100644 --- a/docs/modules/svelte.md +++ b/docs/modules/svelte.md @@ -44,7 +44,7 @@ Create a Svelte component in `components`. ``` -Create a route and serve the Svelte `PostIndex` component. +Serve it from a route. ```js caption=routes/svelte.js import { view } from "primate"; @@ -61,22 +61,22 @@ export default { }; ``` -Your rendered Svelte route will be accessible at http://localhost:6161/svelte. +The rendered component will be accessible at http://localhost:6161/svelte. ## Configuration options -### directory - -Default `config.location.components` - -Directory where the Svelte components reside. - ### extension Default `".svelte"` The file extension associated with Svelte components. +### spa + +Default `true` + +Whether SPA browsing using `fetch` should be active. + ## Resources * [Repository][repo] diff --git a/docs/modules/vue.md b/docs/modules/vue.md index 1389ffb8..08dd37eb 100644 --- a/docs/modules/vue.md +++ b/docs/modules/vue.md @@ -34,7 +34,7 @@ Create a SFC component in `components`. ``` -Create a route and serve the Vue `PostIndex` component. +Serve it from a route. ```js caption=routes/vue.js import { view } from "primate"; @@ -51,16 +51,10 @@ export default { }; ``` -Your rendered Vue component will be accessible at http://localhost:6161/react. +The rendered component will be accessible at http://localhost:6161/react. ## Configuration options -### directory - -Default `config.location.components` - -Directory where the Vue SFC components reside. - ### extension Default `".vue"` diff --git a/docs/modules/websocket.md b/docs/modules/websocket.md deleted file mode 100644 index 9a545d5d..00000000 --- a/docs/modules/websocket.md +++ /dev/null @@ -1,119 +0,0 @@ -# WebSocket - -This module adds WebSocket support in routes to your application. - -## Install - -`npm install @primate/ws` - -## Configure - -Import and initialize the module in your configuration. - -```js caption=primate.config.js -import ws from "@primate/ws"; - -export default { - modules: [ - ws(), - ], -}; -``` - -## Use - -This module adds a new verb that you can use in your routes, `ws`. Although -web sockets are a `GET` upgrade, this route is distinct from a `get` route, -which you could use to send the JavaScript client used to connect. Like all -other routes, you can have multiple WebSocket routes each with their different -logic. - -```js caption=routes/chat.js -import { view } from "primate"; - -export default { - get() { - return view("chat.html"); - }, - ws(request) { - const limit = Number(request.query.get("limit")); - let n = 1; - return { - message(payload) { - if (n > 0 && n < limit) { - n++; - return `You wrote ${payload}`; - } - }, - }; - }, -}; -``` - -```html caption=components/chat.html - - -
- -
- -``` - -By that example, a client requesting a `GET` WebSocket upgrade at -`/chat?limit=20` will be able to start chat communication with the server using -web sockets, with a limit of 20 messages. - -## Error list - -### Invalid Handler - -Level [`Error`][error] - -WebSocket routes must return a valid handler, such as, - -```js caption=routes/websocket-route.js | valid handler -export default { - ws() { - return { - message(payload) { - return `You sent ${JSON.stringify(payload)}!`; - }, - }; - }, -}; -``` - -*As a handler, return an object that handles at least the `message` event.* - -## Resources - -* [Repository][repo] -* [Error list](/reference/errors/primate/ws) - -[repo]: https://github.com/primatejs/primate/tree/master/packages/ws -[error]: /guide/logging#error diff --git a/packages/binding/package.json b/packages/binding/package.json index faee2383..349c6dbc 100644 --- a/packages/binding/package.json +++ b/packages/binding/package.json @@ -1,6 +1,6 @@ { "name": "@primate/binding", - "version": "0.3.0", + "version": "0.4.0", "description": "Primate binding module", "homepage": "https://primatejs.com/modules/binding", "bugs": "https://github.com/primatejs/primate/issues", @@ -27,7 +27,7 @@ "@ruby/head-wasm-wasi": "2.5", "@ruby/wasm-wasi": "2.5", "@swc/core": "1.3", - "primate": "0.28", + "primate": "0.29", "pyodide": "0.25" }, "peerDependenciesMeta": { diff --git a/packages/build/package.json b/packages/build/package.json index beca0193..839f55ce 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -1,6 +1,6 @@ { "name": "@primate/build", - "version": "0.3.2", + "version": "0.4.0", "description": "Primate build module", "homepage": "https://primatejs.com/modules/build", "bugs": "https://github.com/primatejs/primate/issues", @@ -23,7 +23,7 @@ }, "peerDependencies": { "esbuild": "0.20", - "primate": "0.28" + "primate": "0.29" }, "peerDependenciesMeta": { "esbuild": { diff --git a/packages/create-primate/src/templates/web-app.js b/packages/create-primate/src/templates/web-app.js index cd335cc8..8c1a8fff 100644 --- a/packages/create-primate/src/templates/web-app.js +++ b/packages/create-primate/src/templates/web-app.js @@ -47,19 +47,6 @@ export default async root => { }, }); } - if (await confirm({ message: `Add WebSocket support? ${link("websocket")}` })) { - configs.push({ - dependencies: { - "@primate/ws": dependencies["@primate/ws"], - }, - imports: { - ws: "@primate/ws", - }, - modules: { - ws: "", - }, - }); - } if (await confirm({ message: `Enable bundling? ${link("build")}` })) { configs.push({ diff --git a/packages/frontend/package.json b/packages/frontend/package.json index a6c86840..802dbd85 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@primate/frontend", - "version": "0.11.0", + "version": "0.12.0", "description": "Primate frontend code", "bugs": "https://github.com/primatejs/primate/issues", "license": "MIT", @@ -43,7 +43,7 @@ "handlebars": "4", "htmx-esm": "0.2", "marked": "12", - "primate": "0.28", + "primate": "0.29", "react": "18", "react-dom": "18", "solid-js": "1", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 5a9ea8ee..e790d8f0 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@primate/i18n", - "version": "0.4.0", + "version": "0.5.0", "description": "Primate I18N module", "homepage": "https://primatejs.com/modules/i18n", "bugs": "https://github.com/primatejs/primate/issues", @@ -19,7 +19,7 @@ "rcompat": "^0.7.2" }, "peerDependencies": { - "primate": "0.28" + "primate": "0.29" }, "type": "module", "exports": { diff --git a/packages/primate/package.json b/packages/primate/package.json index 373ff1fe..aa01fa45 100644 --- a/packages/primate/package.json +++ b/packages/primate/package.json @@ -1,6 +1,6 @@ { "name": "primate", - "version": "0.28.0", + "version": "0.29.0", "description": "Polymorphic development platform", "homepage": "https://primatejs.com", "bugs": "https://github.com/primatejs/primate/issues", diff --git a/packages/session/package.json b/packages/session/package.json index 95cb9d4f..85466414 100644 --- a/packages/session/package.json +++ b/packages/session/package.json @@ -1,6 +1,6 @@ { "name": "@primate/session", - "version": "0.16.0", + "version": "0.17.0", "description": "Primate session module", "homepage": "https://primatejs.com/modules/session", "bugs": "https://github.com/primatejs/primate/issues", @@ -18,7 +18,7 @@ "rcompat": "^0.7.2" }, "peerDependencies": { - "primate": "0.28" + "primate": "0.29" }, "type": "module", "exports": "./src/exports.js" diff --git a/packages/store/package.json b/packages/store/package.json index cf446c17..02001ce1 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -1,6 +1,6 @@ { "name": "@primate/store", - "version": "0.21.0", + "version": "0.22.0", "description": "Primate data store module", "homepage": "https://primatejs.com/modules/store", "bugs": "https://github.com/primatejs/primate/issues", @@ -22,7 +22,7 @@ "rcompat": "^0.7.2" }, "devDependencies": { - "@primate/types": "^0.12.0", + "@primate/types": "^0.13.0", "better-sqlite3": "^9.4.0", "mongodb": "^6.3.0", "mysql2": "^3.9.1", @@ -34,7 +34,7 @@ "mongodb": "6", "mysql2": "3", "postgres": "3", - "primate": "0.28", + "primate": "0.29", "surrealdb.js": "0.11" }, "peerDependenciesMeta": { diff --git a/packages/types/package.json b/packages/types/package.json index 3b345d91..73d5260f 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@primate/types", - "version": "0.12.0", + "version": "0.13.0", "description": "Primate types module", "homepage": "https://primatejs.com/modules/types", "bugs": "https://github.com/primatejs/primate/issues", @@ -18,7 +18,7 @@ "rcompat": "^0.7.2" }, "peerDependencies": { - "primate": "0.28" + "primate": "0.29" }, "type": "module", "exports": "./src/exports.js" diff --git a/packages/website/components/Homepage.svelte b/packages/website/components/Homepage.svelte index e0e42470..feeac026 100644 --- a/packages/website/components/Homepage.svelte +++ b/packages/website/components/Homepage.svelte @@ -103,6 +103,7 @@ +

Seamlessly switch between frontend @@ -163,11 +164,11 @@

-

Use esbuild for hot reload during development and bundling in - production, add user sessions, web - sockets, and liveview browsing, or - write your own modules using the available hooks. +

Use esbuild for hot reload during + development and bundling in production, add + user sessions or + write your own modules using the + available hooks.

@@ -198,7 +199,8 @@ Svelte - React, Vue, Svelte, Solid, HTMX, Handlebars, WC + React, Vue, Svelte, Angular, Solid, HTMX, Handlebars, WC, + Handlebars, Marko @@ -253,7 +255,14 @@ ✗ ✗ ✗ - @primate/ws + ✓ + + + Server-sent events + ✗ + ✗ + ✗ + ✓ User sessions diff --git a/packages/website/primate.config.js b/packages/website/primate.config.js index ff713d1d..da9dba98 100644 --- a/packages/website/primate.config.js +++ b/packages/website/primate.config.js @@ -153,6 +153,7 @@ export default { "React", "Solid", "Vue", + "Angular", "Web Components", "HTMX", "Handlebars", @@ -168,10 +169,8 @@ export default { "Python", "Ruby", { heading: "Others" }, - "Liveview", "Session", "I18N", - "WebSocket", "Build", ], }, diff --git a/packages/website/static/logos/angular.svg b/packages/website/static/logos/angular.svg new file mode 100644 index 00000000..bf081acb --- /dev/null +++ b/packages/website/static/logos/angular.svg @@ -0,0 +1,16 @@ + + + + + + + + + +