From adebfca7ea52827e19603a92609759250d2e6c63 Mon Sep 17 00:00:00 2001 From: terrablue <102580937+terrablue@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:50:09 +0200 Subject: [PATCH] 0.32 --- README.md | 108 ++++--- docs/blog/introducing-a-head-component.md | 36 ++- docs/blog/introducing-rcompat.md | 42 ++- docs/blog/release-021.md | 4 +- docs/blog/release-022.md | 6 +- docs/blog/release-023.md | 77 +---- docs/blog/release-025.md | 26 +- docs/blog/release-026.md | 12 +- docs/blog/release-027.md | 24 +- docs/blog/release-028.md | 30 +- docs/blog/release-029.md | 26 +- docs/blog/release-031.md | 2 +- docs/blog/release-032.json | 5 + docs/blog/release-032.md | 314 ++++++++++++++++++++ docs/blog/supporting-solid.md | 4 +- docs/examples/backend.md | 5 +- docs/examples/frontend.md | 2 +- docs/examples/i18n.md | 12 +- docs/guide/components.md | 4 +- docs/guide/configuration.md | 73 ++--- docs/guide/errors.md | 6 +- docs/guide/extending-primate.md | 5 +- docs/guide/getting-started.md | 23 +- docs/guide/guards.md | 2 +- docs/guide/hooks.md | 2 +- docs/guide/layouts.md | 4 +- docs/guide/logging.md | 59 +--- docs/guide/responses.md | 41 +-- docs/guide/routes.md | 8 +- docs/guide/types.md | 156 +--------- docs/modules/angular.md | 8 +- docs/modules/{binding.md => backend.md} | 26 +- docs/modules/drivers.md | 101 ++++--- docs/modules/eta.md | 67 +++++ docs/modules/frontend.md | 18 +- docs/modules/go.md | 15 +- docs/modules/handlebars.md | 8 +- docs/modules/html.md | 69 +++++ docs/modules/htmx.md | 8 +- docs/modules/i18n.md | 9 +- docs/modules/markdown.md | 8 +- docs/modules/marko.md | 8 +- docs/modules/native.md | 66 ++++ docs/modules/python.md | 17 +- docs/modules/react.md | 10 +- docs/modules/ruby.md | 15 +- docs/modules/solid.md | 14 +- docs/modules/store.md | 63 ++-- docs/modules/svelte.md | 8 +- docs/modules/types.md | 30 +- docs/modules/typescript.md | 13 +- docs/modules/voby.md | 67 +++++ docs/modules/vue.md | 8 +- docs/modules/web-components.md | 12 +- packages/website/components/Homepage.svelte | 5 +- packages/website/primate.config.js | 24 +- 56 files changed, 1108 insertions(+), 707 deletions(-) create mode 100644 docs/blog/release-032.json create mode 100644 docs/blog/release-032.md rename docs/modules/{binding.md => backend.md} (83%) create mode 100644 docs/modules/eta.md create mode 100644 docs/modules/html.md create mode 100644 docs/modules/native.md create mode 100644 docs/modules/voby.md diff --git a/README.md b/README.md index 6aafef1f..1db35953 100644 --- a/README.md +++ b/README.md @@ -12,55 +12,83 @@ Polymorphic development platform. To start [read guide]. [![GitHub last commit](https://img.shields.io/github/last-commit/primatejs/primate?style=for-the-badge)](https://github.com/primatejs/primate/commits/master) [![Discord](https://img.shields.io/discord/1256590312177012806?style=for-the-badge&logo=discord&label=Discord&logoColor=a16836&color=5865f2)](https://discord.gg/RSg4NNwM4f) -## Why use Primate? +## Why Primate? -### Framework Independence +### Mix and match the best web tech, in one stack -Primate stands apart as a framework-agnostic tool, allowing you to seamlessly -integrate and start coding within any major framework, eliminating the -constraints of being tied to specific options like Nuxt, Next, or others. +Primate is a frontend and backend-agnostic tool, allowing you to seamlessly +integrate and start coding within any major frontend framework and several +backend languages, eliminating the constraints of being tied to specific +options like Next, Nuxt, or others. Primate runs on Node, Deno and Bun with the +same codebase. -### Frameworks We Support +### Supported backends -- Svelte +- Go +- JavaScript +- Python +- Ruby +- TypeScript + +### Supported frontends + +- Angular +- Eta +- Handlebars +- HTML +- HTMX +- Markdown +- Marko - React - Solid +- Svelte +- Voby - Vue -- Angular - Web Components -- HTMX -- Handlebars -- Marko -### Databases We Support +### Supported databases -- SQLite - MongoDB -- Postgresql - MySQL +- Postgresql +- SQLite - SurrealDB -### Languages We Support - -- JavaScript -- TypeScript -- Golang -- Python -- Ruby - ## Packages | Package | Description | |---------------------------------------------|-------------------------------| |[primate](packages/primate) | Primate framework | -|[create-primate](packages/create-primate) | GUI for creating Primate apps | -|[@primate/frontend](packages/frontend) | Frontend frameworks | -|[@primate/store](packages/store) | Data store | -|[@primate/types](packages/types) | Runtime types | +|[@primate/core](packages/core) | Core framework | +|[@primate/go](packages/go) | Go backend | +|[@primate/python](packages/python) | Python backend | +|[@primate/ruby](packages/ruby) | Ruby backend | +|[@primate/typescript](packages/typescript) | TypeScript backend | +|[@primate/angular](packages/angular) | Angular frontend | +|[@primate/eta](packages/eta) | Eta frontend | +|[@primate/handlebars](packages/handlebars) | Handlebars frontend | +|[@primate/html](packages/html) | HTML frontend | +|[@primate/htmx](packages/htmx) | HTMX frontend | +|[@primate/markdown](packages/markdown) | Markdown frontend | +|[@primate/marko](packages/marko) | Marko frontend | +|[@primate/react](packages/react) | React frontend | +|[@primate/solid](packages/solid) | Solid frontend | +|[@primate/svelte](packages/svelte) | Svelte frontend | +|[@primate/voby](packages/voby) | Voby frontend | +|[@primate/vue](packages/vue) | Vue frontend | +|[@primate/webc](packages/webc) | Web Components frontend | +|[@primate/store](packages/store) | Databases | +|[@primate/mongodb](packages/mongodb) | MongoDB database | +|[@primate/mysql](packages/mysql) | MySQL database | +|[@primate/postgresql](packages/postgresql) | PostgreSQL database | +|[@primate/sqlite](packages/sqlite) | SQLite database | +|[@primate/surrealdb](packages/surrealdb) | SurrealDB database | +|[@primate/types](packages/types) | Schema validation | |[@primate/session](packages/session) | User sessions | |[@primate/i18n](packages/i18n) | Internationalization | -|[@primate/binding](packages/binding) | Other backend languages | +|[@primate/native](packages/native) | Compile native apps | |[website](packages/website) | Primate website | +|[create-primate](packages/create-primate) | GUI for creating Primate apps | ## Comparison with other frameworks @@ -68,12 +96,12 @@ constraints of being tied to specific options like Nuxt, Next, or others. |------------------|------|------|---------|--------------------------------------------------------| |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 | +|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 | +|Data stores/ORM |✗ |✗ |✗ |MongoDB, MySQL, PostgreSQL, SQLite, SurrealDb | |WebSockets |✗ |✗ |✗ |✓ | |Server-sent events|✗ |✗ |✗ |✓ | |User sessions |✗ |✓ |✗ |@primate/session | @@ -81,17 +109,12 @@ constraints of being tied to specific options like Nuxt, Next, or others. ## Resources * Website: https://primatejs.com -* IRC: Join the `#primate` channel on `irc.libera.chat` * Discord: https://discord.gg/RSg4NNwM4f * Reddit: [r/primatejs](https://reddit.com/r/primatejs) * Twitter (X): [@primatejs](https://x.com/primatejs) * Blog: https://primatejs.com/blog * StackOverflow: https://stackoverflow.com/questions/tagged/primate - -## Example Applications - -- [starter app](https://github.com/primatejs/app) - demos most of the features of Primate -- [FastestEngineer](https://fastest.engineer) - A fully-featured SaaS boilerplate +* Demo app: https://github.com/primatejs/app ## License @@ -102,10 +125,15 @@ MIT By contributing to Primate, you agree that your contributions will be licensed under its MIT license. -Clone https://github.com/primatejs/app alongside your Primate directory and -switch to the `dev` branch. This branch uses symbolic links to Primate and its -modules. In the case of some modules (`@primate/frontend`, `@primate/i18n`, -`@primate/binding`), symbolic links lead to errors and the modules need to be -copied in verbatim. Use the `refresh-deps.sh` script to do so. +Clone this repo and https://github.com/primatejs/app in the same location, +and switch to the `dev` branch in the app repo. Then, in the app repo, run + +* `npm run node` for Node in development mode +* `npm run node:prod` for Node in production mode +* `npm run deno` for Deno in development mode +* `npm run deno:prod` for Deno in production mode +* `npm run bun` for Bun in development mode +* `npm run bun:prod` for Bun in production mode +* `npm run bun:compile` for compiling desktop app with Bun [read guide]: https://primatejs.com/guide/getting-started diff --git a/docs/blog/introducing-a-head-component.md b/docs/blog/introducing-a-head-component.md index e23913b4..c8428436 100644 --- a/docs/blog/introducing-a-head-component.md +++ b/docs/blog/introducing-a-head-component.md @@ -13,17 +13,13 @@ If you're new to Primate, we recommend reading the [Getting started] page to get an idea of the framework. !!! -## Install - -To use `Head`, update `@primate/frontend` to version `0.5.0` or later. - ## Use -In a component of your choice, import `Head` from `@primate/frontend/react` and +In a component of your choice, import `Head` from `@primate/react` and use it anywhere within the component. ```js caption=components/PostIndex.jsx -import { Head } from "@primate/frontend/react"; +import Head from "@primate/react/head"; export default function (props) { return <> @@ -40,22 +36,22 @@ export default function (props) { ``` !!! -For Solid, replace `@primate/frontend/react` with `@primate/frontend/solid`. +For Solid, replace `@primate/react` with `@primate/solid`. !!! You can also use `Head` in any layout. During SSR, a combined list of head tags will be generated and sent along with the page. Later during hydration, the client components will take over management of their head tags. -If you use `@primate/liveview` to navigate between pages without a full reload, -`Head` will manage its head tags between page changes, automatically removing -the tags used by the previous page's components and inserting new ones. Tags in -`pages/app.html` won't be managed by `Head` and will be left intact. +When you navigate between pages without a full reload, `Head` will manage its +head tags between page changes, automatically removing the tags used by the +previous page's components and inserting new ones. Tags in `pages/app.html` +won't be managed by `Head` and will be left intact. ## Use outside of Primate -As `@primate/frontend` exports `react/Head` and `solid/Head` and has virtually -no dependencies, you can use it even if you don't use Primate itself. +As `@primate/react` and `@primate/solid` exports `/Head` and have virtually no +dependencies, you can use it even if you don't use Primate itself. ### Without SSR @@ -117,18 +113,20 @@ The only thing left to do is wrap your root component with a context provider. It is assumed that `body` here contains your component hierarchy. ```js caption=root-component-react.jsx -import { HeadContext, is } from "@primate/frontend/react"; +import HeadContext from "@primate/react/context/head"; +import platform from "@rcompat/platform"; + const Provider = HeadContext.Provider; export default ({ components, data, push_heads: value }) => - is.client ? body : {body}; + platform === "browser" ? body : {body}; ``` -For Solid, use `@primate/frontend/solid` instead for the import. +For Solid, use `@primate/solid` instead for the import. -We use here the `is` export to check if we're on the client or the server. You -don't have to do it, but using the provider on the client doesn't make a lot of -sense. +We use check, using `@rcompat/platform` if we're on the client or the server. +You don't have to do this, but using the provider on the client doesn't make a +lot of sense. ## Fin diff --git a/docs/blog/introducing-rcompat.md b/docs/blog/introducing-rcompat.md index 74b6e9a0..bf687279 100644 --- a/docs/blog/introducing-rcompat.md +++ b/docs/blog/introducing-rcompat.md @@ -29,30 +29,30 @@ JavaScript offers this kind of flexibility. ## Batteries included -rcompat is designed with many submodules in mind, including `rcompat/fs` for -filesystem operations, `rcompat/http` for using a modern HTTP server working +rcompat is designed with many submodules in mind, including `@rcompat/fs` for +filesystem operations, `@rcompat/http` for using a modern HTTP server working with WHATWG `Request`/`Response` (which Node doesn't support; rcompat wraps a Node request object into a WHATWG `Request` as it comes in), -`rcompat/invariant` for ensuring runtime invariants, `rcompat/object` for +`@rcompat/invariant` for ensuring runtime invariants, `@rcompat/object` for object transformations, and many more useful modules and abstractions. The standard library is designed to accommodate modern development needs: for -example, `rcompat/http` supports WebSockets (natively on Deno/Bun, and using -NPM's `ws` on Node), while `rcompat/fs.File` offers globbing, listing and +example, `@rcompat/http` supports WebSockets (natively on Deno/Bun, and using +NPM's `ws` on Node), while `@rcompat/fs/file` offers globbing, listing and manipulation of files, similarly to Python's `pathlib`. -For example, to set up a server with rcompat, use the `serve` export of -`rcompat/http` -- the server-side equivalent of `fetch`. +For example, to set up a server with rcompat, use `@rcompat/http/serve` -- the +server-side equivalent of `fetch`. ```js -import { serve } from "rcompat/http"; +import serve from "@rcompat/http/serve"; serve(request => new Response("Hi!"), { host: "localhost", port: 6161 }); ``` This code runs successfully with either `node app.js` (if you set your -package.json to `{ "type": "module" }`; otherwise use `app.mjs`), `deno run ---allow-all app.js` or `bun --bun app.js`, taking advantage of native +package.json to `{ "type": "module" }`; otherwise use `app.mjs`), +`deno run -A app.js` or `bun --bun app.js`, taking advantage of native optimizations. ## Another standard library? @@ -69,11 +69,9 @@ target everything. For example, here's how you can read a file and parse it as JSON. ```js -import FS from "rcompat/fs"; -// or import individually, shadowing globalThis.File, WHATWG's File class -// import { File } from "rcompat/fs"; +import file from "@rcompat/fs/file"; -console.log(await FS.File.json("./users.json")); +console.log(await file("./users.json").json()); ``` Again, this code runs successfully on Node, Deno or Bun, taking advantage of @@ -86,14 +84,14 @@ Primate's development and is largely influenced by its needs. We'd like to invite more participation by other projects / individuals in order to converge on APIs that best serve everyone and are the most useful on a broad basis. -To illustrate this, Primate 0.31 will be using `rcompat/fs`'s upcoming `Router` -class, which is meant to be used by frameworks using filesystem-routing (such -as Primate, Next, SvelteKit, etc.) to resolve requests to routes. The design is -aimed to be generic, but undoubtedly will be influenced by Primate's needs. -External feedback will help keep it useful for other frameworks as well. -Once `FS.Router` is ready, we will also aim to upstream our ideas to Bun's -native [FileSystemRouter][FileSystemRouter] class such that rcompat can -delegate to it natively on Bun. +To illustrate this, Primate 0.31 will be using `@rcompat/fs/router`'s upcoming +`Router` class, which is meant to be used by frameworks using +filesystem-routing (such as Primate, Next, SvelteKit, etc.) to resolve requests +to routes. The design is aimed to be generic, but undoubtedly will be +influenced by Primate's needs. External feedback will help keep it useful for +other frameworks as well. Once `Router` is ready, we will also aim to upstream +our ideas to Bun's native [FileSystemRouter][FileSystemRouter] class such that +rcompat can delegate to it natively on Bun. ## Participation diff --git a/docs/blog/release-021.md b/docs/blog/release-021.md index ebbb8d68..c82a56e7 100644 --- a/docs/blog/release-021.md +++ b/docs/blog/release-021.md @@ -62,7 +62,7 @@ and responds with a proper handler. Here is an example with an error route file rendering a Svelte component. ```js caption=routes/+error.js -import { view } from "primate"; +import view from "primate/handler/view"; export default request => view("ErrorPage.svelte"); ``` @@ -113,7 +113,7 @@ passing a `page` property to the third handler parameter. The page file itself must be located under `pages`. ```js caption=routes/+error.js -import { view } from "primate"; +import view from "primate/handler/view"; export default request => view("ErrorPage.svelte", {}, { page: "other-error.html", diff --git a/docs/blog/release-022.md b/docs/blog/release-022.md index f18c8d56..20935719 100644 --- a/docs/blog/release-022.md +++ b/docs/blog/release-022.md @@ -43,7 +43,7 @@ energy to mankind. Lastly, serve your Markdown component from a route of your choice. ```js caption=routes/about-us.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -60,7 +60,7 @@ props to it is meaningless. However, you can still use a different page with it by modifying the `page` property of the third (options) parameter. ```js caption=routes/about-us.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -180,7 +180,7 @@ function directly as an export of `@primate/markdown`. This allows you to compile and serve Markdown content from a dynamic source (like a database). ```js caption=routes/markdown/{page}.js -import { view } from "primate"; +import view from "primate/handler/view"; import { compile } from "@primate/markdown"; export default { diff --git a/docs/blog/release-023.md b/docs/blog/release-023.md index 9185e965..581113e0 100644 --- a/docs/blog/release-023.md +++ b/docs/blog/release-023.md @@ -14,10 +14,10 @@ This release adds support for a Handlebars frontend handler, including precompiling. The Handlebars handler works like all other frontend handlers. To activate it, -load the module in your configuration. +install and load the module in your configuration. ```js caption=primate.config.js -import { handlebars } from "@primate/frontend"; +import handlebars from "@primate/handlebars"; export default { modules: [ @@ -40,7 +40,7 @@ Then place a Handlebars file in your `components` directory. Lastly, serve your Handlebars component from a route of your choice. ```js caption=routes/index.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -57,40 +57,22 @@ export default { If you then run Primate, your Handlebars component should be served at `GET /` as HTML. -Like other frontend handlers, you can change the directory from which -Handlebars components are loaded and the file extension associated with them by -changing the module configuration. - -```js caption=primate.config.js -import { handlebars } from "@primate/frontend"; - -export default { - modules: [ - handlebars({ - // load Handlebars files from $project_root$/hbs - // default: `config.location.components` - directory: "hbs", - // using the "handlebars" file extension - // default: "hbs" - extension: "handlebars", - }), - ], -}; -``` - ## Transactions across the board -With the exception of SurrealDB, all supported data store drivers in -`@primate/store` (SQLite, PostgreSQL, MongoDB) now support transactions within -routes. You don't need to explictly start a transaction or commit it at the -end; the store module does that for you automatically. If at any point during -the route execution an error occurs, the transaction will be rolled back and no -changes will be committed to the data store. +With the exception of SurrealDB, all supported database drivers now support +transactions within routes. You don't need to explictly start a transaction or +commit it in the end; the store module does that for you automatically. If at +any point during the route execution an error occurs, the transaction will be +rolled back and no changes will be committed to the data store. Consider the following store. ```js caption=stores/User.js -import { primary, string, u8, email, date } from "primate/@types"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; +import u8 from "@primate/types/u8"; +import email from "@primate/types/email"; +import date from "@primate/types/date"; export default { id: primary, @@ -106,7 +88,7 @@ with an `age` value that is larger than 2^8-1. Validation will fail, and the record won't be inserted. ```js caption=routes/index.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get(request) { @@ -152,35 +134,6 @@ behavior you would see without a bundler, where all files are kept separately. This release features several quality of life improvements. -### Consolidation of frontend modules - -Previously, the frontend modules were split across several packages, such as -`@primate/svelte`, `@primate/react` and so on. As the frontend handlers -increasingly started sharing code, we decided to unify them under a common -`@primate/frontend` package with individual exports. - -For example, if you need the Svelte handler, you would use the `svelte` export -of `@primate/frontend`. - -```js caption=primate.config.js -import { svelte } from "@primate/frontend"; - -export default { - modules: [ - svelte(), - ], -}; -``` - -If you don't have Svelte itself installed, Primate will tell that it's missing -the dependency and what command you need to issue to install Svelte. - -```sh -!! primate/frontend cannot find svelte (imported from frontend:svelte) -++ install dependencies by issuing npm install svelte@ - -> https://primatejs.com/reference/errors/primate/frontend#missing-dependencies -``` - ### Hoisted frontend props Previously, all props passed from a route to its frontend component were @@ -190,7 +143,7 @@ props are made available directly to the frontend component. Consider this Svelte route. ```js caption=routes/svelte.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, diff --git a/docs/blog/release-025.md b/docs/blog/release-025.md index f93c2caa..872a9927 100644 --- a/docs/blog/release-025.md +++ b/docs/blog/release-025.md @@ -72,14 +72,13 @@ Add another locale. } ``` -Next, use the default deep export from `@primate/i18n` for your frontend of -choice in your component, for example Svelte. +Next, import `@primate/svelte/i18n`, if you're a Svelte user. ### Svelte ```js caption=components/Home.svelte @@ -93,12 +92,13 @@ choice in your component, for example Svelte. In the case of Svelte, since the default export exposes a store, you need to subscribe to it by prefixing it with `$` wherever you use it. -To switch between locales, use the `locale` named export and call `locale.set` -with the new locale. +To switch between locales, import `@primate/svelte/locale` and call +`locale.set` with the new locale. ```js caption=components/Home.svelte @@ -118,8 +118,8 @@ with the new locale. You can use an almost identical API for React and Solid to achieve the same. ```jsx caption=components/Home.jsx -import t from "@primate/i18n/react"; -// import t from "@primate/i18n/solid"; // for solid +import t from "@primate/react/i18n"; +// import t from "@primate/solid/i18n"; // for solid export default function ({ username }) { return <> @@ -135,12 +135,14 @@ export default function ({ username }) { In this case, since the default export exposes a function that returns a state variable, you just use it as is (without prefixing it with `$` as with Svelte). -Again, to switch between locales, use the `locale` named export and call -`locale.set` with the new locale. +Again, to switch between locales, call `locale.set` with the new locale. ```jsx -import { default as t, locale } from "@primate/i18n/react"; -// import { default as t, locale } from "@primate/i18n/solid"; // for solid +import t from "@primate/react/i18n"; +import locale from "@primate/react/locale"; +// for Solid +// import t from "@primate/solid/i18n"; +// import locale from "@primate/solid/locale"; export default function ({ username }) { return <> diff --git a/docs/blog/release-026.md b/docs/blog/release-026.md index 016846d8..fca4ea36 100644 --- a/docs/blog/release-026.md +++ b/docs/blog/release-026.md @@ -21,9 +21,9 @@ rendering. ### Install -To add support for Go, install the `@primate/binding` module. +To add support for Go, install the `@primate/go` module. -`npm install @primate/binding` +`npm install @primate/go` In addition, your system needs to have the `go` executable in its path, as it is used to compile Go routes into WebAssembly. @@ -33,7 +33,7 @@ is used to compile Go routes into WebAssembly. Import and initialize the module in your configuration. ```js caption=primate.config.js -import { go } from "@primate/binding"; +import go from "@primate/go"; export default { modules: [ @@ -155,7 +155,7 @@ To use an HTMX extension, pass it to the HTMX module's `extensions` array property in your Primate configuration. ```js primate.config.js -import { htmx } from "@primate/frontend"; +import htmx from "@primate/htmx"; export default { modules: [ @@ -170,7 +170,7 @@ If you're using the `client-side-templates` extension, include the individual client side templates in the `client_side_templates` array property. ```js primate.config.js -import { htmx } from "@primate/frontend"; +import htmx from "@primate/frontend/htmx"; export default { modules: [ @@ -275,7 +275,7 @@ Some of the things we plan to tackle in the upcoming weeks are, * Add projections and relations to stores * Multidriver transactions * Introduce IDE TypeScript support -* Add support for TypeScript (.ts) routes in `@primate/binding` +* Add support for TypeScript (.ts) routes * 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 diff --git a/docs/blog/release-027.md b/docs/blog/release-027.md index 45026ed0..bc0b8301 100644 --- a/docs/blog/release-027.md +++ b/docs/blog/release-027.md @@ -20,16 +20,16 @@ or Solid -- including SSR, hydration, and client side rendering. ### Install -To add support for Python, install the `@primate/binding` module and `pyodide`. +To add support for Python, install the `@primate/python` package. -`npm install @primate/binding pyodide@0.24` +`npm install @primate/python` ### Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { python } from "@primate/binding"; +import python from "@primate/python"; export default { modules: [ @@ -43,7 +43,7 @@ standard library) you'd like to use to the `packages` configuration array of the module. ```js caption=primate.config.js -import { python } from "@primate/binding"; +import python from "@primate/python"; export default { modules: [ @@ -125,8 +125,8 @@ handler. This is particularly useful case you're using a template engine such as Handlebars to generate an XML file. ```js caption=routes/sitemap.xml.js -import { view } from "primate"; -import { MediaType } from "rcompat/http"; +import view from "primate/handler/view"; +import { xml } from "@rcompat/http/mime"; // this assumes you've imported and loaded the `handlebars` module from // `@primate/frontend` @@ -136,7 +136,7 @@ export default { // load data and save it in a variable `pages` // ... - const headers = { "Content-Type": MediaType.APPLICATION_XML }; + const headers = { "Content-Type": xml }; // serve Handlebars template as XML return view("sitemap.hbs", { pages }, { headers }); @@ -155,7 +155,7 @@ layout, and this is now achievable by using `export const recursive = false;` within the `+layout.js` file. ```js caption=routes/inner/+layout.js -import { view } from "primate" ; +import view from "primate/handler/view" ; export default () => { return view("inner-layout.svelte"); @@ -180,7 +180,7 @@ As of this release we differentiate between how `request.body` and `request.{path,query,cookies,headers}` behave. `request.body`'s properties are now accessed directly instead of with `request.body.all()` before. -This change also applies to the Go binding, where request.Body is now a +This change also applies to the Go backend, where request.Body is now a `map[string]any`. ### .all removed from dispatchers @@ -188,14 +188,14 @@ This change also applies to the Go binding, where request.Body is now a Dispatchers (`request.{path,query,headers,cookies}`) no longer expose a `.all` method. -This change also applies to the Go binding, where `Dispatcher` no longer has an +This change also applies to the Go backend, where `Dispatcher` no longer has an `All` function. ### .all removed from request.session `request.session` no longer exposes a `.all` method. -This change also applies to the Go binding, where `Session` no longer has an +This change also applies to the Go backend, where `Session` no longer has an an `All` function. ## Other changes @@ -209,7 +209,7 @@ Some of the things we plan to tackle in the upcoming weeks are, * Add projections and relations to stores * Multidriver transactions * Introduce IDE TypeScript support -* Add support for TypeScript (.ts) routes in `@primate/binding` +* Add support for TypeScript (.ts) routes * 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 diff --git a/docs/blog/release-028.md b/docs/blog/release-028.md index afe67241..b464a9dc 100644 --- a/docs/blog/release-028.md +++ b/docs/blog/release-028.md @@ -15,16 +15,16 @@ create and use TypeScript routes. ### Install -To add support for TypeScript, install the `@primate/binding` module. +To add support for TypeScript, install the `@primate/typescript` module. -`npm install @primate/binding` +`npm install @primate/typescript` ### Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { typescript } from "@primate/binding"; +import typescript from "@primate/typescript"; export default { modules: [ @@ -64,17 +64,16 @@ Under the hood, we make use of the `ruby.wasm` project through WASI. ### Install -To add support for Ruby, install the `@primate/binding` module and the -`@ruby/head-wasm-wasi` and `@ruby/wasm-wasi` packages. +To add support for Ruby, install the `@primate/ruby` package. -`npm install @primate/binding @ruby/head-wasm-wasi@2.5 @ruby/wasm-wasi@2.5` +`npm install @primate/ruby` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { ruby } from "@primate/binding"; +import ruby from "@primate/ruby"; export default { modules: [ @@ -140,14 +139,14 @@ in your application, and in particular to pass props into them. ## Install -`npm install @primate/frontend` +`npm install @primate/webc` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { webc } from "@primate/frontend"; +import webc from "@primate/webc"; export default { modules: [ @@ -158,8 +157,8 @@ export default { ## Use -To create a web component, import `Component` from `@primate/frontend/webc` and -default export a class extending it. Implement the `render` function property +To create a web component, import `@primate/webc/Component` and create a +default export of class extending it. Implement the `render` function property of that class, which returns a string representing the HTML code of this component. Note that `Component` extends `HTMLElement`, and is thus a proper web component for any purpose. @@ -175,7 +174,7 @@ Create an web component in `components`. ```html caption=components/post-index.webc +

{$t("Counter")}

+
+ + +{count} +
+

{$t("Switch language")}

+
locale.set("en-US")}>{$t("English")}
+
locale.set("de-DE")}>{$t("German")}
+``` + +## 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, + +* 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 SPA 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.33, and other features may be prioritized according to +feedback. + +## Fin + +If you like Primate, consider [joining our Discord server][discord]. + +Otherwise, have a blast with the new version! + +[rcompat]: /blog/introducing-rcompat +[Getting started]: /guide/getting-started +[irc]: https://web.libera.chat#primate +[changelog]: https://github.com/primatejs/primate/releases/tag/0.32.0 +[Eta]: https://eta.js.org +[Voby]: https://github.com/vobyjs/voby +[discord]: https://discord.gg/RSg4NNwM4f +[HTML frontend]: /modules/html diff --git a/docs/blog/supporting-solid.md b/docs/blog/supporting-solid.md index 9bac285a..ac015e22 100644 --- a/docs/blog/supporting-solid.md +++ b/docs/blog/supporting-solid.md @@ -56,7 +56,7 @@ To use a Solid component, create a route under `routes`. This example assumes you have changed the Solid component file extension to `solid`. ```js caption=routes/posts.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -92,7 +92,7 @@ Create a `+layout.js` file alongside your routes (layouts apply to all routes in their directory and its subdirectories, hierarchically). ```js caption=routes/+layout.js -import { view } from "primate"; +import view from "primate/handler/view"; export default () => { return view("layout.solid", { user: "Tom" }); diff --git a/docs/examples/backend.md b/docs/examples/backend.md index a19216f1..b1d85916 100644 --- a/docs/examples/backend.md +++ b/docs/examples/backend.md @@ -1,7 +1,7 @@ %%% JS, TS, Go, Python, Ruby ```js caption=routes/index.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -16,7 +16,8 @@ export default { ``` ```ts caption=routes/index.ts -import { view, Route } from "primate"; +import type { Route } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, diff --git a/docs/examples/frontend.md b/docs/examples/frontend.md index f610e9d7..6e6b39f2 100644 --- a/docs/examples/frontend.md +++ b/docs/examples/frontend.md @@ -96,7 +96,7 @@ ${posts.map(post => ` ```html caption=components/post-index.webc @@ -54,8 +54,8 @@ export default function ({ username }) { ``` ```jsx caption=components/Index.jsx -import t from "@primate/i18n/solid"; -import { locale } from "@primate/i18n/solid"; +import t from "@primate/solid/i18n"; +import locale from "@primate/solid/locale"; export default function ({ username }) { return <> diff --git a/docs/guide/components.md b/docs/guide/components.md index 1fe853ce..34a0d2dd 100644 --- a/docs/guide/components.md +++ b/docs/guide/components.md @@ -16,7 +16,7 @@ Serve it using the `view` handler, passing in the name of the HTML file you just created. ```js caption=routes/hello.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -63,7 +63,7 @@ page, especially if you're replacing some parts of the page. To this end, you can use the `partial` option of the `view` handler. ```js caption=routes/partial-hello.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 0dfa35da..0a4c328d 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -10,19 +10,15 @@ If Primate doesn't find a `primate.config.js` in your project root directory a default object, Primate will fall back to its default configuration file. ```js -import { identity } from "rcompat/function"; -import { Logger } from "primate"; - export default { base: "/", modules: [], pages: { - index: "app.html", + app: "app.html", error: "error.html", }, - logger: { - level: Logger.Warn, - trace: false, + log: { + level: "warn", }, http: { host: "localhost", @@ -51,10 +47,7 @@ export default { name: "app", includes: [], excludes: [], - transform: { - paths: [], - mapper: identity, - }, + define: {}, }, }; ``` @@ -68,11 +61,9 @@ To illustrate this, if you wanted to change the default logging level to `primate.config.js` in your project root with the following overrides. ```js -import { Logger } from "primate"; - export default { - logger: { - level: Logger.Info, + log: { + level: "info", }, http: { port: 6262, @@ -84,19 +75,15 @@ Primate will merge your custom configuration with its default, resulting in effectively the following configuration. ```js -import { identity } from "rcompat/function"; -import { Logger } from "primate"; - export default { base: "/", modules: [], pages: { - index: "app.html", + app: "app.html", error: "error.html", }, - logger: { - level: Logger.Info, - trace: false, + log: { + level: "info", }, http: { host: "localhost", @@ -125,10 +112,6 @@ export default { name: "app", includes: [], excludes: [], - transform: { - paths: [], - mapper: identity, - }, }, }; ``` @@ -152,7 +135,7 @@ their hooks will be evaluated, and modules can depend on each using ## Page options -### index +### app Default: `app.html` @@ -172,20 +155,13 @@ Name of the default error HTML page located in `location.pages`. If For more info on logging, refer to the [Logging](/guide/logging) section. -### logger.level +### log.level -Default `Logger.Warn` +Default `"warn"` The logging level to be used. Primate has three logging levels, `Error`, `Warn` and `Info`. -### logger.trace - -Default `false` - -Whether Primate should show the original stack trace of errors in addition to -its own errors. - ## HTTP options ### http.host @@ -343,19 +319,26 @@ Default `[]` A list of files to be excluded from bundling. Wildcards can be used. -### build.transform.paths - -Default `[]` +### build.define -A list of paths for which the contents are to be transformed at runtime before -being copied to the [build directory](#location-build). Relative paths will be -relative to project root. Glob patterns are supported. +Default `{}` -### build.transform.mapper +A map of identifier substitutions during build-time. For example, you could +replace `APP_NAME` with your actual application name, loading from an `.env` +file, by specifying -Default `_ => _` (identity function) +```js +export default { + build: { + define: { + APP_NAME: "'my-app'", + }, + }, +}; +``` -A file content mapper for the files specified in `build.transform.files`. +Note that subtitutions take place as in -- if you want a string to be +substituted in, you need to quote it properly, as in the example. ## pages/app.html diff --git a/docs/guide/errors.md b/docs/guide/errors.md index 8bf4ea00..d7e18113 100644 --- a/docs/guide/errors.md +++ b/docs/guide/errors.md @@ -12,11 +12,11 @@ directory. To define an error route, create a `+error.js` file inside `routes`. Similarly to the special guard and layout files, the error route gets a `request` parameter and can respond with a proper handler. Here is an example -with an error route file rendering a Svelte component (`@primate/frontend` must +with an error route file rendering a Svelte component (`@primate/svelte` must be installed and loaded in the project). ```js caption=routes/+error.js -import { view } from "primate"; +import view from "primate/handler/view"; export default request => view("ErrorPage.svelte"); ``` @@ -58,7 +58,7 @@ passing a `page` property to the third handler parameter. The page itself must be located under `pages`. ```js caption=routes/+error.js -import { view } from "primate"; +import view from "primate/handler/view"; export default request => view("ErrorPage.svelte", {}, { page: "other-error.html", diff --git a/docs/guide/extending-primate.md b/docs/guide/extending-primate.md index 5edb3268..56a680bc 100644 --- a/docs/guide/extending-primate.md +++ b/docs/guide/extending-primate.md @@ -74,8 +74,7 @@ instructing the client to save the cookie so that it sends it with the next request. !!! -Modules may subscribe to any or all hooks, including none. Primate will warn -you in case you try to use a module that subscribes to no hook. All modules must +Modules may subscribe to any or all hooks, including none. All modules must advertise a `name` property, and Primate will refuse to start if it encounters the same name for a module more than once. !!! @@ -122,8 +121,6 @@ All modules are just subscription objects. You can therefore easily create and pass modules directly in your configuration file. ```js caption=primate.config.js -import console from "rcompat/console"; - export default { modules: [{ name: "ad-hoc module", diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 0c2f1592..2e7ace97 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -19,7 +19,7 @@ http://localhost:6161. !!! Primate is a multi-runtime platform. If you're a Bun user, you can take advantage of [significant speed gains][r24] by running `bun --bun x primate`. -If you're a Deno user, use `deno run --allow-all npm:primate` to run Primate. +If you're a Deno user, use `deno run -A npm:primate` to run Primate. !!! !!! @@ -49,8 +49,9 @@ Same as before, run `npx -y primate@latest` and point your browser to http://localhost:6161 to run your route. !!! -Additional backend languages require installing `@primate/binding` and -[initializing the module](https://primatejs.com/modules/binding) in your config. +Additional backend languages require loading +[additional backend packages](https://primatejs.com/modules/backend) in your +config. !!! ## Serving HTML @@ -64,7 +65,7 @@ and redirect users who have submitted the form to a success page. This requires first changing the previous route to show a form. ```js caption=routes/index.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -98,7 +99,8 @@ Next we need to handle the form submission. We'll do that by adding a `post` function to our route. ```js caption=routes/index.js -import { view, redirect } from "primate"; +import view from "primate/handler/view"; +import redirect from "primate/handler/redirect"; export default { get() { @@ -145,12 +147,11 @@ export default { Beyond pure HTML, Primate supports a variety of frontend frameworks. Here is the same code as before, in Svelte. -First add frontend support by issuing `npm install @primate/frontend` and -loading the frontend framework of your choice in your configuration file -(create it first). +First add Svelte support by issuing `npm install @primate/svelte` and +loading it your configuration file. ```js caption=primate.config.js -import { svelte } from "@primate/frontend"; +import svelte from "@primate/svelte"; export default { modules: [ @@ -165,7 +166,7 @@ svelte@4`. Now change your route to serve a Svelte component. ```js caption=routes/index.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -202,7 +203,7 @@ by Svelte. !!! It is likewise easily possible to write React, Vue or HTMX components. Refer to -the [frontend] module page to see what's available. +the [frontend] page to see what's available. !!! ## Deeper dive diff --git a/docs/guide/guards.md b/docs/guide/guards.md index 5f49e68f..1a62e0b1 100644 --- a/docs/guide/guards.md +++ b/docs/guide/guards.md @@ -15,7 +15,7 @@ Guards are defined hierarchically alongside routes in the `routes` directory. To define a guard, create a `+guard.js` file inside `routes`. ```js caption=routes/+guard.js -import { redirect } from "primate"; +import redirect from "primate/handler/redirect"; export default request => { const { headers } = request; diff --git a/docs/guide/hooks.md b/docs/guide/hooks.md index fc155f38..67c37df2 100644 --- a/docs/guide/hooks.md +++ b/docs/guide/hooks.md @@ -145,7 +145,7 @@ By that definition, any `mustache` file in `components` will be handled by the specified `mustacheHandler` handler function. ```js caption=routes/clock.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get(request) { diff --git a/docs/guide/layouts.md b/docs/guide/layouts.md index d55f631f..c91f7911 100644 --- a/docs/guide/layouts.md +++ b/docs/guide/layouts.md @@ -21,11 +21,11 @@ directory. To define a layout, create a `+layout.js` file inside `routes`. Similarly to the special guard files, a layout gets a `request` parameter and can respond with a proper handler -- usually `view`. Here is an example of a layout rendering a Svelte component with its own data, distinct from that -of the route itself (`@primate/frontend` must be installed and loaded in the +of the route itself (`@primate/svelte` must be installed and loaded in the project). ```js caption=+layout.js -import { view } from "primate"; +import view from "primate/handler/view"; export default () => { return view("layout.svelte", { hello: "world" }); diff --git a/docs/guide/logging.md b/docs/guide/logging.md index 3bfeb5a4..6b285f0d 100644 --- a/docs/guide/logging.md +++ b/docs/guide/logging.md @@ -1,41 +1,22 @@ # Logging -Primate has three log levels, `Error`, `Warn` and `Info`. As a general rule, -an `Error` causes severe disruption to the application (and leads to bailout -during startup), `Warn` indicates degraded functionality in an otherwise -nominal system, and `Info` serves to give more information. In terms of -actionability, `Error` logs **must** be addressed, `Warn` logs **should** be -checked, and `Info` logs **may** be ignored. +Primate has three log levels, `error`, `warn` and `info`. As a general rule, +'error' means a severe disruption to the application (and leads to bailout +during startup), 'warn' indicates degraded functionality in an otherwise +nominal system, and 'info' serves to give more information. In terms of +actionability, 'error' logs **must** be addressed, 'warn' logs **should** be +checked, and 'info' logs **may** be ignored. ## Configuring -By default, the error level is to set to `Warn`, which logs all errors and +By default, the error level is to set to `warn`, which logs all errors and warnings. You can change this in your configuration. ```js primate.config.js -import { Logger } from "primate"; - export default { - logger: { + log: { // show all logs - level: Logger.Info, - }, -}; -``` - -Primate logs are implemented as JavaScript errors and thus carry a stack. If -you want the stack trace to be output in addition to Primate's own logging (in -the case of `Error` or `Warn` logs), set `trace` to `true`. - -```js primate.config.js -import { Logger } from "primate"; - -export default { - logger: { - // show all logs - level: Logger.Info, - // show stack traces for errors and warnings - trace: true, + level: "info", }, }; ``` @@ -44,7 +25,7 @@ When Primate shows errors or warnings, it will include a short reason and a quickfix, as well as link to the website for a longer explanation and fix. ```text -?? primate/store empty store directory +?? @primate/store empty store directory ++ populate /home/user/app/stores with stores -> https://primatejs.com/modules/store#empty-store-directory ``` @@ -100,7 +81,7 @@ error on the website. ## Error list -### Double File Extension +### Double Extension Level [`Error`][error] | [`Bailout`][bailout] @@ -142,15 +123,6 @@ must not use the same style for the same route. *Disambiguate the routes by consolidating them into one file of the path style of your choosing.* -### Empty Route File - -Level [`Warn`][warn] - -An empty route file exists, that is a route file without or with an empty -default export. - -*Add routes to the file or remove it.* - ### Empty Config File Level [`Warn`][warn] @@ -268,15 +240,6 @@ The mismatch happened in a `body`, `query`, `cookies` or `headers` field. *If unintentional, fix the type or the caller.* -### Module Has No Hooks - -Level [`Warn`][warn] - -Module loaded without hooks. - -*If this is a ad-hoc module, add hooks to it to make effective. If a -third-party module, contact the maintainer.* - ### Modules Has No Name Level [`Error`][error] | [`Bailout`][bailout] diff --git a/docs/guide/responses.md b/docs/guide/responses.md index 75996fc2..517bf772 100644 --- a/docs/guide/responses.md +++ b/docs/guide/responses.md @@ -30,7 +30,8 @@ them the string "Donald" in plain text. To use this handler explicitly, import and use the `text` function. ```js caption=routes/plain-text.js -import { text, Status } from "primate"; +import text from "primate/handler/text"; +import { UNPROCESSABLE_ENTITY, STATUS } from "@rcompat/http/status"; export default { post(request) { @@ -76,11 +77,11 @@ Instances of `ReadableStream` or `Blob` are streamed to the client with the content type `application/octet-stream`. ```js caption=routes/stream.js -import { File } from "rcompat/fs"; +import file from "@rcompat/fs/file"; export default { get() { - return File.stream("/tmp/users.json"); + return file("/tmp/users.json").stream(); }, }; ``` @@ -89,8 +90,8 @@ This route function handles GET requests to the path `/stream` by streaming them the contents of the file at `/tmp/users.json`. !!! -We used here the `File.stream` function from the `rcompat/fs` module, which -exposes a `ReadableStream`. +We used here the `FileRef#stream` function from the `@rcompat/fs` package, +which exposes a `ReadableStream`. !!! This handler can be also used explicitly with the `stream` import. @@ -101,8 +102,6 @@ Instances of `URL` redirect the client to the location they represent using the status code `302 Found`. ```js caption=routes/redirect.js -import { URL } from "primate"; - export default { get() { return new URL("https://primatejs.com"); @@ -123,7 +122,8 @@ easier to use the explicit `redirect` handler to redirect to paths within the same app. ```js caption=routes/redirect.js -import { redirect, Status } from "primate"; +import redirect from "primate/handler/redirect"; +import { MOVED_PERMANENTLY } from "@rcompat/http/status"; export default { get() { @@ -141,7 +141,7 @@ The `view` handler allows you to serve responses with content type `text/html` from the `components` directory. ```js caption=routes/view.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -161,14 +161,14 @@ exists, Primate will fall back to its [default index page][default-page]. In addition to the built-in support for HTML components, Primate features many [frontend frameworks][frontend] you can add by installing the -`@primate/frontend` module. +`@primate/frontend` package. ### Props You can pass props to your view handler. ```js caption=routes/view-props.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -196,7 +196,7 @@ To use a different HTML page to embed your component instead of [the default][default-page], pass in a differing `page` string property. ```js caption=routes/view-page.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -214,7 +214,7 @@ You may sometimes want to serve only the component HTML (without the encasing page). For that pass in the boolean property `partial` set to `true`. ```js caption=routes/view-partial.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -232,7 +232,7 @@ To replace any placeholders of the form `%placeholder%` in your rendered HTML, pass any an object property `placeholders`. ```js caption=routes/view-placeholders.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -253,7 +253,7 @@ The `error` handler allows you to generate an error (typically with a 4xx or `404 Not Found` using the content type `text/html`. ```js caption=routes/error.js -import { error } from "primate"; +import error from "primate/handler/error"; export default { get() { @@ -267,7 +267,8 @@ A request to `/error` will result in a `404 Not Found` response. You can customize the body and the status of this handler. ```js caption=routes/server-error.js -import { error, Status } from "primate"; +import error from "primate/handler/error"; +import { INTERNAL_SERVER_ERROR } from "@rcompat/http/status"; export default { get() { @@ -285,7 +286,7 @@ A request to `/server-error` will result in a `500` response with the HTML body You can upgrade any `GET` route to a WebSocket route with the `ws` handler. ```js caption=routes/ws.js -import { ws } from "primate"; +import ws from "primate/handler/ws"; export default { get(request) { @@ -353,7 +354,7 @@ 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"; +import sse from "primate/handler/sse"; const passed = start_time => Math.floor((Date.now() - start_time) / 1000); @@ -393,7 +394,7 @@ The client subscribes to this event and prints it to the console. This client is then served using another route. ```js caption=routes/sse-client.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -408,7 +409,7 @@ Lastly, for a custom response status, you can return a `Response` object from a route. ```js caption=routes/response.js -import { Response, Status } from "primate"; +import { CREATED } from "@rcompat/http/status"; export default { get() { diff --git a/docs/guide/routes.md b/docs/guide/routes.md index babb8763..6016b817 100644 --- a/docs/guide/routes.md +++ b/docs/guide/routes.md @@ -159,7 +159,7 @@ saying Hello and the provided name. The request's path, an object containing path parameters. ```js caption=routes/users/[user].js -import { error } from "primate"; +import error from "primate/handler/error"; const users = ["Donald", "Ryan"]; @@ -187,7 +187,7 @@ We will later handle [routes with parameters](#parameters) in depth. The request's query string, broken down into its constituent parts. ```js caption=routes/users.js -import { error } from "primate"; +import error from "primate/handler/error"; const users = ["Donald", "Ryan"]; @@ -212,7 +212,7 @@ will respond with `200`, otherwise with `404`. The request's `Cookie` header, broken down into individual cookies. ```js caption=routes/current-user.js -import { error } from "primate"; +import error from "primate/handler/error"; const users = ["Donald", "Ryan"]; @@ -238,7 +238,7 @@ If a user requests POST `/current-user` with the `Cookie` header set to The request's individual headers. ```js caption=routes/current-x-user.js -import { error } from "primate"; +import error from "primate/handler/error"; const users = ["Donald", "Ryan"]; diff --git a/docs/guide/types.md b/docs/guide/types.md index fab9edfd..f06f17ef 100644 --- a/docs/guide/types.md +++ b/docs/guide/types.md @@ -29,7 +29,8 @@ numeric and outputs its as a number. ```js caption=types/number.js // `is` asserts invariants, `numeric` returns true if a string is numeric -import { is, numeric } from "rcompat/invariant"; +import is from "@rcompat/invariant/is"; +import numeric from "@rcompat/invariant/numeric"; export default { base: "f64", @@ -63,7 +64,7 @@ JavaScript (like Go). You can also create more elaborate types, like `uuid`. ```js caption=types/uuid.js -import { is } from "rcompat/invariant"; +import is from "@rcompat/invariant/is"; const valid = /^[^\W_]{8}-[^\W_]{4}-[^\W_]{4}-[^\W_]{4}-[^\W_]{12}$/u; @@ -93,16 +94,14 @@ types. ## Using Primate types can be imported and used anywhere in your application to ensure -certain code invariants are met. In addition, many of Primate's built-in -features integrate seamlessly with the types you define. +certain code invariants are met. ### Path parameters In Primate's [filesystem-based routes](/guide/routes), path parameters may be -additionally specified with types to ensure the path adheres to a certain -format. +specified with types to ensure the path adheres to a certain format. -```js caption=routes/user/[userId=uuid].js +```js caption=routes/user/[user_id=uuid].js export default { /* GET /user/b8c5b7b2-4f4c-4939-81d8-d1bdadd888c5 @@ -112,8 +111,8 @@ export default { -> Error */ get(request) { - const userId = request.path.get("userId"); - return `User ID is ${userId}`; + const user_id = request.path.get("user_id"); + return `User ID is ${user_id}`; } } ``` @@ -121,142 +120,3 @@ export default { In the above example, using the `uuid` type we previously defined in `types`, we make sure the route function is only executed if the `GET` request is to a pathname starting with `user/` and followed by a valid UUID. - -### Request query - -Likewise, the request's query string parts, which we previously accessed using -`request.query.get`, may be typed to ensure adherence to a given format. This -can be achieved manually by importing the type. Here we'll also create an -additional `user` type coercing the ID into a user object, to get a proper user -object and not just the ID. - -```js caption=types/user.js -import number from "./number.js"; - -const users = [ - { - id: 6161 - name: "Donald", - }, -]; - -export default { - type: "object", - validate(id) { - // ids must be numbers - const n = number(id); - - const user = users.find(user => user.id === n); - if (user !== undefined) { - return user; - } - throw new Error(`no user with ID ${id}`); - }, -}; -``` - -We then use the type to assert the id is a user id and coerce it into a user, -returning the user object as JSON. - -```js caption=routes/user.js -import user from "../types/user.js"; - -export default { - /* - GET /user?userId=6161 - -> `{ id: 6161, name: "Donald" }` - - GET /user?userId=1616 - -> Error - */ - get(request) { - return user.validate(request.query.get("userId")); - } -} -``` - -This is generally OK, but as routes may be arbitrarily nested, it can make -importing from relative paths unseemly. For that, Primate enhances the `query` -object with dispatcher functions in the form `getX`, where `x` is the type name, -for validating a property's value. - -```js caption=routes/user.js -export default { - /* - GET /user?userId=6161 - -> `{ id: 6161, name: "Donald" }` - - GET /user?userId=1616 - -> Error - */ - get(request) { - // get the "userId" property from the request query, running it through the - // `user` type, returning the user object or throwing in case of a failure - return request.query.getUser("userId"); - } -} -``` - -Primate here adds any defined types as dispatcher `getX` functions of the -`request.query` object, in addition to the `get` function which allows direct -access to the query string parts. As there is no user in our dataset with the -ID `1616`, if a client tries to access `GET /user?userId=1616`, the route will -throw with the client redirected to an error page. - -### Headers and cookies - -In identical fashion to the request query, you can make sure certain headers -follow a given format, by retrieving them using the appropriate type getter. - -```js caption=routes/user.js -export default { - /* - GET /user - X-User-Id: 6161 - -> `{ id: 6161, name: "Donald" }` - - GET /user - X-User-Id: 1616 - -> Error - */ - get(request) { - return request.headers.getUser("X-User-Id"); - } -} -``` - -The same applies to cookies. - -```js caption=routes/user.js -export default { - /* - GET /user - Cookie: userId=6161 - -> `{ id: 6161, name: "Donald" }` - - GET /user - Cookie: userId=1616 - -> Error - */ - get(request) { - return request.cookies.getUser("userId"); - } -} -``` - -## Related modules - -Primate's ecosystem extends the concept of runtime types by providing many -defaults and integrating with database types. - -### Types - -Most of the time you won't need to define your own runtime types unless you -have very specific use cases. Primate's [Types module](/modules/types) comes -with a handful of common types. By importing and loading this module, its types -will be injected and available wherever types are used. - -### Store - -The Primate [Store module](/modules/store), used for data persistence, also -integrates with Primate's runtime type concept and extends upon it. diff --git a/docs/modules/angular.md b/docs/modules/angular.md index bb8dfc6c..21c3e7a5 100644 --- a/docs/modules/angular.md +++ b/docs/modules/angular.md @@ -5,14 +5,14 @@ This handler module supports SSR and serves Angular components with the ## Install -`npm install @primate/frontend @angular/{compiler,core,platform-browser,platform-server,ssr}@17` +`npm install @primate/angular` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { angular } from "@primate/frontend"; +import angular from "@primate/angular"; export default { modules: [ @@ -52,7 +52,7 @@ export default class PostIndex { Serve it from a route. ```js caption=routes/angular.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -87,4 +87,4 @@ Angular to development mode. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/angular diff --git a/docs/modules/binding.md b/docs/modules/backend.md similarity index 83% rename from docs/modules/binding.md rename to docs/modules/backend.md index 93a8b393..309351de 100644 --- a/docs/modules/binding.md +++ b/docs/modules/backend.md @@ -1,21 +1,19 @@ -# Bindings +# Backends -The binding module allows you to write backend logic (route files) in other -programming languages than JavaScript. Primate will then compile your -non-JavaScript routes to Wasm, and run them as WebAssembly. +Primate supports writing backend logic (route files) in other programming +languages than JavaScript. Non-JavaScript/TypeScript routes will be compiled to +Wasm and run as WebAssembly. -As a general rule, Primate endeavors to offer the same or a similar API in other -programming languages as concerns the request object that is passed to and -the handlers (`view`, `redirect`) that are available to JavaScript routes. +As a general rule, Primate endeavors to offer the same or a similar API in +other programming languages as concerns the request object that is passed to +the route and the available handlers (`view`, `redirect`). -The `@primate/binding` module currently supports TypeScript, Go, Python and -Ruby. +## Supported backends -## Install - -`npm install @primate/binding` - -The individual programming languages are available as individual exports. +* [Go](/modules/go) +* [Python](/modules/python) +* [Ruby](/modules/ruby) +* [TypeScript](/modules/typescript) ## Support matrix diff --git a/docs/modules/drivers.md b/docs/modules/drivers.md index 6243dd80..0b1d9d33 100644 --- a/docs/modules/drivers.md +++ b/docs/modules/drivers.md @@ -32,19 +32,20 @@ likely want to switch to persistent storage later on. ## JSON file The JSON file driver stores all data in JSON file on the filesystem. It accepts -a configuration object with the `filename` property to indicate in which file +a configuration object with the `database` property to indicate in which file the data will be managed. This file doesn't have to exist and will be created for you if it doesn't, but you must have permissions to write to it. ```js caption=primate.config.js -import { default as store, json } from "@primate/store"; +import store from "@primate/store"; +import json from "@primate/store/json"; export default { modules: [ store({ // use the JSON file driver, store at the data /tmp/data.json driver: json({ - filename: "/tmp/data.json", + database: "/tmp/data.json", }), }), ], @@ -53,24 +54,22 @@ export default { ## SQLite -`npm install better-sqlite3@9` - -The SQLite driver uses the `better-sqlite3` package for its underlying driver. -Install this package before you proceed. +`npm install @primate/sqlite` -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 -default to `":memory:"`, using SQLite in-memory, volatile database. +Similarly to the JSON file driver, the SQLite driver uses the `database` +property to indicate in what file to manage the data. If unset, it will default +to `":memory:"`, using SQLite in-memory, volatile database. ```js caption=primate.config.js -import { default as store, sqlite } from "@primate/store"; +import store from "@primate/store"; +import sqlite from "@primate/sqlite"; export default { modules: [ store({ // use the SQLite driver, store at the data /tmp/data.db driver: sqlite({ - filename: "/tmp/data.db", + database: "/tmp/data.db", }), }), ], @@ -79,18 +78,18 @@ export default { ## MongoDB -`npm install mongodb@6` +`npm install @primate/mongodb` -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. +The MongoDB module requires running a 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. +and `database` configuration properties. ```js caption=primate.config.js -import { default as store, mongodb } from "@primate/store"; +import store from "@primate/store"; +import mongodb from "@primate/mongodb"; export default { modules: [ @@ -101,7 +100,8 @@ export default { host: "localhost", // if 27017, can be omitted port: 27017, - db: "app", + // database to use + database: "app", }), }), ], @@ -110,31 +110,33 @@ export default { ## PostgreSQL -`npm install postgres@3` +`npm install @primate/postgresql` -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. +The PostgreSQL module requires running a 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. +`database`, `username`, and `password` configuration properties. ```js caption=primate.config.js -import { default as store, postgresql } from "@primate/store"; +import store from "@primate/store"; +import postgresql from "@primate/postgresql"; export default { modules: [ store({ // use the PostgreSQL server at localhost:5432 and the "app" database - driver: mongodb({ + driver: postgresql({ // if "localhost", can be omitted host: "localhost", // if 5432, can be omitted port: 5432, - db: "app", - user: "username", - pass: "password", + // database to use + database: "app", + // user credentials + username: "username", + password: "password", }), }), ], @@ -143,20 +145,20 @@ export default { ## MySQL -`npm install mysql2@3` +`npm install @primate/mysql` -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. +The MySQL module 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. +`database`, `username`, and `password` configuration properties. ### Configure ```js caption=primate.config.js -import { default as store, mysql } from "@primate/store"; +import store from "@primate/store"; +import mysql from "@primate/mysql"; export default { modules: [ @@ -167,9 +169,10 @@ export default { host: "localhost", // if 3306, can be omitted port: 3306, + // database to use database: "app", - user: "username", - // can be omitted + // user credentials + username: "username", password: "password", }), }), @@ -183,19 +186,19 @@ export default { This driver does not yet support automatic transaction rollback. !!! -`npm install surrealdb.js@0.11` +`npm install @primate/surrealdb` -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. +The SurrealDB module 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`"), `namespace`, `database`, `username`, and +`8000`), `path` (default: "`"rpc"`"), `namespace`, `database`, `username`, and `password` configuration properties. ```js caption=primate.config.js -import { default as store, surrealdb } from "@primate/store"; +import store from "@primate/store"; +import surrealdb from "@primate/surrealdb"; export default { modules: [ @@ -206,12 +209,14 @@ export default { // if "http://localhost", can be omitted host: "http://localhost", // if 8000, can be omitted - port: 800, + port: 8000, // if "rpc", can be omitted path: "rpc", // if "default", can be omitted, namespace: "default", + // database to use database: "app", + // credentials username: "username", password: "password", }), diff --git a/docs/modules/eta.md b/docs/modules/eta.md new file mode 100644 index 00000000..2528938e --- /dev/null +++ b/docs/modules/eta.md @@ -0,0 +1,67 @@ +# Eta + +This handler module serves Eta components with the `.eta` extension. + +## Install + +`npm install @primate/eta` + +## Configure + +Import and initialize the module in your configuration. + +```js caption=primate.config.js +import eta from "@primate/eta"; + +export default { + modules: [ + eta(), + ], +}; +``` + +## Use + +Create an Eta component in `components`. + +```html caption=components/post-index.eta +

All posts

+
+<% it.posts.forEach(function(post){ %> +

<%= post.title %>

+<% }) %> +
+``` + +Serve it from a route. + +```js caption=routes/eta.js +import view from "primate/handler/view"; + +const posts = [{ + id: 1, + title: "First post", +}]; + +export default { + get() { + return view("post-index.eta", { posts }); + }, +}; +``` + +The rendered component will be accessible at http://localhost:6161/eta. + +## Configuration options + +### extension + +Default `".eta"` + +The file extension associated with Eta components. + +## Resources + +* [Repository][repo] + +[repo]: https://github.com/primatejs/primate/tree/master/packages/eta diff --git a/docs/modules/frontend.md b/docs/modules/frontend.md index ddb41343..26659588 100644 --- a/docs/modules/frontend.md +++ b/docs/modules/frontend.md @@ -1,10 +1,10 @@ # Frontends Primate isn't tied to any specific frontend framework. The core platform comes -along with a [`view`][view] handler that serves `html` files from the -`components` directory. Additionally there are officially supported modules -that cover many frontend frameworks. When loaded, they extend the `view` -handler to support more file extensions. +along with a [`view`][view] handler that serves files from the `components` +directory. Additionally there are officially supported modules that 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 SPA support. In some cases, some capabilities have simply @@ -49,8 +49,7 @@ reloading the entire page, and also manages browsing the history. If you need to manipulate the `` part from within an individual component, use `` for Svelte. For React and Solid, you can use the -`Head` export of `@primate/frontend/react` and `@primate/frontend/solid` for -the same behavior. +`@primate/react/head` or `@primate/solid/head` export for the same behavior. ## Support matrix @@ -60,15 +59,17 @@ use different frontend frameworks alongside each other, in different routes. |Framework |Extension |Props|Layouts|SSR|Hydration|SPA|Head|I18N| |------------|---------------|-----|-------|---|---------|---|----|----| -|HTML |`.html` |✗ |✗ |✗ |✗ |✗ |✗ |✗ | |[Svelte] |`.svelte` |✓ |✓ |✓ |✓ |✓ |✓ |✓ | |[React] |`.jsx` |✓ |✓ |✓ |✓ |✓ |✓ |✓ | |[Solid] |`.jsx` |✓ |✓ |✓ |✓ |✓ |✓ |✓ | |[Vue] |`.vue` |✓ |✗ |✓ |✗ |✗ |✗ |✗ | |[Angular] |`.component.ts`|✓ |✗ |✓ |✗ |✗ |✗ |✗ | +|[HTML] |`.html` |✗ |✗ |✗ |✗ |✗ |✗ |✗ | |[HTMX] |`.htmx` |✓ |✗ |✗ |✗ |✗ |✗ |✗ | |[Handlebars]|`.hbs` |✓ |✗ |✓ |✓ |✗ |✗ |✗ | |[Marko] |`.marko` |✓ |✗ |✓ |✗ |✗ |✗ |✗ | +|[Eta] |`.eta` |✓ |✗ |✓ |✗ |✗ |✗ |✗ | +|[webc] |`.webc` |✓ |✗ |✗ |✗ |✗ |✗ |✗ | ## Error list @@ -91,9 +92,12 @@ dependency is missing and what command you need to issue to install it. [Solid]: /modules/solid [Vue]: /modules/vue [Angular]: /modules/angular +[HTML]: /modules/html [HTMX]: /modules/htmx [Handlebars]: /modules/handlebars [Marko]: /modules/marko +[Eta]: /modules/eta +[webc]: /modules/webc [Layouts]: /guide/layouts [I18N]: /modules/i18n [bailout]: /guide/logging#bailout diff --git a/docs/modules/go.md b/docs/modules/go.md index 159b387f..5dcf87dd 100644 --- a/docs/modules/go.md +++ b/docs/modules/go.md @@ -1,10 +1,10 @@ # Go -This binding introduces support for routes written in Golang. +This backend introduces support for routes written in Golang. ## Install -`npm install @primate/binding` +`npm install @primate/go` In addition, your system needs to have the `go` executable in its path, as it is used to compile the Go routes into Wasm. @@ -14,7 +14,7 @@ is used to compile the Go routes into Wasm. Import and initialize the module in your configuration. ```js caption=primate.config.js -import { go } from "@primate/binding"; +import go from "@primate/go"; export default { modules: [ @@ -207,7 +207,7 @@ In addition, any types defined in `types` will be available to a `Dispatcher`. Suppose you have defined the following type. ```js caption=types/uuid.js -import { is } from "rcompat/invariant"; +import is from "@rcompat/invariant/is"; const valid = /^[^\W_]{8}-[^\W_]{4}-[^\W_]{4}-[^\W_]{4}-[^\W_]{12}$/u; @@ -283,6 +283,10 @@ Default `".go"` The file extension associated with Go routes. +## Resources + +* [Repository][repo] + [plain text]: /guide/responses#plain-text [json]: /guide/responses#json [redirect]: /guide/responses#redirect @@ -291,4 +295,5 @@ The file extension associated with Go routes. [path]: /guide/routes#path [session]: /modules/session#use [default-index]: -https://github.com/primatejs/primate/blob/master/packages/primate/src/defaults/app.html +https://github.com/primatejs/primate/blob/master/packages/core/src/build/defaults/app.html +[repo]: https://github.com/primatejs/primate/tree/master/packages/go diff --git a/docs/modules/handlebars.md b/docs/modules/handlebars.md index 455d84bf..2669d6fa 100644 --- a/docs/modules/handlebars.md +++ b/docs/modules/handlebars.md @@ -4,14 +4,14 @@ This handler module serves Handlebars components with the `.hbs` extension. ## Install -`npm install @primate/frontend handlebars@4` +`npm install @primate/handlebars` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { handlebars } from "@primate/frontend"; +import handlebars from "@primate/handlebars"; export default { modules: [ @@ -36,7 +36,7 @@ Create a Handlebars component in `components`. Serve it from a route. ```js caption=routes/hbs.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -64,4 +64,4 @@ The file extension associated with Handlebars components. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/handlebars diff --git a/docs/modules/html.md b/docs/modules/html.md new file mode 100644 index 00000000..706a467b --- /dev/null +++ b/docs/modules/html.md @@ -0,0 +1,69 @@ +# HTML + +This handler module serves HTML components with the `.html` extension. + +## Install + +`npm install @primate/html` + +## Configure + +Import and initialize the module in your configuration. + +```js caption=primate.config.js +import html from "@primate/html"; + +export default { + modules: [ + html(), + ], +}; +``` + +## Use + +Create an HTML component in `components`. + +```html caption=components/post-index.html +

All posts

+${posts.map(post => ` +

+ + ${post.title} + +

+`).join("")} +``` + +Serve it from a route. + +```js caption=routes/html.js +import view from "primate/handler/view"; + +const posts = [{ + id: 1, + title: "First post", +}]; + +export default { + get() { + return view("post-index.html", { posts }); + }, +}; +``` + +The rendered component will be accessible at http://localhost:6161/html. + +## Configuration options + +### extension + +Default `".html"` + +The file extension associated with HTML components. + +## Resources + +* [Repository][repo] + +[repo]: https://github.com/primatejs/primate/tree/master/packages/html diff --git a/docs/modules/htmx.md b/docs/modules/htmx.md index a1d125cf..a0137b65 100644 --- a/docs/modules/htmx.md +++ b/docs/modules/htmx.md @@ -4,14 +4,14 @@ This handler module serves HTMX components with the `.htmx` extension. ## Install -`npm install @primate/frontend htmx-esm@0.2` +`npm install @primate/htmx` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { htmx } from "@primate/frontend"; +import htmx from "@primate/htmx"; export default { modules: [ @@ -38,7 +38,7 @@ ${posts.map(post => ` Serve it from a route. ```js caption=routes/htmx.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -87,7 +87,7 @@ add `"client-side-templates"` to the `extensions` array option. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/htmx [extensions]: https://htmx.org/extensions [client-side-templates]: https://htmx.org/extensions/client-side-templates [partial]: /guide/responses#partial diff --git a/docs/modules/i18n.md b/docs/modules/i18n.md index ca9e6ba3..c8a7923e 100644 --- a/docs/modules/i18n.md +++ b/docs/modules/i18n.md @@ -81,7 +81,8 @@ new locale. ```js caption=components/Home.svelte @@ -120,8 +121,10 @@ To create a language switcher, import `locale` and call `locale.set` with the new locale. ```jsx -import { default as t, locale } from "@primate/i18n/react"; -// import { default as t, locale } from "@primate/i18n/solid"; // for solid +import t from "@primate/i18n/react"; +// import t from "@primate/i18n/solid"; // for solid +import locale from "@primate/i18n/react/locale" +// import locale from "@primate/i18n/solid/locale"; // for solid export default function ({ username }) { return <> diff --git a/docs/modules/markdown.md b/docs/modules/markdown.md index f0e9de38..5e4534f7 100644 --- a/docs/modules/markdown.md +++ b/docs/modules/markdown.md @@ -4,14 +4,14 @@ This handler module serves Markdown components with the `.md` extension. ## Install -`npm install @primate/frontend marked@10` +`npm install @primate/markdown` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { markdown } from "@primate/frontend"; +import markdown from "@primate/markdown"; export default { modules: [ @@ -34,7 +34,7 @@ This is the **first** post Serve it from a route. ```js caption=routes/markdown.js -import { view } from "primate"; +import view from "primate/handler/view"; export default { get() { @@ -70,4 +70,4 @@ HTML. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/markdown diff --git a/docs/modules/marko.md b/docs/modules/marko.md index c72dd6e5..a5134c4c 100644 --- a/docs/modules/marko.md +++ b/docs/modules/marko.md @@ -5,14 +5,14 @@ extension. ## Install -`npm install @primate/frontend @marko/{compiler,translator-default}@5` +`npm install @primate/marko` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { marko } from "@primate/frontend"; +import marko from "@primate/marko"; export default { modules: [ @@ -39,7 +39,7 @@ Create a Marko component in `components`. Serve it from a route. ```js caption=routes/marko.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -67,4 +67,4 @@ The file extension associated with Marko components. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/marko diff --git a/docs/modules/native.md b/docs/modules/native.md new file mode 100644 index 00000000..7e88c297 --- /dev/null +++ b/docs/modules/native.md @@ -0,0 +1,66 @@ +# Native + +This module allows you to compile your Primate project as a desktop +application. + +!!! +Compilation is currently only supported using Bun. In the future, as runtimes +mature their compilation capabilities, we will add support for Node and Deno. +!!! + +!!! +Bun currently has issues compiling binaries that run properly on MacOS x64 +[under certain conditions][bun-compilation-issues]. +!!! + +## Install + +`npm install @primate/native` + +## Configure + +Import and initialize the module in your configuration. + +```js caption=primate.config.js +import native from "@primate/native"; + +export default { + modules: [ + native(), + ], +}; +``` + +By default, when the application is launched, it will access `/` (the route +under `routes/index.js`. Change that by setting the `start` property during +configuration. + +```js caption=primate.config.js +import native from "@primate/native"; + +export default { + modules: [ + native({ + start: "/home", + }), + ], +}; +``` + +## Compile + +To compile your project, make sure you have Bun installed, and then run + +`bun --bun x primate build desktop` + +## Cross-compile + +Choosing the `desktop` target will detect your current operating system and use +it as the compilation target. To cross-compile, specify the exact target. + +`bun --bun x primate build linux-x64` + +Currently available targets are `linux-x64`, `windows-x64`, `darwin-x64` and +`darwin-arm64`. + +[bun-running-issues]: https://github.com/oven-sh/bun/issues/11959 diff --git a/docs/modules/python.md b/docs/modules/python.md index 2eb749d1..e31e876b 100644 --- a/docs/modules/python.md +++ b/docs/modules/python.md @@ -1,17 +1,17 @@ # Python -This binding introduces support for routes written in Python. +This backend introduces support for routes written in Python, using Pyodide. ## Install -`npm install @primate/binding pyodide@0.25` +`npm install @primate/python` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { python } from "@primate/binding"; +import python from "@primate/python"; export default { modules: [ @@ -25,7 +25,7 @@ standard library) you'd like to use to the `packages` configuration array of this module. ```js caption=primate.config.js -import { python } from "@primate/binding"; +import python from "@primate/python"; export default { modules: [ @@ -206,7 +206,7 @@ function to access individual properties. In addition, any types defined in Suppose you have defined the following type. ```js caption=types/uuid.js -import { is } from "rcompat/invariant"; +import is from "@rcompat/invariant/is"; const valid = /^[^\W_]{8}-[^\W_]{4}-[^\W_]{4}-[^\W_]{4}-[^\W_]{12}$/u; @@ -279,6 +279,10 @@ Default `[]` A list of package names to be loaded, in addition to the Python standard library. +## Resources + +* [Repository][repo] + [plain text]: /guide/responses#plain-text [json]: /guide/responses#json [redirect]: /guide/responses#redirect @@ -289,4 +293,5 @@ library. [session]: /modules/session#use [session]: /modules/store [default-index]: -https://github.com/primatejs/primate/blob/master/packages/primate/src/defaults/app.html +https://github.com/primatejs/primate/blob/master/packages/core/src/build/defaults/app.html +[repo]: https://github.com/primatejs/primate/tree/master/packages/python diff --git a/docs/modules/react.md b/docs/modules/react.md index a7b5a1a7..50d8b96f 100644 --- a/docs/modules/react.md +++ b/docs/modules/react.md @@ -5,14 +5,14 @@ components with the `.jsx` extension. ## Install -`npm install @primate/frontend react@18 react-dom@18` +`npm install @primate/react` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { react } from "@primate/frontend"; +import react from "@primate/react"; export default { modules: [ @@ -39,7 +39,7 @@ export default function PostIndex({ posts }) { Serve it from a route. ```js caption=routes/react.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -60,7 +60,7 @@ The rendered component will be accessible at http://localhost:6161/react. To use TSX instead of JSX files, change this handler's extension to `.tsx`. ```js caption=primate.config.js -import { react } from "@primate/frontend"; +import react from "@primate/react"; export default { modules: [ @@ -87,4 +87,4 @@ Whether SPA browsing using `fetch` should be active. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/react diff --git a/docs/modules/ruby.md b/docs/modules/ruby.md index ac3c986d..b0d4f0de 100644 --- a/docs/modules/ruby.md +++ b/docs/modules/ruby.md @@ -1,17 +1,17 @@ # Ruby -This binding introduces support for routes written in Ruby. +This backend introduces support for routes written in Ruby. ## Install -`npm install @primate/binding @ruby/head-wasm-wasi@2.5 @ruby/wasm-wasi@2.5` +`npm install @primate/ruby` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { ruby } from "@primate/binding"; +import ruby from "@primate/ruby"; export default { modules: [ @@ -198,7 +198,7 @@ function to access individual properties. In addition, any types defined in Suppose you have defined the following type. ```js caption=types/uuid.js -import { is } from "rcompat/invariant"; +import is from "@rcompat/invariant/is"; const valid = /^[^\W_]{8}-[^\W_]{4}-[^\W_]{4}-[^\W_]{4}-[^\W_]{12}$/u; @@ -251,6 +251,10 @@ Default `".rb"` The file extension associated with Ruby routes. +## Resources + +* [Repository][repo] + [plain text]: /guide/responses#plain-text [json]: /guide/responses#json [redirect]: /guide/responses#redirect @@ -259,4 +263,5 @@ The file extension associated with Ruby routes. [body]: /guide/routes#body [path]: /guide/routes#path [default-index]: -https://github.com/primatejs/primate/blob/master/packages/primate/src/defaults/app.html +https://github.com/primatejs/primate/blob/master/packages/core/src/build/defaults/app.html +[repo]: https://github.com/primatejs/primate/tree/master/packages/ruby diff --git a/docs/modules/solid.md b/docs/modules/solid.md index 44721b52..e58bda95 100644 --- a/docs/modules/solid.md +++ b/docs/modules/solid.md @@ -5,14 +5,14 @@ components with the `.jsx` extension. ## Install -`npm install @primate/frontend @babel/core@7 babel-preset-solid@1 solid-js@1` +`npm install @primate/solid` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { solid } from "@primate/frontend"; +import solid from "@primate/solid"; export default { modules: [ @@ -21,11 +21,11 @@ export default { }; ``` -If you are using another JSX frontend module alongside Solid, consider changing -the file extension for Solid to something else, to avoid conflicts. +If you're using another JSX frontend framework alongside Solid, consider +changing the file extension for Solid to something else, to avoid conflicts. ```js caption=primate.config.js -import { solid } from "@primate/frontend"; +import solid from "@primate/solid"; export default { modules: [ @@ -57,7 +57,7 @@ export default function PostIndex(props) { Serve it from a route. ```js caption=routes/solid.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -91,4 +91,4 @@ Whether SPA browsing using `fetch` should be active. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/solid diff --git a/docs/modules/store.md b/docs/modules/store.md index b64f6073..e112c1b6 100644 --- a/docs/modules/store.md +++ b/docs/modules/store.md @@ -47,13 +47,14 @@ data only as long as the application runs. Alternatively you can use the [JSON file][json-file] driver which persists onto a file. ```js caption=primate.config.js -import { default as store, json } from "@primate/store"; +import store from "@primate/store"; +import json from "@primate/store/json"; export default { modules: [ store({ driver: json({ - filename: "/tmp/db.json", + database: "/tmp/db.json", }), }), ], @@ -68,7 +69,11 @@ range of values this field may hold. We here define a `User` store representing a user of our application. ```js caption=stores/User.js -import { primary, string, u8, email, date } from "primate/@types"; +import primary from "@primate/types/primary"; +import email from "@primate/types/email"; +import date from "@primate/types/date"; +import string from "@primate/types/string"; +import u8 from "@primate/types/u8"; export default { id: primary, @@ -105,7 +110,8 @@ Directories must start with a lowercase letter and will be otherwise ignored. ```js caption=stores/Comment.js // this store will be available as `request.store.Comment` in routes -import { primary, string } from "primate/@types"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; export default { id: primary, @@ -115,7 +121,8 @@ export default { ```js caption=stores/post/Comment.js // this store will be available as `request.store.post.Comment` in routes -import { primary, string } from "primate/@types"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; export default { id: primary, @@ -130,10 +137,13 @@ The objects you use for types will be automatically mapped by the driver into the appropriate database types. ```js caption=store/User.js -import { id, string, u8, array } from "primate/@types"; +import array from "@primate/types/array"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; +import u8 from "@primate/types/u8"; export default { - id, + id: primary, name: string.between(0, 20), age: u8.range(0, 120), hobbies: array.of(string), @@ -157,7 +167,7 @@ saving it. Normally though, you wouldn't call `validate` directly but have `insert` or `update` call it for you. ```js caption=routes/create-user.js -import { redirect } from "primate"; +import redirect from "primate/handler/redirect"; export default { post(request) { @@ -194,7 +204,9 @@ In addition to using type functions, Primate supports using an object with a `validate` function property for validation. ```js caption=stores/User.js -import { primary, u8, array } from "primate/@types" +import array from "@primate/types/array"; +import primary from "@primate/types/primary"; +import u8 from "@primate/types/u8"; const between = ({ length }, min, max) => length >= min && length <= max; @@ -227,7 +239,8 @@ to save a new record into the store. If you wish to strictly enforce all fields to be non-empty, export `mode = "strict"`. ```js caption=stores/Comment.js -import { primary, string } from "primate/@types"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; export const mode = "strict"; @@ -255,7 +268,8 @@ In that case, you can opt-out on individual store level by exporting `mode = "loose"`. ```js caption=stores/Comment.js -import { primary, string } from "@primate/types"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; export const mode = "loose"; @@ -280,7 +294,8 @@ this behavior by exporting a `name`, allowing you to map several store files to the same database store. ```js caption=stores/Post/Comment.js -import { primary, string } from "@primate/types"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; // would use `post_comment` if not overriden export const name = "comment"; @@ -298,8 +313,9 @@ module (which defaults to the in-memory driver). A store can override this default by exporting a `driver`. ```js caption=stores/Comment.js -import { primary, string } from "@primate/types"; -import { mongodb } from "@primate/store"; +import mongodb from "@primate/mongodb"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; export const driver = mongodb(); @@ -314,7 +330,7 @@ recommend initializing it in a separate file (lowercase-first files in the `stores` directory are ignored by Primate). ```js caption=stores/mongodb.js -import { mongodb } from "@primate/store"; +import mongodb from "@primate/mongodb"; export default mongodb(); ``` @@ -322,7 +338,8 @@ export default mongodb(); You can then import and reexport the driver as needed across files. ```js caption=stores/Post.js -import { primary, string } from "@primate/types"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; export { default as driver } from "./mongodb.js"; @@ -334,7 +351,8 @@ export default { ``` ```js caption=stores/Comment.js -import { primary, string } from "@primate/types"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; export { default as driver } from "./mongodb.js"; @@ -352,7 +370,7 @@ indexing. This module, too, uses the primary field automatically for a store's complain. ```js caption=stores/Comment.js -import { string } from "@primate/types"; +import string from "@primate/types/string"; export default { text: string, @@ -366,7 +384,7 @@ warning. If this ambiguity is intentional, export `ambiguous = true` in your store. ```js caption=stores/Comment.js -import { string } from "@primate/types"; +import string from "@primate/types/string"; export const ambiguous = true; @@ -388,7 +406,10 @@ underlying driver and the store itself to create your own actions. To do so, export `actions` as an object containing individual, additional actions. ```js caption=store/User.js -import { id, string, u8, array } from "primate/@types"; +import array from "@primate/types/array"; +import primary from "@primate/types/primary"; +import string from "@primate/types/string"; +import u8 from "@primate/types/u8"; export const actions = store => { return { @@ -399,7 +420,7 @@ export const actions = store => { }; export default { - id, + id: primary, name: string.between(0, 20), age: u8.range(0, 120), hobbies: array.of(string), diff --git a/docs/modules/svelte.md b/docs/modules/svelte.md index 276a52c3..8f9934b1 100644 --- a/docs/modules/svelte.md +++ b/docs/modules/svelte.md @@ -5,14 +5,14 @@ with the `.svelte` extension. ## Install -`npm install @primate/frontend svelte@4` +`npm install @primate/svelte` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { svelte } from "@primate/frontend"; +import svelte from "@primate/svelte"; export default { modules: [ @@ -47,7 +47,7 @@ Create a Svelte component in `components`. Serve it from a route. ```js caption=routes/svelte.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -81,4 +81,4 @@ Whether SPA browsing using `fetch` should be active. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/svelte diff --git a/docs/modules/types.md b/docs/modules/types.md index ab46a42a..2992feea 100644 --- a/docs/modules/types.md +++ b/docs/modules/types.md @@ -1,40 +1,14 @@ # Types -This module adds common runtime types to your application. +This module includes common runtime types for your application. ## Install `npm install @primate/types` -## Configure - -Import and initialize the module in your configuration. - -```js caption=primate.config.js -import types from "@primate/types"; - -export default { - modules: [ - types(), - ], -}; -``` - ## Use -Similar to the types you define in `types`, the types included in this module -will be injected into the `body`, `path`, `query`, `cookies` and `headers` -properties of a request, as `getX` function, where `x` is the type's name. In -addition, all the types are exported for use in stores. - -## Configuration options - -### directory - -Default `"types"` - -The directory where types are located. If specified as a relative path, will -be relative to project root. +You normally import this module's types in your stores for use. ## Resources diff --git a/docs/modules/typescript.md b/docs/modules/typescript.md index 16284cff..9f2b1dd6 100644 --- a/docs/modules/typescript.md +++ b/docs/modules/typescript.md @@ -1,17 +1,17 @@ # TypeScript -This binding introduces support for routes written in TypeScript. +This backend introduces support for routes written in TypeScript. ## Install -`npm install @primate/binding` +`npm install @primate/typescript` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { typescript } from "@primate/binding"; +import typescript from "@primate/typescript"; export default { modules: [ @@ -27,7 +27,7 @@ get proper editor completions for your route function parameters and return code, your route needs to use `satisfies Route` with the Primate `Route` export. ```ts caption=routes/plain-text.ts -import { Route } from "primate"; +import type { Route } from "primate"; export default { get() { @@ -52,5 +52,10 @@ Default `".ts"` The file extension associated with TypeScript routes. +## Resources + +* [Repository][repo] + [routes]: /guide/routes [responses]: /guide/responses +[repo]: https://github.com/primatejs/primate/tree/master/packages/typescript diff --git a/docs/modules/voby.md b/docs/modules/voby.md new file mode 100644 index 00000000..dd697d31 --- /dev/null +++ b/docs/modules/voby.md @@ -0,0 +1,67 @@ +# Voby + +This handler module serves Voby components with the `.voby` extension. + +## Install + +`npm install @primate/voby` + +## Configure + +Import and initialize the module in your configuration. + +```js caption=primate.config.js +import voby from "@primate/voby"; + +export default { + modules: [ + voby(), + ], +}; +``` + +## Use + +Create a Voby component in `components`. + +```html caption=components/PostIndex.voby +export default ({ posts, title }) => { + return <> +

All posts

+ {posts.map(({ id, title}) =>

{title}

)} + ; +} +``` + +Serve it from a route. + +```js caption=routes/voby.js +import view from "primate/handler/view"; + +const posts = [{ + id: 1, + title: "First post", +}]; + +export default { + get() { + return view("PostIndex.voby", { posts }); + }, +}; +``` + +The rendered component will be accessible at http://localhost:6161/voby. + +## Configuration options + +### extension + +Default `".voby"` + +The file extension associated with Voby components. + +## Resources + +* [Repository][repo] + +[repo]: https://github.com/primatejs/primate/tree/master/packages/voby diff --git a/docs/modules/vue.md b/docs/modules/vue.md index 8a8b5d84..33c028a5 100644 --- a/docs/modules/vue.md +++ b/docs/modules/vue.md @@ -5,14 +5,14 @@ extension. ## Install -`npm install @primate/frontend vue@3` +`npm install @primate/vue` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { vue } from "@primate/frontend"; +import vue from "@primate/vue"; export default { modules: [ @@ -37,7 +37,7 @@ Create a SFC component in `components`. Serve it from a route. ```js caption=routes/vue.js -import { view } from "primate"; +import view from "primate/handler/view"; const posts = [{ id: 1, @@ -65,4 +65,4 @@ The file extension associated with Vue SFC components. * [Repository][repo] -[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend +[repo]: https://github.com/primatejs/primate/tree/master/packages/vue diff --git a/docs/modules/web-components.md b/docs/modules/web-components.md index f9ee1c6f..142a2665 100644 --- a/docs/modules/web-components.md +++ b/docs/modules/web-components.md @@ -4,14 +4,14 @@ This handler module serves web components with the `.webc` extension. ## Install -`npm install @primate/frontend` +`npm install @primate/webc` ## Configure Import and initialize the module in your configuration. ```js caption=primate.config.js -import { webc } from "@primate/frontend"; +import webc from "@primate/webc"; export default { modules: [ @@ -26,7 +26,7 @@ Create an web component in `components`. ```html caption=components/post-index.webc